diff --git a/.eslintrc.json b/.eslintrc.json index 69603b245d842..862e700ae1f06 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,11 +3,12 @@ "es6": true, "node": true }, - "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "google"], + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"], "parser": "@typescript-eslint/parser", "plugins": [ "@typescript-eslint", - "eslint-plugin-tsdoc" + "eslint-plugin-tsdoc", + "perfectionist" ], "root": true, "parserOptions": { @@ -15,15 +16,50 @@ }, "rules": { "require-jsdoc": "off", - "indent": ["error", 4], "no-prototype-builtins": "off", - "max-len": ["error", { "code": 150 }], "@typescript-eslint/no-empty-function": "off", "@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/no-unused-vars": "off", "@typescript-eslint/no-floating-promises": "error", "tsdoc/syntax": "warn", - "valid-jsdoc": "off" + "valid-jsdoc": "off", + "perfectionist/sort-imports": [ + "error", + { + "groups": [ + "type", + [ + "builtin", + "external" + ], + "internal-type", + "internal", + [ + "parent-type", + "sibling-type", + "index-type" + ], + [ + "parent", + "sibling", + "index" + ], + "object", + "unknown" + ], + "custom-groups": { + "value": {}, + "type": {} + }, + "newlines-between": "always", + "internal-pattern": [ + "~/**" + ], + "type": "natural", + "order": "asc", + "ignore-case": false + } + ] }, "overrides": [ { diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b0278350b2bec..a179bc444fef0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,10 +20,12 @@ jobs: run: npm ci - name: Build run: npm run build + - name: Lint + run: | + npm run pretty:check + npm run eslint - name: Test run: npm test - - name: Lint - run: npm run lint - name: Publish new release if: startsWith(github.ref, 'refs/tags/') && github.event_name == 'push' run: | diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000000..0267e1af9c7ef --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "semi": true, + "trailingComma": "all", + "singleQuote": true, + "printWidth": 150, + "bracketSpacing": false, + "endOfLine": "lf", + "tabWidth": 4 +} diff --git a/README.md b/README.md index 54828a1023212..b19364f4f0ead 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,8 @@ See [Zigbee2MQTT how to support new devices](https://www.zigbee2mqtt.io/advanced If you'd like to submit a pull request, you should run the following commands to ensure your changes will pass the tests: ```sh npm install -npm run lint +npm run eslint -- --fix +npm run pretty:write npm run build npm test ``` diff --git a/package-lock.json b/package-lock.json index a6721aecbbde6..94fb6500b85e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,11 +26,13 @@ "@typescript-eslint/eslint-plugin": "^7.13.1", "@typescript-eslint/parser": "^7.13.1", "eslint": "^8.57.0", - "eslint-config-google": "^0.14.0", + "eslint-config-prettier": "^9.1.0", "eslint-plugin-jest": "^28.6.0", + "eslint-plugin-perfectionist": "^2.11.0", "eslint-plugin-tsdoc": "^0.3.0", "fast-deep-equal": "*", "jest": "^29.7.0", + "prettier": "^3.3.2", "rimraf": "^5.0.7", "ts-jest": "^29.1.5", "ts-morph": "^22.0.0", @@ -2668,16 +2670,16 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-config-google": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/eslint-config-google/-/eslint-config-google-0.14.0.tgz", - "integrity": "sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==", + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, - "engines": { - "node": ">=0.10.0" + "bin": { + "eslint-config-prettier": "bin/cli.js" }, "peerDependencies": { - "eslint": ">=5.16.0" + "eslint": ">=7.0.0" } }, "node_modules/eslint-plugin-jest": { @@ -2705,6 +2707,38 @@ } } }, + "node_modules/eslint-plugin-perfectionist": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-2.11.0.tgz", + "integrity": "sha512-XrtBtiu5rbQv88gl+1e2RQud9te9luYNvKIgM9emttQ2zutHPzY/AQUucwxscDKV4qlTkvLTxjOFvxqeDpPorw==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^6.13.0 || ^7.0.0", + "minimatch": "^9.0.3", + "natural-compare-lite": "^1.4.0" + }, + "peerDependencies": { + "astro-eslint-parser": "^1.0.2", + "eslint": ">=8.0.0", + "svelte": ">=3.0.0", + "svelte-eslint-parser": "^0.37.0", + "vue-eslint-parser": ">=9.0.0" + }, + "peerDependenciesMeta": { + "astro-eslint-parser": { + "optional": true + }, + "svelte": { + "optional": true + }, + "svelte-eslint-parser": { + "optional": true + }, + "vue-eslint-parser": { + "optional": true + } + } + }, "node_modules/eslint-plugin-tsdoc": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/eslint-plugin-tsdoc/-/eslint-plugin-tsdoc-0.3.0.tgz", @@ -4443,6 +4477,12 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "node_modules/node-addon-api": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz", @@ -4788,6 +4828,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", + "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", diff --git a/package.json b/package.json index d31c37068b465..b22c77cffbf8a 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,9 @@ "zigbee-shepherd" ], "scripts": { - "lint": "eslint .", + "eslint": "eslint src/ --max-warnings=0", + "pretty:write": "prettier --write src test", + "pretty:check": "prettier --check src test", "test": "ZHC_TEST=true jest test --silent --maxWorkers=50%", "test-watch": "ZHC_TEST=true jest test --silent --watch --maxWorkers=25%", "clean": "rimraf --glob index* devices lib converters tsconfig.tsbuildinfo", @@ -59,11 +61,13 @@ "@typescript-eslint/eslint-plugin": "^7.13.1", "@typescript-eslint/parser": "^7.13.1", "eslint": "^8.57.0", - "eslint-config-google": "^0.14.0", + "eslint-config-prettier": "^9.1.0", "eslint-plugin-jest": "^28.6.0", + "eslint-plugin-perfectionist": "^2.11.0", "eslint-plugin-tsdoc": "^0.3.0", "fast-deep-equal": "*", "jest": "^29.7.0", + "prettier": "^3.3.2", "rimraf": "^5.0.7", "ts-jest": "^29.1.5", "ts-morph": "^22.0.0", diff --git a/src/converters/fromZigbee.ts b/src/converters/fromZigbee.ts index c57c21af43cee..da0f359cdeae5 100644 --- a/src/converters/fromZigbee.ts +++ b/src/converters/fromZigbee.ts @@ -1,14 +1,22 @@ -import { - precisionRound, mapNumberRange, isLegacyEnabled, toLocalISOString, numberWithinRange, hasAlreadyProcessedMessage, - addActionGroup, postfixWithEndpointName, getKey, batteryVoltageToPercentage, -} from '../lib/utils'; -import {Fz, KeyValue, KeyValueAny, KeyValueNumberString} from '../lib/types'; -import * as globalStore from '../lib/store'; -import * as constants from '../lib/constants'; import * as libColor from '../lib/color'; -import * as utils from '../lib/utils'; +import * as constants from '../lib/constants'; import * as exposes from '../lib/exposes'; import {logger} from '../lib/logger'; +import * as globalStore from '../lib/store'; +import {Fz, KeyValue, KeyValueAny, KeyValueNumberString} from '../lib/types'; +import { + precisionRound, + mapNumberRange, + isLegacyEnabled, + toLocalISOString, + numberWithinRange, + hasAlreadyProcessedMessage, + addActionGroup, + postfixWithEndpointName, + getKey, + batteryVoltageToPercentage, +} from '../lib/utils'; +import * as utils from '../lib/utils'; const NS = 'zhc:fz'; const defaultSimulatedBrightness = 255; @@ -50,7 +58,7 @@ const converters1 = { } } if (msg.data.hasOwnProperty('occupancy')) { - result[postfixWithEndpointName('occupancy', msg, model, meta)] = (msg.data.occupancy % 2) > 0; + result[postfixWithEndpointName('occupancy', msg, model, meta)] = msg.data.occupancy % 2 > 0; } if (msg.data.hasOwnProperty('occupiedHeatingSetpoint')) { const value = precisionRound(msg.data['occupiedHeatingSetpoint'], 2) / 100; @@ -87,9 +95,9 @@ const converters1 = { if (msg.data.hasOwnProperty('remoteSensing')) { const value = msg.data['remoteSensing']; result[postfixWithEndpointName('remote_sensing', msg, model, meta)] = { - local_temperature: ((value & 1) > 0) ? 'remotely' : 'internally', - outdoor_temperature: ((value & 1<<1) > 0) ? 'remotely' : 'internally', - occupancy: ((value & 1<<2) > 0) ? 'remotely' : 'internally', + local_temperature: (value & 1) > 0 ? 'remotely' : 'internally', + outdoor_temperature: (value & (1 << 1)) > 0 ? 'remotely' : 'internally', + occupancy: (value & (1 << 2)) > 0 ? 'remotely' : 'internally', }; } if (msg.data.hasOwnProperty('ctrlSeqeOfOper')) { @@ -104,28 +112,35 @@ const converters1 = { result[postfixWithEndpointName('system_mode', msg, model, meta)] = constants.thermostatSystemModes[msg.data['systemMode']]; } if (msg.data.hasOwnProperty('runningMode')) { - result[postfixWithEndpointName('running_mode', msg, model, meta)] = - constants.thermostatRunningMode[msg.data['runningMode']]; + result[postfixWithEndpointName('running_mode', msg, model, meta)] = constants.thermostatRunningMode[msg.data['runningMode']]; } if (msg.data.hasOwnProperty('runningState')) { - result[postfixWithEndpointName('running_state', msg, model, meta)] = - constants.thermostatRunningStates[msg.data['runningState']]; + result[postfixWithEndpointName('running_state', msg, model, meta)] = constants.thermostatRunningStates[msg.data['runningState']]; } if (msg.data.hasOwnProperty('pIHeatingDemand')) { - result[postfixWithEndpointName('pi_heating_demand', msg, model, meta)] = - mapNumberRange(msg.data['pIHeatingDemand'], 0, (dontMapPIHeatingDemand ? 100: 255), 0, 100); + result[postfixWithEndpointName('pi_heating_demand', msg, model, meta)] = mapNumberRange( + msg.data['pIHeatingDemand'], + 0, + dontMapPIHeatingDemand ? 100 : 255, + 0, + 100, + ); } if (msg.data.hasOwnProperty('pICoolingDemand')) { // we assume the behavior is consistent for pIHeatingDemand + pICoolingDemand for the same vendor - result[postfixWithEndpointName('pi_cooling_demand', msg, model, meta)] = - mapNumberRange(msg.data['pICoolingDemand'], 0, (dontMapPIHeatingDemand ? 100: 255), 0, 100); + result[postfixWithEndpointName('pi_cooling_demand', msg, model, meta)] = mapNumberRange( + msg.data['pICoolingDemand'], + 0, + dontMapPIHeatingDemand ? 100 : 255, + 0, + 100, + ); } if (msg.data.hasOwnProperty('tempSetpointHold')) { result[postfixWithEndpointName('temperature_setpoint_hold', msg, model, meta)] = msg.data['tempSetpointHold'] == 1; } if (msg.data.hasOwnProperty('tempSetpointHoldDuration')) { - result[postfixWithEndpointName('temperature_setpoint_hold_duration', msg, model, meta)] = - msg.data['tempSetpointHoldDuration']; + result[postfixWithEndpointName('temperature_setpoint_hold_duration', msg, model, meta)] = msg.data['tempSetpointHoldDuration']; } if (msg.data.hasOwnProperty('minHeatSetpointLimit')) { const value = precisionRound(msg.data['minHeatSetpointLimit'], 2) / 100; @@ -171,7 +186,7 @@ const converters1 = { } if (msg.data.hasOwnProperty('acLouverPosition')) { result[postfixWithEndpointName('ac_louver_position', msg, model, meta)] = - constants.thermostatAcLouverPositions[msg.data['acLouverPosition']]; + constants.thermostatAcLouverPositions[msg.data['acLouverPosition']]; } return result; }, @@ -182,7 +197,7 @@ const converters1 = { convert: (model, msg, publish, options, meta) => { const days = []; for (let i = 0; i < 8; i++) { - if ((msg.data['dayofweek'] & 1< 0) { + if ((msg.data['dayofweek'] & (1 << i)) > 0) { days.push(constants.thermostatDayOfWeek[i]); } } @@ -208,12 +223,14 @@ const converters1 = { convert: (model, msg, publish, options, meta) => { const result: KeyValueAny = {}; if (msg.data.hasOwnProperty('keypadLockout')) { - result.keypad_lockout = constants.keypadLockoutMode.hasOwnProperty(msg.data['keypadLockout']) ? - constants.keypadLockoutMode[msg.data['keypadLockout']] : msg.data['keypadLockout']; + result.keypad_lockout = constants.keypadLockoutMode.hasOwnProperty(msg.data['keypadLockout']) + ? constants.keypadLockoutMode[msg.data['keypadLockout']] + : msg.data['keypadLockout']; } if (msg.data.hasOwnProperty('tempDisplayMode')) { - result.temperature_display_mode = constants.temperatureDisplayMode.hasOwnProperty(msg.data['tempDisplayMode']) ? - constants.temperatureDisplayMode[msg.data['tempDisplayMode']] : msg.data['tempDisplayMode']; + result.temperature_display_mode = constants.temperatureDisplayMode.hasOwnProperty(msg.data['tempDisplayMode']) + ? constants.temperatureDisplayMode[msg.data['tempDisplayMode']] + : msg.data['tempDisplayMode']; } return result; }, @@ -291,7 +308,13 @@ const converters1 = { if (msg.data.hasOwnProperty('doorState')) { const lookup: KeyValueAny = { - 0: 'open', 1: 'closed', 2: 'error_jammed', 3: 'error_forced_open', 4: 'error_unspecified', 0xff: 'undefined'}; + 0: 'open', + 1: 'closed', + 2: 'error_jammed', + 3: 'error_forced_open', + 4: 'error_unspecified', + 0xff: 'undefined', + }; result.door_state = lookup[msg.data['doorState']]; } return result; @@ -351,7 +374,7 @@ const converters1 = { type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { const payload: KeyValueAny = {}; - if (msg.data.hasOwnProperty('batteryPercentageRemaining') && (msg.data['batteryPercentageRemaining'] < 255)) { + if (msg.data.hasOwnProperty('batteryPercentageRemaining') && msg.data['batteryPercentageRemaining'] < 255) { // Some devices do not comply to the ZCL and report a // batteryPercentageRemaining of 100 when the battery is full (should be 200). const dontDividePercentage = model.meta && model.meta.battery && model.meta.battery.dontDividePercentage; @@ -360,7 +383,7 @@ const converters1 = { payload.battery = precisionRound(percentage, 2); } - if (msg.data.hasOwnProperty('batteryVoltage') && (msg.data['batteryVoltage'] < 255)) { + if (msg.data.hasOwnProperty('batteryVoltage') && msg.data['batteryVoltage'] < 255) { // Deprecated: voltage is = mV now but should be V payload.voltage = msg.data['batteryVoltage'] * 100; @@ -370,24 +393,21 @@ const converters1 = { } if (msg.data.hasOwnProperty('batteryAlarmState')) { - const battery1Low = ( - msg.data.batteryAlarmState & 1<<0 || - msg.data.batteryAlarmState & 1<<1 || - msg.data.batteryAlarmState & 1<<2 || - msg.data.batteryAlarmState & 1<<3 - ) > 0; - const battery2Low = ( - msg.data.batteryAlarmState & 1<<10 || - msg.data.batteryAlarmState & 1<<11 || - msg.data.batteryAlarmState & 1<<12 || - msg.data.batteryAlarmState & 1<<13 - ) > 0; - const battery3Low = ( - msg.data.batteryAlarmState & 1<<20 || - msg.data.batteryAlarmState & 1<<21 || - msg.data.batteryAlarmState & 1<<22 || - msg.data.batteryAlarmState & 1<<23 - ) > 0; + const battery1Low = + (msg.data.batteryAlarmState & (1 << 0) || + msg.data.batteryAlarmState & (1 << 1) || + msg.data.batteryAlarmState & (1 << 2) || + msg.data.batteryAlarmState & (1 << 3)) > 0; + const battery2Low = + (msg.data.batteryAlarmState & (1 << 10) || + msg.data.batteryAlarmState & (1 << 11) || + msg.data.batteryAlarmState & (1 << 12) || + msg.data.batteryAlarmState & (1 << 13)) > 0; + const battery3Low = + (msg.data.batteryAlarmState & (1 << 20) || + msg.data.batteryAlarmState & (1 << 21) || + msg.data.batteryAlarmState & (1 << 22) || + msg.data.batteryAlarmState & (1 << 23)) > 0; payload.battery_low = battery1Low || battery2Low || battery3Low; } @@ -449,7 +469,7 @@ const converters1 = { return {[property]: flow}; } }, - }satisfies Fz.Converter, + } satisfies Fz.Converter, soil_moisture: { cluster: 'msSoilMoisture', type: ['attributeReport', 'readResponse'], @@ -496,7 +516,7 @@ const converters1 = { options: [exposes.options.no_occupancy_since_false()], convert: (model, msg, publish, options, meta) => { if (msg.data.hasOwnProperty('occupancy')) { - const payload = {occupancy: (msg.data.occupancy % 2) > 0}; + const payload = {occupancy: msg.data.occupancy % 2 > 0}; utils.noOccupancySince(msg.endpoint, options, publish, payload.occupancy ? 'stop' : 'start'); return payload; } @@ -518,8 +538,7 @@ const converters1 = { // The occupancy sensor only sends a message when motion detected. // Therefore we need to publish the no_motion detected by ourselves. - const timeout = options && options.hasOwnProperty('occupancy_timeout') ? - Number(options.occupancy_timeout) : 90; + const timeout = options && options.hasOwnProperty('occupancy_timeout') ? Number(options.occupancy_timeout) : 90; // Stop existing timers because motion is detected and set a new one. clearTimeout(globalStore.getValue(msg.endpoint, 'occupancy_timer', null)); @@ -560,16 +579,16 @@ const converters1 = { cluster: 'genLevelCtrl', type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { - const result: KeyValueAny = {'level_config': {}}; + const result: KeyValueAny = {level_config: {}}; // onOffTransitionTime - range 0x0000 to 0xffff - optional - if (msg.data.hasOwnProperty('onOffTransitionTime') && (msg.data['onOffTransitionTime'] !== undefined)) { + if (msg.data.hasOwnProperty('onOffTransitionTime') && msg.data['onOffTransitionTime'] !== undefined) { result.level_config.on_off_transition_time = Number(msg.data['onOffTransitionTime']); } // onTransitionTime - range 0x0000 to 0xffff - optional // 0xffff = use onOffTransitionTime - if (msg.data.hasOwnProperty('onTransitionTime') && (msg.data['onTransitionTime'] !== undefined)) { + if (msg.data.hasOwnProperty('onTransitionTime') && msg.data['onTransitionTime'] !== undefined) { result.level_config.on_transition_time = Number(msg.data['onTransitionTime']); if (result.level_config.on_transition_time == 65535) { result.level_config.on_transition_time = 'disabled'; @@ -578,7 +597,7 @@ const converters1 = { // offTransitionTime - range 0x0000 to 0xffff - optional // 0xffff = use onOffTransitionTime - if (msg.data.hasOwnProperty('offTransitionTime') && (msg.data['offTransitionTime'] !== undefined)) { + if (msg.data.hasOwnProperty('offTransitionTime') && msg.data['offTransitionTime'] !== undefined) { result.level_config.off_transition_time = Number(msg.data['offTransitionTime']); if (result.level_config.off_transition_time == 65535) { result.level_config.off_transition_time = 'disabled'; @@ -588,7 +607,7 @@ const converters1 = { // startUpCurrentLevel - range 0x00 to 0xff - optional // 0x00 = return to minimum supported level // 0xff - return to previous previous - if (msg.data.hasOwnProperty('startUpCurrentLevel') && (msg.data['startUpCurrentLevel'] !== undefined)) { + if (msg.data.hasOwnProperty('startUpCurrentLevel') && msg.data['startUpCurrentLevel'] !== undefined) { result.level_config.current_level_startup = Number(msg.data['startUpCurrentLevel']); if (result.level_config.current_level_startup == 255) { result.level_config.current_level_startup = 'previous'; @@ -600,7 +619,7 @@ const converters1 = { // onLevel - range 0x00 to 0xff - optional // Any value outside of MinLevel to MaxLevel, including 0xff and 0x00, is interpreted as "previous". - if (msg.data.hasOwnProperty('onLevel') && (msg.data['onLevel'] !== undefined)) { + if (msg.data.hasOwnProperty('onLevel') && msg.data['onLevel'] !== undefined) { result.level_config.on_level = Number(msg.data['onLevel']); if (result.level_config.on_level === 255) { result.level_config.on_level = 'previous'; @@ -637,13 +656,16 @@ const converters1 = { } if (msg.data.hasOwnProperty('colorMode')) { - result.color_mode = constants.colorModeLookup.hasOwnProperty(msg.data['colorMode']) ? - constants.colorModeLookup[msg.data['colorMode']] : msg.data['colorMode']; + result.color_mode = constants.colorModeLookup.hasOwnProperty(msg.data['colorMode']) + ? constants.colorModeLookup[msg.data['colorMode']] + : msg.data['colorMode']; } if ( - msg.data.hasOwnProperty('currentX') || msg.data.hasOwnProperty('currentY') || - msg.data.hasOwnProperty('currentSaturation') || msg.data.hasOwnProperty('currentHue') || + msg.data.hasOwnProperty('currentX') || + msg.data.hasOwnProperty('currentY') || + msg.data.hasOwnProperty('currentSaturation') || + msg.data.hasOwnProperty('currentHue') || msg.data.hasOwnProperty('enhancedCurrentHue') ) { result.color = {}; @@ -667,12 +689,12 @@ const converters1 = { if (msg.data.hasOwnProperty('options')) { /* - * Bit | Value & Summary - * -------------------------- - * 0 | 0: Do not execute command if the On/Off cluster, OnOff attribute is 0x00 (FALSE) - * | 1: Execute command if the On/Off cluster, OnOff attribute is 0x00 (FALSE) - */ - result.color_options = {execute_if_off: ((msg.data.options & 1<<0) > 0)}; + * Bit | Value & Summary + * -------------------------- + * 0 | 0: Do not execute command if the On/Off cluster, OnOff attribute is 0x00 (FALSE) + * | 1: Execute command if the On/Off cluster, OnOff attribute is 0x00 (FALSE) + */ + result.color_options = {execute_if_off: (msg.data.options & (1 << 0)) > 0}; } // handle color property sync @@ -686,13 +708,12 @@ const converters1 = { type: ['readResponse'], convert: (model, msg, publish, options, meta) => { const result: KeyValueAny = {}; - const elements = [ - /* 0x000A*/ 'softwareRevision', - /* 0x000D*/ 'availablePower', - /* 0x000E*/ 'powerThreshold', - ]; + const elements = [/* 0x000A*/ 'softwareRevision', /* 0x000D*/ 'availablePower', /* 0x000E*/ 'powerThreshold']; for (const at of elements) { - const atSnake = at.split(/(?=[A-Z])/).join('_').toLowerCase(); + const atSnake = at + .split(/(?=[A-Z])/) + .join('_') + .toLowerCase(); if (msg.data[at]) { result[atSnake] = msg.data[at]; } @@ -717,14 +738,13 @@ const converters1 = { if (msg.data.hasOwnProperty('instantaneousDemand')) { let power = msg.data['instantaneousDemand']; if (factor != null) { - power = (power * factor) * 1000; // kWh to Watt + power = power * factor * 1000; // kWh to Watt } const property = postfixWithEndpointName('power', msg, model, meta); payload[property] = power; } - if (factor != null && (msg.data.hasOwnProperty('currentSummDelivered') || - msg.data.hasOwnProperty('currentSummReceived'))) { + if (factor != null && (msg.data.hasOwnProperty('currentSummDelivered') || msg.data.hasOwnProperty('currentSummReceived'))) { if (msg.data.hasOwnProperty('currentSummDelivered')) { const data = msg.data['currentSummDelivered']; const value = (parseInt(data[0]) << 32) + parseInt(data[1]); @@ -831,8 +851,7 @@ const converters1 = { // has combined power measurements (power, energy)) if (msg.data.hasOwnProperty('onOff')) { const payload: KeyValueAny = {}; - const endpointName = model.hasOwnProperty('endpoint') ? - utils.getKey(model.endpoint(meta.device), msg.endpoint.ID) : msg.endpoint.ID; + const endpointName = model.hasOwnProperty('endpoint') ? utils.getKey(model.endpoint(meta.device), msg.endpoint.ID) : msg.endpoint.ID; const state = msg.data['onOff'] === 1 ? 'ON' : 'OFF'; payload[`state_${endpointName}`] = state; if (options && options.state_action) { @@ -879,8 +898,8 @@ const converters1 = { convert: (model, msg, publish, options, meta) => { const zoneStatus = msg.data.zoneStatus; return { - tamper: (zoneStatus & 1<<2) > 0, - battery_low: (zoneStatus & 1<<3) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, }; }, } satisfies Fz.Converter, @@ -891,12 +910,12 @@ const converters1 = { const zoneStatus = msg.data.zonestatus; return { alarm: (zoneStatus & 1) > 0, - tamper: (zoneStatus & 1<<2) > 0, - battery_low: (zoneStatus & 1<<3) > 0, - supervision_reports: (zoneStatus & 1<<4) > 0, - restore_reports: (zoneStatus & 1<<5) > 0, - ac_status: (zoneStatus & 1<<7) > 0, - test: (zoneStatus & 1<<8) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, + supervision_reports: (zoneStatus & (1 << 4)) > 0, + restore_reports: (zoneStatus & (1 << 5)) > 0, + ac_status: (zoneStatus & (1 << 7)) > 0, + test: (zoneStatus & (1 << 8)) > 0, }; }, } satisfies Fz.Converter, @@ -907,8 +926,8 @@ const converters1 = { const zoneStatus = msg.data.zonestatus; return { water_leak: (zoneStatus & 1) > 0, - tamper: (zoneStatus & 1<<2) > 0, - battery_low: (zoneStatus & 1<<3) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, }; }, } satisfies Fz.Converter, @@ -919,8 +938,8 @@ const converters1 = { const zoneStatus = msg.data.zoneStatus; return { water_leak: (zoneStatus & 1) > 0, - tamper: (zoneStatus & 1<<2) > 0, - battery_low: (zoneStatus & 1<<3) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, }; }, } satisfies Fz.Converter, @@ -931,8 +950,8 @@ const converters1 = { const zoneStatus = msg.data.zonestatus; return { vibration: (zoneStatus & 1) > 0, - tamper: (zoneStatus & 1<<2) > 0, - battery_low: (zoneStatus & 1<<3) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, }; }, } satisfies Fz.Converter, @@ -943,8 +962,7 @@ const converters1 = { convert: (model, msg, publish, options, meta) => { const zoneStatus = msg.data.zonestatus; - const timeout = options && options.hasOwnProperty('vibration_timeout') ? - Number(options.vibration_timeout) : 90; + const timeout = options && options.hasOwnProperty('vibration_timeout') ? Number(options.vibration_timeout) : 90; // Stop existing timers because vibration is detected and set a new one. globalStore.getValue(msg.endpoint, 'timers', []).forEach((t: NodeJS.Timeout) => clearTimeout(t)); @@ -960,8 +978,8 @@ const converters1 = { return { vibration: (zoneStatus & 1) > 0, - tamper: (zoneStatus & 1<<2) > 0, - battery_low: (zoneStatus & 1<<3) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, }; }, } satisfies Fz.Converter, @@ -972,8 +990,8 @@ const converters1 = { const zoneStatus = msg.data.zonestatus; return { gas: (zoneStatus & 1) > 0, - tamper: (zoneStatus & 1<<2) > 0, - battery_low: (zoneStatus & 1<<3) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, }; }, } satisfies Fz.Converter, @@ -983,9 +1001,9 @@ const converters1 = { convert: (model, msg, publish, options, meta) => { const zoneStatus = msg.data.zonestatus; return { - gas: (zoneStatus & 1<<1) > 0, - tamper: (zoneStatus & 1<<2) > 0, - battery_low: (zoneStatus & 1<<3) > 0, + gas: (zoneStatus & (1 << 1)) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, }; }, } satisfies Fz.Converter, @@ -996,14 +1014,14 @@ const converters1 = { const zoneStatus = msg.type === 'commandStatusChangeNotification' ? msg.data.zonestatus : msg.data.zoneStatus; return { smoke: (zoneStatus & 1) > 0, - tamper: (zoneStatus & 1<<2) > 0, - battery_low: (zoneStatus & 1<<3) > 0, - supervision_reports: (zoneStatus & 1<<4) > 0, - restore_reports: (zoneStatus & 1<<5) > 0, - trouble: (zoneStatus & 1<<6) > 0, - ac_status: (zoneStatus & 1<<7) > 0, - test: (zoneStatus & 1<<8) > 0, - battery_defect: (zoneStatus & 1<<9) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, + supervision_reports: (zoneStatus & (1 << 4)) > 0, + restore_reports: (zoneStatus & (1 << 5)) > 0, + trouble: (zoneStatus & (1 << 6)) > 0, + ac_status: (zoneStatus & (1 << 7)) > 0, + test: (zoneStatus & (1 << 8)) > 0, + battery_defect: (zoneStatus & (1 << 9)) > 0, }; }, } satisfies Fz.Converter, @@ -1018,8 +1036,8 @@ const converters1 = { return { [contactProperty]: !((zoneStatus & 1) > 0), - [tamperProperty]: (zoneStatus & 1<<2) > 0, - [batteryLowProperty]: (zoneStatus & 1<<3) > 0, + [tamperProperty]: (zoneStatus & (1 << 2)) > 0, + [batteryLowProperty]: (zoneStatus & (1 << 3)) > 0, }; }, } satisfies Fz.Converter, @@ -1030,8 +1048,8 @@ const converters1 = { const zoneStatus = msg.data.zoneStatus; return { contact: !((zoneStatus & 1) > 0), - tamper: (zoneStatus & 1<<2) > 0, - battery_low: (zoneStatus & 1<<3) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, }; }, } satisfies Fz.Converter, @@ -1042,8 +1060,8 @@ const converters1 = { const zoneStatus = msg.data.zonestatus; return { carbon_monoxide: (zoneStatus & 1) > 0, - tamper: (zoneStatus & 1<<2) > 0, - battery_low: (zoneStatus & 1<<3) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, }; }, } satisfies Fz.Converter, @@ -1054,13 +1072,13 @@ const converters1 = { const {zoneStatus} = msg.data; return { carbon_monoxide: (zoneStatus & 1) > 0, - gas: (zoneStatus & 1 << 1) > 0, - tamper: (zoneStatus & 1 << 2) > 0, - battery_low: (zoneStatus & 1 << 3) > 0, - trouble: (zoneStatus & 1 << 6) > 0, - ac_connected: !((zoneStatus & 1 << 7) > 0), - test: (zoneStatus & 1 << 8) > 0, - battery_defect: (zoneStatus & 1 << 9) > 0, + gas: (zoneStatus & (1 << 1)) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, + trouble: (zoneStatus & (1 << 6)) > 0, + ac_connected: !((zoneStatus & (1 << 7)) > 0), + test: (zoneStatus & (1 << 8)) > 0, + battery_defect: (zoneStatus & (1 << 9)) > 0, }; }, } satisfies Fz.Converter, @@ -1070,9 +1088,9 @@ const converters1 = { convert: (model, msg, publish, options, meta) => { const zoneStatus = msg.data.zonestatus; return { - sos: (zoneStatus & 1<<1) > 0, - tamper: (zoneStatus & 1<<2) > 0, - battery_low: (zoneStatus & 1<<3) > 0, + sos: (zoneStatus & (1 << 1)) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, }; }, } satisfies Fz.Converter, @@ -1083,8 +1101,8 @@ const converters1 = { const zoneStatus = msg.data.zonestatus; return { occupancy: (zoneStatus & 1) > 0, - tamper: (zoneStatus & 1<<2) > 0, - battery_low: (zoneStatus & 1<<3) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, }; }, } satisfies Fz.Converter, @@ -1095,8 +1113,8 @@ const converters1 = { const zoneStatus = msg.data.zoneStatus; return { occupancy: (zoneStatus & 1) > 0, - tamper: (zoneStatus & 1<<2) > 0, - battery_low: (zoneStatus & 1<<3) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, }; }, } satisfies Fz.Converter, @@ -1106,9 +1124,9 @@ const converters1 = { convert: (model, msg, publish, options, meta) => { const zoneStatus = msg.data.zonestatus; return { - occupancy: (zoneStatus & 1<<1) > 0, - tamper: (zoneStatus & 1<<2) > 0, - battery_low: (zoneStatus & 1<<3) > 0, + occupancy: (zoneStatus & (1 << 1)) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, }; }, } satisfies Fz.Converter, @@ -1128,7 +1146,7 @@ const converters1 = { convert: (model, msg, publish, options, meta) => { const zoneStatus = msg.data.zonestatus; return { - occupancy: (zoneStatus & 1<<1) > 0, + occupancy: (zoneStatus & (1 << 1)) > 0, }; }, } satisfies Fz.Converter, @@ -1138,8 +1156,7 @@ const converters1 = { options: [exposes.options.occupancy_timeout()], convert: (model, msg, publish, options, meta) => { const zoneStatus = msg.data.zonestatus; - const timeout = options && options.hasOwnProperty('occupancy_timeout') ? - Number(options.occupancy_timeout) : 90; + const timeout = options && options.hasOwnProperty('occupancy_timeout') ? Number(options.occupancy_timeout) : 90; clearTimeout(globalStore.getValue(msg.endpoint, 'timer')); @@ -1150,8 +1167,8 @@ const converters1 = { return { occupancy: (zoneStatus & 1) > 0, - tamper: (zoneStatus & 1<<2) > 0, - battery_low: (zoneStatus & 1<<3) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, }; }, } satisfies Fz.Converter, @@ -1314,8 +1331,7 @@ const converters1 = { if (globalStore.getValue(msg.endpoint, 'simulated_brightness_timer') === undefined) { const timer = setInterval(() => { let brightness = globalStore.getValue(msg.endpoint, 'simulated_brightness_brightness', defaultSimulatedBrightness); - const delta = globalStore.getValue(msg.endpoint, 'simulated_brightness_direction') === 'up' ? - deltaOpts : -1 * deltaOpts; + const delta = globalStore.getValue(msg.endpoint, 'simulated_brightness_direction') === 'up' ? deltaOpts : -1 * deltaOpts; brightness += delta; brightness = numberWithinRange(brightness, 0, 255); globalStore.putValue(msg.endpoint, 'simulated_brightness_brightness', brightness); @@ -1449,7 +1465,7 @@ const converters1 = { const payload = { action: postfixWithEndpointName(`color_hue_step_${direction}`, msg, model, meta), action_step_size: msg.data.stepsize, - action_transition_time: msg.data.transtime/100, + action_transition_time: msg.data.transtime / 100, }; addActionGroup(payload, msg, model); return payload; @@ -1464,7 +1480,7 @@ const converters1 = { const payload = { action: postfixWithEndpointName(`color_saturation_step_${direction}`, msg, model, meta), action_step_size: msg.data.stepsize, - action_transition_time: msg.data.transtime/100, + action_transition_time: msg.data.transtime / 100, }; addActionGroup(payload, msg, model); return payload; @@ -1485,10 +1501,10 @@ const converters1 = { const payload = { action: postfixWithEndpointName(`color_loop_set`, msg, model, meta), action_update_flags: { - action: (updateFlags & 1 << 0) > 0, - direction: (updateFlags & 1 << 1) > 0, - time: (updateFlags & 1 << 2) > 0, - start_hue: (updateFlags & 1 << 3) > 0, + action: (updateFlags & (1 << 0)) > 0, + direction: (updateFlags & (1 << 1)) > 0, + time: (updateFlags & (1 << 2)) > 0, + start_hue: (updateFlags & (1 << 3)) > 0, }, action_action: actionLookup[msg.data.action], action_direction: msg.data.direction === 0 ? 'decrement' : 'increment', @@ -1624,24 +1640,34 @@ const converters1 = { const value = msg.data['currentPositionLiftPercentage']; result[postfixWithEndpointName('position', msg, model, meta)] = invert ? value : 100 - value; if (!coverStateFromTilt) { - result[postfixWithEndpointName('state', msg, model, meta)] = - metaInvert ? (value === 0 ? 'CLOSE' : 'OPEN') : (value === 100 ? 'CLOSE' : 'OPEN'); + result[postfixWithEndpointName('state', msg, model, meta)] = metaInvert + ? value === 0 + ? 'CLOSE' + : 'OPEN' + : value === 100 + ? 'CLOSE' + : 'OPEN'; } } if (msg.data.hasOwnProperty('currentPositionTiltPercentage') && msg.data['currentPositionTiltPercentage'] <= 100) { const value = msg.data['currentPositionTiltPercentage']; result[postfixWithEndpointName('tilt', msg, model, meta)] = invert ? value : 100 - value; if (coverStateFromTilt) { - result[postfixWithEndpointName('state', msg, model, meta)] = - metaInvert ? (value === 100 ? 'OPEN' : 'CLOSE') : (value === 0 ? 'OPEN' : 'CLOSE'); + result[postfixWithEndpointName('state', msg, model, meta)] = metaInvert + ? value === 100 + ? 'OPEN' + : 'CLOSE' + : value === 0 + ? 'OPEN' + : 'CLOSE'; } } if (msg.data.hasOwnProperty('windowCoveringMode')) { result[postfixWithEndpointName('cover_mode', msg, model, meta)] = { - reversed: (msg.data.windowCoveringMode & 1<<0) > 0, - calibration: (msg.data.windowCoveringMode & 1<<1) > 0, - maintenance: (msg.data.windowCoveringMode & 1<<2) > 0, - led: (msg.data.windowCoveringMode & 1<<3) > 0, + reversed: (msg.data.windowCoveringMode & (1 << 0)) > 0, + calibration: (msg.data.windowCoveringMode & (1 << 1)) > 0, + maintenance: (msg.data.windowCoveringMode & (1 << 2)) > 0, + led: (msg.data.windowCoveringMode & (1 << 3)) > 0, }; } return result; @@ -1655,7 +1681,7 @@ const converters1 = { const currentLevel = Number(msg.data['currentLevel']); let position = mapNumberRange(currentLevel, 0, 255, 0, 100); position = options.invert_cover ? 100 - position : position; - const state = options.invert_cover ? (position > 0 ? 'CLOSE' : 'OPEN') : (position > 0 ? 'OPEN' : 'CLOSE'); + const state = options.invert_cover ? (position > 0 ? 'CLOSE' : 'OPEN') : position > 0 ? 'OPEN' : 'CLOSE'; return {state: state, position: position}; }, } satisfies Fz.Converter, @@ -1808,53 +1834,67 @@ const converters1 = { convert: (model, msg, publish, options, meta) => { const result: KeyValueAny = {}; const data = msg.data; - if (data.hasOwnProperty(0x1000)) { // Display brightness + if (data.hasOwnProperty(0x1000)) { + // Display brightness const lookup: KeyValueAny = {0: 'low', 1: 'mid', 2: 'high'}; result.lcd_brightness = lookup[data[0x1000]]; } - if (data.hasOwnProperty(0x1001)) { // Button vibration level + if (data.hasOwnProperty(0x1001)) { + // Button vibration level const lookup: KeyValueAny = {0: 'off', 1: 'low', 2: 'high'}; result.button_vibration_level = lookup[data[0x1001]]; } - if (data.hasOwnProperty(0x1002)) { // Floor sensor type + if (data.hasOwnProperty(0x1002)) { + // Floor sensor type const lookup: KeyValueAny = {1: '10k', 2: '15k', 3: '50k', 4: '100k', 5: '12k'}; result.floor_sensor_type = lookup[data[0x1002]]; } - if (data.hasOwnProperty(0x1003)) { // Sensor + if (data.hasOwnProperty(0x1003)) { + // Sensor const lookup: KeyValueAny = {0: 'air', 1: 'floor', 2: 'both'}; result.sensor = lookup[data[0x1003]]; } - if (data.hasOwnProperty(0x1004)) { // PowerUpStatus + if (data.hasOwnProperty(0x1004)) { + // PowerUpStatus const lookup: KeyValueAny = {0: 'default', 1: 'last_status'}; result.powerup_status = lookup[data[0x1004]]; } - if (data.hasOwnProperty(0x1005)) { // FloorSensorCalibration + if (data.hasOwnProperty(0x1005)) { + // FloorSensorCalibration result.floor_sensor_calibration = precisionRound(data[0x1005], 2) / 10; } - if (data.hasOwnProperty(0x1006)) { // DryTime + if (data.hasOwnProperty(0x1006)) { + // DryTime result.dry_time = data[0x1006]; } - if (data.hasOwnProperty(0x1007)) { // ModeAfterDry + if (data.hasOwnProperty(0x1007)) { + // ModeAfterDry const lookup: KeyValueAny = {0: 'off', 1: 'manual', 2: 'auto', 3: 'away'}; result.mode_after_dry = lookup[data[0x1007]]; } - if (data.hasOwnProperty(0x1008)) { // TemperatureDisplay + if (data.hasOwnProperty(0x1008)) { + // TemperatureDisplay const lookup: KeyValueAny = {0: 'room', 1: 'floor'}; result.temperature_display = lookup[data[0x1008]]; } - if (data.hasOwnProperty(0x1009)) { // WindowOpenCheck + if (data.hasOwnProperty(0x1009)) { + // WindowOpenCheck result.window_open_check = data[0x1009] / 2; } - if (data.hasOwnProperty(0x100A)) { // Hysterersis - result.hysterersis = precisionRound(data[0x100A], 2) / 10; + if (data.hasOwnProperty(0x100a)) { + // Hysterersis + result.hysterersis = precisionRound(data[0x100a], 2) / 10; } - if (data.hasOwnProperty(0x100B)) { // DisplayAutoOffEnable - result.display_auto_off_enabled = data[0x100B] ? 'enabled' : 'disabled'; + if (data.hasOwnProperty(0x100b)) { + // DisplayAutoOffEnable + result.display_auto_off_enabled = data[0x100b] ? 'enabled' : 'disabled'; } - if (data.hasOwnProperty(0x2001)) { // AlarmAirTempOverValue + if (data.hasOwnProperty(0x2001)) { + // AlarmAirTempOverValue result.alarm_airtemp_overvalue = data[0x2001]; } - if (data.hasOwnProperty(0x2002)) { // Away Mode Set + if (data.hasOwnProperty(0x2002)) { + // Away Mode Set result.away_mode = data[0x2002] ? 'ON' : 'OFF'; } @@ -1866,7 +1906,8 @@ const converters1 = { type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { const result: KeyValueAny = {}; - if (msg.data.hasOwnProperty('keypadLockout')) { // Set as child lock instead as keypadlockout + if (msg.data.hasOwnProperty('keypadLockout')) { + // Set as child lock instead as keypadlockout result.child_lock = msg.data['keypadLockout'] === 0 ? 'UNLOCK' : 'LOCK'; } return result; @@ -1879,60 +1920,74 @@ const converters1 = { const result: KeyValueAny = {}; const data = msg.data; - if (data.hasOwnProperty('elkoLoad')) { // Load + if (data.hasOwnProperty('elkoLoad')) { + // Load result.load = data['elkoLoad']; } - if (data.hasOwnProperty('elkoDisplayText')) { // Display text + if (data.hasOwnProperty('elkoDisplayText')) { + // Display text result.display_text = data['elkoDisplayText']; } - if (data.hasOwnProperty('elkoSensor')) { // Sensor + if (data.hasOwnProperty('elkoSensor')) { + // Sensor const sensorModeLookup: KeyValueAny = {'0': 'air', '1': 'floor', '3': 'supervisor_floor'}; result.sensor = sensorModeLookup[data['elkoSensor']]; } - if (data.hasOwnProperty('elkoRegulatorTime')) { // Regulator time + if (data.hasOwnProperty('elkoRegulatorTime')) { + // Regulator time result.regulator_time = data['elkoRegulatorTime']; } - if (data.hasOwnProperty('elkoRegulatorMode')) { // Regulator mode + if (data.hasOwnProperty('elkoRegulatorMode')) { + // Regulator mode result.regulator_mode = data['elkoRegulatorMode'] ? 'regulator' : 'thermostat'; } - if (data.hasOwnProperty('elkoPowerStatus')) { // Power status + if (data.hasOwnProperty('elkoPowerStatus')) { + // Power status result.system_mode = data['elkoPowerStatus'] ? 'heat' : 'off'; } - if (data.hasOwnProperty('elkoMeanPower')) { // Mean power + if (data.hasOwnProperty('elkoMeanPower')) { + // Mean power result.mean_power = data['elkoMeanPower']; } - if (data.hasOwnProperty('elkoExternalTemp')) { // External temp (floor) - result.floor_temp = utils.precisionRound(data['elkoExternalTemp'], 2) /100; + if (data.hasOwnProperty('elkoExternalTemp')) { + // External temp (floor) + result.floor_temp = utils.precisionRound(data['elkoExternalTemp'], 2) / 100; } - if (data.hasOwnProperty('elkoNightSwitching')) { // Night switching + if (data.hasOwnProperty('elkoNightSwitching')) { + // Night switching result.night_switching = data['elkoNightSwitching'] ? 'on' : 'off'; } - if (data.hasOwnProperty('elkoFrostGuard')) { // Frost guard + if (data.hasOwnProperty('elkoFrostGuard')) { + // Frost guard result.frost_guard = data['elkoFrostGuard'] ? 'on' : 'off'; } - if (data.hasOwnProperty('elkoChildLock')) { // Child lock + if (data.hasOwnProperty('elkoChildLock')) { + // Child lock result.child_lock = data['elkoChildLock'] ? 'lock' : 'unlock'; } - if (data.hasOwnProperty('elkoMaxFloorTemp')) { // Max floor temp + if (data.hasOwnProperty('elkoMaxFloorTemp')) { + // Max floor temp result.max_floor_temp = data['elkoMaxFloorTemp']; } - if (data.hasOwnProperty('elkoRelayState')) { // Relay state + if (data.hasOwnProperty('elkoRelayState')) { + // Relay state result.running_state = data['elkoRelayState'] ? 'heat' : 'idle'; } - if (data.hasOwnProperty('elkoCalibration')) { // Calibration + if (data.hasOwnProperty('elkoCalibration')) { + // Calibration result.local_temperature_calibration = precisionRound(data['elkoCalibration'], 2) / 10; } @@ -1946,10 +2001,10 @@ const converters1 = { const zoneStatus = msg.data.zonestatus; return { smoke: (zoneStatus & 1) > 0, - battery_low: (zoneStatus & 1<<3) > 0, - supervision_reports: (zoneStatus & 1<<4) > 0, - restore_reports: (zoneStatus & 1<<5) > 0, - test: (zoneStatus & 1<<8) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, + supervision_reports: (zoneStatus & (1 << 4)) > 0, + restore_reports: (zoneStatus & (1 << 5)) > 0, + test: (zoneStatus & (1 << 8)) > 0, }; }, } satisfies Fz.Converter, @@ -1980,7 +2035,6 @@ const converters1 = { } return result; }, - } satisfies Fz.Converter, tuya_led_controller: { cluster: 'lightingColorCtrl', @@ -2031,15 +2085,15 @@ const converters1 = { // TODO What is ALG const alg = data.slice(1); result['ALG'] = alg.join(','); - result['occupied_heating_setpoint'] = alg[2]/10; - result['local_temperature'] = alg[3]/10; + result['occupied_heating_setpoint'] = alg[2] / 10; + result['local_temperature'] = alg[3] / 10; result['pi_heating_demand'] = parseInt(alg[9]); } else if (data[0] === 'ADC') { // TODO What is ADC const adc = data.slice(1); result['ADC'] = adc.join(','); - result['occupied_heating_setpoint'] = adc[5]/100; - result['local_temperature'] = adc[3]/10; + result['occupied_heating_setpoint'] = adc[5] / 100; + result['local_temperature'] = adc[3] / 10; } else if (data[0] === 'UI') { if (data[1] === 'BoostUp') { result['boost'] = 'Up'; @@ -2064,8 +2118,8 @@ const converters1 = { const zoneStatus = msg.data.zonestatus; return { action: lookup[zoneStatus & 1], - tamper: (zoneStatus & 1<<2) > 0, - battery_low: (zoneStatus & 1<<3) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, }; }, } satisfies Fz.Converter, @@ -2074,8 +2128,8 @@ const converters1 = { type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { if (typeof msg.data['27'] === 'number') { - const direction = (msg.data['27'] > 0 ? 'clockwise' : 'counterclockwise'); - const number = (Math.abs(msg.data['27']) / 12); + const direction = msg.data['27'] > 0 ? 'clockwise' : 'counterclockwise'; + const number = Math.abs(msg.data['27']) / 12; return {action: 'rotate', action_direction: direction, action_number: number}; } }, @@ -2132,7 +2186,7 @@ const converters1 = { cluster: 'genBinaryInput', type: 'attributeReport', convert: (model, msg, publish, options, meta) => { - return {contact: (msg.data['presentValue']==0)}; + return {contact: msg.data['presentValue'] == 0}; }, } satisfies Fz.Converter, terncy_temperature: { @@ -2153,7 +2207,7 @@ const converters1 = { result['volume'] = mapNumberRange(msg.data['2'], 100, 10, 0, 100); } if (msg.data.hasOwnProperty('61440')) { - result['alarm'] = (msg.data['61440'] == 0) ? false : true; + result['alarm'] = msg.data['61440'] == 0 ? false : true; } return result; }, @@ -2325,7 +2379,8 @@ const converters1 = { } else if (msg.data[10] === 13) { const status = msg.data[13]; return {state: status & 1 ? 'ON' : 'OFF'}; - } else if (msg.data[10] === 5) { // TODO: Unknown dp, assumed value type + } else if (msg.data[10] === 5) { + // TODO: Unknown dp, assumed value type const value = msg.data[14] * 10; return { brightness: mapNumberRange(value, 0, 1000, 0, 255), @@ -2343,39 +2398,42 @@ const converters1 = { const dp = msg.data[10]; const defaults = {motor_direction: 'FORWARD', motor_speed: 40}; // @ts-expect-error - if (msg.data[0] === 0x7a & msg.data[1] === 0xd1) { + if ((msg.data[0] === 0x7a) & (msg.data[1] === 0xd1)) { const reportType = msg.data[12]; switch (dp) { - case 0x0c: - case 0x0f: - if (reportType === 0x04) { // Position report - const position = 100 - msg.data[13]; - const state = position > 0 ? 'OPEN' : 'CLOSE'; - const moving = dp === 0x0f; - return {...defaults, ...meta.state, position, state, moving}; - } - if (reportType === 0x12) { // Speed report - const motorSpeed = msg.data[13]; - return {...defaults, ...meta.state, motor_speed: motorSpeed}; - } else if (reportType === 0x13) { // Direction report - const direction = msg.data[13]; - if (direction < 0x80) { - return {...defaults, ...meta.state, motor_direction: 'FORWARD'}; - } else { - return {...defaults, ...meta.state, motor_direction: 'REVERSE'}; + case 0x0c: + case 0x0f: + if (reportType === 0x04) { + // Position report + const position = 100 - msg.data[13]; + const state = position > 0 ? 'OPEN' : 'CLOSE'; + const moving = dp === 0x0f; + return {...defaults, ...meta.state, position, state, moving}; } - } - break; - case 0x02: - case 0x03: - // Ignore special commands used only when pairing, as these will rather be handled by `onEvent` - return null; - case 0x08: - // Ignore general command acknowledgements, as they provide no useful information. - return null; - default: - // Unknown dps - logger.debug(`Unhandled DP ${dp} for ${meta.device.manufacturerName}: ${msg.data.toString('hex')}`, NS); + if (reportType === 0x12) { + // Speed report + const motorSpeed = msg.data[13]; + return {...defaults, ...meta.state, motor_speed: motorSpeed}; + } else if (reportType === 0x13) { + // Direction report + const direction = msg.data[13]; + if (direction < 0x80) { + return {...defaults, ...meta.state, motor_direction: 'FORWARD'}; + } else { + return {...defaults, ...meta.state, motor_direction: 'REVERSE'}; + } + } + break; + case 0x02: + case 0x03: + // Ignore special commands used only when pairing, as these will rather be handled by `onEvent` + return null; + case 0x08: + // Ignore general command acknowledgements, as they provide no useful information. + return null; + default: + // Unknown dps + logger.debug(`Unhandled DP ${dp} for ${meta.device.manufacturerName}: ${msg.data.toString('hex')}`, NS); } } }, @@ -2386,14 +2444,14 @@ const converters1 = { convert: (model, msg, publish, options, meta) => { const dp = msg.data[10]; switch (dp) { - case 14: - return { - temperature: Number(msg.data[13]), - }; - case 12: - return { - humidity: Number(msg.data[13]), - }; + case 14: + return { + temperature: Number(msg.data[13]), + }; + case 12: + return { + humidity: Number(msg.data[13]), + }; } }, } satisfies Fz.Converter, @@ -2547,7 +2605,7 @@ const converters1 = { let index; for (index = 0; index < data.length; index += 1) { code = data[index]; - if ((code < 32) || (code > 127)) { + if (code < 32 || code > 127) { bHex = true; break; } @@ -2558,7 +2616,7 @@ const converters1 = { data = [...data]; } } - return {'action': data}; + return {action: data}; }, } satisfies Fz.Converter, ptvo_switch_analog_input: { @@ -2588,19 +2646,19 @@ const converters1 = { let val = precisionRound(valRaw, 1); const nameLookup: KeyValueAny = { - 'C': 'temperature', + C: 'temperature', '%': 'humidity', - 'm': 'altitude', - 'Pa': 'pressure', - 'ppm': 'quality', - 'psize': 'particle_size', - 'V': 'voltage', - 'A': 'current', - 'Wh': 'energy', - 'W': 'power', - 'Hz': 'frequency', - 'pf': 'power_factor', - 'lx': 'illuminance_lux', + m: 'altitude', + Pa: 'pressure', + ppm: 'quality', + psize: 'particle_size', + V: 'voltage', + A: 'current', + Wh: 'energy', + W: 'power', + Hz: 'frequency', + pf: 'power_factor', + lx: 'illuminance_lux', }; let nameAlt = ''; @@ -2611,14 +2669,14 @@ const converters1 = { } if (unit.startsWith('mcpm') || unit.startsWith('ncpm')) { const num = unit.substr(4, 1); - nameAlt = (num === 'A')? unit.substr(0, 4) + '10': unit; + nameAlt = num === 'A' ? unit.substr(0, 4) + '10' : unit; val = precisionRound(valRaw, 2); } else { nameAlt = nameLookup[unit]; } if (nameAlt === undefined) { const valueIndex = parseInt(unit, 10); - if (! isNaN(valueIndex)) { + if (!isNaN(valueIndex)) { nameAlt = 'val' + unit; } } @@ -2647,7 +2705,7 @@ const converters1 = { cluster: 'genPowerCfg', type: ['readResponse', 'attributeReport'], convert: (model, msg, publish, options, meta) => { - const voltage = msg.data['mainsVoltage'] /10; + const voltage = msg.data['mainsVoltage'] / 10; return { battery: batteryVoltageToPercentage(voltage, '3V_2100'), voltage: voltage, // @deprecated @@ -2675,51 +2733,51 @@ const converters1 = { type: ['commandStudyKeyRsp', 'commandCreateIdRsp', 'commandGetIdAndKeyCodeListRsp'], convert: (model, msg, publish, options, meta) => { switch (msg.type) { - case 'commandStudyKeyRsp': - return { - action: 'learn', - action_result: msg.data.result === 1 ? 'success' : 'error', - action_key_code: msg.data.keyCode, - action_id: msg.data.result === 1 ? msg.data.id : undefined, - }; - case 'commandCreateIdRsp': - return { - action: 'create', - action_result: msg.data.id === 0xFF ? 'error' : 'success', - action_model_type: msg.data.modelType, - action_id: msg.data.id !== 0xFF ? msg.data.id : undefined, - }; - case 'commandGetIdAndKeyCodeListRsp': { - // See cluster.js with data format description - if (msg.data.packetNumber === 1) { - // start to collect and merge list - // so, we use store instance for temp storage during merging - globalStore.putValue(msg.endpoint, 'db', []); - } - const buffer = msg.data.learnedDevicesList; - for (let i = 0; i < msg.data.packetLength;) { - const modelDescription: KeyValueAny = { - id: buffer[i], - model_type: buffer[i + 1], - key_codes: [], + case 'commandStudyKeyRsp': + return { + action: 'learn', + action_result: msg.data.result === 1 ? 'success' : 'error', + action_key_code: msg.data.keyCode, + action_id: msg.data.result === 1 ? msg.data.id : undefined, }; - const numberOfKeys = buffer[i + 2]; - for (let j = i + 3; j < i + 3 + numberOfKeys; j++) { - modelDescription.key_codes.push(buffer[j]); - } - i = i + 3 + numberOfKeys; - globalStore.getValue(msg.endpoint, 'db').push(modelDescription); - } - if (msg.data.packetNumber === msg.data.packetsTotal) { - // last packet, all data collected, can publish - const result: KeyValueAny = { - 'devices': globalStore.getValue(msg.endpoint, 'db'), + case 'commandCreateIdRsp': + return { + action: 'create', + action_result: msg.data.id === 0xff ? 'error' : 'success', + action_model_type: msg.data.modelType, + action_id: msg.data.id !== 0xff ? msg.data.id : undefined, }; - globalStore.clearValue(msg.endpoint, 'db'); - return result; + case 'commandGetIdAndKeyCodeListRsp': { + // See cluster.js with data format description + if (msg.data.packetNumber === 1) { + // start to collect and merge list + // so, we use store instance for temp storage during merging + globalStore.putValue(msg.endpoint, 'db', []); + } + const buffer = msg.data.learnedDevicesList; + for (let i = 0; i < msg.data.packetLength; ) { + const modelDescription: KeyValueAny = { + id: buffer[i], + model_type: buffer[i + 1], + key_codes: [], + }; + const numberOfKeys = buffer[i + 2]; + for (let j = i + 3; j < i + 3 + numberOfKeys; j++) { + modelDescription.key_codes.push(buffer[j]); + } + i = i + 3 + numberOfKeys; + globalStore.getValue(msg.endpoint, 'db').push(modelDescription); + } + if (msg.data.packetNumber === msg.data.packetsTotal) { + // last packet, all data collected, can publish + const result: KeyValueAny = { + devices: globalStore.getValue(msg.endpoint, 'db'), + }; + globalStore.clearValue(msg.endpoint, 'db'); + return result; + } + break; } - break; - } } }, } satisfies Fz.Converter, @@ -2739,7 +2797,7 @@ const converters1 = { } if (msg.data.hasOwnProperty('8192')) { - result.line_frequency = precisionRound((parseFloat(msg.data['8192'])) / 100.0, 2); + result.line_frequency = precisionRound(parseFloat(msg.data['8192']) / 100.0, 2); result.linefrequency = result.line_frequency; // deprecated } @@ -2798,52 +2856,51 @@ const converters1 = { convert: (model, msg, publish, options, meta) => { const result: KeyValueAny = {}; if (msg.data.hasOwnProperty('danfossWindowOpenFeatureEnable')) { - result[postfixWithEndpointName('window_open_feature', msg, model, meta)] = - (msg.data['danfossWindowOpenFeatureEnable'] === 1); + result[postfixWithEndpointName('window_open_feature', msg, model, meta)] = msg.data['danfossWindowOpenFeatureEnable'] === 1; } if (msg.data.hasOwnProperty('danfossWindowOpenInternal')) { - result[postfixWithEndpointName('window_open_internal', msg, model, meta)] = - constants.danfossWindowOpen.hasOwnProperty(msg.data['danfossWindowOpenInternal']) ? - constants.danfossWindowOpen[msg.data['danfossWindowOpenInternal']] : - msg.data['danfossWindowOpenInternal']; + result[postfixWithEndpointName('window_open_internal', msg, model, meta)] = constants.danfossWindowOpen.hasOwnProperty( + msg.data['danfossWindowOpenInternal'], + ) + ? constants.danfossWindowOpen[msg.data['danfossWindowOpenInternal']] + : msg.data['danfossWindowOpenInternal']; } if (msg.data.hasOwnProperty('danfossWindowOpenExternal')) { - result[postfixWithEndpointName('window_open_external', msg, model, meta)] = (msg.data['danfossWindowOpenExternal'] === 1); + result[postfixWithEndpointName('window_open_external', msg, model, meta)] = msg.data['danfossWindowOpenExternal'] === 1; } if (msg.data.hasOwnProperty('danfossDayOfWeek')) { - result[postfixWithEndpointName('day_of_week', msg, model, meta)] = - constants.thermostatDayOfWeek.hasOwnProperty(msg.data['danfossDayOfWeek']) ? - constants.thermostatDayOfWeek[msg.data['danfossDayOfWeek']] : - msg.data['danfossDayOfWeek']; + result[postfixWithEndpointName('day_of_week', msg, model, meta)] = constants.thermostatDayOfWeek.hasOwnProperty( + msg.data['danfossDayOfWeek'], + ) + ? constants.thermostatDayOfWeek[msg.data['danfossDayOfWeek']] + : msg.data['danfossDayOfWeek']; } if (msg.data.hasOwnProperty('danfossTriggerTime')) { result[postfixWithEndpointName('trigger_time', msg, model, meta)] = msg.data['danfossTriggerTime']; } if (msg.data.hasOwnProperty('danfossMountedModeActive')) { - result[postfixWithEndpointName('mounted_mode_active', msg, model, meta)] = (msg.data['danfossMountedModeActive'] === 1); + result[postfixWithEndpointName('mounted_mode_active', msg, model, meta)] = msg.data['danfossMountedModeActive'] === 1; } if (msg.data.hasOwnProperty('danfossMountedModeControl')) { - result[postfixWithEndpointName('mounted_mode_control', msg, model, meta)] = (msg.data['danfossMountedModeControl'] === 1); + result[postfixWithEndpointName('mounted_mode_control', msg, model, meta)] = msg.data['danfossMountedModeControl'] === 1; } if (msg.data.hasOwnProperty('danfossThermostatOrientation')) { - result[postfixWithEndpointName('thermostat_vertical_orientation', msg, model, meta)] = - (msg.data['danfossThermostatOrientation'] === 1); + result[postfixWithEndpointName('thermostat_vertical_orientation', msg, model, meta)] = msg.data['danfossThermostatOrientation'] === 1; } if (msg.data.hasOwnProperty('danfossExternalMeasuredRoomSensor')) { - result[postfixWithEndpointName('external_measured_room_sensor', msg, model, meta)] = - msg.data['danfossExternalMeasuredRoomSensor']; + result[postfixWithEndpointName('external_measured_room_sensor', msg, model, meta)] = msg.data['danfossExternalMeasuredRoomSensor']; } if (msg.data.hasOwnProperty('danfossRadiatorCovered')) { - result[postfixWithEndpointName('radiator_covered', msg, model, meta)] = (msg.data['danfossRadiatorCovered'] === 1); + result[postfixWithEndpointName('radiator_covered', msg, model, meta)] = msg.data['danfossRadiatorCovered'] === 1; } if (msg.data.hasOwnProperty('danfossViewingDirection')) { - result[postfixWithEndpointName('viewing_direction', msg, model, meta)] = (msg.data['danfossViewingDirection'] === 1); + result[postfixWithEndpointName('viewing_direction', msg, model, meta)] = msg.data['danfossViewingDirection'] === 1; } if (msg.data.hasOwnProperty('danfossAlgorithmScaleFactor')) { result[postfixWithEndpointName('algorithm_scale_factor', msg, model, meta)] = msg.data['danfossAlgorithmScaleFactor']; } if (msg.data.hasOwnProperty('danfossHeatAvailable')) { - result[postfixWithEndpointName('heat_available', msg, model, meta)] = (msg.data['danfossHeatAvailable'] === 1); + result[postfixWithEndpointName('heat_available', msg, model, meta)] = msg.data['danfossHeatAvailable'] === 1; } if (msg.data.hasOwnProperty('danfossHeatRequired')) { if (msg.data['danfossHeatRequired'] === 1) { @@ -2855,7 +2912,7 @@ const converters1 = { } } if (msg.data.hasOwnProperty('danfossLoadBalancingEnable')) { - result[postfixWithEndpointName('load_balancing_enable', msg, model, meta)] = (msg.data['danfossLoadBalancingEnable'] === 1); + result[postfixWithEndpointName('load_balancing_enable', msg, model, meta)] = msg.data['danfossLoadBalancingEnable'] === 1; } if (msg.data.hasOwnProperty('danfossLoadRoomMean')) { result[postfixWithEndpointName('load_room_mean', msg, model, meta)] = msg.data['danfossLoadRoomMean']; @@ -2864,30 +2921,29 @@ const converters1 = { result[postfixWithEndpointName('load_estimate', msg, model, meta)] = msg.data['danfossLoadEstimate']; } if (msg.data.hasOwnProperty('danfossPreheatStatus')) { - result[postfixWithEndpointName('preheat_status', msg, model, meta)] = (msg.data['danfossPreheatStatus'] === 1); + result[postfixWithEndpointName('preheat_status', msg, model, meta)] = msg.data['danfossPreheatStatus'] === 1; } if (msg.data.hasOwnProperty('danfossAdaptionRunStatus')) { result[postfixWithEndpointName('adaptation_run_status', msg, model, meta)] = constants.danfossAdaptionRunStatus[msg.data['danfossAdaptionRunStatus']]; } if (msg.data.hasOwnProperty('danfossAdaptionRunSettings')) { - result[postfixWithEndpointName('adaptation_run_settings', msg, model, meta)] = - (msg.data['danfossAdaptionRunSettings'] === 1); + result[postfixWithEndpointName('adaptation_run_settings', msg, model, meta)] = msg.data['danfossAdaptionRunSettings'] === 1; } if (msg.data.hasOwnProperty('danfossAdaptionRunControl')) { result[postfixWithEndpointName('adaptation_run_control', msg, model, meta)] = constants.danfossAdaptionRunControl[msg.data['danfossAdaptionRunControl']]; } if (msg.data.hasOwnProperty('danfossRegulationSetpointOffset')) { - result[postfixWithEndpointName('regulation_setpoint_offset', msg, model, meta)] = - msg.data['danfossRegulationSetpointOffset']; + result[postfixWithEndpointName('regulation_setpoint_offset', msg, model, meta)] = msg.data['danfossRegulationSetpointOffset']; } // Danfoss Icon Converters if (msg.data.hasOwnProperty('danfossRoomStatusCode')) { - result[postfixWithEndpointName('room_status_code', msg, model, meta)] = - constants.danfossRoomStatusCode.hasOwnProperty(msg.data['danfossRoomStatusCode']) ? - constants.danfossRoomStatusCode[msg.data['danfossRoomStatusCode']] : - msg.data['danfossRoomStatusCode']; + result[postfixWithEndpointName('room_status_code', msg, model, meta)] = constants.danfossRoomStatusCode.hasOwnProperty( + msg.data['danfossRoomStatusCode'], + ) + ? constants.danfossRoomStatusCode[msg.data['danfossRoomStatusCode']] + : msg.data['danfossRoomStatusCode']; } if (msg.data.hasOwnProperty('danfossOutputStatus')) { if (msg.data['danfossOutputStatus'] === 1) { @@ -2919,10 +2975,11 @@ const converters1 = { convert: (model, msg, publish, options, meta) => { const result: KeyValueAny = {}; if (msg.data.hasOwnProperty('danfossRoomFloorSensorMode')) { - result[postfixWithEndpointName('room_floor_sensor_mode', msg, model, meta)] = - constants.danfossRoomFloorSensorMode.hasOwnProperty(msg.data['danfossRoomFloorSensorMode']) ? - constants.danfossRoomFloorSensorMode[msg.data['danfossRoomFloorSensorMode']] : - msg.data['danfossRoomFloorSensorMode']; + result[postfixWithEndpointName('room_floor_sensor_mode', msg, model, meta)] = constants.danfossRoomFloorSensorMode.hasOwnProperty( + msg.data['danfossRoomFloorSensorMode'], + ) + ? constants.danfossRoomFloorSensorMode[msg.data['danfossRoomFloorSensorMode']] + : msg.data['danfossRoomFloorSensorMode']; } if (msg.data.hasOwnProperty('danfossFloorMinSetpoint')) { const value = precisionRound(msg.data['danfossFloorMinSetpoint'], 2) / 100; @@ -2962,22 +3019,25 @@ const converters1 = { convert: (model, msg, publish, options, meta) => { const result: KeyValueAny = {}; if (msg.data.hasOwnProperty('danfossSystemStatusCode')) { - result[postfixWithEndpointName('system_status_code', msg, model, meta)] = - constants.danfossSystemStatusCode.hasOwnProperty(msg.data['danfossSystemStatusCode']) ? - constants.danfossSystemStatusCode[msg.data['danfossSystemStatusCode']] : - msg.data['danfossSystemStatusCode']; + result[postfixWithEndpointName('system_status_code', msg, model, meta)] = constants.danfossSystemStatusCode.hasOwnProperty( + msg.data['danfossSystemStatusCode'], + ) + ? constants.danfossSystemStatusCode[msg.data['danfossSystemStatusCode']] + : msg.data['danfossSystemStatusCode']; } if (msg.data.hasOwnProperty('danfossSystemStatusWater')) { - result[postfixWithEndpointName('system_status_water', msg, model, meta)] = - constants.danfossSystemStatusWater.hasOwnProperty(msg.data['danfossSystemStatusWater']) ? - constants.danfossSystemStatusWater[msg.data['danfossSystemStatusWater']] : - msg.data['danfossSystemStatusWater']; + result[postfixWithEndpointName('system_status_water', msg, model, meta)] = constants.danfossSystemStatusWater.hasOwnProperty( + msg.data['danfossSystemStatusWater'], + ) + ? constants.danfossSystemStatusWater[msg.data['danfossSystemStatusWater']] + : msg.data['danfossSystemStatusWater']; } if (msg.data.hasOwnProperty('danfossMultimasterRole')) { - result[postfixWithEndpointName('multimaster_role', msg, model, meta)] = - constants.danfossMultimasterRole.hasOwnProperty(msg.data['danfossMultimasterRole']) ? - constants.danfossMultimasterRole[msg.data['danfossMultimasterRole']] : - msg.data['danfossMultimasterRole']; + result[postfixWithEndpointName('multimaster_role', msg, model, meta)] = constants.danfossMultimasterRole.hasOwnProperty( + msg.data['danfossMultimasterRole'], + ) + ? constants.danfossMultimasterRole[msg.data['danfossMultimasterRole']] + : msg.data['danfossMultimasterRole']; } return result; }, @@ -2988,16 +3048,18 @@ const converters1 = { convert: (model, msg, publish, options, meta) => { const result: KeyValueAny = {}; if (msg.data.hasOwnProperty('keypadLockout')) { - result[postfixWithEndpointName('keypad_lockout', msg, model, meta)] = - constants.keypadLockoutMode.hasOwnProperty(msg.data['keypadLockout']) ? - constants.keypadLockoutMode[msg.data['keypadLockout']] : - msg.data['keypadLockout']; + result[postfixWithEndpointName('keypad_lockout', msg, model, meta)] = constants.keypadLockoutMode.hasOwnProperty( + msg.data['keypadLockout'], + ) + ? constants.keypadLockoutMode[msg.data['keypadLockout']] + : msg.data['keypadLockout']; } if (msg.data.hasOwnProperty('tempDisplayMode')) { - result[postfixWithEndpointName('temperature_display_mode', msg, model, meta)] = - constants.temperatureDisplayMode.hasOwnProperty(msg.data['tempDisplayMode']) ? - constants.temperatureDisplayMode[msg.data['tempDisplayMode']] : - msg.data['tempDisplayMode']; + result[postfixWithEndpointName('temperature_display_mode', msg, model, meta)] = constants.temperatureDisplayMode.hasOwnProperty( + msg.data['tempDisplayMode'], + ) + ? constants.temperatureDisplayMode[msg.data['tempDisplayMode']] + : msg.data['tempDisplayMode']; } return result; }, @@ -3124,7 +3186,7 @@ const converters1 = { cluster: 'genOnOff', type: ['commandOn', 'commandOff', 'commandToggle'], convert: (model, msg, publish, options, meta) => { - const lookup: KeyValueAny = {'commandToggle': 'single', 'commandOn': 'double', 'commandOff': 'long'}; + const lookup: KeyValueAny = {commandToggle: 'single', commandOn: 'double', commandOff: 'long'}; return {action: lookup[msg.type]}; }, } satisfies Fz.Converter, @@ -3144,7 +3206,7 @@ const converters1 = { state: msg.data['onOff'] === 1 ? 'ON' : 'OFF', cpu_temperature: precisionRound(msg.data['41361'], 2), power: power, - current: precisionRound(power/230, 2), + current: precisionRound(power / 230, 2), action: msg.data['41367'] === 1 ? 'hold' : 'release', }; }, @@ -3169,9 +3231,19 @@ const converters1 = { // Button 3: B0 (top right) // Button 4: B1 (bottom right) const lookup: KeyValueAny = { - 0x10: 'press_1', 0x14: 'release_1', 0x11: 'press_2', 0x15: 'release_2', 0x13: 'press_3', 0x17: 'release_3', - 0x12: 'press_4', 0x16: 'release_4', 0x64: 'press_1_and_3', 0x65: 'release_1_and_3', 0x62: 'press_2_and_4', - 0x63: 'release_2_and_4', 0x22: 'press_energy_bar', + 0x10: 'press_1', + 0x14: 'release_1', + 0x11: 'press_2', + 0x15: 'release_2', + 0x13: 'press_3', + 0x17: 'release_3', + 0x12: 'press_4', + 0x16: 'release_4', + 0x64: 'press_1_and_3', + 0x65: 'release_1_and_3', + 0x62: 'press_2_and_4', + 0x63: 'release_2_and_4', + 0x22: 'press_energy_bar', }; const action = lookup.hasOwnProperty(commandID) ? lookup[commandID] : `unknown_${commandID}`; @@ -3191,12 +3263,33 @@ const converters1 = { // Button 3: B0 (top right) // Button 4: B1 (bottom right) const lookup: KeyValueAny = { - 0x22: 'press_1', 0x23: 'release_1', 0x18: 'press_2', 0x19: 'release_2', 0x14: 'press_3', 0x15: 'release_3', 0x12: 'press_4', - 0x13: 'release_4', 0x64: 'press_1_and_2', 0x65: 'release_1_and_2', 0x62: 'press_1_and_3', 0x63: 'release_1_and_3', - 0x1e: 'press_1_and_4', 0x1f: 'release_1_and_4', 0x1c: 'press_2_and_3', 0x1d: 'release_2_and_3', 0x1a: 'press_2_and_4', - 0x1b: 'release_2_and_4', 0x16: 'press_3_and_4', 0x17: 'release_3_and_4', 0x10: 'press_energy_bar', - 0x11: 'release_energy_bar', 0x0: 'press_or_release_all', - 0x50: 'lock', 0x51: 'unlock', 0x52: 'half_open', 0x53: 'tilt', + 0x22: 'press_1', + 0x23: 'release_1', + 0x18: 'press_2', + 0x19: 'release_2', + 0x14: 'press_3', + 0x15: 'release_3', + 0x12: 'press_4', + 0x13: 'release_4', + 0x64: 'press_1_and_2', + 0x65: 'release_1_and_2', + 0x62: 'press_1_and_3', + 0x63: 'release_1_and_3', + 0x1e: 'press_1_and_4', + 0x1f: 'release_1_and_4', + 0x1c: 'press_2_and_3', + 0x1d: 'release_2_and_3', + 0x1a: 'press_2_and_4', + 0x1b: 'release_2_and_4', + 0x16: 'press_3_and_4', + 0x17: 'release_3_and_4', + 0x10: 'press_energy_bar', + 0x11: 'release_energy_bar', + 0x0: 'press_or_release_all', + 0x50: 'lock', + 0x51: 'unlock', + 0x52: 'half_open', + 0x53: 'tilt', }; if (!lookup.hasOwnProperty(commandID)) { @@ -3219,10 +3312,23 @@ const converters1 = { // Button 3: B0 (top right) // Button 4: B1 (bottom right) const lookup: KeyValueAny = { - '105_1': 'press_1', '105_2': 'press_2', '105_3': 'press_1_and_2', '105_4': 'press_3', '105_5': 'press_1_and_3', - '105_6': 'press_3_and_4', '105_7': 'press_1_and_2_and_3', '105_8': 'press_4', '105_9': 'press_1_and_4', - '105_10': 'press_2_and_4', '105_11': 'press_1_and_2_and_4', '105_12': 'press_3_and_4', '105_13': 'press_1_and_3_and_4', - '105_14': 'press_2_and_3_and_4', '105_15': 'press_all', '105_16': 'press_energy_bar', '106_0': 'release', + '105_1': 'press_1', + '105_2': 'press_2', + '105_3': 'press_1_and_2', + '105_4': 'press_3', + '105_5': 'press_1_and_3', + '105_6': 'press_3_and_4', + '105_7': 'press_1_and_2_and_3', + '105_8': 'press_4', + '105_9': 'press_1_and_4', + '105_10': 'press_2_and_4', + '105_11': 'press_1_and_2_and_4', + '105_12': 'press_3_and_4', + '105_13': 'press_1_and_3_and_4', + '105_14': 'press_2_and_3_and_4', + '105_15': 'press_all', + '105_16': 'press_energy_bar', + '106_0': 'release', '104_': 'short_press_2_of_2', }; @@ -3240,7 +3346,7 @@ const converters1 = { convert: (model, msg, publish, options, meta) => { const alertStatus = msg.data.aalert; return { - water_leak: (alertStatus & 1<<12) > 0, + water_leak: (alertStatus & (1 << 12)) > 0, }; }, } satisfies Fz.Converter, @@ -3259,14 +3365,14 @@ const converters1 = { cluster: 'genBinaryInput', type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { - return {occupancy: (msg.data['presentValue']===1)}; + return {occupancy: msg.data['presentValue'] === 1}; }, } satisfies Fz.Converter, kmpcil_res005_on_off: { cluster: 'genBinaryOutput', type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { - return {state: (msg.data['presentValue']==0) ? 'OFF' : 'ON'}; + return {state: msg.data['presentValue'] == 0 ? 'OFF' : 'ON'}; }, } satisfies Fz.Converter, _3310_humidity: { @@ -3284,7 +3390,6 @@ const converters1 = { const payload: KeyValueAny = {}; if (msg.data.hasOwnProperty('acceleration')) payload.moving = msg.data['acceleration'] === 1; - // eslint-disable-next-line // https://github.com/SmartThingsCommunity/SmartThingsPublic/blob/master/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy#L222 /* The axes reported by the sensor are mapped differently in the SmartThings DTH. @@ -3295,7 +3400,7 @@ const converters1 = { */ if (msg.data.hasOwnProperty('z_axis')) payload.x_axis = msg.data['z_axis']; if (msg.data.hasOwnProperty('y_axis')) payload.y_axis = msg.data['y_axis']; - if (msg.data.hasOwnProperty('x_axis')) payload.z_axis = - msg.data['x_axis']; + if (msg.data.hasOwnProperty('x_axis')) payload.z_axis = -msg.data['x_axis']; return payload; }, @@ -3366,9 +3471,9 @@ const converters1 = { type: ['attributeReport', 'readResponse'], options: [exposes.options.no_position_support()], convert: (model, msg, publish, options, meta) => { - return options.no_position_support ? - {action: msg.data.presentValue ? 'stopped' : 'moving', position: 50} : - {action: msg.data.presentValue ? 'stopped' : 'moving'}; + return options.no_position_support + ? {action: msg.data.presentValue ? 'stopped' : 'moving', position: 50} + : {action: msg.data.presentValue ? 'stopped' : 'moving'}; }, } satisfies Fz.Converter, legrand_scenes: { @@ -3383,8 +3488,16 @@ const converters1 = { cluster: 'manuSpecificLegrandDevices', type: 'raw', convert: (model, msg, publish, options, meta) => { - if (msg.data && msg.data.length === 6 && msg.data[0] === 0x15 && msg.data[1] === 0x21 && msg.data[2] === 0x10 && - msg.data[3] === 0x00 && msg.data[4] === 0x03 && msg.data[5] === 0xff) { + if ( + msg.data && + msg.data.length === 6 && + msg.data[0] === 0x15 && + msg.data[1] === 0x21 && + msg.data[2] === 0x10 && + msg.data[3] === 0x00 && + msg.data[4] === 0x03 && + msg.data[5] === 0xff + ) { return {action: 'center'}; } }, @@ -3419,7 +3532,7 @@ const converters1 = { // This attribute returns usually 2 when power is over the defined threshold. if (msg.data.hasOwnProperty('61440')) { payload.power_alarm_active_value = msg.data['61440']; - payload.power_alarm_active = (payload.power_alarm_active_value > 0); + payload.power_alarm_active = payload.power_alarm_active_value > 0; } // 0xf001 = 61441 if (msg.data.hasOwnProperty('61441')) { @@ -3440,11 +3553,19 @@ const converters1 = { if (hasAlreadyProcessedMessage(msg, model, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return; if (commandID === 224) return; const lookup: KeyValueAny = { - 0x10: 'home_arrival', 0x11: 'home_departure', // ZLGP14 - 0x12: 'daytime_day', 0x13: 'daytime_night', // ZLGP16, yes these commandIDs are lower than ZLGP15s' - 0x14: 'press_1', 0x15: 'press_2', 0x16: 'press_3', 0x17: 'press_4', // ZLGP15 - 0x22: 'press_once', 0x20: 'press_twice', // ZLGP17, ZLGP18 - 0x34: 'stop', 0x35: 'up', 0x36: 'down', // 600087l + 0x10: 'home_arrival', + 0x11: 'home_departure', // ZLGP14 + 0x12: 'daytime_day', + 0x13: 'daytime_night', // ZLGP16, yes these commandIDs are lower than ZLGP15s' + 0x14: 'press_1', + 0x15: 'press_2', + 0x16: 'press_3', + 0x17: 'press_4', // ZLGP15 + 0x22: 'press_once', + 0x20: 'press_twice', // ZLGP17, ZLGP18 + 0x34: 'stop', + 0x35: 'up', + 0x36: 'down', // 600087l }; if (!lookup.hasOwnProperty(commandID)) { logger.error(`Legrand GreenPower: missing command '${commandID}'`, NS); @@ -3459,7 +3580,7 @@ const converters1 = { convert: (model, msg, publish, options, meta) => { const zoneStatus = msg.data.zonestatus; return { - carbon_monoxide: (zoneStatus & 1<<8) > 8, + carbon_monoxide: (zoneStatus & (1 << 8)) > 8, }; }, } satisfies Fz.Converter, @@ -3602,7 +3723,7 @@ const converters1 = { const now = Date.now(); const since = globalStore.getValue(msg.endpoint, 'since'); - if ((now-since)>100 && lookup[action]) { + if (now - since > 100 && lookup[action]) { globalStore.putValue(msg.endpoint, 'since', now); return {action: lookup[action]}; } @@ -3618,7 +3739,7 @@ const converters1 = { globalStore.putValue(msg.endpoint, 'action', []); } - const lookup: KeyValueAny = {'commandOn': 'bell1', 'commandOff': 'bell2'}; + const lookup: KeyValueAny = {commandOn: 'bell1', commandOff: 'bell2'}; const timer = setTimeout(() => globalStore.getValue(msg.endpoint, 'action').pop(), timeout * 1000); const list = globalStore.getValue(msg.endpoint, 'action'); @@ -3635,10 +3756,8 @@ const converters1 = { type: ['attributeReport', 'readResponse'], options: [ exposes.options.invert_cover(), - e.numeric('time_close', ea.SET) - .withDescription(`Set the full closing time of the roller shutter (e.g. set it to 20) (value is in s).`), - e.numeric('time_open', ea.SET) - .withDescription(`Set the full opening time of the roller shutter (e.g. set it to 21) (value is in s).`), + e.numeric('time_close', ea.SET).withDescription(`Set the full closing time of the roller shutter (e.g. set it to 20) (value is in s).`), + e.numeric('time_open', ea.SET).withDescription(`Set the full opening time of the roller shutter (e.g. set it to 21) (value is in s).`), ], convert: (model, msg, publish, options, meta) => { const result: KeyValueAny = {}; @@ -3653,30 +3772,29 @@ const converters1 = { const entry = globalStore.getValue(msg.endpoint, 'position'); // ignore if first action is middle and ignore action middle if previous action is middle - if (msg.data.hasOwnProperty('currentPositionLiftPercentage') && msg.data['currentPositionLiftPercentage'] == 50 ) { - if ((entry.CurrentPosition == -1 && entry.lastPreviousAction == -1) || - entry.lastPreviousAction == 50 ) { + if (msg.data.hasOwnProperty('currentPositionLiftPercentage') && msg.data['currentPositionLiftPercentage'] == 50) { + if ((entry.CurrentPosition == -1 && entry.lastPreviousAction == -1) || entry.lastPreviousAction == 50) { logger.warning(`ZMCSW032D ignore action`, NS); return; } } let currentPosition = entry.CurrentPosition; const lastPreviousAction = entry.lastPreviousAction; - const deltaTimeSec = Math.floor((Date.now() - entry.since)/1000); // convert to sec + const deltaTimeSec = Math.floor((Date.now() - entry.since) / 1000); // convert to sec entry.since = Date.now(); entry.lastPreviousAction = msg.data['currentPositionLiftPercentage']; - if (msg.data.hasOwnProperty('currentPositionLiftPercentage') && msg.data['currentPositionLiftPercentage'] == 50 ) { + if (msg.data.hasOwnProperty('currentPositionLiftPercentage') && msg.data['currentPositionLiftPercentage'] == 50) { if (deltaTimeSec < timeCoverSetMiddle || deltaTimeSec > timeCoverSetMiddle) { - if (lastPreviousAction == 100 ) { + if (lastPreviousAction == 100) { // Open currentPosition = currentPosition == -1 ? 0 : currentPosition; - currentPosition = currentPosition + ((deltaTimeSec * 100)/Number(options.time_open)); - } else if (lastPreviousAction == 0 ) { + currentPosition = currentPosition + (deltaTimeSec * 100) / Number(options.time_open); + } else if (lastPreviousAction == 0) { // Close currentPosition = currentPosition == -1 ? 100 : currentPosition; - currentPosition = currentPosition - ((deltaTimeSec * 100)/Number(options.time_close)); + currentPosition = currentPosition - (deltaTimeSec * 100) / Number(options.time_close); } currentPosition = currentPosition > 100 ? 100 : currentPosition; currentPosition = currentPosition < 0 ? 0 : currentPosition; @@ -3684,7 +3802,7 @@ const converters1 = { } entry.CurrentPosition = currentPosition; - if (msg.data.hasOwnProperty('currentPositionLiftPercentage') && msg.data['currentPositionLiftPercentage'] !== 50 ) { + if (msg.data.hasOwnProperty('currentPositionLiftPercentage') && msg.data['currentPositionLiftPercentage'] !== 50) { // position cast float to int result.position = currentPosition | 0; } else { @@ -3751,11 +3869,11 @@ const converters1 = { type: ['commandAtHome', 'commandGoOut', 'commandCinema', 'commandRepast', 'commandSleep'], convert: (model, msg, publish, options, meta) => { const lookup: KeyValueAny = { - 'commandCinema': 'cinema', - 'commandAtHome': 'at_home', - 'commandSleep': 'sleep', - 'commandGoOut': 'go_out', - 'commandRepast': 'repast', + commandCinema: 'cinema', + commandAtHome: 'at_home', + commandSleep: 'sleep', + commandGoOut: 'go_out', + commandRepast: 'repast', }; if (lookup.hasOwnProperty(msg.type)) return {action: lookup[msg.type]}; }, @@ -3772,7 +3890,7 @@ const converters1 = { }; const utf8FromStr = (s: string) => { const a = []; - for (let i = 0, enc = encodeURIComponent(s); i < enc.length;) { + for (let i = 0, enc = encodeURIComponent(s); i < enc.length; ) { if (enc[i] === '%') { a.push(parseInt(enc.substr(i + 1, 2), 16)); i += 3; @@ -3826,23 +3944,23 @@ const converters1 = { type: 'readResponse', convert: (model, msg, publish, options, meta) => { const result: KeyValueAny = {}; - if (msg.data.hasOwnProperty(0xF001)) { - result.led_feedback = ['OFF', 'ON'][msg.data[0xF001]]; + if (msg.data.hasOwnProperty(0xf001)) { + result.led_feedback = ['OFF', 'ON'][msg.data[0xf001]]; } - if (msg.data.hasOwnProperty(0xF002)) { - result.buzzer_feedback = ['OFF', 'ON'][msg.data[0xF002]]; + if (msg.data.hasOwnProperty(0xf002)) { + result.buzzer_feedback = ['OFF', 'ON'][msg.data[0xf002]]; } - if (msg.data.hasOwnProperty(0xF000)) { - result.sensitivity = msg.data[0xF000]; + if (msg.data.hasOwnProperty(0xf000)) { + result.sensitivity = msg.data[0xf000]; } - if (msg.data.hasOwnProperty(0xF003)) { - result.sensors_count = msg.data[0xF003]; + if (msg.data.hasOwnProperty(0xf003)) { + result.sensors_count = msg.data[0xf003]; } - if (msg.data.hasOwnProperty(0xF004)) { - result.sensors_type = ['СБМ-20/СТС-5/BOI-33', 'СБМ-19/СТС-6', 'Others'][msg.data[0xF004]]; + if (msg.data.hasOwnProperty(0xf004)) { + result.sensors_type = ['СБМ-20/СТС-5/BOI-33', 'СБМ-19/СТС-6', 'Others'][msg.data[0xf004]]; } - if (msg.data.hasOwnProperty(0xF005)) { - result.alert_threshold = msg.data[0xF005]; + if (msg.data.hasOwnProperty(0xf005)) { + result.alert_threshold = msg.data[0xf005]; } return result; }, @@ -3888,7 +4006,6 @@ const converters1 = { } return result; }, - } satisfies Fz.Converter, diyruz_airsense_config_hum: { cluster: 'msRelativeHumidity', @@ -4059,14 +4176,14 @@ const converters1 = { const direction = msg.data.level > globalStore.getValue(msg.endpoint, 'last_brightness') ? 'up' : 'down'; cmd = `${clk}_${direction}`; globalStore.putValue(msg.endpoint, 'last_brightness', msg.data.level); - } else if ( msg.type == 'commandMoveToLevelWithOnOff' ) { + } else if (msg.type == 'commandMoveToLevelWithOnOff') { // This is the 'start' of the 4th button sequence. clk = 'memory'; globalStore.putValue(msg.endpoint, 'last_move_level', msg.data.level); globalStore.putValue(msg.endpoint, 'last_clk', clk); } - if ( clk != 'memory' ) { + if (clk != 'memory') { globalStore.putValue(msg.endpoint, 'last_seq', msg.meta.zclTransactionSequenceNumber); globalStore.putValue(msg.endpoint, 'last_clk', clk); payload.click = clk; @@ -4124,21 +4241,21 @@ const converters1 = { const seq = msg.meta.zclTransactionSequenceNumber; let clk = 'colortemp'; payload.color_temp = msg.data.colortemp; - payload.transition = parseFloat(msg.data.transtime) /10.0; + payload.transition = parseFloat(msg.data.transtime) / 10.0; payload.action_color_temp = msg.data.colortemp; - payload.action_transition = parseFloat(msg.data.transtime) /10.0; + payload.action_transition = parseFloat(msg.data.transtime) / 10.0; // because the remote sends two commands for button4, we need to look at the previous command and // see if it was the recognized start command for button4 - if so, ignore this second command, // because it's not really button3, it's actually button4 - if ( lastClk == 'memory' ) { + if (lastClk == 'memory') { payload.click = lastClk; payload.action = 'recall'; payload.brightness = globalStore.getValue(msg.endpoint, 'last_move_level'); payload.action_brightness = globalStore.getValue(msg.endpoint, 'last_move_level'); // ensure the "last" message was really the message prior to this one // accounts for missed messages (gap >1) and for the remote's rollover from 127 to 0 - if ( (seq == 0 && lastSeq == 127 ) || ( seq - lastSeq ) == 1 ) { + if ((seq == 0 && lastSeq == 127) || seq - lastSeq == 1) { clk = null; } } else { @@ -4152,7 +4269,7 @@ const converters1 = { globalStore.putValue(msg.endpoint, 'last_color_temp', msg.data.colortemp); } - if ( clk != null ) { + if (clk != null) { globalStore.putValue(msg.endpoint, 'last_seq', msg.meta.zclTransactionSequenceNumber); globalStore.putValue(msg.endpoint, 'last_clk', clk); } @@ -4220,10 +4337,16 @@ const converters1 = { if (hasAlreadyProcessedMessage(msg, model, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return; if (commandID === 224) return; const lookup: KeyValueAny = { - 0x22: 'press_1', 0x10: 'press_2', 0x11: 'press_3', 0x12: 'press_4', + 0x22: 'press_1', + 0x10: 'press_2', + 0x11: 'press_3', + 0x12: 'press_4', // Actions below are never generated by a Hue Tap but by a PMT 215Z // https://github.com/Koenkk/zigbee2mqtt/issues/18088 - 0x62: 'press_3_and_4', 0x63: 'release_3_and_4', 0x64: 'press_1_and_2', 0x65: 'release_1_and_2', + 0x62: 'press_3_and_4', + 0x63: 'release_3_and_4', + 0x64: 'press_1_and_2', + 0x65: 'release_1_and_2', }; if (!lookup.hasOwnProperty(commandID)) { logger.error(`Hue Tap: missing command '${commandID}'`, NS); @@ -4254,9 +4377,9 @@ const converters1 = { convert: (model, msg, publish, options, meta) => { const zoneStatus = msg.data.zonestatus; return { - tamper: (zoneStatus & 1<<2) > 0, - battery_low: (zoneStatus & 1<<3) > 0, - restore_reports: (zoneStatus & 1<<5) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, + restore_reports: (zoneStatus & (1 << 5)) > 0, }; }, } satisfies Fz.Converter, @@ -4264,8 +4387,7 @@ const converters1 = { cluster: 'genMultistateInput', type: ['readResponse', 'attributeReport'], convert: (model, msg, publish, options, meta) => { - const lookup: KeyValueAny = {0: 'hold', 1: 'single', 2: 'double', 3: 'triple', - 4: 'quadruple', 255: 'release'}; + const lookup: KeyValueAny = {0: 'hold', 1: 'single', 2: 'double', 3: 'triple', 4: 'quadruple', 255: 'release'}; const clicks = msg.data['presentValue']; const action = lookup[clicks] ? lookup[clicks] : `many`; return {action}; @@ -4308,8 +4430,13 @@ const converters1 = { result.rfid_enable = msg.data[0x4001] == 1 ? true : false; } if (0x4003 in msg.data) { - const lookup: KeyValueAny = {0: 'deactivated', 1: 'random_pin_1x_use', 5: 'random_pin_1x_use', 6: 'random_pin_24_hours', - 9: 'random_pin_24_hours'}; + const lookup: KeyValueAny = { + 0: 'deactivated', + 1: 'random_pin_1x_use', + 5: 'random_pin_1x_use', + 6: 'random_pin_24_hours', + 9: 'random_pin_24_hours', + }; result.service_mode = lookup[msg.data[0x4003]]; } if (0x4004 in msg.data) { @@ -4376,7 +4503,7 @@ const converters1 = { } msg.endpoint.saveClusterAttributeKeyValue('hvacThermostat', {occupiedHeatingSetpoint: occupiedHeatingSetpoint}); - result.occupied_heating_setpoint = occupiedHeatingSetpoint/100; + result.occupied_heating_setpoint = occupiedHeatingSetpoint / 100; } return result; @@ -4399,7 +4526,7 @@ const converters1 = { const response: KeyValueAny = {}; if (msg.data[0] == 0xe010) { // Zone Mode - const lookup: KeyValueAny = {'manual': 1, 'schedule': 2, 'energy_saver': 3, 'holiday': 6}; + const lookup: KeyValueAny = {manual: 1, schedule: 2, energy_saver: 3, holiday: 6}; const zonemodeNum = meta.state.zone_mode ? lookup[meta.state.zone_mode] : 1; response[0xe010] = {value: zonemodeNum, type: 0x30}; await msg.endpoint.readResponse(msg.cluster, msg.meta.zclTransactionSequenceNumber, response, {srcEndpoint: 11}); @@ -4444,8 +4571,8 @@ const converters1 = { const zoneStatus = msg.data.zonestatus; return { action: lookup[zoneStatus], - tamper: (zoneStatus & 1<<2) > 0, - battery_low: (zoneStatus & 1<<3) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, }; }, } satisfies Fz.Converter, @@ -4458,7 +4585,7 @@ const converters1 = { const people = precisionRound(msg.data.presentValue, 0); let result = null; if (value <= 80) { - result = {people: people, status: lookup[value*10%10]}; + result = {people: people, status: lookup[(value * 10) % 10]}; return result; } }, @@ -4467,7 +4594,7 @@ const converters1 = { cluster: 'genOnOff', type: ['commandOn', 'commandOff', 'commandToggle'], convert: (model, msg, publish, options, meta) => { - const lookup: KeyValueAny = {'commandToggle': 'long', 'commandOn': 'double', 'commandOff': 'single'}; + const lookup: KeyValueAny = {commandToggle: 'long', commandOn: 'double', commandOff: 'single'}; let buttonMapping: KeyValueAny = null; if (model.model === 'SBM300ZB2') { buttonMapping = {1: '1', 2: '2'}; @@ -4946,19 +5073,19 @@ const converters2 = { // 3: window open (OO on display, no heating) // 4: window open (OO on display, heating) if (msg.data.hasOwnProperty('viessmannWindowOpenInternal')) { - result.window_open = ((msg.data['viessmannWindowOpenInternal'] == 3) || (msg.data['viessmannWindowOpenInternal'] == 4)); + result.window_open = msg.data['viessmannWindowOpenInternal'] == 3 || msg.data['viessmannWindowOpenInternal'] == 4; } // viessmannWindowOpenForce (rw, bool) if (msg.data.hasOwnProperty('viessmannWindowOpenForce')) { - result.window_open_force = (msg.data['viessmannWindowOpenForce'] == 1); + result.window_open_force = msg.data['viessmannWindowOpenForce'] == 1; } // viessmannAssemblyMode (ro, bool) // 0: TRV installed // 1: TRV ready to install (-- on display) if (msg.data.hasOwnProperty('viessmannAssemblyMode')) { - result.assembly_mode = (msg.data['viessmannAssemblyMode'] == 1); + result.assembly_mode = msg.data['viessmannAssemblyMode'] == 1; } } @@ -4980,11 +5107,14 @@ const converters2 = { // This seems broken... We need to write 0x20 to turn it off and 0x10 to set // it to auto mode. However, when it reports the flag, it will report 0x10 // when it's off, and nothing at all when it's in auto mode - if ((msg.data[0x4008] & 0x10) != 0) { // reports auto -> setting to force_off + if ((msg.data[0x4008] & 0x10) != 0) { + // reports auto -> setting to force_off result.system_mode = constants.thermostatSystemModes[0]; - } else if ((msg.data[0x4008] & 0x04) != 0) { // always_on + } else if ((msg.data[0x4008] & 0x04) != 0) { + // always_on result.system_mode = constants.thermostatSystemModes[4]; - } else { // auto + } else { + // auto result.system_mode = constants.thermostatSystemModes[1]; } } @@ -5039,7 +5169,7 @@ const converters2 = { const sidelookup: KeyValueAny = {5: 'right', 7: 'right', 40: 'left', 56: 'left'}; if (sidelookup[value]) { msg.data.occupancy = 1; - const payload = await converters1.occupancy_with_timeout.convert(model, msg, publish, options, meta) as KeyValueAny; + const payload = (await converters1.occupancy_with_timeout.convert(model, msg, publish, options, meta)) as KeyValueAny; if (payload) { payload.action_side = sidelookup[value]; payload.side = sidelookup[value]; /* legacy: remove this line (replaced by action_side) */ @@ -5113,8 +5243,7 @@ const converters2 = { } if (msg.data.hasOwnProperty(0xe020)) { // wiserSmartCurrentFilPiloteMode - const lookup: KeyValueAny = {0: 'comfort', 1: 'comfort_-1', 2: 'comfort_-2', 3: 'energy_saving', - 4: 'frost_protection', 5: 'off'}; + const lookup: KeyValueAny = {0: 'comfort', 1: 'comfort_-1', 2: 'comfort_-2', 3: 'energy_saving', 4: 'frost_protection', 5: 'off'}; result['fip_setting'] = lookup[msg.data[0xe020]]; } if (msg.data.hasOwnProperty(0xe030)) { @@ -5128,11 +5257,13 @@ const converters2 = { } // Radiator thermostats command changes from UI, but report value periodically for sync, // force an update of the value if it doesn't match the current existing value - if (meta.device.modelID === 'EH-ZB-VACT' && - msg.data.hasOwnProperty('occupiedHeatingSetpoint') && - meta.state.hasOwnProperty('occupied_heating_setpoint')) { + if ( + meta.device.modelID === 'EH-ZB-VACT' && + msg.data.hasOwnProperty('occupiedHeatingSetpoint') && + meta.state.hasOwnProperty('occupied_heating_setpoint') + ) { if (result.occupied_heating_setpoint != meta.state.occupied_heating_setpoint) { - const lookup: KeyValueAny = {'manual': 1, 'schedule': 2, 'energy_saver': 3, 'holiday': 6}; + const lookup: KeyValueAny = {manual: 1, schedule: 2, energy_saver: 3, holiday: 6}; const zonemodeNum = lookup[Number(meta.state.zone_mode)]; const setpoint = Number((Math.round(Number((Number(meta.state.occupied_heating_setpoint) * 2).toFixed(1))) / 2).toFixed(1)) * 100; @@ -5142,11 +5273,15 @@ const converters2 = { setpoint: setpoint, reserved: 0xff, }; - await msg.endpoint.command('hvacThermostat', 'wiserSmartSetSetpoint', payload, - {srcEndpoint: 11, disableDefaultResponse: true}); - - logger.debug(`syncing vact setpoint was: '${result.occupied_heating_setpoint}'` + - ` now: '${meta.state.occupied_heating_setpoint}'`, NS); + await msg.endpoint.command('hvacThermostat', 'wiserSmartSetSetpoint', payload, { + srcEndpoint: 11, + disableDefaultResponse: true, + }); + + logger.debug( + `syncing vact setpoint was: '${result.occupied_heating_setpoint}'` + ` now: '${meta.state.occupied_heating_setpoint}'`, + NS, + ); } } else { publish(result); diff --git a/src/converters/toZigbee.ts b/src/converters/toZigbee.ts index e7ffd6f9c8360..d507d70703ce4 100644 --- a/src/converters/toZigbee.ts +++ b/src/converters/toZigbee.ts @@ -1,13 +1,14 @@ import {Zcl} from 'zigbee-herdsman'; -import {Tz, KeyValue, KeyValueAny} from '../lib/types'; -import * as globalStore from '../lib/store'; -import * as constants from '../lib/constants'; + import * as libColor from '../lib/color'; -import * as utils from '../lib/utils'; -import * as light from '../lib/light'; -import * as legacy from '../lib/legacy'; +import * as constants from '../lib/constants'; import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; +import * as light from '../lib/light'; import {logger} from '../lib/logger'; +import * as globalStore from '../lib/store'; +import {Tz, KeyValue, KeyValueAny} from '../lib/types'; +import * as utils from '../lib/utils'; const NS = 'zhc:tz'; const manufacturerOptions = { @@ -146,8 +147,7 @@ const converters1 = { } await entity.command('lightingColorCtrl', command, zclData, utils.getOptions(meta.mapped, entity)); - return {state: libColor.syncColorState(newState, meta.state, entity, meta.options), - readAfterWriteTime: zclData.transtime * 100}; + return {state: libColor.syncColorState(newState, meta.state, entity, meta.options), readAfterWriteTime: zclData.transtime * 100}; }, convertGet: async (entity, key, meta) => { await entity.read('lightingColorCtrl', light.readColorAttributes(entity, meta)); @@ -158,14 +158,13 @@ const converters1 = { options: [exposes.options.color_sync(), exposes.options.transition()], convertSet: async (entity, key, value, meta) => { const [colorTempMin, colorTempMax] = light.findColorTempRange(entity); - const preset = {'warmest': colorTempMax, 'warm': 454, 'neutral': 370, 'cool': 250, 'coolest': colorTempMin}; + const preset = {warmest: colorTempMax, warm: 454, neutral: 370, cool: 250, coolest: colorTempMin}; if (key === 'color_temp_percent') { utils.assertNumber(value); - value = utils.mapNumberRange(value, - 0, 100, - ((colorTempMin != null) ? colorTempMin : 154), ((colorTempMax != null) ? colorTempMax : 500), - ).toString(); + value = utils + .mapNumberRange(value, 0, 100, colorTempMin != null ? colorTempMin : 154, colorTempMax != null ? colorTempMax : 500) + .toString(); } if (utils.isString(value) && value in preset) { @@ -181,8 +180,8 @@ const converters1 = { const payload = {colortemp: value, transtime: utils.getTransition(entity, key, meta).time}; await entity.command('lightingColorCtrl', 'moveToColorTemp', payload, utils.getOptions(meta.mapped, entity)); return { - state: libColor.syncColorState({'color_mode': constants.colorModeLookup[2], 'color_temp': value}, meta.state, - entity, meta.options), readAfterWriteTime: payload.transtime * 100, + state: libColor.syncColorState({color_mode: constants.colorModeLookup[2], color_temp: value}, meta.state, entity, meta.options), + readAfterWriteTime: payload.transtime * 100, }; }, convertGet: async (entity, key, meta) => { @@ -197,7 +196,7 @@ const converters2 = { key: ['read'], convertSet: async (entity, key, value, meta) => { utils.assertObject(value, key); - const result = await entity.read(value.cluster, value.attributes, (value.hasOwnProperty('options') ? value.options : {})); + const result = await entity.read(value.cluster, value.attributes, value.hasOwnProperty('options') ? value.options : {}); logger.info(`Read result of '${value.cluster}': ${JSON.stringify(result)}`, NS); if (value.hasOwnProperty('state_property')) { return {state: {[value.state_property]: result}}; @@ -221,7 +220,7 @@ const converters2 = { convertSet: async (entity, key, value, meta) => { utils.assertObject(value, key); const options = utils.getOptions(meta.mapped, entity); - await entity.command(value.cluster, value.command, (value.hasOwnProperty('payload') ? value.payload : {}), options); + await entity.command(value.cluster, value.command, value.hasOwnProperty('payload') ? value.payload : {}, options); logger.info(`Invoked '${value.cluster}.${value.command}' with payload '${JSON.stringify(value.payload)}'`, NS); }, } satisfies Tz.Converter, @@ -244,9 +243,9 @@ const converters2 = { key: ['zclcommand'], convertSet: async (entity, key, value, meta) => { utils.assertObject(value, key); - const payload = (value.hasOwnProperty('payload') ? value.payload : {}); + const payload = value.hasOwnProperty('payload') ? value.payload : {}; utils.assertEndpoint(entity); - await entity.zclCommand(value.cluster, value.command, payload, (value.hasOwnProperty('options') ? value.options : {})); + await entity.zclCommand(value.cluster, value.command, payload, value.hasOwnProperty('options') ? value.options : {}); logger.info(`Invoked ZCL command ${value.cluster}.${value.command} with payload '${JSON.stringify(payload)}'`, NS); }, } satisfies Tz.Converter, @@ -299,7 +298,7 @@ const converters2 = { convertSet: async (entity, key, value, meta) => { utils.assertString(value, key); value = value.toLowerCase(); - const lookup = {'off': 0, 'on': 1, 'toggle': 2, 'previous': 255}; + const lookup = {off: 0, on: 1, toggle: 2, previous: 255}; await entity.write('genOnOff', {startUpOnOff: utils.getFromLookup(value, lookup)}, utils.getOptions(meta.mapped, entity)); return {state: {power_on_behavior: value}}; }, @@ -317,9 +316,9 @@ const converters2 = { key: ['color_options'], convertSet: async (entity, key, value, meta) => { utils.assertObject(value, key); - const options = (value.hasOwnProperty('execute_if_off') && value.execute_if_off) ? 1 : 0; + const options = value.hasOwnProperty('execute_if_off') && value.execute_if_off ? 1 : 0; await entity.write('lightingColorCtrl', {options}, utils.getOptions(meta.mapped, entity)); - return {state: {'color_options': value}}; + return {state: {color_options: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('lightingColorCtrl', ['options']); @@ -329,12 +328,7 @@ const converters2 = { key: ['state'], convertSet: async (entity, key, value, meta) => { utils.assertString(value, key); - await entity.command( - 'closuresDoorLock', - `${value.toLowerCase()}Door`, - {'pincodevalue': ''}, - utils.getOptions(meta.mapped, entity), - ); + await entity.command('closuresDoorLock', `${value.toLowerCase()}Door`, {pincodevalue: ''}, utils.getOptions(meta.mapped, entity)); return {readAfterWriteTime: 200}; }, @@ -357,8 +351,7 @@ const converters2 = { convertSet: async (entity, key, value, meta) => { utils.assertString(value, key); utils.validateValue(value, constants.lockSoundVolume); - await entity.write('closuresDoorLock', - {soundVolume: constants.lockSoundVolume.indexOf(value)}, utils.getOptions(meta.mapped, entity)); + await entity.write('closuresDoorLock', {soundVolume: constants.lockSoundVolume.indexOf(value)}, utils.getOptions(meta.mapped, entity)); return {state: {sound_volume: value}}; }, convertGet: async (entity, key, meta) => { @@ -378,15 +371,15 @@ const converters2 = { if (!utils.isInRange(0, pinCodeCount - 1, user)) throw new Error('user must be in range for device'); if (pinCode == null) { - await entity.command('closuresDoorLock', 'clearPinCode', {'userid': user}, utils.getOptions(meta.mapped, entity)); + await entity.command('closuresDoorLock', 'clearPinCode', {userid: user}, utils.getOptions(meta.mapped, entity)); } else { if (isNaN(pinCode)) throw new Error('pinCode must be a number'); - const typeLookup = {'unrestricted': 0, 'year_day_schedule': 1, 'week_day_schedule': 2, 'master': 3, 'non_access': 4}; + const typeLookup = {unrestricted: 0, year_day_schedule: 1, week_day_schedule: 2, master: 3, non_access: 4}; const payload = { - 'userid': user, - 'userstatus': userEnabled ? 1 : 3, - 'usertype': utils.getFromLookup(userType, typeLookup), - 'pincodevalue': pinCode.toString(), + userid: user, + userstatus: userEnabled ? 1 : 3, + usertype: utils.getFromLookup(userType, typeLookup), + pincodevalue: pinCode.toString(), }; await entity.command('closuresDoorLock', 'setPinCode', payload, utils.getOptions(meta.mapped, entity)); } @@ -437,8 +430,8 @@ const converters2 = { 'closuresDoorLock', 'setUserStatus', { - 'userid': user, - 'userstatus': status, + userid: user, + userstatus: status, }, utils.getOptions(meta.mapped, entity), ); @@ -477,12 +470,13 @@ const converters2 = { await entity.command('genLevelCtrl', 'stop', {}, utils.getOptions(meta.mapped, entity)); return; } - const lookup = {'open': 100, 'close': 0}; + const lookup = {open: 100, close: 0}; value = utils.getFromLookup(value, lookup); } - const invert = utils.getMetaValue(entity, meta.mapped, 'coverInverted', 'allEqual', false) ? - !meta.options.invert_cover : meta.options.invert_cover; + const invert = utils.getMetaValue(entity, meta.mapped, 'coverInverted', 'allEqual', false) + ? !meta.options.invert_cover + : meta.options.invert_cover; utils.assertNumber(value); const position = invert ? 100 - value : value; await entity.command( @@ -501,9 +495,9 @@ const converters2 = { warning: { key: ['warning'], convertSet: async (entity, key, value, meta) => { - const mode = {'stop': 0, 'burglar': 1, 'fire': 2, 'emergency': 3, 'police_panic': 4, 'fire_panic': 5, 'emergency_panic': 6}; - const level = {'low': 0, 'medium': 1, 'high': 2, 'very_high': 3}; - const strobeLevel = {'low': 0, 'medium': 1, 'high': 2, 'very_high': 3}; + const mode = {stop: 0, burglar: 1, fire: 2, emergency: 3, police_panic: 4, fire_panic: 5, emergency_panic: 6}; + const level = {low: 0, medium: 1, high: 2, very_high: 3}; + const strobeLevel = {low: 0, medium: 1, high: 2, very_high: 3}; const values = { // @ts-expect-error @@ -524,16 +518,15 @@ const converters2 = { // https://github.com/Koenkk/zigbee2mqtt/issues/8310 some devices require the info to be reversed. if (Array.isArray(meta.mapped)) throw new Error(`Not supported for groups`); if (['SIRZB-110', 'SRAC-23B-ZBSR', 'AV2010/29A', 'AV2010/24A'].includes(meta.mapped.model)) { - info = (utils.getFromLookup(values.mode, mode)) + ((values.strobe ? 1 : 0) << 4) + (utils.getFromLookup(values.level, level) << 6); + info = utils.getFromLookup(values.mode, mode) + ((values.strobe ? 1 : 0) << 4) + (utils.getFromLookup(values.level, level) << 6); } else { - info = (utils.getFromLookup(values.mode, mode) << 4) + ((values.strobe ? 1 : 0) << 2) + (utils.getFromLookup(values.level, level)); + info = (utils.getFromLookup(values.mode, mode) << 4) + ((values.strobe ? 1 : 0) << 2) + utils.getFromLookup(values.level, level); } await entity.command( 'ssIasWd', 'startWarning', - {startwarninginfo: info, warningduration: values.duration, - strobedutycycle: values.strobeDutyCycle, strobelevel: values.strobeLevel}, + {startwarninginfo: info, warningduration: values.duration, strobedutycycle: values.strobeDutyCycle, strobelevel: values.strobeLevel}, utils.getOptions(meta.mapped, entity), ); }, @@ -541,7 +534,7 @@ const converters2 = { ias_max_duration: { key: ['max_duration'], convertSet: async (entity, key, value, meta) => { - await entity.write('ssIasWd', {'maxDuration': value}); + await entity.write('ssIasWd', {maxDuration: value}); return {state: {max_duration: value}}; }, convertGet: async (entity, key, meta) => { @@ -551,15 +544,15 @@ const converters2 = { warning_simple: { key: ['alarm'], convertSet: async (entity, key, value, meta) => { - const alarmState = (value === 'alarm' || value === 'OFF' ? 0 : 1); + const alarmState = value === 'alarm' || value === 'OFF' ? 0 : 1; let info; // For Develco SMSZB-120 and HESZB-120, introduced change in fw 4.0.5, tested backward with 4.0.4 if (Array.isArray(meta.mapped)) throw new Error(`Not supported for groups`); if (['SMSZB-120', 'HESZB-120'].includes(meta.mapped.model)) { - info = ((alarmState) << 7) + ((alarmState) << 6); + info = (alarmState << 7) + (alarmState << 6); } else { - info = (3 << 6) + ((alarmState) << 2); + info = (3 << 6) + (alarmState << 2); } await entity.command( @@ -574,22 +567,21 @@ const converters2 = { key: ['squawk'], convertSet: async (entity, key, value, meta) => { utils.assertObject(value, key); - const state = {'system_is_armed': 0, 'system_is_disarmed': 1}; - const level = {'low': 0, 'medium': 1, 'high': 2, 'very_high': 3}; + const state = {system_is_armed: 0, system_is_disarmed: 1}; + const level = {low: 0, medium: 1, high: 2, very_high: 3}; const values = { state: value.state, level: value.level || 'very_high', strobe: value.hasOwnProperty('strobe') ? value.strobe : false, }; - const info = (utils.getFromLookup(values.state, state)) + ((values.strobe ? 1 : 0) << 4) + - (utils.getFromLookup(values.level, level) << 6); + const info = utils.getFromLookup(values.state, state) + ((values.strobe ? 1 : 0) << 4) + (utils.getFromLookup(values.level, level) << 6); await entity.command('ssIasWd', 'squawk', {squawkinfo: info}, utils.getOptions(meta.mapped, entity)); }, } satisfies Tz.Converter, cover_state: { key: ['state'], convertSet: async (entity, key, value, meta) => { - const lookup = {'open': 'upOpen', 'close': 'downClose', 'stop': 'stop', 'on': 'upOpen', 'off': 'downClose'}; + const lookup = {open: 'upOpen', close: 'downClose', stop: 'stop', on: 'upOpen', off: 'downClose'}; utils.assertString(value, key); value = value.toLowerCase(); await entity.command('closuresWindowCovering', utils.getFromLookup(value, lookup), {}, utils.getOptions(meta.mapped, entity)); @@ -597,17 +589,16 @@ const converters2 = { } satisfies Tz.Converter, cover_position_tilt: { key: ['position', 'tilt'], - options: [ - exposes.options.invert_cover(), - exposes.options.cover_position_tilt_disable_report(), - ], + options: [exposes.options.invert_cover(), exposes.options.cover_position_tilt_disable_report()], convertSet: async (entity, key, value, meta) => { utils.assertNumber(value, key); - const isPosition = (key === 'position'); - const invert = !(utils.getMetaValue(entity, meta.mapped, 'coverInverted', 'allEqual', false) ? - !meta.options.invert_cover : meta.options.invert_cover); - const disableReport = (utils.getMetaValue(entity, meta.mapped, 'coverPositionTiltDisableReport', 'allEqual', false) ? - !meta.options.cover_position_tilt_disable_report : meta.options.cover_position_tilt_disable_report); + const isPosition = key === 'position'; + const invert = !(utils.getMetaValue(entity, meta.mapped, 'coverInverted', 'allEqual', false) + ? !meta.options.invert_cover + : meta.options.invert_cover); + const disableReport = utils.getMetaValue(entity, meta.mapped, 'coverPositionTiltDisableReport', 'allEqual', false) + ? !meta.options.cover_position_tilt_disable_report + : meta.options.cover_position_tilt_disable_report; const position = invert ? 100 - value : value; // Zigbee officially expects 'open' to be 0 and 'closed' to be 100 whereas @@ -626,7 +617,7 @@ const converters2 = { } }, convertGet: async (entity, key, meta) => { - const isPosition = (key === 'position'); + const isPosition = key === 'position'; await entity.read('closuresWindowCovering', [isPosition ? 'currentPositionLiftPercentage' : 'currentPositionTiltPercentage']); }, } satisfies Tz.Converter, @@ -635,10 +626,10 @@ const converters2 = { convertSet: async (entity, key, value, meta) => { utils.assertObject(value, key); const windowCoveringMode = - (value.reversed ? 1 : 0) << 0 | - (value.calibration ? 1 : 0) << 1 | - (value.maintenance ? 1 : 0) << 2 | - (value.led ? 1 : 0) << 3; + ((value.reversed ? 1 : 0) << 0) | + ((value.calibration ? 1 : 0) << 1) | + ((value.maintenance ? 1 : 0) << 2) | + ((value.led ? 1 : 0) << 3); await entity.write('closuresWindowCovering', {windowCoveringMode}, utils.getOptions(meta.mapped, entity)); return {state: {cover_mode: value}}; }, @@ -784,10 +775,7 @@ const converters2 = { } }, convertGet: async (entity, key, meta) => { - for (const attribute of [ - 'onOffTransitionTime', 'onTransitionTime', 'offTransitionTime', 'startUpCurrentLevel', - 'onLevel', 'options', - ]) { + for (const attribute of ['onOffTransitionTime', 'onTransitionTime', 'offTransitionTime', 'startUpCurrentLevel', 'onLevel', 'options']) { try { await entity.read('genLevelCtrl', [attribute]); } catch (ex) { @@ -797,10 +785,7 @@ const converters2 = { }, } satisfies Tz.Converter, ballast_config: { - key: ['ballast_config', - 'ballast_minimum_level', - 'ballast_maximum_level', - 'ballast_power_on_level'], + key: ['ballast_config', 'ballast_minimum_level', 'ballast_maximum_level', 'ballast_power_on_level'], // zcl attribute names are camel case, but we want to use snake case in the outside communication convertSet: async (entity, key, value, meta) => { if (key === 'ballast_config') { @@ -811,13 +796,13 @@ const converters2 = { } } if (key === 'ballast_minimum_level') { - await entity.write('lightingBallastCfg', {'minLevel': value}); + await entity.write('lightingBallastCfg', {minLevel: value}); } if (key === 'ballast_maximum_level') { - await entity.write('lightingBallastCfg', {'maxLevel': value}); + await entity.write('lightingBallastCfg', {maxLevel: value}); } if (key === 'ballast_power_on_level') { - await entity.write('lightingBallastCfg', {'powerOnLevel': value}); + await entity.write('lightingBallastCfg', {powerOnLevel: value}); } return {state: {[key]: value}}; }, @@ -928,7 +913,7 @@ const converters2 = { // - Color mode could have been switched (x/y or hue/saturation) const entityToRead = utils.getEntityOrFirstGroupMember(entity); if (entityToRead) { - await utils.sleep(100 + (transition * 100)); + await utils.sleep(100 + transition * 100); await entityToRead.read('lightingColorCtrl', ['colorTemperature']); } }, @@ -992,8 +977,8 @@ const converters2 = { }; await entity.command('lightingColorCtrl', 'moveToColor', payload, utils.getOptions(meta.mapped, entity)); return { - state: libColor.syncColorState({'color_mode': constants.colorModeLookup[2], 'color_temp': value}, meta.state, - entity, meta.options), readAfterWriteTime: payload.transtime * 100, + state: libColor.syncColorState({color_mode: constants.colorModeLookup[2], color_temp: value}, meta.state, entity, meta.options), + readAfterWriteTime: payload.transtime * 100, }; } }, @@ -1019,7 +1004,7 @@ const converters2 = { // - Color mode could have been switched (x/y or colortemp) const entityToRead = utils.getEntityOrFirstGroupMember(entity); if (entityToRead) { - await utils.sleep(100 + (transition * 100)); + await utils.sleep(100 + transition * 100); await entityToRead.read('lightingColorCtrl', [attribute, 'colorMode']); } }, @@ -1152,7 +1137,7 @@ const converters2 = { if (brightness === undefined) { // Converting the type to a generic one so we can set readAfterWriteTime and state.brightness without errors - const result = await converters1.on_off.convertSet(entity, 'state', state, meta) as KeyValueAny; + const result = (await converters1.on_off.convertSet(entity, 'state', state, meta)) as KeyValueAny; if (result) { result.readAfterWriteTime = 0; if (result.state && result.state.state === 'ON' && meta.state.brightness === 0) { @@ -1201,7 +1186,7 @@ const converters2 = { key: ['color_temp_startup'], convertSet: async (entity, key, value, meta) => { const [colorTempMin, colorTempMax] = light.findColorTempRange(entity); - const preset = {'warmest': colorTempMax, 'warm': 454, 'neutral': 370, 'cool': 250, 'coolest': colorTempMin, 'previous': 65535}; + const preset = {warmest: colorTempMax, warm: 454, neutral: 370, cool: 250, coolest: colorTempMin, previous: 65535}; if (utils.isString(value) && value in preset) { value = utils.getFromLookup(value, preset); @@ -1269,9 +1254,10 @@ const converters2 = { const payload = {effectid: utils.getFromLookup(value, lookup), effectvariant: 0}; await entity.command('genIdentify', 'triggerEffect', payload, utils.getOptions(meta.mapped, entity)); } - } else if (key === 'alert' || key === 'flash') { // Deprecated + } else if (key === 'alert' || key === 'flash') { + // Deprecated let effectid = 0; - const lookup = {'select': 0x00, 'lselect': 0x01, 'none': 0xFF}; + const lookup = {select: 0x00, lselect: 0x01, none: 0xff}; if (key === 'flash') { if (value === 2) { value = 'select'; @@ -1332,7 +1318,8 @@ const converters2 = { if (typeof value.numoftrans !== 'undefined') { logger.warning( `weekly_schedule: ignoring provided numoftrans value (${JSON.stringify(value.numoftrans)}), ` + - 'this is now calculated automatically', NS, + 'this is now calculated automatically', + NS, ); } payload.numoftrans = payload.transitions.length; @@ -1340,8 +1327,8 @@ const converters2 = { // mode is calculated below if (typeof value.mode !== 'undefined') { logger.warning( - `weekly_schedule: ignoring provided mode value (${JSON.stringify(value.mode)}), ` + - 'this is now calculated automatically', NS, + `weekly_schedule: ignoring provided mode value (${JSON.stringify(value.mode)}), ` + 'this is now calculated automatically', + NS, ); } payload.mode = []; @@ -1367,36 +1354,28 @@ const converters2 = { // accept 24h time notation (e.g. 19:30) if (typeof elem['transitionTime'] === 'string') { const time = elem['transitionTime'].split(':'); - const timeHour = (parseInt(time[0]) * 60); + const timeHour = parseInt(time[0]) * 60; const timeMinute = parseInt(time[1]); - if ((time.length != 2) || isNaN(timeHour) || isNaN(timeMinute)) { - logger.warning( - `weekly_schedule: expected 24h time notation (e.g. 19:30) but got '${elem['transitionTime']}'!`, NS, - ); + if (time.length != 2 || isNaN(timeHour) || isNaN(timeMinute)) { + logger.warning(`weekly_schedule: expected 24h time notation (e.g. 19:30) but got '${elem['transitionTime']}'!`, NS); } else { - elem['transitionTime'] = (timeHour + timeMinute); + elem['transitionTime'] = timeHour + timeMinute; } } else if (typeof elem['transitionTime'] === 'object') { if (!elem['transitionTime'].hasOwnProperty('hour') || !elem['transitionTime'].hasOwnProperty('minute')) { throw new Error( 'weekly_schedule: expected 24h time object (e.g. {"hour": 19, "minute": 30}), ' + - `but got '${JSON.stringify(elem['transitionTime'])}'!`, + `but got '${JSON.stringify(elem['transitionTime'])}'!`, ); } else if (isNaN(elem['transitionTime']['hour'])) { - throw new Error( - 'weekly_schedule: expected time.hour to be a number, ' + - `but got '${elem['transitionTime']['hour']}'!`, - ); + throw new Error('weekly_schedule: expected time.hour to be a number, ' + `but got '${elem['transitionTime']['hour']}'!`); } else if (isNaN(elem['transitionTime']['minute'])) { throw new Error( - 'weekly_schedule: expected time.minute to be a number, ' + - `but got '${elem['transitionTime']['minute']}'!`, + 'weekly_schedule: expected time.minute to be a number, ' + `but got '${elem['transitionTime']['minute']}'!`, ); } else { - elem['transitionTime'] = ( - (parseInt(elem['transitionTime']['hour']) * 60) + parseInt(elem['transitionTime']['minute']) - ); + elem['transitionTime'] = parseInt(elem['transitionTime']['hour']) * 60 + parseInt(elem['transitionTime']['minute']); } } } @@ -1410,7 +1389,7 @@ const converters2 = { for (let m of payload.mode) { // lookup mode bit m = utils.getKey(constants.thermostatScheduleMode, m.toLowerCase(), m, Number); - mode |= (1 << m); + mode |= 1 << m; } payload.mode = mode; @@ -1422,15 +1401,14 @@ const converters2 = { if (typeof d === 'object') { if (!d.hasOwnProperty('day')) { throw new Error( - 'weekly_schedule: expected dayofweek to be string or {"day": "str"}, ' + - `but got '${JSON.stringify(d)}'!`, + 'weekly_schedule: expected dayofweek to be string or {"day": "str"}, ' + `but got '${JSON.stringify(d)}'!`, ); } d = d.day; } // lookup dayofweek bit d = utils.getKey(constants.thermostatDayOfWeek, d.toLowerCase(), d, Number); - dayofweek |= (1 << d); + dayofweek |= 1 << d; } payload.dayofweek = dayofweek; } @@ -1497,8 +1475,9 @@ const converters2 = { convertSet: async (entity, key, value, meta) => { const val = utils.getKey(constants.thermostatProgrammingOperationModes, value, undefined, Number); if (val === undefined) { - throw new Error('Programming operation mode invalid, must be one of: ' + - Object.values(constants.thermostatProgrammingOperationModes).join(', ')); + throw new Error( + 'Programming operation mode invalid, must be one of: ' + Object.values(constants.thermostatProgrammingOperationModes).join(', '), + ); } await entity.write('hvacThermostat', {programingOperMode: val}); return {state: {programming_operation_mode: value}}; @@ -1864,7 +1843,7 @@ const converters2 = { elko_load: { key: ['load'], convertSet: async (entity, key, value, meta) => { - await entity.write('hvacThermostat', {'elkoLoad': value}); + await entity.write('hvacThermostat', {elkoLoad: value}); return {state: {load: value}}; }, convertGet: async (entity, key, meta) => { @@ -1876,7 +1855,7 @@ const converters2 = { convertSet: async (entity, key, value, meta) => { utils.assertString(value); if (value.length <= 14) { - await entity.write('hvacThermostat', {'elkoDisplayText': value}); + await entity.write('hvacThermostat', {elkoDisplayText: value}); return {state: {display_text: value}}; } else { throw new Error('Length of text is greater than 14'); @@ -1889,7 +1868,7 @@ const converters2 = { elko_power_status: { key: ['system_mode'], convertSet: async (entity, key, value, meta) => { - await entity.write('hvacThermostat', {'elkoPowerStatus': value === 'heat'}); + await entity.write('hvacThermostat', {elkoPowerStatus: value === 'heat'}); return {state: {system_mode: value}}; }, convertGet: async (entity, key, meta) => { @@ -1911,7 +1890,7 @@ const converters2 = { elko_child_lock: { key: ['child_lock'], convertSet: async (entity, key, value, meta) => { - await entity.write('hvacThermostat', {'elkoChildLock': value === 'lock'}); + await entity.write('hvacThermostat', {elkoChildLock: value === 'lock'}); return {state: {child_lock: value}}; }, convertGet: async (entity, key, meta) => { @@ -1921,7 +1900,7 @@ const converters2 = { elko_frost_guard: { key: ['frost_guard'], convertSet: async (entity, key, value, meta) => { - await entity.write('hvacThermostat', {'elkoFrostGuard': value === 'on'}); + await entity.write('hvacThermostat', {elkoFrostGuard: value === 'on'}); return {state: {frost_guard: value}}; }, convertGet: async (entity, key, meta) => { @@ -1931,7 +1910,7 @@ const converters2 = { elko_night_switching: { key: ['night_switching'], convertSet: async (entity, key, value, meta) => { - await entity.write('hvacThermostat', {'elkoNightSwitching': value === 'on'}); + await entity.write('hvacThermostat', {elkoNightSwitching: value === 'on'}); return {state: {night_switching: value}}; }, convertGet: async (entity, key, meta) => { @@ -1947,14 +1926,14 @@ const converters2 = { elko_sensor_mode: { key: ['sensor'], convertSet: async (entity, key, value, meta) => { - await entity.write('hvacThermostat', {'elkoSensor': utils.getFromLookup(value, {'air': '0', 'floor': '1', 'supervisor_floor': '3'})}); + await entity.write('hvacThermostat', {elkoSensor: utils.getFromLookup(value, {air: '0', floor: '1', supervisor_floor: '3'})}); return {state: {sensor: value}}; }, } satisfies Tz.Converter, elko_regulator_time: { key: ['regulator_time'], convertSet: async (entity, key, value, meta) => { - await entity.write('hvacThermostat', {'elkoRegulatorTime': value}); + await entity.write('hvacThermostat', {elkoRegulatorTime: value}); return {state: {sensor: value}}; }, convertGet: async (entity, key, meta) => { @@ -1964,7 +1943,7 @@ const converters2 = { elko_regulator_mode: { key: ['regulator_mode'], convertSet: async (entity, key, value, meta) => { - await entity.write('hvacThermostat', {'elkoRegulatorMode': value === 'regulator'}); + await entity.write('hvacThermostat', {elkoRegulatorMode: value === 'regulator'}); return {state: {regulator_mode: value}}; }, convertGet: async (entity, key, meta) => { @@ -1975,7 +1954,7 @@ const converters2 = { key: ['local_temperature_calibration'], convertSet: async (entity, key, value, meta) => { utils.assertNumber(value, key); - await entity.write('hvacThermostat', {'elkoCalibration': Math.round(value * 10)}); + await entity.write('hvacThermostat', {elkoCalibration: Math.round(value * 10)}); return {state: {local_temperature_calibration: value}}; }, convertGet: async (entity, key, meta) => { @@ -1987,7 +1966,7 @@ const converters2 = { convertSet: async (entity, key, value, meta) => { utils.assertArray(value); if (value.length <= 14) { - await entity.write('hvacThermostat', {'elkoMaxFloorTemp': value}); + await entity.write('hvacThermostat', {elkoMaxFloorTemp: value}); return {state: {max_floor_temp: value}}; } }, @@ -2020,34 +1999,46 @@ const converters2 = { const payloadOffBottomRight = {0x0001: {value: Buffer.from([0, 0, 0, 0, 0, 0, 0, 0]), type: 136}}; if (postfix === 'left') { await entity.command('genLevelCtrl', 'moveToLevelWithOnOff', {level: oldstate, transtime: channel}); - await entity.write('genPowerCfg', (state === 'on') ? payloadOn : payloadOff, - { - manufacturerCode: 0x1ad2, disableDefaultResponse: true, disableResponse: true, - reservedBits: 3, direction: 1, transactionSequenceNumber: 0xe9, - }); + await entity.write('genPowerCfg', state === 'on' ? payloadOn : payloadOff, { + manufacturerCode: 0x1ad2, + disableDefaultResponse: true, + disableResponse: true, + reservedBits: 3, + direction: 1, + transactionSequenceNumber: 0xe9, + }); return {state: {state: value.toUpperCase()}, readAfterWriteTime: 250}; } else if (postfix === 'right') { channel = 2.0; await entity.command('genLevelCtrl', 'moveToLevelWithOnOff', {level: oldstate, transtime: channel}); - await entity.write('genPowerCfg', (state === 'on') ? payloadOnRight : payloadOffRight, - { - manufacturerCode: 0x1ad2, disableDefaultResponse: true, disableResponse: true, - reservedBits: 3, direction: 1, transactionSequenceNumber: 0xe9, - }); + await entity.write('genPowerCfg', state === 'on' ? payloadOnRight : payloadOffRight, { + manufacturerCode: 0x1ad2, + disableDefaultResponse: true, + disableResponse: true, + reservedBits: 3, + direction: 1, + transactionSequenceNumber: 0xe9, + }); return {state: {state: value.toUpperCase()}, readAfterWriteTime: 250}; } else if (postfix === 'bottom_right') { - await entity.write('genPowerCfg', (state === 'on') ? payloadOnBottomRight : payloadOffBottomRight, - { - manufacturerCode: 0x1ad2, disableDefaultResponse: true, disableResponse: true, - reservedBits: 3, direction: 1, transactionSequenceNumber: 0xe9, - }); + await entity.write('genPowerCfg', state === 'on' ? payloadOnBottomRight : payloadOffBottomRight, { + manufacturerCode: 0x1ad2, + disableDefaultResponse: true, + disableResponse: true, + reservedBits: 3, + direction: 1, + transactionSequenceNumber: 0xe9, + }); return {state: {state: value.toUpperCase()}, readAfterWriteTime: 250}; } else if (postfix === 'bottom_left') { - await entity.write('genPowerCfg', (state === 'on') ? payloadOnBottomLeft : payloadOffBottomLeft, - { - manufacturerCode: 0x1ad2, disableDefaultResponse: true, disableResponse: true, - reservedBits: 3, direction: 1, transactionSequenceNumber: 0xe9, - }); + await entity.write('genPowerCfg', state === 'on' ? payloadOnBottomLeft : payloadOffBottomLeft, { + manufacturerCode: 0x1ad2, + disableDefaultResponse: true, + disableResponse: true, + reservedBits: 3, + direction: 1, + transactionSequenceNumber: 0xe9, + }); return {state: {state: value.toUpperCase()}, readAfterWriteTime: 250}; } return {state: {state: value.toUpperCase()}, readAfterWriteTime: 250}; @@ -2107,13 +2098,17 @@ const converters2 = { } await entity.command('genOnOff', 'toggle', {}, {transactionSequenceNumber: 0}); const payload = {0x0301: {value: Buffer.from([newValue, 0, 0, 0, 0, 0, 0, 0]), type: 1}}; - await entity.write('genPowerCfg', payload, - { - manufacturerCode: 0x1ad2, disableDefaultResponse: true, disableResponse: true, - reservedBits: 3, direction: 1, transactionSequenceNumber: 0xe9, writeUndiv: true, - }); + await entity.write('genPowerCfg', payload, { + manufacturerCode: 0x1ad2, + disableDefaultResponse: true, + disableResponse: true, + reservedBits: 3, + direction: 1, + transactionSequenceNumber: 0xe9, + writeUndiv: true, + }); return { - state: {brightness_percent: newValue, brightness: utils.mapNumberRange(newValue, 0, 100, 0, 255), level: (newValue * 10)}, + state: {brightness_percent: newValue, brightness: utils.mapNumberRange(newValue, 0, 100, 0, 255), level: newValue * 10}, readAfterWriteTime: 250, }; }, @@ -2127,25 +2122,27 @@ const converters2 = { utils.assertEndpoint(entity); let payload; const options = { - frameType: 0, manufacturerCode: 0x1ad2, disableDefaultResponse: true, - disableResponse: true, reservedBits: 3, direction: 1, writeUndiv: true, + frameType: 0, + manufacturerCode: 0x1ad2, + disableDefaultResponse: true, + disableResponse: true, + reservedBits: 3, + direction: 1, + writeUndiv: true, transactionSequenceNumber: 0xe9, }; switch (value) { - case 'OPEN': - payload = - {attrId: 0x0000, selector: null, elementData: [0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]}; - break; - case 'CLOSE': - payload = - {attrId: 0x0000, selector: null, elementData: [0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]}; - break; - case 'STOP': - payload = - {attrId: 0x0000, selector: null, elementData: [0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]}; - break; - default: - throw new Error(`Value '${value}' is not a valid cover position (must be one of 'OPEN' or 'CLOSE')`); + case 'OPEN': + payload = {attrId: 0x0000, selector: null, elementData: [0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]}; + break; + case 'CLOSE': + payload = {attrId: 0x0000, selector: null, elementData: [0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]}; + break; + case 'STOP': + payload = {attrId: 0x0000, selector: null, elementData: [0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]}; + break; + default: + throw new Error(`Value '${value}' is not a valid cover position (must be one of 'OPEN' or 'CLOSE')`); } await entity.writeStructured('genPowerCfg', [payload], options); return { @@ -2163,11 +2160,15 @@ const converters2 = { const position = 100 - value; await entity.command('genOnOff', 'toggle', {}, {transactionSequenceNumber: 0}); const payload = {0x0401: {value: [position, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], type: 1}}; - await entity.write('genPowerCfg', payload, - { - manufacturerCode: 0x1ad2, disableDefaultResponse: true, disableResponse: true, - reservedBits: 3, direction: 1, transactionSequenceNumber: 0xe9, writeUndiv: true, - }); + await entity.write('genPowerCfg', payload, { + manufacturerCode: 0x1ad2, + disableDefaultResponse: true, + disableResponse: true, + reservedBits: 3, + direction: 1, + transactionSequenceNumber: 0xe9, + writeUndiv: true, + }); return { state: { position: value, @@ -2182,27 +2183,31 @@ const converters2 = { convertSet: async (entity, key, value, meta) => { utils.assertObject(value); const options = { - frameType: 0, manufacturerCode: 0x1ad2, disableDefaultResponse: true, - disableResponse: true, reservedBits: 3, direction: 1, writeUndiv: true, + frameType: 0, + manufacturerCode: 0x1ad2, + disableDefaultResponse: true, + disableResponse: true, + reservedBits: 3, + direction: 1, + writeUndiv: true, transactionSequenceNumber: 0xe9, }; if (value.hasOwnProperty('motor_direction')) { let direction; switch (value.motor_direction) { - case 'FORWARD': - direction = 0x00; - break; - case 'REVERSE': - direction = 0x80; - break; - default: - throw new Error(`livolo_cover_options: ${value.motor_direction} is not a valid motor direction \ + case 'FORWARD': + direction = 0x00; + break; + case 'REVERSE': + direction = 0x80; + break; + default: + throw new Error(`livolo_cover_options: ${value.motor_direction} is not a valid motor direction \ (must be one of 'FORWARD' or 'REVERSE')`); } - const payload = - {0x1301: {value: [direction, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]}}; + const payload = {0x1301: {value: [direction, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]}}; await entity.write('genPowerCfg', payload, options); } @@ -2210,8 +2215,7 @@ const converters2 = { if (value.motor_speed < 20 || value.motor_speed > 40) { throw new Error('livolo_cover_options: Motor speed is out of range (20-40)'); } - const payload = - {0x1201: {value: [value.motor_speed, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]}}; + const payload = {0x1201: {value: [value.motor_speed, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]}}; await entity.write('genPowerCfg', payload, options); } }, @@ -2219,16 +2223,19 @@ const converters2 = { ZigUP_lock: { key: ['led'], convertSet: async (entity, key, value, meta) => { - const lookup = {'off': 'lockDoor', 'on': 'unlockDoor', 'toggle': 'toggleDoor'}; - await entity.command('closuresDoorLock', utils.getFromLookup(value, lookup), {'pincodevalue': ''}); + const lookup = {off: 'lockDoor', on: 'unlockDoor', toggle: 'toggleDoor'}; + await entity.command('closuresDoorLock', utils.getFromLookup(value, lookup), {pincodevalue: ''}); }, } satisfies Tz.Converter, LS21001_alert_behaviour: { key: ['alert_behaviour'], convertSet: async (entity, key, value, meta) => { - const lookup = {'siren_led': 3, 'siren': 2, 'led': 1, 'nothing': 0}; - await entity.write('genBasic', {0x400a: {value: utils.getFromLookup(value, lookup), type: 32}}, - {manufacturerCode: Zcl.ManufacturerCode.LEEDARSON_LIGHTING_CO_LTD, disableDefaultResponse: true}); + const lookup = {siren_led: 3, siren: 2, led: 1, nothing: 0}; + await entity.write( + 'genBasic', + {0x400a: {value: utils.getFromLookup(value, lookup), type: 32}}, + {manufacturerCode: Zcl.ManufacturerCode.LEEDARSON_LIGHTING_CO_LTD, disableDefaultResponse: true}, + ); return {state: {alert_behaviour: value}}; }, } satisfies Tz.Converter, @@ -2250,7 +2257,7 @@ const converters2 = { convertSet: async (entity, key, value, meta) => { utils.assertString(value, key); value = value.toLowerCase(); - const lookup = {'off': 0x00, 'on_off': 0x01, 'off_on': 0x02}; + const lookup = {off: 0x00, on_off: 0x01, off_on: 0x02}; const payload = utils.getFromLookup(value, lookup); await entity.write('genOnOff', {0x8001: {value: payload, type: 0x30}}); return {state: {indicator_mode: value}}; @@ -2268,12 +2275,12 @@ const converters2 = { if (!meta.state.hasOwnProperty('state')) { throw new Error('Cannot toggle, state not known yet'); } else { - const payload = {0x0055: {value: (meta.state.state === 'OFF') ? 0x01 : 0x00, type: 0x10}}; + const payload = {0x0055: {value: meta.state.state === 'OFF' ? 0x01 : 0x00, type: 0x10}}; await entity.write('genBinaryOutput', payload, options); return {state: {state: meta.state.state === 'OFF' ? 'ON' : 'OFF'}}; } } else { - const payload = {0x0055: {value: (value.toUpperCase() === 'OFF') ? 0x00 : 0x01, type: 0x10}}; + const payload = {0x0055: {value: value.toUpperCase() === 'OFF' ? 0x00 : 0x01, type: 0x10}}; await entity.write('genBinaryOutput', payload, options); return {state: {state: value.toUpperCase()}}; } @@ -2335,8 +2342,8 @@ const converters2 = { danfoss_mounted_mode_control: { key: ['mounted_mode_control'], convertSet: async (entity, key, value, meta) => { - await entity.write('hvacThermostat', {'danfossMountedModeControl': value}, manufacturerOptions.danfoss); - return {readAfterWriteTime: 200, state: {'mounted_mode_control': value}}; + await entity.write('hvacThermostat', {danfossMountedModeControl: value}, manufacturerOptions.danfoss); + return {readAfterWriteTime: 200, state: {mounted_mode_control: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('hvacThermostat', ['danfossMountedModeControl'], manufacturerOptions.danfoss); @@ -2345,8 +2352,8 @@ const converters2 = { danfoss_thermostat_vertical_orientation: { key: ['thermostat_vertical_orientation'], convertSet: async (entity, key, value, meta) => { - await entity.write('hvacThermostat', {'danfossThermostatOrientation': value}, manufacturerOptions.danfoss); - return {readAfterWriteTime: 200, state: {'thermostat_vertical_orientation': value}}; + await entity.write('hvacThermostat', {danfossThermostatOrientation: value}, manufacturerOptions.danfoss); + return {readAfterWriteTime: 200, state: {thermostat_vertical_orientation: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('hvacThermostat', ['danfossThermostatOrientation'], manufacturerOptions.danfoss); @@ -2355,8 +2362,8 @@ const converters2 = { danfoss_external_measured_room_sensor: { key: ['external_measured_room_sensor'], convertSet: async (entity, key, value, meta) => { - await entity.write('hvacThermostat', {'danfossExternalMeasuredRoomSensor': value}, manufacturerOptions.danfoss); - return {readAfterWriteTime: 200, state: {'external_measured_room_sensor': value}}; + await entity.write('hvacThermostat', {danfossExternalMeasuredRoomSensor: value}, manufacturerOptions.danfoss); + return {readAfterWriteTime: 200, state: {external_measured_room_sensor: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('hvacThermostat', ['danfossExternalMeasuredRoomSensor'], manufacturerOptions.danfoss); @@ -2365,8 +2372,8 @@ const converters2 = { danfoss_radiator_covered: { key: ['radiator_covered'], convertSet: async (entity, key, value, meta) => { - await entity.write('hvacThermostat', {'danfossRadiatorCovered': value}, manufacturerOptions.danfoss); - return {readAfterWriteTime: 200, state: {'radiator_covered': value}}; + await entity.write('hvacThermostat', {danfossRadiatorCovered: value}, manufacturerOptions.danfoss); + return {readAfterWriteTime: 200, state: {radiator_covered: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('hvacThermostat', ['danfossRadiatorCovered'], manufacturerOptions.danfoss); @@ -2375,8 +2382,8 @@ const converters2 = { danfoss_viewing_direction: { key: ['viewing_direction'], convertSet: async (entity, key, value, meta) => { - await entity.write('hvacUserInterfaceCfg', {'danfossViewingDirection': value}, manufacturerOptions.danfoss); - return {readAfterWriteTime: 200, state: {'viewing_direction': value}}; + await entity.write('hvacUserInterfaceCfg', {danfossViewingDirection: value}, manufacturerOptions.danfoss); + return {readAfterWriteTime: 200, state: {viewing_direction: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('hvacUserInterfaceCfg', ['danfossViewingDirection'], manufacturerOptions.danfoss); @@ -2385,8 +2392,8 @@ const converters2 = { danfoss_algorithm_scale_factor: { key: ['algorithm_scale_factor'], convertSet: async (entity, key, value, meta) => { - await entity.write('hvacThermostat', {'danfossAlgorithmScaleFactor': value}, manufacturerOptions.danfoss); - return {readAfterWriteTime: 200, state: {'algorithm_scale_factor': value}}; + await entity.write('hvacThermostat', {danfossAlgorithmScaleFactor: value}, manufacturerOptions.danfoss); + return {readAfterWriteTime: 200, state: {algorithm_scale_factor: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('hvacThermostat', ['danfossAlgorithmScaleFactor'], manufacturerOptions.danfoss); @@ -2395,8 +2402,8 @@ const converters2 = { danfoss_heat_available: { key: ['heat_available'], convertSet: async (entity, key, value, meta) => { - await entity.write('hvacThermostat', {'danfossHeatAvailable': value}, manufacturerOptions.danfoss); - return {readAfterWriteTime: 200, state: {'heat_available': value}}; + await entity.write('hvacThermostat', {danfossHeatAvailable: value}, manufacturerOptions.danfoss); + return {readAfterWriteTime: 200, state: {heat_available: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('hvacThermostat', ['danfossHeatAvailable'], manufacturerOptions.danfoss); @@ -2411,9 +2418,9 @@ const converters2 = { danfoss_day_of_week: { key: ['day_of_week'], convertSet: async (entity, key, value, meta) => { - const payload = {'danfossDayOfWeek': utils.getKey(constants.thermostatDayOfWeek, value, undefined, Number)}; + const payload = {danfossDayOfWeek: utils.getKey(constants.thermostatDayOfWeek, value, undefined, Number)}; await entity.write('hvacThermostat', payload, manufacturerOptions.danfoss); - return {readAfterWriteTime: 200, state: {'day_of_week': value}}; + return {readAfterWriteTime: 200, state: {day_of_week: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('hvacThermostat', ['danfossDayOfWeek'], manufacturerOptions.danfoss); @@ -2422,8 +2429,8 @@ const converters2 = { danfoss_trigger_time: { key: ['trigger_time'], convertSet: async (entity, key, value, meta) => { - await entity.write('hvacThermostat', {'danfossTriggerTime': value}, manufacturerOptions.danfoss); - return {readAfterWriteTime: 200, state: {'trigger_time': value}}; + await entity.write('hvacThermostat', {danfossTriggerTime: value}, manufacturerOptions.danfoss); + return {readAfterWriteTime: 200, state: {trigger_time: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('hvacThermostat', ['danfossTriggerTime'], manufacturerOptions.danfoss); @@ -2432,8 +2439,8 @@ const converters2 = { danfoss_window_open_feature: { key: ['window_open_feature'], convertSet: async (entity, key, value, meta) => { - await entity.write('hvacThermostat', {'danfossWindowOpenFeatureEnable': value}, manufacturerOptions.danfoss); - return {readAfterWriteTime: 200, state: {'window_open_feature': value}}; + await entity.write('hvacThermostat', {danfossWindowOpenFeatureEnable: value}, manufacturerOptions.danfoss); + return {readAfterWriteTime: 200, state: {window_open_feature: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('hvacThermostat', ['danfossWindowOpenFeatureEnable'], manufacturerOptions.danfoss); @@ -2448,8 +2455,8 @@ const converters2 = { danfoss_window_open_external: { key: ['window_open_external'], convertSet: async (entity, key, value, meta) => { - await entity.write('hvacThermostat', {'danfossWindowOpenExternal': value}, manufacturerOptions.danfoss); - return {readAfterWriteTime: 200, state: {'window_open_external': value}}; + await entity.write('hvacThermostat', {danfossWindowOpenExternal: value}, manufacturerOptions.danfoss); + return {readAfterWriteTime: 200, state: {window_open_external: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('hvacThermostat', ['danfossWindowOpenExternal'], manufacturerOptions.danfoss); @@ -2458,8 +2465,8 @@ const converters2 = { danfoss_load_balancing_enable: { key: ['load_balancing_enable'], convertSet: async (entity, key, value, meta) => { - await entity.write('hvacThermostat', {'danfossLoadBalancingEnable': value}, manufacturerOptions.danfoss); - return {readAfterWriteTime: 200, state: {'load_balancing_enable': value}}; + await entity.write('hvacThermostat', {danfossLoadBalancingEnable: value}, manufacturerOptions.danfoss); + return {readAfterWriteTime: 200, state: {load_balancing_enable: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('hvacThermostat', ['danfossLoadBalancingEnable'], manufacturerOptions.danfoss); @@ -2468,8 +2475,8 @@ const converters2 = { danfoss_load_room_mean: { key: ['load_room_mean'], convertSet: async (entity, key, value, meta) => { - await entity.write('hvacThermostat', {'danfossLoadRoomMean': value}, manufacturerOptions.danfoss); - return {readAfterWriteTime: 200, state: {'load_room_mean': value}}; + await entity.write('hvacThermostat', {danfossLoadRoomMean: value}, manufacturerOptions.danfoss); + return {readAfterWriteTime: 200, state: {load_room_mean: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('hvacThermostat', ['danfossLoadRoomMean'], manufacturerOptions.danfoss); @@ -2496,8 +2503,8 @@ const converters2 = { danfoss_adaptation_settings: { key: ['adaptation_run_settings'], convertSet: async (entity, key, value, meta) => { - await entity.write('hvacThermostat', {'danfossAdaptionRunSettings': value}, manufacturerOptions.danfoss); - return {readAfterWriteTime: 200, state: {'adaptation_run_settings': value}}; + await entity.write('hvacThermostat', {danfossAdaptionRunSettings: value}, manufacturerOptions.danfoss); + return {readAfterWriteTime: 200, state: {adaptation_run_settings: value}}; }, convertGet: async (entity, key, meta) => { @@ -2507,9 +2514,9 @@ const converters2 = { danfoss_adaptation_control: { key: ['adaptation_run_control'], convertSet: async (entity, key, value, meta) => { - const payload = {'danfossAdaptionRunControl': utils.getKey(constants.danfossAdaptionRunControl, value, value, Number)}; + const payload = {danfossAdaptionRunControl: utils.getKey(constants.danfossAdaptionRunControl, value, value, Number)}; await entity.write('hvacThermostat', payload, manufacturerOptions.danfoss); - return {readAfterWriteTime: 250, state: {'adaptation_run_control': value}}; + return {readAfterWriteTime: 250, state: {adaptation_run_control: value}}; }, convertGet: async (entity, key, meta) => { @@ -2519,9 +2526,9 @@ const converters2 = { danfoss_regulation_setpoint_offset: { key: ['regulation_setpoint_offset'], convertSet: async (entity, key, value, meta) => { - const payload = {'danfossRegulationSetpointOffset': value}; + const payload = {danfossRegulationSetpointOffset: value}; await entity.write('hvacThermostat', payload, manufacturerOptions.danfoss); - return {readAfterWriteTime: 250, state: {'regulation_setpoint_offset': value}}; + return {readAfterWriteTime: 250, state: {regulation_setpoint_offset: value}}; }, convertGet: async (entity, key, meta) => { @@ -2626,23 +2633,34 @@ const converters2 = { } }, convertGet: async (entity, key, meta) => { - const isPosition = (key === 'position'); + const isPosition = key === 'position'; await entity.read('closuresWindowCovering', [isPosition ? 'currentPositionLiftPercentage' : 'currentPositionTiltPercentage']); }, } satisfies Tz.Converter, namron_thermostat: { key: [ - 'lcd_brightness', 'button_vibration_level', 'floor_sensor_type', 'sensor', 'powerup_status', 'floor_sensor_calibration', - 'dry_time', 'mode_after_dry', 'temperature_display', 'window_open_check', 'hysterersis', 'display_auto_off_enabled', - 'alarm_airtemp_overvalue', 'away_mode', + 'lcd_brightness', + 'button_vibration_level', + 'floor_sensor_type', + 'sensor', + 'powerup_status', + 'floor_sensor_calibration', + 'dry_time', + 'mode_after_dry', + 'temperature_display', + 'window_open_check', + 'hysterersis', + 'display_auto_off_enabled', + 'alarm_airtemp_overvalue', + 'away_mode', ], convertSet: async (entity, key, value, meta) => { if (key === 'lcd_brightness') { - const lookup = {'low': 0, 'mid': 1, 'high': 2}; + const lookup = {low: 0, mid: 1, high: 2}; const payload = {0x1000: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.ENUM8}}; await entity.write('hvacThermostat', payload, manufacturerOptions.sunricher); } else if (key === 'button_vibration_level') { - const lookup = {'off': 0, 'low': 1, 'high': 2}; + const lookup = {off: 0, low: 1, high: 2}; const payload = {0x1001: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.ENUM8}}; await entity.write('hvacThermostat', payload, manufacturerOptions.sunricher); } else if (key === 'floor_sensor_type') { @@ -2650,103 +2668,102 @@ const converters2 = { const payload = {0x1002: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.ENUM8}}; await entity.write('hvacThermostat', payload, manufacturerOptions.sunricher); } else if (key === 'sensor') { - const lookup = {'air': 0, 'floor': 1, 'both': 2}; + const lookup = {air: 0, floor: 1, both: 2}; const payload = {0x1003: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.ENUM8}}; await entity.write('hvacThermostat', payload, manufacturerOptions.sunricher); - } else if (key==='powerup_status') { - const lookup = {'default': 0, 'last_status': 1}; + } else if (key === 'powerup_status') { + const lookup = {default: 0, last_status: 1}; const payload = {0x1004: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.ENUM8}}; await entity.write('hvacThermostat', payload, manufacturerOptions.sunricher); - } else if (key==='floor_sensor_calibration') { + } else if (key === 'floor_sensor_calibration') { utils.assertNumber(value); const payload = {0x1005: {value: Math.round(value * 10), type: 0x28}}; // INT8S await entity.write('hvacThermostat', payload, manufacturerOptions.sunricher); - } else if (key==='dry_time') { + } else if (key === 'dry_time') { const payload = {0x1006: {value: value, type: 0x20}}; // INT8U await entity.write('hvacThermostat', payload, manufacturerOptions.sunricher); - } else if (key==='mode_after_dry') { - const lookup = {'off': 0, 'manual': 1, 'auto': 2, 'away': 3}; + } else if (key === 'mode_after_dry') { + const lookup = {off: 0, manual: 1, auto: 2, away: 3}; const payload = {0x1007: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.ENUM8}}; await entity.write('hvacThermostat', payload, manufacturerOptions.sunricher); - } else if (key==='temperature_display') { - const lookup = {'room': 0, 'floor': 1}; + } else if (key === 'temperature_display') { + const lookup = {room: 0, floor: 1}; const payload = {0x1008: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.ENUM8}}; await entity.write('hvacThermostat', payload, manufacturerOptions.sunricher); - } else if (key==='window_open_check') { + } else if (key === 'window_open_check') { utils.assertNumber(value); const payload = {0x1009: {value: value * 2, type: 0x20}}; await entity.write('hvacThermostat', payload, manufacturerOptions.sunricher); - } else if (key==='hysterersis') { + } else if (key === 'hysterersis') { utils.assertNumber(value); - const payload = {0x100A: {value: value * 10, type: 0x20}}; + const payload = {0x100a: {value: value * 10, type: 0x20}}; await entity.write('hvacThermostat', payload, manufacturerOptions.sunricher); - } else if (key==='display_auto_off_enabled') { - const lookup = {'disabled': 0, 'enabled': 1}; - const payload = {0x100B: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.ENUM8}}; + } else if (key === 'display_auto_off_enabled') { + const lookup = {disabled: 0, enabled: 1}; + const payload = {0x100b: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.ENUM8}}; await entity.write('hvacThermostat', payload, manufacturerOptions.sunricher); - } else if (key==='alarm_airtemp_overvalue') { + } else if (key === 'alarm_airtemp_overvalue') { const payload = {0x2001: {value: value, type: 0x20}}; await entity.write('hvacThermostat', payload, manufacturerOptions.sunricher); - } else if (key==='away_mode') { - const payload = {0x2002: {value: Number(value==='ON'), type: 0x30}}; + } else if (key === 'away_mode') { + const payload = {0x2002: {value: Number(value === 'ON'), type: 0x30}}; await entity.write('hvacThermostat', payload, manufacturerOptions.sunricher); } }, convertGet: async (entity, key, meta) => { switch (key) { - case 'lcd_brightness': - await entity.read('hvacThermostat', [0x1000], manufacturerOptions.sunricher); - break; - case 'button_vibration_level': - await entity.read('hvacThermostat', [0x1001], manufacturerOptions.sunricher); - break; - case 'floor_sensor_type': - await entity.read('hvacThermostat', [0x1002], manufacturerOptions.sunricher); - break; - case 'sensor': - await entity.read('hvacThermostat', [0x1003], manufacturerOptions.sunricher); - break; - case 'powerup_status': - await entity.read('hvacThermostat', [0x1004], manufacturerOptions.sunricher); - break; - case 'floor_sensor_calibration': - await entity.read('hvacThermostat', [0x1005], manufacturerOptions.sunricher); - break; - case 'dry_time': - await entity.read('hvacThermostat', [0x1006], manufacturerOptions.sunricher); - break; - case 'mode_after_dry': - await entity.read('hvacThermostat', [0x1007], manufacturerOptions.sunricher); - break; - case 'temperature_display': - await entity.read('hvacThermostat', [0x1008], manufacturerOptions.sunricher); - break; - case 'window_open_check': - await entity.read('hvacThermostat', [0x1009], manufacturerOptions.sunricher); - break; - case 'hysterersis': - await entity.read('hvacThermostat', [0x100A], manufacturerOptions.sunricher); - break; - case 'display_auto_off_enabled': - await entity.read('hvacThermostat', [0x100B], manufacturerOptions.sunricher); - break; - case 'alarm_airtemp_overvalue': - await entity.read('hvacThermostat', [0x2001], manufacturerOptions.sunricher); - break; - case 'away_mode': - await entity.read('hvacThermostat', [0x2002], manufacturerOptions.sunricher); - break; - - default: // Unknown key - throw new Error(`Unhandled key toZigbee.namron_thermostat.convertGet ${key}`); + case 'lcd_brightness': + await entity.read('hvacThermostat', [0x1000], manufacturerOptions.sunricher); + break; + case 'button_vibration_level': + await entity.read('hvacThermostat', [0x1001], manufacturerOptions.sunricher); + break; + case 'floor_sensor_type': + await entity.read('hvacThermostat', [0x1002], manufacturerOptions.sunricher); + break; + case 'sensor': + await entity.read('hvacThermostat', [0x1003], manufacturerOptions.sunricher); + break; + case 'powerup_status': + await entity.read('hvacThermostat', [0x1004], manufacturerOptions.sunricher); + break; + case 'floor_sensor_calibration': + await entity.read('hvacThermostat', [0x1005], manufacturerOptions.sunricher); + break; + case 'dry_time': + await entity.read('hvacThermostat', [0x1006], manufacturerOptions.sunricher); + break; + case 'mode_after_dry': + await entity.read('hvacThermostat', [0x1007], manufacturerOptions.sunricher); + break; + case 'temperature_display': + await entity.read('hvacThermostat', [0x1008], manufacturerOptions.sunricher); + break; + case 'window_open_check': + await entity.read('hvacThermostat', [0x1009], manufacturerOptions.sunricher); + break; + case 'hysterersis': + await entity.read('hvacThermostat', [0x100a], manufacturerOptions.sunricher); + break; + case 'display_auto_off_enabled': + await entity.read('hvacThermostat', [0x100b], manufacturerOptions.sunricher); + break; + case 'alarm_airtemp_overvalue': + await entity.read('hvacThermostat', [0x2001], manufacturerOptions.sunricher); + break; + case 'away_mode': + await entity.read('hvacThermostat', [0x2002], manufacturerOptions.sunricher); + break; + + default: // Unknown key + throw new Error(`Unhandled key toZigbee.namron_thermostat.convertGet ${key}`); } }, - } satisfies Tz.Converter, namron_thermostat_child_lock: { key: ['child_lock'], convertSet: async (entity, key, value, meta) => { - const keypadLockout = Number(value==='LOCK'); + const keypadLockout = Number(value === 'LOCK'); await entity.write('hvacUserInterfaceCfg', {keypadLockout}); return {readAfterWriteTime: 250, state: {child_lock: value}}; }, @@ -2765,8 +2782,12 @@ const converters2 = { key: ['brightness', 'color', 'color_temp'], options: [exposes.options.color_sync()], convertSet: async (entity, key, value, meta) => { - if (key === 'brightness' && meta.state.color_mode == constants.colorModeLookup[2] && - !meta.message.hasOwnProperty('color') && !meta.message.hasOwnProperty('color_temp')) { + if ( + key === 'brightness' && + meta.state.color_mode == constants.colorModeLookup[2] && + !meta.message.hasOwnProperty('color') && + !meta.message.hasOwnProperty('color_temp') + ) { const zclData = {level: Number(value), transtime: 0}; await entity.command('genLevelCtrl', 'moveToLevel', zclData, utils.getOptions(meta.mapped, entity)); @@ -2792,8 +2813,7 @@ const converters2 = { color_temp: meta.message.color_temp, }; - return {state: libColor.syncColorState(newState, meta.state, entity, meta.options), - readAfterWriteTime: zclData.transtime * 100}; + return {state: libColor.syncColorState(newState, meta.state, entity, meta.options), readAfterWriteTime: zclData.transtime * 100}; } if (key === 'color_temp') { @@ -2811,8 +2831,7 @@ const converters2 = { color_temp: value, }; - return {state: libColor.syncColorState(newState, meta.state, entity, meta.options), - readAfterWriteTime: zclData.transtime * 100}; + return {state: libColor.syncColorState(newState, meta.state, entity, meta.options), readAfterWriteTime: zclData.transtime * 100}; } const zclData = { @@ -2866,8 +2885,7 @@ const converters2 = { } await entity.command('lightingColorCtrl', 'tuyaRgbMode', {enable: 1}); - await entity.command('lightingColorCtrl', 'tuyaMoveToHueAndSaturationBrightness', - zclData, utils.getOptions(meta.mapped, entity)); + await entity.command('lightingColorCtrl', 'tuyaMoveToHueAndSaturationBrightness', zclData, utils.getOptions(meta.mapped, entity)); globalStore.putValue(entity, 'brightness', zclData.brightness); @@ -2882,13 +2900,10 @@ const converters2 = { color_mode: constants.colorModeLookup[0], }; - return {state: libColor.syncColorState(newState, meta.state, entity, meta.options), - readAfterWriteTime: zclData.transtime * 100}; + return {state: libColor.syncColorState(newState, meta.state, entity, meta.options), readAfterWriteTime: zclData.transtime * 100}; }, convertGet: async (entity, key, meta) => { - await entity.read('lightingColorCtrl', [ - 'currentHue', 'currentSaturation', 'tuyaBrightness', 'tuyaRgbMode', 'colorTemperature', - ]); + await entity.read('lightingColorCtrl', ['currentHue', 'currentSaturation', 'tuyaBrightness', 'tuyaRgbMode', 'colorTemperature']); }, } satisfies Tz.Converter, tuya_led_controller: { @@ -2897,9 +2912,7 @@ const converters2 = { if (key === 'state') { utils.assertString(value, key); if (value.toLowerCase() === 'off') { - await entity.command( - 'genOnOff', 'offWithEffect', {effectid: 0x01, effectvariant: 0x01}, utils.getOptions(meta.mapped, entity), - ); + await entity.command('genOnOff', 'offWithEffect', {effectid: 0x01, effectvariant: 0x01}, utils.getOptions(meta.mapped, entity)); } else { const payload = {level: 255, transtime: 0}; await entity.command('genLevelCtrl', 'moveToLevelWithOnOff', payload, utils.getOptions(meta.mapped, entity)); @@ -2933,14 +2946,16 @@ const converters2 = { convertSet: async (entity, key, value, meta) => { const endpoint = meta.device.getEndpoint(2); const lookup = { - 'norwegian_han': {value: 0x0200, acVoltageDivisor: 10, acCurrentDivisor: 10}, - 'norwegian_han_extra_load': {value: 0x0201, acVoltageDivisor: 10, acCurrentDivisor: 10}, - 'aidon_meter': {value: 0x0202, acVoltageDivisor: 10, acCurrentDivisor: 10}, - 'kaifa_and_kamstrup': {value: 0x0203, acVoltageDivisor: 10, acCurrentDivisor: 1000}, + norwegian_han: {value: 0x0200, acVoltageDivisor: 10, acCurrentDivisor: 10}, + norwegian_han_extra_load: {value: 0x0201, acVoltageDivisor: 10, acCurrentDivisor: 10}, + aidon_meter: {value: 0x0202, acVoltageDivisor: 10, acCurrentDivisor: 10}, + kaifa_and_kamstrup: {value: 0x0203, acVoltageDivisor: 10, acCurrentDivisor: 1000}, }; await endpoint.write( - 'seMetering', {0x0302: {value: utils.getFromLookup(value, lookup).value, type: 49}}, {manufacturerCode: Zcl.ManufacturerCode.DEVELCO}, + 'seMetering', + {0x0302: {value: utils.getFromLookup(value, lookup).value, type: 49}}, + {manufacturerCode: Zcl.ManufacturerCode.DEVELCO}, ); // As the device reports the incorrect divisor, we need to set it here @@ -2966,11 +2981,14 @@ const converters2 = { if (meta.state.mirror_display == 'ON') { bitValue |= 0x02; } - if (value == constants.thermostatSystemModes[0]) { // off + if (value == constants.thermostatSystemModes[0]) { + // off bitValue |= 0x20; - } else if (value == constants.thermostatSystemModes[4]) { // "heat" + } else if (value == constants.thermostatSystemModes[4]) { + // "heat" bitValue |= 0x04; - } else { // auto + } else { + // auto bitValue |= 0x10; } if (meta.state.child_lock == 'LOCK') { @@ -3034,11 +3052,14 @@ const converters2 = { if (meta.state.mirror_display == 'ON') { bitValue |= 0x02; } - if (meta.state.system_mode == constants.thermostatSystemModes[0]) { // off + if (meta.state.system_mode == constants.thermostatSystemModes[0]) { + // off bitValue |= 0x20; - } else if (meta.state.system_mode == constants.thermostatSystemModes[4]) { // "heat" + } else if (meta.state.system_mode == constants.thermostatSystemModes[4]) { + // "heat" bitValue |= 0x04; - } else { // auto + } else { + // auto bitValue |= 0x10; } if (value == 'LOCK') { @@ -3060,11 +3081,14 @@ const converters2 = { if (value == 'ON') { bitValue |= 0x02; } - if (meta.state.system_mode == constants.thermostatSystemModes[0]) { // off + if (meta.state.system_mode == constants.thermostatSystemModes[0]) { + // off bitValue |= 0x20; - } else if (meta.state.system_mode == constants.thermostatSystemModes[4]) { // "heat" + } else if (meta.state.system_mode == constants.thermostatSystemModes[4]) { + // "heat" bitValue |= 0x04; - } else { // auto + } else { + // auto bitValue |= 0x10; } if (meta.state.child_lock == 'LOCK') { @@ -3096,13 +3120,13 @@ const converters2 = { value = 1; } const lookup = { - 'OFF': 0, - 'ON': 1, + OFF: 0, + ON: 1, }; value = utils.getFromLookup(value, lookup); // Check for valid data utils.assertNumber(value, key); - if (((value >= 0) && value < 2) == false) value = 0; + if ((value >= 0 && value < 2) == false) value = 0; const payload = { 0x4010: { @@ -3122,12 +3146,14 @@ const converters2 = { if (key === 'trigger') { await entity.command('genOnOff', 'onWithTimedOff', {ctrlbits: 0, ontime: Math.round(value / 100), offwaittime: 0}); } else if (key === 'interval') { - await entity.configureReporting('genOnOff', [{ - attribute: 'onOff', - minimumReportInterval: value, - maximumReportInterval: value, - reportableChange: 0, - }]); + await entity.configureReporting('genOnOff', [ + { + attribute: 'onOff', + minimumReportInterval: value, + maximumReportInterval: value, + reportableChange: 0, + }, + ]); } }, } satisfies Tz.Converter, @@ -3167,7 +3193,7 @@ const converters2 = { if (isNaN(value2)) { return; } - const payload = {'currentLevel': value2}; + const payload = {currentLevel: value2}; await endpoint.write(cluster, payload); return; } @@ -3178,7 +3204,7 @@ const converters2 = { if (isNaN(value2)) { return; } - const payload = {'presentValue': value2}; + const payload = {presentValue: value2}; await endpoint.write(cluster, payload); return; } @@ -3197,11 +3223,12 @@ const converters2 = { options: [exposes.options.invert_cover()], convertSet: async (entity, key, value, meta) => { utils.assertString(value); - const invert = !(utils.getMetaValue(entity, meta.mapped, 'coverInverted', 'allEqual', false) ? - !meta.options.invert_cover : meta.options.invert_cover); - const lookup = invert ? - {'open': 'upOpen', 'close': 'downClose', 'stop': 'stop', 'on': 'upOpen', 'off': 'downClose'} : - {'open': 'downClose', 'close': 'upOpen', 'stop': 'stop', 'on': 'downClose', 'off': 'upOpen'}; + const invert = !(utils.getMetaValue(entity, meta.mapped, 'coverInverted', 'allEqual', false) + ? !meta.options.invert_cover + : meta.options.invert_cover); + const lookup = invert + ? {open: 'upOpen', close: 'downClose', stop: 'stop', on: 'upOpen', off: 'downClose'} + : {open: 'downClose', close: 'upOpen', stop: 'stop', on: 'downClose', off: 'upOpen'}; value = value.toLowerCase(); utils.validateValue(value, Object.keys(lookup)); @@ -3220,8 +3247,9 @@ const converters2 = { key: ['position'], options: [exposes.options.invert_cover(), exposes.options.no_position_support()], convertSet: async (entity, key, value, meta) => { - const invert = !(utils.getMetaValue(entity, meta.mapped, 'coverInverted', 'allEqual', false) ? - !meta.options.invert_cover : meta.options.invert_cover); + const invert = !(utils.getMetaValue(entity, meta.mapped, 'coverInverted', 'allEqual', false) + ? !meta.options.invert_cover + : meta.options.invert_cover); utils.assertNumber(value, key); let newPosition = value; if (meta.options.no_position_support) { @@ -3231,8 +3259,12 @@ const converters2 = { if (invert) { newPosition = 100 - newPosition; } - await entity.command('closuresWindowCovering', 'goToLiftPercentage', {percentageliftvalue: newPosition}, - utils.getOptions(meta.mapped, entity)); + await entity.command( + 'closuresWindowCovering', + 'goToLiftPercentage', + {percentageliftvalue: newPosition}, + utils.getOptions(meta.mapped, entity), + ); return {state: {['position']: position}, readAfterWriteTime: 0}; }, convertGet: async (entity, key, meta) => { @@ -3246,21 +3278,21 @@ const converters2 = { // enable the dimmer, requires a recent firmware on the device const lookup = { // dimmer - 'dimmer_on': 0x0101, - 'dimmer_off': 0x0100, + dimmer_on: 0x0101, + dimmer_off: 0x0100, // contactor - 'switch': 0x0003, - 'auto': 0x0004, + switch: 0x0003, + auto: 0x0004, // pilot wire - 'pilot_on': 0x0002, - 'pilot_off': 0x0001, + pilot_on: 0x0002, + pilot_off: 0x0001, }; value = value.toLowerCase(); utils.validateValue(value, Object.keys(lookup)); const payload = {0: {value: utils.getFromLookup(value, lookup), type: 9}}; await entity.write('manuSpecificLegrandDevices', payload, manufacturerOptions.legrand); - return {state: {'device_mode': value}}; + return {state: {device_mode: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificLegrandDevices', [0x0000, 0x0001, 0x0002], manufacturerOptions.legrand); @@ -3270,16 +3302,16 @@ const converters2 = { key: ['pilot_wire_mode'], convertSet: async (entity, key, value, meta) => { const mode = { - 'comfort': 0x00, + comfort: 0x00, 'comfort_-1': 0x01, 'comfort_-2': 0x02, - 'eco': 0x03, - 'frost_protection': 0x04, - 'off': 0x05, + eco: 0x03, + frost_protection: 0x04, + off: 0x05, }; const payload = {data: Buffer.from([utils.getFromLookup(value, mode)])}; await entity.command('manuSpecificLegrandDevices2', 'command0', payload); - return {state: {'pilot_wire_mode': value}}; + return {state: {pilot_wire_mode: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificLegrandDevices2', [0x0000], manufacturerOptions.legrand); @@ -3288,7 +3320,7 @@ const converters2 = { legrand_power_alarm: { key: ['power_alarm'], convertSet: async (entity, key, value, meta) => { - const enableAlarm = (value === 'DISABLE' || value === false ? false : true); + const enableAlarm = value === 'DISABLE' || value === false ? false : true; const payloadBolean = {0xf001: {value: enableAlarm ? 0x01 : 0x00, type: 0x10}}; const payloadValue = {0xf002: {value: value, type: 0x29}}; await entity.write('haElectricalMeasurement', payloadValue); @@ -3339,7 +3371,7 @@ const converters2 = { return result; } const messageKeys = Object.keys(meta.message); - const timeInSecondsValue = function() { + const timeInSecondsValue = (function () { if (messageKeys.includes('state')) { return meta.message.time_in_seconds; } @@ -3347,14 +3379,13 @@ const converters2 = { return meta.message[`time_in_seconds_${meta.endpoint_name}`]; } return null; - }(); + })(); if (!timeInSecondsValue) { return result; } const timeInSeconds = Number(timeInSecondsValue); if (!Number.isInteger(timeInSeconds) || timeInSeconds < 0 || timeInSeconds > 0xfffe) { - throw Error('The time_in_seconds value must be convertible to an integer in the ' + - 'range: <0x0000, 0xFFFE>'); + throw Error('The time_in_seconds value must be convertible to an integer in the ' + 'range: <0x0000, 0xFFFE>'); } const on = lowerCaseValue === 'on'; await entity.command( @@ -3362,10 +3393,11 @@ const converters2 = { 'onWithTimedOff', { ctrlbits: 0, - ontime: (on ? 0 : timeInSeconds.valueOf()), - offwaittime: (on ? timeInSeconds.valueOf() : 0), + ontime: on ? 0 : timeInSeconds.valueOf(), + offwaittime: on ? timeInSeconds.valueOf() : 0, }, - utils.getOptions(meta.mapped, entity)); + utils.getOptions(meta.mapped, entity), + ); return result; }, convertGet: async (entity, key, meta) => { @@ -3376,13 +3408,13 @@ const converters2 = { key: ['sensitivity', 'led_feedback', 'buzzer_feedback', 'sensors_count', 'sensors_type', 'alert_threshold'], convertSet: async (entity, key, rawValue, meta) => { const lookup = { - 'OFF': 0x00, - 'ON': 0x01, + OFF: 0x00, + ON: 0x01, }; const sensorsTypeLookup = { 'СБМ-20/СТС-5/BOI-33': '0', 'СБМ-19/СТС-6': '1', - 'Others': '2', + Others: '2', }; let value = utils.getFromLookup(rawValue, lookup, Number(rawValue)); @@ -3393,12 +3425,12 @@ const converters2 = { } const payloads: KeyValueAny = { - sensitivity: {0xF000: {value, type: 0x21}}, - led_feedback: {0xF001: {value, type: 0x10}}, - buzzer_feedback: {0xF002: {value, type: 0x10}}, - sensors_count: {0xF003: {value, type: 0x20}}, - sensors_type: {0xF004: {value, type: 0x30}}, - alert_threshold: {0xF005: {value, type: 0x23}}, + sensitivity: {0xf000: {value, type: 0x21}}, + led_feedback: {0xf001: {value, type: 0x10}}, + buzzer_feedback: {0xf002: {value, type: 0x10}}, + sensors_count: {0xf003: {value, type: 0x20}}, + sensors_type: {0xf004: {value, type: 0x30}}, + alert_threshold: {0xf005: {value, type: 0x23}}, }; await entity.write('msIlluminanceLevelSensing', payloads[key]); @@ -3408,12 +3440,12 @@ const converters2 = { }, convertGet: async (entity, key, meta) => { const payloads: KeyValueAny = { - sensitivity: ['msIlluminanceLevelSensing', 0xF000], - led_feedback: ['msIlluminanceLevelSensing', 0xF001], - buzzer_feedback: ['msIlluminanceLevelSensing', 0xF002], - sensors_count: ['msIlluminanceLevelSensing', 0xF003], - sensors_type: ['msIlluminanceLevelSensing', 0xF004], - alert_threshold: ['msIlluminanceLevelSensing', 0xF005], + sensitivity: ['msIlluminanceLevelSensing', 0xf000], + led_feedback: ['msIlluminanceLevelSensing', 0xf001], + buzzer_feedback: ['msIlluminanceLevelSensing', 0xf002], + sensors_count: ['msIlluminanceLevelSensing', 0xf003], + sensors_type: ['msIlluminanceLevelSensing', 0xf004], + alert_threshold: ['msIlluminanceLevelSensing', 0xf005], }; await entity.read(payloads[key][0], [payloads[key][1]]); }, @@ -3421,7 +3453,7 @@ const converters2 = { diyruz_airsense_config: { key: ['led_feedback', 'enable_abc', 'threshold1', 'threshold2', 'temperature_offset', 'pressure_offset', 'humidity_offset'], convertSet: async (entity, key, rawValue, meta) => { - const lookup = {'OFF': 0x00, 'ON': 0x01}; + const lookup = {OFF: 0x00, ON: 0x01}; const value = utils.getFromLookup(rawValue, lookup, Number(rawValue)); const payloads: KeyValueAny = { led_feedback: ['msCO2', {0x0203: {value, type: 0x10}}], @@ -3453,8 +3485,8 @@ const converters2 = { diyruz_zintercom_config: { key: ['mode', 'sound', 'time_ring', 'time_talk', 'time_open', 'time_bell', 'time_report'], convertSet: async (entity, key, rawValue, meta) => { - const lookup: KeyValueAny = {'OFF': 0x00, 'ON': 0x01}; - const modeOpenLookup = {'never': '0', 'once': '1', 'always': '2', 'drop': '3'}; + const lookup: KeyValueAny = {OFF: 0x00, ON: 0x01}; + const modeOpenLookup = {never: '0', once: '1', always: '2', drop: '3'}; let value = utils.getFromLookup(rawValue, lookup, Number(rawValue)); if (key == 'mode') { // @ts-expect-error @@ -3498,21 +3530,25 @@ const converters2 = { key: ['alarm_humidity_max', 'alarm_humidity_min', 'alarm_temperature_max', 'alarm_temperature_min'], convertSet: async (entity, key, value, meta) => { switch (key) { - case 'alarm_temperature_max': - case 'alarm_temperature_min': - case 'alarm_humidity_max': - case 'alarm_humidity_min': { - // await entity.write('manuSpecificTuya_2', {[key]: value}); - // instead write as custom attribute to override incorrect herdsman dataType from uint16 to int16 - // https://github.com/Koenkk/zigbee-herdsman/blob/v0.13.191/src/zcl/definition/cluster.ts#L4235 - const keyToAttributeLookup = {'alarm_temperature_max': 0xD00A, 'alarm_temperature_min': 0xD00B, - 'alarm_humidity_max': 0xD00D, 'alarm_humidity_min': 0xD00E}; - const payload = {[keyToAttributeLookup[key]]: {value: value, type: Zcl.DataType.INT16}}; - await entity.write('manuSpecificTuya_2', payload); - break; - } - default: // Unknown key - logger.warning(`Unhandled key ${key}`, NS); + case 'alarm_temperature_max': + case 'alarm_temperature_min': + case 'alarm_humidity_max': + case 'alarm_humidity_min': { + // await entity.write('manuSpecificTuya_2', {[key]: value}); + // instead write as custom attribute to override incorrect herdsman dataType from uint16 to int16 + // https://github.com/Koenkk/zigbee-herdsman/blob/v0.13.191/src/zcl/definition/cluster.ts#L4235 + const keyToAttributeLookup = { + alarm_temperature_max: 0xd00a, + alarm_temperature_min: 0xd00b, + alarm_humidity_max: 0xd00d, + alarm_humidity_min: 0xd00e, + }; + const payload = {[keyToAttributeLookup[key]]: {value: value, type: Zcl.DataType.INT16}}; + await entity.write('manuSpecificTuya_2', payload); + break; + } + default: // Unknown key + logger.warning(`Unhandled key ${key}`, NS); } }, } satisfies Tz.Converter, @@ -3527,30 +3563,27 @@ const converters2 = { ...utils.getOptions(meta.mapped, entity), }; switch (key) { - case 'send_key': - utils.assertObject(value); - await entity.command('heimanSpecificInfraRedRemote', 'sendKey', - {id: value['id'], keyCode: value['key_code']}, options); - break; - case 'create': - utils.assertObject(value); - await entity.command('heimanSpecificInfraRedRemote', 'createId', {modelType: value['model_type']}, options); - break; - case 'learn': - utils.assertObject(value); - await entity.command('heimanSpecificInfraRedRemote', 'studyKey', - {id: value['id'], keyCode: value['key_code']}, options); - break; - case 'delete': - utils.assertObject(value); - await entity.command('heimanSpecificInfraRedRemote', 'deleteKey', - {id: value['id'], keyCode: value['key_code']}, options); - break; - case 'get_list': - await entity.command('heimanSpecificInfraRedRemote', 'getIdAndKeyCodeList', {}, options); - break; - default: // Unknown key - throw new Error(`Unhandled key ${key}`); + case 'send_key': + utils.assertObject(value); + await entity.command('heimanSpecificInfraRedRemote', 'sendKey', {id: value['id'], keyCode: value['key_code']}, options); + break; + case 'create': + utils.assertObject(value); + await entity.command('heimanSpecificInfraRedRemote', 'createId', {modelType: value['model_type']}, options); + break; + case 'learn': + utils.assertObject(value); + await entity.command('heimanSpecificInfraRedRemote', 'studyKey', {id: value['id'], keyCode: value['key_code']}, options); + break; + case 'delete': + utils.assertObject(value); + await entity.command('heimanSpecificInfraRedRemote', 'deleteKey', {id: value['id'], keyCode: value['key_code']}, options); + break; + case 'get_list': + await entity.command('heimanSpecificInfraRedRemote', 'getIdAndKeyCodeList', {}, options); + break; + default: // Unknown key + throw new Error(`Unhandled key ${key}`); } }, } satisfies Tz.Converter, @@ -3558,7 +3591,7 @@ const converters2 = { key: ['scene_store'], convertSet: async (entity, key, value: KeyValueAny, meta) => { const isGroup = utils.isGroup(entity); - const groupid = isGroup ? entity.groupID : (value.group_id != undefined ? value.group_id : 0); + const groupid = isGroup ? entity.groupID : value.group_id != undefined ? value.group_id : 0; let sceneid = value; let scenename = null; if (typeof value === 'object') { @@ -3582,7 +3615,7 @@ const converters2 = { utils.saveSceneState(member, sceneid, groupid, meta.membersState[member.getDevice().ieeeAddr], scenename); } } - // @ts-expect-error + // @ts-expect-error } else if (response.status === 0) { utils.saveSceneState(entity, sceneid, groupid, meta.state, scenename); } else { @@ -3663,7 +3696,7 @@ const converters2 = { } const isGroup = utils.isGroup(entity); - const groupid = isGroup ? entity.groupID : (value.group_id != undefined ? value.group_id : 0); + const groupid = isGroup ? entity.groupID : value.group_id != undefined ? value.group_id : 0; const sceneid = value.ID; const scenename = value.name; const transtime = value.transition != undefined ? value.transition : 0; @@ -3676,17 +3709,19 @@ const converters2 = { const state: KeyValueAny = {}; const extensionfieldsets = []; - for (let [attribute, val] of Object.entries(value)) { + for (const attribute of Object.keys(value)) { + let val = value[attribute]; if (attribute === 'state') { - extensionfieldsets.push({'clstId': 6, 'len': 1, 'extField': [val.toLowerCase() === 'on' ? 1 : 0]}); + extensionfieldsets.push({clstId: 6, len: 1, extField: [val.toLowerCase() === 'on' ? 1 : 0]}); state['state'] = val.toUpperCase(); } else if (attribute === 'brightness') { - extensionfieldsets.push({'clstId': 8, 'len': 1, 'extField': [val]}); + extensionfieldsets.push({clstId: 8, len: 1, extField: [val]}); state['brightness'] = val; } else if (attribute === 'position') { - const invert = utils.getMetaValue(entity, meta.mapped, 'coverInverted', 'allEqual', false) ? - !meta.options.invert_cover : meta.options.invert_cover; - extensionfieldsets.push({'clstId': 258, 'len': 1, 'extField': [invert ? 100 - val : val]}); + const invert = utils.getMetaValue(entity, meta.mapped, 'coverInverted', 'allEqual', false) + ? !meta.options.invert_cover + : meta.options.invert_cover; + extensionfieldsets.push({clstId: 258, len: 1, extField: [invert ? 100 - val : val]}); state['position'] = val; } else if (attribute === 'color_temp') { /* @@ -3708,7 +3743,7 @@ const converters2 = { const xy = libColor.ColorXY.fromMireds(val); const xScaled = utils.mapNumberRange(xy.x, 0, 1, 0, 65535); const yScaled = utils.mapNumberRange(xy.y, 0, 1, 0, 65535); - extensionfieldsets.push({'clstId': 768, 'len': 4, 'extField': [xScaled, yScaled]}); + extensionfieldsets.push({clstId: 768, len: 4, extField: [xScaled, yScaled]}); state['color_mode'] = constants.colorModeLookup[2]; state['color_temp'] = val; } else if (attribute === 'color') { @@ -3722,13 +3757,11 @@ const converters2 = { if (newColor.isXY()) { const xScaled = utils.mapNumberRange(newColor.xy.x, 0, 1, 0, 65535); const yScaled = utils.mapNumberRange(newColor.xy.y, 0, 1, 0, 65535); - extensionfieldsets.push( - { - 'clstId': 768, - 'len': 4, - 'extField': [xScaled, yScaled], - }, - ); + extensionfieldsets.push({ + clstId: 768, + len: 4, + extField: [xScaled, yScaled], + }); state['color_mode'] = constants.colorModeLookup[1]; state['color'] = newColor.xy.toObject(); } else if (newColor.isHSV()) { @@ -3736,26 +3769,22 @@ const converters2 = { if (utils.getMetaValue(entity, meta.mapped, 'supportsEnhancedHue', 'allEqual', true)) { const hScaled = utils.mapNumberRange(hsvCorrected.hue, 0, 360, 0, 65535); const sScaled = utils.mapNumberRange(hsvCorrected.saturation, 0, 100, 0, 254); - extensionfieldsets.push( - { - 'clstId': 768, - 'len': 13, - 'extField': [0, 0, hScaled, sScaled, 0, 0, 0, 0], - }, - ); + extensionfieldsets.push({ + clstId: 768, + len: 13, + extField: [0, 0, hScaled, sScaled, 0, 0, 0, 0], + }); } else { // The extensionFieldSet is always EnhancedCurrentHue according to ZCL // When the bulb or all bulbs in a group do not support enhanchedHue, const colorXY = hsvCorrected.toXY(); const xScaled = utils.mapNumberRange(colorXY.x, 0, 1, 0, 65535); const yScaled = utils.mapNumberRange(colorXY.y, 0, 1, 0, 65535); - extensionfieldsets.push( - { - 'clstId': 768, - 'len': 4, - 'extField': [xScaled, yScaled], - }, - ); + extensionfieldsets.push({ + clstId: 768, + len: 4, + extField: [xScaled, yScaled], + }); } state['color_mode'] = constants.colorModeLookup[0]; state['color'] = newColor.hsv.toObject(false, false); @@ -3772,16 +3801,16 @@ const converters2 = { * * We accept a SUCCESS or NOT_FOUND as a result of the remove call. */ - const removeresp = await entity.command( - 'genScenes', 'remove', {groupid, sceneid}, utils.getOptions(meta.mapped, entity), - ); + const removeresp = await entity.command('genScenes', 'remove', {groupid, sceneid}, utils.getOptions(meta.mapped, entity)); if (isGroup || (utils.isObject(removeresp) && (removeresp.status === 0 || removeresp.status == 133 || removeresp.status == 139))) { const addSceneCommand = Number.isInteger(transtime) ? 'add' : 'enhancedAdd'; const commandTransitionTime = addSceneCommand === 'enhancedAdd' ? Math.floor(transtime * 10) : transtime; const response = await entity.command( - 'genScenes', addSceneCommand, {groupid, sceneid, scenename: '', transtime: commandTransitionTime, extensionfieldsets}, + 'genScenes', + addSceneCommand, + {groupid, sceneid, scenename: '', transtime: commandTransitionTime, extensionfieldsets}, utils.getOptions(meta.mapped, entity), ); @@ -3814,16 +3843,14 @@ const converters2 = { utils.assertNumber(value); const groupid = isGroup ? entity.groupID : 0; const sceneid = value; - const response = await entity.command( - 'genScenes', 'remove', {groupid, sceneid}, utils.getOptions(meta.mapped, entity), - ); + const response = await entity.command('genScenes', 'remove', {groupid, sceneid}, utils.getOptions(meta.mapped, entity)); if (isGroup) { if (meta.membersState) { for (const member of entity.members) { utils.deleteSceneState(member, sceneid, groupid); } } - // @ts-expect-error + // @ts-expect-error } else if (response.status === 0) { utils.deleteSceneState(entity, sceneid, groupid); } else { @@ -3837,9 +3864,7 @@ const converters2 = { key: ['scene_remove_all'], convertSet: async (entity, key, value, meta) => { const groupid = utils.isGroup(entity) ? entity.groupID : 0; - const response = await entity.command( - 'genScenes', 'removeAll', {groupid}, utils.getOptions(meta.mapped, entity), - ); + const response = await entity.command('genScenes', 'removeAll', {groupid}, utils.getOptions(meta.mapped, entity)); utils.assertObject(response); if (utils.isGroup(entity)) { if (meta.membersState) { @@ -3888,7 +3913,7 @@ const converters2 = { convertSet: async (entity, key, value, meta) => { utils.assertString(value, key); utils.assertEndpoint(entity); - const lookup = {'close': 1, 'stop': 2, 'open': 1}; + const lookup = {close: 1, stop: 2, open: 1}; value = value.toLowerCase(); utils.validateValue(value, Object.keys(lookup)); const endpointID = utils.getFromLookup(value, lookup); @@ -3902,7 +3927,7 @@ const converters2 = { ts0216_duration: { key: ['duration'], convertSet: async (entity, key, value, meta) => { - await entity.write('ssIasWd', {'maxDuration': value}); + await entity.write('ssIasWd', {maxDuration: value}); }, convertGet: async (entity, key, meta) => { await entity.read('ssIasWd', ['maxDuration']); @@ -3921,7 +3946,7 @@ const converters2 = { ts0216_alarm: { key: ['alarm'], convertSet: async (entity, key, value, meta) => { - const info = (value) ? (2 << 4) + (1 << 2) + 0 : 0; + const info = value ? (2 << 4) + (1 << 2) + 0 : 0; await entity.command( 'ssIasWd', @@ -3935,7 +3960,7 @@ const converters2 = { key: ['calibration'], convertSet: async (entity, key, value, meta) => { utils.assertString(value, key); - const lookup = {'ON': 0, 'OFF': 1}; + const lookup = {ON: 0, OFF: 1}; value = value.toUpperCase(); const calibration = utils.getFromLookup(value, lookup); await entity.write('closuresWindowCovering', {tuyaCalibration: calibration}); @@ -3949,7 +3974,7 @@ const converters2 = { key: ['motor_reversal'], convertSet: async (entity, key, value, meta) => { utils.assertString(value, key); - const lookup = {'ON': 1, 'OFF': 0}; + const lookup = {ON: 1, OFF: 0}; value = value.toUpperCase(); const reversal = utils.getFromLookup(value, lookup); await entity.write('closuresWindowCovering', {tuyaMotorReversal: reversal}); @@ -3963,7 +3988,7 @@ const converters2 = { key: ['calibration_time'], convertSet: async (entity, key, value, meta) => { utils.assertNumber(value); - const calibration = value *10; + const calibration = value * 10; await entity.write('closuresWindowCovering', {moesCalibrationTime: calibration}); return {state: {calibration_time: value}}; }, @@ -3972,20 +3997,17 @@ const converters2 = { }, } satisfies Tz.Converter, ZM35HQ_attr: { - key: [ - 'sensitivity', 'keep_time', - ], + key: ['sensitivity', 'keep_time'], convertSet: async (entity, key, value, meta) => { switch (key) { - case 'sensitivity': - await entity.write('ssIasZone', {currentZoneSensitivityLevel: utils.getFromLookup(value, {'low': 0, 'medium': 1, 'high': 2})}); - break; - case 'keep_time': - await entity.write('ssIasZone', - {61441: {value: utils.getFromLookup(value, {30: 0, 60: 1, 120: 2}), type: 0x20}}); - break; - default: // Unknown key - throw new Error(`Unhandled key ${key}`); + case 'sensitivity': + await entity.write('ssIasZone', {currentZoneSensitivityLevel: utils.getFromLookup(value, {low: 0, medium: 1, high: 2})}); + break; + case 'keep_time': + await entity.write('ssIasZone', {61441: {value: utils.getFromLookup(value, {30: 0, 60: 1, 120: 2}), type: 0x20}}); + break; + default: // Unknown key + throw new Error(`Unhandled key ${key}`); } }, convertGet: async (entity, key, meta) => { @@ -4012,8 +4034,8 @@ const converters2 = { key: ['window_open_force'], convertSet: async (entity, key, value, meta) => { if (typeof value === 'boolean') { - await entity.write('hvacThermostat', {'viessmannWindowOpenForce': value}, manufacturerOptions.viessmann); - return {readAfterWriteTime: 200, state: {'window_open_force': value}}; + await entity.write('hvacThermostat', {viessmannWindowOpenForce: value}, manufacturerOptions.viessmann); + return {readAfterWriteTime: 200, state: {window_open_force: value}}; } else { logger.error('window_open_force must be a boolean!', NS); } @@ -4044,8 +4066,11 @@ const converters2 = { idlock_master_pin_mode: { key: ['master_pin_mode'], convertSet: async (entity, key, value, meta) => { - await entity.write('closuresDoorLock', {0x4000: {value: value === true ? 1 : 0, type: 0x10}}, - {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + await entity.write( + 'closuresDoorLock', + {0x4000: {value: value === true ? 1 : 0, type: 0x10}}, + {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}, + ); return {state: {master_pin_mode: value}}; }, convertGet: async (entity, key, meta) => { @@ -4055,8 +4080,11 @@ const converters2 = { idlock_rfid_enable: { key: ['rfid_enable'], convertSet: async (entity, key, value, meta) => { - await entity.write('closuresDoorLock', {0x4001: {value: value === true ? 1 : 0, type: 0x10}}, - {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + await entity.write( + 'closuresDoorLock', + {0x4001: {value: value === true ? 1 : 0, type: 0x10}}, + {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}, + ); return {state: {rfid_enable: value}}; }, convertGet: async (entity, key, meta) => { @@ -4066,9 +4094,12 @@ const converters2 = { idlock_service_mode: { key: ['service_mode'], convertSet: async (entity, key, value, meta) => { - const lookup = {'deactivated': 0, 'random_pin_1x_use': 5, 'random_pin_24_hours': 6}; - await entity.write('closuresDoorLock', {0x4003: {value: utils.getFromLookup(value, lookup), type: 0x20}}, - {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + const lookup = {deactivated: 0, random_pin_1x_use: 5, random_pin_24_hours: 6}; + await entity.write( + 'closuresDoorLock', + {0x4003: {value: utils.getFromLookup(value, lookup), type: 0x20}}, + {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}, + ); return {state: {service_mode: value}}; }, convertGet: async (entity, key, meta) => { @@ -4078,9 +4109,12 @@ const converters2 = { idlock_lock_mode: { key: ['lock_mode'], convertSet: async (entity, key, value, meta) => { - const lookup = {'auto_off_away_off': 0, 'auto_on_away_off': 1, 'auto_off_away_on': 2, 'auto_on_away_on': 3}; - await entity.write('closuresDoorLock', {0x4004: {value: utils.getFromLookup(value, lookup), type: 0x20}}, - {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + const lookup = {auto_off_away_off: 0, auto_on_away_off: 1, auto_off_away_on: 2, auto_on_away_on: 3}; + await entity.write( + 'closuresDoorLock', + {0x4004: {value: utils.getFromLookup(value, lookup), type: 0x20}}, + {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}, + ); return {state: {lock_mode: value}}; }, convertGet: async (entity, key, meta) => { @@ -4090,8 +4124,11 @@ const converters2 = { idlock_relock_enabled: { key: ['relock_enabled'], convertSet: async (entity, key, value, meta) => { - await entity.write('closuresDoorLock', {0x4005: {value: value === true ? 1 : 0, type: 0x10}}, - {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + await entity.write( + 'closuresDoorLock', + {0x4005: {value: value === true ? 1 : 0, type: 0x10}}, + {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}, + ); return {state: {relock_enabled: value}}; }, convertGet: async (entity, key, meta) => { @@ -4102,10 +4139,10 @@ const converters2 = { key: ['schneider_pilot_mode'], convertSet: async (entity, key, value, meta) => { utils.assertString(value, key); - const lookup = {'contactor': 1, 'pilot': 3}; + const lookup = {contactor: 1, pilot: 3}; value = value.toLowerCase(); const mode = utils.getFromLookup(value, lookup); - await entity.write('schneiderSpecificPilotMode', {'pilotMode': mode}, {manufacturerCode: Zcl.ManufacturerCode.SCHNEIDER_ELECTRIC}); + await entity.write('schneiderSpecificPilotMode', {pilotMode: mode}, {manufacturerCode: Zcl.ManufacturerCode.SCHNEIDER_ELECTRIC}); return {state: {schneider_pilot_mode: value}}; }, convertGet: async (entity, key, meta) => { @@ -4115,7 +4152,7 @@ const converters2 = { schneider_dimmer_mode: { key: ['dimmer_mode'], convertSet: async (entity, key, value, meta) => { - const lookup = {'RC': 1, 'RL': 2}; + const lookup = {RC: 1, RL: 2}; const mode = utils.getFromLookup(value, lookup); await entity.write( 'lightingBallastCfg', @@ -4132,8 +4169,7 @@ const converters2 = { key: ['dimmer_mode'], convertSet: async (entity, key, value, meta) => { const controlMode = utils.getKey(constants.wiserDimmerControlMode, value, value, Number); - await entity.write('lightingBallastCfg', {'wiserControlMode': controlMode}, - {manufacturerCode: Zcl.ManufacturerCode.SCHNEIDER_ELECTRIC}); + await entity.write('lightingBallastCfg', {wiserControlMode: controlMode}, {manufacturerCode: Zcl.ManufacturerCode.SCHNEIDER_ELECTRIC}); return {state: {dimmer_mode: value}}; }, convertGet: async (entity, key, meta) => { @@ -4145,7 +4181,7 @@ const converters2 = { convertSet: async (entity, key, value, meta) => { utils.assertNumber(value); utils.assertEndpoint(entity); - await entity.report('msTemperatureMeasurement', {'measuredValue': Math.round(value * 100)}); + await entity.report('msTemperatureMeasurement', {measuredValue: Math.round(value * 100)}); }, } satisfies Tz.Converter, schneider_thermostat_system_mode: { @@ -4198,11 +4234,10 @@ const converters2 = { key: ['fip_setting'], convertSet: async (entity, key, value, meta) => { utils.assertString(value, key); - const zoneLookup = {'manual': 1, 'schedule': 2, 'energy_saver': 3, 'holiday': 6}; + const zoneLookup = {manual: 1, schedule: 2, energy_saver: 3, holiday: 6}; const zonemodeNum = utils.getFromLookup(meta.state.zone_mode, zoneLookup); - const fipLookup = {'comfort': 0, 'comfort_-1': 1, 'comfort_-2': 2, 'energy_saving': 3, - 'frost_protection': 4, 'off': 5}; + const fipLookup = {comfort: 0, 'comfort_-1': 1, 'comfort_-2': 2, energy_saving: 3, frost_protection: 4, off: 5}; value = value.toLowerCase(); utils.validateValue(value, Object.keys(fipLookup)); const fipmodeNum = utils.getFromLookup(value, fipLookup); @@ -4212,10 +4247,9 @@ const converters2 = { fipmode: fipmodeNum, reserved: 0xff, }; - await entity.command('hvacThermostat', 'wiserSmartSetFipMode', payload, - {srcEndpoint: 11, disableDefaultResponse: true}); + await entity.command('hvacThermostat', 'wiserSmartSetFipMode', payload, {srcEndpoint: 11, disableDefaultResponse: true}); - return {state: {'fip_setting': value}}; + return {state: {fip_setting: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('hvacThermostat', [0xe020]); @@ -4225,11 +4259,11 @@ const converters2 = { key: ['hact_config'], convertSet: async (entity, key, value, meta) => { utils.assertString(value, key); - const lookup = {'unconfigured': 0x00, 'setpoint_switch': 0x80, 'setpoint_fip': 0x82, 'fip_fip': 0x83}; + const lookup = {unconfigured: 0x00, setpoint_switch: 0x80, setpoint_fip: 0x82, fip_fip: 0x83}; value = value.toLowerCase(); const mode = utils.getFromLookup(value, lookup); await entity.write('hvacThermostat', {0xe011: {value: mode, type: 0x18}}); - return {state: {'hact_config': value}}; + return {state: {hact_config: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('hvacThermostat', [0xe011]); @@ -4238,10 +4272,10 @@ const converters2 = { wiser_zone_mode: { key: ['zone_mode'], convertSet: async (entity, key, value, meta) => { - const lookup = {'manual': 1, 'schedule': 2, 'energy_saver': 3, 'holiday': 6}; + const lookup = {manual: 1, schedule: 2, energy_saver: 3, holiday: 6}; const zonemodeNum = utils.getFromLookup(value, lookup); await entity.write('hvacThermostat', {0xe010: {value: zonemodeNum, type: 0x30}}); - return {state: {'zone_mode': value}}; + return {state: {zone_mode: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('hvacThermostat', [0xe010]); @@ -4250,15 +4284,14 @@ const converters2 = { wiser_vact_calibrate_valve: { key: ['calibrate_valve'], convertSet: async (entity, key, value, meta) => { - await entity.command('hvacThermostat', 'wiserSmartCalibrateValve', {}, - {srcEndpoint: 11, disableDefaultResponse: true}); - return {state: {'calibrate_valve': value}}; + await entity.command('hvacThermostat', 'wiserSmartCalibrateValve', {}, {srcEndpoint: 11, disableDefaultResponse: true}); + return {state: {calibrate_valve: value}}; }, } satisfies Tz.Converter, wiser_sed_zone_mode: { key: ['zone_mode'], convertSet: async (entity, key, value, meta) => { - return {state: {'zone_mode': value}}; + return {state: {zone_mode: value}}; }, } satisfies Tz.Converter, wiser_sed_occupied_heating_setpoint: { @@ -4268,15 +4301,18 @@ const converters2 = { utils.assertEndpoint(entity); const occupiedHeatingSetpoint = Number((Math.round(Number((value * 2).toFixed(1))) / 2).toFixed(1)) * 100; entity.saveClusterAttributeKeyValue('hvacThermostat', {occupiedHeatingSetpoint}); - return {state: {'occupied_heating_setpoint': value}}; + return {state: {occupied_heating_setpoint: value}}; }, } satisfies Tz.Converter, wiser_sed_thermostat_local_temperature_calibration: { key: ['local_temperature_calibration'], convertSet: async (entity, key, value, meta) => { utils.assertNumber(value); - await entity.write('hvacThermostat', {localTemperatureCalibration: Math.round(value * 10)}, - {srcEndpoint: 11, disableDefaultResponse: true}); + await entity.write( + 'hvacThermostat', + {localTemperatureCalibration: Math.round(value * 10)}, + {srcEndpoint: 11, disableDefaultResponse: true}, + ); return {state: {local_temperature_calibration: value}}; }, } satisfies Tz.Converter, @@ -4284,15 +4320,14 @@ const converters2 = { key: ['keypad_lockout'], convertSet: async (entity, key, value, meta) => { const keypadLockout = utils.getKey(constants.keypadLockoutMode, value, value, Number); - await entity.write('hvacUserInterfaceCfg', {keypadLockout}, - {srcEndpoint: 11, disableDefaultResponse: true}); + await entity.write('hvacUserInterfaceCfg', {keypadLockout}, {srcEndpoint: 11, disableDefaultResponse: true}); return {state: {keypad_lockout: value}}; }, } satisfies Tz.Converter, sihas_set_people: { key: ['people'], convertSet: async (entity, key, value, meta) => { - const payload = {'presentValue': value}; + const payload = {presentValue: value}; const endpoint = meta.device.endpoints.find((e) => e.supportsInputCluster('genAnalogInput')); await endpoint.write('genAnalogInput', payload); }, @@ -4309,7 +4344,7 @@ const converters2 = { // 1 - 'event' mode. keys send events. useful for handling utils.assertString(value, key); const endpoint = meta.device.getEndpoint(1); - await endpoint.write('genOnOff', {'tuyaOperationMode': utils.getFromLookup(value, {command: 0, event: 1})}); + await endpoint.write('genOnOff', {tuyaOperationMode: utils.getFromLookup(value, {command: 0, event: 1})}); return {state: {operation_mode: value.toLowerCase()}}; }, convertGet: async (entity, key, meta) => { @@ -4320,8 +4355,11 @@ const converters2 = { led_on_motion: { key: ['led_on_motion'], convertSet: async (entity, key, value, meta) => { - await entity.write('ssIasZone', {0x4000: {value: value === true ? 1 : 0, type: 0x10}}, - {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + await entity.write( + 'ssIasZone', + {0x4000: {value: value === true ? 1 : 0, type: 0x10}}, + {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}, + ); return {state: {led_on_motion: value}}; }, convertGet: async (entity, key, meta) => { @@ -4332,16 +4370,16 @@ const converters2 = { key: ['pilot_wire_mode'], convertSet: async (entity, key, value, meta) => { const mode = utils.getFromLookup(value, { - 'off': 0x00, - 'comfort': 0x01, - 'eco': 0x02, - 'frost_protection': 0x03, + off: 0x00, + comfort: 0x01, + eco: 0x02, + frost_protection: 0x03, 'comfort_-1': 0x04, 'comfort_-2': 0x05, }); - const payload = {'mode': mode}; + const payload = {mode: mode}; await entity.command('manuSpecificNodOnPilotWire', 'setMode', payload); - return {state: {'pilot_wire_mode': value}}; + return {state: {pilot_wire_mode: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificNodOnPilotWire', [0x0000], manufacturerOptions.nodon); @@ -4352,13 +4390,11 @@ const converters2 = { // #region Ignore converters ignore_transition: { key: ['transition'], - convertSet: async (entity, key, value, meta) => { - }, + convertSet: async (entity, key, value, meta) => {}, } satisfies Tz.Converter, ignore_rate: { key: ['rate'], - convertSet: async (entity, key, value, meta) => { - }, + convertSet: async (entity, key, value, meta) => {}, } satisfies Tz.Converter, // #endregion @@ -4453,7 +4489,7 @@ const converters3 = { brightness = Number(message.brightness); } else if (message.hasOwnProperty('brightness_percent')) brightness = Math.round(Number(message.brightness_percent) * 2.55); - if ((brightness !== undefined) && (brightness === 0)) { + if (brightness !== undefined && brightness === 0) { message.state = 'off'; message.brightness = 1; } diff --git a/src/devices/ITCommander.ts b/src/devices/ITCommander.ts index 94844eabb3ccc..123d8e709bcf9 100644 --- a/src/devices/ITCommander.ts +++ b/src/devices/ITCommander.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; import fz from '../converters/fromZigbee'; import * as exposes from '../lib/exposes'; +import {Definition} from '../lib/types'; const e = exposes.presets; import * as reporting from '../lib/reporting'; diff --git a/src/devices/acova.ts b/src/devices/acova.ts index 4be31d1266218..6abbfa9054c2d 100644 --- a/src/devices/acova.ts +++ b/src/devices/acova.ts @@ -1,14 +1,16 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ { - zigbeeModel: ['ALCANTARA2 D1.00P1.01Z1.00\u0000\u0000\u0000\u0000\u0000\u0000', - 'ALCANTARA2 D1.00P1.02Z1.00\u0000\u0000\u0000\u0000\u0000\u0000'], + zigbeeModel: [ + 'ALCANTARA2 D1.00P1.01Z1.00\u0000\u0000\u0000\u0000\u0000\u0000', + 'ALCANTARA2 D1.00P1.02Z1.00\u0000\u0000\u0000\u0000\u0000\u0000', + ], model: 'ALCANTARA2', vendor: 'Acova', description: 'Alcantara 2 heater', @@ -21,7 +23,8 @@ const definitions: Definition[] = [ tz.thermostat_running_state, ], exposes: [ - e.climate() + e + .climate() .withSetpoint('occupied_heating_setpoint', 7, 28, 0.5) .withSetpoint('unoccupied_heating_setpoint', 7, 28, 0.5) .withLocalTemperature() @@ -38,9 +41,14 @@ const definitions: Definition[] = [ }, }, { - zigbeeModel: ['TAFFETAS2 D1.00P1.02Z1.00\u0000\u0000\u0000\u0000\u0000\u0000\u0000', + zigbeeModel: [ + 'TAFFETAS2 D1.00P1.02Z1.00\u0000\u0000\u0000\u0000\u0000\u0000\u0000', 'TAFFETAS2 D1.00P1.01Z1.00\u0000\u0000\u0000\u0000\u0000\u0000\u0000', - 'PERCALE2 D1.00P1.01Z1.00', 'PERCALE2 D1.00P1.02Z1.00', 'PERCALE2 D1.00P1.03Z1.00', 'TAFFETAS2 D1.00P1.03Z1.00'], + 'PERCALE2 D1.00P1.01Z1.00', + 'PERCALE2 D1.00P1.02Z1.00', + 'PERCALE2 D1.00P1.03Z1.00', + 'TAFFETAS2 D1.00P1.03Z1.00', + ], model: 'TAFFETAS2/PERCALE2', vendor: 'Acova', description: 'Taffetas 2 / Percale 2 heater', @@ -54,7 +62,8 @@ const definitions: Definition[] = [ tz.thermostat_local_temperature_calibration, ], exposes: [ - e.climate() + e + .climate() .withSetpoint('occupied_heating_setpoint', 7, 28, 0.5) .withSetpoint('unoccupied_heating_setpoint', 7, 28, 0.5) .withLocalTemperature() diff --git a/src/devices/acuity_brands_lighting.ts b/src/devices/acuity_brands_lighting.ts index 9dfd06b00e6a3..f4e03fdcf78f1 100644 --- a/src/devices/acuity_brands_lighting.ts +++ b/src/devices/acuity_brands_lighting.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/adeo.ts b/src/devices/adeo.ts index a670939923039..fcbe00edf2d26 100644 --- a/src/devices/adeo.ts +++ b/src/devices/adeo.ts @@ -1,9 +1,9 @@ -import {Definition, Fz, Tz} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import * as reporting from '../lib/reporting'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import {electricityMeter, light, onOff, quirkCheckinInterval} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition, Fz, Tz} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -16,9 +16,9 @@ const fzLocal = { const zoneStatus = msg.data.zonestatus; return { contact: !((zoneStatus & 1) > 0), - vibration: (zoneStatus & 1<<1) > 0, - tamper: (zoneStatus & 1<<2) > 0, - battery_low: (zoneStatus & 1<<3) > 0, + vibration: (zoneStatus & (1 << 1)) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, }; }, } satisfies Fz.Converter, @@ -42,8 +42,14 @@ const definitions: Definition[] = [ description: 'ENKI LEXMAN wireless smart door window sensor with vibration', fromZigbee: [fzLocal.LDSENK08, fz.battery], toZigbee: [tzLocal.LDSENK08_sensitivity], - exposes: [e.battery_low(), e.contact(), e.vibration(), e.tamper(), e.battery(), - e.numeric('sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(4).withDescription('Sensitivity of the motion sensor')], + exposes: [ + e.battery_low(), + e.contact(), + e.vibration(), + e.tamper(), + e.battery(), + e.numeric('sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(4).withDescription('Sensitivity of the motion sensor'), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']); @@ -226,12 +232,41 @@ const definitions: Definition[] = [ model: 'HR-C99C-Z-C045', vendor: 'ADEO', description: 'RGB CTT LEXMAN ENKI remote control', - fromZigbee: [fz.battery, fz.command_on, fz.command_off, fz.command_step, fz.command_stop, fz.command_step_color_temperature, - fz.command_step_hue, fz.command_step_saturation, fz.color_stop_raw, fz.scenes_recall_scene_65024, fz.ignore_genOta], + fromZigbee: [ + fz.battery, + fz.command_on, + fz.command_off, + fz.command_step, + fz.command_stop, + fz.command_step_color_temperature, + fz.command_step_hue, + fz.command_step_saturation, + fz.color_stop_raw, + fz.scenes_recall_scene_65024, + fz.ignore_genOta, + ], toZigbee: [], - exposes: [e.battery(), e.action(['on', 'off', 'scene_1', 'scene_2', 'scene_3', 'scene_4', 'color_saturation_step_up', - 'color_saturation_step_down', 'color_stop', 'color_hue_step_up', 'color_hue_step_down', - 'color_temperature_step_up', 'color_temperature_step_down', 'brightness_step_up', 'brightness_step_down', 'brightness_stop'])], + exposes: [ + e.battery(), + e.action([ + 'on', + 'off', + 'scene_1', + 'scene_2', + 'scene_3', + 'scene_4', + 'color_saturation_step_up', + 'color_saturation_step_down', + 'color_stop', + 'color_hue_step_up', + 'color_hue_step_down', + 'color_temperature_step_up', + 'color_temperature_step_down', + 'brightness_step_up', + 'brightness_step_down', + 'brightness_stop', + ]), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const binds = ['genBasic', 'genOnOff', 'genPowerCfg', 'lightingColorCtrl', 'genLevelCtrl']; @@ -275,9 +310,7 @@ const definitions: Definition[] = [ fromZigbee: [fz.battery, fz.ias_siren], toZigbee: [tz.warning], exposes: [e.warning(), e.battery(), e.battery_low(), e.tamper()], - extend: [ - quirkCheckinInterval(0), - ], + extend: [quirkCheckinInterval(0)], configure: async (device, coordinatorEndpoint) => { await device.getEndpoint(1).unbind('genPollCtrl', coordinatorEndpoint); }, @@ -351,12 +384,7 @@ const definitions: Definition[] = [ description: 'Equation pilot wire heating module', fromZigbee: [fz.on_off, fz.metering, fz.nodon_pilot_wire_mode], toZigbee: [tz.on_off, tz.nodon_pilot_wire_mode], - exposes: [ - e.switch(), - e.power(), - e.energy(), - e.pilot_wire_mode(), - ], + exposes: [e.switch(), e.power(), e.energy(), e.pilot_wire_mode()], configure: async (device, coordinatorEndpoint) => { const ep = device.getEndpoint(1); await reporting.bind(ep, coordinatorEndpoint, ['genBasic', 'genIdentify', 'genOnOff', 'seMetering', 'manuSpecificNodOnPilotWire']); diff --git a/src/devices/adurosmart.ts b/src/devices/adurosmart.ts index 8e2bd95fc2175..b6b88198dfea9 100644 --- a/src/devices/adurosmart.ts +++ b/src/devices/adurosmart.ts @@ -1,10 +1,10 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; -import * as reporting from '../lib/reporting'; import {light, onOff} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; @@ -43,7 +43,7 @@ const definitions: Definition[] = [ description: 'ERIA colors and white shades smart light bulb A19/BR30', extend: [light({colorTemp: {range: undefined}, color: {applyRedFix: true}})], endpoint: (device) => { - return {'default': 2}; + return {default: 2}; }, }, { diff --git a/src/devices/aeotec.ts b/src/devices/aeotec.ts index b9865cdbbe1fd..a56cd10cdbfc0 100644 --- a/src/devices/aeotec.ts +++ b/src/devices/aeotec.ts @@ -1,4 +1,4 @@ -import {Definition} from '../lib/types'; +import fz from '../converters/fromZigbee'; import { deviceEndpoints, deviceTemperature, @@ -10,8 +10,7 @@ import { commandsOnOff, commandsLevelCtrl, } from '../lib/modernExtend'; - -import fz from '../converters/fromZigbee'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/airam.ts b/src/devices/airam.ts index 6791ca53fa51c..34d6bdf95d298 100644 --- a/src/devices/airam.ts +++ b/src/devices/airam.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; -import * as reporting from '../lib/reporting'; import {light} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; @@ -20,11 +20,30 @@ const definitions: Definition[] = [ model: 'AIRAM-CTR.U', vendor: 'Airam', description: 'CTR.U remote', - exposes: [e.action(['on', 'off', 'brightness_down_click', 'brightness_up_click', 'brightness_down_hold', 'brightness_up_hold', - 'brightness_down_release', 'brightness_up_release'])], - fromZigbee: [fz.command_on, legacy.fz.genOnOff_cmdOn, fz.command_off, legacy.fz.genOnOff_cmdOff, - legacy.fz.CTR_U_brightness_updown_click, fz.ignore_basic_report, - legacy.fz.CTR_U_brightness_updown_hold, legacy.fz.CTR_U_brightness_updown_release, fz.command_recall, legacy.fz.CTR_U_scene], + exposes: [ + e.action([ + 'on', + 'off', + 'brightness_down_click', + 'brightness_up_click', + 'brightness_down_hold', + 'brightness_up_hold', + 'brightness_down_release', + 'brightness_up_release', + ]), + ], + fromZigbee: [ + fz.command_on, + legacy.fz.genOnOff_cmdOn, + fz.command_off, + legacy.fz.genOnOff_cmdOff, + legacy.fz.CTR_U_brightness_updown_click, + fz.ignore_basic_report, + legacy.fz.CTR_U_brightness_updown_hold, + legacy.fz.CTR_U_brightness_updown_release, + fz.command_recall, + legacy.fz.CTR_U_scene, + ], toZigbee: [], }, { @@ -32,10 +51,19 @@ const definitions: Definition[] = [ model: 'CTR.UBX', vendor: 'Airam', description: 'CTR.U remote BX', - fromZigbee: [fz.command_on, fz.command_off, fz.command_step, fz.command_move, fz.command_stop, fz.command_recall, - fz.ignore_basic_report], - exposes: [e.action(['on', 'off', 'brightness_step_up', 'brightness_step_down', 'brightness_move_up', 'brightness_move_down', - 'brightness_stop', 'recall_*'])], + fromZigbee: [fz.command_on, fz.command_off, fz.command_step, fz.command_move, fz.command_stop, fz.command_recall, fz.ignore_basic_report], + exposes: [ + e.action([ + 'on', + 'off', + 'brightness_step_up', + 'brightness_step_down', + 'brightness_move_up', + 'brightness_move_down', + 'brightness_stop', + 'recall_*', + ]), + ], toZigbee: [], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); diff --git a/src/devices/airzone_aidoo.ts b/src/devices/airzone_aidoo.ts index 86b0cfed9430d..42e6063f4e0ed 100644 --- a/src/devices/airzone_aidoo.ts +++ b/src/devices/airzone_aidoo.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; @@ -22,7 +22,8 @@ const definitions: Definition[] = [ tz.fan_mode, ], exposes: [ - e.climate() + e + .climate() .withLocalTemperature() .withSystemMode(['off', 'auto', 'cool', 'heat', 'fan_only', 'dry']) .withFanMode(['off', 'low', 'medium', 'high', 'on', 'auto']) diff --git a/src/devices/ajax_online.ts b/src/devices/ajax_online.ts index 44a4673a05a95..d31c1f316a33d 100644 --- a/src/devices/ajax_online.ts +++ b/src/devices/ajax_online.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; -import * as tuya from '../lib/tuya'; import {light} from '../lib/modernExtend'; +import * as tuya from '../lib/tuya'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/akuvox.ts b/src/devices/akuvox.ts index c5bb1f4f929c9..e491a2d8dc290 100644 --- a/src/devices/akuvox.ts +++ b/src/devices/akuvox.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/alchemy.ts b/src/devices/alchemy.ts index 9cc1d4a3330cb..2465c54c9be35 100644 --- a/src/devices/alchemy.ts +++ b/src/devices/alchemy.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/aldi.ts b/src/devices/aldi.ts index 516e0314d19f8..4e675930f1a60 100644 --- a/src/devices/aldi.ts +++ b/src/devices/aldi.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ @@ -9,8 +9,9 @@ const definitions: Definition[] = [ model: '141L100RC', vendor: 'Aldi', description: 'MEGOS switch and dimming light remote control', - exposes: [e.action(['on', 'off', 'brightness_stop', 'brightness_step_up', 'brightness_step_down', 'brightness_move_up', - 'brightness_move_down'])], + exposes: [ + e.action(['on', 'off', 'brightness_stop', 'brightness_step_up', 'brightness_step_down', 'brightness_move_up', 'brightness_move_down']), + ], fromZigbee: [fz.command_on, fz.command_off, fz.command_step, fz.command_move, fz.command_stop], toZigbee: [], }, diff --git a/src/devices/alecto.ts b/src/devices/alecto.ts index 8d99234632d9b..31a5ddb7c6562 100644 --- a/src/devices/alecto.ts +++ b/src/devices/alecto.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; +import {Definition} from '../lib/types'; const fz = {...require('../converters/fromZigbee'), legacy: legacy.fromZigbee}; const tz = {...require('../converters/toZigbee'), legacy: legacy.toZigbee}; const e = exposes.presets; @@ -16,14 +16,27 @@ const definitions: Definition[] = [ vendor: 'Alecto', description: 'Radiator valve with thermostat', fromZigbee: [fz.legacy.tuya_thermostat, fz.ignore_basic_report], - meta: {tuyaThermostatSystemMode: legacy.thermostatSystemModes4, tuyaThermostatPreset: legacy.thermostatPresets, - tuyaThermostatPresetToSystemMode: legacy.thermostatSystemModes4}, - toZigbee: [tz.legacy.tuya_thermostat_child_lock, tz.legacy.siterwell_thermostat_window_detection, - tz.legacy.tuya_thermostat_current_heating_setpoint, tz.legacy.tuya_thermostat_system_mode, + meta: { + tuyaThermostatSystemMode: legacy.thermostatSystemModes4, + tuyaThermostatPreset: legacy.thermostatPresets, + tuyaThermostatPresetToSystemMode: legacy.thermostatSystemModes4, + }, + toZigbee: [ + tz.legacy.tuya_thermostat_child_lock, + tz.legacy.siterwell_thermostat_window_detection, + tz.legacy.tuya_thermostat_current_heating_setpoint, + tz.legacy.tuya_thermostat_system_mode, + ], + exposes: [ + e.child_lock(), + e.window_detection(), + e.battery(), + e + .climate() + .withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.STATE_SET) + .withLocalTemperature(ea.STATE) + .withSystemMode(['off', 'auto', 'heat'], ea.STATE_SET), ], - exposes: [e.child_lock(), e.window_detection(), e.battery(), e.climate() - .withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.STATE_SET).withLocalTemperature(ea.STATE) - .withSystemMode(['off', 'auto', 'heat'], ea.STATE_SET)], }, { fingerprint: [ @@ -36,14 +49,16 @@ const definitions: Definition[] = [ fromZigbee: [fz.legacy.tuya_alecto_smoke], toZigbee: [tz.legacy.tuya_alecto_smoke], meta: {}, - exposes: [e.enum('smoke_state', ea.STATE, ['alarm', 'normal']), + exposes: [ + e.enum('smoke_state', ea.STATE, ['alarm', 'normal']), e.enum('battery_state', ea.STATE, ['low', 'middle', 'high']), e.enum('checking_result', ea.STATE, ['checking', 'check_success', 'check_failure', 'others']), e.numeric('smoke_value', ea.STATE), e.numeric('battery', ea.STATE), e.binary('lifecycle', ea.STATE, true, false), e.binary('self_checking', ea.STATE_SET, true, false), - e.binary('silence', ea.STATE_SET, true, false)], + e.binary('silence', ea.STATE_SET, true, false), + ], }, ]; diff --git a/src/devices/anchor.ts b/src/devices/anchor.ts index cd211ea72e10f..23e84cc27f6b5 100644 --- a/src/devices/anchor.ts +++ b/src/devices/anchor.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/atlantic.ts b/src/devices/atlantic.ts index e511183be13cd..f132f8f20a8fc 100644 --- a/src/devices/atlantic.ts +++ b/src/devices/atlantic.ts @@ -1,20 +1,20 @@ +import assert from 'assert'; import {Zcl} from 'zigbee-herdsman'; -import {Definition, KeyValue, Tz} from '../lib/types'; + import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition, KeyValue, Tz} from '../lib/types'; import * as utils from '../lib/utils'; -import assert from 'assert'; const e = exposes.presets; const ea = exposes.access; - const thermostatPositions: KeyValue = { - 'quarter_open': 1, - 'half_open': 2, - 'three_quarters_open': 3, - 'fully_open': 4, + quarter_open: 1, + half_open: 2, + three_quarters_open: 3, + fully_open: 4, }; const tzLocal = { @@ -47,7 +47,7 @@ const tzLocal = { const eco = value === 'eco' ? 4 : 0; await entity.write('hvacThermostat', {0x4275: {value: activity, type: 0x30}}, {manufacturerCode: Zcl.ManufacturerCode.ATLANTIC_GROUP}); - await entity.write('hvacThermostat', {'programingOperMode': eco}); + await entity.write('hvacThermostat', {programingOperMode: eco}); await entity.write('hvacThermostat', {0x4270: {value: boost, type: 0x10}}, {manufacturerCode: Zcl.ManufacturerCode.ATLANTIC_GROUP}); return {state: {preset: value}}; @@ -75,10 +75,7 @@ const definitions: Definition[] = [ model: 'GW003-AS-IN-TE-FC', vendor: 'Atlantic Group', description: 'Interface Naviclim for Takao air conditioners', - fromZigbee: [ - fz.thermostat, - fz.fan, - ], + fromZigbee: [fz.thermostat, fz.fan], toZigbee: [ tzLocal.ac_louver_position, tzLocal.preset, @@ -93,7 +90,8 @@ const definitions: Definition[] = [ ], exposes: [ e.programming_operation_mode(), - e.climate() + e + .climate() .withLocalTemperature() .withSetpoint('occupied_cooling_setpoint', 18, 30, 0.5) .withSetpoint('occupied_heating_setpoint', 16, 30, 0.5) @@ -102,8 +100,7 @@ const definitions: Definition[] = [ .withFanMode(['low', 'medium', 'high', 'auto']) .withSwingMode(['on', 'off'], ea.STATE_SET), e.binary('quiet_fan', ea.STATE_SET, true, false).withDescription('Fan quiet mode'), - e.enum('ac_louver_position', ea.STATE_SET, Object.keys(thermostatPositions)) - .withDescription('Ac louver position of this device'), + e.enum('ac_louver_position', ea.STATE_SET, Object.keys(thermostatPositions)).withDescription('Ac louver position of this device'), ], configure: async (device, coordinatorEndpoint) => { const endpoint1 = device.getEndpoint(1); diff --git a/src/devices/atsmart.ts b/src/devices/atsmart.ts index c9265d4c3dcf5..73be4ac53412f 100644 --- a/src/devices/atsmart.ts +++ b/src/devices/atsmart.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {deviceEndpoints, onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { @@ -7,10 +7,7 @@ const definitions: Definition[] = [ model: 'Z6', vendor: 'Atsmart', description: '3 gang smart wall switch (no neutral wire)', - extend: [ - deviceEndpoints({endpoints: {'left': 1, 'center': 2, 'right': 3}}), - onOff({endpointNames: ['left', 'center', 'right']}), - ], + extend: [deviceEndpoints({endpoints: {left: 1, center: 2, right: 3}}), onOff({endpointNames: ['left', 'center', 'right']})], }, ]; diff --git a/src/devices/aubess.ts b/src/devices/aubess.ts index 9d5b60fcd3876..a70e6e0a8a02a 100644 --- a/src/devices/aubess.ts +++ b/src/devices/aubess.ts @@ -11,12 +11,14 @@ const definitions: Definition[] = [ vendor: 'Aubess', description: 'Universal smart IR remote control', fromZigbee: [ - fzZosung.zosung_send_ir_code_00, fzZosung.zosung_send_ir_code_01, fzZosung.zosung_send_ir_code_02, - fzZosung.zosung_send_ir_code_03, fzZosung.zosung_send_ir_code_04, fzZosung.zosung_send_ir_code_05, - ], - toZigbee: [ - tzZosung.zosung_ir_code_to_send, tzZosung.zosung_learn_ir_code, + fzZosung.zosung_send_ir_code_00, + fzZosung.zosung_send_ir_code_01, + fzZosung.zosung_send_ir_code_02, + fzZosung.zosung_send_ir_code_03, + fzZosung.zosung_send_ir_code_04, + fzZosung.zosung_send_ir_code_05, ], + toZigbee: [tzZosung.zosung_ir_code_to_send, tzZosung.zosung_learn_ir_code], exposes: [ez.learn_ir_code(), ez.learned_ir_code(), ez.ir_code_to_send()], }, ]; diff --git a/src/devices/aurora_lighting.ts b/src/devices/aurora_lighting.ts index 08859d612aebb..592fb7e9b49c9 100644 --- a/src/devices/aurora_lighting.ts +++ b/src/devices/aurora_lighting.ts @@ -1,12 +1,12 @@ -import {Configure, Definition, Fz, OnEvent, Tz, Zh} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Configure, Definition, Fz, OnEvent, Tz, Zh} from '../lib/types'; const e = exposes.presets; -import * as utils from '../lib/utils'; import {identify, light} from '../lib/modernExtend'; import * as ota from '../lib/ota'; +import * as utils from '../lib/utils'; const ea = exposes.access; @@ -39,15 +39,17 @@ const disableBatteryRotaryDimmerReporting = async (endpoint: Zh.Endpoint) => { // The default is for the device to also report the on/off and // brightness at the same time as sending on/off and step commands. // Disable the reporting by setting the max interval to 0xFFFF. - await reporting.brightness(endpoint, {max: 0xFFFF}); - await reporting.onOff(endpoint, {max: 0xFFFF}); + await reporting.brightness(endpoint, {max: 0xffff}); + await reporting.onOff(endpoint, {max: 0xffff}); }; const batteryRotaryDimmer = (...endpointsIds: number[]) => ({ fromZigbee: [fz.battery, fz.command_on, fz.command_off, fz.command_step, fz.command_step_color_temperature] satisfies Fz.Converter[], toZigbee: [] as Tz.Converter[], // TODO: Needs documented reasoning for asserting this as a type it isn't - exposes: [e.battery(), e.action([ - 'on', 'off', 'brightness_step_up', 'brightness_step_down', 'color_temperature_step_up', 'color_temperature_step_down'])], + exposes: [ + e.battery(), + e.action(['on', 'off', 'brightness_step_up', 'brightness_step_down', 'color_temperature_step_up', 'color_temperature_step_down']), + ], configure: (async (device, coordinatorEndpoint) => { const endpoints = endpointsIds.map((endpoint) => device.getEndpoint(endpoint)); @@ -55,8 +57,7 @@ const batteryRotaryDimmer = (...endpointsIds: number[]) => ({ await reporting.batteryVoltage(endpoints[0]); for await (const endpoint of endpoints) { - await reporting.bind(endpoint, coordinatorEndpoint, - ['genIdentify', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl']); + await reporting.bind(endpoint, coordinatorEndpoint, ['genIdentify', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl']); await disableBatteryRotaryDimmerReporting(endpoint); } @@ -72,10 +73,14 @@ const batteryRotaryDimmer = (...endpointsIds: number[]) => ({ } // Then re-apply the configured reportings for (const c of endpoint.configuredReportings) { - await endpoint.configureReporting(c.cluster.name, [{ - attribute: c.attribute.name, minimumReportInterval: c.minimumReportInterval, - maximumReportInterval: c.maximumReportInterval, reportableChange: c.reportableChange, - }]); + await endpoint.configureReporting(c.cluster.name, [ + { + attribute: c.attribute.name, + minimumReportInterval: c.minimumReportInterval, + maximumReportInterval: c.maximumReportInterval, + reportableChange: c.reportableChange, + }, + ]); } } } @@ -218,15 +223,18 @@ const definitions: Definition[] = [ vendor: 'Aurora Lighting', description: 'Double smart socket UK', fromZigbee: [fz.identify, fz.on_off, fz.electrical_measurement, fz.brightness], - exposes: [e.switch().withEndpoint('left'), e.switch().withEndpoint('right'), - e.power().withEndpoint('left'), e.power().withEndpoint('right'), - e.numeric('brightness', ea.ALL).withValueMin(0).withValueMax(254) - .withDescription('Brightness of this backlight LED')], + exposes: [ + e.switch().withEndpoint('left'), + e.switch().withEndpoint('right'), + e.power().withEndpoint('left'), + e.power().withEndpoint('right'), + e.numeric('brightness', ea.ALL).withValueMin(0).withValueMax(254).withDescription('Brightness of this backlight LED'), + ], toZigbee: [tzLocal.backlight_brightness, tz.on_off], meta: {multiEndpoint: true}, ota: ota.zigbeeOTA, endpoint: (device) => { - return {'left': 1, 'right': 2}; + return {left: 1, right: 2}; }, configure: async (device, coordinatorEndpoint) => { const endpoint1 = device.getEndpoint(1); @@ -246,12 +254,17 @@ const definitions: Definition[] = [ exposes: [e.switch(), e.power(), e.voltage(), e.current(), e.device_temperature(), e.energy()], toZigbee: [tz.on_off], endpoint: (device) => { - return {'default': 2}; + return {default: 2}; }, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(2); - await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genIdentify', 'haElectricalMeasurement', 'seMetering', - 'genDeviceTempCfg']); + await reporting.bind(endpoint, coordinatorEndpoint, [ + 'genOnOff', + 'genIdentify', + 'haElectricalMeasurement', + 'seMetering', + 'genDeviceTempCfg', + ]); await reporting.onOff(endpoint); await reporting.deviceTemperature(endpoint); @@ -282,7 +295,7 @@ const definitions: Definition[] = [ description: 'AOne two gang wireless battery rotary dimmer', meta: {multiEndpoint: true, battery: {voltageToPercentage: '3V_2100'}}, endpoint: (device) => { - return {'right': 1, 'left': 2}; + return {right: 1, left: 2}; }, // Two gang battery rotary dimmer with endpoint IDs 1 and 2 ...batteryRotaryDimmer(1, 2), diff --git a/src/devices/automaton.ts b/src/devices/automaton.ts index 2f350860a2d15..df8c78ada35d8 100644 --- a/src/devices/automaton.ts +++ b/src/devices/automaton.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; import * as reporting from '../lib/reporting'; import * as tuya from '../lib/tuya'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { @@ -10,7 +10,7 @@ const definitions: Definition[] = [ description: 'Underfloor heating controller - 5 zones', extend: [tuya.modernExtend.tuyaOnOff({powerOnBehavior2: true, childLock: true, endpoints: ['l1', 'l2', 'l3', 'l4', 'l5']})], endpoint: (device) => { - return {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4, 'l5': 5}; + return {l1: 1, l2: 2, l3: 3, l4: 4, l5: 5}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { diff --git a/src/devices/awox.ts b/src/devices/awox.ts index c6f5e0092c566..6cb9886485e4e 100644 --- a/src/devices/awox.ts +++ b/src/devices/awox.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; import fz from '../converters/fromZigbee'; import * as exposes from '../lib/exposes'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const e = exposes.presets; @@ -25,7 +25,11 @@ const definitions: Definition[] = [ zigbeeModel: ['ERCU_Zm'], fingerprint: [ { - type: 'EndDevice', manufacturerName: 'AwoX', modelID: 'TLSR82xx', powerSource: 'Battery', endpoints: [ + type: 'EndDevice', + manufacturerName: 'AwoX', + modelID: 'TLSR82xx', + powerSource: 'Battery', + endpoints: [ {ID: 1, profileID: 260, deviceID: 2048, inputClusters: [0, 3, 4, 4096], outputClusters: [0, 3, 4, 5, 6, 8, 768, 4096]}, {ID: 3, profileID: 4751, deviceID: 2048, inputClusters: [65360, 65361], outputClusters: [65360, 65361]}, ], @@ -34,44 +38,86 @@ const definitions: Definition[] = [ model: '33952', vendor: 'AwoX', description: 'Remote controller', - fromZigbee: [fz.command_on, fz.awox_colors, fz.awox_refresh, fz.awox_refreshColored, fz.command_off, - fz.command_step, fz.command_move, fz.command_stop, fz.command_recall, fz.command_step_color_temperature], + fromZigbee: [ + fz.command_on, + fz.awox_colors, + fz.awox_refresh, + fz.awox_refreshColored, + fz.command_off, + fz.command_step, + fz.command_move, + fz.command_stop, + fz.command_recall, + fz.command_step_color_temperature, + ], toZigbee: [], - exposes: [e.action(['on', 'off', 'red', 'refresh', 'refresh_colored', 'blue', 'yellow', - 'green', 'brightness_step_up', 'brightness_step_down', 'brightness_move_up', 'brightness_move_down', 'brightness_stop', - 'recall_1', 'color_temperature_step_up', 'color_temperature_step_down'])], + exposes: [ + e.action([ + 'on', + 'off', + 'red', + 'refresh', + 'refresh_colored', + 'blue', + 'yellow', + 'green', + 'brightness_step_up', + 'brightness_step_down', + 'brightness_move_up', + 'brightness_move_down', + 'brightness_stop', + 'recall_1', + 'color_temperature_step_up', + 'color_temperature_step_down', + ]), + ], }, { fingerprint: [ { - type: 'Router', manufacturerName: 'AwoX', modelID: 'TLSR82xx', endpoints: [ + type: 'Router', + manufacturerName: 'AwoX', + modelID: 'TLSR82xx', + endpoints: [ {ID: 1, profileID: 260, deviceID: 269, inputClusters: [0, 3, 4, 5, 6, 8, 768, 4096, 64599, 10], outputClusters: [6]}, {ID: 3, profileID: 4751, deviceID: 269, inputClusters: [65360, 65361], outputClusters: [65360, 65361]}, {ID: 242, profileID: 41440, deviceID: 97, inputClusters: [], outputClusters: [33]}, ], }, { - type: 'Router', manufacturerName: 'AwoX', modelID: 'TLSR82xx', endpoints: [ + type: 'Router', + manufacturerName: 'AwoX', + modelID: 'TLSR82xx', + endpoints: [ {ID: 1, profileID: 260, deviceID: 269, inputClusters: [0, 3, 4, 5, 6, 8, 768, 4096, 64599, 10], outputClusters: [6]}, {ID: 3, profileID: 4751, deviceID: 269, inputClusters: [65360, 65361, 4], outputClusters: [65360, 65361]}, {ID: 242, profileID: 41440, deviceID: 97, inputClusters: [], outputClusters: [33]}, ], }, { - type: 'Router', manufacturerName: 'AwoX', modelID: 'TLSR82xx', endpoints: [ + type: 'Router', + manufacturerName: 'AwoX', + modelID: 'TLSR82xx', + endpoints: [ {ID: 1, profileID: 260, deviceID: 258, inputClusters: [0, 3, 4, 5, 6, 8, 768, 4096], outputClusters: [6, 25]}, {ID: 3, profileID: 49152, deviceID: 258, inputClusters: [65360, 65361], outputClusters: [65360, 65361]}, ], }, { - type: 'Router', manufacturerName: 'AwoX', modelID: 'TLSR82xx', endpoints: [ + type: 'Router', + manufacturerName: 'AwoX', + modelID: 'TLSR82xx', + endpoints: [ {ID: 1, profileID: 260, deviceID: 269, inputClusters: [0, 3, 4, 5, 6, 8, 768, 4096, 64599], outputClusters: [6]}, {ID: 3, profileID: 4751, deviceID: 269, inputClusters: [65360, 65361], outputClusters: [65360, 65361]}, {ID: 242, profileID: 41440, deviceID: 97, inputClusters: [], outputClusters: [33]}, ], }, { - type: 'Router', manufacturerName: 'AwoX', modelID: 'TLSR82xx', endpoints: [ + type: 'Router', + manufacturerName: 'AwoX', + modelID: 'TLSR82xx', + endpoints: [ {ID: 1, profileID: 260, deviceID: 269, inputClusters: [0, 3, 4, 5, 6, 8, 768, 4096, 64599], outputClusters: [6]}, {ID: 3, profileID: 4751, deviceID: 269, inputClusters: [65360, 65361], outputClusters: [65360, 65361]}, ], @@ -85,27 +131,39 @@ const definitions: Definition[] = [ { fingerprint: [ { - type: 'Router', manufacturerName: 'AwoX', modelID: 'TLSR82xx', endpoints: [ + type: 'Router', + manufacturerName: 'AwoX', + modelID: 'TLSR82xx', + endpoints: [ {ID: 1, profileID: 260, deviceID: 268, inputClusters: [0, 3, 4, 5, 6, 8, 768, 4096, 64599], outputClusters: [6]}, {ID: 3, profileID: 4751, deviceID: 268, inputClusters: [65360, 65361], outputClusters: [65360, 65361]}, ], }, { - type: 'Router', manufacturerName: 'AwoX', modelID: 'TLSR82xx', endpoints: [ + type: 'Router', + manufacturerName: 'AwoX', + modelID: 'TLSR82xx', + endpoints: [ {ID: 1, profileID: 260, deviceID: 268, inputClusters: [0, 3, 4, 5, 6, 8, 768, 4096, 64599], outputClusters: [6]}, {ID: 242, profileID: 41440, deviceID: 97, inputClusters: [], outputClusters: [33]}, {ID: 3, profileID: 4751, deviceID: 268, inputClusters: [65360, 65361], outputClusters: [65360, 65361]}, ], }, { - type: 'Router', manufacturerName: 'AwoX', modelID: 'TLSR82xx', endpoints: [ + type: 'Router', + manufacturerName: 'AwoX', + modelID: 'TLSR82xx', + endpoints: [ {ID: 1, profileID: 260, deviceID: 268, inputClusters: [0, 3, 4, 5, 6, 8, 768, 4096, 64599, 10], outputClusters: [6]}, {ID: 242, profileID: 41440, deviceID: 97, inputClusters: [], outputClusters: [33]}, {ID: 3, profileID: 4751, deviceID: 268, inputClusters: [65360, 65361], outputClusters: [65360, 65361]}, ], }, { - type: 'Router', manufacturerName: 'AwoX', modelID: 'TLSR82xx', endpoints: [ + type: 'Router', + manufacturerName: 'AwoX', + modelID: 'TLSR82xx', + endpoints: [ {ID: 1, profileID: 260, deviceID: 268, inputClusters: [0, 3, 4, 5, 6, 8, 768, 4096, 64599, 10], outputClusters: [6]}, {ID: 242, profileID: 41440, deviceID: 97, inputClusters: [], outputClusters: [33]}, {ID: 3, profileID: 4751, deviceID: 268, inputClusters: [65360, 65361, 4], outputClusters: [65360, 65361]}, @@ -122,7 +180,10 @@ const definitions: Definition[] = [ zigbeeModel: ['EGLO_ZM_TW'], fingerprint: [ { - type: 'Router', manufacturerName: 'AwoX', modelID: 'TLSR82xx', endpoints: [ + type: 'Router', + manufacturerName: 'AwoX', + modelID: 'TLSR82xx', + endpoints: [ {ID: 1, profileID: 260, deviceID: 268, inputClusters: [0, 3, 4, 5, 6, 8, 768, 4096], outputClusters: [6, 25]}, {ID: 3, profileID: 49152, deviceID: 268, inputClusters: [65360, 65361], outputClusters: [65360, 65361]}, ], @@ -132,7 +193,11 @@ const definitions: Definition[] = [ vendor: 'AwoX', description: 'LED light with color temperature', extend: [light({colorTemp: {range: [153, 370]}})], - whiteLabel: [{vendor: 'EGLO', model: '900316'}, {vendor: 'EGLO', model: '900317'}, {vendor: 'EGLO', model: '900053'}], + whiteLabel: [ + {vendor: 'EGLO', model: '900316'}, + {vendor: 'EGLO', model: '900317'}, + {vendor: 'EGLO', model: '900053'}, + ], }, ]; diff --git a/src/devices/axis.ts b/src/devices/axis.ts index 3fbb6f6fd02d4..4d8cd9c120bf2 100644 --- a/src/devices/axis.ts +++ b/src/devices/axis.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ { diff --git a/src/devices/bankamp.ts b/src/devices/bankamp.ts index 94c7bbced2c8e..48679b54e7afa 100644 --- a/src/devices/bankamp.ts +++ b/src/devices/bankamp.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/bega.ts b/src/devices/bega.ts index 2a5f71cb39222..a9463184322b7 100644 --- a/src/devices/bega.ts +++ b/src/devices/bega.ts @@ -1,12 +1,15 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { fingerprint: [ - {type: 'Router', manufacturerName: 'BEGA Gantenbrink-Leuchten KG', modelID: '', endpoints: [ - {ID: 1, profileID: 260, deviceID: 258, inputClusters: [0, 3, 4, 5, 6, 8, 9, 768, 769, 64733], outputClusters: [25]}, - ]}, + { + type: 'Router', + manufacturerName: 'BEGA Gantenbrink-Leuchten KG', + modelID: '', + endpoints: [{ID: 1, profileID: 260, deviceID: 258, inputClusters: [0, 3, 4, 5, 6, 8, 9, 768, 769, 64733], outputClusters: [25]}], + }, ], model: '70049', vendor: 'Bega', diff --git a/src/devices/belkin.ts b/src/devices/belkin.ts index db7bc558b5303..3a1e5d01051e0 100644 --- a/src/devices/belkin.ts +++ b/src/devices/belkin.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/bitron.ts b/src/devices/bitron.ts index 38a1e2d490009..0c26947008ee2 100644 --- a/src/devices/bitron.ts +++ b/src/devices/bitron.ts @@ -1,11 +1,12 @@ -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; import * as reporting from '../lib/reporting'; const e = exposes.presets; const ea = exposes.access; import {Zcl} from 'zigbee-herdsman'; + import {onOff, light} from '../lib/modernExtend'; import {KeyValueAny, Fz, Tz, Definition} from '../lib/types'; @@ -39,12 +40,12 @@ const bitron = { convertSet: async (entity, key, value: KeyValueAny, meta) => { const result: KeyValueAny = {state: {hysteresis: {}}}; if (value.hasOwnProperty('high')) { - await entity.write('hvacThermostat', {'fourNoksHysteresisHigh': value.high}, manufacturerOptions); + await entity.write('hvacThermostat', {fourNoksHysteresisHigh: value.high}, manufacturerOptions); result.state.hysteresis.high = value.high; } if (value.hasOwnProperty('low')) { - await entity.write('hvacThermostat', {'fourNoksHysteresisLow': value.low}, manufacturerOptions); + await entity.write('hvacThermostat', {fourNoksHysteresisLow: value.low}, manufacturerOptions); result.state.hysteresis.low = value.low; } @@ -227,14 +228,21 @@ const definitions: Definition[] = [ description: 'Wireless wall thermostat with relay', fromZigbee: [legacy.fz.thermostat_att_report, fz.battery, fz.hvac_user_interface, bitron.fz.thermostat_hysteresis], toZigbee: [ - tz.thermostat_control_sequence_of_operation, tz.thermostat_occupied_heating_setpoint, - tz.thermostat_occupied_cooling_setpoint, tz.thermostat_local_temperature_calibration, - tz.thermostat_local_temperature, tz.thermostat_running_state, tz.thermostat_temperature_display_mode, - tz.thermostat_keypad_lockout, tz.thermostat_system_mode, tz.battery_voltage, bitron.tz.thermostat_hysteresis, + tz.thermostat_control_sequence_of_operation, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_occupied_cooling_setpoint, + tz.thermostat_local_temperature_calibration, + tz.thermostat_local_temperature, + tz.thermostat_running_state, + tz.thermostat_temperature_display_mode, + tz.thermostat_keypad_lockout, + tz.thermostat_system_mode, + tz.battery_voltage, + bitron.tz.thermostat_hysteresis, ], exposes: (device, options) => { const dynExposes = []; - let ctrlSeqeOfOper = (device?.getEndpoint(1).getClusterAttributeValue('hvacThermostat', 'ctrlSeqeOfOper') ?? null); + let ctrlSeqeOfOper = device?.getEndpoint(1).getClusterAttributeValue('hvacThermostat', 'ctrlSeqeOfOper') ?? null; const modes = []; if (typeof ctrlSeqeOfOper === 'string') ctrlSeqeOfOper = parseInt(ctrlSeqeOfOper) ?? null; @@ -251,18 +259,22 @@ const definitions: Definition[] = [ modes.push('cool'); } - const hysteresisExposes = e.composite('hysteresis', 'hysteresis', ea.ALL) + const hysteresisExposes = e + .composite('hysteresis', 'hysteresis', ea.ALL) .withFeature(e.numeric('low', ea.SET)) .withFeature(e.numeric('high', ea.SET)) .withDescription('Set thermostat hysteresis low and high trigger values. (1 = 0.01ºC)'); - dynExposes.push(e.climate() - .withSetpoint('occupied_heating_setpoint', 7, 30, 0.5) - .withLocalTemperature() - .withSystemMode(['off'].concat(modes)) - .withRunningState(['idle'].concat(modes)) - .withLocalTemperatureCalibration() - .withControlSequenceOfOperation(['heating_only', 'cooling_only'], ea.ALL)); + dynExposes.push( + e + .climate() + .withSetpoint('occupied_heating_setpoint', 7, 30, 0.5) + .withLocalTemperature() + .withSystemMode(['off'].concat(modes)) + .withRunningState(['idle'].concat(modes)) + .withLocalTemperatureCalibration() + .withControlSequenceOfOperation(['heating_only', 'cooling_only'], ea.ALL), + ); dynExposes.push(e.keypad_lockout()); dynExposes.push(hysteresisExposes); dynExposes.push(e.battery().withAccess(ea.STATE_GET)); @@ -274,9 +286,7 @@ const definitions: Definition[] = [ meta: {battery: {voltageToPercentage: '3V_2500'}}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); - const binds = [ - 'genBasic', 'genPowerCfg', 'genIdentify', 'genPollCtrl', 'hvacThermostat', 'hvacUserInterfaceCfg', - ]; + const binds = ['genBasic', 'genPowerCfg', 'genIdentify', 'genPollCtrl', 'hvacThermostat', 'hvacUserInterfaceCfg']; await reporting.bind(endpoint, coordinatorEndpoint, binds); await reporting.thermostatTemperature(endpoint); await reporting.thermostatOccupiedHeatingSetpoint(endpoint); diff --git a/src/devices/bituo_technik.ts b/src/devices/bituo_technik.ts index c4c092cd84dff..fbcf34212cc86 100644 --- a/src/devices/bituo_technik.ts +++ b/src/devices/bituo_technik.ts @@ -1,10 +1,9 @@ +import fz from '../converters/fromZigbee'; import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; -import fz from '../converters/fromZigbee'; import {Definition} from '../lib/types'; const e = exposes.presets; - const definitions: Definition[] = [ { zigbeeModel: ['SPM01X001'], @@ -13,8 +12,17 @@ const definitions: Definition[] = [ description: 'Smart energy sensor', fromZigbee: [fz.electrical_measurement, fz.metering], toZigbee: [], - exposes: [e.ac_frequency(), e.power(), e.power_reactive(), e.power_apparent(), e.current(), - e.voltage(), e.power_factor(), e.energy(), e.produced_energy()], + exposes: [ + e.ac_frequency(), + e.power(), + e.power_reactive(), + e.power_apparent(), + e.current(), + e.voltage(), + e.power_factor(), + e.energy(), + e.produced_energy(), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['haElectricalMeasurement', 'seMetering']); @@ -23,7 +31,9 @@ const definitions: Definition[] = [ await reporting.currentSummDelivered(endpoint, {change: 0}); await reporting.currentSummReceived(endpoint, {change: 0}); await endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', { - acPowerMultiplier: 1, acPowerDivisor: 1}); + acPowerMultiplier: 1, + acPowerDivisor: 1, + }); }, }, { @@ -33,13 +43,28 @@ const definitions: Definition[] = [ description: 'Smart energy sensor', fromZigbee: [fz.electrical_measurement, fz.metering], toZigbee: [], - exposes: [e.ac_frequency(), e.energy(), e.produced_energy(), - e.power(), e.power_phase_b(), e.power_phase_c(), - e.power_reactive(), e.power_reactive_phase_b(), e.power_reactive_phase_c(), - e.power_apparent(), e.power_apparent_phase_b(), e.power_apparent_phase_c(), - e.current(), e.current_phase_b(), e.current_phase_c(), - e.voltage(), e.voltage_phase_b(), e.voltage_phase_c(), - e.power_factor(), e.power_factor_phase_b(), e.power_factor_phase_c(), + exposes: [ + e.ac_frequency(), + e.energy(), + e.produced_energy(), + e.power(), + e.power_phase_b(), + e.power_phase_c(), + e.power_reactive(), + e.power_reactive_phase_b(), + e.power_reactive_phase_c(), + e.power_apparent(), + e.power_apparent_phase_b(), + e.power_apparent_phase_c(), + e.current(), + e.current_phase_b(), + e.current_phase_c(), + e.voltage(), + e.voltage_phase_b(), + e.voltage_phase_c(), + e.power_factor(), + e.power_factor_phase_b(), + e.power_factor_phase_c(), ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -49,7 +74,9 @@ const definitions: Definition[] = [ await reporting.currentSummDelivered(endpoint, {change: 0}); await reporting.currentSummReceived(endpoint, {change: 0}); await endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', { - acPowerMultiplier: 1, acPowerDivisor: 1}); + acPowerMultiplier: 1, + acPowerDivisor: 1, + }); }, }, ]; diff --git a/src/devices/blaupunkt.ts b/src/devices/blaupunkt.ts index d92c8b7dd3bf7..0d051e8ceab15 100644 --- a/src/devices/blaupunkt.ts +++ b/src/devices/blaupunkt.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; diff --git a/src/devices/blitzwolf.ts b/src/devices/blitzwolf.ts index d45eed9dbb799..a9fbde1c70da0 100644 --- a/src/devices/blitzwolf.ts +++ b/src/devices/blitzwolf.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; +import tz from '../converters/toZigbee'; import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; -import tz from '../converters/toZigbee'; import {deviceEndpoints, onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ @@ -16,7 +16,6 @@ const definitions: Definition[] = [ exposes: [e.occupancy()], }, { - fingerprint: [{modelID: 'TS0003', manufacturerName: '_TYZB01_aneiicmq'}], model: 'BW-SS7_1gang', vendor: 'BlitzWolf', diff --git a/src/devices/bosch.ts b/src/devices/bosch.ts index 0e17f8cd7b161..f04ee1c11f043 100644 --- a/src/devices/bosch.ts +++ b/src/devices/bosch.ts @@ -1,21 +1,30 @@ -import { - identify, light, onOff, quirkCheckinInterval, - deviceAddCustomCluster, binary, numeric, enumLookup, - battery, humidity, iasZoneAlarm, bindCluster, - ota, deviceEndpoints, -} from '../lib/modernExtend'; import {Zcl, ZSpec} from 'zigbee-herdsman'; -import * as exposes from '../lib/exposes'; + import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; -import * as utils from '../lib/utils'; import * as constants from '../lib/constants'; -import * as globalStore from '../lib/store'; -import { - Tz, Fz, Definition, KeyValue, ModernExtend, Expose, -} from '../lib/types'; +import * as exposes from '../lib/exposes'; import {logger} from '../lib/logger'; +import { + identify, + light, + onOff, + quirkCheckinInterval, + deviceAddCustomCluster, + binary, + numeric, + enumLookup, + battery, + humidity, + iasZoneAlarm, + bindCluster, + ota, + deviceEndpoints, +} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import * as globalStore from '../lib/store'; +import {Tz, Fz, Definition, KeyValue, ModernExtend, Expose} from '../lib/types'; +import * as utils from '../lib/utils'; const e = exposes.presets; const ea = exposes.access; @@ -23,26 +32,26 @@ const NS = 'zhc:bosch'; const manufacturerOptions = {manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH}; const sirenVolume = { - 'low': 0x01, - 'medium': 0x02, - 'high': 0x03, + low: 0x01, + medium: 0x02, + high: 0x03, }; const sirenLight = { - 'only_light': 0x00, - 'only_siren': 0x01, - 'siren_and_light': 0x02, + only_light: 0x00, + only_siren: 0x01, + siren_and_light: 0x02, }; const outdoorSirenState = { - 'ON': 0x07, - 'OFF': 0x00, + ON: 0x07, + OFF: 0x00, }; const sirenPowerSupply = { - 'solar_panel': 0x01, - 'ac_power_supply': 0x02, - 'dc_power_supply': 0x03, + solar_panel: 0x01, + ac_power_supply: 0x02, + dc_power_supply: 0x03, }; // Universal Switch II @@ -82,9 +91,8 @@ const labelConfirmation = `Specifies LED color (rgb) and pattern of the confirma Example: 30ff00000102010001`; const boschExtend = { - hvacThermostatCluster: () => deviceAddCustomCluster( - 'hvacThermostat', - { + hvacThermostatCluster: () => + deviceAddCustomCluster('hvacThermostat', { ID: Zcl.Clusters.hvacThermostat.ID, attributes: { operatingMode: { @@ -125,11 +133,9 @@ const boschExtend = { }, }, commandsResponse: {}, - }, - ), - hvacUserInterfaceCfgCluster: () => deviceAddCustomCluster( - 'hvacUserInterfaceCfg', - { + }), + hvacUserInterfaceCfgCluster: () => + deviceAddCustomCluster('hvacUserInterfaceCfg', { ID: Zcl.Clusters.hvacUserInterfaceCfg.ID, attributes: { displayOrientation: { @@ -155,112 +161,124 @@ const boschExtend = { }, commands: {}, commandsResponse: {}, - }, - ), - operatingMode: () => enumLookup({ - name: 'operating_mode', - cluster: 'hvacThermostat', - attribute: 'operatingMode', - reporting: {min: '10_SECONDS', max: 'MAX', change: null}, - description: 'Bosch-specific operating mode (overrides system mode)', - lookup: {'schedule': 0x00, 'manual': 0x01, 'pause': 0x05}, - zigbeeCommandOptions: manufacturerOptions, - }), - windowDetection: () => binary({ - name: 'window_detection', - cluster: 'hvacThermostat', - attribute: 'windowDetection', - description: 'Enable/disable window open (Lo.) mode', - valueOn: ['ON', 0x01], - valueOff: ['OFF', 0x00], - zigbeeCommandOptions: manufacturerOptions, - }), - boostHeating: () => binary({ - name: 'boost_heating', - cluster: 'hvacThermostat', - attribute: 'boostHeating', - reporting: {min: '10_SECONDS', max: 'MAX', change: null, attribute: 'boostHeating'}, - description: 'Activate boost heating (5 min. on TRV)', - valueOn: ['ON', 0x01], - valueOff: ['OFF', 0x00], - zigbeeCommandOptions: manufacturerOptions, - }), - childLock: () => binary({ - name: 'child_lock', - cluster: 'hvacUserInterfaceCfg', - attribute: 'keypadLockout', - description: 'Enables/disables physical input on the device', - valueOn: ['LOCK', 0x01], - valueOff: ['UNLOCK', 0x00], - }), - displayOntime: () => numeric({ - name: 'display_ontime', - cluster: 'hvacUserInterfaceCfg', - attribute: 'displayOntime', - description: 'Sets the display on-time', - valueMin: 5, - valueMax: 30, - unit: 's', - zigbeeCommandOptions: manufacturerOptions, - }), - displayBrightness: () => numeric({ - name: 'display_brightness', - cluster: 'hvacUserInterfaceCfg', - attribute: 'displayBrightness', - description: 'Sets brightness of the display', - valueMin: 0, - valueMax: 10, - zigbeeCommandOptions: manufacturerOptions, - }), + }), + operatingMode: () => + enumLookup({ + name: 'operating_mode', + cluster: 'hvacThermostat', + attribute: 'operatingMode', + reporting: {min: '10_SECONDS', max: 'MAX', change: null}, + description: 'Bosch-specific operating mode (overrides system mode)', + lookup: {schedule: 0x00, manual: 0x01, pause: 0x05}, + zigbeeCommandOptions: manufacturerOptions, + }), + windowDetection: () => + binary({ + name: 'window_detection', + cluster: 'hvacThermostat', + attribute: 'windowDetection', + description: 'Enable/disable window open (Lo.) mode', + valueOn: ['ON', 0x01], + valueOff: ['OFF', 0x00], + zigbeeCommandOptions: manufacturerOptions, + }), + boostHeating: () => + binary({ + name: 'boost_heating', + cluster: 'hvacThermostat', + attribute: 'boostHeating', + reporting: {min: '10_SECONDS', max: 'MAX', change: null, attribute: 'boostHeating'}, + description: 'Activate boost heating (5 min. on TRV)', + valueOn: ['ON', 0x01], + valueOff: ['OFF', 0x00], + zigbeeCommandOptions: manufacturerOptions, + }), + childLock: () => + binary({ + name: 'child_lock', + cluster: 'hvacUserInterfaceCfg', + attribute: 'keypadLockout', + description: 'Enables/disables physical input on the device', + valueOn: ['LOCK', 0x01], + valueOff: ['UNLOCK', 0x00], + }), + displayOntime: () => + numeric({ + name: 'display_ontime', + cluster: 'hvacUserInterfaceCfg', + attribute: 'displayOntime', + description: 'Sets the display on-time', + valueMin: 5, + valueMax: 30, + unit: 's', + zigbeeCommandOptions: manufacturerOptions, + }), + displayBrightness: () => + numeric({ + name: 'display_brightness', + cluster: 'hvacUserInterfaceCfg', + attribute: 'displayBrightness', + description: 'Sets brightness of the display', + valueMin: 0, + valueMax: 10, + zigbeeCommandOptions: manufacturerOptions, + }), valveAdaptProcess: (): ModernExtend => { const adaptationStatus: KeyValue = { - 'none': 0x00, - 'ready_to_calibrate': 0x01, - 'calibration_in_progress': 0x02, - 'error': 0x03, - 'success': 0x04, + none: 0x00, + ready_to_calibrate: 0x01, + calibration_in_progress: 0x02, + error: 0x03, + success: 0x04, }; const exposes: Expose[] = [ - e.binary('valve_adapt_process', ea.ALL, true, false) + e + .binary('valve_adapt_process', ea.ALL, true, false) .withLabel('Trigger adaptation process') - .withDescription('Trigger the valve adaptation process. Only possible when adaptation status ' + - 'is "ready_to_calibrate" or "error".').withCategory('config'), + .withDescription( + 'Trigger the valve adaptation process. Only possible when adaptation status ' + 'is "ready_to_calibrate" or "error".', + ) + .withCategory('config'), ]; - const fromZigbee: Fz.Converter[] = [{ - cluster: 'hvacThermostat', - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - const result: KeyValue = {}; - if (msg.data.hasOwnProperty('valveAdaptStatus')) { - if (msg.data['valveAdaptStatus'] === adaptationStatus.calibration_in_progress) { - result.valve_adapt_process = true; - } else { - result.valve_adapt_process = false; + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'hvacThermostat', + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + const result: KeyValue = {}; + if (msg.data.hasOwnProperty('valveAdaptStatus')) { + if (msg.data['valveAdaptStatus'] === adaptationStatus.calibration_in_progress) { + result.valve_adapt_process = true; + } else { + result.valve_adapt_process = false; + } } - } - return result; + return result; + }, }, - }]; - const toZigbee: Tz.Converter[] = [{ - key: ['valve_adapt_process'], - convertSet: async (entity, key, value, meta) => { - if (value == true) { - const adaptStatus = utils.getFromLookup(meta.state.valve_adapt_status, adaptationStatus); - switch (adaptStatus) { - case adaptationStatus.ready_to_calibrate: - case adaptationStatus.error: - await entity.command('hvacThermostat', 'calibrateValve', {}, manufacturerOptions); - break; - default: - throw new Error('Valve adaptation process not possible right now.'); + ]; + const toZigbee: Tz.Converter[] = [ + { + key: ['valve_adapt_process'], + convertSet: async (entity, key, value, meta) => { + if (value == true) { + const adaptStatus = utils.getFromLookup(meta.state.valve_adapt_status, adaptationStatus); + switch (adaptStatus) { + case adaptationStatus.ready_to_calibrate: + case adaptationStatus.error: + await entity.command('hvacThermostat', 'calibrateValve', {}, manufacturerOptions); + break; + default: + throw new Error('Valve adaptation process not possible right now.'); + } } - } - return {state: {valve_adapt_process: value}}; - }, - convertGet: async (entity, key, meta) => { - await entity.read('hvacThermostat', ['valveAdaptStatus'], manufacturerOptions); + return {state: {valve_adapt_process: value}}; + }, + convertGet: async (entity, key, meta) => { + await entity.read('hvacThermostat', ['valveAdaptStatus'], manufacturerOptions); + }, }, - }]; + ]; return { exposes, fromZigbee, @@ -269,38 +287,43 @@ const boschExtend = { }; }, heatingDemand: (): ModernExtend => { - const fromZigbee: Fz.Converter[] = [{ - cluster: 'hvacThermostat', - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - const result: KeyValue = {}; - if (msg.data.hasOwnProperty('heatingDemand')) { - const demand = msg.data['heatingDemand'] as number; - result.pi_heating_demand = demand; - result.running_state = demand > 0 ? 'heat' : 'idle'; - } - return result; - }, - }]; - const toZigbee: Tz.Converter[] = [{ - key: ['pi_heating_demand'], - convertSet: async (entity, key, value, meta) => { - if (key === 'pi_heating_demand') { - let demand = utils.toNumber(value, key); - demand = utils.numberWithinRange(demand, 0, 100); - await entity.write('hvacThermostat', {heatingDemand: demand}, manufacturerOptions); - return {state: {pi_heating_demand: demand}}; - } + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'hvacThermostat', + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + const result: KeyValue = {}; + if (msg.data.hasOwnProperty('heatingDemand')) { + const demand = msg.data['heatingDemand'] as number; + result.pi_heating_demand = demand; + result.running_state = demand > 0 ? 'heat' : 'idle'; + } + return result; + }, }, - convertGet: async (entity, key, meta) => { - await entity.read('hvacThermostat', ['heatingDemand'], manufacturerOptions); + ]; + const toZigbee: Tz.Converter[] = [ + { + key: ['pi_heating_demand'], + convertSet: async (entity, key, value, meta) => { + if (key === 'pi_heating_demand') { + let demand = utils.toNumber(value, key); + demand = utils.numberWithinRange(demand, 0, 100); + await entity.write('hvacThermostat', {heatingDemand: demand}, manufacturerOptions); + return {state: {pi_heating_demand: demand}}; + } + }, + convertGet: async (entity, key, meta) => { + await entity.read('hvacThermostat', ['heatingDemand'], manufacturerOptions); + }, }, - }, { - key: ['running_state'], - convertGet: async (entity, key, meta) => { - await entity.read('hvacThermostat', ['heatingDemand'], manufacturerOptions); + { + key: ['running_state'], + convertGet: async (entity, key, meta) => { + await entity.read('hvacThermostat', ['heatingDemand'], manufacturerOptions); + }, }, - }]; + ]; return { fromZigbee, toZigbee, @@ -308,20 +331,22 @@ const boschExtend = { }; }, ignoreDst: (): ModernExtend => { - const fromZigbee: Fz.Converter[] = [{ - cluster: 'genTime', - type: 'read', - convert: async (model, msg, publish, options, meta) => { - if (msg.data.includes('dstStart', 'dstEnd', 'dstShift')) { - const response = { - 'dstStart': {attribute: 0x0003, status: Zcl.Status.SUCCESS, value: 0x00}, - 'dstEnd': {attribute: 0x0004, status: Zcl.Status.SUCCESS, value: 0x00}, - 'dstShift': {attribute: 0x0005, status: Zcl.Status.SUCCESS, value: 0x00}, - }; - await msg.endpoint.readResponse(msg.cluster, msg.meta.zclTransactionSequenceNumber, response); - } + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'genTime', + type: 'read', + convert: async (model, msg, publish, options, meta) => { + if (msg.data.includes('dstStart', 'dstEnd', 'dstShift')) { + const response = { + dstStart: {attribute: 0x0003, status: Zcl.Status.SUCCESS, value: 0x00}, + dstEnd: {attribute: 0x0004, status: Zcl.Status.SUCCESS, value: 0x00}, + dstShift: {attribute: 0x0005, status: Zcl.Status.SUCCESS, value: 0x00}, + }; + await msg.endpoint.readResponse(msg.cluster, msg.meta.zclTransactionSequenceNumber, response); + } + }, }, - }]; + ]; return { fromZigbee, isModernExtend: true, @@ -329,40 +354,42 @@ const boschExtend = { }, doorWindowContact: (hasVibrationSensor?: boolean): ModernExtend => { const exposes: Expose[] = [ - e.binary('contact', ea.STATE, false, true) - .withDescription('Indicates whether the device is opened or closed'), - e.enum('action', ea.STATE, ['none', 'single', 'long']) - .withDescription('Triggered action (e.g. a button click)').withCategory('diagnostic'), + e.binary('contact', ea.STATE, false, true).withDescription('Indicates whether the device is opened or closed'), + e + .enum('action', ea.STATE, ['none', 'single', 'long']) + .withDescription('Triggered action (e.g. a button click)') + .withCategory('diagnostic'), ]; if (hasVibrationSensor) { - exposes.push(e.binary('vibration', ea.STATE, true, false) - .withDescription('Indicates whether the device detected vibration')); + exposes.push(e.binary('vibration', ea.STATE, true, false).withDescription('Indicates whether the device detected vibration')); } - const fromZigbee: Fz.Converter[] = [{ - cluster: 'ssIasZone', - type: ['commandStatusChangeNotification', 'attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - if (msg.data.hasOwnProperty('zoneStatus') || msg.data.hasOwnProperty('zonestatus')) { - const zoneStatus = msg.type === 'commandStatusChangeNotification' ? msg.data.zonestatus : msg.data.zoneStatus; - const lookup: KeyValue = {0x00: 'none', 0x01: 'single', 0x02: 'long'}; - const result: KeyValue = { - contact: !((zoneStatus & 1) > 0), - vibration: (zoneStatus & 1<<1) > 0, - tamper: (zoneStatus & 1 << 2) > 0, - battery_low: (zoneStatus & 1 << 3) > 0, - supervision_reports: (zoneStatus & 1 << 4) > 0, - restore_reports: (zoneStatus & 1 << 5) > 0, - trouble: (zoneStatus & 1 << 6) > 0, - ac_status: (zoneStatus & 1 << 7) > 0, - test: (zoneStatus & 1 << 8) > 0, - battery_defect: (zoneStatus & 1 << 9) > 0, - action: lookup[(zoneStatus >> 11) & 3], - }; - if (result.action === 'none') delete result.action; - return result; - } + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'ssIasZone', + type: ['commandStatusChangeNotification', 'attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + if (msg.data.hasOwnProperty('zoneStatus') || msg.data.hasOwnProperty('zonestatus')) { + const zoneStatus = msg.type === 'commandStatusChangeNotification' ? msg.data.zonestatus : msg.data.zoneStatus; + const lookup: KeyValue = {0x00: 'none', 0x01: 'single', 0x02: 'long'}; + const result: KeyValue = { + contact: !((zoneStatus & 1) > 0), + vibration: (zoneStatus & (1 << 1)) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, + supervision_reports: (zoneStatus & (1 << 4)) > 0, + restore_reports: (zoneStatus & (1 << 5)) > 0, + trouble: (zoneStatus & (1 << 6)) > 0, + ac_status: (zoneStatus & (1 << 7)) > 0, + test: (zoneStatus & (1 << 8)) > 0, + battery_defect: (zoneStatus & (1 << 9)) > 0, + action: lookup[(zoneStatus >> 11) & 3], + }; + if (result.action === 'none') delete result.action; + return result; + } + }, }, - }]; + ]; return { exposes, fromZigbee, @@ -371,73 +398,79 @@ const boschExtend = { }, smokeAlarm: (): ModernExtend => { const smokeAlarm: KeyValue = { - 'OFF': 0x0000, - 'ON': 0x3c00, // 15360 or 46080 works + OFF: 0x0000, + ON: 0x3c00, // 15360 or 46080 works }; const burglarAlarm: KeyValue = { - 'OFF': 0x0001, - 'ON': 0xb401, // 46081 + OFF: 0x0001, + ON: 0xb401, // 46081 }; const exposes: Expose[] = [ e.binary('smoke', ea.STATE, true, false).withDescription('Indicates whether the device detected smoke'), - e.binary('test', ea.STATE, true, false).withDescription('Indicates whether the device is currently performing a test') + e + .binary('test', ea.STATE, true, false) + .withDescription('Indicates whether the device is currently performing a test') .withCategory('diagnostic'), e.binary('alarm_smoke', ea.ALL, true, false).withDescription('Toggle the smoke alarm siren').withCategory('config'), e.binary('alarm_burglar', ea.ALL, true, false).withDescription('Toggle the burglar alarm siren').withCategory('config'), ]; - const fromZigbee: Fz.Converter[] = [{ - cluster: 'ssIasZone', - type: ['commandStatusChangeNotification', 'attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - if (msg.data.hasOwnProperty('zoneStatus') || msg.data.hasOwnProperty('zonestatus')) { - const zoneStatus = msg.type === 'commandStatusChangeNotification' ? msg.data.zonestatus : msg.data.zoneStatus; - return { - smoke: (zoneStatus & 1) > 0, - alarm_smoke: (zoneStatus & 1<<1) > 0, - battery_low: (zoneStatus & 1<<3) > 0, - supervision_reports: (zoneStatus & 1<<4) > 0, - restore_reports: (zoneStatus & 1<<5) > 0, - alarm_burglar: (zoneStatus & 1<<7) > 0, - test: (zoneStatus & 1<<8) > 0, - alarm_silenced: (zoneStatus & 1<<11) > 0, - }; - } + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'ssIasZone', + type: ['commandStatusChangeNotification', 'attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + if (msg.data.hasOwnProperty('zoneStatus') || msg.data.hasOwnProperty('zonestatus')) { + const zoneStatus = msg.type === 'commandStatusChangeNotification' ? msg.data.zonestatus : msg.data.zoneStatus; + return { + smoke: (zoneStatus & 1) > 0, + alarm_smoke: (zoneStatus & (1 << 1)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, + supervision_reports: (zoneStatus & (1 << 4)) > 0, + restore_reports: (zoneStatus & (1 << 5)) > 0, + alarm_burglar: (zoneStatus & (1 << 7)) > 0, + test: (zoneStatus & (1 << 8)) > 0, + alarm_silenced: (zoneStatus & (1 << 11)) > 0, + }; + } + }, }, - }]; - const toZigbee: Tz.Converter[] = [{ - key: ['alarm_smoke', 'alarm_burglar'], - convertSet: async (entity, key, value, meta) => { - if (key === 'alarm_smoke') { - let transformedValue = 'OFF'; - if (value === true) { - transformedValue = 'ON'; + ]; + const toZigbee: Tz.Converter[] = [ + { + key: ['alarm_smoke', 'alarm_burglar'], + convertSet: async (entity, key, value, meta) => { + if (key === 'alarm_smoke') { + let transformedValue = 'OFF'; + if (value === true) { + transformedValue = 'ON'; + } + const index = utils.getFromLookup(transformedValue, smokeAlarm); + await entity.command('ssIasZone', 'boschSmokeAlarmSiren', {data: index}, manufacturerOptions); + return {state: {alarm_smoke: value}}; } - const index = utils.getFromLookup(transformedValue, smokeAlarm); - await entity.command('ssIasZone', 'boschSmokeAlarmSiren', {data: index}, manufacturerOptions); - return {state: {alarm_smoke: value}}; - } - if (key === 'alarm_burglar') { - let transformedValue = 'OFF'; - if (value === true) { - transformedValue = 'ON'; + if (key === 'alarm_burglar') { + let transformedValue = 'OFF'; + if (value === true) { + transformedValue = 'ON'; + } + const index = utils.getFromLookup(transformedValue, burglarAlarm); + await entity.command('ssIasZone', 'boschSmokeAlarmSiren', {data: index}, manufacturerOptions); + return {state: {alarm_burglar: value}}; } - const index = utils.getFromLookup(transformedValue, burglarAlarm); - await entity.command('ssIasZone', 'boschSmokeAlarmSiren', {data: index}, manufacturerOptions); - return {state: {alarm_burglar: value}}; - } - }, - convertGet: async (entity, key, meta) => { - switch (key) { - case 'alarm_smoke': - case 'alarm_burglar': - case 'zone_status': - await entity.read('ssIasZone', ['zoneStatus']); - break; - default: - throw new Error(`Unhandled key boschExtend.smokeAlarm.toZigbee.convertGet ${key}`); - } + }, + convertGet: async (entity, key, meta) => { + switch (key) { + case 'alarm_smoke': + case 'alarm_burglar': + case 'zone_status': + await entity.read('ssIasZone', ['zoneStatus']); + break; + default: + throw new Error(`Unhandled key boschExtend.smokeAlarm.toZigbee.convertGet ${key}`); + } + }, }, - }]; + ]; return { exposes, fromZigbee, @@ -447,30 +480,37 @@ const boschExtend = { }, broadcastAlarm: (): ModernExtend => { const sirenState: KeyValue = { - 'smoke_off': 0x0000, - 'smoke_on': 0x3c00, - 'burglar_off': 0x0001, - 'burglar_on': 0xb401, + smoke_off: 0x0000, + smoke_on: 0x3c00, + burglar_off: 0x0001, + burglar_on: 0xb401, }; const exposes: Expose[] = [ - e.enum('broadcast_alarm', ea.SET, Object.keys(sirenState)) - .withDescription('Set siren state of all BSD-2 via broadcast').withCategory('config'), + e + .enum('broadcast_alarm', ea.SET, Object.keys(sirenState)) + .withDescription('Set siren state of all BSD-2 via broadcast') + .withCategory('config'), ]; - const toZigbee: Tz.Converter[] = [{ - key: ['broadcast_alarm'], - convertSet: async (entity, key, value, meta) => { - if (key === 'broadcast_alarm') { - const index = utils.getFromLookup(value, sirenState); - utils.assertEndpoint(entity); - await entity.zclCommandBroadcast( - 255, ZSpec.BroadcastAddress.SLEEPY, - Zcl.Clusters.ssIasZone.ID, 'boschSmokeAlarmSiren', - {data: index}, manufacturerOptions, - ); - return; - } + const toZigbee: Tz.Converter[] = [ + { + key: ['broadcast_alarm'], + convertSet: async (entity, key, value, meta) => { + if (key === 'broadcast_alarm') { + const index = utils.getFromLookup(value, sirenState); + utils.assertEndpoint(entity); + await entity.zclCommandBroadcast( + 255, + ZSpec.BroadcastAddress.SLEEPY, + Zcl.Clusters.ssIasZone.ID, + 'boschSmokeAlarmSiren', + {data: index}, + manufacturerOptions, + ); + return; + } + }, }, - }]; + ]; return { exposes, toZigbee, @@ -479,226 +519,254 @@ const boschExtend = { }, twinguard: (): ModernExtend => { const smokeSensitivity = { - 'low': 0x03, - 'medium': 0x02, - 'high': 0x01, + low: 0x03, + medium: 0x02, + high: 0x01, }; const sirenState = { - 'stop': 0x00, - 'pre_alarm': 0x01, - 'fire': 0x02, - 'burglar': 0x03, + stop: 0x00, + pre_alarm: 0x01, + fire: 0x02, + burglar: 0x03, }; const stateOffOn = { - 'OFF': 0x00, - 'ON': 0x01, + OFF: 0x00, + ON: 0x01, }; const exposes: Expose[] = [ e.binary('smoke', ea.STATE, true, false).withDescription('Indicates whether the device detected smoke'), - e.numeric('temperature', ea.STATE).withValueMin(0).withValueMax(65).withValueStep(0.1) - .withUnit('°C').withDescription('Measured temperature value'), - e.numeric('humidity', ea.STATE).withValueMin(0).withValueMax(100).withValueStep(0.1) - .withUnit('%').withDescription('Measured relative humidity'), - e.numeric('voc', ea.STATE).withValueMin(0).withValueMax(50000).withValueStep(1) - .withLabel('VOC').withUnit('µg/m³').withDescription('Measured VOC value'), - e.numeric('co2', ea.STATE).withValueMin(400).withValueMax(2400).withValueStep(1) - .withLabel('CO2').withUnit('ppm').withDescription('The measured CO2 (carbon dioxide) value'), - e.numeric('aqi', ea.STATE).withValueMin(0).withValueMax(500).withValueStep(1) - .withLabel('AQI').withDescription('Air Quality Index'), - e.numeric('illuminance_lux', ea.STATE).withUnit('lx') - .withDescription('Measured illuminance in lux'), - e.numeric('battery', ea.STATE).withUnit('%').withValueMin(0).withValueMax(100) - .withDescription('Remaining battery in %').withCategory('diagnostic'), - e.text('siren_state', ea.STATE) - .withDescription('Siren state').withCategory('diagnostic'), + e + .numeric('temperature', ea.STATE) + .withValueMin(0) + .withValueMax(65) + .withValueStep(0.1) + .withUnit('°C') + .withDescription('Measured temperature value'), + e + .numeric('humidity', ea.STATE) + .withValueMin(0) + .withValueMax(100) + .withValueStep(0.1) + .withUnit('%') + .withDescription('Measured relative humidity'), + e + .numeric('voc', ea.STATE) + .withValueMin(0) + .withValueMax(50000) + .withValueStep(1) + .withLabel('VOC') + .withUnit('µg/m³') + .withDescription('Measured VOC value'), + e + .numeric('co2', ea.STATE) + .withValueMin(400) + .withValueMax(2400) + .withValueStep(1) + .withLabel('CO2') + .withUnit('ppm') + .withDescription('The measured CO2 (carbon dioxide) value'), + e.numeric('aqi', ea.STATE).withValueMin(0).withValueMax(500).withValueStep(1).withLabel('AQI').withDescription('Air Quality Index'), + e.numeric('illuminance_lux', ea.STATE).withUnit('lx').withDescription('Measured illuminance in lux'), + e + .numeric('battery', ea.STATE) + .withUnit('%') + .withValueMin(0) + .withValueMax(100) + .withDescription('Remaining battery in %') + .withCategory('diagnostic'), + e.text('siren_state', ea.STATE).withDescription('Siren state').withCategory('diagnostic'), e.enum('alarm', ea.ALL, Object.keys(sirenState)).withDescription('Alarm mode for siren'), - e.binary('self_test', ea.ALL, true, false) - .withDescription('Initiate self-test').withCategory('config'), - e.enum('sensitivity', ea.ALL, Object.keys(smokeSensitivity)) - .withDescription('Sensitivity of the smoke detector').withCategory('config'), - e.binary('pre_alarm', ea.ALL, 'ON', 'OFF') - .withDescription('Enable/disable pre-alarm').withCategory('config'), - e.binary('heartbeat', ea.ALL, 'ON', 'OFF') - .withDescription('Enable/disable heartbeat (blue LED)').withCategory('config'), + e.binary('self_test', ea.ALL, true, false).withDescription('Initiate self-test').withCategory('config'), + e.enum('sensitivity', ea.ALL, Object.keys(smokeSensitivity)).withDescription('Sensitivity of the smoke detector').withCategory('config'), + e.binary('pre_alarm', ea.ALL, 'ON', 'OFF').withDescription('Enable/disable pre-alarm').withCategory('config'), + e.binary('heartbeat', ea.ALL, 'ON', 'OFF').withDescription('Enable/disable heartbeat (blue LED)').withCategory('config'), ]; - const fromZigbee: Fz.Converter[] = [{ - cluster: 'twinguardSmokeDetector', - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - const result: KeyValue = {}; - if (msg.data.hasOwnProperty('sensitivity')) { - result.sensitivity = (Object.keys(smokeSensitivity)[msg.data['sensitivity']]); - } - return result; + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'twinguardSmokeDetector', + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + const result: KeyValue = {}; + if (msg.data.hasOwnProperty('sensitivity')) { + result.sensitivity = Object.keys(smokeSensitivity)[msg.data['sensitivity']]; + } + return result; + }, }, - }, { - cluster: 'twinguardMeasurements', - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - const result: KeyValue = {}; - if (msg.data.hasOwnProperty('humidity')) { - const humidity = utils.toNumber(msg.data['humidity']) / 100.0; - if (utils.isInRange(0, 100, humidity)) { - result.humidity = humidity; + { + cluster: 'twinguardMeasurements', + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + const result: KeyValue = {}; + if (msg.data.hasOwnProperty('humidity')) { + const humidity = utils.toNumber(msg.data['humidity']) / 100.0; + if (utils.isInRange(0, 100, humidity)) { + result.humidity = humidity; + } } - } - if (msg.data.hasOwnProperty('airpurity')) { - const iaq = utils.toNumber(msg.data['airpurity']); - result.aqi = iaq; - let factorVoc = 6; - let factorCo2 = 2; - if ((iaq >= 51) && (iaq <= 100)) { - factorVoc = 10; - factorCo2 = 4; - } else if ((iaq >= 101) && (iaq <= 150)) { - factorVoc = 20; - factorCo2 = 4; - } else if ((iaq >= 151) && (iaq <= 200)) { - factorVoc = 50; - factorCo2 = 4; - } else if ((iaq >= 201) && (iaq <= 250)) { - factorVoc = 100; - factorCo2 = 4; - } else if (iaq >= 251) { - factorVoc = 100; - factorCo2 = 4; + if (msg.data.hasOwnProperty('airpurity')) { + const iaq = utils.toNumber(msg.data['airpurity']); + result.aqi = iaq; + let factorVoc = 6; + let factorCo2 = 2; + if (iaq >= 51 && iaq <= 100) { + factorVoc = 10; + factorCo2 = 4; + } else if (iaq >= 101 && iaq <= 150) { + factorVoc = 20; + factorCo2 = 4; + } else if (iaq >= 151 && iaq <= 200) { + factorVoc = 50; + factorCo2 = 4; + } else if (iaq >= 201 && iaq <= 250) { + factorVoc = 100; + factorCo2 = 4; + } else if (iaq >= 251) { + factorVoc = 100; + factorCo2 = 4; + } + result.voc = iaq * factorVoc; + result.co2 = iaq * factorCo2 + 400; } - result.voc = (iaq * factorVoc); - result.co2 = ((iaq * factorCo2) + 400); - } - if (msg.data.hasOwnProperty('temperature')) { - result.temperature = utils.toNumber(msg.data['temperature']) / 100.0; - } - if (msg.data.hasOwnProperty('illuminance_lux')) { - result.illuminance_lux = utils.precisionRound((msg.data['illuminance_lux'] / 2), 2); - } - if (msg.data.hasOwnProperty('battery')) { - result.battery = utils.precisionRound((msg.data['battery'] / 2), 2); - } - return result; + if (msg.data.hasOwnProperty('temperature')) { + result.temperature = utils.toNumber(msg.data['temperature']) / 100.0; + } + if (msg.data.hasOwnProperty('illuminance_lux')) { + result.illuminance_lux = utils.precisionRound(msg.data['illuminance_lux'] / 2, 2); + } + if (msg.data.hasOwnProperty('battery')) { + result.battery = utils.precisionRound(msg.data['battery'] / 2, 2); + } + return result; + }, }, - }, { - cluster: 'twinguardOptions', - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - const result: KeyValue = {}; - if (msg.data.hasOwnProperty('pre_alarm')) { - result.pre_alarm = (Object.keys(stateOffOn)[msg.data['pre_alarm']]); - } - return result; + { + cluster: 'twinguardOptions', + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + const result: KeyValue = {}; + if (msg.data.hasOwnProperty('pre_alarm')) { + result.pre_alarm = Object.keys(stateOffOn)[msg.data['pre_alarm']]; + } + return result; + }, }, - }, { - cluster: 'twinguardSetup', - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - const result: KeyValue = {}; - if (msg.data.hasOwnProperty('heartbeat')) { - result.heartbeat = (Object.keys(stateOffOn)[msg.data['heartbeat']]); - } - return result; + { + cluster: 'twinguardSetup', + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + const result: KeyValue = {}; + if (msg.data.hasOwnProperty('heartbeat')) { + result.heartbeat = Object.keys(stateOffOn)[msg.data['heartbeat']]; + } + return result; + }, }, - }, { - cluster: 'twinguardAlarm', - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - const result: KeyValue = {}; - const lookup: KeyValue = { - 0x00200020: 'clear', - 0x01200020: 'self_test', - 0x02200020: 'burglar', - 0x00200082: 'pre_alarm', - 0x00200081: 'fire', - 0x00200040: 'silenced', - }; - if (msg.data.hasOwnProperty('alarm_status')) { - result.self_test = (msg.data['alarm_status'] & 1<<24) > 0; - result.smoke = (msg.data['alarm_status'] & 1<<7) > 0; - result.siren_state = lookup[msg.data['alarm_status']]; - } - return result; + { + cluster: 'twinguardAlarm', + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + const result: KeyValue = {}; + const lookup: KeyValue = { + 0x00200020: 'clear', + 0x01200020: 'self_test', + 0x02200020: 'burglar', + 0x00200082: 'pre_alarm', + 0x00200081: 'fire', + 0x00200040: 'silenced', + }; + if (msg.data.hasOwnProperty('alarm_status')) { + result.self_test = (msg.data['alarm_status'] & (1 << 24)) > 0; + result.smoke = (msg.data['alarm_status'] & (1 << 7)) > 0; + result.siren_state = lookup[msg.data['alarm_status']]; + } + return result; + }, }, - }, { - cluster: 'genAlarms', - type: ['commandAlarm', 'readResponse'], - convert: async (model, msg, publish, options, meta) => { - const result: KeyValue = {}; - const lookup: KeyValue = { - 0x10: 'fire', - 0x11: 'pre_alarm', - 0x14: 'clear', - 0x16: 'silenced', - }; - result.siren_state = lookup[msg.data.alarmcode]; - if (msg.data.alarmcode == 0x10 || msg.data.alarmcode == 0x11) { - await msg.endpoint.commandResponse('genAlarms', 'alarm', - {alarmcode: msg.data.alarmcode, clusterid: 0xe000}, {direction: 1}); - } - return result; + { + cluster: 'genAlarms', + type: ['commandAlarm', 'readResponse'], + convert: async (model, msg, publish, options, meta) => { + const result: KeyValue = {}; + const lookup: KeyValue = { + 0x10: 'fire', + 0x11: 'pre_alarm', + 0x14: 'clear', + 0x16: 'silenced', + }; + result.siren_state = lookup[msg.data.alarmcode]; + if (msg.data.alarmcode == 0x10 || msg.data.alarmcode == 0x11) { + await msg.endpoint.commandResponse('genAlarms', 'alarm', {alarmcode: msg.data.alarmcode, clusterid: 0xe000}, {direction: 1}); + } + return result; + }, }, - }]; - const toZigbee: Tz.Converter[] = [{ - key: ['sensitivity', 'pre_alarm', 'self_test', 'alarm', 'heartbeat'], - convertSet: async (entity, key, value, meta) => { - if (key === 'sensitivity') { - const index = utils.getFromLookup(value, smokeSensitivity); - await entity.write('twinguardSmokeDetector', {sensitivity: index}, manufacturerOptions); - return {state: {sensitivity: value}}; - } - if (key === 'pre_alarm') { - const index = utils.getFromLookup(value, stateOffOn); - await entity.write('twinguardOptions', {pre_alarm: index}, manufacturerOptions); - return {state: {pre_alarm: value}}; - } - if (key === 'heartbeat') { - const endpoint = meta.device.getEndpoint(12); - const index = utils.getFromLookup(value, stateOffOn); - await endpoint.write('twinguardSetup', {heartbeat: index}, manufacturerOptions); - return {state: {heartbeat: value}}; - } - if (key === 'self_test') { - if (value) { - await entity.command('twinguardSmokeDetector', 'initiateTestMode', manufacturerOptions); + ]; + const toZigbee: Tz.Converter[] = [ + { + key: ['sensitivity', 'pre_alarm', 'self_test', 'alarm', 'heartbeat'], + convertSet: async (entity, key, value, meta) => { + if (key === 'sensitivity') { + const index = utils.getFromLookup(value, smokeSensitivity); + await entity.write('twinguardSmokeDetector', {sensitivity: index}, manufacturerOptions); + return {state: {sensitivity: value}}; } - } - if (key === 'alarm') { - const endpoint = meta.device.getEndpoint(12); - const index = utils.getFromLookup(value, sirenState); - utils.assertEndpoint(entity); - if (index == 0x00) { - await entity.commandResponse('genAlarms', 'alarm', {alarmcode: 0x16, clusterid: 0xe000}, {direction: 1}); - await entity.commandResponse('genAlarms', 'alarm', {alarmcode: 0x14, clusterid: 0xe000}, {direction: 1}); - await endpoint.command('twinguardAlarm', 'burglarAlarm', {data: 0x00}, manufacturerOptions); - } else if (index == 0x01) { - await entity.commandResponse('genAlarms', 'alarm', {alarmcode: 0x11, clusterid: 0xe000}, {direction: 1}); - return {state: {siren_state: 'pre_alarm'}}; - } else if (index == 0x02) { - await entity.commandResponse('genAlarms', 'alarm', {alarmcode: 0x10, clusterid: 0xe000}, {direction: 1}); - return {state: {siren_state: 'fire'}}; - } else if (index == 0x03) { - await endpoint.command('twinguardAlarm', 'burglarAlarm', {data: 0x01}, manufacturerOptions); + if (key === 'pre_alarm') { + const index = utils.getFromLookup(value, stateOffOn); + await entity.write('twinguardOptions', {pre_alarm: index}, manufacturerOptions); + return {state: {pre_alarm: value}}; } - } - }, - convertGet: async (entity, key, meta) => { - switch (key) { - case 'sensitivity': - await entity.read('twinguardSmokeDetector', ['sensitivity'], manufacturerOptions); - break; - case 'pre_alarm': - await entity.read('twinguardOptions', ['pre_alarm'], manufacturerOptions); - break; - case 'heartbeat': - await meta.device.getEndpoint(12).read('twinguardSetup', ['heartbeat'], manufacturerOptions); - break; - case 'alarm': - case 'self_test': - await meta.device.getEndpoint(12).read('twinguardAlarm', ['alarm_status'], manufacturerOptions); - break; - default: - throw new Error(`Unhandled key boschExtend.twinguard.toZigbee.convertGet ${key}`); - } + if (key === 'heartbeat') { + const endpoint = meta.device.getEndpoint(12); + const index = utils.getFromLookup(value, stateOffOn); + await endpoint.write('twinguardSetup', {heartbeat: index}, manufacturerOptions); + return {state: {heartbeat: value}}; + } + if (key === 'self_test') { + if (value) { + await entity.command('twinguardSmokeDetector', 'initiateTestMode', manufacturerOptions); + } + } + if (key === 'alarm') { + const endpoint = meta.device.getEndpoint(12); + const index = utils.getFromLookup(value, sirenState); + utils.assertEndpoint(entity); + if (index == 0x00) { + await entity.commandResponse('genAlarms', 'alarm', {alarmcode: 0x16, clusterid: 0xe000}, {direction: 1}); + await entity.commandResponse('genAlarms', 'alarm', {alarmcode: 0x14, clusterid: 0xe000}, {direction: 1}); + await endpoint.command('twinguardAlarm', 'burglarAlarm', {data: 0x00}, manufacturerOptions); + } else if (index == 0x01) { + await entity.commandResponse('genAlarms', 'alarm', {alarmcode: 0x11, clusterid: 0xe000}, {direction: 1}); + return {state: {siren_state: 'pre_alarm'}}; + } else if (index == 0x02) { + await entity.commandResponse('genAlarms', 'alarm', {alarmcode: 0x10, clusterid: 0xe000}, {direction: 1}); + return {state: {siren_state: 'fire'}}; + } else if (index == 0x03) { + await endpoint.command('twinguardAlarm', 'burglarAlarm', {data: 0x01}, manufacturerOptions); + } + } + }, + convertGet: async (entity, key, meta) => { + switch (key) { + case 'sensitivity': + await entity.read('twinguardSmokeDetector', ['sensitivity'], manufacturerOptions); + break; + case 'pre_alarm': + await entity.read('twinguardOptions', ['pre_alarm'], manufacturerOptions); + break; + case 'heartbeat': + await meta.device.getEndpoint(12).read('twinguardSetup', ['heartbeat'], manufacturerOptions); + break; + case 'alarm': + case 'self_test': + await meta.device.getEndpoint(12).read('twinguardAlarm', ['alarm_status'], manufacturerOptions); + break; + default: + throw new Error(`Unhandled key boschExtend.twinguard.toZigbee.convertGet ${key}`); + } + }, }, - }]; + ]; return { exposes, fromZigbee, @@ -708,177 +776,175 @@ const boschExtend = { }, bmct: (): ModernExtend => { const stateDeviceMode: KeyValue = { - 'light': 0x04, - 'shutter': 0x01, - 'disabled': 0x00, + light: 0x04, + shutter: 0x01, + disabled: 0x00, }; const stateMotor: KeyValue = { - 'stopped': 0x00, - 'opening': 0x01, - 'closing': 0x02, + stopped: 0x00, + opening: 0x01, + closing: 0x02, }; const stateSwitchType: KeyValue = { - 'button': 0x01, - 'button_key_change': 0x02, - 'rocker_switch': 0x03, - 'rocker_switch_key_change': 0x04, + button: 0x01, + button_key_change: 0x02, + rocker_switch: 0x03, + rocker_switch_key_change: 0x04, }; const stateOffOn = { - 'OFF': 0x00, - 'ON': 0x01, + OFF: 0x00, + ON: 0x01, }; - const fromZigbee: Fz.Converter[] = [fz.on_off, fz.power_on_behavior, fz.cover_position_tilt, { - cluster: 'boschSpecific', - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - const result: KeyValue = {}; - const data = msg.data; - if (data.hasOwnProperty('deviceMode')) { - result.device_mode = Object.keys(stateDeviceMode).find((key) => stateDeviceMode[key] === msg.data['deviceMode']); - const deviceMode = msg.data['deviceMode']; - if (deviceMode !== meta.device.meta.deviceMode) { - meta.device.meta.deviceMode = deviceMode; - meta.deviceExposesChanged(); + const fromZigbee: Fz.Converter[] = [ + fz.on_off, + fz.power_on_behavior, + fz.cover_position_tilt, + { + cluster: 'boschSpecific', + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + const result: KeyValue = {}; + const data = msg.data; + if (data.hasOwnProperty('deviceMode')) { + result.device_mode = Object.keys(stateDeviceMode).find((key) => stateDeviceMode[key] === msg.data['deviceMode']); + const deviceMode = msg.data['deviceMode']; + if (deviceMode !== meta.device.meta.deviceMode) { + meta.device.meta.deviceMode = deviceMode; + meta.deviceExposesChanged(); + } } - } - if (data.hasOwnProperty('switchType')) { - result.switch_type = Object.keys(stateSwitchType).find((key) => stateSwitchType[key] === msg.data['switchType']); - } - if (data.hasOwnProperty('calibrationOpeningTime')) { - result.calibration_opening_time = msg.data['calibrationOpeningTime'] / 10; - } - if (data.hasOwnProperty('calibrationClosingTime')) { - result.calibration_closing_time = msg.data['calibrationClosingTime'] / 10; - } - if (data.hasOwnProperty('calibrationButtonHoldTime')) { - result.calibration_button_hold_time = msg.data['calibrationButtonHoldTime'] / 10; - } - if (data.hasOwnProperty('calibrationMotorStartDelay')) { - result.calibration_motor_start_delay = msg.data['calibrationMotorStartDelay'] / 10; - } - if (data.hasOwnProperty('childLock')) { - const property = utils.postfixWithEndpointName('child_lock', msg, model, meta); - result[property] = msg.data['childLock'] === 1 ? 'ON' : 'OFF'; - } - if (data.hasOwnProperty('motorState')) { - result.motor_state = Object.keys(stateMotor).find((key) => stateMotor[key] === msg.data['motorState']); - } - return result; - }, - }]; - const toZigbee: Tz.Converter[] = [tz.power_on_behavior, tz.cover_position_tilt, { - key: [ - 'device_mode', - 'switch_type', - 'child_lock', - 'state', - 'on_time', - 'off_wait_time', - ], - convertSet: async (entity, key, value, meta) => { - if (key === 'state') { - if ('ID' in entity && entity.ID === 1) { - await tz.cover_state.convertSet(entity, key, value, meta); - } else { - await tz.on_off.convertSet(entity, key, value, meta); + if (data.hasOwnProperty('switchType')) { + result.switch_type = Object.keys(stateSwitchType).find((key) => stateSwitchType[key] === msg.data['switchType']); } - } - if (key === 'on_time' || key === 'on_wait_time') { - if ('ID' in entity && entity.ID !== 1) { - await tz.on_off.convertSet(entity, key, value, meta); + if (data.hasOwnProperty('calibrationOpeningTime')) { + result.calibration_opening_time = msg.data['calibrationOpeningTime'] / 10; } - } - if (key === 'device_mode') { - const index = utils.getFromLookup(value, stateDeviceMode); - await entity.write('boschSpecific', {deviceMode: index}); - await entity.read('boschSpecific', ['deviceMode']); - return {state: {device_mode: value}}; - } - if (key === 'switch_type') { - const index = utils.getFromLookup(value, stateSwitchType); - await entity.write('boschSpecific', {switchType: index}); - return {state: {switch_type: value}}; - } - if (key === 'child_lock') { - const index = utils.getFromLookup(value, stateOffOn); - await entity.write('boschSpecific', {childLock: index}); - return {state: {child_lock: value}}; - } - }, - convertGet: async (entity, key, meta) => { - switch (key) { - case 'state': - case 'on_time': - case 'off_wait_time': - if ('ID' in entity && entity.ID !== 1) { - await entity.read('genOnOff', ['onOff']); + if (data.hasOwnProperty('calibrationClosingTime')) { + result.calibration_closing_time = msg.data['calibrationClosingTime'] / 10; } - break; - case 'device_mode': - await entity.read('boschSpecific', ['deviceMode']); - break; - case 'switch_type': - await entity.read('boschSpecific', ['switchType']); - break; - case 'child_lock': - await entity.read('boschSpecific', ['childLock']); - break; - default: - throw new Error(`Unhandled key boschExtend.bmct.toZigbee.convertGet ${key}`); - } + if (data.hasOwnProperty('calibrationButtonHoldTime')) { + result.calibration_button_hold_time = msg.data['calibrationButtonHoldTime'] / 10; + } + if (data.hasOwnProperty('calibrationMotorStartDelay')) { + result.calibration_motor_start_delay = msg.data['calibrationMotorStartDelay'] / 10; + } + if (data.hasOwnProperty('childLock')) { + const property = utils.postfixWithEndpointName('child_lock', msg, model, meta); + result[property] = msg.data['childLock'] === 1 ? 'ON' : 'OFF'; + } + if (data.hasOwnProperty('motorState')) { + result.motor_state = Object.keys(stateMotor).find((key) => stateMotor[key] === msg.data['motorState']); + } + return result; + }, }, - }, { - key: [ - 'calibration_closing_time', - 'calibration_opening_time', - 'calibration_button_hold_time', - 'calibration_motor_start_delay', - ], - convertSet: async (entity, key, value, meta) => { - if (key === 'calibration_opening_time') { - const number = utils.toNumber(value, 'calibration_opening_time'); - const index = number * 10; - await entity.write('boschSpecific', {calibrationOpeningTime: index}); - return {state: {calibration_opening_time: number}}; - } - if (key === 'calibration_closing_time') { - const number = utils.toNumber(value, 'calibration_closing_time'); - const index = number * 10; - await entity.write('boschSpecific', {calibrationClosingTime: index}); - return {state: {calibration_closing_time: number}}; - } - if (key === 'calibration_button_hold_time') { - const number = utils.toNumber(value, 'calibration_button_hold_time'); - const index = number * 10; - await entity.write('boschSpecific', {calibrationButtonHoldTime: index}); - return {state: {calibration_button_hold_time: number}}; - } - if (key === 'calibration_motor_start_delay') { - const number = utils.toNumber(value, 'calibration_motor_start_delay'); - const index = number * 10; - await entity.write('boschSpecific', {calibrationMotorStartDelay: index}); - return {state: {calibration_motor_start_delay: number}}; - } + ]; + const toZigbee: Tz.Converter[] = [ + tz.power_on_behavior, + tz.cover_position_tilt, + { + key: ['device_mode', 'switch_type', 'child_lock', 'state', 'on_time', 'off_wait_time'], + convertSet: async (entity, key, value, meta) => { + if (key === 'state') { + if ('ID' in entity && entity.ID === 1) { + await tz.cover_state.convertSet(entity, key, value, meta); + } else { + await tz.on_off.convertSet(entity, key, value, meta); + } + } + if (key === 'on_time' || key === 'on_wait_time') { + if ('ID' in entity && entity.ID !== 1) { + await tz.on_off.convertSet(entity, key, value, meta); + } + } + if (key === 'device_mode') { + const index = utils.getFromLookup(value, stateDeviceMode); + await entity.write('boschSpecific', {deviceMode: index}); + await entity.read('boschSpecific', ['deviceMode']); + return {state: {device_mode: value}}; + } + if (key === 'switch_type') { + const index = utils.getFromLookup(value, stateSwitchType); + await entity.write('boschSpecific', {switchType: index}); + return {state: {switch_type: value}}; + } + if (key === 'child_lock') { + const index = utils.getFromLookup(value, stateOffOn); + await entity.write('boschSpecific', {childLock: index}); + return {state: {child_lock: value}}; + } + }, + convertGet: async (entity, key, meta) => { + switch (key) { + case 'state': + case 'on_time': + case 'off_wait_time': + if ('ID' in entity && entity.ID !== 1) { + await entity.read('genOnOff', ['onOff']); + } + break; + case 'device_mode': + await entity.read('boschSpecific', ['deviceMode']); + break; + case 'switch_type': + await entity.read('boschSpecific', ['switchType']); + break; + case 'child_lock': + await entity.read('boschSpecific', ['childLock']); + break; + default: + throw new Error(`Unhandled key boschExtend.bmct.toZigbee.convertGet ${key}`); + } + }, }, - convertGet: async (entity, key, meta) => { - switch (key) { - case 'calibration_opening_time': - await entity.read('boschSpecific', ['calibrationOpeningTime']); - break; - case 'calibration_closing_time': - await entity.read('boschSpecific', ['calibrationClosingTime']); - break; - case 'calibration_button_hold_time': - await entity.read('boschSpecific', ['calibrationButtonHoldTime']); - break; - case 'calibration_motor_start_delay': - await entity.read('boschSpecific', ['calibrationMotorStartDelay']); - break; - default: - throw new Error(`Unhandled key boschExtend.bmct.toZigbee.convertGet ${key}`); - } + { + key: ['calibration_closing_time', 'calibration_opening_time', 'calibration_button_hold_time', 'calibration_motor_start_delay'], + convertSet: async (entity, key, value, meta) => { + if (key === 'calibration_opening_time') { + const number = utils.toNumber(value, 'calibration_opening_time'); + const index = number * 10; + await entity.write('boschSpecific', {calibrationOpeningTime: index}); + return {state: {calibration_opening_time: number}}; + } + if (key === 'calibration_closing_time') { + const number = utils.toNumber(value, 'calibration_closing_time'); + const index = number * 10; + await entity.write('boschSpecific', {calibrationClosingTime: index}); + return {state: {calibration_closing_time: number}}; + } + if (key === 'calibration_button_hold_time') { + const number = utils.toNumber(value, 'calibration_button_hold_time'); + const index = number * 10; + await entity.write('boschSpecific', {calibrationButtonHoldTime: index}); + return {state: {calibration_button_hold_time: number}}; + } + if (key === 'calibration_motor_start_delay') { + const number = utils.toNumber(value, 'calibration_motor_start_delay'); + const index = number * 10; + await entity.write('boschSpecific', {calibrationMotorStartDelay: index}); + return {state: {calibration_motor_start_delay: number}}; + } + }, + convertGet: async (entity, key, meta) => { + switch (key) { + case 'calibration_opening_time': + await entity.read('boschSpecific', ['calibrationOpeningTime']); + break; + case 'calibration_closing_time': + await entity.read('boschSpecific', ['calibrationClosingTime']); + break; + case 'calibration_button_hold_time': + await entity.read('boschSpecific', ['calibrationButtonHoldTime']); + break; + case 'calibration_motor_start_delay': + await entity.read('boschSpecific', ['calibrationMotorStartDelay']); + break; + default: + throw new Error(`Unhandled key boschExtend.bmct.toZigbee.convertGet ${key}`); + } + }, }, - }]; + ]; return { fromZigbee, toZigbee, @@ -939,29 +1005,29 @@ const tzLocal = { }, convertGet: async (entity, key, meta) => { switch (key) { - case 'light_delay': - await entity.read(0x0502, [0xa004], manufacturerOptions); - break; - case 'siren_delay': - await entity.read(0x0502, [0xa003], manufacturerOptions); - break; - case 'light_duration': - await entity.read(0x0502, [0xa005], manufacturerOptions); - break; - case 'siren_duration': - await entity.read(0x0502, [0xa000], manufacturerOptions); - break; - case 'siren_and_light': - await entity.read(0x0502, [0xa001], manufacturerOptions); - break; - case 'siren_volume': - await entity.read(0x0502, [0xa002], manufacturerOptions); - break; - case 'alarm_state': - await entity.read(0x0502, [0xf0], manufacturerOptions); - break; - default: // Unknown key - throw new Error(`Unhandled key toZigbee.rbshoszbeu.convertGet ${key}`); + case 'light_delay': + await entity.read(0x0502, [0xa004], manufacturerOptions); + break; + case 'siren_delay': + await entity.read(0x0502, [0xa003], manufacturerOptions); + break; + case 'light_duration': + await entity.read(0x0502, [0xa005], manufacturerOptions); + break; + case 'siren_duration': + await entity.read(0x0502, [0xa000], manufacturerOptions); + break; + case 'siren_and_light': + await entity.read(0x0502, [0xa001], manufacturerOptions); + break; + case 'siren_volume': + await entity.read(0x0502, [0xa002], manufacturerOptions); + break; + case 'alarm_state': + await entity.read(0x0502, [0xf0], manufacturerOptions); + break; + default: // Unknown key + throw new Error(`Unhandled key toZigbee.rbshoszbeu.convertGet ${key}`); } }, } satisfies Tz.Converter, @@ -974,7 +1040,7 @@ const tzLocal = { await entity.read('boschSpecific', [buttonMap[key as keyof typeof buttonMap]], manufacturerOptions); }, convertSet: async (entity, key, value, meta) => { - if (!buttonMap.hasOwnProperty(key) ) { + if (!buttonMap.hasOwnProperty(key)) { return; } @@ -985,21 +1051,20 @@ const tzLocal = { payload[buttonMap[key as keyof typeof buttonMap]] = {value: buffer, type: 65}; await entity.write('boschSpecific', payload, manufacturerOptions); - const result:{[key: number | string]: string} = {}; + const result: {[key: number | string]: string} = {}; result[key] = value as string; return {state: result}; }, } satisfies Tz.Converter, }; - const fzLocal = { bhius_button_press: { cluster: 'boschSpecific', type: 'raw', options: [e.text('led_response', ea.ALL).withLabel('LED config (confirmation response)').withDescription(labelConfirmation)], convert: async (model, msg, publish, options, meta) => { - const sequenceNumber= msg.data.readUInt8(3); + const sequenceNumber = msg.data.readUInt8(3); const buttonId = msg.data.readUInt8(4); const longPress = msg.data.readUInt8(5); const duration = msg.data.readUInt16LE(6); @@ -1025,9 +1090,8 @@ const fzLocal = { command = 'longpress'; } else { globalStore.clearValue(msg.endpoint, buttons[buttonId]); - command = longPress ? 'longpress_release': 'release'; - msg.endpoint.command('boschSpecific', 'confirmButtonPressed', {data: buffer}, {sendPolicy: 'immediate'}) - .catch((error) => {}); + command = longPress ? 'longpress_release' : 'release'; + msg.endpoint.command('boschSpecific', 'confirmButtonPressed', {data: buffer}, {sendPolicy: 'immediate'}).catch((error) => {}); } return {action: `button_${buttons[buttonId]}_${command}`}; } else { @@ -1068,59 +1132,78 @@ const definitions: Definition[] = [ }, exposes: [ e.binary('alarm_state', ea.ALL, 'ON', 'OFF').withDescription('Alarm turn ON/OFF'), - e.numeric('light_delay', ea.ALL).withValueMin(0).withValueMax(30).withValueStep(1) - .withUnit('s').withDescription('Flashing light delay').withUnit('s'), - e.numeric('siren_delay', ea.ALL).withValueMin(0).withValueMax(30).withValueStep(1) - .withUnit('s').withDescription('Siren alarm delay').withUnit('s'), - e.numeric('siren_duration', ea.ALL).withValueMin(1).withValueMax(15).withValueStep(1) - .withUnit('m').withDescription('Duration of the alarm siren').withUnit('m'), - e.numeric('light_duration', ea.ALL).withValueMin(1).withValueMax(15).withValueStep(1) - .withUnit('m').withDescription('Duration of the alarm light').withUnit('m'), + e + .numeric('light_delay', ea.ALL) + .withValueMin(0) + .withValueMax(30) + .withValueStep(1) + .withUnit('s') + .withDescription('Flashing light delay') + .withUnit('s'), + e + .numeric('siren_delay', ea.ALL) + .withValueMin(0) + .withValueMax(30) + .withValueStep(1) + .withUnit('s') + .withDescription('Siren alarm delay') + .withUnit('s'), + e + .numeric('siren_duration', ea.ALL) + .withValueMin(1) + .withValueMax(15) + .withValueStep(1) + .withUnit('m') + .withDescription('Duration of the alarm siren') + .withUnit('m'), + e + .numeric('light_duration', ea.ALL) + .withValueMin(1) + .withValueMax(15) + .withValueStep(1) + .withUnit('m') + .withDescription('Duration of the alarm light') + .withUnit('m'), e.enum('siren_volume', ea.ALL, Object.keys(sirenVolume)).withDescription('Volume of the alarm'), e.enum('siren_and_light', ea.ALL, Object.keys(sirenLight)).withDescription('Siren and Light behaviour during alarm '), e.enum('power_source', ea.ALL, Object.keys(sirenPowerSupply)).withDescription('Siren power source'), - e.warning() + e + .warning() .removeFeature('strobe_level') .removeFeature('strobe') .removeFeature('strobe_duty_cycle') .removeFeature('level') .removeFeature('duration'), - e.test(), e.tamper(), e.battery(), e.battery_voltage(), e.battery_low(), + e.test(), + e.tamper(), + e.battery(), + e.battery_voltage(), + e.battery_low(), e.binary('ac_status', ea.STATE, true, false).withDescription('Is the device plugged in'), ], extend: [ - deviceAddCustomCluster( - 'ssIasZone', - { - ID: Zcl.Clusters.ssIasZone.ID, - attributes: {}, - commands: { - boschTestTamper: { - ID: 0xF3, - parameters: [ - {name: 'data', type: Zcl.DataType.UINT8}, - ], - }, + deviceAddCustomCluster('ssIasZone', { + ID: Zcl.Clusters.ssIasZone.ID, + attributes: {}, + commands: { + boschTestTamper: { + ID: 0xf3, + parameters: [{name: 'data', type: Zcl.DataType.UINT8}], }, - commandsResponse: {}, }, - ), - deviceAddCustomCluster( - 'ssIasWd', - { - ID: Zcl.Clusters.ssIasWd.ID, - attributes: {}, - commands: { - boschOutdoorSiren: { - ID: 240, - parameters: [ - {name: 'data', type: Zcl.DataType.UINT8}, - ], - }, + commandsResponse: {}, + }), + deviceAddCustomCluster('ssIasWd', { + ID: Zcl.Clusters.ssIasWd.ID, + attributes: {}, + commands: { + boschOutdoorSiren: { + ID: 240, + parameters: [{name: 'data', type: Zcl.DataType.UINT8}], }, - commandsResponse: {}, }, - ), + commandsResponse: {}, + }), quirkCheckinInterval(0), ], }, @@ -1130,21 +1213,18 @@ const definitions: Definition[] = [ vendor: 'Bosch', description: 'Smart water alarm', extend: [ - deviceAddCustomCluster( - 'boschSpecific', - { - ID: 0xfcac, - manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, - attributes: { - alarmOnMotion: { - ID: 0x0003, - type: Zcl.DataType.BOOLEAN, - }, + deviceAddCustomCluster('boschSpecific', { + ID: 0xfcac, + manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, + attributes: { + alarmOnMotion: { + ID: 0x0003, + type: Zcl.DataType.BOOLEAN, }, - commands: {}, - commandsResponse: {}, }, - ), + commands: {}, + commandsResponse: {}, + }), iasZoneAlarm({ zoneType: 'water_leak', zoneAttributes: ['tamper'], @@ -1181,20 +1261,17 @@ const definitions: Definition[] = [ vendor: 'Bosch', description: 'Smoke alarm II', extend: [ - deviceAddCustomCluster( - 'ssIasZone', - { - ID: Zcl.Clusters.ssIasZone.ID, - attributes: {}, - commands: { - boschSmokeAlarmSiren: { - ID: 0x80, - parameters: [{name: 'data', type: Zcl.DataType.UINT16}], - }, + deviceAddCustomCluster('ssIasZone', { + ID: Zcl.Clusters.ssIasZone.ID, + attributes: {}, + commands: { + boschSmokeAlarmSiren: { + ID: 0x80, + parameters: [{name: 'data', type: Zcl.DataType.UINT16}], }, - commandsResponse: {}, }, - ), + commandsResponse: {}, + }), boschExtend.smokeAlarm(), battery({ percentage: true, @@ -1206,9 +1283,9 @@ const definitions: Definition[] = [ attribute: 'currentZoneSensitivityLevel', description: 'Sensitivity of the smoke detector', lookup: { - 'low': 0x00, - 'medium': 0x01, - 'high': 0x02, + low: 0x00, + medium: 0x01, + high: 0x02, }, entityCategory: 'config', }), @@ -1226,8 +1303,20 @@ const definitions: Definition[] = [ }, }, { - zigbeeModel: ['RFDL-ZB', 'RFDL-ZB-EU', 'RFDL-ZB-H', 'RFDL-ZB-K', 'RFDL-ZB-CHI', 'RFDL-ZB-MS', 'RFDL-ZB-ES', 'RFPR-ZB', - 'RFPR-ZB-EU', 'RFPR-ZB-CHI', 'RFPR-ZB-ES', 'RFPR-ZB-MS'], + zigbeeModel: [ + 'RFDL-ZB', + 'RFDL-ZB-EU', + 'RFDL-ZB-H', + 'RFDL-ZB-K', + 'RFDL-ZB-CHI', + 'RFDL-ZB-MS', + 'RFDL-ZB-ES', + 'RFPR-ZB', + 'RFPR-ZB-EU', + 'RFPR-ZB-CHI', + 'RFPR-ZB-ES', + 'RFPR-ZB-MS', + ], model: 'RADON TriTech ZB', vendor: 'Bosch', description: 'Wireless motion detector', @@ -1269,12 +1358,13 @@ const definitions: Definition[] = [ const entry = configs.findIndex((e) => e.type === 'climate'); if (entry) { const commandTopic = configs[entry].discovery_payload.mode_command_topic as string; - configs[entry].discovery_payload.mode_command_topic = - commandTopic.substring(0, commandTopic.lastIndexOf('/system_mode')); - configs[entry].discovery_payload.mode_command_template = `{% set values = ` + + configs[entry].discovery_payload.mode_command_topic = commandTopic.substring(0, commandTopic.lastIndexOf('/system_mode')); + configs[entry].discovery_payload.mode_command_template = + `{% set values = ` + `{ 'auto':'schedule','heat':'manual','off':'pause'} %}` + `{"operating_mode": "{{ values[value] if value in values.keys() else 'pause' }}"}`; - configs[entry].discovery_payload.mode_state_template = `{% set values = ` + + configs[entry].discovery_payload.mode_state_template = + `{% set values = ` + `{'schedule':'auto','manual':'heat','pause':'off'} %}` + `{% set value = value_json.operating_mode %}{{ values[value] if value in values.keys() else 'off' }}`; configs[entry].discovery_payload.modes = ['off', 'heat', 'auto']; @@ -1282,18 +1372,20 @@ const definitions: Definition[] = [ }, }, exposes: [ - e.climate() - .withLocalTemperature(ea.STATE_GET, 'Temperature used by the heating algorithm. ' + - 'This is the temperature measured on the device (by default) or the remote temperature (if set within the last 30 min).') + e + .climate() + .withLocalTemperature( + ea.STATE_GET, + 'Temperature used by the heating algorithm. ' + + 'This is the temperature measured on the device (by default) or the remote temperature (if set within the last 30 min).', + ) .withLocalTemperatureCalibration(-5, 5, 0.1) .withSetpoint('occupied_heating_setpoint', 5, 30, 0.5) .withSystemMode(['heat']) .withPiHeatingDemand(ea.ALL) .withRunningState(['idle', 'heat'], ea.STATE_GET), ], - fromZigbee: [ - fz.thermostat, - ], + fromZigbee: [fz.thermostat], toZigbee: [ tz.thermostat_system_mode, tz.thermostat_occupied_heating_setpoint, @@ -1315,8 +1407,7 @@ const definitions: Definition[] = [ name: 'remote_temperature', cluster: 'hvacThermostat', attribute: 'remoteTemperature', - description: 'Input for remote temperature sensor. ' + - 'Required at least every 30 min. to prevent fallback to internal sensor!', + description: 'Input for remote temperature sensor. ' + 'Required at least every 30 min. to prevent fallback to internal sensor!', valueMin: 0.0, valueMax: 35.0, valueStep: 0.01, @@ -1330,7 +1421,7 @@ const definitions: Definition[] = [ attribute: 'setpointChangeSource', reporting: {min: '10_SECONDS', max: 'MAX', change: null}, description: 'Source of the current setpoint temperature', - lookup: {'manual': 0x00, 'schedule': 0x01, 'externally': 0x02}, + lookup: {manual: 0x00, schedule: 0x01, externally: 0x02}, access: 'STATE_GET', }), boschExtend.childLock(), @@ -1341,7 +1432,7 @@ const definitions: Definition[] = [ cluster: 'hvacUserInterfaceCfg', attribute: 'displayOrientation', description: 'Sets orientation of the display', - lookup: {'normal': 0x00, 'flipped': 0x01}, + lookup: {normal: 0x00, flipped: 0x01}, zigbeeCommandOptions: manufacturerOptions, }), enumLookup({ @@ -1349,7 +1440,7 @@ const definitions: Definition[] = [ cluster: 'hvacUserInterfaceCfg', attribute: 'displayedTemperature', description: 'Temperature displayed on the TRV', - lookup: {'target': 0x00, 'measured': 0x01}, + lookup: {target: 0x00, measured: 0x01}, zigbeeCommandOptions: manufacturerOptions, }), enumLookup({ @@ -1359,11 +1450,11 @@ const definitions: Definition[] = [ reporting: {min: '10_SECONDS', max: 'MAX', change: null}, description: 'Specifies the current status of the valve adaptation', lookup: { - 'none': 0x00, - 'ready_to_calibrate': 0x01, - 'calibration_in_progress': 0x02, - 'error': 0x03, - 'success': 0x04, + none: 0x00, + ready_to_calibrate: 0x01, + calibration_in_progress: 0x02, + error: 0x03, + success: 0x04, }, zigbeeCommandOptions: manufacturerOptions, access: 'STATE_GET', @@ -1379,9 +1470,7 @@ const definitions: Definition[] = [ ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); - await reporting.bind(endpoint, coordinatorEndpoint, [ - 'hvacThermostat', 'hvacUserInterfaceCfg', - ]); + await reporting.bind(endpoint, coordinatorEndpoint, ['hvacThermostat', 'hvacUserInterfaceCfg']); await reporting.thermostatTemperature(endpoint); await reporting.thermostatOccupiedHeatingSetpoint(endpoint, { min: constants.repInterval.SECONDS_10, @@ -1389,21 +1478,31 @@ const definitions: Definition[] = [ change: 50, }); await reporting.thermostatKeypadLockMode(endpoint); - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'heatingDemand', - minimumReportInterval: constants.repInterval.SECONDS_10, - maximumReportInterval: constants.repInterval.MAX, - reportableChange: null, - }], manufacturerOptions); + await endpoint.configureReporting( + 'hvacThermostat', + [ + { + attribute: 'heatingDemand', + minimumReportInterval: constants.repInterval.SECONDS_10, + maximumReportInterval: constants.repInterval.MAX, + reportableChange: null, + }, + ], + manufacturerOptions, + ); await endpoint.read('genPowerCfg', ['batteryPercentageRemaining']); await endpoint.read('hvacThermostat', ['localTemperatureCalibration', 'setpointChangeSource']); - await endpoint.read('hvacThermostat', [ - 'operatingMode', 'heatingDemand', 'valveAdaptStatus', 'remoteTemperature', 'windowDetection', 'boostHeating', - ], manufacturerOptions); + await endpoint.read( + 'hvacThermostat', + ['operatingMode', 'heatingDemand', 'valveAdaptStatus', 'remoteTemperature', 'windowDetection', 'boostHeating'], + manufacturerOptions, + ); await endpoint.read('hvacUserInterfaceCfg', ['keypadLockout']); - await endpoint.read('hvacUserInterfaceCfg', [ - 'displayOrientation', 'displayedTemperature', 'displayOntime', 'displayBrightness', - ], manufacturerOptions); + await endpoint.read( + 'hvacUserInterfaceCfg', + ['displayOrientation', 'displayedTemperature', 'displayOntime', 'displayBrightness'], + manufacturerOptions, + ); }, }, { @@ -1412,7 +1511,8 @@ const definitions: Definition[] = [ vendor: 'Bosch', description: 'Room thermostat II (Battery model)', exposes: [ - e.climate() + e + .climate() .withLocalTemperature() .withSetpoint('occupied_heating_setpoint', 4.5, 30, 0.5) .withSetpoint('occupied_cooling_setpoint', 4.5, 30, 0.5) @@ -1420,10 +1520,7 @@ const definitions: Definition[] = [ .withSystemMode(['off', 'heat', 'cool']) .withRunningState(['idle', 'heat', 'cool']), ], - fromZigbee: [ - fz.thermostat, - fz.hvac_user_interface, - ], + fromZigbee: [fz.thermostat, fz.hvac_user_interface], toZigbee: [ tz.thermostat_system_mode, tz.thermostat_running_state, @@ -1461,9 +1558,7 @@ const definitions: Definition[] = [ ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); - await reporting.bind(endpoint, coordinatorEndpoint, [ - 'hvacThermostat', 'hvacUserInterfaceCfg', - ]); + await reporting.bind(endpoint, coordinatorEndpoint, ['hvacThermostat', 'hvacUserInterfaceCfg']); await reporting.thermostatSystemMode(endpoint); await reporting.thermostatRunningState(endpoint); await reporting.thermostatTemperature(endpoint); @@ -1491,7 +1586,8 @@ const definitions: Definition[] = [ vendor: 'Bosch', description: 'Room thermostat II 230V', exposes: [ - e.climate() + e + .climate() .withLocalTemperature() .withSetpoint('occupied_heating_setpoint', 4.5, 30, 0.5) .withSetpoint('occupied_cooling_setpoint', 4.5, 30, 0.5) @@ -1499,10 +1595,7 @@ const definitions: Definition[] = [ .withSystemMode(['off', 'heat', 'cool']) .withRunningState(['idle', 'heat', 'cool']), ], - fromZigbee: [ - fz.thermostat, - fz.hvac_user_interface, - ], + fromZigbee: [fz.thermostat, fz.hvac_user_interface], toZigbee: [ tz.thermostat_system_mode, tz.thermostat_running_state, @@ -1528,9 +1621,7 @@ const definitions: Definition[] = [ ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); - await reporting.bind(endpoint, coordinatorEndpoint, [ - 'hvacThermostat', 'hvacUserInterfaceCfg', - ]); + await reporting.bind(endpoint, coordinatorEndpoint, ['hvacThermostat', 'hvacUserInterfaceCfg']); await reporting.thermostatSystemMode(endpoint); await reporting.thermostatRunningState(endpoint); await reporting.thermostatTemperature(endpoint); @@ -1557,98 +1648,83 @@ const definitions: Definition[] = [ vendor: 'Bosch', description: 'Twinguard', extend: [ - deviceAddCustomCluster( - 'twinguardSmokeDetector', - { - ID: 0xe000, - manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, - attributes: { - sensitivity: {ID: 0x4003, type: Zcl.DataType.UINT16}, - }, - commands: { - initiateTestMode: { - ID: 0x00, - parameters: [], - }, - }, - commandsResponse: {}, + deviceAddCustomCluster('twinguardSmokeDetector', { + ID: 0xe000, + manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, + attributes: { + sensitivity: {ID: 0x4003, type: Zcl.DataType.UINT16}, }, - ), - deviceAddCustomCluster( - 'twinguardMeasurements', - { - ID: 0xe002, - manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, - attributes: { - humidity: {ID: 0x4000, type: Zcl.DataType.UINT16}, - unknown1: {ID: 0x4001, type: Zcl.DataType.UINT16}, - unknown2: {ID: 0x4002, type: Zcl.DataType.UINT16}, - airpurity: {ID: 0x4003, type: Zcl.DataType.UINT16}, - temperature: {ID: 0x4004, type: Zcl.DataType.INT16}, - illuminance_lux: {ID: 0x4005, type: Zcl.DataType.UINT16}, - battery: {ID: 0x4006, type: Zcl.DataType.UINT16}, - unknown3: {ID: 0x4007, type: Zcl.DataType.UINT16}, - unknown4: {ID: 0x4008, type: Zcl.DataType.UINT16}, - pressure: {ID: 0x4009, type: Zcl.DataType.UINT16}, // Not yet confirmed - unknown6: {ID: 0x400a, type: Zcl.DataType.UINT16}, - unknown7: {ID: 0x400b, type: Zcl.DataType.UINT16}, - unknown8: {ID: 0x400c, type: Zcl.DataType.UINT16}, + commands: { + initiateTestMode: { + ID: 0x00, + parameters: [], }, - commands: {}, - commandsResponse: {}, }, - ), - deviceAddCustomCluster( - 'twinguardOptions', - { - ID: 0xe004, - manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, - attributes: { - unknown1: {ID: 0x4000, type: Zcl.DataType.BITMAP8}, // 0,1 ??? read during pairing - pre_alarm: {ID: 0x4001, type: Zcl.DataType.BITMAP8}, // 0,1 on/off - }, - commands: {}, - commandsResponse: {}, + commandsResponse: {}, + }), + deviceAddCustomCluster('twinguardMeasurements', { + ID: 0xe002, + manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, + attributes: { + humidity: {ID: 0x4000, type: Zcl.DataType.UINT16}, + unknown1: {ID: 0x4001, type: Zcl.DataType.UINT16}, + unknown2: {ID: 0x4002, type: Zcl.DataType.UINT16}, + airpurity: {ID: 0x4003, type: Zcl.DataType.UINT16}, + temperature: {ID: 0x4004, type: Zcl.DataType.INT16}, + illuminance_lux: {ID: 0x4005, type: Zcl.DataType.UINT16}, + battery: {ID: 0x4006, type: Zcl.DataType.UINT16}, + unknown3: {ID: 0x4007, type: Zcl.DataType.UINT16}, + unknown4: {ID: 0x4008, type: Zcl.DataType.UINT16}, + pressure: {ID: 0x4009, type: Zcl.DataType.UINT16}, // Not yet confirmed + unknown6: {ID: 0x400a, type: Zcl.DataType.UINT16}, + unknown7: {ID: 0x400b, type: Zcl.DataType.UINT16}, + unknown8: {ID: 0x400c, type: Zcl.DataType.UINT16}, }, - ), - deviceAddCustomCluster( - 'twinguardSetup', - { - ID: 0xe006, - manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, - attributes: { - unknown1: {ID: 0x5003, type: Zcl.DataType.INT8}, // perhaps signal strength? -7? - unknown2: {ID: 0x5004, type: Zcl.DataType.UINT8}, // ???? - heartbeat: {ID: 0x5005, type: Zcl.DataType.BITMAP8}, // 0 - }, - commands: { - pairingCompleted: { - ID: 0x01, - parameters: [], - }, - }, - commandsResponse: {}, + commands: {}, + commandsResponse: {}, + }), + deviceAddCustomCluster('twinguardOptions', { + ID: 0xe004, + manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, + attributes: { + unknown1: {ID: 0x4000, type: Zcl.DataType.BITMAP8}, // 0,1 ??? read during pairing + pre_alarm: {ID: 0x4001, type: Zcl.DataType.BITMAP8}, // 0,1 on/off }, - ), - deviceAddCustomCluster( - 'twinguardAlarm', - { - ID: 0xe007, - manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, - attributes: { - alarm_status: {ID: 0x5000, type: Zcl.DataType.BITMAP32}, + commands: {}, + commandsResponse: {}, + }), + deviceAddCustomCluster('twinguardSetup', { + ID: 0xe006, + manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, + attributes: { + unknown1: {ID: 0x5003, type: Zcl.DataType.INT8}, // perhaps signal strength? -7? + unknown2: {ID: 0x5004, type: Zcl.DataType.UINT8}, // ???? + heartbeat: {ID: 0x5005, type: Zcl.DataType.BITMAP8}, // 0 + }, + commands: { + pairingCompleted: { + ID: 0x01, + parameters: [], }, - commands: { - burglarAlarm: { - ID: 0x01, - parameters: [ - {name: 'data', type: Zcl.DataType.UINT8}, // data:1 trips the siren data:0 should stop the siren - ], - }, + }, + commandsResponse: {}, + }), + deviceAddCustomCluster('twinguardAlarm', { + ID: 0xe007, + manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, + attributes: { + alarm_status: {ID: 0x5000, type: Zcl.DataType.BITMAP32}, + }, + commands: { + burglarAlarm: { + ID: 0x01, + parameters: [ + {name: 'data', type: Zcl.DataType.UINT8}, // data:1 trips the siren data:0 should stop the siren + ], }, - commandsResponse: {}, }, - ), + commandsResponse: {}, + }), boschExtend.twinguard(), ], configure: async (device, coordinatorEndpoint) => { @@ -1771,33 +1847,28 @@ const definitions: Definition[] = [ vendor: 'Bosch', description: 'Light/shutter control unit II', extend: [ - deviceEndpoints({endpoints: {'left': 2, 'right': 3}}), - deviceAddCustomCluster( - 'boschSpecific', - { - ID: 0xfca0, - manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, - attributes: { - deviceMode: {ID: 0x0000, type: Zcl.DataType.ENUM8}, - switchType: {ID: 0x0001, type: Zcl.DataType.ENUM8}, - calibrationOpeningTime: {ID: 0x0002, type: Zcl.DataType.UINT32}, - calibrationClosingTime: {ID: 0x0003, type: Zcl.DataType.UINT32}, - calibrationButtonHoldTime: {ID: 0x0005, type: Zcl.DataType.UINT8}, - childLock: {ID: 0x0008, type: Zcl.DataType.BOOLEAN}, - calibrationMotorStartDelay: {ID: 0x000f, type: Zcl.DataType.UINT8}, - motorState: {ID: 0x0013, type: Zcl.DataType.ENUM8}, - }, - commands: {}, - commandsResponse: {}, + deviceEndpoints({endpoints: {left: 2, right: 3}}), + deviceAddCustomCluster('boschSpecific', { + ID: 0xfca0, + manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, + attributes: { + deviceMode: {ID: 0x0000, type: Zcl.DataType.ENUM8}, + switchType: {ID: 0x0001, type: Zcl.DataType.ENUM8}, + calibrationOpeningTime: {ID: 0x0002, type: Zcl.DataType.UINT32}, + calibrationClosingTime: {ID: 0x0003, type: Zcl.DataType.UINT32}, + calibrationButtonHoldTime: {ID: 0x0005, type: Zcl.DataType.UINT8}, + childLock: {ID: 0x0008, type: Zcl.DataType.BOOLEAN}, + calibrationMotorStartDelay: {ID: 0x000f, type: Zcl.DataType.UINT8}, + motorState: {ID: 0x0013, type: Zcl.DataType.ENUM8}, }, - ), + commands: {}, + commandsResponse: {}, + }), boschExtend.bmct(), ], configure: async (device, coordinatorEndpoint) => { const endpoint1 = device.getEndpoint(1); - await reporting.bind(endpoint1, coordinatorEndpoint, [ - 'genIdentify', 'closuresWindowCovering', 'boschSpecific', - ]); + await reporting.bind(endpoint1, coordinatorEndpoint, ['genIdentify', 'closuresWindowCovering', 'boschSpecific']); await reporting.currentPositionLiftPercentage(endpoint1); await endpoint1.read('boschSpecific', [ 'deviceMode', @@ -1811,37 +1882,32 @@ const definitions: Definition[] = [ ]); const endpoint2 = device.getEndpoint(2); await endpoint2.read('boschSpecific', ['childLock']); - await reporting.bind(endpoint2, coordinatorEndpoint, [ - 'genIdentify', 'genOnOff', - ]); + await reporting.bind(endpoint2, coordinatorEndpoint, ['genIdentify', 'genOnOff']); await reporting.onOff(endpoint2); const endpoint3 = device.getEndpoint(3); await endpoint3.read('boschSpecific', ['childLock']); - await reporting.bind(endpoint3, coordinatorEndpoint, [ - 'genIdentify', 'genOnOff', - ]); + await reporting.bind(endpoint3, coordinatorEndpoint, ['genIdentify', 'genOnOff']); await reporting.onOff(endpoint3); }, exposes: (device, options) => { const stateDeviceMode: KeyValue = { - 'light': 0x04, - 'shutter': 0x01, - 'disabled': 0x00, + light: 0x04, + shutter: 0x01, + disabled: 0x00, }; const stateMotor: KeyValue = { - 'stopped': 0x00, - 'opening': 0x01, - 'closing': 0x02, + stopped: 0x00, + opening: 0x01, + closing: 0x02, }; const stateSwitchType: KeyValue = { - 'button': 0x01, - 'button_key_change': 0x02, - 'rocker_switch': 0x03, - 'rocker_switch_key_change': 0x04, + button: 0x01, + button_key_change: 0x02, + rocker_switch: 0x03, + rocker_switch_key_change: 0x04, }; const commonExposes = [ - e.enum('switch_type', ea.ALL, Object.keys(stateSwitchType)) - .withDescription('Module controlled by a rocker switch or a button'), + e.enum('switch_type', ea.ALL, Object.keys(stateSwitchType)).withDescription('Module controlled by a rocker switch or a button'), e.linkquality(), ]; const lightExposes = [ @@ -1849,29 +1915,41 @@ const definitions: Definition[] = [ e.switch().withEndpoint('right'), e.power_on_behavior().withEndpoint('left'), e.power_on_behavior().withEndpoint('right'), - e.binary('child_lock', ea.ALL, 'ON', 'OFF').withEndpoint('left') - .withDescription('Enable/Disable child lock'), - e.binary('child_lock', ea.ALL, 'ON', 'OFF').withEndpoint('right') - .withDescription('Enable/Disable child lock'), + e.binary('child_lock', ea.ALL, 'ON', 'OFF').withEndpoint('left').withDescription('Enable/Disable child lock'), + e.binary('child_lock', ea.ALL, 'ON', 'OFF').withEndpoint('right').withDescription('Enable/Disable child lock'), ]; const coverExposes = [ e.cover_position(), - e.enum('motor_state', ea.STATE, Object.keys(stateMotor)) - .withDescription('Current shutter motor state'), - e.binary('child_lock', ea.ALL, 'ON', 'OFF') - .withDescription('Enable/Disable child lock'), - e.numeric('calibration_closing_time', ea.ALL).withUnit('s') + e.enum('motor_state', ea.STATE, Object.keys(stateMotor)).withDescription('Current shutter motor state'), + e.binary('child_lock', ea.ALL, 'ON', 'OFF').withDescription('Enable/Disable child lock'), + e + .numeric('calibration_closing_time', ea.ALL) + .withUnit('s') .withDescription('Calibrate shutter closing time') - .withValueMin(1).withValueMax(90).withValueStep(0.1), - e.numeric('calibration_opening_time', ea.ALL).withUnit('s') + .withValueMin(1) + .withValueMax(90) + .withValueStep(0.1), + e + .numeric('calibration_opening_time', ea.ALL) + .withUnit('s') .withDescription('Calibrate shutter opening time') - .withValueMin(1).withValueMax(90).withValueStep(0.1), - e.numeric('calibration_button_hold_time', ea.ALL).withUnit('s') + .withValueMin(1) + .withValueMax(90) + .withValueStep(0.1), + e + .numeric('calibration_button_hold_time', ea.ALL) + .withUnit('s') .withDescription('Time to hold for long press') - .withValueMin(0.1).withValueMax(2).withValueStep(0.1), - e.numeric('calibration_motor_start_delay', ea.ALL).withUnit('s') + .withValueMin(0.1) + .withValueMax(2) + .withValueStep(0.1), + e + .numeric('calibration_motor_start_delay', ea.ALL) + .withUnit('s') .withDescription('Delay between command and motor start') - .withValueMin(0).withValueMax(20).withValueStep(0.1), + .withValueMin(0) + .withValueMax(20) + .withValueStep(0.1), ]; if (device) { @@ -1884,8 +1962,7 @@ const definitions: Definition[] = [ return [...commonExposes, ...coverExposes]; } } - return [e.enum('device_mode', ea.ALL, Object.keys(stateDeviceMode)).withDescription('Device mode'), - e.linkquality()]; + return [e.enum('device_mode', ea.ALL, Object.keys(stateDeviceMode)).withDescription('Device mode'), e.linkquality()]; }, }, { @@ -1898,28 +1975,44 @@ const definitions: Definition[] = [ exposes: [ e.battery_low(), e.battery_voltage(), - e.text('config_led_top_left_press', ea.ALL).withLabel('LED config (top left short press)') + e + .text('config_led_top_left_press', ea.ALL) + .withLabel('LED config (top left short press)') .withDescription(labelShortPress) .withCategory('config'), - e.text('config_led_top_right_press', ea.ALL).withLabel('LED config (top right short press)') + e + .text('config_led_top_right_press', ea.ALL) + .withLabel('LED config (top right short press)') .withDescription(labelShortPress) .withCategory('config'), - e.text('config_led_bottom_left_press', ea.ALL).withLabel('LED config (bottom left short press)') + e + .text('config_led_bottom_left_press', ea.ALL) + .withLabel('LED config (bottom left short press)') .withDescription(labelShortPress) .withCategory('config'), - e.text('config_led_bottom_right_press', ea.ALL).withLabel('LED config (bottom right short press)') + e + .text('config_led_bottom_right_press', ea.ALL) + .withLabel('LED config (bottom right short press)') .withDescription(labelShortPress) .withCategory('config'), - e.text('config_led_top_left_longpress', ea.ALL).withLabel('LED config (top left long press)') + e + .text('config_led_top_left_longpress', ea.ALL) + .withLabel('LED config (top left long press)') .withDescription(labelLongPress) .withCategory('config'), - e.text('config_led_top_right_longpress', ea.ALL).withLabel('LED config (top right long press)') + e + .text('config_led_top_right_longpress', ea.ALL) + .withLabel('LED config (top right long press)') .withDescription(labelLongPress) .withCategory('config'), - e.text('config_led_bottom_left_longpress', ea.ALL).withLabel('LED config (bottom left long press)') + e + .text('config_led_bottom_left_longpress', ea.ALL) + .withLabel('LED config (bottom left long press)') .withDescription(labelLongPress) .withCategory('config'), - e.text('config_led_bottom_right_longpress', ea.ALL).withLabel('LED config (bottom right long press)') + e + .text('config_led_bottom_right_longpress', ea.ALL) + .withLabel('LED config (bottom right long press)') .withDescription(labelLongPress) .withCategory('config'), e.action([ @@ -1934,40 +2027,36 @@ const definitions: Definition[] = [ 'button_top_left_longpress_release', 'button_top_right_longpress_release', 'button_bottom_left_longpress_release', - 'button_bottom_right_longpress_release']), + 'button_bottom_right_longpress_release', + ]), ], extend: [ - deviceAddCustomCluster( - 'boschSpecific', - { - ID: 0xfca1, - manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, - attributes: {}, - commands: { - confirmButtonPressed: { - ID: 0x0010, - parameters: [ - {name: 'data', type: Zcl.BuffaloZclDataType.BUFFER}, - ], - }, - pairingCompleted: { - ID: 0x0012, - parameters: [ - {name: 'data', type: Zcl.BuffaloZclDataType.BUFFER}, - ], - }, + deviceAddCustomCluster('boschSpecific', { + ID: 0xfca1, + manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, + attributes: {}, + commands: { + confirmButtonPressed: { + ID: 0x0010, + parameters: [{name: 'data', type: Zcl.BuffaloZclDataType.BUFFER}], + }, + pairingCompleted: { + ID: 0x0012, + parameters: [{name: 'data', type: Zcl.BuffaloZclDataType.BUFFER}], }, - commandsResponse: {}, }, - ), + commandsResponse: {}, + }), ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); // Read default LED configuration - await endpoint.read('boschSpecific', [0x0010, 0x0011, 0x0012, 0x0013], {...manufacturerOptions, sendPolicy: 'immediate'}) + await endpoint + .read('boschSpecific', [0x0010, 0x0011, 0x0012, 0x0013], {...manufacturerOptions, sendPolicy: 'immediate'}) .catch((error) => {}); - await endpoint.read('boschSpecific', [0x0020, 0x0021, 0x0022, 0x0023], {...manufacturerOptions, sendPolicy: 'immediate'}) + await endpoint + .read('boschSpecific', [0x0020, 0x0021, 0x0022, 0x0023], {...manufacturerOptions, sendPolicy: 'immediate'}) .catch((error) => {}); // We also have to read this one. Value reads 0x0f, looks like a bitmap diff --git a/src/devices/bouffalo_lab.ts b/src/devices/bouffalo_lab.ts index daba7fe811087..9f410f7c7c00d 100644 --- a/src/devices/bouffalo_lab.ts +++ b/src/devices/bouffalo_lab.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {forcePowerSource, onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/brimate.ts b/src/devices/brimate.ts index 614d34200656d..82b4d99e57930 100644 --- a/src/devices/brimate.ts +++ b/src/devices/brimate.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/bseed.ts b/src/devices/bseed.ts index 66d17df115a2c..ed6af0759852c 100644 --- a/src/devices/bseed.ts +++ b/src/devices/bseed.ts @@ -1,12 +1,15 @@ -import {Definition} from '../lib/types'; import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; const definitions: Definition[] = [ { - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_yenbr4om'}, {modelID: 'TS0601', manufacturerName: '_TZE204_bdblidq3'}], + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE200_yenbr4om'}, + {modelID: 'TS0601', manufacturerName: '_TZE204_bdblidq3'}, + ], model: 'BSEED_TS0601_cover', vendor: 'BSEED', description: 'Zigbee curtain switch', diff --git a/src/devices/bticino.ts b/src/devices/bticino.ts index 7079627209865..ebe979ace3305 100644 --- a/src/devices/bticino.ts +++ b/src/devices/bticino.ts @@ -1,11 +1,11 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; -import * as ota from '../lib/ota'; +import * as exposes from '../lib/exposes'; import {fzLegrand, tzLegrand, eLegrand} from '../lib/legrand'; import {electricityMeter, light, onOff} from '../lib/modernExtend'; +import * as ota from '../lib/ota'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -18,13 +18,7 @@ const definitions: Definition[] = [ ota: ota.zigbeeOTA, fromZigbee: [fz.identify, fz.on_off, fz.K4003C_binary_input, fzLegrand.cluster_fc01], toZigbee: [tz.on_off, tzLegrand.led_mode, tzLegrand.identify], - exposes: [ - e.switch(), - e.action(['identify', 'on', 'off']), - eLegrand.identify(), - eLegrand.ledInDark(), - eLegrand.ledIfOn(), - ], + exposes: [e.switch(), e.action(['identify', 'on', 'off']), eLegrand.identify(), eLegrand.ledInDark(), eLegrand.ledIfOn()], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genIdentify', 'genOnOff', 'genBinaryInput']); @@ -39,12 +33,9 @@ const definitions: Definition[] = [ fromZigbee: [fz.identify, fz.lighting_ballast_configuration, fzLegrand.cluster_fc01], toZigbee: [tzLegrand.led_mode, tz.legrand_device_mode, tzLegrand.identify, tz.ballast_config], exposes: [ - e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254) - .withDescription('Specifies the minimum brightness value'), - e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254) - .withDescription('Specifies the maximum brightness value'), - e.binary('device_mode', ea.ALL, 'dimmer_on', 'dimmer_off') - .withDescription('Allow the device to change brightness'), + e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254).withDescription('Specifies the minimum brightness value'), + e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254).withDescription('Specifies the maximum brightness value'), + e.binary('device_mode', ea.ALL, 'dimmer_on', 'dimmer_off').withDescription('Allow the device to change brightness'), eLegrand.identify(), eLegrand.ledInDark(), eLegrand.ledIfOn(), @@ -60,7 +51,8 @@ const definitions: Definition[] = [ fromZigbee: [fz.identify, fzLegrand.cluster_fc01, fz.ignore_basic_report, fz.ignore_genOta], toZigbee: [tz.legrand_device_mode, tzLegrand.identify], exposes: [ - e.enum('device_mode', ea.ALL, ['switch', 'auto']) + e + .enum('device_mode', ea.ALL, ['switch', 'auto']) .withDescription('switch: allow on/off, auto will use wired action via C1/C2 on contactor for example with HC/HP'), ], }, @@ -71,13 +63,7 @@ const definitions: Definition[] = [ description: 'Power socket with power consumption monitoring', fromZigbee: [fz.identify, fz.on_off, fz.electrical_measurement, fzLegrand.cluster_fc01], toZigbee: [tz.on_off, tzLegrand.led_mode, tzLegrand.identify], - exposes: [ - e.switch(), - e.action(['identify']), - e.power(), - e.voltage(), - e.current(), - ], + exposes: [e.switch(), e.action(['identify']), e.power(), e.voltage(), e.current()], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genIdentify', 'genOnOff', 'haElectricalMeasurement']); diff --git a/src/devices/busch_jaeger.ts b/src/devices/busch_jaeger.ts index c522aca0f60f4..1e89d0ae1f40c 100644 --- a/src/devices/busch_jaeger.ts +++ b/src/devices/busch_jaeger.ts @@ -1,11 +1,11 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; -import * as globalStore from '../lib/store'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; import {onOff} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import * as globalStore from '../lib/store'; +import {Definition} from '../lib/types'; const e = exposes.presets; @@ -35,7 +35,7 @@ const definitions: Definition[] = [ vendor: 'Busch-Jaeger', description: 'Zigbee Light Link power supply/relay/dimmer/wall-switch', endpoint: (device) => { - return {'row_1': 0x0a, 'row_2': 0x0b, 'row_3': 0x0c, 'row_4': 0x0d, 'relay': 0x12}; + return {row_1: 0x0a, row_2: 0x0b, row_3: 0x0c, row_4: 0x0d, relay: 0x12}; }, exposes: (device, options) => { const expose = []; @@ -52,12 +52,30 @@ const definitions: Definition[] = [ } // Not all devices support all actions (depends on number of rocker rows and if relay/dimmer is installed), // but defining all possible actions here won't do any harm. - expose.push(e.action([ - 'row_1_on', 'row_1_off', 'row_1_up', 'row_1_down', 'row_1_stop', - 'row_2_on', 'row_2_off', 'row_2_up', 'row_2_down', 'row_2_stop', - 'row_3_on', 'row_3_off', 'row_3_up', 'row_3_down', 'row_3_stop', - 'row_4_on', 'row_4_off', 'row_4_up', 'row_4_down', 'row_4_stop', - ])); + expose.push( + e.action([ + 'row_1_on', + 'row_1_off', + 'row_1_up', + 'row_1_down', + 'row_1_stop', + 'row_2_on', + 'row_2_off', + 'row_2_up', + 'row_2_down', + 'row_2_stop', + 'row_3_on', + 'row_3_off', + 'row_3_up', + 'row_3_down', + 'row_3_stop', + 'row_4_on', + 'row_4_off', + 'row_4_up', + 'row_4_down', + 'row_4_stop', + ]), + ); expose.push(e.linkquality()); return expose; @@ -113,8 +131,16 @@ const definitions: Definition[] = [ await reporting.bind(endpoint13, coordinatorEndpoint, ['genLevelCtrl']); } }, - fromZigbee: [fz.ignore_basic_report, fz.on_off, fz.brightness, legacy.fz.RM01_on_click, legacy.fz.RM01_off_click, - legacy.fz.RM01_up_hold, legacy.fz.RM01_down_hold, legacy.fz.RM01_stop], + fromZigbee: [ + fz.ignore_basic_report, + fz.on_off, + fz.brightness, + legacy.fz.RM01_on_click, + legacy.fz.RM01_off_click, + legacy.fz.RM01_up_hold, + legacy.fz.RM01_down_hold, + legacy.fz.RM01_stop, + ], toZigbee: [tz.RM01_light_onoff_brightness, tz.RM01_light_brightness_step, tz.RM01_light_brightness_move], onEvent: async (type, data, device) => { const switchEndpoint = device.getEndpoint(0x12); diff --git a/src/devices/byun.ts b/src/devices/byun.ts index d831af1361e6c..d54c4d7e9dfe1 100644 --- a/src/devices/byun.ts +++ b/src/devices/byun.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/calex.ts b/src/devices/calex.ts index dfc293662fe54..6582e3a7af38a 100644 --- a/src/devices/calex.ts +++ b/src/devices/calex.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const e = exposes.presets; @@ -26,14 +26,28 @@ const definitions: Definition[] = [ vendor: 'Calex', description: 'Smart Wall Switch, wall mounted RGB controller', toZigbee: [], - fromZigbee: [fz.command_off, fz.command_on, fz.command_step, fz.command_move_to_color_temp, - fz.command_move, fz.command_stop, fz.command_ehanced_move_to_hue_and_saturation, + fromZigbee: [ + fz.command_off, + fz.command_on, + fz.command_step, + fz.command_move_to_color_temp, + fz.command_move, + fz.command_stop, + fz.command_ehanced_move_to_hue_and_saturation, + ], + exposes: [ + e.action([ + 'on', + 'off', + 'color_temperature_move', + 'brightness_step_up', + 'brightness_step_down', + 'brightness_move_up', + 'brightness_move_down', + 'brightness_stop', + 'enhanced_move_to_hue_and_saturation', + ]), ], - exposes: [e.action([ - 'on', 'off', 'color_temperature_move', 'brightness_step_up', 'brightness_step_down', - 'brightness_move_up', 'brightness_move_down', 'brightness_stop', - 'enhanced_move_to_hue_and_saturation', - ])], meta: {disableActionGroup: true}, }, ]; diff --git a/src/devices/candeo.ts b/src/devices/candeo.ts index 91337a78fe764..5ef94f40a6f05 100644 --- a/src/devices/candeo.ts +++ b/src/devices/candeo.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {electricityMeter, light, onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/casaia.ts b/src/devices/casaia.ts index 1174dad6b01a6..8a7573f28ee70 100644 --- a/src/devices/casaia.ts +++ b/src/devices/casaia.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; import tz from '../converters/toZigbee'; import {onOff} from '../lib/modernExtend'; diff --git a/src/devices/cel.ts b/src/devices/cel.ts index da6ab034b2694..d234d253f0228 100644 --- a/src/devices/cel.ts +++ b/src/devices/cel.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import fz from '../converters/fromZigbee'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/centralite.ts b/src/devices/centralite.ts index a5741877811e7..8ee4823ebc40f 100644 --- a/src/devices/centralite.ts +++ b/src/devices/centralite.ts @@ -1,10 +1,11 @@ import {Zcl} from 'zigbee-herdsman'; -import {Definition, Fz} from '../lib/types'; -import * as exposes from '../lib/exposes'; + import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as globalStore from '../lib/store'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import * as globalStore from '../lib/store'; +import {Definition, Fz} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; import * as constants from '../lib/constants'; @@ -131,7 +132,11 @@ const definitions: Definition[] = [ // For some this fails so set manually // https://github.com/Koenkk/zigbee2mqtt/issues/3575 endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', { - acCurrentDivisor: 1000, acCurrentMultiplier: 1, acPowerMultiplier: 1, acPowerDivisor: 10}); + acCurrentDivisor: 1000, + acCurrentMultiplier: 1, + acPowerMultiplier: 1, + acPowerDivisor: 10, + }); } await reporting.rmsVoltage(endpoint, {change: 2}); // Voltage reports in V await reporting.rmsCurrent(endpoint, {change: 10}); // Current reports in mA @@ -175,12 +180,15 @@ const definitions: Definition[] = [ meta: {battery: {voltageToPercentage: '3V_2100'}}, fromZigbee: [fz.command_arm_with_transaction, fz.temperature, fz.battery, fz.ias_ace_occupancy_with_timeout], toZigbee: [tz.arm_mode], - exposes: [e.battery(), e.temperature(), e.occupancy(), + exposes: [ + e.battery(), + e.temperature(), + e.occupancy(), e.numeric('action_code', ea.STATE).withDescription('Pin code introduced.'), e.numeric('action_transaction', ea.STATE).withDescription('Last action transaction number.'), e.text('action_zone', ea.STATE).withDescription('Alarm zone. Default value 0'), - e.action([ - 'disarm', 'arm_day_zones', 'arm_night_zones', 'arm_all_zones', 'exit_delay', 'emergency'])], + e.action(['disarm', 'arm_day_zones', 'arm_night_zones', 'arm_all_zones', 'exit_delay', 'emergency']), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const clusters = ['msTemperatureMeasurement', 'genPowerCfg', 'ssIasZone', 'ssIasAce']; @@ -189,15 +197,19 @@ const definitions: Definition[] = [ await reporting.batteryVoltage(endpoint); }, onEvent: async (type, data, device) => { - if (type === 'message' && data.type === 'commandGetPanelStatus' && data.cluster === 'ssIasAce' && - globalStore.hasValue(device.getEndpoint(1), 'panelStatus')) { + if ( + type === 'message' && + data.type === 'commandGetPanelStatus' && + data.cluster === 'ssIasAce' && + globalStore.hasValue(device.getEndpoint(1), 'panelStatus') + ) { const payload = { panelstatus: globalStore.getValue(device.getEndpoint(1), 'panelStatus'), - secondsremain: 0x00, audiblenotif: 0x00, alarmstatus: 0x00, + secondsremain: 0x00, + audiblenotif: 0x00, + alarmstatus: 0x00, }; - await device.getEndpoint(1).commandResponse( - 'ssIasAce', 'getPanelStatusRsp', payload, {}, data.meta.zclTransactionSequenceNumber, - ); + await device.getEndpoint(1).commandResponse('ssIasAce', 'getPanelStatusRsp', payload, {}, data.meta.zclTransactionSequenceNumber); } }, }, @@ -209,24 +221,43 @@ const definitions: Definition[] = [ extend: [light()], }, { - fingerprint: [{modelID: '3157100', manufacturerName: 'Centralite'}, {modelID: '3157100-E', manufacturerName: 'Centralite'}], + fingerprint: [ + {modelID: '3157100', manufacturerName: 'Centralite'}, + {modelID: '3157100-E', manufacturerName: 'Centralite'}, + ], model: '3157100', vendor: 'Centralite', description: '3-Series pearl touch thermostat', fromZigbee: [fz.battery, fz.thermostat, fz.fan, fz.ignore_time_read], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_local_temperature_calibration, - tz.thermostat_occupied_heating_setpoint, tz.thermostat_occupied_cooling_setpoint, - tz.thermostat_setpoint_raise_lower, tz.thermostat_remote_sensing, - tz.thermostat_control_sequence_of_operation, tz.thermostat_system_mode, - tz.thermostat_relay_status_log, tz.fan_mode, tz.thermostat_running_state, tz.thermostat_temperature_setpoint_hold], - exposes: [e.battery(), - e.binary('temperature_setpoint_hold', ea.ALL, true, false) + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_local_temperature_calibration, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_occupied_cooling_setpoint, + tz.thermostat_setpoint_raise_lower, + tz.thermostat_remote_sensing, + tz.thermostat_control_sequence_of_operation, + tz.thermostat_system_mode, + tz.thermostat_relay_status_log, + tz.fan_mode, + tz.thermostat_running_state, + tz.thermostat_temperature_setpoint_hold, + ], + exposes: [ + e.battery(), + e + .binary('temperature_setpoint_hold', ea.ALL, true, false) .withDescription('Prevent changes. `false` = run normally. `true` = prevent from making changes.'), - e.climate().withSetpoint('occupied_heating_setpoint', 7, 30, 1).withLocalTemperature() + e + .climate() + .withSetpoint('occupied_heating_setpoint', 7, 30, 1) + .withLocalTemperature() .withSystemMode(['off', 'heat', 'cool', 'emergency_heating']) - .withRunningState(['idle', 'heat', 'cool', 'fan_only']).withFanMode(['auto', 'on']) + .withRunningState(['idle', 'heat', 'cool', 'fan_only']) + .withFanMode(['auto', 'on']) .withSetpoint('occupied_cooling_setpoint', 7, 30, 1) - .withLocalTemperatureCalibration(-2.5, 2.5, 0.1)], + .withLocalTemperatureCalibration(-2.5, 2.5, 0.1), + ], meta: {battery: {voltageToPercentage: '3V_1500_2800'}}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -244,18 +275,33 @@ const definitions: Definition[] = [ vendor: 'Centralite', description: 'HA thermostat', fromZigbee: [fz.battery, fzLocal.thermostat_3156105, fz.fan, fz.ignore_time_read], - toZigbee: [tz.thermostat_local_temperature, - tz.thermostat_occupied_heating_setpoint, tz.thermostat_occupied_cooling_setpoint, - tz.thermostat_setpoint_raise_lower, tz.thermostat_remote_sensing, - tz.thermostat_control_sequence_of_operation, tz.thermostat_system_mode, - tz.thermostat_relay_status_log, tz.fan_mode, tz.thermostat_running_state, tz.thermostat_temperature_setpoint_hold], - exposes: [e.battery(), - e.binary('temperature_setpoint_hold', ea.ALL, true, false) + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_occupied_cooling_setpoint, + tz.thermostat_setpoint_raise_lower, + tz.thermostat_remote_sensing, + tz.thermostat_control_sequence_of_operation, + tz.thermostat_system_mode, + tz.thermostat_relay_status_log, + tz.fan_mode, + tz.thermostat_running_state, + tz.thermostat_temperature_setpoint_hold, + ], + exposes: [ + e.battery(), + e + .binary('temperature_setpoint_hold', ea.ALL, true, false) .withDescription('Prevent changes. `false` = run normally. `true` = prevent from making changes.'), - e.climate().withSetpoint('occupied_heating_setpoint', 7, 30, 1).withLocalTemperature() + e + .climate() + .withSetpoint('occupied_heating_setpoint', 7, 30, 1) + .withLocalTemperature() .withSystemMode(['off', 'heat', 'cool', 'emergency_heating']) - .withRunningState(['idle', 'heat', 'cool', 'fan_only']).withFanMode(['auto', 'on']) - .withSetpoint('occupied_cooling_setpoint', 7, 30, 1)], + .withRunningState(['idle', 'heat', 'cool', 'fan_only']) + .withFanMode(['auto', 'on']) + .withSetpoint('occupied_cooling_setpoint', 7, 30, 1), + ], meta: {battery: {voltageToPercentage: '3V_1500_2800'}}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -289,17 +335,17 @@ const definitions: Definition[] = [ await reporting.bind(endpoint, coordinatorEndpoint, binds); await reporting.temperature(endpoint); - const payload = [{ - attribute: 'measuredValue', - minimumReportInterval: 10, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 10, - }]; - await endpoint.configureReporting( - 'manuSpecificCentraliteHumidity', - payload, - {manufacturerCode: Zcl.ManufacturerCode.CENTRALITE_SYSTEMS_INC}, - ); + const payload = [ + { + attribute: 'measuredValue', + minimumReportInterval: 10, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 10, + }, + ]; + await endpoint.configureReporting('manuSpecificCentraliteHumidity', payload, { + manufacturerCode: Zcl.ManufacturerCode.CENTRALITE_SYSTEMS_INC, + }); await reporting.batteryVoltage(endpoint); }, diff --git a/src/devices/cleode.ts b/src/devices/cleode.ts index f41c7847ef007..198573e6579c9 100644 --- a/src/devices/cleode.ts +++ b/src/devices/cleode.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {electricityMeter, onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/cleverio.ts b/src/devices/cleverio.ts index cc8ae4f2fa4e7..12e8e5e7ef3c9 100644 --- a/src/devices/cleverio.ts +++ b/src/devices/cleverio.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; import * as tuya from '../lib/tuya'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -15,8 +15,11 @@ const definitions: Definition[] = [ description: 'Smart siren', fromZigbee: [fz.ts0216_siren, fz.ias_alarm_only_alarm_1, fz.power_source], toZigbee: [tz.warning, tz.ts0216_volume], - exposes: [e.warning(), e.binary('alarm', ea.STATE, true, false), - e.numeric('volume', ea.ALL).withValueMin(0).withValueMax(100).withDescription('Volume of siren')], + exposes: [ + e.warning(), + e.binary('alarm', ea.STATE, true, false), + e.numeric('volume', ea.ALL).withValueMin(0).withValueMax(100).withDescription('Volume of siren'), + ], meta: {disableDefaultResponse: true}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); diff --git a/src/devices/climax.ts b/src/devices/climax.ts index 980dcf1f3caaa..5821af4785240 100644 --- a/src/devices/climax.ts +++ b/src/devices/climax.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; import {forcePowerSource, identify, onOff} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -24,7 +24,6 @@ const definitions: Definition[] = [ fromZigbee: [fz.ias_smoke_alarm_1, fz.battery], toZigbee: [tz.warning], exposes: [e.smoke(), e.battery(), e.battery_low(), e.tamper(), e.warning()], - }, { zigbeeModel: ['WS15_00.00.00.10TC'], @@ -45,8 +44,15 @@ const definitions: Definition[] = [ exposes: [e.cover_position().setAccess('state', ea.ALL)], }, { - zigbeeModel: ['PSM_00.00.00.35TC', 'PSMP5_00.00.02.02TC', 'PSMP5_00.00.05.01TC', 'PSMP5_00.00.05.10TC', 'PSMP5_00.00.03.15TC', - 'PSMP5_00.00.03.16TC', 'PSMP5_00.00.03.19TC'], + zigbeeModel: [ + 'PSM_00.00.00.35TC', + 'PSMP5_00.00.02.02TC', + 'PSMP5_00.00.05.01TC', + 'PSMP5_00.00.05.10TC', + 'PSMP5_00.00.03.15TC', + 'PSMP5_00.00.03.16TC', + 'PSMP5_00.00.03.19TC', + ], model: 'PSM-29ZBSR', vendor: 'Climax', description: 'Power plug', @@ -91,9 +97,14 @@ const definitions: Definition[] = [ await endpoint.read('ssIasZone', ['zoneState', 'iasCieAddr', 'zoneId']); await endpoint.read('ssIasWd', ['maxDuration']); }, - exposes: [e.battery_low(), e.tamper(), e.warning(), e.squawk(), + exposes: [ + e.battery_low(), + e.tamper(), + e.warning(), + e.squawk(), e.numeric('max_duration', ea.ALL).withUnit('s').withValueMin(0).withValueMax(600).withDescription('Duration of Siren'), - e.binary('alarm', ea.SET, 'START', 'OFF').withDescription('Manual start of siren')], + e.binary('alarm', ea.SET, 'START', 'OFF').withDescription('Manual start of siren'), + ], }, { zigbeeModel: ['WS15_00.00.00.14TC'], @@ -120,8 +131,7 @@ const definitions: Definition[] = [ description: 'Remote Keypad', fromZigbee: [fz.ias_keypad, fz.battery, fz.command_arm, fz.command_panic, fz.command_emergency], toZigbee: [], - exposes: [e.battery_low(), e.tamper(), e.action(['emergency', 'panic', 'disarm', 'arm_all_zones', 'arm_day_zones']), - ], + exposes: [e.battery_low(), e.tamper(), e.action(['emergency', 'panic', 'disarm', 'arm_all_zones', 'arm_day_zones'])], }, { zigbeeModel: ['PRL_00.00.03.04TC'], diff --git a/src/devices/commercial_electric.ts b/src/devices/commercial_electric.ts index f141741433d8e..8326033572d9f 100644 --- a/src/devices/commercial_electric.ts +++ b/src/devices/commercial_electric.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {forcePowerSource, light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/cree.ts b/src/devices/cree.ts index 3cb892863f57b..2f4b574459f10 100644 --- a/src/devices/cree.ts +++ b/src/devices/cree.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/ctm.ts b/src/devices/ctm.ts index 5622808127182..fb734055b8146 100644 --- a/src/devices/ctm.ts +++ b/src/devices/ctm.ts @@ -1,13 +1,14 @@ import {Zcl} from 'zigbee-herdsman'; + import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as constants from '../lib/constants'; import * as exposes from '../lib/exposes'; -import {KeyValue, Definition, Tz, Fz} from '../lib/types'; +import {battery, temperature, identify, light} from '../lib/modernExtend'; +import * as ota from '../lib/ota'; import * as reporting from '../lib/reporting'; -import * as constants from '../lib/constants'; +import {KeyValue, Definition, Tz, Fz} from '../lib/types'; import * as utils from '../lib/utils'; -import * as ota from '../lib/ota'; -import {battery, temperature, identify, light} from '../lib/modernExtend'; const e = exposes.presets; const ea = exposes.access; @@ -97,118 +98,167 @@ const fzLocal = { convert: (model, msg, publish, options, meta) => { const result: KeyValue = {}; const data = msg.data; - if (data.hasOwnProperty(0x0401)) { // Load + if (data.hasOwnProperty(0x0401)) { + // Load result.load = data[0x0401]; } - if (data.hasOwnProperty('elkoLoad')) { // Load + if (data.hasOwnProperty('elkoLoad')) { + // Load result.load = data['elkoLoad']; } - if (data.hasOwnProperty(0x0402)) { // Display text + if (data.hasOwnProperty(0x0402)) { + // Display text result.display_text = data[0x0402]; } - if (data.hasOwnProperty('elkoDisplayText')) { // Display text + if (data.hasOwnProperty('elkoDisplayText')) { + // Display text result.display_text = data['elkoDisplayText']; } - if (data.hasOwnProperty(0x0403)) { // Sensor + if (data.hasOwnProperty(0x0403)) { + // Sensor const sensorModeLookup = { - 0: 'air', 1: 'floor', 2: 'external', 3: 'regulator', 4: 'mv_air', 5: 'mv_external', 6: 'mv_regulator'}; + 0: 'air', + 1: 'floor', + 2: 'external', + 3: 'regulator', + 4: 'mv_air', + 5: 'mv_external', + 6: 'mv_regulator', + }; result.sensor = utils.getFromLookup(data[0x0403], sensorModeLookup); } - if (data.hasOwnProperty('elkoSensor')) { // Sensor + if (data.hasOwnProperty('elkoSensor')) { + // Sensor const sensorModeLookup = { - 0: 'air', 1: 'floor', 2: 'external', 3: 'regulator', 4: 'mv_air', 5: 'mv_external', 6: 'mv_regulator'}; + 0: 'air', + 1: 'floor', + 2: 'external', + 3: 'regulator', + 4: 'mv_air', + 5: 'mv_external', + 6: 'mv_regulator', + }; result.sensor = utils.getFromLookup(data['elkoSensor'], sensorModeLookup); } - if (data.hasOwnProperty(0x0405)) { // Regulator mode + if (data.hasOwnProperty(0x0405)) { + // Regulator mode result.regulator_mode = data[0x0405] ? 'regulator' : 'thermostat'; } - if (data.hasOwnProperty('elkoRegulatorMode')) { // Regulator mode + if (data.hasOwnProperty('elkoRegulatorMode')) { + // Regulator mode result.regulator_mode = data['elkoRegulatorMode'] ? 'regulator' : 'thermostat'; } - if (data.hasOwnProperty(0x0406)) { // Power status + if (data.hasOwnProperty(0x0406)) { + // Power status result.power_status = data[0x0406] ? 'ON' : 'OFF'; } - if (data.hasOwnProperty('elkoPowerStatus')) { // Power status + if (data.hasOwnProperty('elkoPowerStatus')) { + // Power status result.power_status = data['elkoPowerStatus'] ? 'ON' : 'OFF'; } - if (data.hasOwnProperty(0x0408)) { // Mean power + if (data.hasOwnProperty(0x0408)) { + // Mean power result.mean_power = data[0x0408]; } - if (data.hasOwnProperty('elkoMeanPower')) { // Mean power + if (data.hasOwnProperty('elkoMeanPower')) { + // Mean power result.mean_power = data['elkoMeanPower']; } - if (data.hasOwnProperty(0x0409)) { // Floor temp - result.floor_temp = utils.precisionRound(data[0x0409], 2) /100; + if (data.hasOwnProperty(0x0409)) { + // Floor temp + result.floor_temp = utils.precisionRound(data[0x0409], 2) / 100; } - if (data.hasOwnProperty('elkoExternalTemp')) { // External temp (floor) - result.floor_temp = utils.precisionRound(data['elkoExternalTemp'], 2) /100; + if (data.hasOwnProperty('elkoExternalTemp')) { + // External temp (floor) + result.floor_temp = utils.precisionRound(data['elkoExternalTemp'], 2) / 100; } - if (data.hasOwnProperty(0x0411)) { // Night switching + if (data.hasOwnProperty(0x0411)) { + // Night switching result.night_switching = data[0x0411] ? 'ON' : 'OFF'; } - if (data.hasOwnProperty('elkoNightSwitching')) { // Night switching + if (data.hasOwnProperty('elkoNightSwitching')) { + // Night switching result.night_switching = data['elkoNightSwitching'] ? 'ON' : 'OFF'; } - if (data.hasOwnProperty(0x0412)) { // Frost guard + if (data.hasOwnProperty(0x0412)) { + // Frost guard result.frost_guard = data[0x0412] ? 'ON' : 'OFF'; } - if (data.hasOwnProperty('elkoFrostGuard')) { // Frost guard + if (data.hasOwnProperty('elkoFrostGuard')) { + // Frost guard result.frost_guard = data['elkoFrostGuard'] ? 'ON' : 'OFF'; } - if (data.hasOwnProperty(0x0413)) { // Child lock + if (data.hasOwnProperty(0x0413)) { + // Child lock result.child_lock = data[0x0413] ? 'LOCK' : 'UNLOCK'; } - if (data.hasOwnProperty('elkoChildLock')) { // Child lock + if (data.hasOwnProperty('elkoChildLock')) { + // Child lock result.child_lock = data['elkoChildLock'] ? 'LOCK' : 'UNLOCK'; } - if (data.hasOwnProperty(0x0414)) { // Max floor temp + if (data.hasOwnProperty(0x0414)) { + // Max floor temp result.max_floor_temp = data[0x0414]; } - if (data.hasOwnProperty('elkoMaxFloorTemp')) { // Max floor temp + if (data.hasOwnProperty('elkoMaxFloorTemp')) { + // Max floor temp result.max_floor_temp = data['elkoMaxFloorTemp']; } - if (data.hasOwnProperty(0x0415)) { // Running_state + if (data.hasOwnProperty(0x0415)) { + // Running_state result.running_state = data[0x0415] ? 'heat' : 'idle'; } - if (data.hasOwnProperty('elkoRelayState')) { // Running_state + if (data.hasOwnProperty('elkoRelayState')) { + // Running_state result.running_state = data['elkoRelayState'] ? 'heat' : 'idle'; } - if (data.hasOwnProperty(0x0420)) { // Regulator setpoint + if (data.hasOwnProperty(0x0420)) { + // Regulator setpoint result.regulator_setpoint = data[0x0420]; } - if (data.hasOwnProperty(0x0421)) { // Regulation mode + if (data.hasOwnProperty(0x0421)) { + // Regulation mode const regulationModeLookup = {0: 'thermostat', 1: 'regulator', 2: 'zzilent'}; - result.regulation_mode= utils.getFromLookup(data[0x0421], regulationModeLookup); + result.regulation_mode = utils.getFromLookup(data[0x0421], regulationModeLookup); } - if (data.hasOwnProperty(0x0422)) { // Operation mode + if (data.hasOwnProperty(0x0422)) { + // Operation mode const presetLookup = {0: 'off', 1: 'away', 2: 'sleep', 3: 'home'}; const systemModeLookup = {0: 'off', 1: 'off', 2: 'off', 3: 'heat'}; result.preset = utils.getFromLookup(data[0x0422], presetLookup); result.system_mode = utils.getFromLookup(data[0x0422], systemModeLookup); } - if (data.hasOwnProperty(0x0423)) { // Maximum floor temp guard + if (data.hasOwnProperty(0x0423)) { + // Maximum floor temp guard result.max_floor_guard = data[0x0423] ? 'ON' : 'OFF'; } - if (data.hasOwnProperty(0x0424)) { // Weekly timer enabled + if (data.hasOwnProperty(0x0424)) { + // Weekly timer enabled result.weekly_timer = data[0x0424] ? 'ON' : 'OFF'; } - if (data.hasOwnProperty(0x0425)) { // Frost guard setpoint + if (data.hasOwnProperty(0x0425)) { + // Frost guard setpoint result.frost_guard_setpoint = data[0x0425]; } - if (data.hasOwnProperty(0x0426)) { // External temperature - result.external_temp = utils.precisionRound(data[0x0426], 2) /100; + if (data.hasOwnProperty(0x0426)) { + // External temperature + result.external_temp = utils.precisionRound(data[0x0426], 2) / 100; } - if (data.hasOwnProperty(0x0428)) { // External sensor source + if (data.hasOwnProperty(0x0428)) { + // External sensor source result.exteral_sensor_source = data[0x0428]; } - if (data.hasOwnProperty(0x0429)) { // Current air temperature - result.air_temp = utils.precisionRound(data[0x0429], 2) /100; + if (data.hasOwnProperty(0x0429)) { + // Current air temperature + result.air_temp = utils.precisionRound(data[0x0429], 2) / 100; } - if (data.hasOwnProperty(0x0424)) { // Floor Sensor Error - result.floor_sensor_error = data[0x042B] ? 'error' : 'ok'; + if (data.hasOwnProperty(0x0424)) { + // Floor Sensor Error + result.floor_sensor_error = data[0x042b] ? 'error' : 'ok'; } - if (data.hasOwnProperty(0x0424)) { // External Air Sensor Error - result.exteral_sensor_error = data[0x042C] ? 'error' : 'ok'; + if (data.hasOwnProperty(0x0424)) { + // External Air Sensor Error + result.exteral_sensor_error = data[0x042c] ? 'error' : 'ok'; } return result; @@ -233,76 +283,117 @@ const fzLocal = { convert: (model, msg, publish, options, meta) => { const result: KeyValue = {}; const data = msg.data; - if (data.hasOwnProperty(0x0001)) { // Alarm status + if (data.hasOwnProperty(0x0001)) { + // Alarm status const alarmStatusLookup = { - 0: 'ok', 1: 'tamper', 2: 'high_temperatur', 3: 'timer', 4: 'battery_alarm', 5: 'error', 0xFF: 'unknown'}; + 0: 'ok', + 1: 'tamper', + 2: 'high_temperatur', + 3: 'timer', + 4: 'battery_alarm', + 5: 'error', + 0xff: 'unknown', + }; result.alarm_status = utils.getFromLookup(data[0x0001], alarmStatusLookup); } - if (data.hasOwnProperty(0x0002)) { // Change battery + if (data.hasOwnProperty(0x0002)) { + // Change battery result.battery_low = data[0x0002] ? true : false; } - if (data.hasOwnProperty(0x0003)) { // Stove temperature + if (data.hasOwnProperty(0x0003)) { + // Stove temperature result.stove_temperature = data[0x0003]; } - if (data.hasOwnProperty(0x0004)) { // Ambient temperature + if (data.hasOwnProperty(0x0004)) { + // Ambient temperature result.ambient_temperature = data[0x0004]; } - if (data.hasOwnProperty(0x0005)) { // Active + if (data.hasOwnProperty(0x0005)) { + // Active result.active = data[0x0005] ? true : false; } - if (data.hasOwnProperty(0x0006)) { // Runtime + if (data.hasOwnProperty(0x0006)) { + // Runtime result.runtime = data[0x0006]; } - if (data.hasOwnProperty(0x0007)) { // Runtime timeout + if (data.hasOwnProperty(0x0007)) { + // Runtime timeout result.runtime_timeout = data[0x0007]; } - if (data.hasOwnProperty(0x0008)) { // Reset reason + if (data.hasOwnProperty(0x0008)) { + // Reset reason const resetReasonLookup = { - 0: 'unknown', 1: 'power_on', 2: 'external', 3: 'brown_out', 4: 'watchdog', 5: 'program_interface', - 6: 'software', 0xFF: 'unknown'}; + 0: 'unknown', + 1: 'power_on', + 2: 'external', + 3: 'brown_out', + 4: 'watchdog', + 5: 'program_interface', + 6: 'software', + 0xff: 'unknown', + }; result.reset_reason = utils.getFromLookup(data[0x0008], resetReasonLookup); } - if (data.hasOwnProperty(0x0009)) { // Dip switch + if (data.hasOwnProperty(0x0009)) { + // Dip switch result.dip_switch = data[0x0009]; } - if (data.hasOwnProperty(0x000A)) { // Software version - result.sw_version = data[0x000A]; + if (data.hasOwnProperty(0x000a)) { + // Software version + result.sw_version = data[0x000a]; } - if (data.hasOwnProperty(0x000B)) { // Hardware version - result.hw_version = data[0x000B]; + if (data.hasOwnProperty(0x000b)) { + // Hardware version + result.hw_version = data[0x000b]; } - if (data.hasOwnProperty(0x000C)) { // Bootloader version - result.bootloader_version = data[0x000C]; + if (data.hasOwnProperty(0x000c)) { + // Bootloader version + result.bootloader_version = data[0x000c]; } - if (data.hasOwnProperty(0x000D)) { // Model - const modelLookup = {0: 'unknown', 1: '1_8', 2: 'infinity', 3: 'hybrid', 4: 'tak', 0xFF: 'unknown'}; - result.model = utils.getFromLookup(data[0x000D], modelLookup); + if (data.hasOwnProperty(0x000d)) { + // Model + const modelLookup = {0: 'unknown', 1: '1_8', 2: 'infinity', 3: 'hybrid', 4: 'tak', 0xff: 'unknown'}; + result.model = utils.getFromLookup(data[0x000d], modelLookup); } - if (data.hasOwnProperty(0x0010)) { // Relay address + if (data.hasOwnProperty(0x0010)) { + // Relay address result.relay_address = data[0x0010]; } - if (data.hasOwnProperty(0x0100)) { // Relay current flag - const currentFlagLookup = {0: 'false', 1: 'true', 0xFF: 'unknown'}; + if (data.hasOwnProperty(0x0100)) { + // Relay current flag + const currentFlagLookup = {0: 'false', 1: 'true', 0xff: 'unknown'}; result.current_flag = utils.getFromLookup(data[0x0100], currentFlagLookup); } - if (data.hasOwnProperty(0x0101)) { // Relay current + if (data.hasOwnProperty(0x0101)) { + // Relay current result.relay_current = data[0x0101]; } - if (data.hasOwnProperty(0x0102)) { // Relay status - const relayStatusLookup = {0: 'off', 1: 'on', 2: 'not_present', 0xFF: 'unknown'}; + if (data.hasOwnProperty(0x0102)) { + // Relay status + const relayStatusLookup = {0: 'off', 1: 'on', 2: 'not_present', 0xff: 'unknown'}; result.relay_status = utils.getFromLookup(data[0x0102], relayStatusLookup); } - if (data.hasOwnProperty(0x0103)) { // Relay external button - const relayStatusLookup = {0: 'not_clicked', 1: 'clicked', 0xFF: 'unknown'}; + if (data.hasOwnProperty(0x0103)) { + // Relay external button + const relayStatusLookup = {0: 'not_clicked', 1: 'clicked', 0xff: 'unknown'}; result.external_button = utils.getFromLookup(data[0x0103], relayStatusLookup); } - if (data.hasOwnProperty(0x0104)) { // Relay alarm - const relayAlarmLookup = {0: 'ok', 1: 'no_communication', 2: 'over_current', 3: 'over_temperature', 0xFF: 'unknown'}; + if (data.hasOwnProperty(0x0104)) { + // Relay alarm + const relayAlarmLookup = {0: 'ok', 1: 'no_communication', 2: 'over_current', 3: 'over_temperature', 0xff: 'unknown'}; result.relay_alarm = utils.getFromLookup(data[0x0104], relayAlarmLookup); } - if (data.hasOwnProperty(0x0105)) { // Alarm status (from relay) + if (data.hasOwnProperty(0x0105)) { + // Alarm status (from relay) const relayAlarmStatusLookup = { - 0: 'ok', 1: 'tamper', 2: 'high_temperatur', 3: 'timer', 4: 'battery_alarm', 5: 'error', 0xFF: 'unknown'}; + 0: 'ok', + 1: 'tamper', + 2: 'high_temperatur', + 3: 'timer', + 4: 'battery_alarm', + 5: 'error', + 0xff: 'unknown', + }; result.relay_alarm_status = utils.getFromLookup(data[0x0105], relayAlarmStatusLookup); } @@ -316,14 +407,13 @@ const fzLocal = { const zoneStatus = msg.data.zonestatus; return { active_water_leak: (zoneStatus & 1) > 0, - water_leak: (zoneStatus & 1<<1) > 0, - battery_low: (zoneStatus & 1<<3) > 0, + water_leak: (zoneStatus & (1 << 1)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, }; }, } satisfies Fz.Converter, }; - const tzLocal = { ctm_mbd_device_enabled: { key: ['device_enabled'], @@ -338,8 +428,7 @@ const tzLocal = { ctm_mbd_brightness: { key: ['brightness'], convertSet: async (entity, key, value, meta) => { - await entity.command( - 'genLevelCtrl', 'moveToLevel', {level: value, transtime: 1}, utils.getOptions(meta.mapped, entity)); + await entity.command('genLevelCtrl', 'moveToLevel', {level: value, transtime: 1}, utils.getOptions(meta.mapped, entity)); }, convertGet: async (entity, key, meta) => { await entity.read('genLevelCtrl', ['currentLevel']); @@ -354,7 +443,7 @@ const tzLocal = { ctm_device_enabled: { key: ['device_enabled'], convertSet: async (entity, key, value, meta) => { - await entity.write('genOnOff', {0x2201: {value: utils.getFromLookup(value, {'OFF': 0, 'ON': 1}), type: Zcl.DataType.BOOLEAN}}); + await entity.write('genOnOff', {0x2201: {value: utils.getFromLookup(value, {OFF: 0, ON: 1}), type: Zcl.DataType.BOOLEAN}}); }, convertGet: async (entity, key, meta) => { await entity.read('genOnOff', [0x2201]); @@ -377,7 +466,7 @@ const tzLocal = { convertSet: async (entity, key, value, meta) => { await entity.write( 'genOnOff', - {0x5001: {value: utils.getFromLookup(value, {'OFF': 0, 'ON': 1}), type: Zcl.DataType.BOOLEAN}}, + {0x5001: {value: utils.getFromLookup(value, {OFF: 0, ON: 1}), type: Zcl.DataType.BOOLEAN}}, {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}, ); }, @@ -386,244 +475,295 @@ const tzLocal = { }, } satisfies Tz.Converter, ctm_thermostat: { - key: ['load', 'display_text', 'sensor', 'regulator_mode', 'power_status', 'system_mode', 'night_switching', 'frost_guard', - 'max_floor_temp', 'regulator_setpoint', 'regulation_mode', 'max_floor_guard', 'weekly_timer', 'exteral_sensor_source', + key: [ + 'load', + 'display_text', + 'sensor', + 'regulator_mode', + 'power_status', + 'system_mode', + 'night_switching', + 'frost_guard', + 'max_floor_temp', + 'regulator_setpoint', + 'regulation_mode', + 'max_floor_guard', + 'weekly_timer', + 'exteral_sensor_source', ], convertSet: async (entity, key, value, meta) => { switch (key) { - case 'load': - await entity.write('hvacThermostat', {0x0401: {value: value, type: Zcl.DataType.UINT16}}); - break; - case 'display_text': - await entity.write('hvacThermostat', {0x0402: {value: value, type: Zcl.DataType.CHAR_STR}}); - break; - case 'sensor': - await entity.write('hvacThermostat', {0x0403: { - value: utils.getFromLookup(value, - {'air': 0, 'floor': 1, 'external': 2, 'regulator': 3, 'mv_air': 4, 'mv_external': 5, 'mv_regulator': 6}), - type: Zcl.DataType.ENUM8}}); - break; - case 'regulator_mode': - await entity.write('hvacThermostat', {0x0405: - {value: utils.getFromLookup(value, {'thermostat': 0, 'regulator': 1}), type: Zcl.DataType.BOOLEAN}}); - break; - case 'power_status': - await entity.write('hvacThermostat', {0x0406: {value: utils.getFromLookup(value, {'OFF': 0, 'ON': 1}), type: Zcl.DataType.BOOLEAN}}); - break; - case 'system_mode': - if (value === 'off') { - await entity.write('hvacThermostat', {0x0406: {value: 0, type: Zcl.DataType.BOOLEAN}}); - } else if (value === 'heat') { - await entity.write('hvacThermostat', {0x0422: {value: 3, type: Zcl.DataType.UINT8}}); - } - break; - case 'night_switching': - await entity.write('hvacThermostat', {0x0411: {value: utils.getFromLookup(value, {'OFF': 0, 'ON': 1}), type: Zcl.DataType.BOOLEAN}}); - break; - case 'frost_guard': - await entity.write('hvacThermostat', {0x0412: {value: utils.getFromLookup(value, {'OFF': 0, 'ON': 1}), type: Zcl.DataType.BOOLEAN}}); - break; - case 'max_floor_temp': - await entity.write('hvacThermostat', {0x0414: {value: value, type: Zcl.DataType.UINT8}}); - break; - case 'regulator_setpoint': - await entity.write('hvacThermostat', {0x0420: {value: value, type: Zcl.DataType.UINT8}}); - break; - case 'regulation_mode': - await entity.write('hvacThermostat', {0x0421: { - value: utils.getFromLookup(value, {'thermostat': 0, 'regulator': 1, 'zzilent': 2}), - type: Zcl.DataType.UINT8}}); - break; - case 'max_floor_guard': - await entity.write('hvacThermostat', {0x0423: {value: utils.getFromLookup(value, {'OFF': 0, 'ON': 1}), type: Zcl.DataType.BOOLEAN}}); - break; - case 'weekly_timer': - await entity.write('hvacThermostat', {0x0424: {value: utils.getFromLookup(value, {'OFF': 0, 'ON': 1}), type: Zcl.DataType.BOOLEAN}}); - break; - case 'exteral_sensor_source': - await entity.write('hvacThermostat', {0x0428: {value: value, type: Zcl.DataType.UINT16}}); - break; + case 'load': + await entity.write('hvacThermostat', {0x0401: {value: value, type: Zcl.DataType.UINT16}}); + break; + case 'display_text': + await entity.write('hvacThermostat', {0x0402: {value: value, type: Zcl.DataType.CHAR_STR}}); + break; + case 'sensor': + await entity.write('hvacThermostat', { + 0x0403: { + value: utils.getFromLookup(value, { + air: 0, + floor: 1, + external: 2, + regulator: 3, + mv_air: 4, + mv_external: 5, + mv_regulator: 6, + }), + type: Zcl.DataType.ENUM8, + }, + }); + break; + case 'regulator_mode': + await entity.write('hvacThermostat', { + 0x0405: {value: utils.getFromLookup(value, {thermostat: 0, regulator: 1}), type: Zcl.DataType.BOOLEAN}, + }); + break; + case 'power_status': + await entity.write('hvacThermostat', {0x0406: {value: utils.getFromLookup(value, {OFF: 0, ON: 1}), type: Zcl.DataType.BOOLEAN}}); + break; + case 'system_mode': + if (value === 'off') { + await entity.write('hvacThermostat', {0x0406: {value: 0, type: Zcl.DataType.BOOLEAN}}); + } else if (value === 'heat') { + await entity.write('hvacThermostat', {0x0422: {value: 3, type: Zcl.DataType.UINT8}}); + } + break; + case 'night_switching': + await entity.write('hvacThermostat', {0x0411: {value: utils.getFromLookup(value, {OFF: 0, ON: 1}), type: Zcl.DataType.BOOLEAN}}); + break; + case 'frost_guard': + await entity.write('hvacThermostat', {0x0412: {value: utils.getFromLookup(value, {OFF: 0, ON: 1}), type: Zcl.DataType.BOOLEAN}}); + break; + case 'max_floor_temp': + await entity.write('hvacThermostat', {0x0414: {value: value, type: Zcl.DataType.UINT8}}); + break; + case 'regulator_setpoint': + await entity.write('hvacThermostat', {0x0420: {value: value, type: Zcl.DataType.UINT8}}); + break; + case 'regulation_mode': + await entity.write('hvacThermostat', { + 0x0421: { + value: utils.getFromLookup(value, {thermostat: 0, regulator: 1, zzilent: 2}), + type: Zcl.DataType.UINT8, + }, + }); + break; + case 'max_floor_guard': + await entity.write('hvacThermostat', {0x0423: {value: utils.getFromLookup(value, {OFF: 0, ON: 1}), type: Zcl.DataType.BOOLEAN}}); + break; + case 'weekly_timer': + await entity.write('hvacThermostat', {0x0424: {value: utils.getFromLookup(value, {OFF: 0, ON: 1}), type: Zcl.DataType.BOOLEAN}}); + break; + case 'exteral_sensor_source': + await entity.write('hvacThermostat', {0x0428: {value: value, type: Zcl.DataType.UINT16}}); + break; - default: // Unknown key - throw new Error(`Unhandled key tzLocal.ctm_thermostat.convertSet ${key}`); + default: // Unknown key + throw new Error(`Unhandled key tzLocal.ctm_thermostat.convertSet ${key}`); } }, convertGet: async (entity, key, meta) => { switch (key) { - case 'load': - await entity.read('hvacThermostat', [0x0401]); - break; - case 'display_text': - await entity.read('hvacThermostat', [0x0402]); - break; - case 'sensor': - await entity.read('hvacThermostat', [0x0403]); - break; - case 'regulator_mode': - await entity.read('hvacThermostat', [0x0405]); - break; - case 'power_status': - await entity.read('hvacThermostat', [0x0406]); - break; - case 'night_switching': - await entity.read('hvacThermostat', [0x0411]); - break; - case 'frost_guard': - await entity.read('hvacThermostat', [0x0412]); - break; - case 'max_floor_temp': - await entity.read('hvacThermostat', [0x0414]); - break; - case 'regulator_setpoint': - await entity.read('hvacThermostat', [0x0420]); - break; - case 'regulation_mode': - await entity.read('hvacThermostat', [0x0421]); - break; - case 'system_mode': - await entity.read('hvacThermostat', [0x0422]); - break; - case 'max_floor_guard': - await entity.read('hvacThermostat', [0x0423]); - break; - case 'weekly_timer': - await entity.read('hvacThermostat', [0x0424]); - break; - case 'exteral_sensor_source': - await entity.read('hvacThermostat', [0x0428]); - break; + case 'load': + await entity.read('hvacThermostat', [0x0401]); + break; + case 'display_text': + await entity.read('hvacThermostat', [0x0402]); + break; + case 'sensor': + await entity.read('hvacThermostat', [0x0403]); + break; + case 'regulator_mode': + await entity.read('hvacThermostat', [0x0405]); + break; + case 'power_status': + await entity.read('hvacThermostat', [0x0406]); + break; + case 'night_switching': + await entity.read('hvacThermostat', [0x0411]); + break; + case 'frost_guard': + await entity.read('hvacThermostat', [0x0412]); + break; + case 'max_floor_temp': + await entity.read('hvacThermostat', [0x0414]); + break; + case 'regulator_setpoint': + await entity.read('hvacThermostat', [0x0420]); + break; + case 'regulation_mode': + await entity.read('hvacThermostat', [0x0421]); + break; + case 'system_mode': + await entity.read('hvacThermostat', [0x0422]); + break; + case 'max_floor_guard': + await entity.read('hvacThermostat', [0x0423]); + break; + case 'weekly_timer': + await entity.read('hvacThermostat', [0x0424]); + break; + case 'exteral_sensor_source': + await entity.read('hvacThermostat', [0x0428]); + break; - default: // Unknown key - throw new Error(`Unhandled key tzLocal.ctm_thermostat.convertGet ${key}`); + default: // Unknown key + throw new Error(`Unhandled key tzLocal.ctm_thermostat.convertGet ${key}`); } }, } satisfies Tz.Converter, ctm_thermostat_preset: { key: ['preset'], convertSet: async (entity, key, value, meta) => { - const presetLookup = {'off': 0, 'away': 1, 'sleep': 2, 'home': 3}; + const presetLookup = {off: 0, away: 1, sleep: 2, home: 3}; await entity.write('hvacThermostat', {0x0422: {value: utils.getFromLookup(value, presetLookup), type: Zcl.DataType.UINT8}}); }, } satisfies Tz.Converter, ctm_thermostat_child_lock: { key: ['child_lock'], convertSet: async (entity, key, value, meta) => { - await entity.write('hvacThermostat', {0x0413: {value: utils.getFromLookup(value, {'UNLOCK': 0, 'LOCK': 1}), type: Zcl.DataType.BOOLEAN}}); + await entity.write('hvacThermostat', {0x0413: {value: utils.getFromLookup(value, {UNLOCK: 0, LOCK: 1}), type: Zcl.DataType.BOOLEAN}}); }, } satisfies Tz.Converter, ctm_thermostat_gets: { - key: ['mean_power', 'floor_temp', 'running_state', 'frost_guard_setpoint', 'external_temp', - 'air_temp', 'floor_sensor_error', 'exteral_sensor_error', + key: [ + 'mean_power', + 'floor_temp', + 'running_state', + 'frost_guard_setpoint', + 'external_temp', + 'air_temp', + 'floor_sensor_error', + 'exteral_sensor_error', ], convertGet: async (entity, key, meta) => { switch (key) { - case 'mean_power': - await entity.read('hvacThermostat', [0x0408]); - break; - case 'floor_temp': - await entity.read('hvacThermostat', [0x0409]); - break; - case 'running_state': - await entity.read('hvacThermostat', [0x0415]); - break; - case 'frost_guard_setpoint': - await entity.read('hvacThermostat', [0x0425]); - break; - case 'external_temp': - await entity.read('hvacThermostat', [0x0426]); - break; - case 'air_temp': - await entity.read('hvacThermostat', [0x0429]); - break; - case 'floor_sensor_error': - await entity.read('hvacThermostat', [0x042B]); - break; - case 'exteral_sensor_error': - await entity.read('hvacThermostat', [0x042C]); - break; + case 'mean_power': + await entity.read('hvacThermostat', [0x0408]); + break; + case 'floor_temp': + await entity.read('hvacThermostat', [0x0409]); + break; + case 'running_state': + await entity.read('hvacThermostat', [0x0415]); + break; + case 'frost_guard_setpoint': + await entity.read('hvacThermostat', [0x0425]); + break; + case 'external_temp': + await entity.read('hvacThermostat', [0x0426]); + break; + case 'air_temp': + await entity.read('hvacThermostat', [0x0429]); + break; + case 'floor_sensor_error': + await entity.read('hvacThermostat', [0x042b]); + break; + case 'exteral_sensor_error': + await entity.read('hvacThermostat', [0x042c]); + break; - default: // Unknown key - throw new Error(`Unhandled key tzLocal.ctm_thermostat.convertGet ${key}`); + default: // Unknown key + throw new Error(`Unhandled key tzLocal.ctm_thermostat.convertGet ${key}`); } }, } satisfies Tz.Converter, ctm_group_config: { key: ['group_id'], convertGet: async (entity, key, meta) => { - await entity.read(0xFEA7, [0x0000], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + await entity.read(0xfea7, [0x0000], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); }, } satisfies Tz.Converter, ctm_sove_guard: { key: [ - 'alarm_status', 'change_battery', 'stove_temperature', 'ambient_temperature', 'active', 'runtime', 'runtime_timeout', - 'reset_reason', 'dip_switch', 'sw_version', 'hw_version', 'bootloader_version', 'model', 'relay_address', - 'current_flag', 'relay_current', 'relay_status', 'external_button', 'relay_alarm', 'relay_alarm_status', + 'alarm_status', + 'change_battery', + 'stove_temperature', + 'ambient_temperature', + 'active', + 'runtime', + 'runtime_timeout', + 'reset_reason', + 'dip_switch', + 'sw_version', + 'hw_version', + 'bootloader_version', + 'model', + 'relay_address', + 'current_flag', + 'relay_current', + 'relay_status', + 'external_button', + 'relay_alarm', + 'relay_alarm_status', ], convertGet: async (entity, key, meta) => { switch (key) { - case 'alarm_status': - await entity.read(0xFFC9, [0x0001], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - break; - case 'battery_low': - await entity.read(0xFFC9, [0x0002], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - break; - case 'stove_temperature': - await entity.read(0xFFC9, [0x0003], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - break; - case 'ambient_temperature': - await entity.read(0xFFC9, [0x0004], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - break; - case 'active': - await entity.read(0xFFC9, [0x0005], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - break; - case 'runtime': - await entity.read(0xFFC9, [0x0006], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - break; - case 'runtime_timeout': - await entity.read(0xFFC9, [0x0007], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - break; - case 'reset_reason': - await entity.read(0xFFC9, [0x0008], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - break; - case 'dip_switch': - await entity.read(0xFFC9, [0x0009], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - break; - case 'sw_version': - await entity.read(0xFFC9, [0x000A], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - break; - case 'hw_version': - await entity.read(0xFFC9, [0x000B], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - break; - case 'bootloader_version': - await entity.read(0xFFC9, [0x000C], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - break; - case 'model': - await entity.read(0xFFC9, [0x000D], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - break; - case 'relay_address': - await entity.read(0xFFC9, [0x0010], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - break; - case 'current_flag': - await entity.read(0xFFC9, [0x0100], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - break; - case 'relay_current': - await entity.read(0xFFC9, [0x0101], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - break; - case 'relay_status': - await entity.read(0xFFC9, [0x0102], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - break; - case 'external_button': - await entity.read(0xFFC9, [0x0103], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - break; - case 'relay_alarm': - await entity.read(0xFFC9, [0x0104], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - break; - case 'relay_alarm_status': - await entity.read(0xFFC9, [0x0105], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - break; + case 'alarm_status': + await entity.read(0xffc9, [0x0001], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + break; + case 'battery_low': + await entity.read(0xffc9, [0x0002], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + break; + case 'stove_temperature': + await entity.read(0xffc9, [0x0003], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + break; + case 'ambient_temperature': + await entity.read(0xffc9, [0x0004], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + break; + case 'active': + await entity.read(0xffc9, [0x0005], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + break; + case 'runtime': + await entity.read(0xffc9, [0x0006], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + break; + case 'runtime_timeout': + await entity.read(0xffc9, [0x0007], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + break; + case 'reset_reason': + await entity.read(0xffc9, [0x0008], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + break; + case 'dip_switch': + await entity.read(0xffc9, [0x0009], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + break; + case 'sw_version': + await entity.read(0xffc9, [0x000a], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + break; + case 'hw_version': + await entity.read(0xffc9, [0x000b], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + break; + case 'bootloader_version': + await entity.read(0xffc9, [0x000c], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + break; + case 'model': + await entity.read(0xffc9, [0x000d], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + break; + case 'relay_address': + await entity.read(0xffc9, [0x0010], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + break; + case 'current_flag': + await entity.read(0xffc9, [0x0100], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + break; + case 'relay_current': + await entity.read(0xffc9, [0x0101], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + break; + case 'relay_status': + await entity.read(0xffc9, [0x0102], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + break; + case 'external_button': + await entity.read(0xffc9, [0x0103], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + break; + case 'relay_alarm': + await entity.read(0xffc9, [0x0104], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + break; + case 'relay_alarm_status': + await entity.read(0xffc9, [0x0105], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + break; - default: // Unknown key - throw new Error(`Unhandled key tzLocal.ctm_sove_guard.convertGet ${key}`); + default: // Unknown key + throw new Error(`Unhandled key tzLocal.ctm_sove_guard.convertGet ${key}`); } }, } satisfies Tz.Converter, @@ -647,40 +787,59 @@ const definitions: Definition[] = [ await endpoint.read('genLevelCtrl', ['currentLevel']); await reporting.brightness(endpoint); await endpoint.read('lightingBallastCfg', ['minLevel', 'maxLevel', 'powerOnLevel']); - await endpoint.configureReporting('lightingBallastCfg', [{ - attribute: 'minLevel', - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null}]); - await endpoint.configureReporting('lightingBallastCfg', [{ - attribute: 'maxLevel', - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null}]); - await endpoint.configureReporting('lightingBallastCfg', [{ - attribute: 'powerOnLevel', - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null}]); + await endpoint.configureReporting('lightingBallastCfg', [ + { + attribute: 'minLevel', + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ]); + await endpoint.configureReporting('lightingBallastCfg', [ + { + attribute: 'maxLevel', + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ]); + await endpoint.configureReporting('lightingBallastCfg', [ + { + attribute: 'powerOnLevel', + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ]); }, - exposes: [e.light_brightness(), - e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(99) - .withDescription('Specifies the minimum brightness value'), - e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(99) - .withDescription('Specifies the maximum brightness value'), - e.numeric('ballast_power_on_level', ea.ALL).withValueMin(1).withValueMax(99) - .withDescription('Specifies the initialisation light level. Can not be set lower than "ballast_minimum_level"')], - whiteLabel: [ - {vendor: 'CTM Lyng', model: 'CTM_DimmerPille', description: 'CTM Lyng DimmerPille', fingerprint: [{modelID: 'DimmerPille'}]}, + exposes: [ + e.light_brightness(), + e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(99).withDescription('Specifies the minimum brightness value'), + e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(99).withDescription('Specifies the maximum brightness value'), + e + .numeric('ballast_power_on_level', ea.ALL) + .withValueMin(1) + .withValueMax(99) + .withDescription('Specifies the initialisation light level. Can not be set lower than "ballast_minimum_level"'), ], + whiteLabel: [{vendor: 'CTM Lyng', model: 'CTM_DimmerPille', description: 'CTM Lyng DimmerPille', fingerprint: [{modelID: 'DimmerPille'}]}], }, { zigbeeModel: ['mTouch Bryter'], model: 'mTouch_Bryter', vendor: 'CTM Lyng', description: 'mTouch Bryter OP, 3 channel switch', - fromZigbee: [fz.temperature, fz.battery, fz.command_recall, fz.command_on, fz.command_off, fz.command_toggle, - fz.command_move, fz.command_stop, fzLocal.ctm_group_config], + fromZigbee: [ + fz.temperature, + fz.battery, + fz.command_recall, + fz.command_on, + fz.command_off, + fz.command_toggle, + fz.command_move, + fz.command_stop, + fzLocal.ctm_group_config, + ], toZigbee: [], meta: {battery: {voltageToPercentage: '3V_2500_3200'}}, configure: async (device, coordinatorEndpoint) => { @@ -689,13 +848,16 @@ const definitions: Definition[] = [ await reporting.batteryVoltage(endpoint); await endpoint.read('msTemperatureMeasurement', ['measuredValue']); await reporting.temperature(endpoint, {min: constants.repInterval.MINUTES_10, max: constants.repInterval.HOUR, change: 100}); - await endpoint.read(0xFEA7, [0x0000], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + await endpoint.read(0xfea7, [0x0000], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); }, - exposes: [e.battery(), e.temperature(), - e.action(['recall_1', 'recall_2', 'recall_3', 'on', 'off', 'toggle', - 'brightness_move_down', 'brightness_move_up', 'brightness_stop']), - e.numeric('group_id', ea.STATE) - .withDescription('The device sends commands with this group ID. Put dvices in this group to control them.')], + exposes: [ + e.battery(), + e.temperature(), + e.action(['recall_1', 'recall_2', 'recall_3', 'on', 'off', 'toggle', 'brightness_move_down', 'brightness_move_up', 'brightness_stop']), + e + .numeric('group_id', ea.STATE) + .withDescription('The device sends commands with this group ID. Put dvices in this group to control them.'), + ], }, { zigbeeModel: ['mTouch One'], @@ -703,8 +865,14 @@ const definitions: Definition[] = [ vendor: 'CTM Lyng', description: 'mTouch One OP, touch thermostat', fromZigbee: [fz.thermostat, fzLocal.ctm_thermostat], - toZigbee: [tz.thermostat_occupied_heating_setpoint, tz.thermostat_local_temperature, tzLocal.ctm_thermostat, - tzLocal.ctm_thermostat_preset, tzLocal.ctm_thermostat_child_lock, tzLocal.ctm_thermostat_gets], + toZigbee: [ + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_local_temperature, + tzLocal.ctm_thermostat, + tzLocal.ctm_thermostat_preset, + tzLocal.ctm_thermostat_child_lock, + tzLocal.ctm_thermostat_gets, + ], ota: ota.zigbeeOTA, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -716,89 +884,120 @@ const definitions: Definition[] = [ await endpoint.read('hvacThermostat', [0x0402]); // Regulator mode await endpoint.read('hvacThermostat', [0x0405]); - await endpoint.configureReporting('hvacThermostat', [{ - attribute: {ID: 0x0405, type: Zcl.DataType.BOOLEAN}, - minimumReportInterval: 1, - maximumReportInterval: constants.repInterval.MAX, - reportableChange: null}]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: {ID: 0x0405, type: Zcl.DataType.BOOLEAN}, + minimumReportInterval: 1, + maximumReportInterval: constants.repInterval.MAX, + reportableChange: null, + }, + ]); // Power consumption await endpoint.read('hvacThermostat', [0x0408]); - await endpoint.configureReporting('hvacThermostat', [{ - attribute: {ID: 0x0408, type: Zcl.DataType.UINT16}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 5}]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: {ID: 0x0408, type: Zcl.DataType.UINT16}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 5, + }, + ]); // Floor temp sensor await endpoint.read('hvacThermostat', [0x0409]); - await endpoint.configureReporting('hvacThermostat', [{ - attribute: {ID: 0x0409, type: Zcl.DataType.INT16}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 10}]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: {ID: 0x0409, type: Zcl.DataType.INT16}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 10, + }, + ]); // Frost guard await endpoint.read('hvacThermostat', [0x0412]); - await endpoint.configureReporting('hvacThermostat', [{ - attribute: {ID: 0x0412, type: Zcl.DataType.BOOLEAN}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.MAX, - reportableChange: null}]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: {ID: 0x0412, type: Zcl.DataType.BOOLEAN}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.MAX, + reportableChange: null, + }, + ]); // Child lock active/inactive await endpoint.read('hvacThermostat', [0x0413]); - await endpoint.configureReporting('hvacThermostat', [{ - attribute: {ID: 0x0413, type: Zcl.DataType.BOOLEAN}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.MAX, - reportableChange: null}]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: {ID: 0x0413, type: Zcl.DataType.BOOLEAN}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.MAX, + reportableChange: null, + }, + ]); // Regulator setpoint await endpoint.read('hvacThermostat', [0x0420]); - await endpoint.configureReporting('hvacThermostat', [{ - attribute: {ID: 0x0420, type: Zcl.DataType.UINT8}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 1}]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: {ID: 0x0420, type: Zcl.DataType.UINT8}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 1, + }, + ]); // Operation mode await endpoint.read('hvacThermostat', [0x0422]); - await endpoint.configureReporting('hvacThermostat', [{ - attribute: {ID: 0x0422, type: Zcl.DataType.UINT8}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 1}]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: {ID: 0x0422, type: Zcl.DataType.UINT8}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 1, + }, + ]); // Air temp sensor await endpoint.read('hvacThermostat', [0x0429]); - await endpoint.configureReporting('hvacThermostat', [{ - attribute: {ID: 0x0429, type: Zcl.DataType.INT16}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 10}]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: {ID: 0x0429, type: Zcl.DataType.INT16}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 10, + }, + ]); }, - exposes: [e.child_lock(), - e.climate() + exposes: [ + e.child_lock(), + e + .climate() .withSetpoint('occupied_heating_setpoint', 5, 40, 1) .withLocalTemperature() .withSystemMode(['off', 'heat']) .withPreset(['off', 'away', 'sleep', 'home']) .withRunningState(['idle', 'heat']), - e.numeric('load', ea.ALL).withUnit('W') - .withDescription('Load in W when heating is on (between 0-3600 W). The thermostat uses the value as input to the ' + - 'mean_power calculation.') - .withValueMin(0).withValueMax(3600), - e.text('display_text', ea.ALL) - .withDescription('Displayed text on thermostat display (zone). Max 19 characters'), - e.binary('regulator_mode', ea.ALL, 'regulator', 'thermostat') - .withDescription('Device in regulator or thermostat mode.'), - e.numeric('mean_power', ea.STATE_GET).withUnit('W') - .withDescription('Reports average power usage last 10 minutes'), - e.numeric('floor_temp', ea.STATE_GET).withUnit('°C') - .withDescription('Current temperature measured from the floor sensor'), - e.binary('frost_guard', ea.ALL, 'ON', 'OFF') - .withDescription('When frost guard is ON, it is activated when the thermostat is switched OFF with the ON/OFF button.' + - 'At the same time, the display will fade and the text "Frostsikring x °C" appears in the display and remains until the ' + - 'thermostat is switched on again.'), - e.numeric('regulator_setpoint', ea.ALL).withUnit('%') + e + .numeric('load', ea.ALL) + .withUnit('W') + .withDescription( + 'Load in W when heating is on (between 0-3600 W). The thermostat uses the value as input to the ' + 'mean_power calculation.', + ) + .withValueMin(0) + .withValueMax(3600), + e.text('display_text', ea.ALL).withDescription('Displayed text on thermostat display (zone). Max 19 characters'), + e.binary('regulator_mode', ea.ALL, 'regulator', 'thermostat').withDescription('Device in regulator or thermostat mode.'), + e.numeric('mean_power', ea.STATE_GET).withUnit('W').withDescription('Reports average power usage last 10 minutes'), + e.numeric('floor_temp', ea.STATE_GET).withUnit('°C').withDescription('Current temperature measured from the floor sensor'), + e + .binary('frost_guard', ea.ALL, 'ON', 'OFF') + .withDescription( + 'When frost guard is ON, it is activated when the thermostat is switched OFF with the ON/OFF button.' + + 'At the same time, the display will fade and the text "Frostsikring x °C" appears in the display and remains until the ' + + 'thermostat is switched on again.', + ), + e + .numeric('regulator_setpoint', ea.ALL) + .withUnit('%') .withDescription('Setpoint in %, use only when the thermostat is in regulator mode.') - .withValueMin(1).withValueMax(99), - e.numeric('air_temp', ea.STATE_GET).withUnit('°C') - .withDescription('Current temperature measured from the air sensor'), + .withValueMin(1) + .withValueMax(99), + e.numeric('air_temp', ea.STATE_GET).withUnit('°C').withDescription('Current temperature measured from the air sensor'), ], }, { @@ -841,45 +1040,77 @@ const definitions: Definition[] = [ toZigbee: [], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); - await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'msTemperatureMeasurement', 0xFFC9]); + await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'msTemperatureMeasurement', 0xffc9]); await reporting.batteryPercentageRemaining(endpoint); // await endpoint.read('msTemperatureMeasurement', ['measuredValue']); await reporting.temperature(endpoint, {min: constants.repInterval.MINUTES_10, max: constants.repInterval.HOUR, change: 100}); // Alarm status // await endpoint.read(0xFFC9, [0x0001], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - await endpoint.configureReporting(0xFFC9, [{ - attribute: {ID: 0x0001, type: Zcl.DataType.UINT8}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 0}], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + await endpoint.configureReporting( + 0xffc9, + [ + { + attribute: {ID: 0x0001, type: Zcl.DataType.UINT8}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 0, + }, + ], + {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}, + ); // Change battery // await endpoint.read(0xFFC9, [0x0002], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - await endpoint.configureReporting(0xFFC9, [{ - attribute: {ID: 0x0002, type: Zcl.DataType.UINT8}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.MAX, - reportableChange: 0}], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + await endpoint.configureReporting( + 0xffc9, + [ + { + attribute: {ID: 0x0002, type: Zcl.DataType.UINT8}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.MAX, + reportableChange: 0, + }, + ], + {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}, + ); // Active // await endpoint.read(0xFFC9, [0x0005], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - await endpoint.configureReporting(0xFFC9, [{ - attribute: {ID: 0x0005, type: Zcl.DataType.UINT8}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 0}], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + await endpoint.configureReporting( + 0xffc9, + [ + { + attribute: {ID: 0x0005, type: Zcl.DataType.UINT8}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 0, + }, + ], + {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}, + ); }, - exposes: [e.battery(), e.battery_low(), e.temperature(), - e.enum('alarm_status', ea.STATE, ['ok', 'tamper', 'high_temperatur', 'timer', 'battery_alarm', 'error', 'unknown']) + exposes: [ + e.battery(), + e.battery_low(), + e.temperature(), + e + .enum('alarm_status', ea.STATE, ['ok', 'tamper', 'high_temperatur', 'timer', 'battery_alarm', 'error', 'unknown']) .withDescription('Alarm status.'), - e.binary('active', ea.STATE, true, false) - .withDescription('Stove guard active/inactive (Stove in use)')], + e.binary('active', ea.STATE, true, false).withDescription('Stove guard active/inactive (Stove in use)'), + ], }, { zigbeeModel: ['mTouch Astro'], model: 'mTouch_Astro', vendor: 'CTM Lyng', description: 'mTouch Astro OP, astro clock', - fromZigbee: [fz.on_off, fz.command_on, fz.command_off, fzLocal.ctm_device_mode, fzLocal.ctm_device_enabled, - fzLocal.ctm_child_lock, fzLocal.ctm_group_config], + fromZigbee: [ + fz.on_off, + fz.command_on, + fz.command_off, + fzLocal.ctm_device_mode, + fzLocal.ctm_device_enabled, + fzLocal.ctm_child_lock, + fzLocal.ctm_group_config, + ], toZigbee: [tz.on_off, tzLocal.ctm_device_enabled], meta: {disableDefaultResponse: true}, configure: async (device, coordinatorEndpoint) => { @@ -889,33 +1120,42 @@ const definitions: Definition[] = [ await reporting.onOff(endpoint); // Device mode await endpoint.read('genOnOff', [0x2200]); - await endpoint.configureReporting('genOnOff', [{ - attribute: {ID: 0x2200, type: Zcl.DataType.UINT8}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 0}]); + await endpoint.configureReporting('genOnOff', [ + { + attribute: {ID: 0x2200, type: Zcl.DataType.UINT8}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 0, + }, + ]); await endpoint.read('genOnOff', [0x2201]); - await endpoint.configureReporting('genOnOff', [{ - attribute: {ID: 0x2201, type: Zcl.DataType.BOOLEAN}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null}]); + await endpoint.configureReporting('genOnOff', [ + { + attribute: {ID: 0x2201, type: Zcl.DataType.BOOLEAN}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ]); await endpoint.read('genOnOff', [0x2202]); - await endpoint.configureReporting('genOnOff', [{ - attribute: {ID: 0x2202, type: Zcl.DataType.BOOLEAN}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null}]); - await endpoint.read(0xFEA7, [0x0000], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + await endpoint.configureReporting('genOnOff', [ + { + attribute: {ID: 0x2202, type: Zcl.DataType.BOOLEAN}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ]); + await endpoint.read(0xfea7, [0x0000], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); }, - exposes: [e.switch(), e.action(['on', 'off']), - e.enum('device_mode', ea.STATE, ['astro_clock', 'timer', 'daily_timer', 'weekly_timer']) - .withDescription('Device mode.'), - e.binary('device_enabled', ea.ALL, 'ON', 'OFF') - .withDescription('Turn the device on or off'), - e.binary('child_lock', ea.STATE, 'locked', 'unlocked') - .withDescription('Physical input on the device enabled/disabled'), - e.numeric('group_id', ea.STATE) + exposes: [ + e.switch(), + e.action(['on', 'off']), + e.enum('device_mode', ea.STATE, ['astro_clock', 'timer', 'daily_timer', 'weekly_timer']).withDescription('Device mode.'), + e.binary('device_enabled', ea.ALL, 'ON', 'OFF').withDescription('Turn the device on or off'), + e.binary('child_lock', ea.STATE, 'locked', 'unlocked').withDescription('Physical input on the device enabled/disabled'), + e + .numeric('group_id', ea.STATE) .withDescription('The device sends commands with this group ID. Put devices in this group to control them.'), ], }, @@ -933,9 +1173,11 @@ const definitions: Definition[] = [ await reporting.batteryVoltage(endpoint); await endpoint.read('ssIasZone', ['iasCieAddr', 'zoneState', 'zoneId']); }, - exposes: [e.battery(), e.battery_low(), e.water_leak(), - e.binary('active_water_leak', ea.STATE, true, false) - .withDescription('Indicates whether there is an active water leak'), + exposes: [ + e.battery(), + e.battery_low(), + e.water_leak(), + e.binary('active_water_leak', ea.STATE, true, false).withDescription('Indicates whether there is an active water leak'), ], }, { @@ -955,9 +1197,10 @@ const definitions: Definition[] = [ await reporting.bind(endpoint2, coordinatorEndpoint, ['ssIasZone']); await endpoint2.read('ssIasZone', ['iasCieAddr', 'zoneState', 'zoneId']); }, - exposes: [e.switch(), e.water_leak(), - e.binary('active_water_leak', ea.STATE, true, false) - .withDescription('Indicates whether there is an active water leak'), + exposes: [ + e.switch(), + e.water_leak(), + e.binary('active_water_leak', ea.STATE, true, false).withDescription('Indicates whether there is an active water leak'), ], }, { @@ -965,8 +1208,7 @@ const definitions: Definition[] = [ model: 'mSwitch_Mic', vendor: 'CTM Lyng', description: 'Mikrofon, alarm detection microphone', - fromZigbee: [fz.temperature, fz.battery, fz.command_on, fz.command_off, fz.ias_enroll, fz.ias_smoke_alarm_1, - fzLocal.ctm_group_config], + fromZigbee: [fz.temperature, fz.battery, fz.command_on, fz.command_off, fz.ias_enroll, fz.ias_smoke_alarm_1, fzLocal.ctm_group_config], toZigbee: [], meta: {battery: {voltageToPercentage: '3V_2500_3200'}}, configure: async (device, coordinatorEndpoint) => { @@ -976,11 +1218,16 @@ const definitions: Definition[] = [ await endpoint.read('ssIasZone', ['iasCieAddr', 'zoneState', 'zoneId']); await endpoint.read('msTemperatureMeasurement', ['measuredValue']); await reporting.temperature(endpoint, {min: constants.repInterval.MINUTES_10, max: constants.repInterval.HOUR, change: 100}); - await endpoint.read(0xFEA7, [0x0000], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + await endpoint.read(0xfea7, [0x0000], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); }, - exposes: [e.temperature(), e.battery(), e.battery_low(), e.smoke(), + exposes: [ + e.temperature(), + e.battery(), + e.battery_low(), + e.smoke(), e.action(['on', 'off']), - e.numeric('group_id', ea.STATE) + e + .numeric('group_id', ea.STATE) .withDescription('The device sends commands with this group ID. Put devices in this group to control them.'), ], }, @@ -1023,15 +1270,25 @@ const definitions: Definition[] = [ await reporting.occupancy(endpoint); // Relay State await endpoint.read('genOnOff', [0x5001], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - await endpoint.configureReporting('genOnOff', [{ - attribute: {ID: 0x5001, type: Zcl.DataType.BOOLEAN}, - minimumReportInterval: 1, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 0}], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + await endpoint.configureReporting( + 'genOnOff', + [ + { + attribute: {ID: 0x5001, type: Zcl.DataType.BOOLEAN}, + minimumReportInterval: 1, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 0, + }, + ], + {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}, + ); }, - exposes: [e.switch(), e.illuminance(), e.illuminance_lux(), e.occupancy(), - e.binary('device_enabled', ea.ALL, 'ON', 'OFF') - .withDescription('Turn the device on or off'), + exposes: [ + e.switch(), + e.illuminance(), + e.illuminance_lux(), + e.occupancy(), + e.binary('device_enabled', ea.ALL, 'ON', 'OFF').withDescription('Turn the device on or off'), ], }, { @@ -1039,54 +1296,85 @@ const definitions: Definition[] = [ model: 'CTM_MBD_Dim', vendor: 'CTM Lyng', description: 'MBD Dim, motion detector with dimmer', - fromZigbee: [fz.illuminance, fz.occupancy, fzLocal.ctm_mbd_device_enabled, fzLocal.ctm_relay_state, - fz.brightness, fz.lighting_ballast_configuration], + fromZigbee: [ + fz.illuminance, + fz.occupancy, + fzLocal.ctm_mbd_device_enabled, + fzLocal.ctm_relay_state, + fz.brightness, + fz.lighting_ballast_configuration, + ], toZigbee: [tzLocal.ctm_mbd_device_enabled, tzLocal.ctm_relay_state, tzLocal.ctm_mbd_brightness, tz.ballast_config], meta: {disableDefaultResponse: true}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); - await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'lightingBallastCfg', - 'msIlluminanceMeasurement', 'msOccupancySensing']); + await reporting.bind(endpoint, coordinatorEndpoint, [ + 'genOnOff', + 'genLevelCtrl', + 'lightingBallastCfg', + 'msIlluminanceMeasurement', + 'msOccupancySensing', + ]); await endpoint.read('genOnOff', ['onOff']); await reporting.onOff(endpoint); await endpoint.read('genLevelCtrl', ['currentLevel']); await reporting.brightness(endpoint); await endpoint.read('lightingBallastCfg', ['minLevel', 'maxLevel', 'powerOnLevel']); - await endpoint.configureReporting('lightingBallastCfg', [{ - attribute: 'minLevel', - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null}]); - await endpoint.configureReporting('lightingBallastCfg', [{ - attribute: 'maxLevel', - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null}]); - await endpoint.configureReporting('lightingBallastCfg', [{ - attribute: 'powerOnLevel', - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null}]); + await endpoint.configureReporting('lightingBallastCfg', [ + { + attribute: 'minLevel', + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ]); + await endpoint.configureReporting('lightingBallastCfg', [ + { + attribute: 'maxLevel', + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ]); + await endpoint.configureReporting('lightingBallastCfg', [ + { + attribute: 'powerOnLevel', + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ]); await endpoint.read('msIlluminanceMeasurement', ['measuredValue']); await reporting.illuminance(endpoint); await endpoint.read('msOccupancySensing', ['occupancy']); await reporting.occupancy(endpoint); // Relay State await endpoint.read('genOnOff', [0x5001], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); - await endpoint.configureReporting('genOnOff', [{ - attribute: {ID: 0x5001, type: Zcl.DataType.BOOLEAN}, - minimumReportInterval: 1, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 0}], {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}); + await endpoint.configureReporting( + 'genOnOff', + [ + { + attribute: {ID: 0x5001, type: Zcl.DataType.BOOLEAN}, + minimumReportInterval: 1, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 0, + }, + ], + {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}, + ); }, - exposes: [e.light_brightness(), e.illuminance(), e.illuminance_lux(), e.occupancy(), - e.binary('device_enabled', ea.ALL, 'ON', 'OFF') - .withDescription('Turn the device on or off'), - e.numeric('ballast_minimum_level', ea.ALL).withValueMin(10).withValueMax(97) - .withDescription('Specifies the minimum brightness value'), - e.numeric('ballast_maximum_level', ea.ALL).withValueMin(10).withValueMax(97) - .withDescription('Specifies the maximum brightness value'), - e.numeric('ballast_power_on_level', ea.ALL).withValueMin(10).withValueMax(97) + exposes: [ + e.light_brightness(), + e.illuminance(), + e.illuminance_lux(), + e.occupancy(), + e.binary('device_enabled', ea.ALL, 'ON', 'OFF').withDescription('Turn the device on or off'), + e.numeric('ballast_minimum_level', ea.ALL).withValueMin(10).withValueMax(97).withDescription('Specifies the minimum brightness value'), + e.numeric('ballast_maximum_level', ea.ALL).withValueMin(10).withValueMax(97).withDescription('Specifies the maximum brightness value'), + e + .numeric('ballast_power_on_level', ea.ALL) + .withValueMin(10) + .withValueMax(97) .withDescription('Specifies the initialisation light level. Can not be set lower than "ballast_minimum_level"'), ], }, @@ -1099,7 +1387,6 @@ const definitions: Definition[] = [ ota: ota.zigbeeOTA, meta: {}, }, - ]; export default definitions; diff --git a/src/devices/current_products_corp.ts b/src/devices/current_products_corp.ts index 70be36ad5311e..41dc8d36c5079 100644 --- a/src/devices/current_products_corp.ts +++ b/src/devices/current_products_corp.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/custom_devices_diy.ts b/src/devices/custom_devices_diy.ts index 864cd7cb47acc..fb833ba6ec29e 100644 --- a/src/devices/custom_devices_diy.ts +++ b/src/devices/custom_devices_diy.ts @@ -1,14 +1,14 @@ import {Zcl} from 'zigbee-herdsman'; -import * as exposes from '../lib/exposes'; + import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; -import {Definition, Tz, Fz, KeyValue, KeyValueAny, Zh, Expose} from '../lib/types'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; import * as ota from '../lib/ota'; +import * as reporting from '../lib/reporting'; +import {Definition, Tz, Fz, KeyValue, KeyValueAny, Zh, Expose} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; -import {getFromLookup, getKey, postfixWithEndpointName, isEndpoint} from '../lib/utils'; import { light, onOff, @@ -22,9 +22,10 @@ import { deviceEndpoints, commandsOnOff, } from '../lib/modernExtend'; +import {getFromLookup, getKey, postfixWithEndpointName, isEndpoint} from '../lib/utils'; const switchTypesList = { - 'switch': 0x00, + switch: 0x00, 'multi-click': 0x02, }; @@ -88,7 +89,7 @@ const fzLocal = { // in the 0 - 100 range, don't produce messages beyond these values. if (humidity >= 0 && humidity <= 100) { const multiEndpoint = model.meta && model.meta.hasOwnProperty('multiEndpoint') && model.meta.multiEndpoint; - const property = (multiEndpoint)? postfixWithEndpointName('humidity', msg, model, meta): 'humidity'; + const property = multiEndpoint ? postfixWithEndpointName('humidity', msg, model, meta) : 'humidity'; return {[property]: humidity}; } }, @@ -102,8 +103,8 @@ const fzLocal = { const illuminance = msg.data['measuredValue']; const illuminanceLux = illuminance === 0 ? 0 : Math.pow(10, (illuminance - 1) / 10000); const multiEndpoint = model.meta && model.meta.hasOwnProperty('multiEndpoint') && model.meta.multiEndpoint; - const property1 = (multiEndpoint)? postfixWithEndpointName('illuminance', msg, model, meta): 'illuminance'; - const property2 = (multiEndpoint)? postfixWithEndpointName('illuminance_lux', msg, model, meta): 'illuminance_lux'; + const property1 = multiEndpoint ? postfixWithEndpointName('illuminance', msg, model, meta) : 'illuminance'; + const property2 = multiEndpoint ? postfixWithEndpointName('illuminance_lux', msg, model, meta) : 'illuminance_lux'; return {[property1]: illuminance, [property2]: illuminanceLux}; }, } satisfies Fz.Converter, @@ -120,7 +121,7 @@ const fzLocal = { pressure = parseFloat(msg.data['measuredValue']); } const multiEndpoint = model.meta && model.meta.hasOwnProperty('multiEndpoint') && model.meta.multiEndpoint; - const property = (multiEndpoint)? postfixWithEndpointName('pressure', msg, model, meta): 'pressure'; + const property = multiEndpoint ? postfixWithEndpointName('pressure', msg, model, meta) : 'pressure'; return {[property]: pressure}; }, } satisfies Fz.Converter, @@ -138,7 +139,7 @@ const fzLocal = { type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { const button = getKey(model.endpoint?.(msg.device) ?? {}, msg.endpoint.ID); - const actionLookup: { [key: number]: string } = {0: 'release', 1: 'single', 2: 'double', 3: 'triple', 4: 'hold'}; + const actionLookup: {[key: number]: string} = {0: 'release', 1: 'single', 2: 'double', 3: 'triple', 4: 'hold'}; const value = msg.data['presentValue']; const action = actionLookup[value]; return {action: button + '_' + action}; @@ -196,8 +197,7 @@ function ptvoAddStandardExposes(endpoint: Zh.Endpoint, expose: Expose[], options if (endpoint.supportsInputCluster('genAnalogInput') || endpoint.supportsOutputCluster('genAnalogInput')) { if (!options['exposed_analog']) { options['exposed_analog'] = true; - expose.push(e.text(epName, ea.ALL).withEndpoint(epName) - .withProperty(epName).withDescription('State or sensor value')); + expose.push(e.text(epName, ea.ALL).withEndpoint(epName).withProperty(epName).withDescription('State or sensor value')); } } if (endpoint.supportsInputCluster('msTemperatureMeasurement')) { @@ -247,9 +247,18 @@ const definitions: Definition[] = [ description: 'Texas Instruments router', fromZigbee: [fzLocal.tirouter], toZigbee: [tzLocal.tirouter], - exposes: [e.numeric('transmit_power', ea.ALL).withValueMin(-20).withValueMax(20).withValueStep(1).withUnit('dBm') - .withDescription('Transmit power, supported from firmware 20221102. The max for CC1352 is 20 dBm and 5 dBm for CC2652' + - ' (any higher value is converted to 5dBm)')], + exposes: [ + e + .numeric('transmit_power', ea.ALL) + .withValueMin(-20) + .withValueMax(20) + .withValueStep(1) + .withUnit('dBm') + .withDescription( + 'Transmit power, supported from firmware 20221102. The max for CC1352 is 20 dBm and 5 dBm for CC2652' + + ' (any higher value is converted to 5dBm)', + ), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(8); const payload = [{attribute: 'zclVersion', minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0}]; @@ -265,9 +274,7 @@ const definitions: Definition[] = [ fromZigbee: [fz.linkquality_from_basic], toZigbee: [], exposes: [], - whiteLabel: [ - {vendor: 'SMLIGHT', model: 'SLZB-07', description: 'Router', fingerprint: [{modelID: 'SLZB-07'}]}, - ], + whiteLabel: [{vendor: 'SMLIGHT', model: 'SLZB-07', description: 'Router', fingerprint: [{modelID: 'SLZB-07'}]}], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const payload = [{attribute: 'zclVersion', minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0}]; @@ -307,16 +314,30 @@ const definitions: Definition[] = [ model: 'ptvo.switch', vendor: 'Custom devices (DiY)', description: 'Multi-functional device', - fromZigbee: [fz.battery, fz.on_off, fz.ptvo_multistate_action, legacy.fz.ptvo_switch_buttons, fz.ptvo_switch_uart, - fz.ptvo_switch_analog_input, fz.brightness, fz.ignore_basic_report, fz.temperature, - fzLocal.humidity2, fzLocal.pressure2, fzLocal.illuminance2, fz.electrical_measurement, fz.metering, fz.co2], + fromZigbee: [ + fz.battery, + fz.on_off, + fz.ptvo_multistate_action, + legacy.fz.ptvo_switch_buttons, + fz.ptvo_switch_uart, + fz.ptvo_switch_analog_input, + fz.brightness, + fz.ignore_basic_report, + fz.temperature, + fzLocal.humidity2, + fzLocal.pressure2, + fzLocal.illuminance2, + fz.electrical_measurement, + fz.metering, + fz.co2, + ], toZigbee: [tz.ptvo_switch_trigger, tz.ptvo_switch_uart, tz.ptvo_switch_analog_input, tz.ptvo_switch_light_brightness, tzLocal.ptvo_on_off], exposes: (device, options) => { const expose: Expose[] = []; const exposeDeviceOptions: KeyValue = {}; const deviceConfig = ptvoGetMetaOption(device, 'device_config', ''); if (deviceConfig === '') { - if ((device != null) && device.endpoints) { + if (device != null && device.endpoints) { for (const endpoint of device.endpoints) { const exposeEpOptions: KeyValue = {}; ptvoAddStandardExposes(endpoint, expose, exposeEpOptions, exposeDeviceOptions); @@ -325,15 +346,14 @@ const definitions: Definition[] = [ // fallback code for (let epId = 1; epId <= 8; epId++) { const epName = `l${epId}`; - expose.push(e.text(epName, ea.ALL).withEndpoint(epName) - .withProperty(epName).withDescription('State or sensor value')); + expose.push(e.text(epName, ea.ALL).withEndpoint(epName).withProperty(epName).withDescription('State or sensor value')); expose.push(e.switch().withEndpoint(epName)); } } } else { // device configuration description from a device const deviceConfigArray = deviceConfig.split(/[\r\n]+/); - const allEndpoints: { [key: number]: string } = {}; + const allEndpoints: {[key: number]: string} = {}; const allEndpointsSorted = []; let epConfig; for (let i = 0; i < deviceConfigArray.length; i++) { @@ -367,13 +387,12 @@ const definitions: Definition[] = [ epConfig = epConfig.substring(2); const epName = `l${epId}`; const epValueAccessRights = epConfig.substr(0, 1); - const epStateType = ((epValueAccessRights === 'W') || (epValueAccessRights === '*'))? - ea.STATE_SET: ea.STATE; + const epStateType = epValueAccessRights === 'W' || epValueAccessRights === '*' ? ea.STATE_SET : ea.STATE; const valueConfig = epConfig.substr(1); - const valueConfigItems = (valueConfig)? valueConfig.split(','): []; - let valueId = (valueConfigItems[0])? valueConfigItems[0]: ''; - let valueDescription = (valueConfigItems[1])? valueConfigItems[1]: ''; - let valueUnit = (valueConfigItems[2] !== undefined)? valueConfigItems[2]: ''; + const valueConfigItems = valueConfig ? valueConfig.split(',') : []; + let valueId = valueConfigItems[0] ? valueConfigItems[0] : ''; + let valueDescription = valueConfigItems[1] ? valueConfigItems[1] : ''; + let valueUnit = valueConfigItems[2] !== undefined ? valueConfigItems[2] : ''; if (!exposeDeviceOptions.hasOwnProperty(epName)) { exposeDeviceOptions[epName] = {}; } @@ -387,17 +406,35 @@ const definitions: Definition[] = [ exposeEpOptions['exposed_onoff'] = true; let exposeObj = undefined; switch (valueDescription) { - case 'g': exposeObj = e.gas(); break; - case 'n': exposeObj = e.noise_detected(); break; - case 'o': exposeObj = e.occupancy(); break; - case 'p': exposeObj = e.presence(); break; - case 'm': exposeObj = e.smoke(); break; - case 's': exposeObj = e.sos(); break; - case 't': exposeObj = e.tamper(); break; - case 'v': exposeObj = e.vibration(); break; - case 'w': exposeObj = e.water_leak(); break; - default: // 'c' - exposeObj = e.contact(); + case 'g': + exposeObj = e.gas(); + break; + case 'n': + exposeObj = e.noise_detected(); + break; + case 'o': + exposeObj = e.occupancy(); + break; + case 'p': + exposeObj = e.presence(); + break; + case 'm': + exposeObj = e.smoke(); + break; + case 's': + exposeObj = e.sos(); + break; + case 't': + exposeObj = e.tamper(); + break; + case 'v': + exposeObj = e.vibration(); + break; + case 'w': + exposeObj = e.water_leak(); + break; + default: // 'c' + exposeObj = e.contact(); } expose.push(exposeObj.withProperty('state').withEndpoint(epName)); } else if (valueConfigItems.length > 0) { @@ -416,33 +453,33 @@ const definitions: Definition[] = [ // 1: value name (if empty, use the EP name) // 2: description (if empty or undefined, use the value name) // 3: units (if undefined, use the key name) - const infoLookup: { [key: string]: string } = { - 'C': 'temperature', + const infoLookup: {[key: string]: string} = { + C: 'temperature', '%': 'humidity', - 'm': 'altitude', - 'Pa': 'pressure', - 'ppm': 'quality', - 'psize': 'particle_size', - 'V': 'voltage', - 'A': 'current', - 'Wh': 'energy', - 'W': 'power', - 'Hz': 'frequency', - 'pf': 'power_factor', - 'lx': 'illuminance_lux', + m: 'altitude', + Pa: 'pressure', + ppm: 'quality', + psize: 'particle_size', + V: 'voltage', + A: 'current', + Wh: 'energy', + W: 'power', + Hz: 'frequency', + pf: 'power_factor', + lx: 'illuminance_lux', }; - valueName = (valueName !== undefined)? valueName: infoLookup[valueId]; + valueName = valueName !== undefined ? valueName : infoLookup[valueId]; - if ((valueName === undefined) && valueNumIndex) { + if (valueName === undefined && valueNumIndex) { valueName = 'val' + valueNumIndex; } if (valueName) { exposeEpOptions['exposed_' + valueName] = true; } - valueName = (valueName === undefined)? epName: valueName + '_' + epName; + valueName = valueName === undefined ? epName : valueName + '_' + epName; - if ((valueDescription === undefined) || (valueDescription === '')) { + if (valueDescription === undefined || valueDescription === '') { if (infoLookup[valueId]) { valueDescription = infoLookup[valueId]; valueDescription = valueDescription.replace('_', ' '); @@ -450,22 +487,26 @@ const definitions: Definition[] = [ valueDescription = 'Sensor value'; } } - valueDescription = valueDescription.substring(0, 1).toUpperCase() + - valueDescription.substring(1); + valueDescription = valueDescription.substring(0, 1).toUpperCase() + valueDescription.substring(1); if (valueNumIndex) { valueDescription = valueDescription + ' ' + valueNumIndex; } - if (((valueUnit === undefined) || (valueUnit === '')) && infoLookup[valueId]) { + if ((valueUnit === undefined || valueUnit === '') && infoLookup[valueId]) { valueUnit = valueId; } exposeEpOptions['exposed_analog'] = true; - expose.push(e.numeric(valueName, epStateType) - .withValueMin(-9999999).withValueMax(9999999).withValueStep(1) - .withDescription(valueDescription) - .withUnit(valueUnit)); + expose.push( + e + .numeric(valueName, epStateType) + .withValueMin(-9999999) + .withValueMax(9999999) + .withValueStep(1) + .withDescription(valueDescription) + .withUnit(valueUnit), + ); } const epConfigNext = allEndpointsSorted[i + 1] || '-1'; @@ -490,11 +531,11 @@ const definitions: Definition[] = [ }, meta: {multiEndpoint: true, tuyaThermostatPreset: legacy.fz /* for subclassed custom converters */}, endpoint: (device) => { - // eslint-disable-next-line + // eslint-disable-next-line @typescript-eslint/no-explicit-any const endpointList: any = []; const deviceConfig = ptvoGetMetaOption(device, 'device_config', ''); if (deviceConfig === '') { - if ((device != null) && device.endpoints) { + if (device != null && device.endpoints) { for (const endpoint of device.endpoints) { const epId = endpoint.ID; const epName = `l${epId}`; @@ -532,14 +573,26 @@ const definitions: Definition[] = [ ptvoSetMetaOption(device, 'device_config', deviceConfig); device.save(); } - } catch (err) {/* do nothing */} + } catch (err) { + /* do nothing */ + } } for (const endpoint of device.endpoints) { if (endpoint.supportsInputCluster('haElectricalMeasurement')) { - endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', {dcCurrentDivisor: 1000, dcCurrentMultiplier: 1, - dcPowerDivisor: 10, dcPowerMultiplier: 1, dcVoltageDivisor: 100, dcVoltageMultiplier: 1, - acVoltageDivisor: 100, acVoltageMultiplier: 1, acCurrentDivisor: 1000, acCurrentMultiplier: 1, - acPowerDivisor: 10, acPowerMultiplier: 1}); + endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', { + dcCurrentDivisor: 1000, + dcCurrentMultiplier: 1, + dcPowerDivisor: 10, + dcPowerMultiplier: 1, + dcVoltageDivisor: 100, + dcVoltageMultiplier: 1, + acVoltageDivisor: 100, + acVoltageMultiplier: 1, + acCurrentDivisor: 1000, + acCurrentMultiplier: 1, + acPowerDivisor: 10, + acPowerMultiplier: 1, + }); } if (endpoint.supportsInputCluster('seMetering')) { endpoint.saveClusterAttributeKeyValue('seMetering', {divisor: 1000, multiplier: 1}); @@ -588,12 +641,20 @@ const definitions: Definition[] = [ fromZigbee: [fz.DNCKAT_S00X_buttons], extend: [ deviceEndpoints({endpoints: {bottom_left: 1, bottom_right: 2, top_left: 3, top_right: 4}}), - onOff({endpointNames: ['bottom_left', 'bottom_right', 'top_left', 'top_right']})], + onOff({endpointNames: ['bottom_left', 'bottom_right', 'top_left', 'top_right']}), + ], exposes: [ e.action([ - 'release_bottom_left', 'hold_bottom_left', 'release_bottom_right', 'hold_bottom_right', - 'release_top_left', 'hold_top_left', 'release_top_right', 'hold_top_right', - ])], + 'release_bottom_left', + 'hold_bottom_left', + 'release_bottom_right', + 'hold_bottom_right', + 'release_top_left', + 'hold_top_left', + 'release_top_right', + 'hold_top_right', + ]), + ], }, { zigbeeModel: ['ZigUP'], @@ -624,7 +685,11 @@ const definitions: Definition[] = [ configure: async (device, coordinatorEndpoint) => { const firstEndpoint = device.getEndpoint(1); await reporting.bind(firstEndpoint, coordinatorEndpoint, [ - 'genPowerCfg', 'msTemperatureMeasurement', 'msIlluminanceMeasurement', 'msSoilMoisture']); + 'genPowerCfg', + 'msTemperatureMeasurement', + 'msIlluminanceMeasurement', + 'msSoilMoisture', + ]); const overrides = {min: 0, max: 3600, change: 0}; await reporting.batteryVoltage(firstEndpoint, overrides); await reporting.batteryPercentageRemaining(firstEndpoint, overrides); @@ -653,8 +718,13 @@ const definitions: Definition[] = [ exposes: [e.temperature(), e.humidity(), e.battery(), e.soil_moisture(), e.illuminance_lux()], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(10); - await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', - 'msTemperatureMeasurement', 'msRelativeHumidity', 'msSoilMoisture', 'msIlluminanceMeasurement']); + await reporting.bind(endpoint, coordinatorEndpoint, [ + 'genPowerCfg', + 'msTemperatureMeasurement', + 'msRelativeHumidity', + 'msSoilMoisture', + 'msIlluminanceMeasurement', + ]); await reporting.batteryPercentageRemaining(endpoint); await reporting.temperature(endpoint); await reporting.humidity(endpoint); @@ -674,7 +744,9 @@ const definitions: Definition[] = [ ...[e.enum('switch_type_2', exposes.access.ALL, Object.keys(switchTypesList)).withEndpoint('button_2')], ...[e.enum('switch_type_3', exposes.access.ALL, Object.keys(switchTypesList)).withEndpoint('button_3')], ...[e.enum('switch_type_4', exposes.access.ALL, Object.keys(switchTypesList)).withEndpoint('button_4')], - e.battery(), e.action(['single', 'double', 'triple', 'hold', 'release']), e.battery_voltage(), + e.battery(), + e.action(['single', 'double', 'triple', 'hold', 'release']), + e.battery_voltage(), ], meta: {multiEndpoint: true}, endpoint: (device) => { @@ -695,19 +767,14 @@ const definitions: Definition[] = [ quirkAddEndpointCluster({ endpointID: 1, outputClusters: [], - inputClusters: [ - 'genPowerCfg', - 'msTemperatureMeasurement', - 'msRelativeHumidity', - 'hvacUserInterfaceCfg', - ], + inputClusters: ['genPowerCfg', 'msTemperatureMeasurement', 'msRelativeHumidity', 'hvacUserInterfaceCfg'], }), battery(), temperature({reporting: {min: 10, max: 300, change: 10}}), humidity({reporting: {min: 10, max: 300, change: 50}}), enumLookup({ name: 'temperature_display_mode', - lookup: {'celsius': 0, 'fahrenheit': 1}, + lookup: {celsius: 0, fahrenheit: 1}, cluster: 'hvacUserInterfaceCfg', attribute: 'tempDisplayMode', description: 'The units of the temperature displayed on the device screen.', @@ -816,15 +883,8 @@ const definitions: Definition[] = [ extend: [ quirkAddEndpointCluster({ endpointID: 1, - outputClusters: [ - 'hvacUserInterfaceCfg', - ], - inputClusters: [ - 'genPowerCfg', - 'msTemperatureMeasurement', - 'msRelativeHumidity', - 'hvacUserInterfaceCfg', - ], + outputClusters: ['hvacUserInterfaceCfg'], + inputClusters: ['genPowerCfg', 'msTemperatureMeasurement', 'msRelativeHumidity', 'hvacUserInterfaceCfg'], }), battery(), temperature({reporting: {min: 10, max: 300, change: 10}}), @@ -833,7 +893,7 @@ const definitions: Definition[] = [ // For details, see: https://github.com/pvvx/ZigbeeTLc/issues/28#issue-2033984519 enumLookup({ name: 'temperature_display_mode', - lookup: {'celsius': 0, 'fahrenheit': 1}, + lookup: {celsius: 0, fahrenheit: 1}, cluster: 'hvacUserInterfaceCfg', attribute: 'tempDisplayMode', description: 'The units of the temperature displayed on the device screen.', @@ -920,19 +980,14 @@ const definitions: Definition[] = [ quirkAddEndpointCluster({ endpointID: 1, outputClusters: [], - inputClusters: [ - 'genPowerCfg', - 'msTemperatureMeasurement', - 'msRelativeHumidity', - 'hvacUserInterfaceCfg', - ], + inputClusters: ['genPowerCfg', 'msTemperatureMeasurement', 'msRelativeHumidity', 'hvacUserInterfaceCfg'], }), battery({percentage: true}), temperature({reporting: {min: 10, max: 300, change: 10}, access: 'STATE'}), humidity({reporting: {min: 2, max: 300, change: 50}, access: 'STATE'}), enumLookup({ name: 'temperature_display_mode', - lookup: {'celsius': 0, 'fahrenheit': 1}, + lookup: {celsius: 0, fahrenheit: 1}, cluster: 'hvacUserInterfaceCfg', attribute: {ID: 0x0000, type: Zcl.DataType.ENUM8}, description: 'The units of the temperature displayed on the device screen.', @@ -1046,7 +1101,9 @@ const definitions: Definition[] = [ ...[e.enum('switch_type_2', exposes.access.ALL, Object.keys(switchTypesList)).withEndpoint('button_2')], ...[e.enum('switch_type_3', exposes.access.ALL, Object.keys(switchTypesList)).withEndpoint('button_3')], ...[e.enum('switch_type_4', exposes.access.ALL, Object.keys(switchTypesList)).withEndpoint('button_4')], - e.battery(), e.action(['single', 'double', 'triple', 'hold', 'release']), e.battery_voltage(), + e.battery(), + e.action(['single', 'double', 'triple', 'hold', 'release']), + e.battery_voltage(), ], meta: {multiEndpoint: true}, endpoint: (device) => { @@ -1064,11 +1121,20 @@ const definitions: Definition[] = [ description: '2 channel counter', fromZigbee: [fz.ignore_basic_report, fz.battery, fz.ptvo_switch_analog_input, fz.on_off], toZigbee: [tz.ptvo_switch_trigger, tz.ptvo_switch_analog_input, tz.on_off], - exposes: [e.battery(), - e.enum('l3', ea.ALL, ['set']).withDescription('Counter value. Write zero or positive value to set a counter value. ' + - 'Write a negative value to set a wakeup interval in minutes'), - e.enum('l5', ea.ALL, ['set']).withDescription('Counter value. Write zero or positive value to set a counter value. ' + - 'Write a negative value to set a wakeup interval in minutes'), + exposes: [ + e.battery(), + e + .enum('l3', ea.ALL, ['set']) + .withDescription( + 'Counter value. Write zero or positive value to set a counter value. ' + + 'Write a negative value to set a wakeup interval in minutes', + ), + e + .enum('l5', ea.ALL, ['set']) + .withDescription( + 'Counter value. Write zero or positive value to set a counter value. ' + + 'Write a negative value to set a wakeup interval in minutes', + ), e.switch().withEndpoint('l6'), e.battery_voltage(), ], @@ -1083,12 +1149,12 @@ const definitions: Definition[] = [ vendor: 'Alab', description: 'Four channel relay board with four inputs', extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4, 'in1': 5, 'in2': 6, 'in3': 7, 'in4': 8}}), + deviceEndpoints({endpoints: {l1: 1, l2: 2, l3: 3, l4: 4, in1: 5, in2: 6, in3: 7, in4: 8}}), onOff({ powerOnBehavior: false, configureReporting: false, - endpointNames: ['l1', 'l2', 'l3', 'l4']}, - ), + endpointNames: ['l1', 'l2', 'l3', 'l4'], + }), commandsOnOff({endpointNames: ['l1', 'l2', 'l3', 'l4']}), numeric({ name: 'input_state', diff --git a/src/devices/cy_lighting.ts b/src/devices/cy_lighting.ts index b4af9bec85b72..9fabc7a3f9d11 100644 --- a/src/devices/cy_lighting.ts +++ b/src/devices/cy_lighting.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/danalock.ts b/src/devices/danalock.ts index 14774e36f22da..04d0f781ae681 100644 --- a/src/devices/danalock.ts +++ b/src/devices/danalock.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; import * as ota from '../lib/ota'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/danfoss.ts b/src/devices/danfoss.ts index 059040b8dab46..0eac30cb2eb0b 100644 --- a/src/devices/danfoss.ts +++ b/src/devices/danfoss.ts @@ -1,11 +1,12 @@ import {Zcl} from 'zigbee-herdsman'; -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; + import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as ota from '../lib/ota'; import * as constants from '../lib/constants'; +import * as exposes from '../lib/exposes'; +import * as ota from '../lib/ota'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -23,109 +24,189 @@ const definitions: Definition[] = [ {vendor: 'Popp', model: '701721', description: 'Smart thermostat', fingerprint: [{modelID: 'eT093WRO'}, {modelID: 'eT093WRG'}]}, ], meta: {thermostat: {dontMapPIHeatingDemand: true}}, - fromZigbee: [fz.battery, fz.thermostat, fz.thermostat_weekly_schedule, fz.hvac_user_interface, - fz.danfoss_thermostat, fz.danfoss_thermostat_setpoint_scheduled], - toZigbee: [tz.danfoss_thermostat_occupied_heating_setpoint, tz.thermostat_local_temperature, tz.danfoss_mounted_mode_active, - tz.danfoss_mounted_mode_control, tz.danfoss_thermostat_vertical_orientation, tz.danfoss_algorithm_scale_factor, - tz.danfoss_heat_available, tz.danfoss_heat_required, tz.danfoss_day_of_week, tz.danfoss_trigger_time, - tz.danfoss_window_open_internal, tz.danfoss_window_open_external, tz.danfoss_load_estimate, - tz.danfoss_viewing_direction, tz.danfoss_external_measured_room_sensor, tz.danfoss_radiator_covered, - tz.thermostat_keypad_lockout, tz.thermostat_system_mode, tz.danfoss_load_balancing_enable, tz.danfoss_load_room_mean, - tz.thermostat_weekly_schedule, tz.thermostat_clear_weekly_schedule, tz.thermostat_programming_operation_mode, - tz.danfoss_window_open_feature, tz.danfoss_preheat_status, tz.danfoss_adaptation_status, tz.danfoss_adaptation_settings, - tz.danfoss_adaptation_control, tz.danfoss_regulation_setpoint_offset, - tz.danfoss_thermostat_occupied_heating_setpoint_scheduled], + fromZigbee: [ + fz.battery, + fz.thermostat, + fz.thermostat_weekly_schedule, + fz.hvac_user_interface, + fz.danfoss_thermostat, + fz.danfoss_thermostat_setpoint_scheduled, + ], + toZigbee: [ + tz.danfoss_thermostat_occupied_heating_setpoint, + tz.thermostat_local_temperature, + tz.danfoss_mounted_mode_active, + tz.danfoss_mounted_mode_control, + tz.danfoss_thermostat_vertical_orientation, + tz.danfoss_algorithm_scale_factor, + tz.danfoss_heat_available, + tz.danfoss_heat_required, + tz.danfoss_day_of_week, + tz.danfoss_trigger_time, + tz.danfoss_window_open_internal, + tz.danfoss_window_open_external, + tz.danfoss_load_estimate, + tz.danfoss_viewing_direction, + tz.danfoss_external_measured_room_sensor, + tz.danfoss_radiator_covered, + tz.thermostat_keypad_lockout, + tz.thermostat_system_mode, + tz.danfoss_load_balancing_enable, + tz.danfoss_load_room_mean, + tz.thermostat_weekly_schedule, + tz.thermostat_clear_weekly_schedule, + tz.thermostat_programming_operation_mode, + tz.danfoss_window_open_feature, + tz.danfoss_preheat_status, + tz.danfoss_adaptation_status, + tz.danfoss_adaptation_settings, + tz.danfoss_adaptation_control, + tz.danfoss_regulation_setpoint_offset, + tz.danfoss_thermostat_occupied_heating_setpoint_scheduled, + ], exposes: (device, options) => { const maxSetpoint = ['TRV001', 'TRV003'].includes(device?.modelID) ? 32 : 35; return [ e.linkquality(), - e.battery(), e.keypad_lockout(), e.programming_operation_mode(), - e.binary('mounted_mode_active', ea.STATE_GET, true, false) - .withDescription('Is the unit in mounting mode. This is set to `false` for mounted (already on ' + - 'the radiator) or `true` for not mounted (after factory reset)'), - e.binary('mounted_mode_control', ea.ALL, true, false) + e.battery(), + e.keypad_lockout(), + e.programming_operation_mode(), + e + .binary('mounted_mode_active', ea.STATE_GET, true, false) + .withDescription( + 'Is the unit in mounting mode. This is set to `false` for mounted (already on ' + + 'the radiator) or `true` for not mounted (after factory reset)', + ), + e + .binary('mounted_mode_control', ea.ALL, true, false) .withDescription('Set the unit mounting mode. `false` Go to Mounted Mode or `true` Go to Mounting Mode'), - e.binary('thermostat_vertical_orientation', ea.ALL, true, false) - .withDescription('Thermostat Orientation. This is important for the PID in how it assesses temperature. ' + - '`false` Horizontal or `true` Vertical'), - e.binary('viewing_direction', ea.ALL, true, false) - .withDescription('Viewing/display direction, `false` normal or `true` upside-down'), - e.binary('heat_available', ea.ALL, true, false) - .withDescription('Not clear how this affects operation. However, it would appear that the device does not execute any ' + - 'motor functions if this is set to false. This may be a means to conserve battery during periods that the heating ' + - 'system is not energized (e.g. during summer). `false` No Heat Available or `true` Heat Available'), - e.binary('heat_required', ea.STATE_GET, true, false) + e + .binary('thermostat_vertical_orientation', ea.ALL, true, false) + .withDescription( + 'Thermostat Orientation. This is important for the PID in how it assesses temperature. ' + + '`false` Horizontal or `true` Vertical', + ), + e.binary('viewing_direction', ea.ALL, true, false).withDescription('Viewing/display direction, `false` normal or `true` upside-down'), + e + .binary('heat_available', ea.ALL, true, false) + .withDescription( + 'Not clear how this affects operation. However, it would appear that the device does not execute any ' + + 'motor functions if this is set to false. This may be a means to conserve battery during periods that the heating ' + + 'system is not energized (e.g. during summer). `false` No Heat Available or `true` Heat Available', + ), + e + .binary('heat_required', ea.STATE_GET, true, false) .withDescription('Whether or not the unit needs warm water. `false` No Heat Request or `true` Heat Request'), - e.enum('setpoint_change_source', ea.STATE, ['manual', 'schedule', 'externally']) + e + .enum('setpoint_change_source', ea.STATE, ['manual', 'schedule', 'externally']) .withDescription('Values observed are `0` (manual), `1` (schedule) or `2` (externally)'), - e.climate().withSetpoint('occupied_heating_setpoint', 5, maxSetpoint, 0.5).withLocalTemperature().withPiHeatingDemand() - .withSystemMode(['heat']).withRunningState(['idle', 'heat'], ea.STATE), - e.numeric('occupied_heating_setpoint_scheduled', ea.ALL) - .withValueMin(5).withValueMax(maxSetpoint).withValueStep(0.5).withUnit('°C') - .withDescription('Scheduled change of the setpoint. Alternative method for changing the setpoint. In the opposite ' + - 'to occupied_heating_setpoint it does not trigger an aggressive response from the actuator. ' + - '(more suitable for scheduled changes)'), - e.numeric('external_measured_room_sensor', ea.ALL) - .withDescription('The temperature sensor of the TRV is — due to its design — relatively close to the heat source ' + - '(i.e. the hot water in the radiator). Thus there are situations where the `local_temperature` measured by the ' + - 'TRV is not accurate enough: If the radiator is covered behind curtains or furniture, if the room is rather big, or ' + - 'if the radiator itself is big and the flow temperature is high, then the temperature in the room may easily diverge ' + - 'from the `local_temperature` measured by the TRV by 5°C to 8°C. In this case you might choose to use an external ' + - 'room sensor and send the measured value of the external room sensor to the `External_measured_room_sensor` property.' + - 'The way the TRV operates on the `External_measured_room_sensor` depends on the setting of the `Radiator_covered` ' + - 'property: If `Radiator_covered` is `false` (Auto Offset Mode): You *must* set the `External_measured_room_sensor` ' + - 'property *at least* every 3 hours. After 3 hours the TRV disables this function and resets the value of the ' + - '`External_measured_room_sensor` property to -8000 (disabled). You *should* set the `External_measured_room_sensor` ' + - 'property *at most* every 30 minutes or every 0.1K change in measured room temperature.' + - 'If `Radiator_covered` is `true` (Room Sensor Mode): You *must* set the `External_measured_room_sensor` property *at ' + - 'least* every 30 minutes. After 35 minutes the TRV disables this function and resets the value of the ' + - '`External_measured_room_sensor` property to -8000 (disabled). You *should* set the `External_measured_room_sensor` ' + - 'property *at most* every 5 minutes or every 0.1K change in measured room temperature.') - .withValueMin(-8000).withValueMax(3500), - e.binary('radiator_covered', ea.ALL, true, false) - .withDescription('Controls whether the TRV should solely rely on an external room sensor or operate in offset mode. ' + - '`false` = Auto Offset Mode (use this e.g. for exposed radiators) or `true` = Room Sensor Mode (use this e.g. for ' + - 'covered radiators). Please note that this flag only controls how the TRV operates on the value of ' + - '`External_measured_room_sensor`; only setting this flag without setting the `External_measured_room_sensor` ' + - 'has no (noticeable?) effect.'), - e.binary('window_open_feature', ea.ALL, true, false) - .withDescription('Whether or not the window open feature is enabled'), - e.enum('window_open_internal', ea.STATE_GET, - ['quarantine', 'closed', 'hold', 'open', 'external_open']) - .withDescription('0=Quarantine, 1=Windows are closed, 2=Hold - Windows are maybe about to open, ' + - '3=Open window detected, 4=In window open state from external but detected closed locally'), - e.binary('window_open_external', ea.ALL, true, false) - .withDescription('Set if the window is open or close. This setting will trigger a change in the internal ' + - 'window and heating demand. `false` (windows are closed) or `true` (windows are open)'), - e.enum('day_of_week', ea.ALL, - ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'away_or_vacation']) + e + .climate() + .withSetpoint('occupied_heating_setpoint', 5, maxSetpoint, 0.5) + .withLocalTemperature() + .withPiHeatingDemand() + .withSystemMode(['heat']) + .withRunningState(['idle', 'heat'], ea.STATE), + e + .numeric('occupied_heating_setpoint_scheduled', ea.ALL) + .withValueMin(5) + .withValueMax(maxSetpoint) + .withValueStep(0.5) + .withUnit('°C') + .withDescription( + 'Scheduled change of the setpoint. Alternative method for changing the setpoint. In the opposite ' + + 'to occupied_heating_setpoint it does not trigger an aggressive response from the actuator. ' + + '(more suitable for scheduled changes)', + ), + e + .numeric('external_measured_room_sensor', ea.ALL) + .withDescription( + 'The temperature sensor of the TRV is — due to its design — relatively close to the heat source ' + + '(i.e. the hot water in the radiator). Thus there are situations where the `local_temperature` measured by the ' + + 'TRV is not accurate enough: If the radiator is covered behind curtains or furniture, if the room is rather big, or ' + + 'if the radiator itself is big and the flow temperature is high, then the temperature in the room may easily diverge ' + + 'from the `local_temperature` measured by the TRV by 5°C to 8°C. In this case you might choose to use an external ' + + 'room sensor and send the measured value of the external room sensor to the `External_measured_room_sensor` property.' + + 'The way the TRV operates on the `External_measured_room_sensor` depends on the setting of the `Radiator_covered` ' + + 'property: If `Radiator_covered` is `false` (Auto Offset Mode): You *must* set the `External_measured_room_sensor` ' + + 'property *at least* every 3 hours. After 3 hours the TRV disables this function and resets the value of the ' + + '`External_measured_room_sensor` property to -8000 (disabled). You *should* set the `External_measured_room_sensor` ' + + 'property *at most* every 30 minutes or every 0.1K change in measured room temperature.' + + 'If `Radiator_covered` is `true` (Room Sensor Mode): You *must* set the `External_measured_room_sensor` property *at ' + + 'least* every 30 minutes. After 35 minutes the TRV disables this function and resets the value of the ' + + '`External_measured_room_sensor` property to -8000 (disabled). You *should* set the `External_measured_room_sensor` ' + + 'property *at most* every 5 minutes or every 0.1K change in measured room temperature.', + ) + .withValueMin(-8000) + .withValueMax(3500), + e + .binary('radiator_covered', ea.ALL, true, false) + .withDescription( + 'Controls whether the TRV should solely rely on an external room sensor or operate in offset mode. ' + + '`false` = Auto Offset Mode (use this e.g. for exposed radiators) or `true` = Room Sensor Mode (use this e.g. for ' + + 'covered radiators). Please note that this flag only controls how the TRV operates on the value of ' + + '`External_measured_room_sensor`; only setting this flag without setting the `External_measured_room_sensor` ' + + 'has no (noticeable?) effect.', + ), + e.binary('window_open_feature', ea.ALL, true, false).withDescription('Whether or not the window open feature is enabled'), + e + .enum('window_open_internal', ea.STATE_GET, ['quarantine', 'closed', 'hold', 'open', 'external_open']) + .withDescription( + '0=Quarantine, 1=Windows are closed, 2=Hold - Windows are maybe about to open, ' + + '3=Open window detected, 4=In window open state from external but detected closed locally', + ), + e + .binary('window_open_external', ea.ALL, true, false) + .withDescription( + 'Set if the window is open or close. This setting will trigger a change in the internal ' + + 'window and heating demand. `false` (windows are closed) or `true` (windows are open)', + ), + e + .enum('day_of_week', ea.ALL, ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'away_or_vacation']) .withDescription('Exercise day of week: 0=Sun...6=Sat, 7=undefined'), - e.numeric('trigger_time', ea.ALL).withValueMin(0).withValueMax(65535) + e + .numeric('trigger_time', ea.ALL) + .withValueMin(0) + .withValueMax(65535) .withDescription('Exercise trigger time. Minutes since midnight (65535=undefined). Range 0 to 1439'), - e.numeric('algorithm_scale_factor', ea.ALL).withValueMin(1).withValueMax(10) - .withDescription('Scale factor of setpoint filter timeconstant ("aggressiveness" of control algorithm) '+ - '1= Quick ... 5=Moderate ... 10=Slow'), - e.binary('load_balancing_enable', ea.ALL, true, false) - .withDescription('Whether or not the thermostat acts as standalone thermostat or shares load with other ' + - 'thermostats in the room. The gateway must update load_room_mean if enabled.'), - e.numeric('load_room_mean', ea.ALL) + e + .numeric('algorithm_scale_factor', ea.ALL) + .withValueMin(1) + .withValueMax(10) + .withDescription( + 'Scale factor of setpoint filter timeconstant ("aggressiveness" of control algorithm) ' + + '1= Quick ... 5=Moderate ... 10=Slow', + ), + e + .binary('load_balancing_enable', ea.ALL, true, false) + .withDescription( + 'Whether or not the thermostat acts as standalone thermostat or shares load with other ' + + 'thermostats in the room. The gateway must update load_room_mean if enabled.', + ), + e + .numeric('load_room_mean', ea.ALL) .withDescription('Mean radiator load for room calculated by gateway for load balancing purposes (-8000=undefined)') - .withValueMin(-8000).withValueMax(3600), - e.numeric('load_estimate', ea.STATE_GET) - .withDescription('Load estimate on this radiator') - .withValueMin(-8000).withValueMax(3600), - e.binary('preheat_status', ea.STATE_GET, true, false) - .withDescription('Specific for pre-heat running in Zigbee Weekly Schedule mode'), - e.enum('adaptation_run_status', ea.STATE_GET, ['none', 'in_progress', 'found', 'lost']) - .withDescription('Status of adaptation run: None (before first run), In Progress, Valve Characteristic Found, ' + - 'Valve Characteristic Lost'), - e.binary('adaptation_run_settings', ea.ALL, true, false) + .withValueMin(-8000) + .withValueMax(3600), + e.numeric('load_estimate', ea.STATE_GET).withDescription('Load estimate on this radiator').withValueMin(-8000).withValueMax(3600), + e.binary('preheat_status', ea.STATE_GET, true, false).withDescription('Specific for pre-heat running in Zigbee Weekly Schedule mode'), + e + .enum('adaptation_run_status', ea.STATE_GET, ['none', 'in_progress', 'found', 'lost']) + .withDescription( + 'Status of adaptation run: None (before first run), In Progress, Valve Characteristic Found, ' + 'Valve Characteristic Lost', + ), + e + .binary('adaptation_run_settings', ea.ALL, true, false) .withDescription('Automatic adaptation run enabled (the one during the night)'), - e.enum('adaptation_run_control', ea.ALL, ['none', 'initiate_adaptation', 'cancel_adaptation']) + e + .enum('adaptation_run_control', ea.ALL, ['none', 'initiate_adaptation', 'cancel_adaptation']) .withDescription('Adaptation run control: Initiate Adaptation Run or Cancel Adaptation Run'), - e.numeric('regulation_setpoint_offset', ea.ALL) + e + .numeric('regulation_setpoint_offset', ea.ALL) .withDescription('Regulation SetPoint Offset in range -2.5°C to 2.5°C in steps of 0.1°C. Value 2.5°C = 25.') - .withValueMin(-25).withValueMax(25)]; + .withValueMin(-25) + .withValueMax(25), + ]; }, ota: ota.zigbeeOTA, configure: async (device, coordinatorEndpoint) => { @@ -140,66 +221,106 @@ const definitions: Definition[] = [ await reporting.thermostatOccupiedHeatingSetpoint(endpoint); // danfoss attributes - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'danfossMountedModeActive', - minimumReportInterval: constants.repInterval.MINUTE, - maximumReportInterval: constants.repInterval.MAX, - reportableChange: 1, - }], options); - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'danfossWindowOpenInternal', - minimumReportInterval: constants.repInterval.MINUTE, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 1, - }], options); - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'danfossHeatRequired', - minimumReportInterval: constants.repInterval.MINUTE, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 1, - }], options); - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'danfossExternalMeasuredRoomSensor', - minimumReportInterval: constants.repInterval.MINUTE, - maximumReportInterval: constants.repInterval.MAX, - reportableChange: 1, - }], options); - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'danfossAdaptionRunStatus', - minimumReportInterval: constants.repInterval.MINUTE, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 1, - }], options); + await endpoint.configureReporting( + 'hvacThermostat', + [ + { + attribute: 'danfossMountedModeActive', + minimumReportInterval: constants.repInterval.MINUTE, + maximumReportInterval: constants.repInterval.MAX, + reportableChange: 1, + }, + ], + options, + ); + await endpoint.configureReporting( + 'hvacThermostat', + [ + { + attribute: 'danfossWindowOpenInternal', + minimumReportInterval: constants.repInterval.MINUTE, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 1, + }, + ], + options, + ); + await endpoint.configureReporting( + 'hvacThermostat', + [ + { + attribute: 'danfossHeatRequired', + minimumReportInterval: constants.repInterval.MINUTE, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 1, + }, + ], + options, + ); + await endpoint.configureReporting( + 'hvacThermostat', + [ + { + attribute: 'danfossExternalMeasuredRoomSensor', + minimumReportInterval: constants.repInterval.MINUTE, + maximumReportInterval: constants.repInterval.MAX, + reportableChange: 1, + }, + ], + options, + ); + await endpoint.configureReporting( + 'hvacThermostat', + [ + { + attribute: 'danfossAdaptionRunStatus', + minimumReportInterval: constants.repInterval.MINUTE, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 1, + }, + ], + options, + ); try { - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'danfossPreheatStatus', - minimumReportInterval: constants.repInterval.MINUTE, - maximumReportInterval: constants.repInterval.MAX, - reportableChange: 1, - }], options); + await endpoint.configureReporting( + 'hvacThermostat', + [ + { + attribute: 'danfossPreheatStatus', + minimumReportInterval: constants.repInterval.MINUTE, + maximumReportInterval: constants.repInterval.MAX, + reportableChange: 1, + }, + ], + options, + ); } catch (e) { /* not supported by all */ } try { - await endpoint.read('hvacThermostat', [ - 'danfossWindowOpenFeatureEnable', - 'danfossWindowOpenExternal', - 'danfossDayOfWeek', - 'danfossTriggerTime', - 'danfossAlgorithmScaleFactor', - 'danfossHeatAvailable', - 'danfossMountedModeControl', - 'danfossMountedModeActive', - 'danfossExternalMeasuredRoomSensor', - 'danfossRadiatorCovered', - 'danfossLoadBalancingEnable', - 'danfossLoadRoomMean', - 'danfossAdaptionRunControl', - 'danfossAdaptionRunSettings', - 'danfossRegulationSetpointOffset', - ], options); + await endpoint.read( + 'hvacThermostat', + [ + 'danfossWindowOpenFeatureEnable', + 'danfossWindowOpenExternal', + 'danfossDayOfWeek', + 'danfossTriggerTime', + 'danfossAlgorithmScaleFactor', + 'danfossHeatAvailable', + 'danfossMountedModeControl', + 'danfossMountedModeActive', + 'danfossExternalMeasuredRoomSensor', + 'danfossRadiatorCovered', + 'danfossLoadBalancingEnable', + 'danfossLoadRoomMean', + 'danfossAdaptionRunControl', + 'danfossAdaptionRunSettings', + 'danfossRegulationSetpointOffset', + ], + options, + ); } catch (e) { /* not supported by all https://github.com/Koenkk/zigbee2mqtt/issues/11872 */ } @@ -212,9 +333,9 @@ const definitions: Definition[] = [ // Seems that it is bug in Danfoss, device does not asks for the time with binding // So, we need to write time during configure (same as for HEIMAN devices) - const time = Math.round(((new Date()).getTime() - constants.OneJanuary2000) / 1000); + const time = Math.round((new Date().getTime() - constants.OneJanuary2000) / 1000); // Time-master + synchronised - const values = {timeStatus: 3, time: time, timeZone: ((new Date()).getTimezoneOffset() * -1) * 60}; + const values = {timeStatus: 3, time: time, timeZone: new Date().getTimezoneOffset() * -1 * 60}; await endpoint.write('genTime', values); }, }, @@ -257,96 +378,165 @@ const definitions: Definition[] = [ meta: {multiEndpoint: true, thermostat: {dontMapPIHeatingDemand: true}}, endpoint: (device) => { return { - 'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4, 'l5': 5, - 'l6': 6, 'l7': 7, 'l8': 8, 'l9': 9, 'l10': 10, - 'l11': 11, 'l12': 12, 'l13': 13, 'l14': 14, 'l15': 15, 'l16': 232, + l1: 1, + l2: 2, + l3: 3, + l4: 4, + l5: 5, + l6: 6, + l7: 7, + l8: 8, + l9: 9, + l10: 10, + l11: 11, + l12: 12, + l13: 13, + l14: 14, + l15: 15, + l16: 232, }; }, - exposes: [].concat(((endpointsCount) => { - const features = []; - - for (let i = 1; i <= endpointsCount; i++) { - const epName = `l${i}`; - - if (i < 16) { - features.push(e.battery().withEndpoint(epName)); - - features.push(e.climate().withSetpoint('occupied_heating_setpoint', 5, 35, 0.5) - .withLocalTemperature() - .withSystemMode(['heat']) - .withRunningState(['idle', 'heat'], ea.STATE) - .withEndpoint(epName)); - - features.push(e.numeric('abs_min_heat_setpoint_limit', ea.STATE) - .withUnit('°C') - .withEndpoint(epName) - .withDescription('Absolute min temperature allowed on the device')); - features.push(e.numeric('abs_max_heat_setpoint_limit', ea.STATE) - .withUnit('°C') - .withEndpoint(epName) - .withDescription('Absolute max temperature allowed on the device')); - - features.push(e.numeric('min_heat_setpoint_limit', ea.ALL) - .withValueMin(4) - .withValueMax(35) - .withValueStep(0.5) - .withUnit('°C') - .withEndpoint(epName) - .withDescription('Min temperature limit set on the device')); - features.push(e.numeric('max_heat_setpoint_limit', ea.ALL) - .withValueMin(4) - .withValueMax(35) - .withValueStep(0.5) - .withUnit('°C') - .withEndpoint(epName) - .withDescription('Max temperature limit set on the device')); - - features.push(e.enum('setpoint_change_source', ea.STATE, ['manual', 'schedule', 'externally']) - .withEndpoint(epName)); - - features.push(e.enum('output_status', ea.STATE_GET, ['inactive', 'active']) - .withEndpoint(epName) - .withDescription('Actuator status')); - - features.push(e.enum('room_status_code', ea.STATE_GET, ['no_error', 'missing_rt', 'rt_touch_error', - 'floor_sensor_short_circuit', 'floor_sensor_disconnected']) - .withEndpoint(epName) - .withDescription('Thermostat status')); - - features.push(e.enum('room_floor_sensor_mode', ea.STATE_GET, ['comfort', 'floor_only', 'dual_mode']) - .withEndpoint(epName) - .withDescription('Floor sensor mode')); - features.push(e.numeric('floor_min_setpoint', ea.ALL) - .withValueMin(18).withValueMax(35).withValueStep(0.5).withUnit('°C') - .withEndpoint(epName) - .withDescription('Min floor temperature')); - features.push(e.numeric('floor_max_setpoint', ea.ALL) - .withValueMin(18).withValueMax(35).withValueStep(0.5).withUnit('°C') - .withEndpoint(epName) - .withDescription('Max floor temperature')); - - features.push(e.numeric('temperature', ea.STATE_GET) - .withUnit('°C') - .withEndpoint(epName) - .withDescription('Floor temperature')); - } else { - features.push(e.enum('system_status_code', ea.STATE_GET, ['no_error', 'missing_expansion_board', - 'missing_radio_module', 'missing_command_module', 'missing_master_rail', 'missing_slave_rail_no_1', - 'missing_slave_rail_no_2', 'pt1000_input_short_circuit', 'pt1000_input_open_circuit', - 'error_on_one_or_more_output']) - .withEndpoint('l16') - .withDescription('Main Controller Status')); - features.push(e.enum('system_status_water', ea.STATE_GET, ['hot_water_flow_in_pipes', 'cool_water_flow_in_pipes']) - .withEndpoint('l16') - .withDescription('Main Controller Water Status')); - features.push(e.enum('multimaster_role', ea.STATE_GET, ['invalid_unused', 'master', 'slave_1', 'slave_2']) - .withEndpoint('l16') - .withDescription('Main Controller Role')); + exposes: [].concat( + ((endpointsCount) => { + const features = []; + + for (let i = 1; i <= endpointsCount; i++) { + const epName = `l${i}`; + + if (i < 16) { + features.push(e.battery().withEndpoint(epName)); + + features.push( + e + .climate() + .withSetpoint('occupied_heating_setpoint', 5, 35, 0.5) + .withLocalTemperature() + .withSystemMode(['heat']) + .withRunningState(['idle', 'heat'], ea.STATE) + .withEndpoint(epName), + ); + + features.push( + e + .numeric('abs_min_heat_setpoint_limit', ea.STATE) + .withUnit('°C') + .withEndpoint(epName) + .withDescription('Absolute min temperature allowed on the device'), + ); + features.push( + e + .numeric('abs_max_heat_setpoint_limit', ea.STATE) + .withUnit('°C') + .withEndpoint(epName) + .withDescription('Absolute max temperature allowed on the device'), + ); + + features.push( + e + .numeric('min_heat_setpoint_limit', ea.ALL) + .withValueMin(4) + .withValueMax(35) + .withValueStep(0.5) + .withUnit('°C') + .withEndpoint(epName) + .withDescription('Min temperature limit set on the device'), + ); + features.push( + e + .numeric('max_heat_setpoint_limit', ea.ALL) + .withValueMin(4) + .withValueMax(35) + .withValueStep(0.5) + .withUnit('°C') + .withEndpoint(epName) + .withDescription('Max temperature limit set on the device'), + ); + + features.push(e.enum('setpoint_change_source', ea.STATE, ['manual', 'schedule', 'externally']).withEndpoint(epName)); + + features.push( + e.enum('output_status', ea.STATE_GET, ['inactive', 'active']).withEndpoint(epName).withDescription('Actuator status'), + ); + + features.push( + e + .enum('room_status_code', ea.STATE_GET, [ + 'no_error', + 'missing_rt', + 'rt_touch_error', + 'floor_sensor_short_circuit', + 'floor_sensor_disconnected', + ]) + .withEndpoint(epName) + .withDescription('Thermostat status'), + ); + + features.push( + e + .enum('room_floor_sensor_mode', ea.STATE_GET, ['comfort', 'floor_only', 'dual_mode']) + .withEndpoint(epName) + .withDescription('Floor sensor mode'), + ); + features.push( + e + .numeric('floor_min_setpoint', ea.ALL) + .withValueMin(18) + .withValueMax(35) + .withValueStep(0.5) + .withUnit('°C') + .withEndpoint(epName) + .withDescription('Min floor temperature'), + ); + features.push( + e + .numeric('floor_max_setpoint', ea.ALL) + .withValueMin(18) + .withValueMax(35) + .withValueStep(0.5) + .withUnit('°C') + .withEndpoint(epName) + .withDescription('Max floor temperature'), + ); + + features.push( + e.numeric('temperature', ea.STATE_GET).withUnit('°C').withEndpoint(epName).withDescription('Floor temperature'), + ); + } else { + features.push( + e + .enum('system_status_code', ea.STATE_GET, [ + 'no_error', + 'missing_expansion_board', + 'missing_radio_module', + 'missing_command_module', + 'missing_master_rail', + 'missing_slave_rail_no_1', + 'missing_slave_rail_no_2', + 'pt1000_input_short_circuit', + 'pt1000_input_open_circuit', + 'error_on_one_or_more_output', + ]) + .withEndpoint('l16') + .withDescription('Main Controller Status'), + ); + features.push( + e + .enum('system_status_water', ea.STATE_GET, ['hot_water_flow_in_pipes', 'cool_water_flow_in_pipes']) + .withEndpoint('l16') + .withDescription('Main Controller Water Status'), + ); + features.push( + e + .enum('multimaster_role', ea.STATE_GET, ['invalid_unused', 'master', 'slave_1', 'slave_2']) + .withEndpoint('l16') + .withDescription('Main Controller Role'), + ); + } } - } - return features; - })(16)), + return features; + })(16), + ), configure: async (device, coordinatorEndpoint) => { const options = {manufacturerCode: Zcl.ManufacturerCode.DANFOSS_A_S}; @@ -354,22 +544,25 @@ const definitions: Definition[] = [ const endpoint = device.getEndpoint(i); if (typeof endpoint !== 'undefined') { - await reporting.bind(endpoint, coordinatorEndpoint, [ - 'genPowerCfg', - 'hvacThermostat', - ]); + await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'hvacThermostat']); await reporting.batteryPercentageRemaining(endpoint); await reporting.thermostatTemperature(endpoint); await reporting.thermostatOccupiedHeatingSetpoint(endpoint); await reporting.temperature(endpoint, {change: 10}); - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'danfossOutputStatus', - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 1, - }], options); + await endpoint.configureReporting( + 'hvacThermostat', + [ + { + attribute: 'danfossOutputStatus', + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 1, + }, + ], + options, + ); await endpoint.read('genPowerCfg', ['batteryPercentageRemaining']); await endpoint.read('hvacThermostat', [ @@ -382,13 +575,17 @@ const definitions: Definition[] = [ 'maxHeatSetpointLimit', 'systemMode', ]); - await endpoint.read('hvacThermostat', [ - 'danfossRoomStatusCode', - 'danfossOutputStatus', - 'danfossRoomFloorSensorMode', - 'danfossFloorMinSetpoint', - 'danfossFloorMaxSetpoint', - ], options); + await endpoint.read( + 'hvacThermostat', + [ + 'danfossRoomStatusCode', + 'danfossOutputStatus', + 'danfossRoomFloorSensorMode', + 'danfossFloorMinSetpoint', + 'danfossFloorMaxSetpoint', + ], + options, + ); } } @@ -397,11 +594,7 @@ const definitions: Definition[] = [ await reporting.bind(mainController, coordinatorEndpoint, ['haDiagnostic']); - await mainController.read('haDiagnostic', [ - 'danfossSystemStatusCode', - 'danfossSystemStatusWater', - 'danfossMultimasterRole', - ], options); + await mainController.read('haDiagnostic', ['danfossSystemStatusCode', 'danfossSystemStatusWater', 'danfossMultimasterRole'], options); }, }, { @@ -444,82 +637,134 @@ const definitions: Definition[] = [ tz.danfoss_multimaster_role, ], meta: {multiEndpoint: true, thermostat: {dontMapPIHeatingDemand: true}}, - exposes: [].concat(((endpointsCount) => { - const features = []; - - for (let i = 1; i <= endpointsCount; i++) { - if (i < 16) { - const epName = `${i}`; - - features.push(e.battery().withEndpoint(epName)); - - features.push(e.climate() - .withSetpoint('occupied_heating_setpoint', 5, 35, 0.5) - .withLocalTemperature() - .withSystemMode(['heat']) - .withRunningState(['idle', 'heat'], ea.STATE) - .withEndpoint(epName)); - - features.push(e.numeric('min_heat_setpoint_limit', ea.ALL) - .withValueMin(4).withValueMax(35).withValueStep(0.5).withUnit('°C') - .withEndpoint(epName) - .withDescription('Min temperature limit set on the device')); - features.push(e.numeric('max_heat_setpoint_limit', ea.ALL) - .withValueMin(4).withValueMax(35).withValueStep(0.5).withUnit('°C') - .withEndpoint(epName) - .withDescription('Max temperature limit set on the device')); - - features.push(e.enum('setpoint_change_source', ea.STATE, ['manual', 'schedule', 'externally']) - .withEndpoint(epName)); - - features.push(e.enum('output_status', ea.STATE_GET, ['inactive', 'active']) - .withEndpoint(epName) - .withDescription('Actuator status')); - - features.push(e.enum('room_status_code', ea.STATE_GET, ['no_error', 'missing_rt', 'rt_touch_error', - 'floor_sensor_short_circuit', 'floor_sensor_disconnected']) - .withEndpoint(epName) - .withDescription('Thermostat status')); - - features.push(e.enum('room_floor_sensor_mode', ea.STATE_GET, ['comfort', 'floor_only', 'dual_mode']) - .withEndpoint(epName) - .withDescription('Floor sensor mode')); - features.push(e.numeric('floor_min_setpoint', ea.ALL) - .withValueMin(18).withValueMax(35).withValueStep(0.5).withUnit('°C') - .withEndpoint(epName) - .withDescription('Min floor temperature')); - features.push(e.numeric('floor_max_setpoint', ea.ALL) - .withValueMin(18).withValueMax(35).withValueStep(0.5).withUnit('°C') - .withEndpoint(epName) - .withDescription('Max floor temperature')); - - features.push(e.numeric('temperature', ea.STATE_GET) - .withUnit('°C') - .withEndpoint(epName) - .withDescription('Floor temperature')); - - features.push(e.numeric('humidity', ea.STATE_GET) - .withUnit('%') - .withEndpoint(epName) - .withDescription('Humidity')); - } else { - features.push(e.enum('system_status_code', ea.STATE_GET, ['no_error', 'missing_expansion_board', - 'missing_radio_module', 'missing_command_module', 'missing_master_rail', 'missing_slave_rail_no_1', - 'missing_slave_rail_no_2', 'pt1000_input_short_circuit', 'pt1000_input_open_circuit', - 'error_on_one_or_more_output']) - .withEndpoint('232') - .withDescription('Main Controller Status')); - features.push(e.enum('system_status_water', ea.STATE_GET, ['hot_water_flow_in_pipes', 'cool_water_flow_in_pipes']) - .withEndpoint('232') - .withDescription('Main Controller Water Status')); - features.push(e.enum('multimaster_role', ea.STATE_GET, ['invalid_unused', 'master', 'slave_1', 'slave_2']) - .withEndpoint('232') - .withDescription('Main Controller Role')); + exposes: [].concat( + ((endpointsCount) => { + const features = []; + + for (let i = 1; i <= endpointsCount; i++) { + if (i < 16) { + const epName = `${i}`; + + features.push(e.battery().withEndpoint(epName)); + + features.push( + e + .climate() + .withSetpoint('occupied_heating_setpoint', 5, 35, 0.5) + .withLocalTemperature() + .withSystemMode(['heat']) + .withRunningState(['idle', 'heat'], ea.STATE) + .withEndpoint(epName), + ); + + features.push( + e + .numeric('min_heat_setpoint_limit', ea.ALL) + .withValueMin(4) + .withValueMax(35) + .withValueStep(0.5) + .withUnit('°C') + .withEndpoint(epName) + .withDescription('Min temperature limit set on the device'), + ); + features.push( + e + .numeric('max_heat_setpoint_limit', ea.ALL) + .withValueMin(4) + .withValueMax(35) + .withValueStep(0.5) + .withUnit('°C') + .withEndpoint(epName) + .withDescription('Max temperature limit set on the device'), + ); + + features.push(e.enum('setpoint_change_source', ea.STATE, ['manual', 'schedule', 'externally']).withEndpoint(epName)); + + features.push( + e.enum('output_status', ea.STATE_GET, ['inactive', 'active']).withEndpoint(epName).withDescription('Actuator status'), + ); + + features.push( + e + .enum('room_status_code', ea.STATE_GET, [ + 'no_error', + 'missing_rt', + 'rt_touch_error', + 'floor_sensor_short_circuit', + 'floor_sensor_disconnected', + ]) + .withEndpoint(epName) + .withDescription('Thermostat status'), + ); + + features.push( + e + .enum('room_floor_sensor_mode', ea.STATE_GET, ['comfort', 'floor_only', 'dual_mode']) + .withEndpoint(epName) + .withDescription('Floor sensor mode'), + ); + features.push( + e + .numeric('floor_min_setpoint', ea.ALL) + .withValueMin(18) + .withValueMax(35) + .withValueStep(0.5) + .withUnit('°C') + .withEndpoint(epName) + .withDescription('Min floor temperature'), + ); + features.push( + e + .numeric('floor_max_setpoint', ea.ALL) + .withValueMin(18) + .withValueMax(35) + .withValueStep(0.5) + .withUnit('°C') + .withEndpoint(epName) + .withDescription('Max floor temperature'), + ); + + features.push( + e.numeric('temperature', ea.STATE_GET).withUnit('°C').withEndpoint(epName).withDescription('Floor temperature'), + ); + + features.push(e.numeric('humidity', ea.STATE_GET).withUnit('%').withEndpoint(epName).withDescription('Humidity')); + } else { + features.push( + e + .enum('system_status_code', ea.STATE_GET, [ + 'no_error', + 'missing_expansion_board', + 'missing_radio_module', + 'missing_command_module', + 'missing_master_rail', + 'missing_slave_rail_no_1', + 'missing_slave_rail_no_2', + 'pt1000_input_short_circuit', + 'pt1000_input_open_circuit', + 'error_on_one_or_more_output', + ]) + .withEndpoint('232') + .withDescription('Main Controller Status'), + ); + features.push( + e + .enum('system_status_water', ea.STATE_GET, ['hot_water_flow_in_pipes', 'cool_water_flow_in_pipes']) + .withEndpoint('232') + .withDescription('Main Controller Water Status'), + ); + features.push( + e + .enum('multimaster_role', ea.STATE_GET, ['invalid_unused', 'master', 'slave_1', 'slave_2']) + .withEndpoint('232') + .withDescription('Main Controller Role'), + ); + } } - } - return features; - })(16)), + return features; + })(16), + ), configure: async (device, coordinatorEndpoint) => { const options = {manufacturerCode: Zcl.ManufacturerCode.DANFOSS_A_S}; @@ -555,11 +800,7 @@ const definitions: Definition[] = [ 'maxHeatSetpointLimit', 'systemMode', ]); - await endpoint.read('hvacThermostat', [ - 'danfossRoomFloorSensorMode', - 'danfossFloorMinSetpoint', - 'danfossFloorMaxSetpoint', - ], options); + await endpoint.read('hvacThermostat', ['danfossRoomFloorSensorMode', 'danfossFloorMinSetpoint', 'danfossFloorMaxSetpoint'], options); await endpoint.read('hvacUserInterfaceCfg', ['keypadLockout']); await endpoint.read('msTemperatureMeasurement', ['measuredValue']); await endpoint.read('msRelativeHumidity', ['measuredValue']); @@ -568,12 +809,18 @@ const definitions: Definition[] = [ if (typeof mainController == 'undefined') { await endpoint.read('genBasic', ['modelId', 'powerSource']); } else { - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'danfossOutputStatus', - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 1, - }], options); + await endpoint.configureReporting( + 'hvacThermostat', + [ + { + attribute: 'danfossOutputStatus', + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 1, + }, + ], + options, + ); await endpoint.read('hvacThermostat', ['setpointChangeSource']); await endpoint.read('hvacThermostat', ['danfossRoomStatusCode', 'danfossOutputStatus'], options); @@ -584,20 +831,9 @@ const definitions: Definition[] = [ if (typeof mainController != 'undefined') { await reporting.bind(mainController, coordinatorEndpoint, ['genBasic', 'haDiagnostic']); - await mainController.read('genBasic', [ - 'modelId', - 'powerSource', - 'appVersion', - 'stackVersion', - 'hwVersion', - 'dateCode', - ]); + await mainController.read('genBasic', ['modelId', 'powerSource', 'appVersion', 'stackVersion', 'hwVersion', 'dateCode']); - await mainController.read('haDiagnostic', [ - 'danfossSystemStatusCode', - 'danfossSystemStatusWater', - 'danfossMultimasterRole', - ], options); + await mainController.read('haDiagnostic', ['danfossSystemStatusCode', 'danfossSystemStatusWater', 'danfossMultimasterRole'], options); } }, }, diff --git a/src/devices/databyte.ts b/src/devices/databyte.ts index b7207b66ada98..44eddd1e74d1e 100644 --- a/src/devices/databyte.ts +++ b/src/devices/databyte.ts @@ -1,8 +1,8 @@ -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import {Definition, Fz} from '../lib/types'; +import * as exposes from '../lib/exposes'; import {onOff} from '../lib/modernExtend'; +import {Definition, Fz} from '../lib/types'; const ea = exposes.access; const e = exposes.presets; @@ -30,8 +30,7 @@ const definitions: Definition[] = [ description: 'CC2530 based IO Board', fromZigbee: [fz.DTB190502A1], toZigbee: [tz.DTB190502A1_LED], - exposes: [e.binary('led_state', ea.STATE, 'ON', 'OFF'), - e.enum('key_state', ea.STATE, ['KEY_SYS', 'KEY_UP', 'KEY_DOWN', 'KEY_NONE'])], + exposes: [e.binary('led_state', ea.STATE, 'ON', 'OFF'), e.enum('key_state', ea.STATE, ['KEY_SYS', 'KEY_UP', 'KEY_DOWN', 'KEY_NONE'])], }, { zigbeeModel: ['DTB-ED2004-012'], @@ -47,7 +46,9 @@ const definitions: Definition[] = [ description: 'Wall touchsensor with 4 keys', fromZigbee: [fzLocal.DTB2011014, fz.battery], toZigbee: [], - exposes: [e.battery(), e.linkquality(), + exposes: [ + e.battery(), + e.linkquality(), e.binary('key_1', ea.STATE, 'ON', 'OFF'), e.binary('key_2', ea.STATE, 'ON', 'OFF'), e.binary('key_3', ea.STATE, 'ON', 'OFF'), diff --git a/src/devices/datek.ts b/src/devices/datek.ts index 50e1ff3e958a7..2c9798508e6cb 100644 --- a/src/devices/datek.ts +++ b/src/devices/datek.ts @@ -1,11 +1,12 @@ import {Zcl} from 'zigbee-herdsman'; -import * as exposes from '../lib/exposes'; + import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as ota from '../lib/ota'; -import * as reporting from '../lib/reporting'; import * as constants from '../lib/constants'; import {repInterval} from '../lib/constants'; +import * as exposes from '../lib/exposes'; +import * as ota from '../lib/ota'; +import * as reporting from '../lib/reporting'; import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -52,30 +53,32 @@ const definitions: Definition[] = [ } catch (e) { e; } - const payload = [{ - attribute: 'rmsVoltagePhB', - minimumReportInterval: 60, - maximumReportInterval: 3600, - reportableChange: 0, - }, - { - attribute: 'rmsVoltagePhC', - minimumReportInterval: 60, - maximumReportInterval: 3600, - reportableChange: 0, - }, - { - attribute: 'rmsCurrentPhB', - minimumReportInterval: 60, - maximumReportInterval: 3600, - reportableChange: 0, - }, - { - attribute: 'rmsCurrentPhC', - minimumReportInterval: 60, - maximumReportInterval: 3600, - reportableChange: 0, - }]; + const payload = [ + { + attribute: 'rmsVoltagePhB', + minimumReportInterval: 60, + maximumReportInterval: 3600, + reportableChange: 0, + }, + { + attribute: 'rmsVoltagePhC', + minimumReportInterval: 60, + maximumReportInterval: 3600, + reportableChange: 0, + }, + { + attribute: 'rmsCurrentPhB', + minimumReportInterval: 60, + maximumReportInterval: 3600, + reportableChange: 0, + }, + { + attribute: 'rmsCurrentPhC', + minimumReportInterval: 60, + maximumReportInterval: 3600, + reportableChange: 0, + }, + ]; await endpoint.configureReporting('haElectricalMeasurement', payload); await reporting.rmsVoltage(endpoint, {min: 60, max: 3600, change: 0}); await reporting.rmsCurrent(endpoint, {min: 60, max: 3600, change: 0}); @@ -84,16 +87,34 @@ const definitions: Definition[] = [ await reporting.currentSummReceived(endpoint); await reporting.temperature(endpoint, {min: 60, max: 3600, change: 0}); }, - exposes: [e.power(), e.energy(), e.current(), e.voltage(), e.current_phase_b(), e.voltage_phase_b(), e.current_phase_c(), - e.voltage_phase_c(), e.temperature()], + exposes: [ + e.power(), + e.energy(), + e.current(), + e.voltage(), + e.current_phase_b(), + e.voltage_phase_b(), + e.current_phase_c(), + e.voltage_phase_c(), + e.temperature(), + ], }, { zigbeeModel: ['Motion Sensor'], model: 'HSE2927E', vendor: 'Datek', description: 'Eva motion sensor', - fromZigbee: [fz.battery, fz.occupancy, fz.occupancy_timeout, fz.illuminance, fz.temperature, - fz.ias_enroll, fz.ias_occupancy_alarm_1, fz.ias_occupancy_alarm_1_report, fz.led_on_motion], + fromZigbee: [ + fz.battery, + fz.occupancy, + fz.occupancy_timeout, + fz.illuminance, + fz.temperature, + fz.ias_enroll, + fz.ias_occupancy_alarm_1, + fz.ias_occupancy_alarm_1_report, + fz.led_on_motion, + ], toZigbee: [tz.occupancy_timeout, tz.led_on_motion], configure: async (device, coordinatorEndpoint) => { const options = {manufacturerCode: Zcl.ManufacturerCode.DATEK_WIRELESS_AS}; @@ -103,28 +124,43 @@ const definitions: Definition[] = [ await reporting.occupancy(endpoint); await reporting.temperature(endpoint); await reporting.illuminance(endpoint); - const payload = [{ - attribute: {ID: 0x4000, type: 0x10}, - }]; + const payload = [ + { + attribute: {ID: 0x4000, type: 0x10}, + }, + ]; // @ts-expect-error await endpoint.configureReporting('ssIasZone', payload, options); await endpoint.read('ssIasZone', ['iasCieAddr', 'zoneState', 'zoneId']); await endpoint.read('msOccupancySensing', ['pirOToUDelay']); await endpoint.read('ssIasZone', [0x4000], options); }, - exposes: [e.temperature(), e.occupancy(), e.battery_low(), e.illuminance_lux(), e.illuminance(), + exposes: [ + e.temperature(), + e.occupancy(), + e.battery_low(), + e.illuminance_lux(), + e.illuminance(), e.binary('led_on_motion', ea.ALL, true, false).withDescription('Enable/disable LED on motion'), - e.numeric('occupancy_timeout', ea.ALL).withUnit('s').withValueMin(0).withValueMax(65535)], + e.numeric('occupancy_timeout', ea.ALL).withUnit('s').withValueMin(0).withValueMax(65535), + ], }, { zigbeeModel: ['ID Lock 150', 'ID Lock 202'], model: '0402946', vendor: 'Datek', description: 'Zigbee module for ID lock', - fromZigbee: [fz.lock, fz.battery, fz.lock_operation_event, fz.lock_programming_event, - fz.idlock, fz.idlock_fw, fz.lock_pin_code_response], - toZigbee: [tz.lock, tz.lock_sound_volume, tz.idlock_master_pin_mode, tz.idlock_rfid_enable, - tz.idlock_service_mode, tz.idlock_lock_mode, tz.idlock_relock_enabled, tz.pincode_lock], + fromZigbee: [fz.lock, fz.battery, fz.lock_operation_event, fz.lock_programming_event, fz.idlock, fz.idlock_fw, fz.lock_pin_code_response], + toZigbee: [ + tz.lock, + tz.lock_sound_volume, + tz.idlock_master_pin_mode, + tz.idlock_rfid_enable, + tz.idlock_service_mode, + tz.idlock_lock_mode, + tz.idlock_relock_enabled, + tz.pincode_lock, + ], meta: {pinCodeCount: 109}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -132,36 +168,38 @@ const definitions: Definition[] = [ await reporting.bind(endpoint, coordinatorEndpoint, ['closuresDoorLock', 'genPowerCfg']); await reporting.lockState(endpoint); await reporting.batteryPercentageRemaining(endpoint); - const payload = [{ - attribute: {ID: 0x4000, type: 0x10}, - minimumReportInterval: 0, - maximumReportInterval: repInterval.HOUR, - reportableChange: 1, - }, - { - attribute: {ID: 0x4001, type: 0x10}, - minimumReportInterval: 0, - maximumReportInterval: repInterval.HOUR, - reportableChange: 1, - }, - { - attribute: {ID: 0x4003, type: 0x20}, - minimumReportInterval: 0, - maximumReportInterval: repInterval.HOUR, - reportableChange: 1, - }, - { - attribute: {ID: 0x4004, type: 0x20}, - minimumReportInterval: 0, - maximumReportInterval: repInterval.HOUR, - reportableChange: 1, - }, - { - attribute: {ID: 0x4005, type: 0x10}, - minimumReportInterval: 0, - maximumReportInterval: repInterval.HOUR, - reportableChange: 1, - }]; + const payload = [ + { + attribute: {ID: 0x4000, type: 0x10}, + minimumReportInterval: 0, + maximumReportInterval: repInterval.HOUR, + reportableChange: 1, + }, + { + attribute: {ID: 0x4001, type: 0x10}, + minimumReportInterval: 0, + maximumReportInterval: repInterval.HOUR, + reportableChange: 1, + }, + { + attribute: {ID: 0x4003, type: 0x20}, + minimumReportInterval: 0, + maximumReportInterval: repInterval.HOUR, + reportableChange: 1, + }, + { + attribute: {ID: 0x4004, type: 0x20}, + minimumReportInterval: 0, + maximumReportInterval: repInterval.HOUR, + reportableChange: 1, + }, + { + attribute: {ID: 0x4005, type: 0x10}, + minimumReportInterval: 0, + maximumReportInterval: repInterval.HOUR, + reportableChange: 1, + }, + ]; await endpoint.configureReporting('closuresDoorLock', payload, options); await endpoint.read('closuresDoorLock', ['lockState', 'soundVolume', 'doorState']); await endpoint.read('closuresDoorLock', [0x4000, 0x4001, 0x4003, 0x4004, 0x4005], options); @@ -169,7 +207,8 @@ const definitions: Definition[] = [ }, onEvent: async (type, data, device) => { // When we receive a code updated message, lets read the new value - if (data.type === 'commandProgrammingEventNotification' && + if ( + data.type === 'commandProgrammingEventNotification' && data.cluster === 'closuresDoorLock' && data.data && data.data.userid !== undefined && @@ -179,16 +218,23 @@ const definitions: Definition[] = [ await device.endpoints[0].command('closuresDoorLock', 'getPinCode', {userid: data.data.userid}, {}); } }, - exposes: [e.lock(), e.battery(), e.pincode(), e.door_state(), - e.lock_action(), e.lock_action_source_name(), e.lock_action_user(), + exposes: [ + e.lock(), + e.battery(), + e.pincode(), + e.door_state(), + e.lock_action(), + e.lock_action_source_name(), + e.lock_action_user(), e.enum('sound_volume', ea.ALL, constants.lockSoundVolume).withDescription('Sound volume of the lock'), e.binary('master_pin_mode', ea.ALL, true, false).withDescription('Allow Master PIN Unlock'), e.binary('rfid_enable', ea.ALL, true, false).withDescription('Allow RFID to Unlock'), - e.binary('relock_enabled', ea.ALL, true, false).withDescription( 'Allow Auto Re-Lock'), - e.enum('lock_mode', ea.ALL, ['auto_off_away_off', 'auto_on_away_off', 'auto_off_away_on', - 'auto_on_away_on']).withDescription('Lock-Mode of the Lock'), - e.enum('service_mode', ea.ALL, ['deactivated', 'random_pin_1x_use', - 'random_pin_24_hours']).withDescription('Service Mode of the Lock')], + e.binary('relock_enabled', ea.ALL, true, false).withDescription('Allow Auto Re-Lock'), + e + .enum('lock_mode', ea.ALL, ['auto_off_away_off', 'auto_on_away_off', 'auto_off_away_on', 'auto_on_away_on']) + .withDescription('Lock-Mode of the Lock'), + e.enum('service_mode', ea.ALL, ['deactivated', 'random_pin_1x_use', 'random_pin_24_hours']).withDescription('Service Mode of the Lock'), + ], }, { zigbeeModel: ['Water Sensor'], @@ -222,14 +268,15 @@ const definitions: Definition[] = [ meta: {battery: {voltageToPercentage: '3V_2500'}}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); - await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'genBasic', 'genOnOff', - 'genLevelCtrl', 'msTemperatureMeasurement']); + await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'genBasic', 'genOnOff', 'genLevelCtrl', 'msTemperatureMeasurement']); await reporting.batteryVoltage(endpoint); await reporting.temperature(endpoint, {min: constants.repInterval.MINUTES_10, max: constants.repInterval.HOUR, change: 100}); }, - exposes: [e.battery(), e.temperature(), - e.action(['recall_1', 'recall_2', 'recall_3', 'recall_4', 'on', 'off', - 'brightness_move_down', 'brightness_move_up', 'brightness_stop'])], + exposes: [ + e.battery(), + e.temperature(), + e.action(['recall_1', 'recall_2', 'recall_3', 'recall_4', 'on', 'off', 'brightness_move_down', 'brightness_move_up', 'brightness_stop']), + ], }, { zigbeeModel: ['Door/Window Sensor'], diff --git a/src/devices/dawon_dns.ts b/src/devices/dawon_dns.ts index c73e5cf904935..defe8a9937516 100644 --- a/src/devices/dawon_dns.ts +++ b/src/devices/dawon_dns.ts @@ -1,8 +1,8 @@ -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; import {deviceEndpoints, forcePowerSource, onOff} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; import {Definition, Fz, Tz} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -15,13 +15,12 @@ const fzLocal = { const zoneStatus = msg.data.zonestatus; return { card: (zoneStatus & 1) > 0, - battery_low: (zoneStatus & 1<<3) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, }; }, } satisfies Fz.Converter, }; - const tzLocal = { dawon_card_holder: { key: ['card'], @@ -117,10 +116,7 @@ const definitions: Definition[] = [ model: 'PM-S240-ZB', vendor: 'Dawon DNS', description: 'IOT smart switch 2 gang without neutral wire', - extend: [ - deviceEndpoints({endpoints: {'top': 1, 'bottom': 2}}), - onOff({endpointNames: ['top', 'bottom'], powerOnBehavior: false}), - ], + extend: [deviceEndpoints({endpoints: {top: 1, bottom: 2}}), onOff({endpointNames: ['top', 'bottom'], powerOnBehavior: false})], }, { zigbeeModel: ['PM-S340-ZB'], @@ -128,7 +124,7 @@ const definitions: Definition[] = [ vendor: 'Dawon DNS', description: 'IOT smart switch 3 gang without neutral wire', extend: [ - deviceEndpoints({endpoints: {'top': 1, 'center': 2, 'bottom': 3}}), + deviceEndpoints({endpoints: {top: 1, center: 2, bottom: 3}}), onOff({endpointNames: ['top', 'center', 'bottom'], powerOnBehavior: false}), ], }, @@ -144,10 +140,7 @@ const definitions: Definition[] = [ model: 'PM-S240R-ZB', vendor: 'Dawon DNS', description: 'IOT smart switch 2 gang without neutral wire', - extend: [ - deviceEndpoints({endpoints: {'top': 1, 'bottom': 2}}), - onOff({endpointNames: ['top', 'bottom'], powerOnBehavior: false}), - ], + extend: [deviceEndpoints({endpoints: {top: 1, bottom: 2}}), onOff({endpointNames: ['top', 'bottom'], powerOnBehavior: false})], }, { zigbeeModel: ['PM-S340R-ZB'], @@ -155,7 +148,7 @@ const definitions: Definition[] = [ vendor: 'Dawon DNS', description: 'IOT smart switch 3 gang without neutral wire', extend: [ - deviceEndpoints({endpoints: {'top': 1, 'center': 2, 'bottom': 3}}), + deviceEndpoints({endpoints: {top: 1, center: 2, bottom: 3}}), onOff({endpointNames: ['top', 'center', 'bottom'], powerOnBehavior: false}), ], }, @@ -172,7 +165,7 @@ const definitions: Definition[] = [ vendor: 'Dawon DNS', description: 'IOT smart switch 2 gang without neutral wire', extend: [ - deviceEndpoints({endpoints: {'top': 1, 'bottom': 2}}), + deviceEndpoints({endpoints: {top: 1, bottom: 2}}), onOff({endpointNames: ['top', 'bottom'], powerOnBehavior: false}), forcePowerSource({powerSource: 'Mains (single phase)'}), ], @@ -183,7 +176,7 @@ const definitions: Definition[] = [ vendor: 'Dawon DNS', description: 'IOT smart switch 3 gang without neutral wire', extend: [ - deviceEndpoints({endpoints: {'top': 1, 'center': 2, 'bottom': 3}}), + deviceEndpoints({endpoints: {top: 1, center: 2, bottom: 3}}), onOff({endpointNames: ['top', 'center', 'bottom'], powerOnBehavior: false}), forcePowerSource({powerSource: 'Mains (single phase)'}), ], @@ -230,12 +223,23 @@ const definitions: Definition[] = [ configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['ssIasZone']); - const payload = [{ - attribute: 'zoneState', minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0}]; + const payload = [ + { + attribute: 'zoneState', + minimumReportInterval: 0, + maximumReportInterval: 3600, + reportableChange: 0, + }, + ]; await endpoint.configureReporting('ssIasZone', payload); }, - exposes: [e.binary('card', ea.STATE, true, false).withAccess(ea.STATE_GET) - .withDescription('Indicates if the card is inserted (= true) or not (= false)'), e.battery_low()], + exposes: [ + e + .binary('card', ea.STATE, true, false) + .withAccess(ea.STATE_GET) + .withDescription('Indicates if the card is inserted (= true) or not (= false)'), + e.battery_low(), + ], }, { zigbeeModel: ['KB-B540R-ZB'], diff --git a/src/devices/develco.ts b/src/devices/develco.ts index 15380e5dd9519..9a301b82ada3c 100644 --- a/src/devices/develco.ts +++ b/src/devices/develco.ts @@ -1,15 +1,16 @@ import {Zcl} from 'zigbee-herdsman'; -import {Definition, Fz, Tz, Zh, KeyValue} from '../lib/types'; -import * as exposes from '../lib/exposes'; + import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; import * as constants from '../lib/constants'; +import * as exposes from '../lib/exposes'; +import {logger} from '../lib/logger'; +import {illuminance} from '../lib/modernExtend'; +import * as ota from '../lib/ota'; import * as reporting from '../lib/reporting'; import * as globalStore from '../lib/store'; +import {Definition, Fz, Tz, Zh, KeyValue} from '../lib/types'; import * as utils from '../lib/utils'; -import * as ota from '../lib/ota'; -import {logger} from '../lib/logger'; -import {illuminance} from '../lib/modernExtend'; const e = exposes.presets; const ea = exposes.access; @@ -27,7 +28,7 @@ const develcoLedControlMap = { 0x00: 'off', 0x01: 'fault_only', 0x02: 'motion_only', - 0xFF: 'both', + 0xff: 'both', }; // develco specific converters @@ -37,8 +38,7 @@ const develco = { for (const ep of device.endpoints) { if (ep.supportsInputCluster('genBasic')) { try { - const data = await ep.read('genBasic', ['develcoPrimarySwVersion', 'develcoPrimaryHwVersion'], - manufacturerOptions); + const data = await ep.read('genBasic', ['develcoPrimarySwVersion', 'develcoPrimaryHwVersion'], manufacturerOptions); if (data.hasOwnProperty('develcoPrimarySwVersion')) { device.softwareBuildID = data.develcoPrimarySwVersion.join('.'); @@ -47,7 +47,9 @@ const develco = { if (data.hasOwnProperty('develcoPrimaryHwVersion')) { device.hardwareVersion = data.develcoPrimaryHwVersion.join('.'); } - } catch (error) {/* catch timeouts of sleeping devices */} + } catch (error) { + /* catch timeouts of sleeping devices */ + } break; } } @@ -59,7 +61,7 @@ const develco = { electrical_measurement: { ...fz.electrical_measurement, convert: (model, msg, publish, options, meta) => { - if (msg.data.rmsVoltage !== 0xFFFF && msg.data.rmsCurrent !== 0xFFFF && msg.data.activePower !== -0x8000) { + if (msg.data.rmsVoltage !== 0xffff && msg.data.rmsCurrent !== 0xffff && msg.data.activePower !== -0x8000) { return fz.electrical_measurement.convert(model, msg, publish, options, meta); } }, @@ -70,12 +72,10 @@ const develco = { convert: (model, msg, publish, options, meta) => { const result: KeyValue = {}; if (msg.data.hasOwnProperty('totalActivePower') && msg.data['totalActivePower'] !== -0x80000000) { - result[utils.postfixWithEndpointName('power', msg, model, meta)] = - msg.data['totalActivePower']; + result[utils.postfixWithEndpointName('power', msg, model, meta)] = msg.data['totalActivePower']; } if (msg.data.hasOwnProperty('totalReactivePower') && msg.data['totalReactivePower'] !== -0x80000000) { - result[utils.postfixWithEndpointName('power_reactive', msg, model, meta)] = - msg.data['totalReactivePower']; + result[utils.postfixWithEndpointName('power_reactive', msg, model, meta)] = msg.data['totalReactivePower']; } return result; }, @@ -91,7 +91,7 @@ const develco = { temperature: { ...fz.temperature, convert: (model, msg, publish, options, meta) => { - if (msg.data.measuredValue !== -0x8000 && msg.data.measuredValue !== 0xFFFF) { + if (msg.data.measuredValue !== -0x8000 && msg.data.measuredValue !== 0xffff) { return fz.temperature.convert(model, msg, publish, options, meta); } }, @@ -110,8 +110,7 @@ const develco = { convert: (model, msg, publish, options, meta) => { const result: KeyValue = {}; if (msg.data.hasOwnProperty('develcoPulseConfiguration')) { - result[utils.postfixWithEndpointName('pulse_configuration', msg, model, meta)] = - msg.data['develcoPulseConfiguration']; + result[utils.postfixWithEndpointName('pulse_configuration', msg, model, meta)] = msg.data['develcoPulseConfiguration']; } return result; @@ -123,10 +122,11 @@ const develco = { convert: (model, msg, publish, options, meta) => { const result: KeyValue = {}; if (msg.data.hasOwnProperty('develcoInterfaceMode')) { - result[utils.postfixWithEndpointName('interface_mode', msg, model, meta)] = - constants.develcoInterfaceMode.hasOwnProperty(msg.data['develcoInterfaceMode']) ? - constants.develcoInterfaceMode[msg.data['develcoInterfaceMode']] : - msg.data['develcoInterfaceMode']; + result[utils.postfixWithEndpointName('interface_mode', msg, model, meta)] = constants.develcoInterfaceMode.hasOwnProperty( + msg.data['develcoInterfaceMode'], + ) + ? constants.develcoInterfaceMode[msg.data['develcoInterfaceMode']] + : msg.data['develcoInterfaceMode']; } if (msg.data.hasOwnProperty('status')) { result['battery_low'] = (msg.data.status & 2) > 0; @@ -146,7 +146,7 @@ const develco = { result.reliability = utils.getFromLookup(msg.data['reliability'], lookup); } if (msg.data.hasOwnProperty('statusFlags')) { - result.fault = (msg.data['statusFlags']===1); + result.fault = msg.data['statusFlags'] === 1; } return result; }, @@ -195,7 +195,7 @@ const develco = { * Low batt LED indication–RED LED will blink twice every 60 second. */ const result = await fz.battery.convert(model, msg, publish, options, meta); - if (result) result.battery_low = (result.voltage <= 2500); + if (result) result.battery_low = result.voltage <= 2500; return result; }, } satisfies Fz.Converter, @@ -242,8 +242,8 @@ const develco = { pulse_configuration: { key: ['pulse_configuration'], convertSet: async (entity, key, value, meta) => { - await entity.write('seMetering', {'develcoPulseConfiguration': value}, manufacturerOptions); - return {readAfterWriteTime: 200, state: {'pulse_configuration': value}}; + await entity.write('seMetering', {develcoPulseConfiguration: value}, manufacturerOptions); + return {readAfterWriteTime: 200, state: {pulse_configuration: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('seMetering', ['develcoPulseConfiguration'], manufacturerOptions); @@ -252,9 +252,9 @@ const develco = { interface_mode: { key: ['interface_mode'], convertSet: async (entity, key, value, meta) => { - const payload = {'develcoInterfaceMode': utils.getKey(constants.develcoInterfaceMode, value, undefined, Number)}; + const payload = {develcoInterfaceMode: utils.getKey(constants.develcoInterfaceMode, value, undefined, Number)}; await entity.write('seMetering', payload, manufacturerOptions); - return {readAfterWriteTime: 200, state: {'interface_mode': value}}; + return {readAfterWriteTime: 200, state: {interface_mode: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('seMetering', ['develcoInterfaceMode'], manufacturerOptions); @@ -263,15 +263,15 @@ const develco = { current_summation: { key: ['current_summation'], convertSet: async (entity, key, value, meta) => { - await entity.write('seMetering', {'develcoCurrentSummation': value}, manufacturerOptions); - return {state: {'current_summation': value}}; + await entity.write('seMetering', {develcoCurrentSummation: value}, manufacturerOptions); + return {state: {current_summation: value}}; }, } satisfies Tz.Converter, led_control: { key: ['led_control'], convertSet: async (entity, key, value, meta) => { const ledControl = utils.getKey(develcoLedControlMap, value, value, Number); - await entity.write('genBasic', {'develcoLedControl': ledControl}, manufacturerOptions); + await entity.write('genBasic', {develcoLedControl: ledControl}, manufacturerOptions); return {state: {led_control: value}}; }, convertGet: async (entity, key, meta) => { @@ -286,7 +286,7 @@ const develco = { logger.warning(`Minimum occupancy_timeout is 5, using 5 instead of ${timeoutValue}!`, NS); timeoutValue = 5; } - await entity.write('ssIasZone', {'develcoAlarmOffDelay': timeoutValue}, manufacturerOptions); + await entity.write('ssIasZone', {develcoAlarmOffDelay: timeoutValue}, manufacturerOptions); return {state: {occupancy_timeout: timeoutValue}}; }, convertGet: async (entity, key, meta) => { @@ -465,10 +465,16 @@ const definitions: Definition[] = [ await reporting.readEletricalMeasurementMultiplierDivisors(endpoint); await reporting.rmsVoltage(endpoint); await reporting.rmsCurrent(endpoint); - await endpoint.configureReporting('haElectricalMeasurement', [{attribute: 'totalActivePower', minimumReportInterval: 5, - maximumReportInterval: 3600, reportableChange: 1}], manufacturerOptions); - await endpoint.configureReporting('haElectricalMeasurement', [{attribute: 'totalReactivePower', minimumReportInterval: 5, - maximumReportInterval: 3600, reportableChange: 1}], manufacturerOptions); + await endpoint.configureReporting( + 'haElectricalMeasurement', + [{attribute: 'totalActivePower', minimumReportInterval: 5, maximumReportInterval: 3600, reportableChange: 1}], + manufacturerOptions, + ); + await endpoint.configureReporting( + 'haElectricalMeasurement', + [{attribute: 'totalReactivePower', minimumReportInterval: 5, maximumReportInterval: 3600, reportableChange: 1}], + manufacturerOptions, + ); } catch (e) { e; } @@ -479,10 +485,17 @@ const definitions: Definition[] = [ await reporting.currentSummReceived(endpoint); await develco.configure.read_sw_hw_version(device); }, - exposes: [e.numeric('power', ea.STATE).withUnit('W').withDescription('Total active power'), + exposes: [ + e.numeric('power', ea.STATE).withUnit('W').withDescription('Total active power'), e.numeric('power_reactive', ea.STATE).withUnit('VAr').withDescription('Total reactive power'), - e.energy(), e.current(), e.voltage(), e.current_phase_b(), e.voltage_phase_b(), e.current_phase_c(), - e.voltage_phase_c()], + e.energy(), + e.current(), + e.voltage(), + e.current_phase_b(), + e.voltage_phase_b(), + e.current_phase_c(), + e.voltage_phase_c(), + ], onEvent: async (type, data, device) => { if (type === 'message' && data.type === 'attributeReport' && data.cluster === 'seMetering' && data.data['divisor']) { // Device sends wrong divisor (512) while it should be fixed to 1000 @@ -500,8 +513,15 @@ const definitions: Definition[] = [ {vendor: 'Frient', model: '94430', description: 'Smart Intelligent Smoke Alarm'}, {vendor: 'Cavius', model: '2103', description: 'RF SMOKE ALARM, 5 YEAR 65MM'}, ], - fromZigbee: [develco.fz.temperature, fz.battery, fz.ias_smoke_alarm_1_develco, fz.ignore_basic_report, - fz.ias_enroll, fz.ias_wd, develco.fz.fault_status], + fromZigbee: [ + develco.fz.temperature, + fz.battery, + fz.ias_smoke_alarm_1_develco, + fz.ignore_basic_report, + fz.ias_enroll, + fz.ias_wd, + develco.fz.fault_status, + ], toZigbee: [tz.warning, tz.ias_max_duration, tz.warning_simple], ota: ota.zigbeeOTA, meta: {battery: {voltageToPercentage: '3V_2500'}}, @@ -523,12 +543,19 @@ const definitions: Definition[] = [ endpoint: (device) => { return {default: 35}; }, - exposes: [e.temperature(), e.battery(), e.smoke(), e.battery_low(), e.test(), + exposes: [ + e.temperature(), + e.battery(), + e.smoke(), + e.battery_low(), + e.test(), e.numeric('max_duration', ea.ALL).withUnit('s').withValueMin(0).withValueMax(600).withDescription('Duration of Siren'), e.binary('alarm', ea.SET, 'START', 'OFF').withDescription('Manual Start of Siren'), - e.enum('reliability', ea.STATE, ['no_fault_detected', 'unreliable_other', 'process_error']) + e + .enum('reliability', ea.STATE, ['no_fault_detected', 'unreliable_other', 'process_error']) .withDescription('Indicates reason if any fault'), - e.binary('fault', ea.STATE, true, false).withDescription('Indicates whether the device are in fault state')], + e.binary('fault', ea.STATE, true, false).withDescription('Indicates whether the device are in fault state'), + ], }, { zigbeeModel: ['SPLZB-141'], @@ -560,11 +587,16 @@ const definitions: Definition[] = [ model: 'HESZB-120', vendor: 'Develco', description: 'Fire detector with siren', - whiteLabel: [ - {vendor: 'Frient', model: '94431', description: 'Smart Intelligent Heat Alarm'}, + whiteLabel: [{vendor: 'Frient', model: '94431', description: 'Smart Intelligent Heat Alarm'}], + fromZigbee: [ + develco.fz.temperature, + fz.battery, + fz.ias_smoke_alarm_1_develco, + fz.ignore_basic_report, + fz.ias_enroll, + fz.ias_wd, + develco.fz.fault_status, ], - fromZigbee: [develco.fz.temperature, fz.battery, fz.ias_smoke_alarm_1_develco, fz.ignore_basic_report, - fz.ias_enroll, fz.ias_wd, develco.fz.fault_status], toZigbee: [tz.warning, tz.ias_max_duration, tz.warning_simple], ota: ota.zigbeeOTA, meta: {battery: {voltageToPercentage: '3V_2500'}}, @@ -586,12 +618,19 @@ const definitions: Definition[] = [ endpoint: (device) => { return {default: 35}; }, - exposes: [e.temperature(), e.battery(), e.smoke(), e.battery_low(), e.test(), + exposes: [ + e.temperature(), + e.battery(), + e.smoke(), + e.battery_low(), + e.test(), e.numeric('max_duration', ea.ALL).withUnit('s').withValueMin(0).withValueMax(600).withDescription('Duration of Siren'), e.binary('alarm', ea.SET, 'START', 'OFF').withDescription('Manual Start of Siren'), - e.enum('reliability', ea.STATE, ['no_fault_detected', 'unreliable_other', 'process_error']) + e + .enum('reliability', ea.STATE, ['no_fault_detected', 'unreliable_other', 'process_error']) .withDescription('Indicates reason if any fault'), - e.binary('fault', ea.STATE, true, false).withDescription('Indicates whether the device are in fault state')], + e.binary('fault', ea.STATE, true, false).withDescription('Indicates whether the device are in fault state'), + ], }, { zigbeeModel: ['WISZB-120'], @@ -682,26 +721,23 @@ const definitions: Definition[] = [ model: 'MOSZB-140', vendor: 'Develco', description: 'Motion sensor', - fromZigbee: [ - develco.fz.temperature, fz.ias_occupancy_alarm_1, fz.battery, - develco.fz.led_control, develco.fz.ias_occupancy_timeout, - ], + fromZigbee: [develco.fz.temperature, fz.ias_occupancy_alarm_1, fz.battery, develco.fz.led_control, develco.fz.ias_occupancy_timeout], extend: [illuminance()], toZigbee: [develco.tz.led_control, develco.tz.ias_occupancy_timeout], exposes: (device, options) => { const dynExposes = []; dynExposes.push(e.occupancy()); if (device && device.softwareBuildID && Number(device.softwareBuildID.split('.')[0]) >= 3) { - dynExposes.push(e.numeric('occupancy_timeout', ea.ALL).withUnit('s'). - withValueMin(5).withValueMax(65535)); + dynExposes.push(e.numeric('occupancy_timeout', ea.ALL).withUnit('s').withValueMin(5).withValueMax(65535)); } dynExposes.push(e.temperature()); dynExposes.push(e.tamper()); dynExposes.push(e.battery_low()); dynExposes.push(e.battery()); if (device && device.softwareBuildID && Number(device.softwareBuildID.split('.')[0]) >= 4) { - dynExposes.push(e.enum('led_control', ea.ALL, ['off', 'fault_only', 'motion_only', 'both']). - withDescription('Control LED indicator usage.')); + dynExposes.push( + e.enum('led_control', ea.ALL, ['off', 'fault_only', 'motion_only', 'both']).withDescription('Control LED indicator usage.'), + ); } dynExposes.push(e.linkquality()); return dynExposes; @@ -714,8 +750,7 @@ const definitions: Definition[] = [ configure: async (device, coordinatorEndpoint) => { const endpoint38 = device.getEndpoint(38); await reporting.bind(endpoint38, coordinatorEndpoint, ['msTemperatureMeasurement']); - await reporting.temperature(endpoint38, - {min: constants.repInterval.MINUTE, max: constants.repInterval.MINUTES_10, change: 100}); + await reporting.temperature(endpoint38, {min: constants.repInterval.MINUTE, max: constants.repInterval.MINUTES_10, change: 100}); const endpoint35 = device.getEndpoint(35); await reporting.bind(endpoint35, coordinatorEndpoint, ['genPowerCfg']); @@ -741,9 +776,7 @@ const definitions: Definition[] = [ exposes: [e.occupancy(), e.battery_low()], }, { - whiteLabel: [ - {vendor: 'Frient', model: 'HMSZB-120', description: 'Temperature & humidity sensor', fingerprint: [{modelID: 'HMSZB-120'}]}, - ], + whiteLabel: [{vendor: 'Frient', model: 'HMSZB-120', description: 'Temperature & humidity sensor', fingerprint: [{modelID: 'HMSZB-120'}]}], zigbeeModel: ['HMSZB-110', 'HMSZB-120'], model: 'HMSZB-110', vendor: 'Develco', @@ -770,7 +803,7 @@ const definitions: Definition[] = [ fromZigbee: [develco.fz.metering, develco.fz.pulse_configuration, develco.fz.interface_mode], toZigbee: [develco.tz.pulse_configuration, develco.tz.interface_mode, develco.tz.current_summation], endpoint: (device) => { - return {'default': 2}; + return {default: 2}; }, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(2); @@ -782,16 +815,20 @@ const definitions: Definition[] = [ e.power(), e.energy(), e.battery_low(), - e.numeric('pulse_configuration', ea.ALL).withValueMin(0).withValueMax(65535) + e + .numeric('pulse_configuration', ea.ALL) + .withValueMin(0) + .withValueMax(65535) .withDescription('Pulses per kwh. Default 1000 imp/kWh. Range 0 to 65535'), - e.enum('interface_mode', ea.ALL, - ['electricity', 'gas', 'water', 'kamstrup-kmp', 'linky', 'IEC62056-21', 'DSMR-2.3', 'DSMR-4.0']) + e + .enum('interface_mode', ea.ALL, ['electricity', 'gas', 'water', 'kamstrup-kmp', 'linky', 'IEC62056-21', 'DSMR-2.3', 'DSMR-4.0']) .withDescription('Operating mode/probe'), - e.numeric('current_summation', ea.SET) - .withDescription('Current summation value sent to the display. e.g. 570 = 0,570 kWh').withValueMin(0) + e + .numeric('current_summation', ea.SET) + .withDescription('Current summation value sent to the display. e.g. 570 = 0,570 kWh') + .withValueMin(0) .withValueMax(268435455), - e.binary('check_meter', ea.STATE, true, false) - .withDescription('Is true if communication problem with meter is experienced'), + e.binary('check_meter', ea.STATE, true, false).withDescription('Is true if communication problem with meter is experienced'), ], }, { @@ -803,7 +840,7 @@ const definitions: Definition[] = [ toZigbee: [tz.on_off], exposes: [e.power(), e.energy(), e.switch()], endpoint: (device) => { - return {'default': 2}; + return {default: 2}; }, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(2); @@ -838,20 +875,29 @@ const definitions: Definition[] = [ toZigbee: [], ota: ota.zigbeeOTA, exposes: [ - e.voc(), e.temperature(), e.humidity(), - e.battery(), e.battery_low(), - e.enum('air_quality', ea.STATE, [ - 'excellent', 'good', 'moderate', - 'poor', 'unhealthy', 'out_of_range', - 'unknown']).withDescription('Measured air quality'), + e.voc(), + e.temperature(), + e.humidity(), + e.battery(), + e.battery_low(), + e + .enum('air_quality', ea.STATE, ['excellent', 'good', 'moderate', 'poor', 'unhealthy', 'out_of_range', 'unknown']) + .withDescription('Measured air quality'), ], meta: {battery: {voltageToPercentage: '3V_2500'}}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(38); - await reporting.bind(endpoint, coordinatorEndpoint, - ['develcoSpecificAirQuality', 'msTemperatureMeasurement', 'msRelativeHumidity', 'genPowerCfg']); - await endpoint.configureReporting('develcoSpecificAirQuality', [{attribute: 'measuredValue', minimumReportInterval: 60, - maximumReportInterval: 3600, reportableChange: 10}], manufacturerOptions); + await reporting.bind(endpoint, coordinatorEndpoint, [ + 'develcoSpecificAirQuality', + 'msTemperatureMeasurement', + 'msRelativeHumidity', + 'genPowerCfg', + ]); + await endpoint.configureReporting( + 'develcoSpecificAirQuality', + [{attribute: 'measuredValue', minimumReportInterval: 60, maximumReportInterval: 3600, reportableChange: 10}], + manufacturerOptions, + ); await reporting.temperature(endpoint, {min: constants.repInterval.MINUTE, max: constants.repInterval.MINUTES_10, change: 10}); await reporting.humidity(endpoint, {min: constants.repInterval.MINUTE, max: constants.repInterval.MINUTES_10, change: 300}); await reporting.batteryVoltage(endpoint, {min: constants.repInterval.HOUR, max: 43200, change: 100}); @@ -882,13 +928,16 @@ const definitions: Definition[] = [ endpoint: (device) => { return {default: 43}; }, - whiteLabel: [ - {model: 'SIRZB-111', vendor: 'Develco', description: 'Customizable siren', fingerprint: [{modelID: 'SIRZB-111'}]}, + whiteLabel: [{model: 'SIRZB-111', vendor: 'Develco', description: 'Customizable siren', fingerprint: [{modelID: 'SIRZB-111'}]}], + exposes: [ + e.battery(), + e.battery_low(), + e.test(), + e.warning(), + e.squawk(), + e.numeric('max_duration', ea.ALL).withUnit('s').withValueMin(0).withValueMax(900).withDescription('Max duration of the siren'), + e.binary('alarm', ea.SET, 'START', 'OFF').withDescription('Manual start of the siren'), ], - exposes: [e.battery(), e.battery_low(), e.test(), e.warning(), e.squawk(), - e.numeric('max_duration', ea.ALL).withUnit('s').withValueMin(0).withValueMax(900) - .withDescription('Max duration of the siren'), - e.binary('alarm', ea.SET, 'START', 'OFF').withDescription('Manual start of the siren')], }, { zigbeeModel: ['KEPZB-110'], @@ -896,15 +945,25 @@ const definitions: Definition[] = [ vendor: 'Develco', description: 'Keypad', whiteLabel: [{vendor: 'Frient', model: 'KEPZB-110'}], - fromZigbee: [fz.command_arm_with_transaction, fz.battery, fz.command_emergency, fz.ias_no_alarm, - fz.ignore_iaszone_attreport, fz.ignore_iasace_commandgetpanelstatus], + fromZigbee: [ + fz.command_arm_with_transaction, + fz.battery, + fz.command_emergency, + fz.ias_no_alarm, + fz.ignore_iaszone_attreport, + fz.ignore_iasace_commandgetpanelstatus, + ], toZigbee: [tz.arm_mode], - exposes: [e.battery(), e.battery_low(), e.battery_voltage(), e.tamper(), + exposes: [ + e.battery(), + e.battery_low(), + e.battery_voltage(), + e.tamper(), e.text('action_code', ea.STATE).withDescription('Pin code introduced.'), e.numeric('action_transaction', ea.STATE).withDescription('Last action transaction number.'), e.text('action_zone', ea.STATE).withDescription('Alarm zone. Default value 23'), - e.action([ - 'disarm', 'arm_day_zones', 'arm_night_zones', 'arm_all_zones', 'exit_delay', 'emergency'])], + e.action(['disarm', 'arm_day_zones', 'arm_night_zones', 'arm_all_zones', 'exit_delay', 'emergency']), + ], ota: ota.zigbeeOTA, meta: {battery: {voltageToPercentage: '4LR6AA1_5v'}}, configure: async (device, coordinatorEndpoint) => { @@ -917,15 +976,19 @@ const definitions: Definition[] = [ return {default: 44}; }, onEvent: async (type, data, device) => { - if (type === 'message' && data.type === 'commandGetPanelStatus' && data.cluster === 'ssIasAce' && - globalStore.hasValue(device.getEndpoint(44), 'panelStatus')) { + if ( + type === 'message' && + data.type === 'commandGetPanelStatus' && + data.cluster === 'ssIasAce' && + globalStore.hasValue(device.getEndpoint(44), 'panelStatus') + ) { const payload = { panelstatus: globalStore.getValue(device.getEndpoint(44), 'panelStatus'), - secondsremain: 0x00, audiblenotif: 0x00, alarmstatus: 0x00, + secondsremain: 0x00, + audiblenotif: 0x00, + alarmstatus: 0x00, }; - await data.endpoint.commandResponse( - 'ssIasAce', 'getPanelStatusRsp', payload, {}, data.meta.zclTransactionSequenceNumber, - ); + await data.endpoint.commandResponse('ssIasAce', 'getPanelStatusRsp', payload, {}, data.meta.zclTransactionSequenceNumber); } }, }, @@ -974,7 +1037,7 @@ const definitions: Definition[] = [ }, endpoint: (device) => { - return {'l1': 112, 'l2': 113, 'l3': 114, 'l4': 115, 'l11': 116, 'l12': 117}; + return {l1: 112, l2: 113, l3: 114, l4: 115, l11: 116, l12: 117}; }, }, { diff --git a/src/devices/digi.ts b/src/devices/digi.ts index 2f5a7085315c7..69fa578687458 100644 --- a/src/devices/digi.ts +++ b/src/devices/digi.ts @@ -1,10 +1,16 @@ import {Definition} from '../lib/types'; const definitions: Definition[] = [ { - fingerprint: [{type: 'Router', manufacturerID: 4126, endpoints: [ - {ID: 230, profileID: 49413, deviceID: 1, inputClusters: [], outputClusters: []}, - {ID: 232, profileID: 49413, deviceID: 1, inputClusters: [], outputClusters: []}, - ]}], + fingerprint: [ + { + type: 'Router', + manufacturerID: 4126, + endpoints: [ + {ID: 230, profileID: 49413, deviceID: 1, inputClusters: [], outputClusters: []}, + {ID: 232, profileID: 49413, deviceID: 1, inputClusters: [], outputClusters: []}, + ], + }, + ], model: 'XBee', description: 'Router', vendor: 'Digi', diff --git a/src/devices/diyruz.ts b/src/devices/diyruz.ts index 4fc01f765420f..967ea8cbe5c29 100644 --- a/src/devices/diyruz.ts +++ b/src/devices/diyruz.ts @@ -1,11 +1,11 @@ -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; import * as constants from '../lib/constants'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; +import {deviceEndpoints, onOff} from '../lib/modernExtend'; import * as reporting from '../lib/reporting'; import {Definition} from '../lib/types'; -import {deviceEndpoints, onOff} from '../lib/modernExtend'; const e = exposes.presets; const ea = exposes.access; @@ -17,7 +17,7 @@ const definitions: Definition[] = [ vendor: 'DIYRuZ', description: 'DiY 4 Relays + 4 switches + 1 buzzer', extend: [ - deviceEndpoints({endpoints: {'bottom_left': 1, 'bottom_right': 2, 'top_left': 3, 'top_right': 4, 'center': 5}}), + deviceEndpoints({endpoints: {bottom_left: 1, bottom_right: 2, top_left: 3, top_right: 4, center: 5}}), onOff({endpointNames: ['bottom_left', 'bottom_right', 'top_left', 'top_right', 'center']}), ], }, @@ -31,8 +31,26 @@ const definitions: Definition[] = [ exposes: [e.battery()], endpoint: (device) => { return { - btn_1: 1, btn_2: 2, btn_3: 3, btn_4: 4, btn_5: 5, btn_6: 6, btn_7: 7, btn_8: 8, btn_9: 9, btn_10: 10, - btn_11: 11, btn_12: 12, btn_13: 13, btn_14: 14, btn_15: 15, btn_16: 16, btn_17: 17, btn_18: 18, btn_19: 19, btn_20: 20, + btn_1: 1, + btn_2: 2, + btn_3: 3, + btn_4: 4, + btn_5: 5, + btn_6: 6, + btn_7: 7, + btn_8: 8, + btn_9: 9, + btn_10: 10, + btn_11: 11, + btn_12: 12, + btn_13: 13, + btn_14: 14, + btn_15: 15, + btn_16: 16, + btn_17: 17, + btn_18: 18, + btn_19: 19, + btn_20: 20, }; }, }, @@ -63,27 +81,37 @@ const definitions: Definition[] = [ vendor: 'DIYRuZ', description: 'DiY 8/12/20 button keypad', fromZigbee: [fz.diyruz_freepad_clicks, fz.diyruz_freepad_config, fz.battery], - exposes: [e.battery(), - e.action(['*_single', '*_double', '*_triple', '*_quadruple', '*_release', '*_hold'])].concat(((enpoinsCount) => { - const features = []; - for (let i = 1; i <= enpoinsCount; i++) { - const epName = `button_${i}`; - features.push( - e.enum('switch_type', ea.ALL, ['toggle', 'momentary', 'multifunction']).withEndpoint(epName)); - features.push(e.enum('switch_actions', ea.ALL, ['on', 'off', 'toggle']).withEndpoint(epName)); - } - return features; - })(20)), + exposes: [e.battery(), e.action(['*_single', '*_double', '*_triple', '*_quadruple', '*_release', '*_hold'])].concat( + ((enpoinsCount) => { + const features = []; + for (let i = 1; i <= enpoinsCount; i++) { + const epName = `button_${i}`; + features.push(e.enum('switch_type', ea.ALL, ['toggle', 'momentary', 'multifunction']).withEndpoint(epName)); + features.push(e.enum('switch_actions', ea.ALL, ['on', 'off', 'toggle']).withEndpoint(epName)); + } + return features; + })(20), + ), toZigbee: [tz.diyruz_freepad_on_off_config], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']); - if (device.applicationVersion < 3) { // Legacy PM2 firmwares - const payload = [{ - attribute: 'batteryPercentageRemaining', minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0, - }, { - attribute: 'batteryVoltage', minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0, - }]; + if (device.applicationVersion < 3) { + // Legacy PM2 firmwares + const payload = [ + { + attribute: 'batteryPercentageRemaining', + minimumReportInterval: 0, + maximumReportInterval: 3600, + reportableChange: 0, + }, + { + attribute: 'batteryVoltage', + minimumReportInterval: 0, + maximumReportInterval: 3600, + reportableChange: 0, + }, + ]; await endpoint.configureReporting('genPowerCfg', payload); } device.endpoints.forEach(async (ep) => { @@ -94,10 +122,26 @@ const definitions: Definition[] = [ }, endpoint: (device) => { return { - button_1: 1, button_2: 2, button_3: 3, button_4: 4, button_5: 5, - button_6: 6, button_7: 7, button_8: 8, button_9: 9, button_10: 10, - button_11: 11, button_12: 12, button_13: 13, button_14: 14, button_15: 15, - button_16: 16, button_17: 17, button_18: 18, button_19: 19, button_20: 20, + button_1: 1, + button_2: 2, + button_3: 3, + button_4: 4, + button_5: 5, + button_6: 6, + button_7: 7, + button_8: 8, + button_9: 9, + button_10: 10, + button_11: 11, + button_12: 12, + button_13: 13, + button_14: 14, + button_15: 15, + button_16: 16, + button_17: 17, + button_18: 18, + button_19: 19, + button_20: 20, }; }, }, @@ -107,26 +151,37 @@ const definitions: Definition[] = [ vendor: 'DIYRuZ', description: 'LeTV 8key FreePad mod', fromZigbee: [fz.diyruz_freepad_clicks, fz.diyruz_freepad_config, fz.battery], - exposes: [e.battery(), e.action(['*_single', '*_double', '*_triple', '*_quadruple', '*_release'])].concat(((enpoinsCount) => { - const features = []; - for (let i = 1; i <= enpoinsCount; i++) { - const epName = `button_${i}`; - features.push( - e.enum('switch_type', ea.ALL, ['toggle', 'momentary', 'multifunction']).withEndpoint(epName)); - features.push(e.enum('switch_actions', ea.ALL, ['on', 'off', 'toggle']).withEndpoint(epName)); - } - return features; - })(8)), + exposes: [e.battery(), e.action(['*_single', '*_double', '*_triple', '*_quadruple', '*_release'])].concat( + ((enpoinsCount) => { + const features = []; + for (let i = 1; i <= enpoinsCount; i++) { + const epName = `button_${i}`; + features.push(e.enum('switch_type', ea.ALL, ['toggle', 'momentary', 'multifunction']).withEndpoint(epName)); + features.push(e.enum('switch_actions', ea.ALL, ['on', 'off', 'toggle']).withEndpoint(epName)); + } + return features; + })(8), + ), toZigbee: [tz.diyruz_freepad_on_off_config], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']); - if (device.applicationVersion < 3) { // Legacy PM2 firmwares - const payload = [{ - attribute: 'batteryPercentageRemaining', minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0, - }, { - attribute: 'batteryVoltage', minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0, - }]; + if (device.applicationVersion < 3) { + // Legacy PM2 firmwares + const payload = [ + { + attribute: 'batteryPercentageRemaining', + minimumReportInterval: 0, + maximumReportInterval: 3600, + reportableChange: 0, + }, + { + attribute: 'batteryVoltage', + minimumReportInterval: 0, + maximumReportInterval: 3600, + reportableChange: 0, + }, + ]; await endpoint.configureReporting('genPowerCfg', payload); } device.endpoints.forEach(async (ep) => { @@ -145,29 +200,36 @@ const definitions: Definition[] = [ vendor: 'DIYRuZ', description: 'DiY Geiger counter', fromZigbee: [fz.diyruz_geiger, fz.command_on, fz.command_off, fz.diyruz_geiger_config], - exposes: [e.action(['on', 'off']), - e.numeric('radioactive_events_per_minute', ea.STATE).withUnit('rpm') - .withDescription('Current count radioactive pulses per minute'), + exposes: [ + e.action(['on', 'off']), + e.numeric('radioactive_events_per_minute', ea.STATE).withUnit('rpm').withDescription('Current count radioactive pulses per minute'), e.numeric('radiation_dose_per_hour', ea.STATE).withUnit('μR/h').withDescription('Current radiation level'), e.binary('led_feedback', ea.ALL, 'ON', 'OFF').withDescription('Enable LED feedback'), e.binary('buzzer_feedback', ea.ALL, 'ON', 'OFF').withDescription('Enable buzzer feedback'), - e.numeric('alert_threshold', ea.ALL).withUnit('μR/h').withDescription('Critical radiation level') - .withValueMin(0).withValueMax(10000), - e.enum('sensors_type', ea.ALL, ['СБМ-20/СТС-5/BOI-33', 'СБМ-19/СТС-6', 'Others']) - .withDescription('Type of installed tubes'), + e.numeric('alert_threshold', ea.ALL).withUnit('μR/h').withDescription('Critical radiation level').withValueMin(0).withValueMax(10000), + e.enum('sensors_type', ea.ALL, ['СБМ-20/СТС-5/BOI-33', 'СБМ-19/СТС-6', 'Others']).withDescription('Type of installed tubes'), e.numeric('sensors_count', ea.ALL).withDescription('Count of installed tubes').withValueMin(0).withValueMax(50), - e.numeric('sensitivity', ea.ALL).withDescription('This is applicable if tubes type is set to other') - .withValueMin(0).withValueMax(100)], + e.numeric('sensitivity', ea.ALL).withDescription('This is applicable if tubes type is set to other').withValueMin(0).withValueMax(100), + ], toZigbee: [tz.diyruz_geiger_config], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['msIlluminanceMeasurement', 'genOnOff']); const payload = [ - {attribute: {ID: 0xF001, type: 0x21}, minimumReportInterval: 0, maximumReportInterval: constants.repInterval.MINUTE, - reportableChange: 0}, - {attribute: {ID: 0xF002, type: 0x23}, minimumReportInterval: 0, maximumReportInterval: constants.repInterval.MINUTE, - reportableChange: 0}]; + { + attribute: {ID: 0xf001, type: 0x21}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.MINUTE, + reportableChange: 0, + }, + { + attribute: {ID: 0xf002, type: 0x23}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.MINUTE, + reportableChange: 0, + }, + ]; await endpoint.configureReporting('msIlluminanceMeasurement', payload); }, }, @@ -200,14 +262,19 @@ const definitions: Definition[] = [ toZigbee: [], meta: {multiEndpoint: true, multiEndpointSkip: ['humidity']}, endpoint: (device) => { - return {'bme': 1, 'ds': 2}; + return {bme: 1, ds: 2}; }, configure: async (device, coordinatorEndpoint) => { const firstEndpoint = device.getEndpoint(1); const secondEndpoint = device.getEndpoint(2); await reporting.bind(firstEndpoint, coordinatorEndpoint, [ - 'genPowerCfg', 'msTemperatureMeasurement', 'msRelativeHumidity', 'msPressureMeasurement', - 'msIlluminanceMeasurement', 'msSoilMoisture']); + 'genPowerCfg', + 'msTemperatureMeasurement', + 'msRelativeHumidity', + 'msPressureMeasurement', + 'msIlluminanceMeasurement', + 'msSoilMoisture', + ]); await reporting.bind(secondEndpoint, coordinatorEndpoint, ['msTemperatureMeasurement']); const overrides = {min: 0, max: 3600, change: 0}; await reporting.batteryVoltage(firstEndpoint, overrides); @@ -220,7 +287,12 @@ const definitions: Definition[] = [ await reporting.temperature(secondEndpoint, overrides); await firstEndpoint.read('msPressureMeasurement', ['scale']); }, - exposes: [e.soil_moisture(), e.battery(), e.illuminance(), e.humidity(), e.pressure(), + exposes: [ + e.soil_moisture(), + e.battery(), + e.illuminance(), + e.humidity(), + e.pressure(), e.temperature().withEndpoint('ds'), e.temperature().withEndpoint('bme'), ], @@ -230,8 +302,16 @@ const definitions: Definition[] = [ model: 'DIYRuZ_AirSense', vendor: 'DIYRuZ', description: 'Air quality sensor', - fromZigbee: [fz.temperature, fz.humidity, fz.co2, fz.pressure, fz.diyruz_airsense_config_co2, - fz.diyruz_airsense_config_temp, fz.diyruz_airsense_config_pres, fz.diyruz_airsense_config_hum], + fromZigbee: [ + fz.temperature, + fz.humidity, + fz.co2, + fz.pressure, + fz.diyruz_airsense_config_co2, + fz.diyruz_airsense_config_temp, + fz.diyruz_airsense_config_pres, + fz.diyruz_airsense_config_hum, + ], toZigbee: [tz.diyruz_airsense_config], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -244,19 +324,19 @@ const definitions: Definition[] = [ } await endpoint.read('msPressureMeasurement', ['scale']); }, - exposes: [e.co2(), e.temperature(), e.humidity(), e.pressure(), + exposes: [ + e.co2(), + e.temperature(), + e.humidity(), + e.pressure(), e.binary('led_feedback', ea.ALL, 'ON', 'OFF').withDescription('Enable LEDs feedback'), e.binary('enable_abc', ea.ALL, 'ON', 'OFF').withDescription('Enable ABC (Automatic Baseline Correction)'), - e.numeric('threshold1', ea.ALL).withUnit('ppm').withDescription('Warning (LED2) CO2 level') - .withValueMin(0).withValueMax(50000), - e.numeric('threshold2', ea.ALL).withUnit('ppm').withDescription('Critical (LED3) CO2 level') - .withValueMin(0).withValueMax(50000), - e.numeric('temperature_offset', ea.ALL).withUnit('°C').withDescription('Adjust temperature') - .withValueMin(-20).withValueMax(20), - e.numeric('humidity_offset', ea.ALL).withUnit('%').withDescription('Adjust humidity') - .withValueMin(-50).withValueMax(50), - e.numeric('pressure_offset', ea.ALL).withUnit('hPa').withDescription('Adjust pressure') - .withValueMin(-1000).withValueMax(1000)], + e.numeric('threshold1', ea.ALL).withUnit('ppm').withDescription('Warning (LED2) CO2 level').withValueMin(0).withValueMax(50000), + e.numeric('threshold2', ea.ALL).withUnit('ppm').withDescription('Critical (LED3) CO2 level').withValueMin(0).withValueMax(50000), + e.numeric('temperature_offset', ea.ALL).withUnit('°C').withDescription('Adjust temperature').withValueMin(-20).withValueMax(20), + e.numeric('humidity_offset', ea.ALL).withUnit('%').withDescription('Adjust humidity').withValueMin(-50).withValueMax(50), + e.numeric('pressure_offset', ea.ALL).withUnit('hPa').withDescription('Adjust pressure').withValueMin(-1000).withValueMax(1000), + ], }, { zigbeeModel: ['DIY_Zintercom'], @@ -270,29 +350,21 @@ const definitions: Definition[] = [ await reporting.bind(firstEndpoint, coordinatorEndpoint, ['closuresDoorLock', 'genPowerCfg']); const payload1 = [ {attribute: 'batteryPercentageRemaining', minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0}, - {attribute: 'batteryVoltage', minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0}]; + {attribute: 'batteryVoltage', minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0}, + ]; await firstEndpoint.configureReporting('genPowerCfg', payload1); - const payload2 = [{attribute: {ID: 0x0050, type: 0x30}, - minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0}]; + const payload2 = [{attribute: {ID: 0x0050, type: 0x30}, minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0}]; await firstEndpoint.configureReporting('closuresDoorLock', payload2); }, exposes: [ - e.enum('state', ea.STATE, ['idle', 'ring', 'talk', 'open', 'drop']) - .withDescription('Current state'), - e.enum('mode', ea.ALL, ['never', 'once', 'always', 'drop']) - .withDescription('Select open mode'), - e.binary('sound', ea.ALL, 'ON', 'OFF').withProperty('sound') - .withDescription('Enable or disable sound'), - e.numeric('time_ring', ea.ALL).withUnit('sec') - .withDescription('Time to ring before answer').withValueMin(0).withValueMax(600), - e.numeric('time_talk', ea.ALL).withUnit('sec') - .withDescription('Time to hold before open').withValueMin(0).withValueMax(600), - e.numeric('time_open', ea.ALL).withUnit('sec') - .withDescription('Time to open before end').withValueMin(0).withValueMax(600), - e.numeric('time_bell', ea.ALL).withUnit('sec') - .withDescription('Time after last bell to finish ring').withValueMin(0).withValueMax(600), - e.numeric('time_report', ea.ALL).withUnit('min') - .withDescription('Reporting interval').withValueMin(0).withValueMax(1440), + e.enum('state', ea.STATE, ['idle', 'ring', 'talk', 'open', 'drop']).withDescription('Current state'), + e.enum('mode', ea.ALL, ['never', 'once', 'always', 'drop']).withDescription('Select open mode'), + e.binary('sound', ea.ALL, 'ON', 'OFF').withProperty('sound').withDescription('Enable or disable sound'), + e.numeric('time_ring', ea.ALL).withUnit('sec').withDescription('Time to ring before answer').withValueMin(0).withValueMax(600), + e.numeric('time_talk', ea.ALL).withUnit('sec').withDescription('Time to hold before open').withValueMin(0).withValueMax(600), + e.numeric('time_open', ea.ALL).withUnit('sec').withDescription('Time to open before end').withValueMin(0).withValueMax(600), + e.numeric('time_bell', ea.ALL).withUnit('sec').withDescription('Time after last bell to finish ring').withValueMin(0).withValueMax(600), + e.numeric('time_report', ea.ALL).withUnit('min').withDescription('Reporting interval').withValueMin(0).withValueMax(1440), e.battery(), ], }, diff --git a/src/devices/dlink.ts b/src/devices/dlink.ts index 08c6b8c2eb6d3..eb9e1adb1daf5 100644 --- a/src/devices/dlink.ts +++ b/src/devices/dlink.ts @@ -1,7 +1,7 @@ -import {Definition, Fz} from '../lib/types'; import fz from '../converters/fromZigbee'; import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition, Fz} from '../lib/types'; const e = exposes.presets; const fzLocal = { @@ -12,9 +12,9 @@ const fzLocal = { const zoneStatus = msg.data.zonestatus; return { contact: !((zoneStatus & 1) > 0), - vibration: (zoneStatus & 1<<1) > 0, - tamper: (zoneStatus & 1<<2) > 0, - battery_low: (zoneStatus & 1<<3) > 0, + vibration: (zoneStatus & (1 << 1)) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, }; }, } satisfies Fz.Converter, diff --git a/src/devices/dnake.ts b/src/devices/dnake.ts index 4c09e08e04a1d..d6d367cf6cc96 100644 --- a/src/devices/dnake.ts +++ b/src/devices/dnake.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/dresden_elektronik.ts b/src/devices/dresden_elektronik.ts index e505420877e08..b1de131fa7dfd 100644 --- a/src/devices/dresden_elektronik.ts +++ b/src/devices/dresden_elektronik.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; -import * as ota from '../lib/ota'; import {battery, deviceEndpoints, light} from '../lib/modernExtend'; +import * as ota from '../lib/ota'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { @@ -10,7 +10,7 @@ const definitions: Definition[] = [ description: 'ZigBee Light Link wireless electronic ballast', ota: ota.zigbeeOTA, extend: [ - deviceEndpoints({endpoints: {'rgb': 10, 'white': 11}}), + deviceEndpoints({endpoints: {rgb: 10, white: 11}}), light({colorTemp: {range: undefined}, color: true, endpointNames: ['rgb', 'white']}), ], }, @@ -42,10 +42,7 @@ const definitions: Definition[] = [ model: 'BN-600078', vendor: 'Dresden Elektronik', description: 'Zigbee controller for 1-10V/PWM', - extend: [ - deviceEndpoints({endpoints: {'l1': 11, 'l2': 12, 'l3': 13, 'l4': 14}}), - light({endpointNames: ['l1', 'l2', 'l3', 'l4']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 11, l2: 12, l3: 13, l4: 14}}), light({endpointNames: ['l1', 'l2', 'l3', 'l4']})], meta: {disableDefaultResponse: true}, }, ]; diff --git a/src/devices/easyaccess.ts b/src/devices/easyaccess.ts index bdb5a2affdc95..f6a98f8703467 100644 --- a/src/devices/easyaccess.ts +++ b/src/devices/easyaccess.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -20,9 +20,13 @@ const definitions: Definition[] = [ await reporting.lockState(endpoint); await reporting.batteryPercentageRemaining(endpoint); }, - exposes: [e.lock(), e.battery(), e.sound_volume(), + exposes: [ + e.lock(), + e.battery(), + e.sound_volume(), e.action(['zigbee_unlock', 'lock', 'rfid_unlock', 'keypad_unlock']), - e.binary('auto_relock', ea.STATE_SET, true, false).withDescription('Auto relock after 7 seconds.')], + e.binary('auto_relock', ea.STATE_SET, true, false).withDescription('Auto relock after 7 seconds.'), + ], whiteLabel: [{vendor: 'Datek Wireless', model: 'EasyCode903G2.1'}], }, ]; diff --git a/src/devices/easyiot.ts b/src/devices/easyiot.ts index 86f108be114a9..95a5803afab5a 100644 --- a/src/devices/easyiot.ts +++ b/src/devices/easyiot.ts @@ -1,8 +1,9 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; -import {Fz, Tz} from '../lib/types'; import * as iconv from 'iconv-lite'; + +import * as exposes from '../lib/exposes'; import {logger} from '../lib/logger'; +import {Definition} from '../lib/types'; +import {Fz, Tz} from '../lib/types'; const NS = 'zhc:easyiot'; const ea = exposes.access; @@ -41,12 +42,15 @@ const tzLocal = { } logger.debug(`Sending IR code: ${value}`, NS); - await entity.command('tunneling', 'transferData', + await entity.command( + 'tunneling', + 'transferData', { - 'tunnelID': 0x0000, - 'data': Buffer.from(value as string, 'hex'), + tunnelID: 0x0000, + data: Buffer.from(value as string, 'hex'), }, - {disableDefaultResponse: true}); + {disableDefaultResponse: true}, + ); logger.debug(`Sending IR command success.`, NS); }, } as Tz.Converter, @@ -59,7 +63,7 @@ const tzLocal = { } logger.debug(`Sending IR code: ${value}`, NS); - const frameHeader = Buffer.from([0xFD]); + const frameHeader = Buffer.from([0xfd]); const gb2312Buffer = iconv.encode(value as string, 'GB2312'); const dataLength = gb2312Buffer.length + 2; @@ -68,12 +72,15 @@ const tzLocal = { const commandByte = Buffer.from([0x01, 0x01]); const protocolFrame = Buffer.concat([frameHeader, dataLengthBuffer, commandByte, gb2312Buffer]); - await entity.command('tunneling', 'transferData', + await entity.command( + 'tunneling', + 'transferData', { - 'tunnelID': 0x0000, - 'data': protocolFrame, + tunnelID: 0x0000, + data: protocolFrame, }, - {disableDefaultResponse: true}); + {disableDefaultResponse: true}, + ); logger.debug(`Sending IR command success.`, NS); }, } as Tz.Converter, @@ -84,7 +91,8 @@ const definitions: Definition[] = [ fingerprint: [{modelID: 'ZB-IR01', manufacturerName: 'easyiot'}], model: 'ZB-IR01', vendor: 'easyiot', - description: 'This is an infrared remote control equipped with a local code library,' + + description: + 'This is an infrared remote control equipped with a local code library,' + 'supporting devices such as air conditioners, televisions, projectors, and more.', fromZigbee: [fzLocal.easyiot_ir_recv_command], toZigbee: [tzLocal.easyiot_ir_send_command], diff --git a/src/devices/eatonhalo_led.ts b/src/devices/eatonhalo_led.ts index 6aaeef9f38423..ab8e024c5b20e 100644 --- a/src/devices/eatonhalo_led.ts +++ b/src/devices/eatonhalo_led.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/echostar.ts b/src/devices/echostar.ts index 75c2afba02373..75f8d549cfd57 100644 --- a/src/devices/echostar.ts +++ b/src/devices/echostar.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/ecodim.ts b/src/devices/ecodim.ts index b4322c69a2300..33f25c7c971b4 100644 --- a/src/devices/ecodim.ts +++ b/src/devices/ecodim.ts @@ -1,45 +1,65 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; +import {Definition} from '../lib/types'; const e = exposes.presets; +import {deviceEndpoints, light} from '../lib/modernExtend'; import * as ota from '../lib/ota'; import * as tuya from '../lib/tuya'; -import {deviceEndpoints, light} from '../lib/modernExtend'; const definitions: Definition[] = [ { fingerprint: [ - {type: 'Router', manufacturerName: 'EcoDim BV', modelID: 'EcoDim-Zigbee 3.0', endpoints: [ - {ID: 1, profileID: 260, inputClusters: [0, 3, 4, 5, 6, 8, 2821, 4096], outputClusters: [25]}, - {ID: 2, profileID: 260, inputClusters: [0, 3, 4, 5, 6, 8], outputClusters: []}, - {ID: 242, profileID: 41440, inputClusters: [], outputClusters: [33]}, - ]}, - {type: 'Router', manufacturerName: 'EcoDim BV', modelID: 'EcoDim-Zigbee 3.0', endpoints: [ - {ID: 1, profileID: 260, inputClusters: [0, 3, 4, 5, 6, 8, 4096], outputClusters: [25]}, - {ID: 2, profileID: 260, inputClusters: [0, 3, 4, 5, 6, 8, 4096], outputClusters: [25]}, - {ID: 242, profileID: 41440, inputClusters: [], outputClusters: [33]}, - ]}, + { + type: 'Router', + manufacturerName: 'EcoDim BV', + modelID: 'EcoDim-Zigbee 3.0', + endpoints: [ + {ID: 1, profileID: 260, inputClusters: [0, 3, 4, 5, 6, 8, 2821, 4096], outputClusters: [25]}, + {ID: 2, profileID: 260, inputClusters: [0, 3, 4, 5, 6, 8], outputClusters: []}, + {ID: 242, profileID: 41440, inputClusters: [], outputClusters: [33]}, + ], + }, + { + type: 'Router', + manufacturerName: 'EcoDim BV', + modelID: 'EcoDim-Zigbee 3.0', + endpoints: [ + {ID: 1, profileID: 260, inputClusters: [0, 3, 4, 5, 6, 8, 4096], outputClusters: [25]}, + {ID: 2, profileID: 260, inputClusters: [0, 3, 4, 5, 6, 8, 4096], outputClusters: [25]}, + {ID: 242, profileID: 41440, inputClusters: [], outputClusters: [33]}, + ], + }, ], model: 'Eco-Dim.05', vendor: 'EcoDim', description: 'LED dimmer duo 2x 0-100W', extend: [ - deviceEndpoints({endpoints: {'left': 2, 'right': 1}}), + deviceEndpoints({endpoints: {left: 2, right: 1}}), light({effect: false, configureReporting: true, endpointNames: ['left', 'right']}), ], }, { fingerprint: [ {type: 'Router', manufacturerID: 4714, modelID: 'Dimmer-Switch-ZB3.0'}, - {type: 'Router', manufacturerName: 'EcoDim BV', modelID: 'EcoDim-Zigbee 3.0', endpoints: [ - {ID: 1, profileID: 260, deviceID: 257, inputClusters: [0, 3, 4, 5, 6, 8, 2821, 4096], outputClusters: [25]}, - {ID: 242, profileID: 41440, deviceID: 97, inputClusters: [], outputClusters: [33]}, - ]}, - {type: 'Router', manufacturerName: 'EcoDim BV', modelID: 'EcoDim-Zigbee 3.0', endpoints: [ - {ID: 1, profileID: 260, deviceID: 257, inputClusters: [0, 3, 4, 5, 6, 8, 2821, 4096], outputClusters: [25]}, - {ID: 67, inputClusters: [], outputClusters: []}, - {ID: 242, profileID: 41440, deviceID: 97, inputClusters: [], outputClusters: [33]}, - ]}, + { + type: 'Router', + manufacturerName: 'EcoDim BV', + modelID: 'EcoDim-Zigbee 3.0', + endpoints: [ + {ID: 1, profileID: 260, deviceID: 257, inputClusters: [0, 3, 4, 5, 6, 8, 2821, 4096], outputClusters: [25]}, + {ID: 242, profileID: 41440, deviceID: 97, inputClusters: [], outputClusters: [33]}, + ], + }, + { + type: 'Router', + manufacturerName: 'EcoDim BV', + modelID: 'EcoDim-Zigbee 3.0', + endpoints: [ + {ID: 1, profileID: 260, deviceID: 257, inputClusters: [0, 3, 4, 5, 6, 8, 2821, 4096], outputClusters: [25]}, + {ID: 67, inputClusters: [], outputClusters: []}, + {ID: 242, profileID: 41440, deviceID: 97, inputClusters: [], outputClusters: [33]}, + ], + }, ], model: 'Eco-Dim.07/Eco-Dim.10', vendor: 'EcoDim', @@ -72,8 +92,21 @@ const definitions: Definition[] = [ vendor: 'EcoDim', description: 'Zigbee 4 button wall switch - white', fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.battery], - exposes: [e.battery(), e.action(['on_1', 'off_1', 'brightness_move_up_1', 'brightness_move_down_1', 'brightness_stop_1', - 'on_2', 'off_2', 'brightness_move_up_2', 'brightness_move_down_2', 'brightness_stop_2'])], + exposes: [ + e.battery(), + e.action([ + 'on_1', + 'off_1', + 'brightness_move_up_1', + 'brightness_move_down_1', + 'brightness_stop_1', + 'on_2', + 'off_2', + 'brightness_move_up_2', + 'brightness_move_down_2', + 'brightness_stop_2', + ]), + ], toZigbee: [], meta: {multiEndpoint: true}, }, @@ -83,8 +116,21 @@ const definitions: Definition[] = [ vendor: 'EcoDim', description: 'Zigbee 4 button wall switch - black', fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.battery], - exposes: [e.battery(), e.action(['on_1', 'off_1', 'brightness_move_up_1', 'brightness_move_down_1', 'brightness_stop_1', - 'on_2', 'off_2', 'brightness_move_up_2', 'brightness_move_down_2', 'brightness_stop_2'])], + exposes: [ + e.battery(), + e.action([ + 'on_1', + 'off_1', + 'brightness_move_up_1', + 'brightness_move_down_1', + 'brightness_stop_1', + 'on_2', + 'off_2', + 'brightness_move_up_2', + 'brightness_move_down_2', + 'brightness_stop_2', + ]), + ], toZigbee: [], meta: {multiEndpoint: true}, }, @@ -94,10 +140,31 @@ const definitions: Definition[] = [ vendor: 'EcoDim', description: 'Zigbee 8 button wall switch - white', fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.battery], - exposes: [e.battery(), e.action(['on_1', 'off_1', 'brightness_move_up_1', 'brightness_move_down_1', 'brightness_stop_1', - 'on_2', 'off_2', 'brightness_move_up_2', 'brightness_move_down_2', 'brightness_stop_2', 'on_3', 'off_3', - 'brightness_move_up_3', 'brightness_move_down_3', 'brightness_stop_3', 'on_4', 'off_4', 'brightness_move_up_4', - 'brightness_move_down_4', 'brightness_stop_4'])], + exposes: [ + e.battery(), + e.action([ + 'on_1', + 'off_1', + 'brightness_move_up_1', + 'brightness_move_down_1', + 'brightness_stop_1', + 'on_2', + 'off_2', + 'brightness_move_up_2', + 'brightness_move_down_2', + 'brightness_stop_2', + 'on_3', + 'off_3', + 'brightness_move_up_3', + 'brightness_move_down_3', + 'brightness_stop_3', + 'on_4', + 'off_4', + 'brightness_move_up_4', + 'brightness_move_down_4', + 'brightness_stop_4', + ]), + ], toZigbee: [], meta: {multiEndpoint: true, battery: {dontDividePercentage: true}}, }, @@ -107,10 +174,31 @@ const definitions: Definition[] = [ vendor: 'EcoDim', description: 'Zigbee 8 button wall switch - black', fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.battery], - exposes: [e.battery(), e.action(['on_1', 'off_1', 'brightness_move_up_1', 'brightness_move_down_1', 'brightness_stop_1', - 'on_2', 'off_2', 'brightness_move_up_2', 'brightness_move_down_2', 'brightness_stop_2', 'on_3', 'off_3', 'brightness_move_up_3', - 'brightness_move_down_3', 'brightness_stop_3', 'on_4', 'off_4', 'brightness_move_up_4', 'brightness_move_down_4', - 'brightness_stop_4'])], + exposes: [ + e.battery(), + e.action([ + 'on_1', + 'off_1', + 'brightness_move_up_1', + 'brightness_move_down_1', + 'brightness_stop_1', + 'on_2', + 'off_2', + 'brightness_move_up_2', + 'brightness_move_down_2', + 'brightness_stop_2', + 'on_3', + 'off_3', + 'brightness_move_up_3', + 'brightness_move_down_3', + 'brightness_stop_3', + 'on_4', + 'off_4', + 'brightness_move_up_4', + 'brightness_move_down_4', + 'brightness_stop_4', + ]), + ], toZigbee: [], meta: {multiEndpoint: true}, }, @@ -122,8 +210,10 @@ const definitions: Definition[] = [ extend: [tuya.modernExtend.tuyaLight()], }, { - fingerprint: [{modelID: 'CCT Light', manufacturerName: 'ZigBee/CCT', manufacturerID: 4137}, - {modelID: 'CCT Light', manufacturerName: 'Astuta/ZB-CCT', manufacturerID: 4137}], + fingerprint: [ + {modelID: 'CCT Light', manufacturerName: 'ZigBee/CCT', manufacturerID: 4137}, + {modelID: 'CCT Light', manufacturerName: 'Astuta/ZB-CCT', manufacturerID: 4137}, + ], model: 'ED-10041', vendor: 'EcoDim', description: 'Zigbee LED filament light dimmable E27, edison ST64, flame 2200K', diff --git a/src/devices/ecolink.ts b/src/devices/ecolink.ts index 0719fbd06a8d4..a74457f80c1ad 100644 --- a/src/devices/ecolink.ts +++ b/src/devices/ecolink.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/ecosmart.ts b/src/devices/ecosmart.ts index 8fbec8a99f1ea..68f42216065e2 100644 --- a/src/devices/ecosmart.ts +++ b/src/devices/ecosmart.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { @@ -24,16 +24,19 @@ const definitions: Definition[] = [ extend: [light({colorTemp: {range: undefined}, color: true})], }, { - // eslint-disable-next-line - zigbeeModel: ['\u0000\u0002\u0000\u0004\u0000\f^I\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e','\u0000\u0002\u0000\u0004^��&\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e'], + zigbeeModel: [ + '\u0000\u0002\u0000\u0004\u0000\f^I\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e', + '\u0000\u0002\u0000\u0004^��&\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e', + ], model: 'D1531', vendor: 'EcoSmart', description: 'A19 bright white bulb', extend: [light()], }, { - // eslint-disable-next-line - zigbeeModel: ['\u0000\u0002\u0000\u0004\u0012 �P\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e'], + zigbeeModel: [ + '\u0000\u0002\u0000\u0004\u0012 �P\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e', + ], model: 'D1532', vendor: 'EcoSmart', description: 'A19 soft white bulb', @@ -47,16 +50,28 @@ const definitions: Definition[] = [ extend: [light({colorTemp: {range: undefined}})], }, { - // eslint-disable-next-line - zigbeeModel: ['\u0000\u0002\u0000\u0004T\u0002\u000eZ\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e', '\u0000\u0002\u0000\u0004\u0000\f]�\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e', '\u0000\u0002\u0000\u0004\"�T\u0004\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e', '\u0000\u0002\u0000\u0004\u0000\f^�\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e','\u0000\u0002\u0000\u0004\u0011�\"�\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e','\u0000\u0002\u0000\u0004� �P\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e','\u0000\u0002\u0000\u0004\u0000\f^\u0014\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e'], + zigbeeModel: [ + '\u0000\u0002\u0000\u0004T\u0002\u000eZ\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e', + '\u0000\u0002\u0000\u0004\u0000\f]�\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e', + '\u0000\u0002\u0000\u0004"�T\u0004\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e', + '\u0000\u0002\u0000\u0004\u0000\f^�\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e', + '\u0000\u0002\u0000\u0004\u0011�"�\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e', + '\u0000\u0002\u0000\u0004� �P\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e', + '\u0000\u0002\u0000\u0004\u0000\f^\u0014\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e', + ], model: 'D1533', vendor: 'EcoSmart', description: 'PAR20/A19 bright white bulb', extend: [light()], }, { - // eslint-disable-next-line - zigbeeModel: ['\u0000\u0002\u0000\u0004�V\u0000\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e', '\u0000\u0002\u0000\u0004��\"�\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e','\u0000\u0002\u0000\u0004�\u0003\"�\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e','\u0000\u0002\u0000\u0004r �P\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e','\u0000\u0002\u0000\u0004b �P\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e'], + zigbeeModel: [ + '\u0000\u0002\u0000\u0004�V\u0000\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e', + '\u0000\u0002\u0000\u0004��"�\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e', + '\u0000\u0002\u0000\u0004�\u0003"�\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e', + '\u0000\u0002\u0000\u0004r �P\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e', + '\u0000\u0002\u0000\u0004b �P\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e', + ], model: 'D1523', vendor: 'EcoSmart', description: 'A19 soft white bulb', diff --git a/src/devices/ecozy.ts b/src/devices/ecozy.ts index 8ca9ee47f2369..d4ed29d7b0fff 100644 --- a/src/devices/ecozy.ts +++ b/src/devices/ecozy.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -14,15 +14,33 @@ const definitions: Definition[] = [ vendor: 'eCozy', description: 'Smart heating thermostat', fromZigbee: [fz.battery, legacy.fz.thermostat_att_report], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_local_temperature_calibration, tz.thermostat_occupancy, - tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint, tz.thermostat_setpoint_raise_lower, - tz.thermostat_remote_sensing, tz.thermostat_control_sequence_of_operation, tz.thermostat_system_mode, - tz.thermostat_weekly_schedule, tz.thermostat_clear_weekly_schedule, tz.thermostat_relay_status_log, - tz.thermostat_pi_heating_demand, tz.thermostat_running_state], - exposes: [e.battery(), e.climate().withSetpoint('occupied_heating_setpoint', 7, 30, 1).withLocalTemperature() - .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat']) - .withLocalTemperatureCalibration() - .withPiHeatingDemand(ea.STATE_GET)], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_local_temperature_calibration, + tz.thermostat_occupancy, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_unoccupied_heating_setpoint, + tz.thermostat_setpoint_raise_lower, + tz.thermostat_remote_sensing, + tz.thermostat_control_sequence_of_operation, + tz.thermostat_system_mode, + tz.thermostat_weekly_schedule, + tz.thermostat_clear_weekly_schedule, + tz.thermostat_relay_status_log, + tz.thermostat_pi_heating_demand, + tz.thermostat_running_state, + ], + exposes: [ + e.battery(), + e + .climate() + .withSetpoint('occupied_heating_setpoint', 7, 30, 1) + .withLocalTemperature() + .withSystemMode(['off', 'auto', 'heat']) + .withRunningState(['idle', 'heat']) + .withLocalTemperatureCalibration() + .withPiHeatingDemand(ea.STATE_GET), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(3); const binds = ['genBasic', 'genPowerCfg', 'genIdentify', 'genTime', 'genPollCtrl', 'hvacThermostat', 'hvacUserInterfaceCfg']; diff --git a/src/devices/edp.ts b/src/devices/edp.ts index e5d443529604e..d528ef9fab0b3 100644 --- a/src/devices/edp.ts +++ b/src/devices/edp.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; import {onOff} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; diff --git a/src/devices/efekta.ts b/src/devices/efekta.ts index f3245cd22fd9f..98c98b6670bfb 100644 --- a/src/devices/efekta.ts +++ b/src/devices/efekta.ts @@ -1,5 +1,5 @@ import {Zcl} from 'zigbee-herdsman'; -import {Definition} from '../lib/types'; + import { deviceEndpoints, temperature, @@ -13,6 +13,7 @@ import { battery, pressure, } from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const defaultReporting = {min: 0, max: 300, change: 0}; const normalReporting = {min: 0, max: 3600, change: 0}; diff --git a/src/devices/eglo.ts b/src/devices/eglo.ts index a1db3fa825909..96917b47976df 100644 --- a/src/devices/eglo.ts +++ b/src/devices/eglo.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; import fz from '../converters/fromZigbee'; import * as exposes from '../lib/exposes'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const e = exposes.presets; @@ -39,19 +39,50 @@ const definitions: Definition[] = [ model: '99099', vendor: 'EGLO', description: '3 groups remote controller', - fromZigbee: [fz.command_on, fz.awox_colors, fz.awox_refresh, fz.awox_refreshColored, fz.command_off, - fz.command_step, fz.command_move, fz.command_move_to_level, fz.command_move_to_color_temp, - fz.command_stop, fz.command_recall, fz.command_step_color_temperature], + fromZigbee: [ + fz.command_on, + fz.awox_colors, + fz.awox_refresh, + fz.awox_refreshColored, + fz.command_off, + fz.command_step, + fz.command_move, + fz.command_move_to_level, + fz.command_move_to_color_temp, + fz.command_stop, + fz.command_recall, + fz.command_step_color_temperature, + ], toZigbee: [], - exposes: [e.action(['on', 'off', 'red', 'refresh', 'refresh_colored', 'blue', 'yellow', - 'green', 'brightness_step_up', 'brightness_step_down', 'brightness_move_up', 'brightness_move_down', 'brightness_stop', - 'recall_1', 'color_temperature_step_up', 'color_temperature_step_down'])], + exposes: [ + e.action([ + 'on', + 'off', + 'red', + 'refresh', + 'refresh_colored', + 'blue', + 'yellow', + 'green', + 'brightness_step_up', + 'brightness_step_down', + 'brightness_move_up', + 'brightness_move_down', + 'brightness_stop', + 'recall_1', + 'color_temperature_step_up', + 'color_temperature_step_down', + ]), + ], }, { fingerprint: [ - {type: 'EndDevice', manufacturerID: 4417, modelID: 'TLSR82xx', endpoints: [ - {ID: 1, profileID: 260, deviceID: 263, inputClusters: [0, 3, 4, 4096], outputClusters: [0, 3, 4, 5, 6, 8, 768, 4096]}, - ]}, + { + type: 'EndDevice', + manufacturerID: 4417, + modelID: 'TLSR82xx', + endpoints: [{ID: 1, profileID: 260, deviceID: 263, inputClusters: [0, 3, 4, 4096], outputClusters: [0, 3, 4, 5, 6, 8, 768, 4096]}], + }, ], model: '99106', vendor: 'EGLO', diff --git a/src/devices/elko.ts b/src/devices/elko.ts index 3da87994d6c99..ad3ab558ccad1 100644 --- a/src/devices/elko.ts +++ b/src/devices/elko.ts @@ -1,10 +1,10 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; import * as constants from '../lib/constants'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; import {light} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const ea = exposes.access; const e = exposes.presets; @@ -37,42 +37,71 @@ const definitions: Definition[] = [ vendor: 'ELKO', description: 'ESH Plus Super TR RF PH', fromZigbee: [fz.elko_thermostat, fz.thermostat], - toZigbee: [tz.thermostat_occupied_heating_setpoint, tz.thermostat_occupied_heating_setpoint, tz.elko_load, - tz.elko_display_text, tz.elko_power_status, tz.elko_external_temp, tz.elko_mean_power, tz.elko_child_lock, tz.elko_frost_guard, - tz.elko_relay_state, tz.elko_sensor_mode, tz.elko_local_temperature_calibration, tz.elko_max_floor_temp, - tz.elko_regulator_mode, tz.elko_regulator_time, tz.elko_night_switching], - exposes: [e.text('display_text', ea.ALL).withDescription('Displayed text on thermostat display (zone). Max 14 characters'), - e.numeric('load', ea.ALL).withUnit('W') - .withDescription('Load in W when heating is on (between 0-2300 W). The thermostat uses the value as input to the ' + - 'mean_power calculation.') - .withValueMin(0).withValueMax(2300), - e.binary('regulator_mode', ea.ALL, 'regulator', 'thermostat') - .withDescription('Device in regulator or thermostat mode.'), - e.numeric('regulator_time', ea.ALL).withUnit('min') - .withValueMin(5).withValueMax(20).withDescription('When device is in regulator mode this controls the time between each ' + - 'in/out connection. When device is in thermostat mode this controls the time between each in/out switch when measured ' + - 'temperature is within +-0.5 °C set temperature. Choose a long time for (slow) concrete floors and a short time for ' + - '(quick) wooden floors.'), - e.climate().withSetpoint('occupied_heating_setpoint', 5, 50, 1) + toZigbee: [ + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_occupied_heating_setpoint, + tz.elko_load, + tz.elko_display_text, + tz.elko_power_status, + tz.elko_external_temp, + tz.elko_mean_power, + tz.elko_child_lock, + tz.elko_frost_guard, + tz.elko_relay_state, + tz.elko_sensor_mode, + tz.elko_local_temperature_calibration, + tz.elko_max_floor_temp, + tz.elko_regulator_mode, + tz.elko_regulator_time, + tz.elko_night_switching, + ], + exposes: [ + e.text('display_text', ea.ALL).withDescription('Displayed text on thermostat display (zone). Max 14 characters'), + e + .numeric('load', ea.ALL) + .withUnit('W') + .withDescription( + 'Load in W when heating is on (between 0-2300 W). The thermostat uses the value as input to the ' + 'mean_power calculation.', + ) + .withValueMin(0) + .withValueMax(2300), + e.binary('regulator_mode', ea.ALL, 'regulator', 'thermostat').withDescription('Device in regulator or thermostat mode.'), + e + .numeric('regulator_time', ea.ALL) + .withUnit('min') + .withValueMin(5) + .withValueMax(20) + .withDescription( + 'When device is in regulator mode this controls the time between each ' + + 'in/out connection. When device is in thermostat mode this controls the time between each in/out switch when measured ' + + 'temperature is within +-0.5 °C set temperature. Choose a long time for (slow) concrete floors and a short time for ' + + '(quick) wooden floors.', + ), + e + .climate() + .withSetpoint('occupied_heating_setpoint', 5, 50, 1) .withLocalTemperature(ea.STATE) .withLocalTemperatureCalibration() - .withSystemMode(['off', 'heat']).withRunningState(['idle', 'heat']), + .withSystemMode(['off', 'heat']) + .withRunningState(['idle', 'heat']), e.temperature_sensor_select(['air', 'floor', 'supervisor_floor']), - e.numeric('floor_temp', ea.STATE_GET).withUnit('°C') - .withDescription('Current temperature measured from the floor sensor'), - e.numeric('max_floor_temp', ea.ALL).withUnit('°C') + e.numeric('floor_temp', ea.STATE_GET).withUnit('°C').withDescription('Current temperature measured from the floor sensor'), + e + .numeric('max_floor_temp', ea.ALL) + .withUnit('°C') .withDescription('Set max floor temperature (between 20-35 °C) when "supervisor_floor" is set') - .withValueMin(20).withValueMax(35), - e.numeric('mean_power', ea.STATE_GET).withUnit('W') - .withDescription('Reports average power usage last 10 minutes'), - e.binary('child_lock', ea.ALL, 'lock', 'unlock') - .withDescription('Enables/disables physical input on the device'), - e.binary('frost_guard', ea.ALL, 'on', 'off') - .withDescription('When frost guard is ON, it is activated when the thermostat is switched OFF with the ON/OFF button.' + - 'At the same time, the display will fade and the text "Frostsikring x °C" appears in the display and remains until the ' + - 'thermostat is switched on again.'), - e.binary('night_switching', ea.ALL, 'on', 'off') - .withDescription('Turn on or off night setting.'), + .withValueMin(20) + .withValueMax(35), + e.numeric('mean_power', ea.STATE_GET).withUnit('W').withDescription('Reports average power usage last 10 minutes'), + e.binary('child_lock', ea.ALL, 'lock', 'unlock').withDescription('Enables/disables physical input on the device'), + e + .binary('frost_guard', ea.ALL, 'on', 'off') + .withDescription( + 'When frost guard is ON, it is activated when the thermostat is switched OFF with the ON/OFF button.' + + 'At the same time, the display will fade and the text "Frostsikring x °C" appears in the display and remains until the ' + + 'thermostat is switched on again.', + ), + e.binary('night_switching', ea.ALL, 'on', 'off').withDescription('Turn on or off night setting.'), ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -84,83 +113,105 @@ const definitions: Definition[] = [ // ELKO attributes // Load value - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'elkoLoad', - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 1, - }]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: 'elkoLoad', + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 1, + }, + ]); // Power status - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'elkoPowerStatus', - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: 'elkoPowerStatus', + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ]); // Power consumption - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'elkoMeanPower', - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 5, - }]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: 'elkoMeanPower', + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 5, + }, + ]); // External temp sensor (floor) - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'elkoExternalTemp', - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 10, - }]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: 'elkoExternalTemp', + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 10, + }, + ]); // Child lock active/inactive - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'elkoChildLock', - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: 'elkoChildLock', + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ]); // Night switching - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'elkoNightSwitching', - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: 'elkoNightSwitching', + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ]); // Frost guard - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'elkoFrostGuard', - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: 'elkoFrostGuard', + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ]); // Heating active/inactive - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'elkoRelayState', - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: 'elkoRelayState', + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ]); // Max floor temp - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'elkoMaxFloorTemp', - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 1, - }]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: 'elkoMaxFloorTemp', + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 1, + }, + ]); // Regulator mode - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'elkoRegulatorMode', - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: 'elkoRegulatorMode', + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ]); // Regulator time - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'elkoRegulatorTime', - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 1, - }]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: 'elkoRegulatorTime', + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 1, + }, + ]); // Trigger read await endpoint.read('hvacThermostat', ['elkoDisplayText', 'elkoSensor']); diff --git a/src/devices/enbrighten.ts b/src/devices/enbrighten.ts index b925d86ce10c4..d146a1bf79f18 100644 --- a/src/devices/enbrighten.ts +++ b/src/devices/enbrighten.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; import fz from '../converters/fromZigbee'; import {onOff, light, electricityMeter} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/enocean.ts b/src/devices/enocean.ts index f53c894508466..5ef2e636664c2 100644 --- a/src/devices/enocean.ts +++ b/src/devices/enocean.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ @@ -11,8 +11,23 @@ const definitions: Definition[] = [ description: 'Pushbutton transmitter module', fromZigbee: [fz.enocean_ptm215z], toZigbee: [], - exposes: [e.action(['press_1', 'release_1', 'press_2', 'release_2', 'press_3', 'release_3', 'press_4', 'release_4', - 'press_1_and_3', 'release_1_and_3', 'press_2_and_4', 'release_2_and_4', 'press_energy_bar'])], + exposes: [ + e.action([ + 'press_1', + 'release_1', + 'press_2', + 'release_2', + 'press_3', + 'release_3', + 'press_4', + 'release_4', + 'press_1_and_3', + 'release_1_and_3', + 'press_2_and_4', + 'release_2_and_4', + 'press_energy_bar', + ]), + ], whiteLabel: [ {vendor: 'Niko', description: 'Dimmer switch for Hue system', model: '91004'}, {vendor: 'NodOn', description: 'Smart switch for Philips Hue', model: 'CWS-4-1-01_HUE'}, @@ -30,11 +45,37 @@ const definitions: Definition[] = [ description: 'Pushbutton transmitter module', fromZigbee: [fz.enocean_ptm215ze], toZigbee: [], - exposes: [e.action(['press_1', 'release_1', 'press_2', 'release_2', 'press_3', 'release_3', 'press_4', 'release_4', - 'press_1_and_2', 'release_1_and_2', 'press_1_and_3', 'release_1_and_3', 'press_1_and_4', 'release_1_and_4', - 'press_2_and_3', 'release_2_and_3', 'press_2_and_4', 'release_2_and_4', 'press_3_and_4', 'release_3_and_4', - 'press_energy_bar', 'release_energy_bar', 'press_or_release_all', - 'lock', 'unlock', 'half_open', 'tilt'])], + exposes: [ + e.action([ + 'press_1', + 'release_1', + 'press_2', + 'release_2', + 'press_3', + 'release_3', + 'press_4', + 'release_4', + 'press_1_and_2', + 'release_1_and_2', + 'press_1_and_3', + 'release_1_and_3', + 'press_1_and_4', + 'release_1_and_4', + 'press_2_and_3', + 'release_2_and_3', + 'press_2_and_4', + 'release_2_and_4', + 'press_3_and_4', + 'release_3_and_4', + 'press_energy_bar', + 'release_energy_bar', + 'press_or_release_all', + 'lock', + 'unlock', + 'half_open', + 'tilt', + ]), + ], whiteLabel: [ {vendor: 'Easyfit by EnOcean', description: 'Wall switch for Zigbee', model: 'EWSxZ'}, {vendor: 'Trio2sys', description: 'Zigbee Green Power complete switch', model: '20020002'}, @@ -47,9 +88,28 @@ const definitions: Definition[] = [ description: 'Pushbutton transmitter module', fromZigbee: [fz.enocean_ptm216z], toZigbee: [], - exposes: [e.action(['press_1', 'press_2', 'press_1_and_2', 'press_3', 'press_1_and_3', 'press_3_and_4', 'press_1_and_2_and_3', - 'press_4', 'press_1_and_4', 'press_2_and_4', 'press_1_and_2_and_4', 'press_3_and_4', 'press_1_and_3_and_4', - 'press_2_and_3_and_4', 'press_all', 'press_energy_bar', 'release', 'short_press_2_of_2'])], + exposes: [ + e.action([ + 'press_1', + 'press_2', + 'press_1_and_2', + 'press_3', + 'press_1_and_3', + 'press_3_and_4', + 'press_1_and_2_and_3', + 'press_4', + 'press_1_and_4', + 'press_2_and_4', + 'press_1_and_2_and_4', + 'press_3_and_4', + 'press_1_and_3_and_4', + 'press_2_and_3_and_4', + 'press_all', + 'press_energy_bar', + 'release', + 'short_press_2_of_2', + ]), + ], }, ]; diff --git a/src/devices/envilar.ts b/src/devices/envilar.ts index 70e67c2895b91..cb3ec364678ac 100644 --- a/src/devices/envilar.ts +++ b/src/devices/envilar.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import * as exposes from '../lib/exposes'; +import {Definition} from '../lib/types'; const e = exposes.presets; import fz from '../converters/fromZigbee'; import {deviceEndpoints, light, onOff, identify, electricityMeter} from '../lib/modernExtend'; @@ -27,8 +27,7 @@ const definitions: Definition[] = [ meta: {battery: {dontDividePercentage: true}}, fromZigbee: [fz.command_recall, fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.battery], toZigbee: [], - exposes: [e.battery(), - e.action(['recall_1', 'recall_2', 'on', 'off', 'brightness_stop', 'brightness_move_up', 'brightness_move_down'])], + exposes: [e.battery(), e.action(['recall_1', 'recall_2', 'on', 'off', 'brightness_stop', 'brightness_move_up', 'brightness_move_down'])], }, { zigbeeModel: ['ZG102-BOX-UNIDIM'], @@ -49,10 +48,7 @@ const definitions: Definition[] = [ model: '2CH-ZG-BOX-RELAY', vendor: 'Envilar', description: '2 channel box relay', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2}}), - onOff({endpointNames: ['l1', 'l2']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2}}), onOff({endpointNames: ['l1', 'l2']})], }, { zigbeeModel: ['7853'], diff --git a/src/devices/essentialb.ts b/src/devices/essentialb.ts index 3e6b7b83d71c1..d1d540b711b30 100644 --- a/src/devices/essentialb.ts +++ b/src/devices/essentialb.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; import {light} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; @@ -77,8 +77,18 @@ const definitions: Definition[] = [ description: 'Smart button', fromZigbee: [fz.battery, fz.command_on, fz.command_off, fz.command_step, fz.command_stop, fz.command_step_color_temperature], toZigbee: [], - exposes: [e.battery(), e.action(['on', 'off', 'color_temperature_step_up', 'color_temperature_step_down', - 'brightness_step_up', 'brightness_step_down', 'brightness_stop'])], + exposes: [ + e.battery(), + e.action([ + 'on', + 'off', + 'color_temperature_step_up', + 'color_temperature_step_down', + 'brightness_step_up', + 'brightness_step_down', + 'brightness_stop', + ]), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const binds = ['genBasic', 'genOnOff', 'genPowerCfg', 'lightingColorCtrl', 'genLevelCtrl']; diff --git a/src/devices/eucontrols.ts b/src/devices/eucontrols.ts index ac6d3645fb523..0ead1d8d3755a 100644 --- a/src/devices/eucontrols.ts +++ b/src/devices/eucontrols.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/eurotronic.ts b/src/devices/eurotronic.ts index 5188a7fb97b7a..29de01d276379 100644 --- a/src/devices/eurotronic.ts +++ b/src/devices/eurotronic.ts @@ -1,11 +1,12 @@ import {Zcl} from 'zigbee-herdsman'; -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; + import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as ota from '../lib/ota'; import * as constants from '../lib/constants'; +import * as exposes from '../lib/exposes'; +import * as ota from '../lib/ota'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -16,27 +17,56 @@ const definitions: Definition[] = [ vendor: 'Eurotronic', description: 'Spirit Zigbee wireless heater thermostat', fromZigbee: [fz.eurotronic_thermostat, fz.battery], - toZigbee: [tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint, - tz.thermostat_local_temperature_calibration, tz.eurotronic_host_flags, - tz.eurotronic_error_status, tz.thermostat_setpoint_raise_lower, tz.thermostat_control_sequence_of_operation, - tz.thermostat_remote_sensing, tz.thermostat_local_temperature, tz.thermostat_running_state, - tz.eurotronic_current_heating_setpoint, tz.eurotronic_trv_mode, tz.eurotronic_valve_position, tz.eurotronic_child_lock, - tz.eurotronic_mirror_display], - exposes: [e.battery(), e.child_lock(), e.climate().withSetpoint('occupied_heating_setpoint', 5, 30, 0.5).withLocalTemperature() - .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat']) - .withLocalTemperatureCalibration() - .withPiHeatingDemand(), - e.enum('trv_mode', exposes.access.ALL, [1, 2]) - .withDescription('Select between direct control of the valve via the `valve_position` or automatic control of the '+ - 'valve based on the `current_heating_setpoint`. For manual control set the value to 1, for automatic control set the value '+ - 'to 2 (the default). When switched to manual mode the display shows a value from 0 (valve closed) to 100 (valve fully open) '+ - 'and the buttons on the device are disabled.'), - e.numeric('valve_position', exposes.access.ALL).withValueMin(0).withValueMax(255) - .withDescription('Directly control the radiator valve when `trv_mode` is set to 1. The values range from 0 (valve '+ - 'closed) to 255 (valve fully open)'), - e.binary('mirror_display', ea.ALL, 'ON', 'OFF') - .withDescription('Mirror display of the thermostat. Useful when it is ' + - 'mounted in a way where the display is presented upside down.')], + toZigbee: [ + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_unoccupied_heating_setpoint, + tz.thermostat_local_temperature_calibration, + tz.eurotronic_host_flags, + tz.eurotronic_error_status, + tz.thermostat_setpoint_raise_lower, + tz.thermostat_control_sequence_of_operation, + tz.thermostat_remote_sensing, + tz.thermostat_local_temperature, + tz.thermostat_running_state, + tz.eurotronic_current_heating_setpoint, + tz.eurotronic_trv_mode, + tz.eurotronic_valve_position, + tz.eurotronic_child_lock, + tz.eurotronic_mirror_display, + ], + exposes: [ + e.battery(), + e.child_lock(), + e + .climate() + .withSetpoint('occupied_heating_setpoint', 5, 30, 0.5) + .withLocalTemperature() + .withSystemMode(['off', 'auto', 'heat']) + .withRunningState(['idle', 'heat']) + .withLocalTemperatureCalibration() + .withPiHeatingDemand(), + e + .enum('trv_mode', exposes.access.ALL, [1, 2]) + .withDescription( + 'Select between direct control of the valve via the `valve_position` or automatic control of the ' + + 'valve based on the `current_heating_setpoint`. For manual control set the value to 1, for automatic control set the value ' + + 'to 2 (the default). When switched to manual mode the display shows a value from 0 (valve closed) to 100 (valve fully open) ' + + 'and the buttons on the device are disabled.', + ), + e + .numeric('valve_position', exposes.access.ALL) + .withValueMin(0) + .withValueMax(255) + .withDescription( + 'Directly control the radiator valve when `trv_mode` is set to 1. The values range from 0 (valve ' + + 'closed) to 255 (valve fully open)', + ), + e + .binary('mirror_display', ea.ALL, 'ON', 'OFF') + .withDescription( + 'Mirror display of the thermostat. Useful when it is ' + 'mounted in a way where the display is presented upside down.', + ), + ], ota: ota.zigbeeOTA, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -46,10 +76,30 @@ const definitions: Definition[] = [ await reporting.thermostatPIHeatingDemand(endpoint); await reporting.thermostatOccupiedHeatingSetpoint(endpoint); await reporting.thermostatUnoccupiedHeatingSetpoint(endpoint); - await endpoint.configureReporting('hvacThermostat', [{attribute: {ID: 0x4003, type: 41}, minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, reportableChange: 25}], options); - await endpoint.configureReporting('hvacThermostat', [{attribute: {ID: 0x4008, type: 34}, minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, reportableChange: 1}], options); + await endpoint.configureReporting( + 'hvacThermostat', + [ + { + attribute: {ID: 0x4003, type: 41}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 25, + }, + ], + options, + ); + await endpoint.configureReporting( + 'hvacThermostat', + [ + { + attribute: {ID: 0x4008, type: 34}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 1, + }, + ], + options, + ); }, }, ]; diff --git a/src/devices/evanell.ts b/src/devices/evanell.ts index 7bf4cd2767ac3..778cc12534358 100644 --- a/src/devices/evanell.ts +++ b/src/devices/evanell.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; -import * as tuya from '../lib/tuya'; import * as reporting from '../lib/reporting'; +import * as tuya from '../lib/tuya'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -13,16 +13,23 @@ const definitions: Definition[] = [ vendor: 'Evanell', description: 'Thermostatic radiator valve', fromZigbee: [legacy.fz.evanell_thermostat], - toZigbee: [legacy.tz.evanell_thermostat_current_heating_setpoint, legacy.tz.evanell_thermostat_system_mode, - legacy.tz.evanell_thermostat_child_lock], + toZigbee: [ + legacy.tz.evanell_thermostat_current_heating_setpoint, + legacy.tz.evanell_thermostat_system_mode, + legacy.tz.evanell_thermostat_child_lock, + ], onEvent: tuya.onEventSetTime, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genBasic']); }, - exposes: [e.child_lock(), e.battery(), - e.climate() - .withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.STATE_SET).withLocalTemperature(ea.STATE) + exposes: [ + e.child_lock(), + e.battery(), + e + .climate() + .withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.STATE_SET) + .withLocalTemperature(ea.STATE) .withSystemMode(['off', 'heat', 'auto'], ea.STATE_SET), ], }, diff --git a/src/devices/evn.ts b/src/devices/evn.ts index 6819d8427d428..a52e397851925 100644 --- a/src/devices/evn.ts +++ b/src/devices/evn.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const e = exposes.presets; @@ -11,12 +11,35 @@ const definitions: Definition[] = [ model: 'ZBHS4RGBW', vendor: 'EVN', description: 'Zigbee 4 channel RGBW remote control', - fromZigbee: [fz.battery, fz.command_move_to_color, fz.command_move_to_color_temp, fz.command_move_hue, - fz.command_step, fz.command_stop, fz.command_move, fz.command_recall, fz.command_on, fz.command_off], - exposes: [e.battery(), e.action([ - 'color_move', 'color_temperature_move', 'brightness_step_up', 'brightness_step_down', - 'brightness_move_up', 'brightness_move_down', 'brightness_stop', - 'hue_move', 'hue_stop', 'recall_*', 'on', 'off'])], + fromZigbee: [ + fz.battery, + fz.command_move_to_color, + fz.command_move_to_color_temp, + fz.command_move_hue, + fz.command_step, + fz.command_stop, + fz.command_move, + fz.command_recall, + fz.command_on, + fz.command_off, + ], + exposes: [ + e.battery(), + e.action([ + 'color_move', + 'color_temperature_move', + 'brightness_step_up', + 'brightness_step_down', + 'brightness_move_up', + 'brightness_move_down', + 'brightness_stop', + 'hue_move', + 'hue_stop', + 'recall_*', + 'on', + 'off', + ]), + ], toZigbee: [], meta: {multiEndpoint: true, battery: {dontDividePercentage: true}}, endpoint: (device) => { diff --git a/src/devices/evology.ts b/src/devices/evology.ts index c40f1a42a3a7c..219c1717f9dc5 100644 --- a/src/devices/evology.ts +++ b/src/devices/evology.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; import * as exposes from '../lib/exposes'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/evvr.ts b/src/devices/evvr.ts index 3bd4de09dd28a..3aef75e8ad063 100644 --- a/src/devices/evvr.ts +++ b/src/devices/evvr.ts @@ -1,6 +1,5 @@ -import {Definition} from '../lib/types'; import {onOff} from '../lib/modernExtend'; - +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/ewelink.ts b/src/devices/ewelink.ts index bfcb14c4d0ba2..43bc08b54dea6 100644 --- a/src/devices/ewelink.ts +++ b/src/devices/ewelink.ts @@ -1,8 +1,8 @@ -import {Definition, Fz} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import {deviceEndpoints, onOff} from '../lib/modernExtend'; +import * as exposes from '../lib/exposes'; import {logger} from '../lib/logger'; +import {deviceEndpoints, onOff} from '../lib/modernExtend'; +import {Definition, Fz} from '../lib/types'; const e = exposes.presets; const NS = 'zhc:ewelink'; @@ -102,10 +102,7 @@ const definitions: Definition[] = [ model: 'ZB-SW02', vendor: 'eWeLink', description: 'Smart light switch/2 gang relay', - extend: [ - deviceEndpoints({endpoints: {'left': 1, 'right': 2}}), - onOff({endpointNames: ['left', 'right'], configureReporting: false}), - ], + extend: [deviceEndpoints({endpoints: {left: 1, right: 2}}), onOff({endpointNames: ['left', 'right'], configureReporting: false})], onEvent: async (type, data, device) => { device.skipDefaultResponse = true; }, @@ -116,7 +113,7 @@ const definitions: Definition[] = [ vendor: 'eWeLink', description: 'Smart light switch - 3 gang', extend: [ - deviceEndpoints({endpoints: {'left': 1, 'center': 2, 'right': 3}}), + deviceEndpoints({endpoints: {left: 1, center: 2, right: 3}}), onOff({endpointNames: ['left', 'center', 'right'], configureReporting: false}), ], onEvent: async (type, data, device) => { @@ -129,7 +126,7 @@ const definitions: Definition[] = [ vendor: 'eWeLink', description: 'Smart light switch - 4 gang', extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4}}), + deviceEndpoints({endpoints: {l1: 1, l2: 2, l3: 3, l4: 4}}), onOff({endpointNames: ['l1', 'l2', 'l3', 'l4'], configureReporting: false}), ], onEvent: async (type, data, device) => { @@ -142,7 +139,7 @@ const definitions: Definition[] = [ vendor: 'eWeLink', description: 'Smart light switch - 5 gang', extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4, 'l5': 5}}), + deviceEndpoints({endpoints: {l1: 1, l2: 2, l3: 3, l4: 4, l5: 5}}), onOff({endpointNames: ['l1', 'l2', 'l3', 'l4', 'l5'], configureReporting: false}), ], onEvent: async (type, data, device) => { diff --git a/src/devices/ezex.ts b/src/devices/ezex.ts index d0d4370eda4be..a61ab3752dc24 100644 --- a/src/devices/ezex.ts +++ b/src/devices/ezex.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {deviceEndpoints, onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { @@ -7,10 +7,7 @@ const definitions: Definition[] = [ model: 'ECW-100-A03', vendor: 'eZEX', description: 'Zigbee switch 3 gang', - extend: [ - deviceEndpoints({endpoints: {'top': 1, 'center': 2, 'bottom': 3}}), - onOff({endpointNames: ['top', 'center', 'bottom']}), - ], + extend: [deviceEndpoints({endpoints: {top: 1, center: 2, bottom: 3}}), onOff({endpointNames: ['top', 'center', 'bottom']})], }, ]; diff --git a/src/devices/fantem.ts b/src/devices/fantem.ts index 9cc358561efda..2d3482389953c 100644 --- a/src/devices/fantem.ts +++ b/src/devices/fantem.ts @@ -1,16 +1,18 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; -import * as tuya from '../lib/tuya'; import {light} from '../lib/modernExtend'; +import * as tuya from '../lib/tuya'; const definitions: Definition[] = [ { - fingerprint: [{modelID: 'TS110F', manufacturerName: '_TZ3210_lfbz816s'}, - {modelID: 'TS110F', manufacturerName: '_TZ3210_ebbfkvoy'}], + fingerprint: [ + {modelID: 'TS110F', manufacturerName: '_TZ3210_lfbz816s'}, + {modelID: 'TS110F', manufacturerName: '_TZ3210_ebbfkvoy'}, + ], model: 'ZB006-X', vendor: 'Fantem', description: 'Smart dimmer module', @@ -20,19 +22,13 @@ const definitions: Definition[] = [ exposes: [ e.action(['on', 'off', 'brightness_move_down', 'brightness_move_up', 'brightness_stop']), e.enum('control_mode', ea.STATE_SET, ['ext_switch', 'remote', 'both']).withDescription('Control mode'), - e.enum('switch_type', ea.STATE_SET, ['unknown', 'toggle', 'momentary', 'rotary', 'auto_config']) - .withDescription('External switch type'), - e.numeric('switch_status', ea.STATE).withDescription('External switch status') - .withValueMin(-10000).withValueMax(10000), - e.enum('load_detection_mode', ea.STATE_SET, ['none', 'first_power_on', 'every_power_on']) - .withDescription('Load detection mode'), + e.enum('switch_type', ea.STATE_SET, ['unknown', 'toggle', 'momentary', 'rotary', 'auto_config']).withDescription('External switch type'), + e.numeric('switch_status', ea.STATE).withDescription('External switch status').withValueMin(-10000).withValueMax(10000), + e.enum('load_detection_mode', ea.STATE_SET, ['none', 'first_power_on', 'every_power_on']).withDescription('Load detection mode'), // If you see load_type 'unknown', pls. check with Tuya gateway and app and update with label from Tuya app. - e.enum('load_type', ea.STATE, ['unknown', 'resistive_capacitive', 'unknown', 'detecting']) - .withDescription('Load type'), - e.enum('load_dimmable', ea.STATE, ['unknown', 'dimmable', 'not_dimmable']) - .withDescription('Load dimmable'), - e.enum('power_supply_mode', ea.STATE, ['unknown', 'no_neutral', 'with_neutral']) - .withDescription('Power supply mode'), + e.enum('load_type', ea.STATE, ['unknown', 'resistive_capacitive', 'unknown', 'detecting']).withDescription('Load type'), + e.enum('load_dimmable', ea.STATE, ['unknown', 'dimmable', 'not_dimmable']).withDescription('Load dimmable'), + e.enum('power_supply_mode', ea.STATE, ['unknown', 'no_neutral', 'with_neutral']).withDescription('Power supply mode'), ], meta: {disableActionGroup: true}, onEvent: tuya.onEventSetLocalTime, @@ -49,27 +45,37 @@ const definitions: Definition[] = [ description: '4 in 1 multi sensor', fromZigbee: [fz.battery, fz.ignore_basic_report, fz.illuminance, legacy.fz.ZB003X, fz.ZB003X_attr, fz.ZB003X_occupancy], toZigbee: [legacy.tz.ZB003X], - whiteLabel: [ - tuya.whitelabel('EFK', 'is-thpl-zb', '4 in 1 multi sensor', ['_TZ3210_0aqbrnts']), - ], - exposes: [e.occupancy(), e.tamper(), e.illuminance_lux(), e.illuminance(), e.temperature(), e.humidity(), - e.battery(), e.battery_voltage(), + whiteLabel: [tuya.whitelabel('EFK', 'is-thpl-zb', '4 in 1 multi sensor', ['_TZ3210_0aqbrnts'])], + exposes: [ + e.occupancy(), + e.tamper(), + e.illuminance_lux(), + e.illuminance(), + e.temperature(), + e.humidity(), + e.battery(), + e.battery_voltage(), e.numeric('battery2', ea.STATE).withUnit('%').withDescription('Remaining battery 2 in %'), - e.numeric('illuminance_calibration', ea.STATE_SET).withDescription('Illuminance calibration in lux') - .withValueMin(-20).withValueMax(20), - e.numeric('temperature_calibration', ea.STATE_SET).withDescription('Temperature calibration (-2.0...2.0)') - .withValueMin(-2).withValueMax(2).withValueStep(0.1), - e.numeric('humidity_calibration', ea.STATE_SET).withDescription('Humidity calibration') - .withValueMin(-15).withValueMax(15), + e.numeric('illuminance_calibration', ea.STATE_SET).withDescription('Illuminance calibration in lux').withValueMin(-20).withValueMax(20), + e + .numeric('temperature_calibration', ea.STATE_SET) + .withDescription('Temperature calibration (-2.0...2.0)') + .withValueMin(-2) + .withValueMax(2) + .withValueStep(0.1), + e.numeric('humidity_calibration', ea.STATE_SET).withDescription('Humidity calibration').withValueMin(-15).withValueMax(15), e.binary('reporting_enable', ea.STATE_SET, true, false).withDescription('Enable reporting'), - e.numeric('reporting_time', ea.STATE_SET).withDescription('Reporting interval in minutes') - .withValueMin(0).withValueMax(1440).withValueStep(5), + e + .numeric('reporting_time', ea.STATE_SET) + .withDescription('Reporting interval in minutes') + .withValueMin(0) + .withValueMax(1440) + .withValueStep(5), e.binary('led_enable', ea.STATE_SET, true, false).withDescription('Enable LED'), e.binary('pir_enable', ea.STATE_SET, true, false).withDescription('Enable PIR sensor'), e.enum('sensitivity', ea.STATE_SET, ['low', 'medium', 'high']).withDescription('PIR sensor sensitivity'), - // eslint-disable-next-line - e.enum('keep_time', ea.STATE_SET, ['0', '30', '60', '120', '240', '480']) - .withDescription('PIR keep time in seconds')], + e.enum('keep_time', ea.STATE_SET, ['0', '30', '60', '120', '240', '480']).withDescription('PIR keep time in seconds'), + ], }, ]; diff --git a/src/devices/feibit.ts b/src/devices/feibit.ts index f5bd5670bf9ba..1635dceb5306d 100644 --- a/src/devices/feibit.ts +++ b/src/devices/feibit.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; import {deviceEndpoints, onOff} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; @@ -13,10 +13,7 @@ const definitions: Definition[] = [ model: 'TZSW22FW-L4', vendor: 'Feibit', description: 'Smart light switch - 2 gang', - extend: [ - deviceEndpoints({endpoints: {'top': 16, 'bottom': 17}}), - onOff({endpointNames: ['top', 'bottom']}), - ], + extend: [deviceEndpoints({endpoints: {top: 16, bottom: 17}}), onOff({endpointNames: ['top', 'bottom']})], }, { zigbeeModel: ['FB56+ZSW1GKJ2.3'], @@ -128,20 +125,14 @@ const definitions: Definition[] = [ model: 'SLS301ZB_2', vendor: 'Feibit', description: 'Smart light switch - 2 gang', - extend: [ - deviceEndpoints({endpoints: {'left': 16, 'right': 17}}), - onOff({endpointNames: ['left', 'right']}), - ], + extend: [deviceEndpoints({endpoints: {left: 16, right: 17}}), onOff({endpointNames: ['left', 'right']})], }, { zigbeeModel: ['FB56+ZSW1IKJ2.2', 'FB56+ZSW1IKJ1.1'], model: 'SLS301ZB_3', vendor: 'Feibit', description: 'Smart light switch - 3 gang', - extend: [ - deviceEndpoints({endpoints: {'left': 16, 'center': 17, 'right': 18}}), - onOff({endpointNames: ['left', 'center', 'right']}), - ], + extend: [deviceEndpoints({endpoints: {left: 16, center: 17, right: 18}}), onOff({endpointNames: ['left', 'center', 'right']})], }, { zigbeeModel: ['FB56+ZSN08KJ2.2'], diff --git a/src/devices/fireangel.ts b/src/devices/fireangel.ts index c68f6434f3830..93f281a6ee374 100644 --- a/src/devices/fireangel.ts +++ b/src/devices/fireangel.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/frankever.ts b/src/devices/frankever.ts index c39fc211e2ff7..08d82249239bf 100644 --- a/src/devices/frankever.ts +++ b/src/devices/frankever.ts @@ -1,24 +1,36 @@ -import {Definition} from '../lib/types'; import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; const definitions: Definition[] = [ { - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_wt9agwf3'}, + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE200_wt9agwf3'}, {modelID: 'TS0601', manufacturerName: '_TZE200_5uodvhgc'}, - {modelID: 'TS0601', manufacturerName: '_TZE200_1n2zev06'}], + {modelID: 'TS0601', manufacturerName: '_TZE200_1n2zev06'}, + ], model: 'FK_V02', vendor: 'FrankEver', description: 'Zigbee smart water valve', fromZigbee: [legacy.fz.frankever_valve], toZigbee: [legacy.tz.tuya_switch_state, legacy.tz.frankever_threshold, legacy.tz.frankever_timer], - exposes: [e.switch().setAccess('state', ea.STATE_SET), - e.numeric('threshold', exposes.access.STATE_SET).withValueMin(0).withValueMax(100).withUnit('%') + exposes: [ + e.switch().setAccess('state', ea.STATE_SET), + e + .numeric('threshold', exposes.access.STATE_SET) + .withValueMin(0) + .withValueMax(100) + .withUnit('%') .withDescription('Valve open percentage (multiple of 10)'), - e.numeric('timer', exposes.access.STATE_SET).withValueMin(0).withValueMax(600).withUnit('min') - .withDescription('Countdown timer in minutes')], + e + .numeric('timer', exposes.access.STATE_SET) + .withValueMin(0) + .withValueMax(600) + .withUnit('min') + .withDescription('Countdown timer in minutes'), + ], }, ]; diff --git a/src/devices/frient.ts b/src/devices/frient.ts index b99c2245c5427..c0f8efd5dc78e 100644 --- a/src/devices/frient.ts +++ b/src/devices/frient.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; import {electricityMeter, onOff, ota} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ @@ -34,4 +34,3 @@ const definitions: Definition[] = [ export default definitions; module.exports = definitions; - diff --git a/src/devices/futurehome.ts b/src/devices/futurehome.ts index 3b598012d3665..749542b5312f1 100644 --- a/src/devices/futurehome.ts +++ b/src/devices/futurehome.ts @@ -1,8 +1,9 @@ import {Definition} from 'src/lib/types'; + import * as exposes from '../lib/exposes'; -import * as tuya from '../lib/tuya'; -import * as ota from '../lib/ota'; import {light} from '../lib/modernExtend'; +import * as ota from '../lib/ota'; +import * as tuya from '../lib/tuya'; const e = exposes.presets; const ea = exposes.access; @@ -15,43 +16,48 @@ const definitions: Definition[] = [ description: 'Thermostat', fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], - whiteLabel: [ - tuya.whitelabel('Futurehome', 'Co020', 'Smart thermostat', ['_TZE200_e5hpkc6d']), - ], + whiteLabel: [tuya.whitelabel('Futurehome', 'Co020', 'Smart thermostat', ['_TZE200_e5hpkc6d'])], onEvent: tuya.onEventSetTime, configure: tuya.configureMagicPacket, exposes: [ - e.climate() + e + .climate() .withSystemMode(['off', 'heat'], ea.STATE_SET, 'Whether the thermostat is turned on or off') .withPreset(['user', 'home', 'away', 'auto']) .withLocalTemperature(ea.STATE) .withLocalTemperatureCalibration(-9, 9, 1, ea.STATE_SET) .withRunningState(['idle', 'heat'], ea.STATE) .withSetpoint('current_heating_setpoint', 5, 35, 1, ea.STATE_SET), - e.temperature_sensor_select(['air_sensor', 'floor_sensor', 'max_guard']) + e + .temperature_sensor_select(['air_sensor', 'floor_sensor', 'max_guard']) .withDescription( 'Max guard. Floor sensor must be installed. The thermostat will regulate according to the room sensor, ' + - 'but interrupt heating if the floor sensor exceeds the maximum guard temperature. Standard is 27°C' + - '\n\n' + - 'There is also a maximum guard when the thermostat is set to floor sensor. ' + - 'The thermostat regulates according to the floor sensor, but will interrupt heating if the floor sensor ' + - 'exceeds the maximum guard temperature. Standard is 27°C.', + 'but interrupt heating if the floor sensor exceeds the maximum guard temperature. Standard is 27°C' + + '\n\n' + + 'There is also a maximum guard when the thermostat is set to floor sensor. ' + + 'The thermostat regulates according to the floor sensor, but will interrupt heating if the floor sensor ' + + 'exceeds the maximum guard temperature. Standard is 27°C.', ), - e.numeric('local_temperature_floor', ea.STATE) + e + .numeric('local_temperature_floor', ea.STATE) .withUnit('°C') .withDescription('Current temperature measured on the external sensor (floor)') .withValueStep(1), e.child_lock(), e.window_detection(), - e.numeric('hysteresis', ea.STATE_SET) + e + .numeric('hysteresis', ea.STATE_SET) .withUnit('°C') - .withDescription('The offset from the target temperature in which the temperature has to ' + - 'change for the heating state to change. This is to prevent erratically turning on/off ' + - 'when the temperature is close to the target.') + .withDescription( + 'The offset from the target temperature in which the temperature has to ' + + 'change for the heating state to change. This is to prevent erratically turning on/off ' + + 'when the temperature is close to the target.', + ) .withValueMin(1) .withValueMax(9) .withValueStep(1), - e.numeric('max_temperature_protection', ea.STATE_SET) + e + .numeric('max_temperature_protection', ea.STATE_SET) .withUnit('°C') .withDescription('Max guarding temperature') .withValueMin(20) @@ -67,8 +73,7 @@ const definitions: Definition[] = [ [28, 'local_temperature_calibration', tuya.valueConverter.raw], [30, 'child_lock', tuya.valueConverter.lockUnlock], [101, 'local_temperature_floor', tuya.valueConverter.raw], - [102, 'sensor', tuya.valueConverterBasic.lookup( - {air_sensor: tuya.enum(0), floor_sensor: tuya.enum(1), max_guard: tuya.enum(2)})], + [102, 'sensor', tuya.valueConverterBasic.lookup({air_sensor: tuya.enum(0), floor_sensor: tuya.enum(1), max_guard: tuya.enum(2)})], [103, 'hysteresis', tuya.valueConverter.raw], [104, 'running_state', tuya.valueConverterBasic.lookup({idle: false, heat: true})], // In the old handler, endpoint 105 was left unused. I don't know what this value means. diff --git a/src/devices/ge.ts b/src/devices/ge.ts index f9722c9be5870..04e7e4a3e1b6c 100644 --- a/src/devices/ge.ts +++ b/src/devices/ge.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; import {electricityMeter, light, onOff} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; @@ -83,7 +83,7 @@ const definitions: Definition[] = [ description: 'Quirky smart switch', extend: [onOff()], endpoint: (device) => { - return {'default': 2}; + return {default: 2}; }, }, { diff --git a/src/devices/gewiss.ts b/src/devices/gewiss.ts index f23de2cd46104..0123b461d4704 100644 --- a/src/devices/gewiss.ts +++ b/src/devices/gewiss.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; import {deviceEndpoints, onOff} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ @@ -19,10 +19,7 @@ const definitions: Definition[] = [ model: 'GWA1522', description: 'Switch actuator 2 channels with input', vendor: 'Gewiss', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2}}), - onOff({endpointNames: ['l1', 'l2']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2}}), onOff({endpointNames: ['l1', 'l2']})], }, { zigbeeModel: ['GWA1531_Shutter'], diff --git a/src/devices/gidealed.ts b/src/devices/gidealed.ts index e08d03d9d2f50..49491c080ece7 100644 --- a/src/devices/gidealed.ts +++ b/src/devices/gidealed.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/giderwel.ts b/src/devices/giderwel.ts index 916ba83167a30..08b46a9105021 100644 --- a/src/devices/giderwel.ts +++ b/src/devices/giderwel.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/giex.ts b/src/devices/giex.ts index fab1b37e2b6d4..c0a6b201977b0 100644 --- a/src/devices/giex.ts +++ b/src/devices/giex.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; import * as exposes from '../lib/exposes'; -import * as tuya from '../lib/tuya'; import * as legacy from '../lib/legacy'; +import * as tuya from '../lib/tuya'; +import {Definition} from '../lib/types'; const e = exposes.presets; const {presets: ep, access: ea} = exposes; @@ -18,23 +18,17 @@ const exportTemplates = { toZigbee: [legacy.toZigbee.giexWaterValve], exposes: [ ep.battery(), - e.binary(legacy.giexWaterValve.state, ea.STATE_SET, 'ON', 'OFF') - .withDescription('State'), - e.enum(legacy.giexWaterValve.mode, ea.STATE_SET, ['duration', 'capacity']) - .withDescription('Irrigation mode'), - e.numeric(legacy.giexWaterValve.cycleIrrigationNumTimes, ea.STATE_SET) + e.binary(legacy.giexWaterValve.state, ea.STATE_SET, 'ON', 'OFF').withDescription('State'), + e.enum(legacy.giexWaterValve.mode, ea.STATE_SET, ['duration', 'capacity']).withDescription('Irrigation mode'), + e + .numeric(legacy.giexWaterValve.cycleIrrigationNumTimes, ea.STATE_SET) .withValueMin(0) .withValueMax(100) .withDescription('Number of cycle irrigation times, set to 0 for single cycle'), - e.numeric(legacy.giexWaterValve.irrigationStartTime, ea.STATE) - .withDescription('Last irrigation start time'), - e.numeric(legacy.giexWaterValve.irrigationEndTime, ea.STATE) - .withDescription('Last irrigation end time'), - e.numeric(legacy.giexWaterValve.lastIrrigationDuration, ea.STATE) - .withDescription('Last irrigation duration'), - e.numeric(legacy.giexWaterValve.waterConsumed, ea.STATE) - .withUnit('L') - .withDescription('Last irrigation water consumption'), + e.numeric(legacy.giexWaterValve.irrigationStartTime, ea.STATE).withDescription('Last irrigation start time'), + e.numeric(legacy.giexWaterValve.irrigationEndTime, ea.STATE).withDescription('Last irrigation end time'), + e.numeric(legacy.giexWaterValve.lastIrrigationDuration, ea.STATE).withDescription('Last irrigation duration'), + e.numeric(legacy.giexWaterValve.waterConsumed, ea.STATE).withUnit('L').withDescription('Last irrigation water consumption'), ], }, }; @@ -44,17 +38,17 @@ const definitions: Definition[] = [ { ...exportTemplates.giexWaterValve, model: 'QT06_1', - fingerprint: [ - {modelID: 'TS0601', manufacturerName: '_TZE200_sh1btabb'}, - ], + fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_sh1btabb'}], exposes: [ ...exportTemplates.giexWaterValve.exposes, - e.numeric(legacy.giexWaterValve.irrigationTarget, ea.STATE_SET) + e + .numeric(legacy.giexWaterValve.irrigationTarget, ea.STATE_SET) .withValueMin(0) .withValueMax(MINUTES_IN_A_DAY) .withUnit('minutes or litres') .withDescription('Irrigation target, duration in minutes or capacity in litres (depending on mode)'), - e.numeric(legacy.giexWaterValve.cycleIrrigationInterval, ea.STATE_SET) + e + .numeric(legacy.giexWaterValve.cycleIrrigationInterval, ea.STATE_SET) .withValueMin(0) .withValueMax(MINUTES_IN_A_DAY) .withUnit('min') @@ -75,22 +69,24 @@ const definitions: Definition[] = [ ], exposes: [ ...exportTemplates.giexWaterValve.exposes, - e.numeric(legacy.giexWaterValve.irrigationTarget, ea.STATE_SET) + e + .numeric(legacy.giexWaterValve.irrigationTarget, ea.STATE_SET) .withValueMin(0) .withValueMax(SECONDS_IN_12_HOURS) .withUnit('seconds or litres') - .withDescription('Irrigation target, duration in seconds or capacity in litres (depending on mode), ' + - 'set to 0 to leave the valve on indefinitely, ' + - 'for safety reasons the target will be forced to a minimum of 10 seconds in duration mode'), - e.numeric(legacy.giexWaterValve.cycleIrrigationInterval, ea.STATE_SET) + .withDescription( + 'Irrigation target, duration in seconds or capacity in litres (depending on mode), ' + + 'set to 0 to leave the valve on indefinitely, ' + + 'for safety reasons the target will be forced to a minimum of 10 seconds in duration mode', + ), + e + .numeric(legacy.giexWaterValve.cycleIrrigationInterval, ea.STATE_SET) .withValueMin(0) .withValueMax(SECONDS_IN_12_HOURS) .withUnit('sec') .withDescription('Cycle irrigation interval'), ], - whiteLabel: [ - tuya.whitelabel('GiEX', 'GX02', 'Water valve', ['_TZE204_7ytb3h8u', '_TZE204_4fblxpma', '_TZE284_7ytb3h8u']), - ], + whiteLabel: [tuya.whitelabel('GiEX', 'GX02', 'Water valve', ['_TZE204_7ytb3h8u', '_TZE204_4fblxpma', '_TZE284_7ytb3h8u'])], }, ]; diff --git a/src/devices/girier.ts b/src/devices/girier.ts index a665c00fcd52d..900abde99e5ac 100644 --- a/src/devices/girier.ts +++ b/src/devices/girier.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; -import * as tuya from '../lib/tuya'; import * as reporting from '../lib/reporting'; +import * as tuya from '../lib/tuya'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/gledopto.ts b/src/devices/gledopto.ts index adab190839164..b100aac7cd1d6 100644 --- a/src/devices/gledopto.ts +++ b/src/devices/gledopto.ts @@ -1,12 +1,12 @@ -import {Configure, Definition, KeyValue, OnEventType, Zh, Tz, ModernExtend} from '../lib/types'; -import * as exposes from '../lib/exposes'; -import * as globalStore from '../lib/store'; -import * as utils from '../lib/utils'; -import * as ota from '../lib/ota'; import tz from '../converters/toZigbee'; import * as libColor from '../lib/color'; -import {light, LightArgs, OnOffArgs, onOff} from '../lib/modernExtend'; +import * as exposes from '../lib/exposes'; import {logger} from '../lib/logger'; +import {light, LightArgs, OnOffArgs, onOff} from '../lib/modernExtend'; +import * as ota from '../lib/ota'; +import * as globalStore from '../lib/store'; +import {Configure, Definition, KeyValue, OnEventType, Zh, Tz, ModernExtend} from '../lib/types'; +import * as utils from '../lib/utils'; const NS = 'zhc:gledopto'; const e = exposes.presets; @@ -117,10 +117,15 @@ function gledoptoLight(args?: LightArgs) { args = {powerOnBehavior: false, ...args}; if (args.color) args.color = {modes: ['xy', 'hs'], ...(utils.isObject(args.color) ? args.color : {})}; const result = light(args); - result.toZigbee = utils.replaceInArray(result.toZigbee, + result.toZigbee = utils.replaceInArray( + result.toZigbee, [tz.light_onoff_brightness, tz.light_colortemp, tz.light_color, tz.light_color_colortemp], - [tzLocal.gledopto_light_onoff_brightness, tzLocal.gledopto_light_colortemp, tzLocal.gledopto_light_color, - tzLocal.gledopto_light_color_colortemp], + [ + tzLocal.gledopto_light_onoff_brightness, + tzLocal.gledopto_light_colortemp, + tzLocal.gledopto_light_color, + tzLocal.gledopto_light_color_colortemp, + ], false, ); return result; @@ -175,10 +180,15 @@ const definitions: Definition[] = [ }, { fingerprint: [ - {type: 'Router', manufacturerName: 'GLEDOPTO', modelID: 'GL-H-001', endpoints: [ - {ID: 11, profileID: 49246, deviceID: 528, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, - {ID: 13, profileID: 49246, deviceID: 528, inputClusters: [4096], outputClusters: [4096]}, - ]}, + { + type: 'Router', + manufacturerName: 'GLEDOPTO', + modelID: 'GL-H-001', + endpoints: [ + {ID: 11, profileID: 49246, deviceID: 528, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, + {ID: 13, profileID: 49246, deviceID: 528, inputClusters: [4096], outputClusters: [4096]}, + ], + }, ], model: 'GL-H-001', vendor: 'Gledopto', @@ -203,10 +213,15 @@ const definitions: Definition[] = [ { zigbeeModel: ['GL-C-006'], fingerprint: [ - {type: 'Router', manufacturerName: 'GLEDOPTO', modelID: 'GLEDOPTO', endpoints: [ - {ID: 11, profileID: 49246, deviceID: 544, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, - {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, - ]}, + { + type: 'Router', + manufacturerName: 'GLEDOPTO', + modelID: 'GLEDOPTO', + endpoints: [ + {ID: 11, profileID: 49246, deviceID: 544, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, + {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, + ], + }, ], model: 'GL-C-006', vendor: 'Gledopto', @@ -238,19 +253,34 @@ const definitions: Definition[] = [ }, { fingerprint: [ - {type: 'Router', manufacturerName: 'GLEDOPTO', modelID: 'GL-C-007', endpoints: [ - {ID: 11, profileID: 49246, deviceID: 528, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, - {ID: 13, profileID: 49246, deviceID: 528, inputClusters: [4096], outputClusters: [4096]}, - ]}, - {type: 'Router', manufacturerName: 'GLEDOPTO', modelID: 'GL-C-007', endpoints: [ - {ID: 11, profileID: 49246, deviceID: 528, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, - {ID: 12, profileID: 260, deviceID: 258, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, - {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, - ]}, - {type: 'Router', manufacturerName: 'GLEDOPTO', modelID: 'GL-C-007', endpoints: [ - {ID: 11, profileID: 260, deviceID: 269, inputClusters: [0, 3, 4, 5, 6, 8, 768, 4096], outputClusters: [25]}, - {ID: 242, profileID: 41440, deviceID: 97, inputClusters: [], outputClusters: [33]}, - ]}, + { + type: 'Router', + manufacturerName: 'GLEDOPTO', + modelID: 'GL-C-007', + endpoints: [ + {ID: 11, profileID: 49246, deviceID: 528, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, + {ID: 13, profileID: 49246, deviceID: 528, inputClusters: [4096], outputClusters: [4096]}, + ], + }, + { + type: 'Router', + manufacturerName: 'GLEDOPTO', + modelID: 'GL-C-007', + endpoints: [ + {ID: 11, profileID: 49246, deviceID: 528, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, + {ID: 12, profileID: 260, deviceID: 258, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, + {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, + ], + }, + { + type: 'Router', + manufacturerName: 'GLEDOPTO', + modelID: 'GL-C-007', + endpoints: [ + {ID: 11, profileID: 260, deviceID: 269, inputClusters: [0, 3, 4, 5, 6, 8, 768, 4096], outputClusters: [25]}, + {ID: 242, profileID: 41440, deviceID: 97, inputClusters: [], outputClusters: [33]}, + ], + }, ], model: 'GL-C-007-1ID', // 1 ID controls white and color together // Only enable disableDefaultResponse for the second fingerprint: @@ -262,16 +292,26 @@ const definitions: Definition[] = [ }, { fingerprint: [ - {type: 'Router', manufacturerName: 'GLEDOPTO', modelID: 'GL-C-007', endpoints: [ - {ID: 11, profileID: 49246, deviceID: 528, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, - {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, - {ID: 15, profileID: 49246, deviceID: 256, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, - ]}, - {type: 'Router', manufacturerName: 'GLEDOPTO', modelID: 'GLEDOPTO', endpoints: [ - {ID: 10, profileID: 49246, deviceID: 256, inputClusters: [0, 3, 4, 5, 6, 8], outputClusters: []}, - {ID: 11, profileID: 49246, deviceID: 528, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, - {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, - ]}, + { + type: 'Router', + manufacturerName: 'GLEDOPTO', + modelID: 'GL-C-007', + endpoints: [ + {ID: 11, profileID: 49246, deviceID: 528, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, + {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, + {ID: 15, profileID: 49246, deviceID: 256, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, + ], + }, + { + type: 'Router', + manufacturerName: 'GLEDOPTO', + modelID: 'GLEDOPTO', + endpoints: [ + {ID: 10, profileID: 49246, deviceID: 256, inputClusters: [0, 3, 4, 5, 6, 8], outputClusters: []}, + {ID: 11, profileID: 49246, deviceID: 528, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, + {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, + ], + }, ], model: 'GL-C-007-2ID', // 2 ID controls white and color separate vendor: 'Gledopto', @@ -307,17 +347,27 @@ const definitions: Definition[] = [ fingerprint: [ // Although the device announces modelID GL-C-007, this is clearly a GL-C-008 // https://github.com/Koenkk/zigbee2mqtt/issues/3525 - {type: 'Router', manufacturerName: 'GLEDOPTO', modelID: 'GL-C-007', endpoints: [ - {ID: 11, profileID: 49246, deviceID: 528, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, - {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, - {ID: 15, profileID: 49246, deviceID: 544, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, - ]}, - {type: 'Router', manufacturerName: 'GLEDOPTO', modelID: 'GL-C-007', endpoints: [ - {ID: 11, profileID: 49246, deviceID: 528, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, - {ID: 12, profileID: 260, deviceID: 258, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, - {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, - {ID: 15, profileID: 49246, deviceID: 256, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, - ]}, + { + type: 'Router', + manufacturerName: 'GLEDOPTO', + modelID: 'GL-C-007', + endpoints: [ + {ID: 11, profileID: 49246, deviceID: 528, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, + {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, + {ID: 15, profileID: 49246, deviceID: 544, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, + ], + }, + { + type: 'Router', + manufacturerName: 'GLEDOPTO', + modelID: 'GL-C-007', + endpoints: [ + {ID: 11, profileID: 49246, deviceID: 528, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, + {ID: 12, profileID: 260, deviceID: 258, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, + {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, + {ID: 15, profileID: 49246, deviceID: 256, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, + ], + }, ], model: 'GL-C-008-2ID', // 2 ID controls color temperature and color separate vendor: 'Gledopto', @@ -333,10 +383,15 @@ const definitions: Definition[] = [ }, { fingerprint: [ - {type: 'Router', manufacturerName: 'GLEDOPTO', modelID: 'GLEDOPTO', endpoints: [ - {ID: 11, profileID: 49246, deviceID: 528, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, - {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, - ]}, + { + type: 'Router', + manufacturerName: 'GLEDOPTO', + modelID: 'GLEDOPTO', + endpoints: [ + {ID: 11, profileID: 49246, deviceID: 528, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, + {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, + ], + }, ], zigbeeModel: ['GL-C-008'], model: 'GL-C-008-1ID', // 1 ID controls color temperature and color separate @@ -367,17 +422,25 @@ const definitions: Definition[] = [ vendor: 'Gledopto', ota: ota.zigbeeOTA, description: 'Zigbee LED Controller RGB+CCT (pro)', - whiteLabel: [{vendor: 'Gledopto', model: 'GL-C-001P'}, {vendor: 'Gledopto', model: 'GL-C-002P'}], + whiteLabel: [ + {vendor: 'Gledopto', model: 'GL-C-001P'}, + {vendor: 'Gledopto', model: 'GL-C-002P'}, + ], extend: [gledoptoLight({colorTemp: {range: [158, 495]}, color: true}), gledoptoConfigureReadModelID()], meta: {disableDefaultResponse: true}, }, { zigbeeModel: ['GL-C-009'], fingerprint: [ - {type: 'Router', manufacturerName: 'GLEDOPTO', modelID: 'GLEDOPTO', endpoints: [ - {ID: 11, profileID: 49246, deviceID: 256, inputClusters: [0, 3, 4, 5, 6, 8], outputClusters: []}, - {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, - ]}, + { + type: 'Router', + manufacturerName: 'GLEDOPTO', + modelID: 'GLEDOPTO', + endpoints: [ + {ID: 11, profileID: 49246, deviceID: 256, inputClusters: [0, 3, 4, 5, 6, 8], outputClusters: []}, + {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, + ], + }, ], model: 'GL-C-009', vendor: 'Gledopto', diff --git a/src/devices/gmmts.ts b/src/devices/gmmts.ts index 647dd12c7fee6..2c10b7d7e772b 100644 --- a/src/devices/gmmts.ts +++ b/src/devices/gmmts.ts @@ -1,17 +1,15 @@ -import {Definition, Fz, Tz, KeyValue, Zh} from '../lib/types'; -/* eslint-disable max-len */ -/* eslint-disable no-multi-spaces */ -import * as exposes from '../lib/exposes'; -import * as globalStore from '../lib/store'; -import * as reporting from '../lib/reporting'; -import fz from '../converters/fromZigbee'; -import {repInterval} from '../lib/constants'; - -import * as ota from '../lib/ota'; import {Buffer} from 'buffer'; +import {Zcl} from 'zigbee-herdsman'; import {Device} from 'zigbee-herdsman/dist/controller/model'; + +import fz from '../converters/fromZigbee'; +import {repInterval} from '../lib/constants'; +import * as exposes from '../lib/exposes'; import {logger} from '../lib/logger'; -import {Zcl} from 'zigbee-herdsman'; +import * as ota from '../lib/ota'; +import * as reporting from '../lib/reporting'; +import * as globalStore from '../lib/store'; +import {Definition, Fz, Tz, KeyValue, Zh} from '../lib/types'; const ea = exposes.access; const e = exposes.presets; @@ -30,7 +28,6 @@ const TIME = 'date'; const TRANSLATION_EN = 'ENGLISH'; const TRANSLATION_FR = 'FRANCAIS'; - const DEFAULT_POLL_INTERVAL = 120; const C = { @@ -75,8 +72,7 @@ interface TICMeterData { max?: number; } - -interface Translation { +interface Translation { nameFR?: string; descFR: string; descEN?: string; @@ -87,12 +83,30 @@ type Translations = { }; const ticmeterOptionsFRTr: Translations = { - 0: {descEN: `Refresh rate for static values (those with refresh buttons). Default: ${DEFAULT_POLL_INTERVAL} s`, descFR: `Temps d'actualisation des valeurs statiques (celles qui possèdent des boutons refresh). Par défaut: ${DEFAULT_POLL_INTERVAL} s`}, - 1: {descEN: 'Linky TIC communication mode. Defaults to AUTO mode. To be used in case of problem', descFR: 'Mode de communication TIC du Linky. Par défaut en mode AUTO. À utiliser en cas de problème'}, - 2: {descEN: 'Current electricity contract on Linky. Defaults to AUTO mode. Displays the correct entities. To be used in case of problem', descFR: 'Contrat électrique actuel sur le Linky. Par défaut en mode AUTO. Permet d\'afficher les bonnes entités. À utiliser en cas de problème'}, - 3: {descEN: 'Linky electrical mode. Defaults to AUTO mode. To be used in case of problem', descFR: 'Mode électrique du Linky. Par défaut en mode AUTO. À utiliser en cas de problème'}, - 4: {descEN: 'Producer mode: displays electricity production indexes. Default: OFF', descFR: 'Mode producteur: affiche les index de production électrique. Par défaut: OFF'}, - 5: {descEN: 'Displays all meter data. For advanced use. Default: OFF', descFR: 'Affiche toutes les données du compteur. Pour un usage avancé. Par défaut: OFF'}, + 0: { + descEN: `Refresh rate for static values (those with refresh buttons). Default: ${DEFAULT_POLL_INTERVAL} s`, + descFR: `Temps d'actualisation des valeurs statiques (celles qui possèdent des boutons refresh). Par défaut: ${DEFAULT_POLL_INTERVAL} s`, + }, + 1: { + descEN: 'Linky TIC communication mode. Defaults to AUTO mode. To be used in case of problem', + descFR: 'Mode de communication TIC du Linky. Par défaut en mode AUTO. À utiliser en cas de problème', + }, + 2: { + descEN: 'Current electricity contract on Linky. Defaults to AUTO mode. Displays the correct entities. To be used in case of problem', + descFR: "Contrat électrique actuel sur le Linky. Par défaut en mode AUTO. Permet d'afficher les bonnes entités. À utiliser en cas de problème", + }, + 3: { + descEN: 'Linky electrical mode. Defaults to AUTO mode. To be used in case of problem', + descFR: 'Mode électrique du Linky. Par défaut en mode AUTO. À utiliser en cas de problème', + }, + 4: { + descEN: 'Producer mode: displays electricity production indexes. Default: OFF', + descFR: 'Mode producteur: affiche les index de production électrique. Par défaut: OFF', + }, + 5: { + descEN: 'Displays all meter data. For advanced use. Default: OFF', + descFR: 'Affiche toutes les données du compteur. Pour un usage avancé. Par défaut: OFF', + }, 6: {descEN: 'Language: Default French', descFR: 'Langue. Par défaut Francais'}, }; @@ -106,222 +120,1589 @@ const ticmeterOptions = [ e.enum('translation', ea.SET, [TRANSLATION_FR, TRANSLATION_EN]).withDescription(ticmeterOptionsFRTr[6].descEN), ]; - const ticmeterDatasFRTranslation: Translations = { - 0: {nameFR: 'Mode_TIC', descFR: 'Mode_de_communication_TIC'}, - 1: {nameFR: 'Mode_electrique', descFR: 'Mode_de_electrique_du_compteur'}, - 2: {nameFR: 'Option_tarifaire', descFR: 'Option_tarifaire'}, - 3: {nameFR: 'Duree_de_fonctionnement', descFR: 'Duree_depuis_le_dernier_redemarrage'}, - 4: {nameFR: 'Duree_actualisation', descFR: 'Duree_entre_les_actualisations'}, - 5: {nameFR: 'Identifiant', descFR: 'Numero_de_serie_du_compteur'}, - 6: {nameFR: 'Puissance_Max_contrat', descFR: 'Puissance_Max_contrat'}, - 7: {nameFR: 'Index_total', descFR: 'Somme_de_tous_les_index'}, - 8: {nameFR: 'Index_BASE', descFR: 'Index_Tarif_Base'}, - 9: {nameFR: 'Index_HC', descFR: 'Index_Tarif_Heures_Creuses'}, - 10: {nameFR: 'Index_HP', descFR: 'Index_Tarif_Heures_Pleines'}, - 11: {nameFR: 'Index_EJP_HN', descFR: 'Index_Tarif_EJP_Heures_Normales'}, - 12: {nameFR: 'Index_EJP_HPM', descFR: 'Index_Tarif_EJP_Heures_de_Pointe_Mobile'}, - 13: {nameFR: 'Preavis_EJP', descFR: 'Preavis_EJP'}, - 14: {nameFR: 'Index_BBRHCJB', descFR: 'Index_Tarif_Heures_Creuses_Jours_Bleus'}, - 15: {nameFR: 'Index_BBRHPJB', descFR: 'Index_Tarif_Heures_Pleines_Jours_Bleus'}, - 16: {nameFR: 'Index_BBRHCJW', descFR: 'Index_Tarif_Heures_Creuses_Jours_Blancs'}, - 17: {nameFR: 'Index_BBRHPJW', descFR: 'Index_Tarif_Heures_Pleines_Jours_Blancs'}, - 18: {nameFR: 'Index_BBRHCJR', descFR: 'Index_Tarif_Heures_Creuses_Jours_Rouges'}, - 19: {nameFR: 'Index_BBRHPJR', descFR: 'Index_Tarif_Heures_Pleines_Jours_Rouges'}, - 20: {nameFR: 'Index_7', descFR: 'Index_7'}, - 21: {nameFR: 'Index_8', descFR: 'Index_8'}, - 22: {nameFR: 'Index_9', descFR: 'Index_9'}, - 23: {nameFR: 'Index_10', descFR: 'Index_10'}, - 24: {nameFR: 'Tarif_en_cours', descFR: 'Option_tarifaire_en_cours'}, - 25: {nameFR: 'Couleur_demain', descFR: 'Couleur_demain'}, - 26: {nameFR: 'Intensite_instantanee', descFR: 'Intensite_instantanee'}, - 27: {nameFR: 'Intensite_instantanee_Ph_A', descFR: 'Intensite_instantanee_Phase_A'}, - 28: {nameFR: 'Intensite_instantanee_Ph_B', descFR: 'Intensite_instantanee_Phase_B'}, - 29: {nameFR: 'Intensite_instantanee_Ph_C', descFR: 'Intensite_instantanee_Phase_C'}, - 30: {nameFR: 'Intensite_maximale', descFR: 'Intensite_maximale'}, - 31: {nameFR: 'Intensite_maximale_Ph_A', descFR: 'Intensite_maximale_Phase_A'}, - 32: {nameFR: 'Intensite_maximale_Ph_B', descFR: 'Intensite_maximale_Phase_B'}, - 33: {nameFR: 'Intensite_maximale_Ph_C', descFR: 'Intensite_maximale_Phase_C'}, - 34: {nameFR: 'Depassement_de_puissance', descFR: 'Depassement_de_puissance'}, - 35: {nameFR: 'Depassement_Itensite_Ph_A', descFR: 'Depassement_de_puissance_Phase_A'}, - 36: {nameFR: 'Depassement_Itensite_Ph_B', descFR: 'Depassement_de_puissance_Phase_B'}, - 37: {nameFR: 'Depassement_Itensite_Ph_C', descFR: 'Depassement_de_puissance_Phase_C'}, - 38: {nameFR: 'Puissance_Apparente', descFR: 'Puissance_Apparente'}, - 39: {nameFR: 'Puissance_Apparente_Ph_A', descFR: 'Puissance_Apparente_Phase_A'}, - 40: {nameFR: 'Puissance_Apparente_Ph_B', descFR: 'Puissance_Apparente_Phase_B'}, - 41: {nameFR: 'Puissance_Apparente_Ph_C', descFR: 'Puissance_Apparente_Phase_C'}, - 42: {nameFR: 'Index_energie_injectee', descFR: 'Index_energie_injectee'}, - 43: {nameFR: 'Puissance_injectee', descFR: 'Puissance_injectee'}, - 44: {nameFR: 'Puissance_max_injectee_Auj.', descFR: 'Puissance_max_injectee_Aujourdhui'}, - 45: {nameFR: 'Heure_PMAX_injectee_Auj.', descFR: 'Date_et_Heure_puissance_max_injectee_aujourdhui'}, - 46: {nameFR: 'Puissance_max_injectee_Hier', descFR: 'Puissance_max_injectee_Hier'}, - 47: {nameFR: 'Heure_PMAX_injectee_Hier', descFR: 'Date_et_Heure_puissance_max_injectee_hier'}, - 48: {nameFR: 'Presence_de_potentiels', descFR: 'Presence_de_potentiels'}, - 49: {nameFR: 'Horaire_Heures_Creuses', descFR: 'Horaire_Heures_Creuses'}, - 50: {nameFR: 'Registre_de_Status', descFR: 'Registre_de_status_du_compteur'}, - 51: {nameFR: 'Index_1_Distributeur', descFR: 'Index_1_Energie_soutiree_Distributeur'}, - 52: {nameFR: 'Index_2_Distributeur', descFR: 'Index_2_Energie_soutiree_Distributeur'}, - 53: {nameFR: 'Index_3_Distributeur', descFR: 'Index_3_Energie_soutiree_Distributeur'}, - 54: {nameFR: 'Index_4_Distributeur', descFR: 'Index_4_Energie_soutiree_Distributeur'}, - 55: {nameFR: 'Tension_instantanee', descFR: 'Tension_instantanee_efficace'}, - 56: {nameFR: 'Tension_instantanee_Ph_A', descFR: 'Tension_instantanee_efficace_Phase_A'}, - 57: {nameFR: 'Tension_instantanee_Ph_B', descFR: 'Tension_instantanee_efficace_Phase_B'}, - 58: {nameFR: 'Tension_instantanee_Ph_C', descFR: 'Tension_instantanee_efficace_Phase_C'}, - 59: {nameFR: 'Tension_moyenne', descFR: 'Tension_moyenne'}, - 60: {nameFR: 'Tension_moyenne_Ph_A', descFR: 'Tension_moyenne_Phase_A'}, - 61: {nameFR: 'Tension_moyenne_Ph_B', descFR: 'Tension_moyenne_Phase_B'}, - 62: {nameFR: 'Tension_moyenne_Ph_C', descFR: 'Tension_moyenne_Phase_C'}, - 63: {nameFR: 'Puissance_max_Auj', descFR: 'Puissance_max_Aujourdhui'}, - 64: {nameFR: 'Heure_Puissance_max_Auj', descFR: 'Date_et_Heure_de_la_puissance_max_aujourdhui'}, - 65: {nameFR: 'Puissance_max_Auj_Ph_A', descFR: 'Puissance_max_Aujourdhui_Phase_A'}, - 66: {nameFR: 'Heure_Puissance_max_Auj_Ph_A', descFR: 'Date_et_Heure_de_la_puissance_max_aujourdhui_Ph_A'}, - 67: {nameFR: 'Puissance_max_Auj_Ph_B', descFR: 'Puissance_max_Aujourdhui_Phase_B'}, - 68: {nameFR: 'Heure_Puissance_max_Auj_Ph_B', descFR: 'Date_et_Heure_de_la_puissance_max_aujourdhui_Ph_B'}, - 69: {nameFR: 'Puissance_max_Auj_Ph_C', descFR: 'Puissance_max_Aujourdhui_Phase_C'}, - 70: {nameFR: 'Heure_Puissance_max_Auj_Ph_C', descFR: 'Date_et_Heure_de_la_puissance_max_aujourdhui_Ph_C'}, - 71: {nameFR: 'Puissance_maximale_triphasee', descFR: 'Puissance_maximale_triphasee'}, - 72: {nameFR: 'Puissance_max_Hier', descFR: 'Puissance_max_Hier'}, - 73: {nameFR: 'Heure_Puissance_max_Hier', descFR: 'Date_et_Heure_de_la_puissance_max_hier'}, - 74: {nameFR: 'Puissance_max_Hier_Ph_A', descFR: 'Puissance_max_Hier_Phase_A'}, + 0: {nameFR: 'Mode_TIC', descFR: 'Mode_de_communication_TIC'}, + 1: {nameFR: 'Mode_electrique', descFR: 'Mode_de_electrique_du_compteur'}, + 2: {nameFR: 'Option_tarifaire', descFR: 'Option_tarifaire'}, + 3: {nameFR: 'Duree_de_fonctionnement', descFR: 'Duree_depuis_le_dernier_redemarrage'}, + 4: {nameFR: 'Duree_actualisation', descFR: 'Duree_entre_les_actualisations'}, + 5: {nameFR: 'Identifiant', descFR: 'Numero_de_serie_du_compteur'}, + 6: {nameFR: 'Puissance_Max_contrat', descFR: 'Puissance_Max_contrat'}, + 7: {nameFR: 'Index_total', descFR: 'Somme_de_tous_les_index'}, + 8: {nameFR: 'Index_BASE', descFR: 'Index_Tarif_Base'}, + 9: {nameFR: 'Index_HC', descFR: 'Index_Tarif_Heures_Creuses'}, + 10: {nameFR: 'Index_HP', descFR: 'Index_Tarif_Heures_Pleines'}, + 11: {nameFR: 'Index_EJP_HN', descFR: 'Index_Tarif_EJP_Heures_Normales'}, + 12: {nameFR: 'Index_EJP_HPM', descFR: 'Index_Tarif_EJP_Heures_de_Pointe_Mobile'}, + 13: {nameFR: 'Preavis_EJP', descFR: 'Preavis_EJP'}, + 14: {nameFR: 'Index_BBRHCJB', descFR: 'Index_Tarif_Heures_Creuses_Jours_Bleus'}, + 15: {nameFR: 'Index_BBRHPJB', descFR: 'Index_Tarif_Heures_Pleines_Jours_Bleus'}, + 16: {nameFR: 'Index_BBRHCJW', descFR: 'Index_Tarif_Heures_Creuses_Jours_Blancs'}, + 17: {nameFR: 'Index_BBRHPJW', descFR: 'Index_Tarif_Heures_Pleines_Jours_Blancs'}, + 18: {nameFR: 'Index_BBRHCJR', descFR: 'Index_Tarif_Heures_Creuses_Jours_Rouges'}, + 19: {nameFR: 'Index_BBRHPJR', descFR: 'Index_Tarif_Heures_Pleines_Jours_Rouges'}, + 20: {nameFR: 'Index_7', descFR: 'Index_7'}, + 21: {nameFR: 'Index_8', descFR: 'Index_8'}, + 22: {nameFR: 'Index_9', descFR: 'Index_9'}, + 23: {nameFR: 'Index_10', descFR: 'Index_10'}, + 24: {nameFR: 'Tarif_en_cours', descFR: 'Option_tarifaire_en_cours'}, + 25: {nameFR: 'Couleur_demain', descFR: 'Couleur_demain'}, + 26: {nameFR: 'Intensite_instantanee', descFR: 'Intensite_instantanee'}, + 27: {nameFR: 'Intensite_instantanee_Ph_A', descFR: 'Intensite_instantanee_Phase_A'}, + 28: {nameFR: 'Intensite_instantanee_Ph_B', descFR: 'Intensite_instantanee_Phase_B'}, + 29: {nameFR: 'Intensite_instantanee_Ph_C', descFR: 'Intensite_instantanee_Phase_C'}, + 30: {nameFR: 'Intensite_maximale', descFR: 'Intensite_maximale'}, + 31: {nameFR: 'Intensite_maximale_Ph_A', descFR: 'Intensite_maximale_Phase_A'}, + 32: {nameFR: 'Intensite_maximale_Ph_B', descFR: 'Intensite_maximale_Phase_B'}, + 33: {nameFR: 'Intensite_maximale_Ph_C', descFR: 'Intensite_maximale_Phase_C'}, + 34: {nameFR: 'Depassement_de_puissance', descFR: 'Depassement_de_puissance'}, + 35: {nameFR: 'Depassement_Itensite_Ph_A', descFR: 'Depassement_de_puissance_Phase_A'}, + 36: {nameFR: 'Depassement_Itensite_Ph_B', descFR: 'Depassement_de_puissance_Phase_B'}, + 37: {nameFR: 'Depassement_Itensite_Ph_C', descFR: 'Depassement_de_puissance_Phase_C'}, + 38: {nameFR: 'Puissance_Apparente', descFR: 'Puissance_Apparente'}, + 39: {nameFR: 'Puissance_Apparente_Ph_A', descFR: 'Puissance_Apparente_Phase_A'}, + 40: {nameFR: 'Puissance_Apparente_Ph_B', descFR: 'Puissance_Apparente_Phase_B'}, + 41: {nameFR: 'Puissance_Apparente_Ph_C', descFR: 'Puissance_Apparente_Phase_C'}, + 42: {nameFR: 'Index_energie_injectee', descFR: 'Index_energie_injectee'}, + 43: {nameFR: 'Puissance_injectee', descFR: 'Puissance_injectee'}, + 44: {nameFR: 'Puissance_max_injectee_Auj.', descFR: 'Puissance_max_injectee_Aujourdhui'}, + 45: {nameFR: 'Heure_PMAX_injectee_Auj.', descFR: 'Date_et_Heure_puissance_max_injectee_aujourdhui'}, + 46: {nameFR: 'Puissance_max_injectee_Hier', descFR: 'Puissance_max_injectee_Hier'}, + 47: {nameFR: 'Heure_PMAX_injectee_Hier', descFR: 'Date_et_Heure_puissance_max_injectee_hier'}, + 48: {nameFR: 'Presence_de_potentiels', descFR: 'Presence_de_potentiels'}, + 49: {nameFR: 'Horaire_Heures_Creuses', descFR: 'Horaire_Heures_Creuses'}, + 50: {nameFR: 'Registre_de_Status', descFR: 'Registre_de_status_du_compteur'}, + 51: {nameFR: 'Index_1_Distributeur', descFR: 'Index_1_Energie_soutiree_Distributeur'}, + 52: {nameFR: 'Index_2_Distributeur', descFR: 'Index_2_Energie_soutiree_Distributeur'}, + 53: {nameFR: 'Index_3_Distributeur', descFR: 'Index_3_Energie_soutiree_Distributeur'}, + 54: {nameFR: 'Index_4_Distributeur', descFR: 'Index_4_Energie_soutiree_Distributeur'}, + 55: {nameFR: 'Tension_instantanee', descFR: 'Tension_instantanee_efficace'}, + 56: {nameFR: 'Tension_instantanee_Ph_A', descFR: 'Tension_instantanee_efficace_Phase_A'}, + 57: {nameFR: 'Tension_instantanee_Ph_B', descFR: 'Tension_instantanee_efficace_Phase_B'}, + 58: {nameFR: 'Tension_instantanee_Ph_C', descFR: 'Tension_instantanee_efficace_Phase_C'}, + 59: {nameFR: 'Tension_moyenne', descFR: 'Tension_moyenne'}, + 60: {nameFR: 'Tension_moyenne_Ph_A', descFR: 'Tension_moyenne_Phase_A'}, + 61: {nameFR: 'Tension_moyenne_Ph_B', descFR: 'Tension_moyenne_Phase_B'}, + 62: {nameFR: 'Tension_moyenne_Ph_C', descFR: 'Tension_moyenne_Phase_C'}, + 63: {nameFR: 'Puissance_max_Auj', descFR: 'Puissance_max_Aujourdhui'}, + 64: {nameFR: 'Heure_Puissance_max_Auj', descFR: 'Date_et_Heure_de_la_puissance_max_aujourdhui'}, + 65: {nameFR: 'Puissance_max_Auj_Ph_A', descFR: 'Puissance_max_Aujourdhui_Phase_A'}, + 66: {nameFR: 'Heure_Puissance_max_Auj_Ph_A', descFR: 'Date_et_Heure_de_la_puissance_max_aujourdhui_Ph_A'}, + 67: {nameFR: 'Puissance_max_Auj_Ph_B', descFR: 'Puissance_max_Aujourdhui_Phase_B'}, + 68: {nameFR: 'Heure_Puissance_max_Auj_Ph_B', descFR: 'Date_et_Heure_de_la_puissance_max_aujourdhui_Ph_B'}, + 69: {nameFR: 'Puissance_max_Auj_Ph_C', descFR: 'Puissance_max_Aujourdhui_Phase_C'}, + 70: {nameFR: 'Heure_Puissance_max_Auj_Ph_C', descFR: 'Date_et_Heure_de_la_puissance_max_aujourdhui_Ph_C'}, + 71: {nameFR: 'Puissance_maximale_triphasee', descFR: 'Puissance_maximale_triphasee'}, + 72: {nameFR: 'Puissance_max_Hier', descFR: 'Puissance_max_Hier'}, + 73: {nameFR: 'Heure_Puissance_max_Hier', descFR: 'Date_et_Heure_de_la_puissance_max_hier'}, + 74: {nameFR: 'Puissance_max_Hier_Ph_A', descFR: 'Puissance_max_Hier_Phase_A'}, 75: {nameFR: 'Heure_Puissance_max_Hier_Ph_A', descFR: 'Date_et_Heure_de_la_puissance_max_hier_Ph_A'}, - 76: {nameFR: 'Puissance_max_Hier_Ph_B', descFR: 'Puissance_max_Hier_Phase_B'}, + 76: {nameFR: 'Puissance_max_Hier_Ph_B', descFR: 'Puissance_max_Hier_Phase_B'}, 77: {nameFR: 'Heure_Puissance_max_Hier_Ph_B', descFR: 'Date_et_Heure_de_la_puissance_max_hier_Ph_B'}, - 78: {nameFR: 'Puissance_max_Hier_Ph_C', descFR: 'Puissance_max_Hier_Phase_C'}, + 78: {nameFR: 'Puissance_max_Hier_Ph_C', descFR: 'Puissance_max_Hier_Phase_C'}, 79: {nameFR: 'Heure_Puissance_max_Hier_Ph_C', descFR: 'Date_et_Heure_de_la_puissance_max_hier_Ph_C'}, - 80: {nameFR: 'Index_en_cours', descFR: 'Numereo_de_lindex_tarifaire_en_cours'}, - 81: {nameFR: 'N_jours_en_cours', descFR: 'N_jours_en_cours_fournisseur'}, - 82: {nameFR: 'N_prochain_jour', descFR: 'N_prochain_jour_fournisseur'}, - 83: {nameFR: 'Relais', descFR: 'Relais_virtuel_du_compteur'}, - 84: {nameFR: 'PMR', descFR: 'Identifiant_Point_Reference_Mesure'}, - 85: {nameFR: 'Message_court', descFR: 'Message_court'}, - 86: {nameFR: 'Message_ultra_court', descFR: 'Message_ultra_court'}, - 87: {nameFR: 'Version_de_la_TIC', descFR: 'Version_de_la_TIC'}, - 88: {nameFR: 'Date_et_heure_Compteur', descFR: 'Date_et_heure_du_compteur'}, - 89: {nameFR: 'Profil_prochain_jour', descFR: 'Profil_du_prochain_jour'}, - 90: {nameFR: 'Profil_prochain_jour_pointe', descFR: 'Profil_du_prochain_jour_pointe'}, - 91: {nameFR: 'Point_n_courbe_soutiree', descFR: 'Point_n_de_la_courbe_de_charge_active_soutiree'}, - 92: {nameFR: 'Point_n-1_courbe_soutiree', descFR: 'Point_n-1_de_la_courbe_de_charge_active_soutiree'}, - 93: {nameFR: 'Point_n_courbe_injectee', descFR: 'Point_n_de_la_courbe_de_charge_active_injectee'}, - 94: {nameFR: 'Point_n-1_courbe_injectee', descFR: 'Point_n-1_de_la_courbe_de_charge_active_injectee'}, - 95: {nameFR: 'Energie_reactive_Q1_totale', descFR: 'Energie_reactive_Q1_totale'}, - 96: {nameFR: 'Energie_reactive_Q2_totale', descFR: 'Energie_reactive_Q2_totale'}, - 97: {nameFR: 'Energie_reactive_Q3_totale', descFR: 'Energie_reactive_Q3_totale'}, - 98: {nameFR: 'Energie_reactive_Q4_totale', descFR: 'Energie_reactive_Q4_totale'}, - 99: {nameFR: 'Debut_Pointe_Mobile_1', descFR: 'Debut_Pointe_Mobile_1'}, - 100: {nameFR: 'Fin_Pointe_Mobile_1', descFR: 'Fin_Pointe_Mobile_1'}, - 101: {nameFR: 'Debut_Pointe_Mobile_2', descFR: 'Debut_Pointe_Mobile_2'}, - 102: {nameFR: 'Fin_Pointe_Mobile_2', descFR: 'Fin_Pointe_Mobile_2'}, - 103: {nameFR: 'Debut_Pointe_Mobile_3', descFR: 'Debut_Pointe_Mobile_3'}, - 104: {nameFR: 'Fin_Pointe_Mobile_3', descFR: 'Fin_Pointe_Mobile_3'}, + 80: {nameFR: 'Index_en_cours', descFR: 'Numereo_de_lindex_tarifaire_en_cours'}, + 81: {nameFR: 'N_jours_en_cours', descFR: 'N_jours_en_cours_fournisseur'}, + 82: {nameFR: 'N_prochain_jour', descFR: 'N_prochain_jour_fournisseur'}, + 83: {nameFR: 'Relais', descFR: 'Relais_virtuel_du_compteur'}, + 84: {nameFR: 'PMR', descFR: 'Identifiant_Point_Reference_Mesure'}, + 85: {nameFR: 'Message_court', descFR: 'Message_court'}, + 86: {nameFR: 'Message_ultra_court', descFR: 'Message_ultra_court'}, + 87: {nameFR: 'Version_de_la_TIC', descFR: 'Version_de_la_TIC'}, + 88: {nameFR: 'Date_et_heure_Compteur', descFR: 'Date_et_heure_du_compteur'}, + 89: {nameFR: 'Profil_prochain_jour', descFR: 'Profil_du_prochain_jour'}, + 90: {nameFR: 'Profil_prochain_jour_pointe', descFR: 'Profil_du_prochain_jour_pointe'}, + 91: {nameFR: 'Point_n_courbe_soutiree', descFR: 'Point_n_de_la_courbe_de_charge_active_soutiree'}, + 92: {nameFR: 'Point_n-1_courbe_soutiree', descFR: 'Point_n-1_de_la_courbe_de_charge_active_soutiree'}, + 93: {nameFR: 'Point_n_courbe_injectee', descFR: 'Point_n_de_la_courbe_de_charge_active_injectee'}, + 94: {nameFR: 'Point_n-1_courbe_injectee', descFR: 'Point_n-1_de_la_courbe_de_charge_active_injectee'}, + 95: {nameFR: 'Energie_reactive_Q1_totale', descFR: 'Energie_reactive_Q1_totale'}, + 96: {nameFR: 'Energie_reactive_Q2_totale', descFR: 'Energie_reactive_Q2_totale'}, + 97: {nameFR: 'Energie_reactive_Q3_totale', descFR: 'Energie_reactive_Q3_totale'}, + 98: {nameFR: 'Energie_reactive_Q4_totale', descFR: 'Energie_reactive_Q4_totale'}, + 99: {nameFR: 'Debut_Pointe_Mobile_1', descFR: 'Debut_Pointe_Mobile_1'}, + 100: {nameFR: 'Fin_Pointe_Mobile_1', descFR: 'Fin_Pointe_Mobile_1'}, + 101: {nameFR: 'Debut_Pointe_Mobile_2', descFR: 'Debut_Pointe_Mobile_2'}, + 102: {nameFR: 'Fin_Pointe_Mobile_2', descFR: 'Fin_Pointe_Mobile_2'}, + 103: {nameFR: 'Debut_Pointe_Mobile_3', descFR: 'Debut_Pointe_Mobile_3'}, + 104: {nameFR: 'Fin_Pointe_Mobile_3', descFR: 'Fin_Pointe_Mobile_3'}, }; - const ticmeterDatas: TICMeterData[] = [ - {id: 0, name: 'TIC_Mode', desc: 'TIC_Communication_Mode', clust: CLUSTER_TIC, attr: 'ticMode', type: ENUM, unit: '', poll: true, tic: T.ANY, contract: C.ANY, elec: E.ANY, prod: false, values: modeTICEnum}, - {id: 1, name: 'Electric_Mode', desc: 'Meter_Electric_Mode', clust: CLUSTER_TIC, attr: 'elecMode', type: ENUM, unit: '', poll: true, tic: T.ANY, contract: C.ANY, elec: E.ANY, prod: false, values: modeElecEnum}, - {id: 2, name: 'Tariff_Option', desc: 'Tariff_Option', clust: CLUSTER_TIC, attr: 'contractType', type: STRING, unit: '', poll: true, tic: T.ANY, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 3, name: 'Uptime', desc: 'Duration_since_last_restart', clust: CLUSTER_TIC, attr: 'uptime', type: NUMBER, unit: 's', poll: true, tic: T.ANY, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 4, name: 'Refresh_Rate', desc: 'Time_between_refreshes', clust: CLUSTER_TIC, attr: 'refreshRate', type: NUM_RW, unit: 's', poll: true, tic: T.ANY, contract: C.ANY, elec: E.ANY, prod: false, min: 30, max: 300}, - {id: 5, name: 'Identifier', desc: 'Meter_serial_number', clust: CLUSTER_MET, attr: 'meterSerialNumber', type: STRING, unit: '', poll: true, tic: T.ANY, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 6, name: 'Max_Contract_Power', desc: 'Max_Contract_Power', clust: CLUSTER_TIC, attr: 'maxContractPower', type: NUMBER, unit: 'kVA', poll: true, tic: T.ANY, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 7, name: 'Total_Index', desc: 'Sum_of_all_Index', clust: CLUSTER_MET, attr: 'currentSummDelivered', type: NUMBER, unit: 'Wh', poll: false, tic: T.ANY, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 8, name: 'BASE_Index', desc: 'Base_Tariff_Index', clust: CLUSTER_MET, attr: 'currentTier1SummDelivered', type: NUMBER, unit: 'Wh', poll: false, tic: T.ANY, contract: C.BASE, elec: E.ANY, prod: false}, - {id: 9, name: 'Off-Peak_Index', desc: 'Off-Peak_Tariff_Index', clust: CLUSTER_MET, attr: 'currentTier1SummDelivered', type: NUMBER, unit: 'Wh', poll: false, tic: T.ANY, contract: C.HCHP, elec: E.ANY, prod: false}, - {id: 10, name: 'Peak_Index', desc: 'Peak_Tariff_Index', clust: CLUSTER_MET, attr: 'currentTier2SummDelivered', type: NUMBER, unit: 'Wh', poll: false, tic: T.ANY, contract: C.HCHP, elec: E.ANY, prod: false}, - {id: 11, name: 'EJP_Normal_Hours_Index', desc: 'EJP_Normal_Hours_Tariff_Index', clust: CLUSTER_MET, attr: 'currentTier1SummDelivered', type: NUMBER, unit: 'Wh', poll: false, tic: T.ANY, contract: C.EJP, elec: E.ANY, prod: false}, - {id: 12, name: 'EJP_Mobile_Peak_Hours_Index', desc: 'EJP_Mobile_Peak_Hours_Tariff_Index', clust: CLUSTER_MET, attr: 'currentTier2SummDelivered', type: NUMBER, unit: 'Wh', poll: false, tic: T.ANY, contract: C.EJP, elec: E.ANY, prod: false}, - {id: 13, name: 'EJP_Notice', desc: 'EJP_Notice', clust: CLUSTER_TIC, attr: 'startEJP', type: STRING, unit: '', poll: false, tic: T.ANY, contract: C.EJP, elec: E.ANY, prod: false}, - {id: 14, name: 'BBRHCJB_Index', desc: 'Blue_Days_Off-Peak_Tariff_Index', clust: CLUSTER_MET, attr: 'currentTier1SummDelivered', type: NUMBER, unit: 'Wh', poll: false, tic: T.ANY, contract: C.TEMPO, elec: E.ANY, prod: false}, - {id: 15, name: 'BBRHPJB_Index', desc: 'Blue_Days_Peak_Tariff_Index', clust: CLUSTER_MET, attr: 'currentTier2SummDelivered', type: NUMBER, unit: 'Wh', poll: false, tic: T.ANY, contract: C.TEMPO, elec: E.ANY, prod: false}, - {id: 16, name: 'BBRHCJW_Index', desc: 'White_Days_Off-Peak_Tariff_Index', clust: CLUSTER_MET, attr: 'currentTier3SummDelivered', type: NUMBER, unit: 'Wh', poll: false, tic: T.ANY, contract: C.TEMPO, elec: E.ANY, prod: false}, - {id: 17, name: 'BBRHPJW_Index', desc: 'White_Days_Peak_Tariff_Index', clust: CLUSTER_MET, attr: 'currentTier4SummDelivered', type: NUMBER, unit: 'Wh', poll: false, tic: T.ANY, contract: C.TEMPO, elec: E.ANY, prod: false}, - {id: 18, name: 'BBRHCJR_Index', desc: 'Red_Days_Off-Peak_Tariff_Index', clust: CLUSTER_MET, attr: 'currentTier5SummDelivered', type: NUMBER, unit: 'Wh', poll: false, tic: T.ANY, contract: C.TEMPO, elec: E.ANY, prod: false}, - {id: 19, name: 'BBRHPJR_Index', desc: 'Red_Days_Peak_Tariff_Index', clust: CLUSTER_MET, attr: 'currentTier6SummDelivered', type: NUMBER, unit: 'Wh', poll: false, tic: T.ANY, contract: C.TEMPO, elec: E.ANY, prod: false}, - {id: 20, name: 'Index_7', desc: 'Index_7', clust: CLUSTER_MET, attr: 'currentTier7SummDelivered', type: NUMBER, unit: 'Wh', poll: false, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 21, name: 'Index_8', desc: 'Index_8', clust: CLUSTER_MET, attr: 'currentTier8SummDelivered', type: NUMBER, unit: 'Wh', poll: false, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 22, name: 'Index_9', desc: 'Index_9', clust: CLUSTER_MET, attr: 'currentTier9SummDelivered', type: NUMBER, unit: 'Wh', poll: false, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 23, name: 'Index_10', desc: 'Index_10', clust: CLUSTER_MET, attr: 'currentTier10SummDelivered', type: NUMBER, unit: 'Wh', poll: false, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 24, name: 'Current_Tariff', desc: 'Current_Tariff_Option', clust: CLUSTER_TIC, attr: 'currentTarif', type: STRING, unit: '', poll: false, tic: T.ANY, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 25, name: 'Tomorrow_Color', desc: 'Tomorrow_Color', clust: CLUSTER_TIC, attr: 'tomorowColor', type: STRING, unit: '', poll: false, tic: T.ANY, contract: C.TEMPO, elec: E.ANY, prod: false}, - {id: 26, name: 'Instant_Intensity', desc: 'Instant_Intensity', clust: CLUSTER_ELE, attr: 'rmsCurrent', type: NUMBER, unit: 'A', poll: false, tic: T.ANY, contract: C.ANY, elec: E.MONO, prod: false}, - {id: 27, name: 'Instant_Intensity_Phase_A', desc: 'Instant_Intensity_Phase_A', clust: CLUSTER_ELE, attr: 'rmsCurrent', type: NUMBER, unit: 'A', poll: false, tic: T.ANY, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 28, name: 'Instant_Intensity_Phase_B', desc: 'Instant_Intensity_Phase_B', clust: CLUSTER_ELE, attr: 'rmsCurrentPhB', type: NUMBER, unit: 'A', poll: false, tic: T.ANY, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 29, name: 'Instant_Intensity_Phase_C', desc: 'Instant_Intensity_Phase_C', clust: CLUSTER_ELE, attr: 'rmsCurrentPhC', type: NUMBER, unit: 'A', poll: false, tic: T.ANY, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 30, name: 'Max_Intensity', desc: 'Max_Intensity', clust: CLUSTER_ELE, attr: 'rmsCurrentMax', type: NUMBER, unit: 'A', poll: true, tic: T.HIST, contract: C.ANY, elec: E.MONO, prod: false}, - {id: 31, name: 'Max_Intensity_Phase_A', desc: 'Max_Intensity_Phase_A', clust: CLUSTER_ELE, attr: 'rmsCurrentMax', type: NUMBER, unit: 'A', poll: true, tic: T.HIST, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 32, name: 'Max_Intensity_Phase_B', desc: 'Max_Intensity_Phase_B', clust: CLUSTER_ELE, attr: 'rmsCurrentMaxPhB', type: NUMBER, unit: 'A', poll: true, tic: T.HIST, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 33, name: 'Max_Intensity_Phase_C', desc: 'Max_Intensity_Phase_C', clust: CLUSTER_ELE, attr: 'rmsCurrentMaxPhC', type: NUMBER, unit: 'A', poll: true, tic: T.HIST, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 34, name: 'Power_Exceedance', desc: 'Power_Exceedance', clust: CLUSTER_TIC, attr: 'powerOverrun', type: NUMBER, unit: 'A', poll: false, tic: T.ANY, contract: C.ANY, elec: E.MONO, prod: false}, - {id: 35, name: 'Power_Exceedance_Phase_A', desc: 'Power_Exceedance_Phase_A', clust: CLUSTER_TIC, attr: 'powerOverrunA', type: NUMBER, unit: 'A', poll: false, tic: T.ANY, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 36, name: 'Power_Exceedance_Phase_B', desc: 'Power_Exceedance_Phase_B', clust: CLUSTER_TIC, attr: 'powerOverrunB', type: NUMBER, unit: 'A', poll: false, tic: T.ANY, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 37, name: 'Power_Exceedance_Phase_C', desc: 'Power_Exceedance_Phase_C', clust: CLUSTER_TIC, attr: 'powerOverrunC', type: NUMBER, unit: 'A', poll: false, tic: T.ANY, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 38, name: 'Apparent_Power', desc: 'Apparent_Power', clust: CLUSTER_ELE, attr: 'apparentPower', type: NUMBER, unit: 'VA', poll: false, tic: T.ANY, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 39, name: 'Apparent_Power_Phase_A', desc: 'Apparent_Power_Phase_A', clust: CLUSTER_ELE, attr: 'apparentPower', type: NUMBER, unit: 'VA', poll: false, tic: T.STD, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 40, name: 'Apparent_Power_Phase_B', desc: 'Apparent_Power_Phase_B', clust: CLUSTER_ELE, attr: 'apparentPowerPhB', type: NUMBER, unit: 'VA', poll: false, tic: T.STD, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 41, name: 'Apparent_Power_Phase_C', desc: 'Apparent_Power_Phase_C', clust: CLUSTER_ELE, attr: 'apparentPowerPhC', type: NUMBER, unit: 'VA', poll: false, tic: T.STD, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 42, name: 'Injected_Energy_Index', desc: 'Injected_Energy_Index', clust: CLUSTER_MET, attr: 'currentSummReceived', type: NUMBER, unit: 'Wh', poll: false, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: true}, - {id: 43, name: 'Injected_Power', desc: 'Injected_Power', clust: CLUSTER_TIC, attr: 'powerInjected', type: NUMBER, unit: 'VA', poll: false, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: true}, - {id: 44, name: 'Today_Max_Injected_Power', desc: 'Max_Injected_Power_Today', clust: CLUSTER_TIC, attr: 'powerMaxInjected', type: NUMBER, unit: 'VA', poll: true, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: true}, - {id: 45, name: 'Today_Time_Max_Injected_Power', desc: 'Date_and_Time_of_Today_Max_Injected', clust: CLUSTER_TIC, attr: 'powerMaxInjectedTime', type: TIME, unit: '', poll: true, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: true}, - {id: 46, name: 'Yesterday_Max_Injected_Power', desc: 'Max_Injected_Power_Yesterday', clust: CLUSTER_TIC, attr: 'powerMaxInjectedYesterday', type: NUMBER, unit: 'VA', poll: true, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: true}, - {id: 47, name: 'Yesterday_Time_Max_Injected_Power', desc: 'Date_and_Time_of_Yesterday_Max_Injected', clust: CLUSTER_TIC, attr: 'powerMaxInjectedYesterdayTime', type: TIME, unit: '', poll: true, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: true}, - {id: 48, name: 'Potential_Presence', desc: 'Potential_Presence', clust: CLUSTER_TIC, attr: 'potentialPresence', type: NUMBER, unit: '', poll: true, tic: T.HIST, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 49, name: 'Off-Peak_Hours_Schedule', desc: 'Off-Peak_Hours_Schedule', clust: CLUSTER_TIC, attr: 'hcHours', type: STRING, unit: '', poll: false, tic: T.ANY, contract: C.HCHP, elec: E.ANY, prod: false}, - {id: 50, name: 'Status_Register', desc: 'Meter_Status_Register', clust: CLUSTER_TIC, attr: 'motdetat', type: STRING, unit: '', poll: true, tic: T.ANY, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 51, name: 'Distributor_Index_1', desc: 'Distributor_Drawn_Energy_Index_1', clust: CLUSTER_TIC, attr: 'index1Dist', type: NUMBER, unit: 'Wh', poll: false, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 52, name: 'Distributor_Index_2', desc: 'Distributor_Drawn_Energy_Index_2', clust: CLUSTER_TIC, attr: 'index2Dist', type: NUMBER, unit: 'Wh', poll: false, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 53, name: 'Distributor_Index_3', desc: 'Distributor_Drawn_Energy_Index_3', clust: CLUSTER_TIC, attr: 'index3Dist', type: NUMBER, unit: 'Wh', poll: false, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 54, name: 'Distributor_Index_4', desc: 'Distributor_Drawn_Energy_Index_4', clust: CLUSTER_TIC, attr: 'index4Dist', type: NUMBER, unit: 'Wh', poll: false, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 55, name: 'Instantaneous_Voltage', desc: 'Instantaneous_Effective_Voltage', clust: CLUSTER_ELE, attr: 'rmsVoltage', type: NUMBER, unit: 'V', poll: false, tic: T.STD, contract: C.ANY, elec: E.MONO, prod: false}, - {id: 56, name: 'Instantaneous_Voltage_Phase_A', desc: 'Instantaneous_Effective_Voltage_Phase_A', clust: CLUSTER_ELE, attr: 'rmsVoltage', type: NUMBER, unit: 'V', poll: false, tic: T.STD, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 57, name: 'Instantaneous_Voltage_Phase_B', desc: 'Instantaneous_Effective_Voltage_Phase_B', clust: CLUSTER_ELE, attr: 'rmsVoltagePhB', type: NUMBER, unit: 'V', poll: false, tic: T.STD, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 58, name: 'Instantaneous_Voltage_Phase_C', desc: 'Instantaneous_Effective_Voltage_Phase_C', clust: CLUSTER_ELE, attr: 'rmsVoltagePhC', type: NUMBER, unit: 'V', poll: false, tic: T.STD, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 59, name: 'Average_Voltage', desc: 'Average_Voltage', clust: CLUSTER_ELE, attr: 'averageRmsVoltageMeasPeriod', type: NUMBER, unit: 'V', poll: true, tic: T.STD, contract: C.ANY, elec: E.MONO, prod: false}, - {id: 60, name: 'Average_Voltage_Phase_A', desc: 'Average_Voltage_Phase_A', clust: CLUSTER_ELE, attr: 'averageRmsVoltageMeasPeriod', type: NUMBER, unit: 'V', poll: true, tic: T.STD, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 61, name: 'Average_Voltage_Phase_B', desc: 'Average_Voltage_Phase_B', clust: CLUSTER_ELE, attr: 'averageRmsVoltageMeasurePeriodPhB', type: NUMBER, unit: 'V', poll: true, tic: T.STD, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 62, name: 'Average_Voltage_Phase_C', desc: 'Average_Voltage_Phase_C', clust: CLUSTER_ELE, attr: 'averageRmsVoltageMeasPeriodPhC', type: NUMBER, unit: 'V', poll: true, tic: T.STD, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 63, name: 'Today_Max_Power', desc: 'Max_Power_Today', clust: CLUSTER_ELE, attr: 'activePowerMax', type: NUMBER, unit: 'VA', poll: true, tic: T.STD, contract: C.ANY, elec: E.MONO, prod: false}, - {id: 64, name: 'Time_Today_Max_Power', desc: 'Date_and_Time_of_Max_Power_Today', clust: CLUSTER_TIC, attr: 'powerMaxTodayTime', type: TIME, unit: '', poll: true, tic: T.STD, contract: C.ANY, elec: E.MONO, prod: false}, - {id: 65, name: 'Today_Max_Power_Phase_A', desc: 'Max_Power_Today_Phase_A', clust: CLUSTER_ELE, attr: 'activePowerMax', type: NUMBER, unit: 'VA', poll: true, tic: T.STD, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 66, name: 'Time_Today_Max_Power_Phase_A', desc: 'Date_and_Time_of_Today_Max Power_Phase_A', clust: CLUSTER_TIC, attr: 'powerMaxToday1Time', type: TIME, unit: '', poll: true, tic: T.STD, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 67, name: 'Today_Max_Power_Phase_B', desc: 'Max_Power_Today_Phase_B', clust: CLUSTER_ELE, attr: 'activePowerMaxPhB', type: NUMBER, unit: 'VA', poll: true, tic: T.STD, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 68, name: 'Time_Today_Max_Power_Phase_B', desc: 'Date_and_Time_of_Max_Power_Today_Phase_B', clust: CLUSTER_TIC, attr: 'powerMaxToday2Time', type: TIME, unit: '', poll: true, tic: T.STD, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 69, name: 'Today_Max_Power_Phase_C', desc: 'Max_Power_Today_Phase_C', clust: CLUSTER_ELE, attr: 'activePowerMaxPhC', type: NUMBER, unit: 'VA', poll: true, tic: T.STD, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 70, name: 'Time_Today_Max_Power_Phase_C', desc: 'Date_and_Time_of_Max_Power_Today_Phase_C', clust: CLUSTER_TIC, attr: 'powerMaxToday3Time', type: TIME, unit: '', poll: true, tic: T.STD, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 71, name: 'Three-Phase_Max_Power', desc: 'Three-Phase_Max_Power', clust: CLUSTER_ELE, attr: 'activePowerMax', type: NUMBER, unit: 'W', poll: true, tic: T.HIST, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 72, name: 'Yesterday_Max_Power', desc: 'Max_Power_Yesterday', clust: CLUSTER_TIC, attr: 'powerMaxYesterday', type: NUMBER, unit: 'VA', poll: true, tic: T.STD, contract: C.ANY, elec: E.MONO, prod: false}, - {id: 73, name: 'Time_Yesterday_Max_Power', desc: 'Date_and_Time_of_Max_Power_Yesterday', clust: CLUSTER_TIC, attr: 'powerMaxYesterdayTime', type: TIME, unit: '', poll: true, tic: T.STD, contract: C.ANY, elec: E.MONO, prod: false}, - {id: 74, name: 'Max_Yesterday_Power_Phase_A', desc: 'Max_Power_Yesterday_Phase_A', clust: CLUSTER_TIC, attr: 'powerMaxYesterday1', type: NUMBER, unit: 'VA', poll: true, tic: T.STD, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 75, name: 'Time_Yesterday_Max_Power_Phase_A', desc: 'DateTime_of_Max_Power_Yesterday_Phase_A', clust: CLUSTER_TIC, attr: 'powerMaxYesterday1Time', type: TIME, unit: '', poll: true, tic: T.STD, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 76, name: 'Max_Yesterday_Power_Phase_B', desc: 'Max_Power_Yesterday_Phase_B', clust: CLUSTER_TIC, attr: 'powerMaxYesterday2', type: NUMBER, unit: 'VA', poll: true, tic: T.STD, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 77, name: 'Time_Yesterday_Max_Power_Phase_B', desc: 'DateTime_of_Max_Power_Yesterday_Phase_B', clust: CLUSTER_TIC, attr: 'powerMaxYesterday2Time', type: TIME, unit: '', poll: true, tic: T.STD, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 78, name: 'Max_Yesterday_Power_Phase_C', desc: 'Max_Power_Yesterday_Phase_C', clust: CLUSTER_TIC, attr: 'powerMaxYesterday3', type: NUMBER, unit: 'VA', poll: true, tic: T.STD, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 79, name: 'Time_Yesterday_Max_Power_Phase_C', desc: 'DateTime_of_Max_Power_Yesterday_Phase_C', clust: CLUSTER_TIC, attr: 'powerMaxYesterday3Time', type: TIME, unit: '', poll: true, tic: T.STD, contract: C.ANY, elec: E.TRI, prod: false}, - {id: 80, name: 'Current_Index', desc: 'Current_Tariff_Index_Number', clust: CLUSTER_TIC, attr: 'currentIndex', type: NUMBER, unit: '', poll: true, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 81, name: 'Current_Days_Number', desc: 'Current_Supplier_Days_Number', clust: CLUSTER_TIC, attr: 'calendarSupplierDay', type: NUMBER, unit: '', poll: true, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 82, name: 'Next_Day_Number', desc: 'Next_Supplier_Day_Number', clust: CLUSTER_TIC, attr: 'nextSupplierCalendarDay', type: NUMBER, unit: '', poll: true, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 83, name: 'Relay', desc: 'Meter_Virtual_Relay', clust: CLUSTER_TIC, attr: 'relays', type: STRING, unit: '', poll: true, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 84, name: 'PMR', desc: 'Measurement_Reference_Point_Identifier', clust: CLUSTER_MET, attr: 'siteId', type: STRING, unit: '', poll: true, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 85, name: 'Short_Message', desc: 'Short_Message', clust: CLUSTER_TIC, attr: 'shortMsg', type: STRING, unit: '', poll: true, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 86, name: 'Ultra-Short_Message', desc: 'Ultra-Short_Message', clust: CLUSTER_TIC, attr: 'ultraShortMsg', type: STRING, unit: '', poll: true, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 87, name: 'TIC_Version', desc: 'TIC_Version', clust: CLUSTER_TIC, attr: 'ticVersion', type: STRING, unit: '', poll: true, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 88, name: 'Meter_Date_and_Time', desc: 'Meter_Date_and_Time', clust: CLUSTER_TIC, attr: 'date', type: TIME, unit: '', poll: true, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 89, name: 'Next_Day_Profile', desc: 'Next_Day_Profile', clust: CLUSTER_TIC, attr: 'calendarDay', type: STRING, unit: '', poll: true, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 90, name: 'Next_Day_Peak_Profile', desc: 'Next_Day_Peak_Profile', clust: CLUSTER_TIC, attr: 'calendarDayPointe', type: STRING, unit: '', poll: true, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 91, name: 'Current_Drawn_Curve_Point', desc: 'Current_Drawn_Active_Load_Curve_Point', clust: CLUSTER_ELE, attr: 'activePower', type: NUMBER, unit: 'VA', poll: true, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 92, name: 'Previous_Drawn_Curve_Point', desc: 'Previous_Drawn_Active_Load_Curve_Point', clust: CLUSTER_ELE, attr: 'activePowerPhB', type: NUMBER, unit: 'VA', poll: true, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 93, name: 'Current_Injected_Curve_Point', desc: 'Current_Injected_Active_Load_Curve_Point', clust: CLUSTER_TIC, attr: 'injectedLoadN', type: NUMBER, unit: 'VA', poll: true, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: true}, - {id: 94, name: 'Previous_Injected_Curve_Point', desc: 'Previous_Injected_Active_Load_Curve_Point', clust: CLUSTER_TIC, attr: 'injectedLoadN_1', type: NUMBER, unit: 'VA', poll: true, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: true}, - {id: 95, name: 'Total_Reactive_Energy_Q1', desc: 'Total_Reactive_Energy_Q1', clust: CLUSTER_ELE, attr: 'totalReactivePower', type: NUMBER, unit: 'VARh', poll: false, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 96, name: 'Total_Reactive_Energy_Q2', desc: 'Total_Reactive_Energy_Q2', clust: CLUSTER_ELE, attr: 'reactivePower', type: NUMBER, unit: 'VARh', poll: false, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 97, name: 'Total_Reactive_Energy_Q3', desc: 'Total_Reactive_Energy_Q3', clust: CLUSTER_ELE, attr: 'reactivePowerPhB', type: NUMBER, unit: 'VARh', poll: false, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 98, name: 'Total_Reactive_Energy_Q4', desc: 'Total_Reactive_Energy_Q4', clust: CLUSTER_ELE, attr: 'reactivePowerPhC', type: NUMBER, unit: 'VARh', poll: false, tic: T.STD, contract: C.ANY, elec: E.ANY, prod: false}, - {id: 99, name: 'Start_Mobile_Peak_1', desc: 'Start_Mobile_Peak_1', clust: CLUSTER_TIC, attr: 'startEJP1', type: TIME, unit: '', poll: true, tic: T.ANY, contract: C.EJP, elec: E.ANY, prod: false}, - {id: 100, name: 'End_Mobile_Peak_1', desc: 'End_Mobile_Peak_1', clust: CLUSTER_TIC, attr: 'stopEJP1', type: TIME, unit: '', poll: true, tic: T.ANY, contract: C.EJP, elec: E.ANY, prod: false}, - {id: 101, name: 'Start_Mobile_Peak_2', desc: 'Start_Mobile_Peak_2', clust: CLUSTER_TIC, attr: 'startEJP2', type: TIME, unit: '', poll: true, tic: T.ANY, contract: C.EJP, elec: E.ANY, prod: false}, - {id: 102, name: 'End_Mobile_Peak_2', desc: 'End_Mobile_Peak_2', clust: CLUSTER_TIC, attr: 'stopEJP2', type: TIME, unit: '', poll: true, tic: T.ANY, contract: C.EJP, elec: E.ANY, prod: false}, - {id: 103, name: 'Start_Mobile_Peak_3', desc: 'Start_Mobile_Peak_3', clust: CLUSTER_TIC, attr: 'startEJP3', type: TIME, unit: '', poll: true, tic: T.ANY, contract: C.EJP, elec: E.ANY, prod: false}, - {id: 104, name: 'End_Mobile_Peak_3', desc: 'End_Mobile_Peak_3', clust: CLUSTER_TIC, attr: 'stopEJP3', type: TIME, unit: '', poll: true, tic: T.ANY, contract: C.EJP, elec: E.ANY, prod: false}, + { + id: 0, + name: 'TIC_Mode', + desc: 'TIC_Communication_Mode', + clust: CLUSTER_TIC, + attr: 'ticMode', + type: ENUM, + unit: '', + poll: true, + tic: T.ANY, + contract: C.ANY, + elec: E.ANY, + prod: false, + values: modeTICEnum, + }, + { + id: 1, + name: 'Electric_Mode', + desc: 'Meter_Electric_Mode', + clust: CLUSTER_TIC, + attr: 'elecMode', + type: ENUM, + unit: '', + poll: true, + tic: T.ANY, + contract: C.ANY, + elec: E.ANY, + prod: false, + values: modeElecEnum, + }, + { + id: 2, + name: 'Tariff_Option', + desc: 'Tariff_Option', + clust: CLUSTER_TIC, + attr: 'contractType', + type: STRING, + unit: '', + poll: true, + tic: T.ANY, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 3, + name: 'Uptime', + desc: 'Duration_since_last_restart', + clust: CLUSTER_TIC, + attr: 'uptime', + type: NUMBER, + unit: 's', + poll: true, + tic: T.ANY, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 4, + name: 'Refresh_Rate', + desc: 'Time_between_refreshes', + clust: CLUSTER_TIC, + attr: 'refreshRate', + type: NUM_RW, + unit: 's', + poll: true, + tic: T.ANY, + contract: C.ANY, + elec: E.ANY, + prod: false, + min: 30, + max: 300, + }, + { + id: 5, + name: 'Identifier', + desc: 'Meter_serial_number', + clust: CLUSTER_MET, + attr: 'meterSerialNumber', + type: STRING, + unit: '', + poll: true, + tic: T.ANY, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 6, + name: 'Max_Contract_Power', + desc: 'Max_Contract_Power', + clust: CLUSTER_TIC, + attr: 'maxContractPower', + type: NUMBER, + unit: 'kVA', + poll: true, + tic: T.ANY, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 7, + name: 'Total_Index', + desc: 'Sum_of_all_Index', + clust: CLUSTER_MET, + attr: 'currentSummDelivered', + type: NUMBER, + unit: 'Wh', + poll: false, + tic: T.ANY, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 8, + name: 'BASE_Index', + desc: 'Base_Tariff_Index', + clust: CLUSTER_MET, + attr: 'currentTier1SummDelivered', + type: NUMBER, + unit: 'Wh', + poll: false, + tic: T.ANY, + contract: C.BASE, + elec: E.ANY, + prod: false, + }, + { + id: 9, + name: 'Off-Peak_Index', + desc: 'Off-Peak_Tariff_Index', + clust: CLUSTER_MET, + attr: 'currentTier1SummDelivered', + type: NUMBER, + unit: 'Wh', + poll: false, + tic: T.ANY, + contract: C.HCHP, + elec: E.ANY, + prod: false, + }, + { + id: 10, + name: 'Peak_Index', + desc: 'Peak_Tariff_Index', + clust: CLUSTER_MET, + attr: 'currentTier2SummDelivered', + type: NUMBER, + unit: 'Wh', + poll: false, + tic: T.ANY, + contract: C.HCHP, + elec: E.ANY, + prod: false, + }, + { + id: 11, + name: 'EJP_Normal_Hours_Index', + desc: 'EJP_Normal_Hours_Tariff_Index', + clust: CLUSTER_MET, + attr: 'currentTier1SummDelivered', + type: NUMBER, + unit: 'Wh', + poll: false, + tic: T.ANY, + contract: C.EJP, + elec: E.ANY, + prod: false, + }, + { + id: 12, + name: 'EJP_Mobile_Peak_Hours_Index', + desc: 'EJP_Mobile_Peak_Hours_Tariff_Index', + clust: CLUSTER_MET, + attr: 'currentTier2SummDelivered', + type: NUMBER, + unit: 'Wh', + poll: false, + tic: T.ANY, + contract: C.EJP, + elec: E.ANY, + prod: false, + }, + { + id: 13, + name: 'EJP_Notice', + desc: 'EJP_Notice', + clust: CLUSTER_TIC, + attr: 'startEJP', + type: STRING, + unit: '', + poll: false, + tic: T.ANY, + contract: C.EJP, + elec: E.ANY, + prod: false, + }, + { + id: 14, + name: 'BBRHCJB_Index', + desc: 'Blue_Days_Off-Peak_Tariff_Index', + clust: CLUSTER_MET, + attr: 'currentTier1SummDelivered', + type: NUMBER, + unit: 'Wh', + poll: false, + tic: T.ANY, + contract: C.TEMPO, + elec: E.ANY, + prod: false, + }, + { + id: 15, + name: 'BBRHPJB_Index', + desc: 'Blue_Days_Peak_Tariff_Index', + clust: CLUSTER_MET, + attr: 'currentTier2SummDelivered', + type: NUMBER, + unit: 'Wh', + poll: false, + tic: T.ANY, + contract: C.TEMPO, + elec: E.ANY, + prod: false, + }, + { + id: 16, + name: 'BBRHCJW_Index', + desc: 'White_Days_Off-Peak_Tariff_Index', + clust: CLUSTER_MET, + attr: 'currentTier3SummDelivered', + type: NUMBER, + unit: 'Wh', + poll: false, + tic: T.ANY, + contract: C.TEMPO, + elec: E.ANY, + prod: false, + }, + { + id: 17, + name: 'BBRHPJW_Index', + desc: 'White_Days_Peak_Tariff_Index', + clust: CLUSTER_MET, + attr: 'currentTier4SummDelivered', + type: NUMBER, + unit: 'Wh', + poll: false, + tic: T.ANY, + contract: C.TEMPO, + elec: E.ANY, + prod: false, + }, + { + id: 18, + name: 'BBRHCJR_Index', + desc: 'Red_Days_Off-Peak_Tariff_Index', + clust: CLUSTER_MET, + attr: 'currentTier5SummDelivered', + type: NUMBER, + unit: 'Wh', + poll: false, + tic: T.ANY, + contract: C.TEMPO, + elec: E.ANY, + prod: false, + }, + { + id: 19, + name: 'BBRHPJR_Index', + desc: 'Red_Days_Peak_Tariff_Index', + clust: CLUSTER_MET, + attr: 'currentTier6SummDelivered', + type: NUMBER, + unit: 'Wh', + poll: false, + tic: T.ANY, + contract: C.TEMPO, + elec: E.ANY, + prod: false, + }, + { + id: 20, + name: 'Index_7', + desc: 'Index_7', + clust: CLUSTER_MET, + attr: 'currentTier7SummDelivered', + type: NUMBER, + unit: 'Wh', + poll: false, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 21, + name: 'Index_8', + desc: 'Index_8', + clust: CLUSTER_MET, + attr: 'currentTier8SummDelivered', + type: NUMBER, + unit: 'Wh', + poll: false, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 22, + name: 'Index_9', + desc: 'Index_9', + clust: CLUSTER_MET, + attr: 'currentTier9SummDelivered', + type: NUMBER, + unit: 'Wh', + poll: false, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 23, + name: 'Index_10', + desc: 'Index_10', + clust: CLUSTER_MET, + attr: 'currentTier10SummDelivered', + type: NUMBER, + unit: 'Wh', + poll: false, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 24, + name: 'Current_Tariff', + desc: 'Current_Tariff_Option', + clust: CLUSTER_TIC, + attr: 'currentTarif', + type: STRING, + unit: '', + poll: false, + tic: T.ANY, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 25, + name: 'Tomorrow_Color', + desc: 'Tomorrow_Color', + clust: CLUSTER_TIC, + attr: 'tomorowColor', + type: STRING, + unit: '', + poll: false, + tic: T.ANY, + contract: C.TEMPO, + elec: E.ANY, + prod: false, + }, + { + id: 26, + name: 'Instant_Intensity', + desc: 'Instant_Intensity', + clust: CLUSTER_ELE, + attr: 'rmsCurrent', + type: NUMBER, + unit: 'A', + poll: false, + tic: T.ANY, + contract: C.ANY, + elec: E.MONO, + prod: false, + }, + { + id: 27, + name: 'Instant_Intensity_Phase_A', + desc: 'Instant_Intensity_Phase_A', + clust: CLUSTER_ELE, + attr: 'rmsCurrent', + type: NUMBER, + unit: 'A', + poll: false, + tic: T.ANY, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 28, + name: 'Instant_Intensity_Phase_B', + desc: 'Instant_Intensity_Phase_B', + clust: CLUSTER_ELE, + attr: 'rmsCurrentPhB', + type: NUMBER, + unit: 'A', + poll: false, + tic: T.ANY, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 29, + name: 'Instant_Intensity_Phase_C', + desc: 'Instant_Intensity_Phase_C', + clust: CLUSTER_ELE, + attr: 'rmsCurrentPhC', + type: NUMBER, + unit: 'A', + poll: false, + tic: T.ANY, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 30, + name: 'Max_Intensity', + desc: 'Max_Intensity', + clust: CLUSTER_ELE, + attr: 'rmsCurrentMax', + type: NUMBER, + unit: 'A', + poll: true, + tic: T.HIST, + contract: C.ANY, + elec: E.MONO, + prod: false, + }, + { + id: 31, + name: 'Max_Intensity_Phase_A', + desc: 'Max_Intensity_Phase_A', + clust: CLUSTER_ELE, + attr: 'rmsCurrentMax', + type: NUMBER, + unit: 'A', + poll: true, + tic: T.HIST, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 32, + name: 'Max_Intensity_Phase_B', + desc: 'Max_Intensity_Phase_B', + clust: CLUSTER_ELE, + attr: 'rmsCurrentMaxPhB', + type: NUMBER, + unit: 'A', + poll: true, + tic: T.HIST, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 33, + name: 'Max_Intensity_Phase_C', + desc: 'Max_Intensity_Phase_C', + clust: CLUSTER_ELE, + attr: 'rmsCurrentMaxPhC', + type: NUMBER, + unit: 'A', + poll: true, + tic: T.HIST, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 34, + name: 'Power_Exceedance', + desc: 'Power_Exceedance', + clust: CLUSTER_TIC, + attr: 'powerOverrun', + type: NUMBER, + unit: 'A', + poll: false, + tic: T.ANY, + contract: C.ANY, + elec: E.MONO, + prod: false, + }, + { + id: 35, + name: 'Power_Exceedance_Phase_A', + desc: 'Power_Exceedance_Phase_A', + clust: CLUSTER_TIC, + attr: 'powerOverrunA', + type: NUMBER, + unit: 'A', + poll: false, + tic: T.ANY, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 36, + name: 'Power_Exceedance_Phase_B', + desc: 'Power_Exceedance_Phase_B', + clust: CLUSTER_TIC, + attr: 'powerOverrunB', + type: NUMBER, + unit: 'A', + poll: false, + tic: T.ANY, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 37, + name: 'Power_Exceedance_Phase_C', + desc: 'Power_Exceedance_Phase_C', + clust: CLUSTER_TIC, + attr: 'powerOverrunC', + type: NUMBER, + unit: 'A', + poll: false, + tic: T.ANY, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 38, + name: 'Apparent_Power', + desc: 'Apparent_Power', + clust: CLUSTER_ELE, + attr: 'apparentPower', + type: NUMBER, + unit: 'VA', + poll: false, + tic: T.ANY, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 39, + name: 'Apparent_Power_Phase_A', + desc: 'Apparent_Power_Phase_A', + clust: CLUSTER_ELE, + attr: 'apparentPower', + type: NUMBER, + unit: 'VA', + poll: false, + tic: T.STD, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 40, + name: 'Apparent_Power_Phase_B', + desc: 'Apparent_Power_Phase_B', + clust: CLUSTER_ELE, + attr: 'apparentPowerPhB', + type: NUMBER, + unit: 'VA', + poll: false, + tic: T.STD, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 41, + name: 'Apparent_Power_Phase_C', + desc: 'Apparent_Power_Phase_C', + clust: CLUSTER_ELE, + attr: 'apparentPowerPhC', + type: NUMBER, + unit: 'VA', + poll: false, + tic: T.STD, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 42, + name: 'Injected_Energy_Index', + desc: 'Injected_Energy_Index', + clust: CLUSTER_MET, + attr: 'currentSummReceived', + type: NUMBER, + unit: 'Wh', + poll: false, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: true, + }, + { + id: 43, + name: 'Injected_Power', + desc: 'Injected_Power', + clust: CLUSTER_TIC, + attr: 'powerInjected', + type: NUMBER, + unit: 'VA', + poll: false, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: true, + }, + { + id: 44, + name: 'Today_Max_Injected_Power', + desc: 'Max_Injected_Power_Today', + clust: CLUSTER_TIC, + attr: 'powerMaxInjected', + type: NUMBER, + unit: 'VA', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: true, + }, + { + id: 45, + name: 'Today_Time_Max_Injected_Power', + desc: 'Date_and_Time_of_Today_Max_Injected', + clust: CLUSTER_TIC, + attr: 'powerMaxInjectedTime', + type: TIME, + unit: '', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: true, + }, + { + id: 46, + name: 'Yesterday_Max_Injected_Power', + desc: 'Max_Injected_Power_Yesterday', + clust: CLUSTER_TIC, + attr: 'powerMaxInjectedYesterday', + type: NUMBER, + unit: 'VA', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: true, + }, + { + id: 47, + name: 'Yesterday_Time_Max_Injected_Power', + desc: 'Date_and_Time_of_Yesterday_Max_Injected', + clust: CLUSTER_TIC, + attr: 'powerMaxInjectedYesterdayTime', + type: TIME, + unit: '', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: true, + }, + { + id: 48, + name: 'Potential_Presence', + desc: 'Potential_Presence', + clust: CLUSTER_TIC, + attr: 'potentialPresence', + type: NUMBER, + unit: '', + poll: true, + tic: T.HIST, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 49, + name: 'Off-Peak_Hours_Schedule', + desc: 'Off-Peak_Hours_Schedule', + clust: CLUSTER_TIC, + attr: 'hcHours', + type: STRING, + unit: '', + poll: false, + tic: T.ANY, + contract: C.HCHP, + elec: E.ANY, + prod: false, + }, + { + id: 50, + name: 'Status_Register', + desc: 'Meter_Status_Register', + clust: CLUSTER_TIC, + attr: 'motdetat', + type: STRING, + unit: '', + poll: true, + tic: T.ANY, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 51, + name: 'Distributor_Index_1', + desc: 'Distributor_Drawn_Energy_Index_1', + clust: CLUSTER_TIC, + attr: 'index1Dist', + type: NUMBER, + unit: 'Wh', + poll: false, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 52, + name: 'Distributor_Index_2', + desc: 'Distributor_Drawn_Energy_Index_2', + clust: CLUSTER_TIC, + attr: 'index2Dist', + type: NUMBER, + unit: 'Wh', + poll: false, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 53, + name: 'Distributor_Index_3', + desc: 'Distributor_Drawn_Energy_Index_3', + clust: CLUSTER_TIC, + attr: 'index3Dist', + type: NUMBER, + unit: 'Wh', + poll: false, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 54, + name: 'Distributor_Index_4', + desc: 'Distributor_Drawn_Energy_Index_4', + clust: CLUSTER_TIC, + attr: 'index4Dist', + type: NUMBER, + unit: 'Wh', + poll: false, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 55, + name: 'Instantaneous_Voltage', + desc: 'Instantaneous_Effective_Voltage', + clust: CLUSTER_ELE, + attr: 'rmsVoltage', + type: NUMBER, + unit: 'V', + poll: false, + tic: T.STD, + contract: C.ANY, + elec: E.MONO, + prod: false, + }, + { + id: 56, + name: 'Instantaneous_Voltage_Phase_A', + desc: 'Instantaneous_Effective_Voltage_Phase_A', + clust: CLUSTER_ELE, + attr: 'rmsVoltage', + type: NUMBER, + unit: 'V', + poll: false, + tic: T.STD, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 57, + name: 'Instantaneous_Voltage_Phase_B', + desc: 'Instantaneous_Effective_Voltage_Phase_B', + clust: CLUSTER_ELE, + attr: 'rmsVoltagePhB', + type: NUMBER, + unit: 'V', + poll: false, + tic: T.STD, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 58, + name: 'Instantaneous_Voltage_Phase_C', + desc: 'Instantaneous_Effective_Voltage_Phase_C', + clust: CLUSTER_ELE, + attr: 'rmsVoltagePhC', + type: NUMBER, + unit: 'V', + poll: false, + tic: T.STD, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 59, + name: 'Average_Voltage', + desc: 'Average_Voltage', + clust: CLUSTER_ELE, + attr: 'averageRmsVoltageMeasPeriod', + type: NUMBER, + unit: 'V', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.MONO, + prod: false, + }, + { + id: 60, + name: 'Average_Voltage_Phase_A', + desc: 'Average_Voltage_Phase_A', + clust: CLUSTER_ELE, + attr: 'averageRmsVoltageMeasPeriod', + type: NUMBER, + unit: 'V', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 61, + name: 'Average_Voltage_Phase_B', + desc: 'Average_Voltage_Phase_B', + clust: CLUSTER_ELE, + attr: 'averageRmsVoltageMeasurePeriodPhB', + type: NUMBER, + unit: 'V', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 62, + name: 'Average_Voltage_Phase_C', + desc: 'Average_Voltage_Phase_C', + clust: CLUSTER_ELE, + attr: 'averageRmsVoltageMeasPeriodPhC', + type: NUMBER, + unit: 'V', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 63, + name: 'Today_Max_Power', + desc: 'Max_Power_Today', + clust: CLUSTER_ELE, + attr: 'activePowerMax', + type: NUMBER, + unit: 'VA', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.MONO, + prod: false, + }, + { + id: 64, + name: 'Time_Today_Max_Power', + desc: 'Date_and_Time_of_Max_Power_Today', + clust: CLUSTER_TIC, + attr: 'powerMaxTodayTime', + type: TIME, + unit: '', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.MONO, + prod: false, + }, + { + id: 65, + name: 'Today_Max_Power_Phase_A', + desc: 'Max_Power_Today_Phase_A', + clust: CLUSTER_ELE, + attr: 'activePowerMax', + type: NUMBER, + unit: 'VA', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 66, + name: 'Time_Today_Max_Power_Phase_A', + desc: 'Date_and_Time_of_Today_Max Power_Phase_A', + clust: CLUSTER_TIC, + attr: 'powerMaxToday1Time', + type: TIME, + unit: '', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 67, + name: 'Today_Max_Power_Phase_B', + desc: 'Max_Power_Today_Phase_B', + clust: CLUSTER_ELE, + attr: 'activePowerMaxPhB', + type: NUMBER, + unit: 'VA', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 68, + name: 'Time_Today_Max_Power_Phase_B', + desc: 'Date_and_Time_of_Max_Power_Today_Phase_B', + clust: CLUSTER_TIC, + attr: 'powerMaxToday2Time', + type: TIME, + unit: '', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 69, + name: 'Today_Max_Power_Phase_C', + desc: 'Max_Power_Today_Phase_C', + clust: CLUSTER_ELE, + attr: 'activePowerMaxPhC', + type: NUMBER, + unit: 'VA', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 70, + name: 'Time_Today_Max_Power_Phase_C', + desc: 'Date_and_Time_of_Max_Power_Today_Phase_C', + clust: CLUSTER_TIC, + attr: 'powerMaxToday3Time', + type: TIME, + unit: '', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 71, + name: 'Three-Phase_Max_Power', + desc: 'Three-Phase_Max_Power', + clust: CLUSTER_ELE, + attr: 'activePowerMax', + type: NUMBER, + unit: 'W', + poll: true, + tic: T.HIST, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 72, + name: 'Yesterday_Max_Power', + desc: 'Max_Power_Yesterday', + clust: CLUSTER_TIC, + attr: 'powerMaxYesterday', + type: NUMBER, + unit: 'VA', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.MONO, + prod: false, + }, + { + id: 73, + name: 'Time_Yesterday_Max_Power', + desc: 'Date_and_Time_of_Max_Power_Yesterday', + clust: CLUSTER_TIC, + attr: 'powerMaxYesterdayTime', + type: TIME, + unit: '', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.MONO, + prod: false, + }, + { + id: 74, + name: 'Max_Yesterday_Power_Phase_A', + desc: 'Max_Power_Yesterday_Phase_A', + clust: CLUSTER_TIC, + attr: 'powerMaxYesterday1', + type: NUMBER, + unit: 'VA', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 75, + name: 'Time_Yesterday_Max_Power_Phase_A', + desc: 'DateTime_of_Max_Power_Yesterday_Phase_A', + clust: CLUSTER_TIC, + attr: 'powerMaxYesterday1Time', + type: TIME, + unit: '', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 76, + name: 'Max_Yesterday_Power_Phase_B', + desc: 'Max_Power_Yesterday_Phase_B', + clust: CLUSTER_TIC, + attr: 'powerMaxYesterday2', + type: NUMBER, + unit: 'VA', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 77, + name: 'Time_Yesterday_Max_Power_Phase_B', + desc: 'DateTime_of_Max_Power_Yesterday_Phase_B', + clust: CLUSTER_TIC, + attr: 'powerMaxYesterday2Time', + type: TIME, + unit: '', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 78, + name: 'Max_Yesterday_Power_Phase_C', + desc: 'Max_Power_Yesterday_Phase_C', + clust: CLUSTER_TIC, + attr: 'powerMaxYesterday3', + type: NUMBER, + unit: 'VA', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 79, + name: 'Time_Yesterday_Max_Power_Phase_C', + desc: 'DateTime_of_Max_Power_Yesterday_Phase_C', + clust: CLUSTER_TIC, + attr: 'powerMaxYesterday3Time', + type: TIME, + unit: '', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.TRI, + prod: false, + }, + { + id: 80, + name: 'Current_Index', + desc: 'Current_Tariff_Index_Number', + clust: CLUSTER_TIC, + attr: 'currentIndex', + type: NUMBER, + unit: '', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 81, + name: 'Current_Days_Number', + desc: 'Current_Supplier_Days_Number', + clust: CLUSTER_TIC, + attr: 'calendarSupplierDay', + type: NUMBER, + unit: '', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 82, + name: 'Next_Day_Number', + desc: 'Next_Supplier_Day_Number', + clust: CLUSTER_TIC, + attr: 'nextSupplierCalendarDay', + type: NUMBER, + unit: '', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 83, + name: 'Relay', + desc: 'Meter_Virtual_Relay', + clust: CLUSTER_TIC, + attr: 'relays', + type: STRING, + unit: '', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 84, + name: 'PMR', + desc: 'Measurement_Reference_Point_Identifier', + clust: CLUSTER_MET, + attr: 'siteId', + type: STRING, + unit: '', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 85, + name: 'Short_Message', + desc: 'Short_Message', + clust: CLUSTER_TIC, + attr: 'shortMsg', + type: STRING, + unit: '', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 86, + name: 'Ultra-Short_Message', + desc: 'Ultra-Short_Message', + clust: CLUSTER_TIC, + attr: 'ultraShortMsg', + type: STRING, + unit: '', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 87, + name: 'TIC_Version', + desc: 'TIC_Version', + clust: CLUSTER_TIC, + attr: 'ticVersion', + type: STRING, + unit: '', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 88, + name: 'Meter_Date_and_Time', + desc: 'Meter_Date_and_Time', + clust: CLUSTER_TIC, + attr: 'date', + type: TIME, + unit: '', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 89, + name: 'Next_Day_Profile', + desc: 'Next_Day_Profile', + clust: CLUSTER_TIC, + attr: 'calendarDay', + type: STRING, + unit: '', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 90, + name: 'Next_Day_Peak_Profile', + desc: 'Next_Day_Peak_Profile', + clust: CLUSTER_TIC, + attr: 'calendarDayPointe', + type: STRING, + unit: '', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 91, + name: 'Current_Drawn_Curve_Point', + desc: 'Current_Drawn_Active_Load_Curve_Point', + clust: CLUSTER_ELE, + attr: 'activePower', + type: NUMBER, + unit: 'VA', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 92, + name: 'Previous_Drawn_Curve_Point', + desc: 'Previous_Drawn_Active_Load_Curve_Point', + clust: CLUSTER_ELE, + attr: 'activePowerPhB', + type: NUMBER, + unit: 'VA', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 93, + name: 'Current_Injected_Curve_Point', + desc: 'Current_Injected_Active_Load_Curve_Point', + clust: CLUSTER_TIC, + attr: 'injectedLoadN', + type: NUMBER, + unit: 'VA', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: true, + }, + { + id: 94, + name: 'Previous_Injected_Curve_Point', + desc: 'Previous_Injected_Active_Load_Curve_Point', + clust: CLUSTER_TIC, + attr: 'injectedLoadN_1', + type: NUMBER, + unit: 'VA', + poll: true, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: true, + }, + { + id: 95, + name: 'Total_Reactive_Energy_Q1', + desc: 'Total_Reactive_Energy_Q1', + clust: CLUSTER_ELE, + attr: 'totalReactivePower', + type: NUMBER, + unit: 'VARh', + poll: false, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 96, + name: 'Total_Reactive_Energy_Q2', + desc: 'Total_Reactive_Energy_Q2', + clust: CLUSTER_ELE, + attr: 'reactivePower', + type: NUMBER, + unit: 'VARh', + poll: false, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 97, + name: 'Total_Reactive_Energy_Q3', + desc: 'Total_Reactive_Energy_Q3', + clust: CLUSTER_ELE, + attr: 'reactivePowerPhB', + type: NUMBER, + unit: 'VARh', + poll: false, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 98, + name: 'Total_Reactive_Energy_Q4', + desc: 'Total_Reactive_Energy_Q4', + clust: CLUSTER_ELE, + attr: 'reactivePowerPhC', + type: NUMBER, + unit: 'VARh', + poll: false, + tic: T.STD, + contract: C.ANY, + elec: E.ANY, + prod: false, + }, + { + id: 99, + name: 'Start_Mobile_Peak_1', + desc: 'Start_Mobile_Peak_1', + clust: CLUSTER_TIC, + attr: 'startEJP1', + type: TIME, + unit: '', + poll: true, + tic: T.ANY, + contract: C.EJP, + elec: E.ANY, + prod: false, + }, + { + id: 100, + name: 'End_Mobile_Peak_1', + desc: 'End_Mobile_Peak_1', + clust: CLUSTER_TIC, + attr: 'stopEJP1', + type: TIME, + unit: '', + poll: true, + tic: T.ANY, + contract: C.EJP, + elec: E.ANY, + prod: false, + }, + { + id: 101, + name: 'Start_Mobile_Peak_2', + desc: 'Start_Mobile_Peak_2', + clust: CLUSTER_TIC, + attr: 'startEJP2', + type: TIME, + unit: '', + poll: true, + tic: T.ANY, + contract: C.EJP, + elec: E.ANY, + prod: false, + }, + { + id: 102, + name: 'End_Mobile_Peak_2', + desc: 'End_Mobile_Peak_2', + clust: CLUSTER_TIC, + attr: 'stopEJP2', + type: TIME, + unit: '', + poll: true, + tic: T.ANY, + contract: C.EJP, + elec: E.ANY, + prod: false, + }, + { + id: 103, + name: 'Start_Mobile_Peak_3', + desc: 'Start_Mobile_Peak_3', + clust: CLUSTER_TIC, + attr: 'startEJP3', + type: TIME, + unit: '', + poll: true, + tic: T.ANY, + contract: C.EJP, + elec: E.ANY, + prod: false, + }, + { + id: 104, + name: 'End_Mobile_Peak_3', + desc: 'End_Mobile_Peak_3', + clust: CLUSTER_TIC, + attr: 'stopEJP3', + type: TIME, + unit: '', + poll: true, + tic: T.ANY, + contract: C.EJP, + elec: E.ANY, + prod: false, + }, ]; const ticmeterCustomCluster = { @@ -403,32 +1784,28 @@ const ticmeterCustomCluster = { commands: { refreshRate: { ID: 0, - parameters: [ - {name: 'refreshRate', type: Zcl.DataType.UINT16}, - ], + parameters: [{name: 'refreshRate', type: Zcl.DataType.UINT16}], }, reboot: { ID: 1, - parameters: [ - {name: 'seq', type: Zcl.DataType.UINT16}, - ], + parameters: [{name: 'seq', type: Zcl.DataType.UINT16}], }, }, commandsResponse: { refreshRate: { ID: 1, - parameters: [ - {name: 'seq', type: Zcl.DataType.UINT16}, - ], + parameters: [{name: 'seq', type: Zcl.DataType.UINT16}], }, }, }; function toSnakeCase(str: string) { - return str.split(/(?=[A-Z])/).join('_').toLowerCase(); + return str + .split(/(?=[A-Z])/) + .join('_') + .toLowerCase(); } - function ticmeterConverter(msg: Fz.Message) { const result: KeyValue = {}; const keys = Object.keys(msg.data); @@ -437,27 +1814,27 @@ function ticmeterConverter(msg: Fz.Message) { if (found) { let value; switch (found.type) { - case STRING: - if (Buffer.isBuffer(msg.data[key])) { - value = msg.data[key].toString(); - } else { - value = msg.data[key]; - } - break; - case NUMBER: - case NUM_RW: - if (Array.isArray(msg.data[key])) { - value = (msg.data[key][0] << 32) + msg.data[key][1]; - } else { - value = msg.data[key]; - } - break; - case ENUM: - value = found.values[msg.data[key]]; - break; - case TIME: - value = new Date(msg.data[key] * 1000).toLocaleString('fr-FR', {timeZone: 'Europe/Paris'}); - break; + case STRING: + if (Buffer.isBuffer(msg.data[key])) { + value = msg.data[key].toString(); + } else { + value = msg.data[key]; + } + break; + case NUMBER: + case NUM_RW: + if (Array.isArray(msg.data[key])) { + value = (msg.data[key][0] << 32) + msg.data[key][1]; + } else { + value = msg.data[key]; + } + break; + case ENUM: + value = found.values[msg.data[key]]; + break; + case TIME: + value = new Date(msg.data[key] * 1000).toLocaleString('fr-FR', {timeZone: 'Europe/Paris'}); + break; } if (found.attr == 'uptime') { @@ -502,7 +1879,7 @@ function genereateTzLocal() { const tzLocal = []; for (const item of ticmeterDatas) { const key = toSnakeCase(item.attr); - const tz : Tz.Converter = { + const tz: Tz.Converter = { key: [key], convertGet: async (entity, key, meta) => { await entity.read(item.clust, [item.attr]); @@ -541,17 +1918,19 @@ async function poll(endpoint: Zh.Endpoint, device: Device) { let toRead = []; for (const item of ticmeterDatas) { - if (item.poll && + if ( + item.poll && (item.tic == currentTIC || item.tic == T.ANY) && (item.contract == currentContract || item.contract == C.ANY) && (item.elec == currentElec || item.elec == E.ANY) && - (item.prod == currentProducer || item.prod == false)) { + (item.prod == currentProducer || item.prod == false) + ) { toRead.push(item); } } toRead = toRead.sort((a, b) => a.clust.localeCompare(b.clust)); - const groupedByCluster: { [key: string]: TICMeterData[] } = {}; + const groupedByCluster: {[key: string]: TICMeterData[]} = {}; toRead.forEach((item) => { if (!groupedByCluster[item.clust]) { groupedByCluster[item.clust] = []; @@ -612,7 +1991,7 @@ const definitions: Definition[] = [ let currentContract: string = ''; let currentElec: string = ''; let currentTIC: string = ''; - let currentProducer:string = ''; + let currentProducer: string = ''; let translation: string = ''; if (device == null) { @@ -625,7 +2004,6 @@ const definitions: Definition[] = [ logger.warning('Exposes: No endpoint', 'TICMeter'); } - if (endpoint != null && endpoint.hasOwnProperty('clusters') && endpoint.clusters[CLUSTER_TIC] != undefined) { if (endpoint.clusters[CLUSTER_TIC].hasOwnProperty('attributes') && endpoint.clusters[CLUSTER_TIC].attributes != undefined) { const attr = endpoint.clusters[CLUSTER_TIC].attributes; @@ -721,7 +2099,6 @@ const definitions: Definition[] = [ globalStore.putValue(device, 'producer', currentProducer); globalStore.putValue(device, 'translation', translation); - ticmeterDatas.forEach((item) => { let contractOK = false; let elecOK = false; @@ -771,21 +2148,29 @@ const definitions: Definition[] = [ desc = ticmeterDatasFRTranslation[item.id].descFR; } switch (item.type) { - case STRING: - exposes.push(e.text(name, access).withProperty(toSnakeCase(item.attr)).withDescription(desc)); - break; - case NUMBER: - exposes.push(e.numeric(name, access).withProperty(toSnakeCase(item.attr)).withDescription(desc).withUnit(item.unit)); - break; - case ENUM: - exposes.push(e.enum(name, access, item.values).withProperty(toSnakeCase(item.attr)).withDescription(desc)); - break; - case TIME: - exposes.push(e.text(name, access).withProperty(toSnakeCase(item.attr)).withDescription(desc)); - break; - case NUM_RW: - exposes.push(e.numeric(name, ea.ALL).withProperty(toSnakeCase(item.attr)).withDescription(desc).withUnit(item.unit).withValueMin(item.min).withValueMax(item.max)); - break; + case STRING: + exposes.push(e.text(name, access).withProperty(toSnakeCase(item.attr)).withDescription(desc)); + break; + case NUMBER: + exposes.push(e.numeric(name, access).withProperty(toSnakeCase(item.attr)).withDescription(desc).withUnit(item.unit)); + break; + case ENUM: + exposes.push(e.enum(name, access, item.values).withProperty(toSnakeCase(item.attr)).withDescription(desc)); + break; + case TIME: + exposes.push(e.text(name, access).withProperty(toSnakeCase(item.attr)).withDescription(desc)); + break; + case NUM_RW: + exposes.push( + e + .numeric(name, ea.ALL) + .withProperty(toSnakeCase(item.attr)) + .withDescription(desc) + .withUnit(item.unit) + .withValueMin(item.min) + .withValueMax(item.max), + ); + break; } } }); @@ -793,18 +2178,18 @@ const definitions: Definition[] = [ if (options.hasOwnProperty('translation')) { switch (options.translation) { - case TRANSLATION_FR: - for (let i = 0; i < ticmeterOptions.length; i++) { - ticmeterOptions[i].description = ticmeterOptionsFRTr[i].descFR; - } - break; - case TRANSLATION_EN: - for (let i = 0; i < ticmeterOptions.length; i++) { - ticmeterOptions[i].description = ticmeterOptionsFRTr[i].descEN; - } - break; - default: - logger.warning(`Unknown translation: ${options.translation}`, 'TICMeter'); + case TRANSLATION_FR: + for (let i = 0; i < ticmeterOptions.length; i++) { + ticmeterOptions[i].description = ticmeterOptionsFRTr[i].descFR; + } + break; + case TRANSLATION_EN: + for (let i = 0; i < ticmeterOptions.length; i++) { + ticmeterOptions[i].description = ticmeterOptionsFRTr[i].descEN; + } + break; + default: + logger.warning(`Unknown translation: ${options.translation}`, 'TICMeter'); } } return exposes; @@ -825,21 +2210,19 @@ const definitions: Definition[] = [ endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', {acCurrentDivisor: 1, acCurrentMultiplier: 1}); endpoint.saveClusterAttributeKeyValue('seMetering', {divisor: 1, multiplier: 1}); - await reporting.bind(endpoint, coordinatorEndpoint, [ - CLUSTER_TIC, - CLUSTER_ELE, - CLUSTER_MET, - METER_ID_CLUSTER, - ]); + await reporting.bind(endpoint, coordinatorEndpoint, [CLUSTER_TIC, CLUSTER_ELE, CLUSTER_MET, METER_ID_CLUSTER]); - const reportingConfig: Promise []= []; + const reportingConfig: Promise[] = []; const wanted: TICMeterData[] = []; for (const item of ticmeterDatas) { - if (!item.poll && (item.tic == TICMode || item.tic == T.ANY) && - (item.contract == contractType || item.contract == C.ANY) && - (item.elec == elecMode || item.elec == E.ANY) && - (item.prod == producer || item.prod == false)) { + if ( + !item.poll && + (item.tic == TICMode || item.tic == T.ANY) && + (item.contract == contractType || item.contract == C.ANY) && + (item.elec == elecMode || item.elec == E.ANY) && + (item.prod == producer || item.prod == false) + ) { wanted.push(item); } } @@ -847,7 +2230,11 @@ const definitions: Definition[] = [ logger.debug(`Configure wanted ${wanted.length}`, 'TICMeter'); endpoint.configuredReportings.forEach(async (r) => { - await endpoint.configureReporting(r.cluster.name, reporting.payload(r.attribute.name, r.minimumReportInterval, 65535, r.reportableChange), {manufacturerCode: null}); + await endpoint.configureReporting( + r.cluster.name, + reporting.payload(r.attribute.name, r.minimumReportInterval, 65535, r.reportableChange), + {manufacturerCode: null}, + ); }); for (const item of wanted) { @@ -859,20 +2246,24 @@ const definitions: Definition[] = [ }; logger.debug(`Configure ${item.name} ${item.clust} ${item.attr} ${conf.min} ${conf.max} ${conf.change}`, 'TICMeter'); - reportingConfig.push(endpoint.configureReporting(item.clust, reporting.payload(item.attr, conf.min, conf.max, conf.change), {manufacturerCode: null})); + reportingConfig.push( + endpoint.configureReporting(item.clust, reporting.payload(item.attr, conf.min, conf.max, conf.change), {manufacturerCode: null}), + ); } - await Promise.allSettled(reportingConfig.map(async (config) => { - try { - await config; - } catch (error) { - if (error.message.includes('UNSUPPORTED_ATTRIBUTE')) { - // ignore: sometimes the attribute is not supported - } else { - logger.warning(`Configure failed: ${error}`, 'TICMeter'); + await Promise.allSettled( + reportingConfig.map(async (config) => { + try { + await config; + } catch (error) { + if (error.message.includes('UNSUPPORTED_ATTRIBUTE')) { + // ignore: sometimes the attribute is not supported + } else { + logger.warning(`Configure failed: ${error}`, 'TICMeter'); + } } - } - })); + }), + ); }, options: ticmeterOptions, onEvent: async (type, data, device, options) => { diff --git a/src/devices/gmy.ts b/src/devices/gmy.ts index ca98295612765..1f54fa5ba0495 100644 --- a/src/devices/gmy.ts +++ b/src/devices/gmy.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/gs.ts b/src/devices/gs.ts index 1fa4ba1343392..5a8a25f03bb65 100644 --- a/src/devices/gs.ts +++ b/src/devices/gs.ts @@ -1,8 +1,16 @@ -import {Definition} from '../lib/types'; import { - light, onOff, electricityMeter, iasZoneAlarm, - temperature, humidity, battery, ignoreClusterReport, iasWarning, identify, + light, + onOff, + electricityMeter, + iasZoneAlarm, + temperature, + humidity, + battery, + ignoreClusterReport, + iasWarning, + identify, } from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { @@ -10,20 +18,14 @@ const definitions: Definition[] = [ model: 'BRHM8E27W70-I1', vendor: 'GS', description: 'Smart color light bulb', - extend: [ - light({colorTemp: {range: undefined}, color: true}), - identify(), - ], + extend: [light({colorTemp: {range: undefined}, color: true}), identify()], }, { zigbeeModel: ['BDHM8E27W70-I1'], model: 'BDHM8E27W70-I1', vendor: 'GS', description: 'Smart light bulb', - extend: [ - light({colorTemp: {range: [153, 370]}}), - identify(), - ], + extend: [light({colorTemp: {range: [153, 370]}}), identify()], }, { zigbeeModel: ['SGMHM-I1'], @@ -44,11 +46,7 @@ const definitions: Definition[] = [ model: 'SKHMP30-I1', vendor: 'GS', description: 'Smart socket', - extend: [ - onOff({powerOnBehavior: false}), - electricityMeter(), - identify(), - ], + extend: [onOff({powerOnBehavior: false}), electricityMeter(), identify()], }, { zigbeeModel: ['SMHM-I1'], @@ -65,10 +63,7 @@ const definitions: Definition[] = [ model: 'SOHM-I1', vendor: 'GS', description: 'Open and close sensor', - extend: [ - iasZoneAlarm({zoneType: 'contact', zoneAttributes: ['alarm_1', 'tamper', 'battery_low']}), - battery({voltage: true}), - ], + extend: [iasZoneAlarm({zoneType: 'contact', zoneAttributes: ['alarm_1', 'tamper', 'battery_low']}), battery({voltage: true})], }, { zigbeeModel: ['SRHMP-I1'], @@ -76,42 +71,28 @@ const definitions: Definition[] = [ vendor: 'GS', description: 'Siren', meta: {disableDefaultResponse: true}, - extend: [ - ignoreClusterReport({cluster: 'genBasic'}), - iasWarning(), - battery(), - ], + extend: [ignoreClusterReport({cluster: 'genBasic'}), iasWarning(), battery()], }, { zigbeeModel: ['SSHM-I1'], model: 'SSHM-I1', vendor: 'GS', description: 'Smoke detector', - extend: [ - iasZoneAlarm({zoneType: 'smoke', zoneAttributes: ['alarm_1', 'tamper', 'battery_low']}), - battery(), - ], + extend: [iasZoneAlarm({zoneType: 'smoke', zoneAttributes: ['alarm_1', 'tamper', 'battery_low']}), battery()], }, { zigbeeModel: ['STHM-I1H'], model: 'STHM-I1H', vendor: 'GS', description: 'Temperature and humidity sensor', - extend: [ - temperature(), - humidity(), - battery({voltageToPercentage: '3V_2500', voltage: true}), - ], + extend: [temperature(), humidity(), battery({voltageToPercentage: '3V_2500', voltage: true})], }, { zigbeeModel: ['SWHM-I1'], model: 'SWHM-I1', vendor: 'GS', description: 'Water leakage sensor', - extend: [ - iasZoneAlarm({zoneType: 'water_leak', zoneAttributes: ['alarm_1', 'tamper', 'battery_low']}), - battery({voltage: true}), - ], + extend: [iasZoneAlarm({zoneType: 'water_leak', zoneAttributes: ['alarm_1', 'tamper', 'battery_low']}), battery({voltage: true})], }, ]; diff --git a/src/devices/halemeier.ts b/src/devices/halemeier.ts index 13d4cec249656..ae0238232c860 100644 --- a/src/devices/halemeier.ts +++ b/src/devices/halemeier.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; import * as exposes from '../lib/exposes'; -import * as reporting from '../lib/reporting'; import {light, battery, identify} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -37,8 +37,18 @@ const definitions: Definition[] = [ description: 'S-Mitter MultiWhite2 smart remote control', fromZigbee: [fz.battery, fz.command_step, fz.command_step_color_temperature, fz.command_recall, fz.command_off, fz.command_on], toZigbee: [tz.battery_percentage_remaining], - exposes: [e.action_group(), e.battery().withAccess(ea.STATE_GET), e.action(['recall_*', 'on', 'off', - 'color_temperature_step_up', 'color_temperature_step_down', 'brightness_step_up', 'brightness_step_down']), + exposes: [ + e.action_group(), + e.battery().withAccess(ea.STATE_GET), + e.action([ + 'recall_*', + 'on', + 'off', + 'color_temperature_step_up', + 'color_temperature_step_down', + 'brightness_step_up', + 'brightness_step_down', + ]), ], }, { @@ -48,8 +58,21 @@ const definitions: Definition[] = [ description: 'S-Mitter basic MultiWhite² 1-channel sender Zigbee ', fromZigbee: [fz.command_recall, fz.command_off, fz.command_on, fz.command_step_color_temperature, fz.command_step, fz.battery], toZigbee: [tz.battery_percentage_remaining], - exposes: [e.battery().withAccess(ea.STATE_GET), e.action(['on', 'off', 'recall_1', 'recall_2', 'recall_3', 'recall_4', - 'color_temperature_step_up', 'color_temperature_step_down', 'brightness_step_up', 'brightness_step_down'])], + exposes: [ + e.battery().withAccess(ea.STATE_GET), + e.action([ + 'on', + 'off', + 'recall_1', + 'recall_2', + 'recall_3', + 'recall_4', + 'color_temperature_step_up', + 'color_temperature_step_down', + 'brightness_step_up', + 'brightness_step_down', + ]), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']); diff --git a/src/devices/hampton_bay.ts b/src/devices/hampton_bay.ts index 5ab18ee5951d8..857a8e6b64703 100644 --- a/src/devices/hampton_bay.ts +++ b/src/devices/hampton_bay.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; import {forcePowerSource, light} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; diff --git a/src/devices/heatit.ts b/src/devices/heatit.ts index b3078897605de..0bf3f2a689396 100644 --- a/src/devices/heatit.ts +++ b/src/devices/heatit.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/heiman.ts b/src/devices/heiman.ts index 125b507802c0d..db29be2ca2d60 100644 --- a/src/devices/heiman.ts +++ b/src/devices/heiman.ts @@ -1,14 +1,14 @@ -import {Definition, Zh, Reporting} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; import * as constants from '../lib/constants'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; import * as reporting from '../lib/reporting'; +import {Definition, Zh, Reporting} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; -import * as tuya from '../lib/tuya'; import {light, battery, iasZoneAlarm} from '../lib/modernExtend'; +import * as tuya from '../lib/tuya'; const definitions: Definition[] = [ { @@ -19,7 +19,7 @@ const definitions: Definition[] = [ fromZigbee: [fz.occupancy, fz.battery, fz.illuminance], toZigbee: [], exposes: [e.occupancy(), e.battery(), e.illuminance()], - configure: async (device, cordinatorEndpoint)=>{ + configure: async (device, cordinatorEndpoint) => { const endpoint1 = device.getEndpoint(1); await reporting.bind(endpoint1, cordinatorEndpoint, ['msOccupancySensing', 'genPowerCfg']); await reporting.batteryPercentageRemaining(endpoint1); @@ -92,9 +92,19 @@ const definitions: Definition[] = [ exposes: [e.switch(), e.power(), e.current(), e.voltage()], }, { - zigbeeModel: ['SMOK_V16', 'SMOK_V15', 'b5db59bfd81e4f1f95dc57fdbba17931', '98293058552c49f38ad0748541ee96ba', 'SMOK_YDLV10', - 'FB56-SMF02HM1.4', 'SmokeSensor-N-3.0', '319fa36e7384414a9ea62cba8f6e7626', 'c3442b4ac59b4ba1a83119d938f283ab', - 'SmokeSensor-EF-3.0', 'SMOK_HV14'], + zigbeeModel: [ + 'SMOK_V16', + 'SMOK_V15', + 'b5db59bfd81e4f1f95dc57fdbba17931', + '98293058552c49f38ad0748541ee96ba', + 'SMOK_YDLV10', + 'FB56-SMF02HM1.4', + 'SmokeSensor-N-3.0', + '319fa36e7384414a9ea62cba8f6e7626', + 'c3442b4ac59b4ba1a83119d938f283ab', + 'SmokeSensor-EF-3.0', + 'SMOK_HV14', + ], model: 'HS1SA', vendor: 'HEIMAN', description: 'Smoke detector', @@ -278,8 +288,10 @@ const definitions: Definition[] = [ exposes: [e.carbon_monoxide(), e.battery_low(), e.battery()], }, { - fingerprint: [{modelID: 'TS0216', manufacturerName: '_TYZB01_8scntis1'}, - {modelID: 'TS0216', manufacturerName: '_TYZB01_4obovpbi'}], + fingerprint: [ + {modelID: 'TS0216', manufacturerName: '_TYZB01_8scntis1'}, + {modelID: 'TS0216', manufacturerName: '_TYZB01_4obovpbi'}, + ], zigbeeModel: ['WarningDevice', 'WarningDevice-EF-3.0'], model: 'HS2WD-E', vendor: 'HEIMAN', @@ -354,7 +366,10 @@ const definitions: Definition[] = [ exposes: [e.switch(), e.power(), e.current(), e.voltage()], }, { - fingerprint: [{modelID: 'SOS-EM', manufacturerName: 'HEIMAN'}, {modelID: 'SOS-EF-3.0', manufacturerName: 'HEIMAN'}], + fingerprint: [ + {modelID: 'SOS-EM', manufacturerName: 'HEIMAN'}, + {modelID: 'SOS-EF-3.0', manufacturerName: 'HEIMAN'}, + ], model: 'HS1EB/HS1EB-E', vendor: 'HEIMAN', description: 'Smart emergency button', @@ -387,8 +402,15 @@ const definitions: Definition[] = [ model: 'HS2WDSC-E', vendor: 'HEIMAN', description: 'Remote dimmer and temperature control', - fromZigbee: [fz.battery, fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.command_move_to_color, - fz.command_move_to_color_temp], + fromZigbee: [ + fz.battery, + fz.command_on, + fz.command_off, + fz.command_move, + fz.command_stop, + fz.command_move_to_color, + fz.command_move_to_color_temp, + ], exposes: [e.battery(), e.action(['on', 'off', 'move', 'stop', 'color_move', 'color_temperature_move'])], toZigbee: [], configure: async (device, coordinatorEndpoint) => { @@ -465,7 +487,10 @@ const definitions: Definition[] = [ exposes: [e.vibration(), e.battery_low(), e.tamper(), e.battery()], }, { - fingerprint: [{modelID: 'Vibration-EF_3.0', manufacturerName: 'HEIMAN'}, {modelID: 'Vibration-EF-3.0', manufacturerName: 'HEIMAN'}], + fingerprint: [ + {modelID: 'Vibration-EF_3.0', manufacturerName: 'HEIMAN'}, + {modelID: 'Vibration-EF-3.0', manufacturerName: 'HEIMAN'}, + ], model: 'HS1VS-EF', vendor: 'HEIMAN', description: 'Vibration sensor', @@ -518,8 +543,14 @@ const definitions: Definition[] = [ const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, [ - 'genPowerCfg', 'genTime', 'msTemperatureMeasurement', 'msRelativeHumidity', 'pm25Measurement', - 'heimanSpecificFormaldehydeMeasurement', 'heimanSpecificAirQuality']); + 'genPowerCfg', + 'genTime', + 'msTemperatureMeasurement', + 'msRelativeHumidity', + 'pm25Measurement', + 'heimanSpecificFormaldehydeMeasurement', + 'heimanSpecificAirQuality', + ]); await reporting.batteryPercentageRemaining(endpoint); await reporting.temperature(endpoint); @@ -536,13 +567,22 @@ const definitions: Definition[] = [ // Seems that it is bug in HEIMAN, device does not asks for the time with binding // So, we need to write time during configure - const time = Math.round(((new Date()).getTime() - constants.OneJanuary2000) / 1000); + const time = Math.round((new Date().getTime() - constants.OneJanuary2000) / 1000); // Time-master + synchronised - const values = {timeStatus: 3, time: time, timeZone: ((new Date()).getTimezoneOffset() * -1) * 60}; + const values = {timeStatus: 3, time: time, timeZone: new Date().getTimezoneOffset() * -1 * 60}; await endpoint.write('genTime', values); }, - exposes: [e.battery(), e.temperature(), e.humidity(), e.pm25(), e.hcho(), e.voc(), e.aqi(), e.pm10(), - e.enum('battery_state', ea.STATE, ['not_charging', 'charging', 'charged'])], + exposes: [ + e.battery(), + e.temperature(), + e.humidity(), + e.pm25(), + e.hcho(), + e.voc(), + e.aqi(), + e.pm10(), + e.enum('battery_state', ea.STATE, ['not_charging', 'charging', 'charged']), + ], }, { fingerprint: [{modelID: 'IRControl-EM', manufacturerName: 'HEIMAN'}], @@ -620,8 +660,7 @@ const definitions: Definition[] = [ await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ['genOnOff']); await reporting.deviceTemperature(device.getEndpoint(1)); }, - exposes: [e.switch().withEndpoint('left'), e.switch().withEndpoint('center'), e.switch().withEndpoint('right'), - e.device_temperature()], + exposes: [e.switch().withEndpoint('left'), e.switch().withEndpoint('center'), e.switch().withEndpoint('right'), e.device_temperature()], }, { zigbeeModel: ['TemperLight'], diff --git a/src/devices/heimgard_technologies.ts b/src/devices/heimgard_technologies.ts index 3abe37760bf0c..c0c3b11b11be0 100644 --- a/src/devices/heimgard_technologies.ts +++ b/src/devices/heimgard_technologies.ts @@ -1,11 +1,11 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; -import * as ota from '../lib/ota'; import {battery} from '../lib/modernExtend'; +import * as ota from '../lib/ota'; const definitions: Definition[] = [ { @@ -13,8 +13,14 @@ const definitions: Definition[] = [ model: 'HC-SLM-1', vendor: 'Heimgard Technologies', description: 'Wattle door lock pro', - fromZigbee: [fz.battery, fz.lock_operation_event, fz.lock_programming_event, fz.lock, fz.lock_pin_code_response, - fz.lock_user_status_response], + fromZigbee: [ + fz.battery, + fz.lock_operation_event, + fz.lock_programming_event, + fz.lock, + fz.lock_pin_code_response, + fz.lock_user_status_response, + ], toZigbee: [tz.identify, tz.lock, tz.lock_sound_volume, tz.lock_auto_relock_time, tz.pincode_lock, tz.lock_userstatus], meta: {pinCodeCount: 39}, ota: ota.zigbeeOTA, @@ -26,8 +32,13 @@ const definitions: Definition[] = [ await endpoint.read('closuresDoorLock', ['lockState', 'soundVolume']); }, exposes: [ - e.lock(), e.battery(), e.sound_volume(), e.auto_relock_time().withValueMin(0).withValueMax(3600), - e.lock_action_user(), e.lock_action_source_name(), e.pincode(), + e.lock(), + e.battery(), + e.sound_volume(), + e.auto_relock_time().withValueMin(0).withValueMax(3600), + e.lock_action_user(), + e.lock_action_source_name(), + e.pincode(), ], }, { diff --git a/src/devices/hej.ts b/src/devices/hej.ts index 2aecc1d745c07..5d26f97afe9cd 100644 --- a/src/devices/hej.ts +++ b/src/devices/hej.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {deviceEndpoints, onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { @@ -15,7 +15,7 @@ const definitions: Definition[] = [ vendor: 'Hej', description: 'Goqual 2 gang Switch', extend: [ - deviceEndpoints({endpoints: {'top': 1, 'bottom': 2}}), + deviceEndpoints({endpoints: {top: 1, bottom: 2}}), onOff({configureReporting: false, endpointNames: ['top', 'bottom'], powerOnBehavior: false}), ], }, @@ -25,7 +25,7 @@ const definitions: Definition[] = [ vendor: 'Hej', description: 'Goqual 3 gang Switch', extend: [ - deviceEndpoints({endpoints: {'top': 1, 'center': 2, 'bottom': 3}}), + deviceEndpoints({endpoints: {top: 1, center: 2, bottom: 3}}), onOff({configureReporting: false, endpointNames: ['top', 'center', 'bottom'], powerOnBehavior: false}), ], }, @@ -35,7 +35,7 @@ const definitions: Definition[] = [ vendor: 'Hej', description: 'Goqual 4 gang Switch', extend: [ - deviceEndpoints({endpoints: {'top_left': 1, 'bottom_left': 2, 'top_right': 3, 'bottom_right': 4}}), + deviceEndpoints({endpoints: {top_left: 1, bottom_left: 2, top_right: 3, bottom_right: 4}}), onOff({configureReporting: false, endpointNames: ['top_left', 'bottom_left', 'top_right', 'bottom_right'], powerOnBehavior: false}), ], }, @@ -45,7 +45,7 @@ const definitions: Definition[] = [ vendor: 'Hej', description: 'Goqual 5 gang Switch', extend: [ - deviceEndpoints({endpoints: {'top_left': 1, 'center_left': 2, 'bottom_left': 3, 'top_right': 4, 'bottom_right': 5}}), + deviceEndpoints({endpoints: {top_left: 1, center_left: 2, bottom_left: 3, top_right: 4, bottom_right: 5}}), onOff({ configureReporting: false, endpointNames: ['top_left', 'center_left', 'bottom_left', 'top_right', 'bottom_right'], @@ -59,7 +59,7 @@ const definitions: Definition[] = [ vendor: 'Hej', description: 'Goqual 6 gang Switch', extend: [ - deviceEndpoints({endpoints: {'top_left': 1, 'center_left': 2, 'bottom_left': 3, 'top_right': 4, 'center_right': 5, 'bottom_right': 6}}), + deviceEndpoints({endpoints: {top_left: 1, center_left: 2, bottom_left: 3, top_right: 4, center_right: 5, bottom_right: 6}}), onOff({ configureReporting: false, endpointNames: ['top_left', 'center_left', 'bottom_left', 'top_right', 'center_right', 'bottom_right'], diff --git a/src/devices/hfh.ts b/src/devices/hfh.ts index 42957010a5926..75a01e35e75f8 100644 --- a/src/devices/hfh.ts +++ b/src/devices/hfh.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/hilux.ts b/src/devices/hilux.ts index 860dfbfc658a5..c1ea56bde6dce 100644 --- a/src/devices/hilux.ts +++ b/src/devices/hilux.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/hive.ts b/src/devices/hive.ts index 5f540e965e260..75533eaf09be7 100644 --- a/src/devices/hive.ts +++ b/src/devices/hive.ts @@ -1,10 +1,10 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as globalStore from '../lib/store'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; import {light} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import * as globalStore from '../lib/store'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -22,8 +22,14 @@ const definitions: Definition[] = [ model: 'MOT003', vendor: 'Hive', description: 'Motion sensor', - fromZigbee: [fz.temperature, fz.ias_occupancy_alarm_1_with_timeout, fz.battery, fz.ignore_basic_report, - fz.ignore_iaszone_statuschange, fz.ignore_iaszone_attreport], + fromZigbee: [ + fz.temperature, + fz.ias_occupancy_alarm_1_with_timeout, + fz.battery, + fz.ignore_basic_report, + fz.ignore_iaszone_statuschange, + fz.ignore_iaszone_attreport, + ], toZigbee: [], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(6); @@ -131,15 +137,28 @@ const definitions: Definition[] = [ vendor: 'Hive', description: 'Alarm security keypad', meta: {battery: {voltageToPercentage: '3V_2100'}}, - fromZigbee: [fz.command_arm_with_transaction, fz.command_panic, fz.battery, fz.ias_occupancy_alarm_1, fz.identify, - fz.ias_contact_alarm_1, fz.ias_ace_occupancy_with_timeout], + fromZigbee: [ + fz.command_arm_with_transaction, + fz.command_panic, + fz.battery, + fz.ias_occupancy_alarm_1, + fz.identify, + fz.ias_contact_alarm_1, + fz.ias_ace_occupancy_with_timeout, + ], toZigbee: [tz.arm_mode], - exposes: [e.battery(), e.battery_voltage(), e.battery_low(), e.occupancy(), e.tamper(), e.contact(), + exposes: [ + e.battery(), + e.battery_voltage(), + e.battery_low(), + e.occupancy(), + e.tamper(), + e.contact(), e.numeric('action_code', ea.STATE).withDescription('Pin code introduced.'), e.numeric('action_transaction', ea.STATE).withDescription('Last action transaction number.'), e.text('action_zone', ea.STATE).withDescription('Alarm zone. Default value 23'), - e.action([ - 'panic', 'disarm', 'arm_day_zones', 'arm_all_zones', 'exit_delay', 'entry_delay'])], + e.action(['panic', 'disarm', 'arm_day_zones', 'arm_all_zones', 'exit_delay', 'entry_delay']), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const clusters = ['genPowerCfg', 'ssIasZone', 'ssIasAce', 'genIdentify']; @@ -150,11 +169,11 @@ const definitions: Definition[] = [ if (data.type === 'commandGetPanelStatus' && data.cluster === 'ssIasAce') { const payload = { panelstatus: globalStore.getValue(data.endpoint, 'panelStatus'), - secondsremain: 0x00, audiblenotif: 0x00, alarmstatus: 0x00, + secondsremain: 0x00, + audiblenotif: 0x00, + alarmstatus: 0x00, }; - await data.endpoint.commandResponse( - 'ssIasAce', 'getPanelStatusRsp', payload, {}, data.meta.zclTransactionSequenceNumber, - ); + await data.endpoint.commandResponse('ssIasAce', 'getPanelStatusRsp', payload, {}, data.meta.zclTransactionSequenceNumber); } }, }, @@ -164,18 +183,39 @@ const definitions: Definition[] = [ vendor: 'Hive', description: 'Heating thermostat', fromZigbee: [fz.thermostat, fz.thermostat_weekly_schedule], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_system_mode, tz.thermostat_running_state, - tz.thermostat_occupied_heating_setpoint, tz.thermostat_control_sequence_of_operation, tz.thermostat_weekly_schedule, - tz.thermostat_clear_weekly_schedule, tz.thermostat_temperature_setpoint_hold, tz.thermostat_temperature_setpoint_hold_duration], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_system_mode, + tz.thermostat_running_state, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_control_sequence_of_operation, + tz.thermostat_weekly_schedule, + tz.thermostat_clear_weekly_schedule, + tz.thermostat_temperature_setpoint_hold, + tz.thermostat_temperature_setpoint_hold_duration, + ], exposes: [ - e.climate().withSetpoint('occupied_heating_setpoint', 5, 32, 0.5).withLocalTemperature() - .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat']), - e.binary('temperature_setpoint_hold', ea.ALL, true, false) - .withDescription('Prevent changes. `false` = run normally. `true` = prevent from making changes.' + - ' Must be set to `false` when system_mode = off or `true` for heat'), - e.numeric('temperature_setpoint_hold_duration', ea.ALL).withValueMin(0).withValueMax(65535) - .withDescription('Period in minutes for which the setpoint hold will be active. 65535 = attribute not' + - ' used. 0 to 360 to match the remote display')], + e + .climate() + .withSetpoint('occupied_heating_setpoint', 5, 32, 0.5) + .withLocalTemperature() + .withSystemMode(['off', 'auto', 'heat']) + .withRunningState(['idle', 'heat']), + e + .binary('temperature_setpoint_hold', ea.ALL, true, false) + .withDescription( + 'Prevent changes. `false` = run normally. `true` = prevent from making changes.' + + ' Must be set to `false` when system_mode = off or `true` for heat', + ), + e + .numeric('temperature_setpoint_hold_duration', ea.ALL) + .withValueMin(0) + .withValueMax(65535) + .withDescription( + 'Period in minutes for which the setpoint hold will be active. 65535 = attribute not' + + ' used. 0 to 360 to match the remote display', + ), + ], meta: {disableDefaultResponse: true}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(5); @@ -195,18 +235,39 @@ const definitions: Definition[] = [ vendor: 'Hive', description: 'Heating thermostat', fromZigbee: [fz.thermostat, fz.thermostat_weekly_schedule], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_system_mode, tz.thermostat_running_state, - tz.thermostat_occupied_heating_setpoint, tz.thermostat_control_sequence_of_operation, tz.thermostat_weekly_schedule, - tz.thermostat_clear_weekly_schedule, tz.thermostat_temperature_setpoint_hold, tz.thermostat_temperature_setpoint_hold_duration], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_system_mode, + tz.thermostat_running_state, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_control_sequence_of_operation, + tz.thermostat_weekly_schedule, + tz.thermostat_clear_weekly_schedule, + tz.thermostat_temperature_setpoint_hold, + tz.thermostat_temperature_setpoint_hold_duration, + ], exposes: [ - e.climate().withSetpoint('occupied_heating_setpoint', 5, 32, 0.5).withLocalTemperature() - .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat']), - e.binary('temperature_setpoint_hold', ea.ALL, true, false) - .withDescription('Prevent changes. `false` = run normally. `true` = prevent from making changes.' + - ' Must be set to `false` when system_mode = off or `true` for heat'), - e.numeric('temperature_setpoint_hold_duration', ea.ALL).withValueMin(0).withValueMax(65535) - .withDescription('Period in minutes for which the setpoint hold will be active. 65535 = attribute not' + - ' used. 0 to 360 to match the remote display')], + e + .climate() + .withSetpoint('occupied_heating_setpoint', 5, 32, 0.5) + .withLocalTemperature() + .withSystemMode(['off', 'auto', 'heat']) + .withRunningState(['idle', 'heat']), + e + .binary('temperature_setpoint_hold', ea.ALL, true, false) + .withDescription( + 'Prevent changes. `false` = run normally. `true` = prevent from making changes.' + + ' Must be set to `false` when system_mode = off or `true` for heat', + ), + e + .numeric('temperature_setpoint_hold_duration', ea.ALL) + .withValueMin(0) + .withValueMax(65535) + .withDescription( + 'Period in minutes for which the setpoint hold will be active. 65535 = attribute not' + + ' used. 0 to 360 to match the remote display', + ), + ], meta: {disableDefaultResponse: true}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(5); @@ -226,18 +287,39 @@ const definitions: Definition[] = [ vendor: 'Hive', description: 'Heating thermostat', fromZigbee: [fz.thermostat, fz.thermostat_weekly_schedule], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_system_mode, tz.thermostat_running_state, - tz.thermostat_occupied_heating_setpoint, tz.thermostat_control_sequence_of_operation, tz.thermostat_weekly_schedule, - tz.thermostat_clear_weekly_schedule, tz.thermostat_temperature_setpoint_hold, tz.thermostat_temperature_setpoint_hold_duration], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_system_mode, + tz.thermostat_running_state, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_control_sequence_of_operation, + tz.thermostat_weekly_schedule, + tz.thermostat_clear_weekly_schedule, + tz.thermostat_temperature_setpoint_hold, + tz.thermostat_temperature_setpoint_hold_duration, + ], exposes: [ - e.climate().withSetpoint('occupied_heating_setpoint', 5, 32, 0.5).withLocalTemperature() - .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat']), - e.binary('temperature_setpoint_hold', ea.ALL, true, false) - .withDescription('Prevent changes. `false` = run normally. `true` = prevent from making changes.' + - ' Must be set to `false` when system_mode = off or `true` for heat'), - e.numeric('temperature_setpoint_hold_duration', ea.ALL).withValueMin(0).withValueMax(65535) - .withDescription('Period in minutes for which the setpoint hold will be active. 65535 = attribute not' + - ' used. 0 to 360 to match the remote display')], + e + .climate() + .withSetpoint('occupied_heating_setpoint', 5, 32, 0.5) + .withLocalTemperature() + .withSystemMode(['off', 'auto', 'heat']) + .withRunningState(['idle', 'heat']), + e + .binary('temperature_setpoint_hold', ea.ALL, true, false) + .withDescription( + 'Prevent changes. `false` = run normally. `true` = prevent from making changes.' + + ' Must be set to `false` when system_mode = off or `true` for heat', + ), + e + .numeric('temperature_setpoint_hold_duration', ea.ALL) + .withValueMin(0) + .withValueMax(65535) + .withDescription( + 'Period in minutes for which the setpoint hold will be active. 65535 = attribute not' + + ' used. 0 to 360 to match the remote display', + ), + ], meta: {disableDefaultResponse: true}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(5); @@ -257,19 +339,25 @@ const definitions: Definition[] = [ vendor: 'Hive', description: 'Dual channel heating and hot water thermostat', fromZigbee: [fz.thermostat, fz.thermostat_weekly_schedule], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_system_mode, tz.thermostat_running_state, - tz.thermostat_occupied_heating_setpoint, tz.thermostat_control_sequence_of_operation, tz.thermostat_weekly_schedule, - tz.thermostat_clear_weekly_schedule, tz.thermostat_temperature_setpoint_hold, tz.thermostat_temperature_setpoint_hold_duration], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_system_mode, + tz.thermostat_running_state, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_control_sequence_of_operation, + tz.thermostat_weekly_schedule, + tz.thermostat_clear_weekly_schedule, + tz.thermostat_temperature_setpoint_hold, + tz.thermostat_temperature_setpoint_hold_duration, + ], endpoint: (device) => { - return {'heat': 5, 'water': 6}; + return {heat: 5, water: 6}; }, meta: {disableDefaultResponse: true, multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { const heatEndpoint = device.getEndpoint(5); const waterEndpoint = device.getEndpoint(6); - const binds = [ - 'genBasic', 'genIdentify', 'genAlarms', 'genTime', 'hvacThermostat', - ]; + const binds = ['genBasic', 'genIdentify', 'genAlarms', 'genTime', 'hvacThermostat']; await reporting.bind(heatEndpoint, coordinatorEndpoint, binds); await reporting.thermostatTemperature(heatEndpoint); await reporting.thermostatRunningState(heatEndpoint); @@ -286,22 +374,53 @@ const definitions: Definition[] = [ await reporting.thermostatTemperatureSetpointHoldDuration(waterEndpoint); }, exposes: [ - e.climate().withSetpoint('occupied_heating_setpoint', 5, 32, 0.5).withLocalTemperature() - .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat']).withEndpoint('heat'), - e.binary('temperature_setpoint_hold', ea.ALL, true, false) - .withDescription('Prevent changes. `false` = run normally. `true` = prevent from making changes.' + - ' Must be set to `false` when system_mode = off or `true` for heat').withEndpoint('heat'), - e.numeric('temperature_setpoint_hold_duration', ea.ALL).withValueMin(0).withValueMax(65535) - .withDescription('Period in minutes for which the setpoint hold will be active. 65535 = attribute not' + - ' used. 0 to 360 to match the remote display').withEndpoint('heat'), - e.climate().withSetpoint('occupied_heating_setpoint', 22, 22, 1).withLocalTemperature() - .withSystemMode(['off', 'auto', 'heat', 'emergency_heating']).withRunningState(['idle', 'heat']).withEndpoint('water'), - e.binary('temperature_setpoint_hold', ea.ALL, true, false) - .withDescription('Prevent changes. `false` = run normally. `true` = prevent from making changes.' + - ' Must be set to `false` when system_mode = off or `true` for heat').withEndpoint('water'), - e.numeric('temperature_setpoint_hold_duration', ea.ALL).withValueMin(0).withValueMax(65535) - .withDescription('Period in minutes for which the setpoint hold will be active. 65535 = attribute not' + - ' used. 0 to 360 to match the remote display').withEndpoint('water')], + e + .climate() + .withSetpoint('occupied_heating_setpoint', 5, 32, 0.5) + .withLocalTemperature() + .withSystemMode(['off', 'auto', 'heat']) + .withRunningState(['idle', 'heat']) + .withEndpoint('heat'), + e + .binary('temperature_setpoint_hold', ea.ALL, true, false) + .withDescription( + 'Prevent changes. `false` = run normally. `true` = prevent from making changes.' + + ' Must be set to `false` when system_mode = off or `true` for heat', + ) + .withEndpoint('heat'), + e + .numeric('temperature_setpoint_hold_duration', ea.ALL) + .withValueMin(0) + .withValueMax(65535) + .withDescription( + 'Period in minutes for which the setpoint hold will be active. 65535 = attribute not' + + ' used. 0 to 360 to match the remote display', + ) + .withEndpoint('heat'), + e + .climate() + .withSetpoint('occupied_heating_setpoint', 22, 22, 1) + .withLocalTemperature() + .withSystemMode(['off', 'auto', 'heat', 'emergency_heating']) + .withRunningState(['idle', 'heat']) + .withEndpoint('water'), + e + .binary('temperature_setpoint_hold', ea.ALL, true, false) + .withDescription( + 'Prevent changes. `false` = run normally. `true` = prevent from making changes.' + + ' Must be set to `false` when system_mode = off or `true` for heat', + ) + .withEndpoint('water'), + e + .numeric('temperature_setpoint_hold_duration', ea.ALL) + .withValueMin(0) + .withValueMax(65535) + .withDescription( + 'Period in minutes for which the setpoint hold will be active. 65535 = attribute not' + + ' used. 0 to 360 to match the remote display', + ) + .withEndpoint('water'), + ], }, { zigbeeModel: ['SLR2b'], @@ -309,19 +428,25 @@ const definitions: Definition[] = [ vendor: 'Hive', description: 'Dual channel heating and hot water thermostat', fromZigbee: [fz.thermostat, fz.thermostat_weekly_schedule], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_system_mode, tz.thermostat_running_state, - tz.thermostat_occupied_heating_setpoint, tz.thermostat_control_sequence_of_operation, tz.thermostat_weekly_schedule, - tz.thermostat_clear_weekly_schedule, tz.thermostat_temperature_setpoint_hold, tz.thermostat_temperature_setpoint_hold_duration], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_system_mode, + tz.thermostat_running_state, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_control_sequence_of_operation, + tz.thermostat_weekly_schedule, + tz.thermostat_clear_weekly_schedule, + tz.thermostat_temperature_setpoint_hold, + tz.thermostat_temperature_setpoint_hold_duration, + ], endpoint: (device) => { - return {'heat': 5, 'water': 6}; + return {heat: 5, water: 6}; }, meta: {disableDefaultResponse: true, multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { const heatEndpoint = device.getEndpoint(5); const waterEndpoint = device.getEndpoint(6); - const binds = [ - 'genBasic', 'genIdentify', 'genAlarms', 'genTime', 'hvacThermostat', - ]; + const binds = ['genBasic', 'genIdentify', 'genAlarms', 'genTime', 'hvacThermostat']; await reporting.bind(heatEndpoint, coordinatorEndpoint, binds); await reporting.thermostatTemperature(heatEndpoint); await reporting.thermostatRunningState(heatEndpoint); @@ -338,22 +463,53 @@ const definitions: Definition[] = [ await reporting.thermostatTemperatureSetpointHoldDuration(waterEndpoint); }, exposes: [ - e.climate().withSetpoint('occupied_heating_setpoint', 5, 32, 0.5).withLocalTemperature() - .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat']).withEndpoint('heat'), - e.binary('temperature_setpoint_hold', ea.ALL, true, false) - .withDescription('Prevent changes. `false` = run normally. `true` = prevent from making changes.' + - ' Must be set to `false` when system_mode = off or `true` for heat').withEndpoint('heat'), - e.numeric('temperature_setpoint_hold_duration', ea.ALL).withValueMin(0).withValueMax(65535) - .withDescription('Period in minutes for which the setpoint hold will be active. 65535 = attribute not' + - ' used. 0 to 360 to match the remote display').withEndpoint('heat'), - e.climate().withSetpoint('occupied_heating_setpoint', 22, 22, 1).withLocalTemperature() - .withSystemMode(['off', 'auto', 'heat', 'emergency_heating']).withRunningState(['idle', 'heat']).withEndpoint('water'), - e.binary('temperature_setpoint_hold', ea.ALL, true, false) - .withDescription('Prevent changes. `false` = run normally. `true` = prevent from making changes.' + - ' Must be set to `false` when system_mode = off or `true` for heat').withEndpoint('water'), - e.numeric('temperature_setpoint_hold_duration', ea.ALL).withValueMin(0).withValueMax(65535) - .withDescription('Period in minutes for which the setpoint hold will be active. 65535 = attribute not' + - ' used. 0 to 360 to match the remote display').withEndpoint('water')], + e + .climate() + .withSetpoint('occupied_heating_setpoint', 5, 32, 0.5) + .withLocalTemperature() + .withSystemMode(['off', 'auto', 'heat']) + .withRunningState(['idle', 'heat']) + .withEndpoint('heat'), + e + .binary('temperature_setpoint_hold', ea.ALL, true, false) + .withDescription( + 'Prevent changes. `false` = run normally. `true` = prevent from making changes.' + + ' Must be set to `false` when system_mode = off or `true` for heat', + ) + .withEndpoint('heat'), + e + .numeric('temperature_setpoint_hold_duration', ea.ALL) + .withValueMin(0) + .withValueMax(65535) + .withDescription( + 'Period in minutes for which the setpoint hold will be active. 65535 = attribute not' + + ' used. 0 to 360 to match the remote display', + ) + .withEndpoint('heat'), + e + .climate() + .withSetpoint('occupied_heating_setpoint', 22, 22, 1) + .withLocalTemperature() + .withSystemMode(['off', 'auto', 'heat', 'emergency_heating']) + .withRunningState(['idle', 'heat']) + .withEndpoint('water'), + e + .binary('temperature_setpoint_hold', ea.ALL, true, false) + .withDescription( + 'Prevent changes. `false` = run normally. `true` = prevent from making changes.' + + ' Must be set to `false` when system_mode = off or `true` for heat', + ) + .withEndpoint('water'), + e + .numeric('temperature_setpoint_hold_duration', ea.ALL) + .withValueMin(0) + .withValueMax(65535) + .withDescription( + 'Period in minutes for which the setpoint hold will be active. 65535 = attribute not' + + ' used. 0 to 360 to match the remote display', + ) + .withEndpoint('water'), + ], }, { zigbeeModel: ['SLR2c'], @@ -361,19 +517,25 @@ const definitions: Definition[] = [ vendor: 'Hive', description: 'Dual channel heating and hot water thermostat', fromZigbee: [fz.thermostat, fz.thermostat_weekly_schedule], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_system_mode, tz.thermostat_running_state, - tz.thermostat_occupied_heating_setpoint, tz.thermostat_control_sequence_of_operation, tz.thermostat_weekly_schedule, - tz.thermostat_clear_weekly_schedule, tz.thermostat_temperature_setpoint_hold, tz.thermostat_temperature_setpoint_hold_duration], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_system_mode, + tz.thermostat_running_state, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_control_sequence_of_operation, + tz.thermostat_weekly_schedule, + tz.thermostat_clear_weekly_schedule, + tz.thermostat_temperature_setpoint_hold, + tz.thermostat_temperature_setpoint_hold_duration, + ], endpoint: (device) => { - return {'heat': 5, 'water': 6}; + return {heat: 5, water: 6}; }, meta: {disableDefaultResponse: true, multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { const heatEndpoint = device.getEndpoint(5); const waterEndpoint = device.getEndpoint(6); - const binds = [ - 'genBasic', 'genIdentify', 'genAlarms', 'genTime', 'hvacThermostat', - ]; + const binds = ['genBasic', 'genIdentify', 'genAlarms', 'genTime', 'hvacThermostat']; await reporting.bind(heatEndpoint, coordinatorEndpoint, binds); await reporting.thermostatTemperature(heatEndpoint); await reporting.thermostatRunningState(heatEndpoint); @@ -390,22 +552,53 @@ const definitions: Definition[] = [ await reporting.thermostatTemperatureSetpointHoldDuration(waterEndpoint); }, exposes: [ - e.climate().withSetpoint('occupied_heating_setpoint', 5, 32, 0.5).withLocalTemperature() - .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat']).withEndpoint('heat'), - e.binary('temperature_setpoint_hold', ea.ALL, true, false) - .withDescription('Prevent changes. `false` = run normally. `true` = prevent from making changes.' + - ' Must be set to `false` when system_mode = off or `true` for heat').withEndpoint('heat'), - e.numeric('temperature_setpoint_hold_duration', ea.ALL).withValueMin(0).withValueMax(65535) - .withDescription('Period in minutes for which the setpoint hold will be active. 65535 = attribute not' + - ' used. 0 to 360 to match the remote display').withEndpoint('heat'), - e.climate().withSetpoint('occupied_heating_setpoint', 22, 22, 1).withLocalTemperature() - .withSystemMode(['off', 'auto', 'heat', 'emergency_heating']).withRunningState(['idle', 'heat']).withEndpoint('water'), - e.binary('temperature_setpoint_hold', ea.ALL, true, false) - .withDescription('Prevent changes. `false` = run normally. `true` = prevent from making changes.' + - ' Must be set to `false` when system_mode = off or `true` for heat').withEndpoint('water'), - e.numeric('temperature_setpoint_hold_duration', ea.ALL).withValueMin(0).withValueMax(65535) - .withDescription('Period in minutes for which the setpoint hold will be active. 65535 = attribute not' + - ' used. 0 to 360 to match the remote display').withEndpoint('water')], + e + .climate() + .withSetpoint('occupied_heating_setpoint', 5, 32, 0.5) + .withLocalTemperature() + .withSystemMode(['off', 'auto', 'heat']) + .withRunningState(['idle', 'heat']) + .withEndpoint('heat'), + e + .binary('temperature_setpoint_hold', ea.ALL, true, false) + .withDescription( + 'Prevent changes. `false` = run normally. `true` = prevent from making changes.' + + ' Must be set to `false` when system_mode = off or `true` for heat', + ) + .withEndpoint('heat'), + e + .numeric('temperature_setpoint_hold_duration', ea.ALL) + .withValueMin(0) + .withValueMax(65535) + .withDescription( + 'Period in minutes for which the setpoint hold will be active. 65535 = attribute not' + + ' used. 0 to 360 to match the remote display', + ) + .withEndpoint('heat'), + e + .climate() + .withSetpoint('occupied_heating_setpoint', 22, 22, 1) + .withLocalTemperature() + .withSystemMode(['off', 'auto', 'heat', 'emergency_heating']) + .withRunningState(['idle', 'heat']) + .withEndpoint('water'), + e + .binary('temperature_setpoint_hold', ea.ALL, true, false) + .withDescription( + 'Prevent changes. `false` = run normally. `true` = prevent from making changes.' + + ' Must be set to `false` when system_mode = off or `true` for heat', + ) + .withEndpoint('water'), + e + .numeric('temperature_setpoint_hold_duration', ea.ALL) + .withValueMin(0) + .withValueMax(65535) + .withDescription( + 'Period in minutes for which the setpoint hold will be active. 65535 = attribute not' + + ' used. 0 to 360 to match the remote display', + ) + .withEndpoint('water'), + ], }, { zigbeeModel: ['WPT1'], @@ -504,13 +697,16 @@ const definitions: Definition[] = [ clearInterval(globalStore.getValue(device, 'interval')); globalStore.clearValue(device, 'interval'); } else if (!globalStore.hasValue(device, 'interval')) { - const interval = setInterval(async () => { - try { - await device.endpoints[0].read('genBasic', ['zclVersion']); - } catch (error) { - // Do nothing - } - }, 1000 * 60 * 30); // Every 30 minutes + const interval = setInterval( + async () => { + try { + await device.endpoints[0].read('genBasic', ['zclVersion']); + } catch (error) { + // Do nothing + } + }, + 1000 * 60 * 30, + ); // Every 30 minutes globalStore.putValue(device, 'interval', interval); } }, diff --git a/src/devices/hommyn.ts b/src/devices/hommyn.ts index 8f93b0678c70b..0ad3403dfd482 100644 --- a/src/devices/hommyn.ts +++ b/src/devices/hommyn.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/honyar.ts b/src/devices/honyar.ts index 2665f3bb1294b..87f94edb9b0f0 100644 --- a/src/devices/honyar.ts +++ b/src/devices/honyar.ts @@ -1,10 +1,10 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; +import {deviceEndpoints, onOff} from '../lib/modernExtend'; import * as reporting from '../lib/reporting'; import * as tuya from '../lib/tuya'; -import {deviceEndpoints, onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const e = exposes.presets; @@ -14,10 +14,7 @@ const definitions: Definition[] = [ model: 'U86K31ND6', vendor: 'Honyar', description: '3 gang switch ', - extend: [ - deviceEndpoints({endpoints: {'left': 1, 'center': 2, 'right': 3}}), - onOff({endpointNames: ['left', 'center', 'right']}), - ], + extend: [deviceEndpoints({endpoints: {left: 1, center: 2, right: 3}}), onOff({endpointNames: ['left', 'center', 'right']})], }, { zigbeeModel: ['HY0043'], @@ -46,8 +43,7 @@ const definitions: Definition[] = [ description: 'Smart power socket 10A with USB (with power monitoring)', fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, fz.ignore_basic_report], toZigbee: [tz.on_off], - exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), e.power(), e.current(), e.voltage(), - e.energy()], + exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), e.power(), e.current(), e.voltage(), e.energy()], meta: {multiEndpoint: true, multiEndpointSkip: ['energy', 'current', 'voltage', 'power']}, endpoint: (device) => { return {l1: 1, l2: 2}; @@ -63,8 +59,14 @@ const definitions: Definition[] = [ await reporting.currentSummDelivered(endpoint, {min: 10}); await reporting.readMeteringMultiplierDivisor(endpoint); endpoint.saveClusterAttributeKeyValue('seMetering', {divisor: 1000, multiplier: 1}); - endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', {acVoltageMultiplier: 1, - acVoltageDivisor: 10, acCurrentMultiplier: 1, acCurrentDivisor: 1000, acPowerMultiplier: 1, acPowerDivisor: 10}); + endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', { + acVoltageMultiplier: 1, + acVoltageDivisor: 10, + acCurrentMultiplier: 1, + acCurrentDivisor: 1000, + acPowerMultiplier: 1, + acPowerDivisor: 10, + }); device.save(); }, }, @@ -92,7 +94,7 @@ const definitions: Definition[] = [ toZigbee: [tz.on_off], exposes: [e.switch().withEndpoint('left'), e.switch().withEndpoint('right')], endpoint: (device) => { - return {'left': 1, 'right': 2}; + return {left: 1, right: 2}; }, meta: {multiEndpoint: true, disableDefaultResponse: true}, configure: async (device, coordinatorEndpoint) => { @@ -113,7 +115,7 @@ const definitions: Definition[] = [ toZigbee: [tz.on_off], exposes: [e.switch().withEndpoint('left'), e.switch().withEndpoint('right'), e.switch().withEndpoint('center')], endpoint: (device) => { - return {'left': 1, 'center': 2, 'right': 3}; + return {left: 1, center: 2, right: 3}; }, meta: {multiEndpoint: true, disableDefaultResponse: true}, configure: async (device, coordinatorEndpoint) => { diff --git a/src/devices/hornbach.ts b/src/devices/hornbach.ts index a0b35f4c627c0..44f6d4bbeac65 100644 --- a/src/devices/hornbach.ts +++ b/src/devices/hornbach.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/hzc.ts b/src/devices/hzc.ts index 72fcb691ae40f..6e48fad38b3e0 100644 --- a/src/devices/hzc.ts +++ b/src/devices/hzc.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; import fz from '../converters/fromZigbee'; import * as exposes from '../lib/exposes'; -import * as reporting from '../lib/reporting'; import {light} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/hzc_electric.ts b/src/devices/hzc_electric.ts index f1e4e441f86d4..6e48773255e33 100644 --- a/src/devices/hzc_electric.ts +++ b/src/devices/hzc_electric.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import {deviceEndpoints, light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ @@ -10,10 +10,7 @@ const definitions: Definition[] = [ model: 'D086-ZG', vendor: 'HZC Electric', description: 'Zigbee dual dimmer', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2}}), - light({endpointNames: ['l1', 'l2'], configureReporting: true}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2}}), light({endpointNames: ['l1', 'l2'], configureReporting: true})], }, { zigbeeModel: ['TempAndHumSensor-ZB3.0'], diff --git a/src/devices/icasa.ts b/src/devices/icasa.ts index f895ad3af0636..44e463d16f3b5 100644 --- a/src/devices/icasa.ts +++ b/src/devices/icasa.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; import {light, onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const e = exposes.presets; @@ -45,12 +45,36 @@ const definitions: Definition[] = [ vendor: 'iCasa', description: 'Zigbee 3.0 Keypad Pulse 4S', meta: {battery: {dontDividePercentage: true}}, - fromZigbee: [fz.command_recall, legacy.fz.scenes_recall_click, fz.command_on, legacy.fz.genOnOff_cmdOn, fz.command_off, - legacy.fz.genOnOff_cmdOff, fz.battery, legacy.fz.cmd_move_with_onoff, legacy.fz.cmd_stop_with_onoff, fz.command_store], - exposes: [e.battery(), e.action([ - 'on', 'off', 'brightness_stop', 'brightness_move_up', 'brightness_move_down', - 'recall_1', 'recall_2', 'recall_3', 'recall_4', - 'store_1', 'store_2', 'store_3', 'store_4'])], + fromZigbee: [ + fz.command_recall, + legacy.fz.scenes_recall_click, + fz.command_on, + legacy.fz.genOnOff_cmdOn, + fz.command_off, + legacy.fz.genOnOff_cmdOff, + fz.battery, + legacy.fz.cmd_move_with_onoff, + legacy.fz.cmd_stop_with_onoff, + fz.command_store, + ], + exposes: [ + e.battery(), + e.action([ + 'on', + 'off', + 'brightness_stop', + 'brightness_move_up', + 'brightness_move_down', + 'recall_1', + 'recall_2', + 'recall_3', + 'recall_4', + 'store_1', + 'store_2', + 'store_3', + 'store_4', + ]), + ], toZigbee: [], }, { @@ -58,12 +82,40 @@ const definitions: Definition[] = [ model: 'ICZB-KPD18S', vendor: 'iCasa', description: 'Zigbee 3.0 Keypad Pulse 8S', - fromZigbee: [fz.command_recall, legacy.fz.scenes_recall_click, fz.command_on, legacy.fz.genOnOff_cmdOn, fz.command_off, - legacy.fz.genOnOff_cmdOff, fz.battery, legacy.fz.cmd_move_with_onoff, legacy.fz.cmd_stop_with_onoff, fz.command_store], - exposes: [e.battery(), e.action([ - 'on', 'off', 'brightness_stop', 'brightness_move_up', 'brightness_move_down', - 'recall_1', 'recall_2', 'recall_3', 'recall_4', 'recall_5', 'recall_6', - 'store_1', 'store_2', 'store_3', 'store_4', 'store_5', 'store_6'])], + fromZigbee: [ + fz.command_recall, + legacy.fz.scenes_recall_click, + fz.command_on, + legacy.fz.genOnOff_cmdOn, + fz.command_off, + legacy.fz.genOnOff_cmdOff, + fz.battery, + legacy.fz.cmd_move_with_onoff, + legacy.fz.cmd_stop_with_onoff, + fz.command_store, + ], + exposes: [ + e.battery(), + e.action([ + 'on', + 'off', + 'brightness_stop', + 'brightness_move_up', + 'brightness_move_down', + 'recall_1', + 'recall_2', + 'recall_3', + 'recall_4', + 'recall_5', + 'recall_6', + 'store_1', + 'store_2', + 'store_3', + 'store_4', + 'store_5', + 'store_6', + ]), + ], toZigbee: [], }, { diff --git a/src/devices/idinio.ts b/src/devices/idinio.ts index 1d2c62fdf4d55..08ddf4c196c7b 100644 --- a/src/devices/idinio.ts +++ b/src/devices/idinio.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/ihorn.ts b/src/devices/ihorn.ts index 7f282c6630cb6..15e1a65c3ffce 100644 --- a/src/devices/ihorn.ts +++ b/src/devices/ihorn.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import {battery, iasZoneAlarm} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/ikea.ts b/src/devices/ikea.ts index fc85524832d88..6140123c109f8 100644 --- a/src/devices/ikea.ts +++ b/src/devices/ikea.ts @@ -1,73 +1,84 @@ import {Zcl} from 'zigbee-herdsman'; -import {Definition} from '../lib/types'; -import * as ota from '../lib/ota'; -import { - onOff, battery, iasZoneAlarm, identify, forcePowerSource, - temperature, humidity, occupancy, illuminance, windowCovering, - commandsOnOff, commandsLevelCtrl, commandsWindowCovering, pm25, - linkQuality, deviceEndpoints, deviceAddCustomCluster, bindCluster, -} from '../lib/modernExtend'; + import { - ikeaConfigureRemote, ikeaLight, ikeaOta, ikeaConfigureStyrbar, - ikeaBattery, ikeaAirPurifier, legacy as ikeaLegacy, - ikeaVoc, ikeaConfigureGenPollCtrl, tradfriOccupancy, - tradfriRequestedBrightness, tradfriCommandsOnOff, - tradfriCommandsLevelCtrl, styrbarCommandOn, - ikeaDotsClick, ikeaArrowClick, ikeaMediaCommands, + ikeaConfigureRemote, + ikeaLight, + ikeaOta, + ikeaConfigureStyrbar, + ikeaBattery, + ikeaAirPurifier, + legacy as ikeaLegacy, + ikeaVoc, + ikeaConfigureGenPollCtrl, + tradfriOccupancy, + tradfriRequestedBrightness, + tradfriCommandsOnOff, + tradfriCommandsLevelCtrl, + styrbarCommandOn, + ikeaDotsClick, + ikeaArrowClick, + ikeaMediaCommands, addCustomClusterManuSpecificIkeaUnknown, addCustomClusterManuSpecificIkeaAirPurifier, addCustomClusterManuSpecificIkeaVocIndexMeasurement, } from '../lib/ikea'; +import { + onOff, + battery, + iasZoneAlarm, + identify, + forcePowerSource, + temperature, + humidity, + occupancy, + illuminance, + windowCovering, + commandsOnOff, + commandsLevelCtrl, + commandsWindowCovering, + pm25, + linkQuality, + deviceEndpoints, + deviceAddCustomCluster, + bindCluster, +} from '../lib/modernExtend'; +import * as ota from '../lib/ota'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ // #region light // lights naming convention: type, light capabilities, form, diffuser type, brightness // #region E26/E27/B22 { - zigbeeModel: [ - 'TRADFRI bulb E27 WS opal 980lm', - 'TRADFRI bulb E26 WS opal 980lm', - 'TRADFRI bulb E27 WS\uFFFDopal 980lm'], + zigbeeModel: ['TRADFRI bulb E27 WS opal 980lm', 'TRADFRI bulb E26 WS opal 980lm', 'TRADFRI bulb E27 WS\uFFFDopal 980lm'], model: 'LED1545G12', vendor: 'IKEA', description: 'TRADFRI bulb E26/E27, white spectrum, globe, opal, 980 lm', extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight({colorTemp: true}), identify()], }, { - zigbeeModel: [ - 'TRADFRI bulb E27 CWS globe 806lm', - 'TRADFRI bulb E26 CWS globe 806lm'], + zigbeeModel: ['TRADFRI bulb E27 CWS globe 806lm', 'TRADFRI bulb E26 CWS globe 806lm'], model: 'LED2109G6', vendor: 'IKEA', description: 'TRADFRI bulb E26/E27, color/white spectrum, globe, opal, 806 lm', extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight({colorTemp: true, color: true}), identify()], }, { - zigbeeModel: [ - 'TRADFRI bulb E27 WS clear 950lm', - 'TRADFRI bulb E26 WS clear 950lm', - 'TRADFRI bulb E27 WS\uFFFDclear 950lm'], + zigbeeModel: ['TRADFRI bulb E27 WS clear 950lm', 'TRADFRI bulb E26 WS clear 950lm', 'TRADFRI bulb E27 WS\uFFFDclear 950lm'], model: 'LED1546G12', vendor: 'IKEA', description: 'TRADFRI bulb E26/E27, white spectrum, globe, clear, 950 lm', extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight({colorTemp: true}), identify()], }, { - zigbeeModel: [ - 'TRADFRI bulb E27 opal 1000lm', - 'TRADFRI bulb E27 W opal 1000lm', - ], + zigbeeModel: ['TRADFRI bulb E27 opal 1000lm', 'TRADFRI bulb E27 W opal 1000lm'], model: 'LED1623G12', vendor: 'IKEA', description: 'TRADFRI bulb E27, white, globe, opal, 1000 lm', extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight(), identify()], }, { - zigbeeModel: [ - 'TRADFRI bulb E27 WW globe 806lm', - 'TRADFRI bulb E26 WW globe 800lm', - 'TRADFRI bulb E26 WW globe 806lm', - ], + zigbeeModel: ['TRADFRI bulb E27 WW globe 806lm', 'TRADFRI bulb E26 WW globe 800lm', 'TRADFRI bulb E26 WW globe 806lm'], model: 'LED2103G5', vendor: 'IKEA', description: 'TRADFRI bulb E26/E27, warm white, globe, 806 lumen', @@ -93,11 +104,7 @@ const definitions: Definition[] = [ extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight(), identify()], }, { - zigbeeModel: [ - 'TRADFRIbulbG125E27WSopal470lm', - 'TRADFRIbulbG125E26WSopal450lm', - 'TRADFRIbulbG125E26WSopal470lm', - ], + zigbeeModel: ['TRADFRIbulbG125E27WSopal470lm', 'TRADFRIbulbG125E26WSopal450lm', 'TRADFRIbulbG125E26WSopal470lm'], model: 'LED1936G5', vendor: 'IKEA', description: 'TRADFRI bulb E26/E27, white spectrum, globe, opal, 450/470 lm', @@ -117,11 +124,7 @@ const definitions: Definition[] = [ extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight({colorTemp: true}), identify()], }, { - zigbeeModel: [ - 'TRADFRIbulbE26WSglobeclear800lm', - 'TRADFRIbulbE27WSglobeclear806lm', - 'TRADFRIbulbE26WSglobeclear806lm', - ], + zigbeeModel: ['TRADFRIbulbE26WSglobeclear800lm', 'TRADFRIbulbE27WSglobeclear806lm', 'TRADFRIbulbE26WSglobeclear806lm'], model: 'LED2004G8', vendor: 'IKEA', description: 'TRADFRI bulb E26/E27, white spectrum, globe, clear, 800/806 lm', @@ -141,83 +144,56 @@ const definitions: Definition[] = [ extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight({colorTemp: true}), identify()], }, { - zigbeeModel: [ - 'TRADFRI bulb E27 WW clear 250lm', - 'TRADFRI bulb E26 WW clear 250lm', - ], + zigbeeModel: ['TRADFRI bulb E27 WW clear 250lm', 'TRADFRI bulb E26 WW clear 250lm'], model: 'LED1842G3', vendor: 'IKEA', description: 'TRADFRI bulb E26/E27, warm white, globe, clear, 250 lm', extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight(), identify()], }, { - zigbeeModel: [ - 'TRADFRIbulbE27WWclear250lm', - 'TRADFRIbulbE26WWclear250lm', - ], + zigbeeModel: ['TRADFRIbulbE27WWclear250lm', 'TRADFRIbulbE26WWclear250lm'], model: 'LED1934G3', vendor: 'IKEA', description: 'TRADFRI bulb E26/E27, warm white, globe, clear, 250 lm', extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight({turnsOffAtBrightness1: true}), identify()], }, { - zigbeeModel: [ - 'TRADFRI bulb E26 opal 1000lm', - 'TRADFRI bulb E26 W opal 1000lm', - ], + zigbeeModel: ['TRADFRI bulb E26 opal 1000lm', 'TRADFRI bulb E26 W opal 1000lm'], model: 'LED1622G12', vendor: 'IKEA', description: 'TRADFRI bulb E26, white, globe, opal, 1000 lm', extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight(), identify()], }, { - zigbeeModel: [ - 'TRADFRI bulb E26 CWS 800lm', - 'TRADFRI bulb E27 CWS 806lm', - 'TRADFRI bulb E26 CWS 806lm', - 'TRADFRI bulb E26 CWS 810lm', - ], + zigbeeModel: ['TRADFRI bulb E26 CWS 800lm', 'TRADFRI bulb E27 CWS 806lm', 'TRADFRI bulb E26 CWS 806lm', 'TRADFRI bulb E26 CWS 810lm'], model: 'LED1924G9', vendor: 'IKEA', description: 'TRADFRI bulb E26/E27, color/white spectrum, globe, opal, 800/806/810 lm', extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight({colorTemp: true, color: true, turnsOffAtBrightness1: true}), identify()], }, { - zigbeeModel: [ - 'TRADFRI bulb E27 WS opal 1000lm', - 'TRADFRI bulb E26 WS opal 1000lm', - ], + zigbeeModel: ['TRADFRI bulb E27 WS opal 1000lm', 'TRADFRI bulb E26 WS opal 1000lm'], model: 'LED1732G11', vendor: 'IKEA', description: 'TRADFRI bulb E26/E27, white spectrum, globe, opal, 1000 lm', extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight({colorTemp: true}), identify()], }, { - zigbeeModel: [ - 'TRADFRI bulb E27 WW 806lm', - 'TRADFRI bulb E26 WW 806lm', - ], + zigbeeModel: ['TRADFRI bulb E27 WW 806lm', 'TRADFRI bulb E26 WW 806lm'], model: 'LED1836G9', vendor: 'IKEA', description: 'TRADFRI bulb E26/E27, warm white, globe, opal, 806 lm', extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight({turnsOffAtBrightness1: true}), identify()], }, { - zigbeeModel: [ - 'TRADFRI bulb E27 WS clear 806lm', - 'TRADFRI bulb E26 WS clear 806lm', - ], + zigbeeModel: ['TRADFRI bulb E27 WS clear 806lm', 'TRADFRI bulb E26 WS clear 806lm'], model: 'LED1736G9', vendor: 'IKEA', description: 'TRADFRI bulb E26/E27, white spectrum, globe, clear, 806 lm', extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight({colorTemp: true}), identify()], }, { - zigbeeModel: [ - 'TRADFRI bulb E27 WS globe 1055lm', - 'TRADFRI bulb E26 WS globe 1055lm', - 'TRADFRI bulb E26 WS globe 1100lm', - ], + zigbeeModel: ['TRADFRI bulb E27 WS globe 1055lm', 'TRADFRI bulb E26 WS globe 1055lm', 'TRADFRI bulb E26 WS globe 1100lm'], model: 'LED2201G8', vendor: 'IKEA', description: 'TRADFRI bulb E26/27, white spectrum, globe, opal, 1055/1100 lm', @@ -231,10 +207,7 @@ const definitions: Definition[] = [ extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight({colorTemp: true}), identify()], }, { - zigbeeModel: [ - 'TRADFRIbulbB22WSglobeopal1055lm', - 'TRADFRIbulbB22WSglobeopal1055lm', - ], + zigbeeModel: ['TRADFRIbulbB22WSglobeopal1055lm', 'TRADFRIbulbB22WSglobeopal1055lm'], model: 'LED2035G10', vendor: 'IKEA', description: 'TRADFRI bulb B22, white spectrum, globe, opal, 1055 lm', @@ -267,31 +240,21 @@ const definitions: Definition[] = [ extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight({colorTemp: true}), identify()], }, { - zigbeeModel: [ - 'TRADFRI bulb E14 WS opal 400lm', - 'TRADFRI bulb E12 WS opal 400lm', - ], + zigbeeModel: ['TRADFRI bulb E14 WS opal 400lm', 'TRADFRI bulb E12 WS opal 400lm'], model: 'LED1536G5', vendor: 'IKEA', description: 'TRADFRI bulb E12/E14, white spectrum, globe, opal, 400 lm', extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight({colorTemp: true}), identify()], }, { - zigbeeModel: [ - 'TRADFRI bulb E14 WS 470lm', - 'TRADFRI bulb E12 WS 450lm', - 'TRADFRI bulb E17 WS 440lm', - ], + zigbeeModel: ['TRADFRI bulb E14 WS 470lm', 'TRADFRI bulb E12 WS 450lm', 'TRADFRI bulb E17 WS 440lm'], model: 'LED1835C6', vendor: 'IKEA', description: 'TRADFRI bulb E12/E14/E17, white spectrum, candle, opal, 450/470/440 lm', extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight({colorTemp: true}), identify()], }, { - zigbeeModel: [ - 'TRADFRI bulb E14 WS globe 470lm', - 'TRADFRI bulb E12 WS globe 450lm', - ], + zigbeeModel: ['TRADFRI bulb E14 WS globe 470lm', 'TRADFRI bulb E12 WS globe 450lm'], model: 'LED2101G4', vendor: 'IKEA', description: 'TRADFRI bulb E12/E14, white spectrum, globe, opal, 450/470 lm', @@ -305,11 +268,7 @@ const definitions: Definition[] = [ extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight({colorTemp: true}), identify()], }, { - zigbeeModel: [ - 'TRADFRI bulb E14 W op/ch 400lm', - 'TRADFRI bulb E12 W op/ch 400lm', - 'TRADFRI bulb E17 W op/ch 400lm', - ], + zigbeeModel: ['TRADFRI bulb E14 W op/ch 400lm', 'TRADFRI bulb E12 W op/ch 400lm', 'TRADFRI bulb E17 W op/ch 400lm'], model: 'LED1649C5', vendor: 'IKEA', description: 'TRADFRI bulb E12/E14/E17, white, candle, opal, 400 lm', @@ -328,32 +287,21 @@ const definitions: Definition[] = [ extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight({colorTemp: true}), identify()], }, { - zigbeeModel: [ - 'TRADFRI bulb E12 WS opal 600lm', - 'TRADFRI bulb E17 WS opal 600lm', - ], + zigbeeModel: ['TRADFRI bulb E12 WS opal 600lm', 'TRADFRI bulb E17 WS opal 600lm'], model: 'LED1738G7', vendor: 'IKEA', description: 'TRADFRI bulb E12/E17, white spectrum, globe, opal, 600 lm', extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight({colorTemp: true}), identify()], }, { - zigbeeModel: [ - 'TRADFRI bulb E14 CWS 470lm', - 'TRADFRI bulb E12 CWS 450lm', - 'TRADFRI bulb E17 CWS 440lm', - ], + zigbeeModel: ['TRADFRI bulb E14 CWS 470lm', 'TRADFRI bulb E12 CWS 450lm', 'TRADFRI bulb E17 CWS 440lm'], model: 'LED1925G6', vendor: 'IKEA', description: 'TRADFRI bulb E12/E14/E17, color/white spectrum, globe, opal, 440/450/470 lm', extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight({colorTemp: true, color: true, turnsOffAtBrightness1: true}), identify()], }, { - zigbeeModel: [ - 'TRADFRIbulbE14WWclear250lm', - 'TRADFRIbulbE12WWclear250lm', - 'TRADFRIbulbE17WWclear250lm', - ], + zigbeeModel: ['TRADFRIbulbE14WWclear250lm', 'TRADFRIbulbE12WWclear250lm', 'TRADFRIbulbE17WWclear250lm'], model: 'LED1935C3', vendor: 'IKEA', description: 'TRADFRI bulb E12/E14/E17, warm white, candle, clear, 250 lm', @@ -367,10 +315,7 @@ const definitions: Definition[] = [ extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight(), identify()], }, { - zigbeeModel: [ - 'TRADFRIbulbE14WScandleopal470lm', - 'TRADFRIbulbE12WScandleopal450lm', - ], + zigbeeModel: ['TRADFRIbulbE14WScandleopal470lm', 'TRADFRIbulbE12WScandleopal450lm'], model: 'LED1949C5', vendor: 'IKEA', description: 'TRADFRI bulb E12/E14, white spectrum, candle, opal, 450/470 lm', @@ -420,10 +365,7 @@ const definitions: Definition[] = [ extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight(), identify()], }, { - zigbeeModel: [ - 'TRADFRI bulb GU10 CWS 345lm', - 'TRADFRI bulb GU10 CWS 380lm', - ], + zigbeeModel: ['TRADFRI bulb GU10 CWS 345lm', 'TRADFRI bulb GU10 CWS 380lm'], model: 'LED1923R5', vendor: 'IKEA', description: 'TRADFRI bulb GU10, color/white spectrum, 345/380 lm', @@ -598,20 +540,14 @@ const definitions: Definition[] = [ extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight({colorTemp: true, color: true}), identify()], }, { - zigbeeModel: [ - 'TRADFRI transformer 10W', - 'TRADFRI Driver 10W', - ], + zigbeeModel: ['TRADFRI transformer 10W', 'TRADFRI Driver 10W'], model: 'ICPSHC24-10EU-IL-1/ICPSHC24-10EU-IL-2', vendor: 'IKEA', description: 'TRADFRI LED driver, 10 w', extend: [addCustomClusterManuSpecificIkeaUnknown(), ikeaLight({turnsOffAtBrightness1: true}), identify()], }, { - zigbeeModel: [ - 'TRADFRI transformer 30W', - 'TRADFRI Driver 30W', - ], + zigbeeModel: ['TRADFRI transformer 30W', 'TRADFRI Driver 30W'], model: 'ICPSHC24-30EU-IL-1/ICPSHC24-10EU-IL-2', vendor: 'IKEA', description: 'TRADFRI LED driver, 30 w', @@ -632,24 +568,14 @@ const definitions: Definition[] = [ model: 'E1603/E1702/E1708', vendor: 'IKEA', description: 'TRADFRI control outlet', - extend: [ - addCustomClusterManuSpecificIkeaUnknown(), - onOff(), - identify(), - ikeaOta(), - ], + extend: [addCustomClusterManuSpecificIkeaUnknown(), onOff(), identify(), ikeaOta()], }, { zigbeeModel: ['ASKVADER on/off switch'], model: 'E1836', vendor: 'IKEA', description: 'ASKVADER on/off switch', - extend: [ - addCustomClusterManuSpecificIkeaUnknown(), - onOff(), - identify(), - ikeaOta(), - ], + extend: [addCustomClusterManuSpecificIkeaUnknown(), onOff(), identify(), ikeaOta()], }, { zigbeeModel: ['KNYCKLAN receiver'], @@ -669,12 +595,7 @@ const definitions: Definition[] = [ model: 'E2204', vendor: 'IKEA', description: 'TRETAKT smart plug', - extend: [ - addCustomClusterManuSpecificIkeaUnknown(), - onOff(), - identify(), - ikeaOta(), - ], + extend: [addCustomClusterManuSpecificIkeaUnknown(), onOff(), identify(), ikeaOta()], }, // #endregion on/off controls // #region blinds @@ -737,32 +658,18 @@ const definitions: Definition[] = [ // #endregion blinds // #region appliances { - zigbeeModel: [ - 'STARKVIND Air purifier', - 'STARKVIND Air purifier table', - ], + zigbeeModel: ['STARKVIND Air purifier', 'STARKVIND Air purifier table'], model: 'E2007', vendor: 'IKEA', description: 'STARKVIND air purifier', - extend: [ - addCustomClusterManuSpecificIkeaUnknown(), - addCustomClusterManuSpecificIkeaAirPurifier(), - ikeaAirPurifier(), - identify(), - ikeaOta(), - ], + extend: [addCustomClusterManuSpecificIkeaUnknown(), addCustomClusterManuSpecificIkeaAirPurifier(), ikeaAirPurifier(), identify(), ikeaOta()], }, { zigbeeModel: ['TRADFRI signal repeater'], model: 'E1746', vendor: 'IKEA', description: 'TRADFRI signal repeater', - extend: [ - addCustomClusterManuSpecificIkeaUnknown(), - identify(), - linkQuality({reporting: true}), - ikeaOta(), - ], + extend: [addCustomClusterManuSpecificIkeaUnknown(), identify(), linkQuality({reporting: true}), ikeaOta()], }, // #endregion appliances // #region remotes @@ -820,9 +727,11 @@ const definitions: Definition[] = [ model: 'E1743', vendor: 'IKEA', description: 'TRADFRI on/off switch', - fromZigbee: [ // DEPRECATED + fromZigbee: [ + // DEPRECATED ikeaLegacy.fromZigbee.E1743_brightness_up, - ikeaLegacy.fromZigbee.E1743_brightness_down, ikeaLegacy.fromZigbee.E1743_brightness_stop, + ikeaLegacy.fromZigbee.E1743_brightness_down, + ikeaLegacy.fromZigbee.E1743_brightness_stop, ], meta: {disableActionGroup: true}, extend: [ @@ -870,8 +779,10 @@ const definitions: Definition[] = [ model: 'E1744', vendor: 'IKEA', description: 'SYMFONISK sound remote, gen 1', - fromZigbee: [ // DEPRECATED - ikeaLegacy.fromZigbee.E1744_play_pause, ikeaLegacy.fromZigbee.E1744_skip, + fromZigbee: [ + // DEPRECATED + ikeaLegacy.fromZigbee.E1744_play_pause, + ikeaLegacy.fromZigbee.E1744_skip, ], extend: [ identify({isSleepy: true}), @@ -889,20 +800,15 @@ const definitions: Definition[] = [ model: 'E1766', vendor: 'IKEA', description: 'TRADFRI open/close remote', - extend: [ - ikeaConfigureRemote(), - identify({isSleepy: true}), - commandsWindowCovering({legacyAction: true}), - ikeaBattery(), - ikeaOta(), - ], + extend: [ikeaConfigureRemote(), identify({isSleepy: true}), commandsWindowCovering({legacyAction: true}), ikeaBattery(), ikeaOta()], }, { zigbeeModel: ['SYMFONISK sound remote gen2'], model: 'E2123', vendor: 'IKEA', description: 'SYMFONISK sound remote, gen 2', - fromZigbee: [ // DEPRECATED + fromZigbee: [ + // DEPRECATED ikeaLegacy.fromZigbee.E1744_play_pause, ], extend: [ @@ -972,17 +878,14 @@ const definitions: Definition[] = [ extend: [ addCustomClusterManuSpecificIkeaUnknown(), addCustomClusterManuSpecificIkeaVocIndexMeasurement(), - deviceAddCustomCluster( - 'pm25Measurement', - { - ID: 0x042a, - attributes: { - measuredValue: {ID: 0x0000, type: Zcl.DataType.SINGLE_PREC}, - }, - commands: {}, - commandsResponse: {}, + deviceAddCustomCluster('pm25Measurement', { + ID: 0x042a, + attributes: { + measuredValue: {ID: 0x0000, type: Zcl.DataType.SINGLE_PREC}, }, - ), + commands: {}, + commandsResponse: {}, + }), temperature(), humidity(), pm25({reporting: {min: '1_MINUTE', max: '2_MINUTES', change: 2}}), @@ -995,14 +898,7 @@ const definitions: Definition[] = [ model: 'E2134', vendor: 'IKEA', description: 'VALLHORN wireless motion sensor', - extend: [ - addCustomClusterManuSpecificIkeaUnknown(), - occupancy(), - illuminance(), - identify({isSleepy: true}), - battery(), - ikeaOta(), - ], + extend: [addCustomClusterManuSpecificIkeaUnknown(), occupancy(), illuminance(), identify({isSleepy: true}), battery(), ikeaOta()], }, { zigbeeModel: ['PARASOLL Door/Window Sensor'], diff --git a/src/devices/ilightsin.ts b/src/devices/ilightsin.ts index 461a094c82ab6..7345a6a747bfe 100644 --- a/src/devices/ilightsin.ts +++ b/src/devices/ilightsin.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/iluminize.ts b/src/devices/iluminize.ts index b9871a55205c5..06fa2ffa9491b 100644 --- a/src/devices/iluminize.ts +++ b/src/devices/iluminize.ts @@ -1,8 +1,6 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as ota from '../lib/ota'; +import * as exposes from '../lib/exposes'; import { deviceEndpoints, light, @@ -14,6 +12,8 @@ import { commandsColorCtrl, commandsScenes, } from '../lib/modernExtend'; +import * as ota from '../lib/ota'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -34,10 +34,7 @@ const definitions: Definition[] = [ extend: [onOff()], }, { - fingerprint: [ - {modelID: '511.050'}, - {modelID: 'RGBWW Lighting', manufacturerName: 'Iluminize'}, - ], + fingerprint: [{modelID: '511.050'}, {modelID: 'RGBWW Lighting', manufacturerName: 'Iluminize'}], model: '511.050', vendor: 'Iluminize', description: 'Zigbee 3.0 LED controller for 5in1 RGB+CCT LEDs', @@ -143,10 +140,7 @@ const definitions: Definition[] = [ exposes: [e.action(['off', 'on', 'color_temperature_move', 'color_move'])], }, { - fingerprint: [ - {modelID: '511.040'}, - {modelID: 'RGBW-CCT', manufacturerName: 'Iluminize'}, - ], + fingerprint: [{modelID: '511.040'}, {modelID: 'RGBW-CCT', manufacturerName: 'Iluminize'}], model: '511.040', vendor: 'Iluminize', description: 'ZigBee 3.0 LED-controller, 4 channel 5A, RGBW LED', @@ -173,7 +167,7 @@ const definitions: Definition[] = [ vendor: 'Iluminize', description: 'Zigbee handheld remote RGBW 4 channels', extend: [ - deviceEndpoints({endpoints: {'ep1': 1, 'ep2': 2, 'ep3': 3, 'ep4': 4}}), + deviceEndpoints({endpoints: {ep1: 1, ep2: 2, ep3: 3, ep4: 4}}), battery(), identify(), commandsOnOff(), @@ -188,18 +182,45 @@ const definitions: Definition[] = [ model: '511.324', vendor: 'Iluminize', description: 'Zigbee handheld remote CCT 4 channels', - fromZigbee: [fz.battery, fz.command_move_to_color, fz.command_move_to_color_temp, fz.command_move_hue, - fz.command_step, fz.command_recall, fz.command_on, fz.command_off, fz.command_toggle, fz.command_stop, - fz.command_move, fz.command_color_loop_set, fz.command_ehanced_move_to_hue_and_saturation], - exposes: [e.battery(), e.action([ - 'color_move', 'color_temperature_move', 'hue_move', 'brightness_step_up', 'brightness_step_down', - 'recall_*', 'on', 'off', 'toggle', 'brightness_stop', 'brightness_move_up', 'brightness_move_down', - 'color_loop_set', 'enhanced_move_to_hue_and_saturation', 'hue_stop']), - e.numeric('action_group', ea.STATE) - .withDescription('Shows the zigbee2mqtt group bound to the active data point EP(1-4).'), - e.numeric('action_transition_time', ea.STATE), - e.numeric('action_step_size', ea.STATE), - e.numeric('action_rate', ea.STATE)], + fromZigbee: [ + fz.battery, + fz.command_move_to_color, + fz.command_move_to_color_temp, + fz.command_move_hue, + fz.command_step, + fz.command_recall, + fz.command_on, + fz.command_off, + fz.command_toggle, + fz.command_stop, + fz.command_move, + fz.command_color_loop_set, + fz.command_ehanced_move_to_hue_and_saturation, + ], + exposes: [ + e.battery(), + e.action([ + 'color_move', + 'color_temperature_move', + 'hue_move', + 'brightness_step_up', + 'brightness_step_down', + 'recall_*', + 'on', + 'off', + 'toggle', + 'brightness_stop', + 'brightness_move_up', + 'brightness_move_down', + 'color_loop_set', + 'enhanced_move_to_hue_and_saturation', + 'hue_stop', + ]), + e.numeric('action_group', ea.STATE).withDescription('Shows the zigbee2mqtt group bound to the active data point EP(1-4).'), + e.numeric('action_transition_time', ea.STATE), + e.numeric('action_step_size', ea.STATE), + e.numeric('action_rate', ea.STATE), + ], toZigbee: [], meta: {multiEndpoint: true}, endpoint: (device) => { @@ -211,11 +232,33 @@ const definitions: Definition[] = [ model: '511.541', vendor: 'Iluminize', description: 'Zigbee 3.0 wall dimmer RGBW 1 zone', - fromZigbee: [fz.command_recall, fz.command_on, fz.command_off, fz.command_move_to_color, fz.command_move_to_color_temp, - fz.command_move_hue, fz.command_step, fz.command_move, fz.command_stop], + fromZigbee: [ + fz.command_recall, + fz.command_on, + fz.command_off, + fz.command_move_to_color, + fz.command_move_to_color_temp, + fz.command_move_hue, + fz.command_step, + fz.command_move, + fz.command_stop, + ], toZigbee: [], - exposes: [e.action(['recall_*', 'on', 'off', 'color_move', 'color_temperature_move', - 'hue_move', 'brightness_step_down', 'brightness_step_up', 'brightness_move_down', 'brightness_move_up', 'brightness_stop'])], + exposes: [ + e.action([ + 'recall_*', + 'on', + 'off', + 'color_move', + 'color_temperature_move', + 'hue_move', + 'brightness_step_down', + 'brightness_step_up', + 'brightness_move_down', + 'brightness_move_up', + 'brightness_stop', + ]), + ], }, { zigbeeModel: ['5112.80'], @@ -231,24 +274,56 @@ const definitions: Definition[] = [ description: 'Zigbee 3.0 wall dimmer RGBW 4 zones', fromZigbee: [fz.command_move_to_color, fz.command_move_hue, fz.command_on, fz.command_off, fz.command_move], toZigbee: [], - exposes: [e.action(['recall_*', 'on', 'off', 'color_move', 'color_temperature_move', - 'hue_move', 'brightness_step_down', 'brightness_step_up', 'brightness_move_down', 'brightness_move_up', 'brightness_stop'])], + exposes: [ + e.action([ + 'recall_*', + 'on', + 'off', + 'color_move', + 'color_temperature_move', + 'hue_move', + 'brightness_step_down', + 'brightness_step_up', + 'brightness_move_down', + 'brightness_move_up', + 'brightness_stop', + ]), + ], }, { zigbeeModel: ['ZGRC-TEUR-003'], model: '511.524', vendor: 'Iluminize', description: 'Zigbee 3.0 wall dimmer CCT 4 zones', - fromZigbee: [fz.command_on, fz.command_off, fz.command_recall, - fz.command_move_to_color_temp, fz.command_step, fz.command_move, fz.command_stop], + fromZigbee: [ + fz.command_on, + fz.command_off, + fz.command_recall, + fz.command_move_to_color_temp, + fz.command_step, + fz.command_move, + fz.command_stop, + ], toZigbee: [], meta: {multiEndpoint: true}, - exposes: [e.action([ - 'recall_*', 'on', 'off', - 'brightness_step_down', 'brightness_step_up', - 'brightness_move_down', 'brightness_move_up', 'brightness_stop', - 'color_move', 'color_temperature_move', 'hue_move', - 'color_loop_set', 'enhanced_move_to_hue_and_saturation', 'hue_stop'])], + exposes: [ + e.action([ + 'recall_*', + 'on', + 'off', + 'brightness_step_down', + 'brightness_step_up', + 'brightness_move_down', + 'brightness_move_up', + 'brightness_stop', + 'color_move', + 'color_temperature_move', + 'hue_move', + 'color_loop_set', + 'enhanced_move_to_hue_and_saturation', + 'hue_stop', + ]), + ], }, ]; diff --git a/src/devices/ilux.ts b/src/devices/ilux.ts index 9923d3bde369e..57f8881c137fb 100644 --- a/src/devices/ilux.ts +++ b/src/devices/ilux.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/imhotepcreation.ts b/src/devices/imhotepcreation.ts index f185cd59c8032..c111432956b3a 100644 --- a/src/devices/imhotepcreation.ts +++ b/src/devices/imhotepcreation.ts @@ -1,7 +1,7 @@ -import tz from '../converters/toZigbee'; import fz from '../converters/fromZigbee'; -import * as exposes from '../lib/exposes'; +import tz from '../converters/toZigbee'; import * as constants from '../lib/constants'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; import {Zh, Definition} from '../lib/types'; @@ -15,23 +15,46 @@ const definitions: Definition[] = [ vendor: 'Imhotep Creation', description: 'Heater thermostat PH25 and compliant', whiteLabel: [ - {vendor: 'Imhotep Creation', model: 'RSS E-Ctrl', description: 'Towel heater thermostat THIE (TH ECTRL) and compliant', - fingerprint: [{modelID: 'RSS E-Ctrl'}]}, - {vendor: 'Imhotep Creation', model: 'RPH E-Ctrl', description: 'Panel radiant heater thermostat MPHIE (NRPH) and compliant', - fingerprint: [{modelID: 'RPH E-Ctrl'}]}, + { + vendor: 'Imhotep Creation', + model: 'RSS E-Ctrl', + description: 'Towel heater thermostat THIE (TH ECTRL) and compliant', + fingerprint: [{modelID: 'RSS E-Ctrl'}], + }, + { + vendor: 'Imhotep Creation', + model: 'RPH E-Ctrl', + description: 'Panel radiant heater thermostat MPHIE (NRPH) and compliant', + fingerprint: [{modelID: 'RPH E-Ctrl'}], + }, ], fromZigbee: [fz.thermostat, fz.occupancy], - toZigbee: [tz.thermostat_system_mode, tz.thermostat_occupied_heating_setpoint, tz.thermostat_min_heat_setpoint_limit, - tz.thermostat_max_heat_setpoint_limit, tz.thermostat_local_temperature, tz.thermostat_setpoint_raise_lower], + toZigbee: [ + tz.thermostat_system_mode, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_min_heat_setpoint_limit, + tz.thermostat_max_heat_setpoint_limit, + tz.thermostat_local_temperature, + tz.thermostat_setpoint_raise_lower, + ], exposes: [ - e.enum('system_mode', ea.ALL, ['off', 'heat']) - .withDescription('Heater mode (Off or Heat)'), + e.enum('system_mode', ea.ALL, ['off', 'heat']).withDescription('Heater mode (Off or Heat)'), e.local_temperature(), e.climate().withSetpoint('occupied_heating_setpoint', 5, 30, 0.5, ea.ALL).withLocalTemperature(), - e.numeric('min_heat_setpoint_limit', ea.ALL).withUnit('°C').withDescription('Minimum Heating set point limit') - .withValueMin(5).withValueMax(30).withValueStep(0.5), - e.numeric('max_heat_setpoint_limit', ea.ALL).withUnit('°C').withDescription('Maximum Heating set point limit') - .withValueMin(5).withValueMax(30).withValueStep(0.5), + e + .numeric('min_heat_setpoint_limit', ea.ALL) + .withUnit('°C') + .withDescription('Minimum Heating set point limit') + .withValueMin(5) + .withValueMax(30) + .withValueStep(0.5), + e + .numeric('max_heat_setpoint_limit', ea.ALL) + .withUnit('°C') + .withDescription('Maximum Heating set point limit') + .withValueMin(5) + .withValueMax(30) + .withValueStep(0.5), e.occupancy(), ], configure: async (device, coordinatorEndpoint) => { @@ -59,44 +82,98 @@ const definitions: Definition[] = [ vendor: 'Imhotep Creation', description: 'BRI4P Bridge for underfloor heating central and local thermostats', fromZigbee: [fz.thermostat], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_system_mode, tz.thermostat_occupied_heating_setpoint, - tz.thermostat_occupied_cooling_setpoint, tz.thermostat_min_heat_setpoint_limit, tz.thermostat_max_heat_setpoint_limit, - tz.thermostat_min_cool_setpoint_limit, tz.thermostat_max_cool_setpoint_limit, tz.thermostat_setpoint_raise_lower], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_system_mode, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_occupied_cooling_setpoint, + tz.thermostat_min_heat_setpoint_limit, + tz.thermostat_max_heat_setpoint_limit, + tz.thermostat_min_cool_setpoint_limit, + tz.thermostat_max_cool_setpoint_limit, + tz.thermostat_setpoint_raise_lower, + ], meta: {multiEndpoint: true}, endpoint: (device) => { return { - 'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4, 'l5': 5, - 'l6': 6, 'l7': 7, 'l8': 8, 'l9': 9, 'l10': 10, - 'l11': 11, 'l12': 12, 'l13': 13, 'l14': 14, 'l15': 15, - 'l16': 16, + l1: 1, + l2: 2, + l3: 3, + l4: 4, + l5: 5, + l6: 6, + l7: 7, + l8: 8, + l9: 9, + l10: 10, + l11: 11, + l12: 12, + l13: 13, + l14: 14, + l15: 15, + l16: 16, }; }, exposes: (device, options) => { const features = []; - if (typeof device !== 'undefined' && (device != null) && device.endpoints) { + if (typeof device !== 'undefined' && device != null && device.endpoints) { for (let i = 1; i <= 16; i++) { const endpoint = device?.getEndpoint(i); if (endpoint !== undefined) { const epName = `l${i}`; - features.push(e.enum('system mode', ea.ALL, ['off', 'cool', 'heat']).withProperty('system_mode') - .withEndpoint(epName).withDescription('Thermostat ' + i + ' mode (Off, Heating or Cooling)')); + features.push( + e + .enum('system mode', ea.ALL, ['off', 'cool', 'heat']) + .withProperty('system_mode') + .withEndpoint(epName) + .withDescription('Thermostat ' + i + ' mode (Off, Heating or Cooling)'), + ); features.push(e.local_temperature().withEndpoint(epName)); - features.push(e.climate().withSetpoint('occupied_heating_setpoint', 5, 30, 0.5, ea.ALL) - .withEndpoint(epName).withLocalTemperature()); - features.push(e.numeric('min_heat_setpoint_limit', ea.ALL).withUnit('°C') - .withDescription('Minimum Heating set point limit') - .withValueMin(5).withValueMax(30).withValueStep(0.5).withEndpoint(epName)); - features.push(e.numeric('max_heat_setpoint_limit', ea.ALL).withUnit('°C') - .withDescription('Maximum Heating set point limit') - .withValueMin(5).withValueMax(30).withValueStep(0.5).withEndpoint(epName)); + features.push( + e.climate().withSetpoint('occupied_heating_setpoint', 5, 30, 0.5, ea.ALL).withEndpoint(epName).withLocalTemperature(), + ); + features.push( + e + .numeric('min_heat_setpoint_limit', ea.ALL) + .withUnit('°C') + .withDescription('Minimum Heating set point limit') + .withValueMin(5) + .withValueMax(30) + .withValueStep(0.5) + .withEndpoint(epName), + ); + features.push( + e + .numeric('max_heat_setpoint_limit', ea.ALL) + .withUnit('°C') + .withDescription('Maximum Heating set point limit') + .withValueMin(5) + .withValueMax(30) + .withValueStep(0.5) + .withEndpoint(epName), + ); features.push(e.climate().withSetpoint('occupied_cooling_setpoint', 5, 38, 0.5, ea.ALL).withEndpoint(epName)); - features.push(e.numeric('min_cool_setpoint_limit', ea.ALL).withUnit('°C') - .withDescription('Minimum Cooling point limit') - .withValueMin(5).withValueMax(38).withValueStep(0.5).withEndpoint(epName)); - features.push(e.numeric('max_cool_setpoint_limit', ea.ALL).withUnit('°C') - .withDescription('Maximum Cooling set point limit') - .withValueMin(5).withValueMax(38).withValueStep(0.5).withEndpoint(epName)); + features.push( + e + .numeric('min_cool_setpoint_limit', ea.ALL) + .withUnit('°C') + .withDescription('Minimum Cooling point limit') + .withValueMin(5) + .withValueMax(38) + .withValueStep(0.5) + .withEndpoint(epName), + ); + features.push( + e + .numeric('max_cool_setpoint_limit', ea.ALL) + .withUnit('°C') + .withDescription('Maximum Cooling set point limit') + .withValueMin(5) + .withValueMax(38) + .withValueStep(0.5) + .withEndpoint(epName), + ); } } } @@ -115,19 +192,19 @@ const definitions: Definition[] = [ await reporting.thermostatOccupiedHeatingSetpoint(endpoint); await reporting.thermostatOccupiedCoolingSetpoint(endpoint); - const thermostatMinHeatSetpointLimit = async (endpoint : Zh.Endpoint) => { + const thermostatMinHeatSetpointLimit = async (endpoint: Zh.Endpoint) => { const p = reporting.payload('minHeatSetpointLimit', 0, constants.repInterval.HOUR, 10); await endpoint.configureReporting('hvacThermostat', p); }; - const thermostatMaxHeatSetpointLimit= async (endpoint : Zh.Endpoint) => { + const thermostatMaxHeatSetpointLimit = async (endpoint: Zh.Endpoint) => { const p = reporting.payload('maxHeatSetpointLimit', 0, constants.repInterval.HOUR, 10); await endpoint.configureReporting('hvacThermostat', p); }; - const thermostatMinCoolSetpointLimit = async (endpoint : Zh.Endpoint) => { + const thermostatMinCoolSetpointLimit = async (endpoint: Zh.Endpoint) => { const p = reporting.payload('minCoolSetpointLimit', 0, constants.repInterval.HOUR, 10); await endpoint.configureReporting('hvacThermostat', p); }; - const thermostatMaxCoolSetpointLimit= async (endpoint : Zh.Endpoint) => { + const thermostatMaxCoolSetpointLimit = async (endpoint: Zh.Endpoint) => { const p = reporting.payload('maxCoolSetpointLimit', 0, constants.repInterval.HOUR, 10); await endpoint.configureReporting('hvacThermostat', p); }; diff --git a/src/devices/immax.ts b/src/devices/immax.ts index a3104eb5ebf85..46b061ddd1de3 100644 --- a/src/devices/immax.ts +++ b/src/devices/immax.ts @@ -1,11 +1,11 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; -import * as tuya from '../lib/tuya'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; import {light} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import * as tuya from '../lib/tuya'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -16,8 +16,15 @@ const definitions: Definition[] = [ model: '07752L', description: 'NEO smart internal double socket', vendor: 'Immax', - extend: [tuya.modernExtend.tuyaOnOff({ - electricalMeasurements: true, powerOutageMemory: true, indicatorMode: true, childLock: true, endpoints: ['l1', 'l2']})], + extend: [ + tuya.modernExtend.tuyaOnOff({ + electricalMeasurements: true, + powerOutageMemory: true, + indicatorMode: true, + childLock: true, + endpoints: ['l1', 'l2'], + }), + ], configure: async (device, coordinatorEndpoint) => { await tuya.configureMagicPacket(device, coordinatorEndpoint); const endpoint = device.getEndpoint(1); @@ -31,12 +38,11 @@ const definitions: Definition[] = [ device.save(); }, endpoint: (device) => { - return {'l1': 1, 'l2': 2}; + return {l1: 1, l2: 2}; }, meta: {multiEndpoint: true, multiEndpointSkip: ['power', 'current', 'voltage', 'energy']}, }, { - zigbeeModel: ['Motion-Sensor-ZB3.0'], model: '07043M', vendor: 'Immax', @@ -130,8 +136,13 @@ const definitions: Definition[] = [ vendor: 'Immax', description: 'Radiator valve', fromZigbee: [legacy.fz.tuya_thermostat_weekly_schedule_1, legacy.fz.etop_thermostat, fz.ignore_basic_report, fz.ignore_tuya_set_time], - toZigbee: [legacy.tz.etop_thermostat_system_mode, legacy.tz.etop_thermostat_away_mode, legacy.tz.tuya_thermostat_child_lock, - legacy.tz.tuya_thermostat_current_heating_setpoint, legacy.tz.tuya_thermostat_weekly_schedule], + toZigbee: [ + legacy.tz.etop_thermostat_system_mode, + legacy.tz.etop_thermostat_away_mode, + legacy.tz.tuya_thermostat_child_lock, + legacy.tz.tuya_thermostat_current_heating_setpoint, + legacy.tz.tuya_thermostat_weekly_schedule, + ], onEvent: tuya.onEventSetTime, meta: { timeout: 20000, // TRV wakes up every 10sec @@ -141,10 +152,17 @@ const definitions: Definition[] = [ weeklyScheduleFirstDayDpId: 101, }, }, - exposes: [e.battery_low(), e.child_lock(), e.away_mode(), e.climate() - .withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET) - .withLocalTemperature(ea.STATE).withSystemMode(['off', 'heat', 'auto'], ea.STATE_SET) - .withRunningState(['idle', 'heat'], ea.STATE)], + exposes: [ + e.battery_low(), + e.child_lock(), + e.away_mode(), + e + .climate() + .withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET) + .withLocalTemperature(ea.STATE) + .withSystemMode(['off', 'heat', 'auto'], ea.STATE_SET) + .withRunningState(['idle', 'heat'], ea.STATE), + ], }, { zigbeeModel: ['Bulb-RGB+CCT-ZB3.0'], @@ -168,8 +186,7 @@ const definitions: Definition[] = [ await reporting.humidity(endpoint); await reporting.illuminance(endpoint); }, - exposes: [e.occupancy(), e.battery_low(), e.tamper(), e.battery(), e.temperature(), e.illuminance(), e.illuminance_lux(), - e.humidity()], + exposes: [e.occupancy(), e.battery_low(), e.tamper(), e.battery(), e.temperature(), e.illuminance(), e.illuminance_lux(), e.humidity()], }, { zigbeeModel: ['ColorTemperature'], @@ -193,21 +210,24 @@ const definitions: Definition[] = [ description: '4 in 1 multi sensor', fromZigbee: [fz.battery, fz.ignore_basic_report, fz.illuminance, legacy.fz.ZB003X, fz.ZB003X_attr, fz.ZB003X_occupancy], toZigbee: [legacy.tz.ZB003X], - exposes: [e.occupancy(), e.tamper(), e.battery(), e.illuminance(), e.illuminance_lux().withUnit('lx'), e.temperature(), - e.humidity(), e.numeric('reporting_time', ea.STATE_SET).withDescription('Reporting interval in minutes') - .withValueMin(0).withValueMax(1440), - e.numeric('temperature_calibration', ea.STATE_SET).withDescription('Temperature calibration') - .withValueMin(-20).withValueMax(20), - e.numeric('humidity_calibration', ea.STATE_SET).withDescription('Humidity calibration') - .withValueMin(-50).withValueMax(50), - e.numeric('illuminance_calibration', ea.STATE_SET).withDescription('Illuminance calibration') - .withValueMin(-10000).withValueMax(10000), + exposes: [ + e.occupancy(), + e.tamper(), + e.battery(), + e.illuminance(), + e.illuminance_lux().withUnit('lx'), + e.temperature(), + e.humidity(), + e.numeric('reporting_time', ea.STATE_SET).withDescription('Reporting interval in minutes').withValueMin(0).withValueMax(1440), + e.numeric('temperature_calibration', ea.STATE_SET).withDescription('Temperature calibration').withValueMin(-20).withValueMax(20), + e.numeric('humidity_calibration', ea.STATE_SET).withDescription('Humidity calibration').withValueMin(-50).withValueMax(50), + e.numeric('illuminance_calibration', ea.STATE_SET).withDescription('Illuminance calibration').withValueMin(-10000).withValueMax(10000), e.binary('pir_enable', ea.STATE_SET, true, false).withDescription('Enable PIR sensor'), e.binary('led_enable', ea.STATE_SET, true, false).withDescription('Enabled LED'), e.binary('reporting_enable', ea.STATE_SET, true, false).withDescription('Enabled reporting'), e.enum('sensitivity', ea.STATE_SET, ['low', 'medium', 'high']).withDescription('PIR sensor sensitivity'), - // eslint-disable-next-line - e.enum('keep_time', ea.STATE_SET, ['0', '30', '60', '120', '240']).withDescription('PIR keep time in seconds')], + e.enum('keep_time', ea.STATE_SET, ['0', '30', '60', '120', '240']).withDescription('PIR keep time in seconds'), + ], }, { fingerprint: tuya.fingerprint('TS0601', ['_TZE200_n9clpsht', '_TZE200_nyvavzbj']), diff --git a/src/devices/imou.ts b/src/devices/imou.ts index 2faa57984b429..652a0dfd47e7e 100644 --- a/src/devices/imou.ts +++ b/src/devices/imou.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {battery, forceDeviceType, iasWarning, iasZoneAlarm} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { @@ -7,10 +7,7 @@ const definitions: Definition[] = [ model: 'ZP1-EN', vendor: 'IMOU', description: 'Zigbee ZP1 PIR motion sensor', - extend: [ - battery(), - iasZoneAlarm({zoneType: 'occupancy', zoneAttributes: ['alarm_1', 'tamper', 'battery_low'], alarmTimeout: true}), - ], + extend: [battery(), iasZoneAlarm({zoneType: 'occupancy', zoneAttributes: ['alarm_1', 'tamper', 'battery_low'], alarmTimeout: true})], }, { zigbeeModel: ['ZR1-EN'], @@ -19,7 +16,8 @@ const definitions: Definition[] = [ description: 'Zigbee ZR1 siren', extend: [ battery(), - forceDeviceType({type: 'EndDevice'}), iasWarning(), + forceDeviceType({type: 'EndDevice'}), + iasWarning(), iasZoneAlarm({zoneType: 'alarm', zoneAttributes: ['alarm_1', 'tamper', 'battery_low']}), ], meta: {disableDefaultResponse: true}, diff --git a/src/devices/index.ts b/src/devices/index.ts index 464de6b988181..810a0e2992f4e 100644 --- a/src/devices/index.ts +++ b/src/devices/index.ts @@ -94,7 +94,7 @@ import giderwel from './giderwel'; import giex from './giex'; import girier from './girier'; import gledopto from './gledopto'; -import gmmts from "./gmmts"; +import gmmts from './gmmts'; import gmy from './gmy'; import gs from './gs'; import halemeier from './halemeier'; diff --git a/src/devices/innr.ts b/src/devices/innr.ts index 81121be994f71..4d006ca60d863 100644 --- a/src/devices/innr.ts +++ b/src/devices/innr.ts @@ -1,10 +1,10 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; -import * as ota from '../lib/ota'; import {light, onOff, electricityMeter, reconfigureReportingsOnDeviceAnnounce} from '../lib/modernExtend'; +import * as ota from '../lib/ota'; const definitions: Definition[] = [ { @@ -12,11 +12,19 @@ const definitions: Definition[] = [ model: 'RC 210', vendor: 'Innr', description: 'Remote control', - fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.command_move_to_level, - fz.command_move_to_color_temp], + fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.command_move_to_level, fz.command_move_to_color_temp], toZigbee: [], - exposes: [e.action(['on', 'off', 'brightness_move_up', 'brightness_move_down', 'brightness_stop', 'brightness_move_to_level', - 'color_temperature_move'])], + exposes: [ + e.action([ + 'on', + 'off', + 'brightness_move_up', + 'brightness_move_down', + 'brightness_stop', + 'brightness_move_to_level', + 'color_temperature_move', + ]), + ], configure: async (device, coordinatorEndpoint) => { const ep = device.getEndpoint(1); await reporting.bind(ep, coordinatorEndpoint, ['genBasic', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl']); @@ -29,12 +37,10 @@ const definitions: Definition[] = [ description: 'Remote control', fromZigbee: [fz.command_step, fz.command_on, fz.command_off, fz.command_move_to_level, fz.command_move_to_color_temp], toZigbee: [], - exposes: [e.action(['on', 'off', 'brightness_step_up', 'brightness_step_down', - 'brightness_move_to_level', 'color_temperature_move'])], + exposes: [e.action(['on', 'off', 'brightness_step_up', 'brightness_step_down', 'brightness_move_to_level', 'color_temperature_move'])], configure: async (device, coordinatorEndpoint) => { const ep = device.getEndpoint(1); - await reporting.bind(ep, coordinatorEndpoint, ['genBasic', 'genGroups', 'genScenes', - 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl']); + await reporting.bind(ep, coordinatorEndpoint, ['genBasic', 'genGroups', 'genScenes', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl']); }, }, { @@ -64,18 +70,28 @@ const definitions: Definition[] = [ model: 'FL 130 C', vendor: 'Innr', description: 'Color Flex LED strip', - extend: [light({ - colorTemp: {range: [153, 555]}, color: {modes: ['xy', 'hs'], applyRedFix: true}, powerOnBehavior: false, turnsOffAtBrightness1: true, - })], + extend: [ + light({ + colorTemp: {range: [153, 555]}, + color: {modes: ['xy', 'hs'], applyRedFix: true}, + powerOnBehavior: false, + turnsOffAtBrightness1: true, + }), + ], }, { zigbeeModel: ['FL 120 C'], model: 'FL 120 C', vendor: 'Innr', description: 'Color Flex LED strip', - extend: [light({ - colorTemp: {range: [153, 555]}, color: {modes: ['xy', 'hs'], applyRedFix: true}, powerOnBehavior: false, turnsOffAtBrightness1: true, - })], + extend: [ + light({ + colorTemp: {range: [153, 555]}, + color: {modes: ['xy', 'hs'], applyRedFix: true}, + powerOnBehavior: false, + turnsOffAtBrightness1: true, + }), + ], }, { zigbeeModel: ['BF 263'], @@ -105,9 +121,13 @@ const definitions: Definition[] = [ model: 'OPL 130 C', vendor: 'Innr', description: 'Outdoor smart pedestal light colour', - extend: [light({ - colorTemp: {range: [153, 555], startup: false}, color: {modes: ['xy', 'hs'], applyRedFix: true}, turnsOffAtBrightness1: true, - })], + extend: [ + light({ + colorTemp: {range: [153, 555], startup: false}, + color: {modes: ['xy', 'hs'], applyRedFix: true}, + turnsOffAtBrightness1: true, + }), + ], }, { zigbeeModel: ['RB 185 C'], @@ -128,18 +148,26 @@ const definitions: Definition[] = [ model: 'RB 250 C', vendor: 'Innr', description: 'E14 bulb RGBW', - extend: [light({ - colorTemp: {range: [153, 555]}, color: {modes: ['xy', 'hs'], enhancedHue: false, applyRedFix: true}, turnsOffAtBrightness1: true, - })], + extend: [ + light({ + colorTemp: {range: [153, 555]}, + color: {modes: ['xy', 'hs'], enhancedHue: false, applyRedFix: true}, + turnsOffAtBrightness1: true, + }), + ], }, { zigbeeModel: ['RB 251 C'], model: 'RB 251 C', vendor: 'Innr', description: 'E14 bulb RGBW', - extend: [light({ - colorTemp: {range: [153, 555]}, color: {modes: ['xy', 'hs'], enhancedHue: false, applyRedFix: true}, turnsOffAtBrightness1: true, - })], + extend: [ + light({ + colorTemp: {range: [153, 555]}, + color: {modes: ['xy', 'hs'], enhancedHue: false, applyRedFix: true}, + turnsOffAtBrightness1: true, + }), + ], ota: ota.zigbeeOTA, }, { @@ -235,9 +263,13 @@ const definitions: Definition[] = [ model: 'RB 285 C', vendor: 'Innr', description: 'E27 bulb RGBW', - extend: [light({ - colorTemp: {range: [153, 555]}, color: {modes: ['xy', 'hs'], enhancedHue: false, applyRedFix: true}, turnsOffAtBrightness1: true, - })], + extend: [ + light({ + colorTemp: {range: [153, 555]}, + color: {modes: ['xy', 'hs'], enhancedHue: false, applyRedFix: true}, + turnsOffAtBrightness1: true, + }), + ], }, { zigbeeModel: ['RB 286 C'], @@ -371,9 +403,13 @@ const definitions: Definition[] = [ model: 'RS 230 C', vendor: 'Innr', description: 'GU10 spot 350 lm, dimmable, RGBW', - extend: [light({ - colorTemp: {range: [153, 555]}, color: {modes: ['xy', 'hs'], enhancedHue: false, applyRedFix: true}, turnsOffAtBrightness1: true, - })], + extend: [ + light({ + colorTemp: {range: [153, 555]}, + color: {modes: ['xy', 'hs'], enhancedHue: false, applyRedFix: true}, + turnsOffAtBrightness1: true, + }), + ], ota: ota.zigbeeOTA, }, { @@ -381,9 +417,13 @@ const definitions: Definition[] = [ model: 'RS 232 C', vendor: 'Innr', description: 'GU10 spot, dimmable, RGBW', - extend: [light({ - colorTemp: {range: [153, 555]}, color: {modes: ['xy', 'hs'], enhancedHue: false, applyRedFix: true}, turnsOffAtBrightness1: true, - })], + extend: [ + light({ + colorTemp: {range: [153, 555]}, + color: {modes: ['xy', 'hs'], enhancedHue: false, applyRedFix: true}, + turnsOffAtBrightness1: true, + }), + ], ota: ota.zigbeeOTA, }, { @@ -614,10 +654,7 @@ const definitions: Definition[] = [ model: 'SP 234', vendor: 'Innr', description: 'Smart plug', - extend: [ - onOff(), - electricityMeter({current: {divisor: 1000}, voltage: {divisor: 1}, power: {divisor: 1}, energy: {divisor: 100}}), - ], + extend: [onOff(), electricityMeter({current: {divisor: 1000}, voltage: {divisor: 1}, power: {divisor: 1}, energy: {divisor: 100}})], ota: ota.zigbeeOTA, }, { @@ -653,9 +690,13 @@ const definitions: Definition[] = [ model: 'RB 255 C', vendor: 'Innr', description: 'E14 mini bulb RGBW', - extend: [light({ - colorTemp: {range: [153, 555]}, color: {modes: ['xy', 'hs'], enhancedHue: false, applyRedFix: true}, turnsOffAtBrightness1: true, - })], + extend: [ + light({ + colorTemp: {range: [153, 555]}, + color: {modes: ['xy', 'hs'], enhancedHue: false, applyRedFix: true}, + turnsOffAtBrightness1: true, + }), + ], ota: ota.zigbeeOTA, }, { @@ -702,12 +743,11 @@ const definitions: Definition[] = [ toZigbee: [], meta: {multiEndpoint: true}, endpoint: (device) => { - return {'all': 1, 'l1': 3, 'l2': 4, 'l3': 5, 'l4': 6, 'l5': 7, 'l6': 8}; + return {all: 1, l1: 3, l2: 4, l3: 5, l4: 6, l5: 7, l6: 8}; }, exposes: [e.action(['on_*', 'off_*', 'brightness_*', 'scene_*'])], configure: async (device, coordinatorEndpoint) => { - await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, - ['genBasic', 'genGroups', 'genScenes', 'genOnOff', 'genLevelCtrl']); + await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genBasic', 'genGroups', 'genScenes', 'genOnOff', 'genLevelCtrl']); for (const ep of [3, 4, 5, 6, 7, 8]) { const endpoint = device.getEndpoint(ep); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl']); @@ -719,10 +759,7 @@ const definitions: Definition[] = [ model: 'SP 240', vendor: 'Innr', description: 'Smart plug', - extend: [ - onOff(), - electricityMeter({current: {divisor: 1000}, voltage: {divisor: 1}, power: {divisor: 1}, energy: {divisor: 100}}), - ], + extend: [onOff(), electricityMeter({current: {divisor: 1000}, voltage: {divisor: 1}, power: {divisor: 1}, energy: {divisor: 100}})], ota: ota.zigbeeOTA, }, { @@ -744,10 +781,7 @@ const definitions: Definition[] = [ model: 'SP 244', vendor: 'Innr', description: 'Smart plug', - extend: [ - onOff(), - electricityMeter({current: {divisor: 1000}, voltage: {divisor: 1}, power: {divisor: 1}, energy: {divisor: 100}}), - ], + extend: [onOff(), electricityMeter({current: {divisor: 1000}, voltage: {divisor: 1}, power: {divisor: 1}, energy: {divisor: 100}})], ota: ota.zigbeeOTA, }, ]; diff --git a/src/devices/inovelli.ts b/src/devices/inovelli.ts index 2e51af5c449c6..5e1912645e3c0 100644 --- a/src/devices/inovelli.ts +++ b/src/devices/inovelli.ts @@ -1,17 +1,18 @@ import {Zcl} from 'zigbee-herdsman'; -import {Definition, Expose, Fz, Tz, Zh} from '../lib/types'; + import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; import * as exposes from '../lib/exposes'; -import * as globalStore from '../lib/store'; -import * as reporting from '../lib/reporting'; import * as ota from '../lib/ota'; +import * as reporting from '../lib/reporting'; +import * as globalStore from '../lib/store'; +import {Definition, Expose, Fz, Tz, Zh} from '../lib/types'; import * as utils from '../lib/utils'; const e = exposes.presets; const ea = exposes.access; -const clickLookup: { [key: number]: string } = { +const clickLookup: {[key: number]: string} = { 0: 'single', 1: 'release', 2: 'held', @@ -20,7 +21,7 @@ const clickLookup: { [key: number]: string } = { 5: 'quadruple', 6: 'quintuple', }; -const buttonLookup: { [key: number]: string } = { +const buttonLookup: {[key: number]: string} = { 1: 'down', 2: 'up', 3: 'config', @@ -29,7 +30,7 @@ const buttonLookup: { [key: number]: string } = { 6: 'aux_config', }; -const ledEffects: { [key: string]: number } = { +const ledEffects: {[key: string]: number} = { off: 0, solid: 1, fast_blink: 2, @@ -53,7 +54,7 @@ const ledEffects: { [key: string]: number } = { clear_effect: 255, }; -const individualLedEffects: { [key: string]: number } = { +const individualLedEffects: {[key: string]: number} = { off: 0, solid: 1, fast_blink: 2, @@ -66,27 +67,34 @@ const individualLedEffects: { [key: string]: number } = { clear_effect: 255, }; -const fanModes: { [key: string]: number } = {off: 0, low: 2, smart: 4, medium: 86, high: 170, on: 255}; +const fanModes: {[key: string]: number} = {off: 0, low: 2, smart: 4, medium: 86, high: 170, on: 255}; const breezemodes: string[] = ['off', 'low', 'medium', 'high']; const INOVELLI = 0x122f; interface Attribute { - ID: number, dataType: number, min?: number, max?: number, description: string, unit?: string, displayType?: string, - values?: {[s: string]: number}, readOnly?: boolean, + ID: number; + dataType: number; + min?: number; + max?: number; + description: string; + unit?: string; + displayType?: string; + values?: {[s: string]: number}; + readOnly?: boolean; } interface BreezeModeValues { - speed1: string, - speed2: string, - speed3: string, - speed4: string, - speed5: string, - time1: number, - time2: number, - time3: number, - time4: number, - time5: number, + speed1: string; + speed2: string; + speed3: string; + speed4: string; + speed5: string; + time1: number; + time2: number; + time3: number; + time4: number; + time5: number; } // Converts brightness level to a fan mode @@ -117,29 +125,26 @@ const intToFanMode = (value: number) => { */ const speedToInt = (speedIn: string): number => { switch (speedIn) { - case 'low': return 1; - case 'medium': return 2; - case 'high': return 3; - default: return 0; + case 'low': + return 1; + case 'medium': + return 2; + case 'high': + return 3; + default: + return 0; } }; // Create Expose list with Inovelli Parameters definitions -const attributesToExposeList = (ATTRIBUTES: {[s: string]: Attribute}, exposesList: Expose[]) =>{ +const attributesToExposeList = (ATTRIBUTES: {[s: string]: Attribute}, exposesList: Expose[]) => { Object.keys(ATTRIBUTES).forEach((key) => { if (ATTRIBUTES[key].displayType === 'enum') { const enumE = e - .enum( - key, - ATTRIBUTES[key].readOnly ? ea.STATE_GET : ea.ALL, - Object.keys(ATTRIBUTES[key].values), - ) + .enum(key, ATTRIBUTES[key].readOnly ? ea.STATE_GET : ea.ALL, Object.keys(ATTRIBUTES[key].values)) .withDescription(ATTRIBUTES[key].description); exposesList.push(enumE); - } else if ( - ATTRIBUTES[key].displayType === 'binary' || - ATTRIBUTES[key].displayType === 'switch' - ) { + } else if (ATTRIBUTES[key].displayType === 'binary' || ATTRIBUTES[key].displayType === 'switch') { exposesList.push( e .binary( @@ -184,9 +189,9 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 127, description: - 'This changes the speed that the light dims up when controlled from the hub. ' + - 'A setting of 0 turns the light immediately on. Increasing the value slows down the transition speed. ' + - 'Every number represents 100ms. Default = 25 (2.5s)', + 'This changes the speed that the light dims up when controlled from the hub. ' + + 'A setting of 0 turns the light immediately on. Increasing the value slows down the transition speed. ' + + 'Every number represents 100ms. Default = 25 (2.5s)', }, dimmingSpeedUpLocal: { ID: 2, @@ -194,9 +199,9 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 127, description: - 'This changes the speed that the light dims up when controlled at the switch. ' + - 'A setting of 0 turns the light immediately on. Increasing the value slows down the transition speed. ' + - 'Every number represents 100ms. Default = 127 - Keep in sync with dimmingSpeedUpRemote setting.', + 'This changes the speed that the light dims up when controlled at the switch. ' + + 'A setting of 0 turns the light immediately on. Increasing the value slows down the transition speed. ' + + 'Every number represents 100ms. Default = 127 - Keep in sync with dimmingSpeedUpRemote setting.', }, rampRateOffToOnRemote: { ID: 3, @@ -204,9 +209,9 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 127, description: - 'This changes the speed that the light turns on when controlled from the hub. ' + - 'A setting of 0 turns the light immediately on. Increasing the value slows down the transition speed. ' + - 'Every number represents 100ms. Default = 127 - Keep in sync with dimmingSpeedUpRemote setting.', + 'This changes the speed that the light turns on when controlled from the hub. ' + + 'A setting of 0 turns the light immediately on. Increasing the value slows down the transition speed. ' + + 'Every number represents 100ms. Default = 127 - Keep in sync with dimmingSpeedUpRemote setting.', }, rampRateOffToOnLocal: { ID: 4, @@ -214,9 +219,9 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 127, description: - 'This changes the speed that the light turns on when controlled at the switch. ' + - 'A setting of 0 turns the light immediately on. Increasing the value slows down the transition speed. ' + - 'Every number represents 100ms. Default = 127 - Keep in sync with dimmingSpeedUpRemote setting.', + 'This changes the speed that the light turns on when controlled at the switch. ' + + 'A setting of 0 turns the light immediately on. Increasing the value slows down the transition speed. ' + + 'Every number represents 100ms. Default = 127 - Keep in sync with dimmingSpeedUpRemote setting.', }, dimmingSpeedDownRemote: { ID: 5, @@ -224,9 +229,9 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 127, description: - 'This changes the speed that the light dims down when controlled from the hub. ' + - 'A setting of 0 turns the light immediately off. Increasing the value slows down the transition speed. ' + - 'Every number represents 100ms. Default = 127 - Keep in sync with dimmingSpeedUpRemote setting.', + 'This changes the speed that the light dims down when controlled from the hub. ' + + 'A setting of 0 turns the light immediately off. Increasing the value slows down the transition speed. ' + + 'Every number represents 100ms. Default = 127 - Keep in sync with dimmingSpeedUpRemote setting.', }, dimmingSpeedDownLocal: { ID: 6, @@ -234,9 +239,9 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 127, description: - 'This changes the speed that the light dims down when controlled at the switch. ' + - 'A setting of 0 turns the light immediately off. Increasing the value slows down the transition speed. ' + - 'Every number represents 100ms. Default = 127 - Keep in sync with dimmingSpeedUpLocal setting.', + 'This changes the speed that the light dims down when controlled at the switch. ' + + 'A setting of 0 turns the light immediately off. Increasing the value slows down the transition speed. ' + + 'Every number represents 100ms. Default = 127 - Keep in sync with dimmingSpeedUpLocal setting.', }, rampRateOnToOffRemote: { ID: 7, @@ -244,9 +249,9 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 127, description: - 'This changes the speed that the light turns off when controlled from the hub. ' + - 'A setting of \'instant\' turns the light immediately off. Increasing the value slows down the transition speed. ' + - 'Every number represents 100ms. Default = 127 - Keep in sync with rampRateOffToOnRemote setting.', + 'This changes the speed that the light turns off when controlled from the hub. ' + + "A setting of 'instant' turns the light immediately off. Increasing the value slows down the transition speed. " + + 'Every number represents 100ms. Default = 127 - Keep in sync with rampRateOffToOnRemote setting.', }, rampRateOnToOffLocal: { ID: 8, @@ -254,9 +259,9 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 127, description: - 'This changes the speed that the light turns off when controlled at the switch. ' + - 'A setting of \'instant\' turns the light immediately off. Increasing the value slows down the transition speed. ' + - 'Every number represents 100ms. Default = 127 - Keep in sync with rampRateOffToOnLocal setting.', + 'This changes the speed that the light turns off when controlled at the switch. ' + + "A setting of 'instant' turns the light immediately off. Increasing the value slows down the transition speed. " + + 'Every number represents 100ms. Default = 127 - Keep in sync with rampRateOffToOnLocal setting.', }, minimumLevel: { ID: 9, @@ -264,8 +269,8 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 1, max: 253, description: - 'The minimum level that the dimmer allows the bulb to be dimmed to. ' + - 'Useful when the user has an LED bulb that does not turn on or flickers at a lower level.', + 'The minimum level that the dimmer allows the bulb to be dimmed to. ' + + 'Useful when the user has an LED bulb that does not turn on or flickers at a lower level.', }, maximumLevel: { ID: 10, @@ -273,9 +278,9 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 2, max: 254, description: - 'The maximum level that the dimmer allows the bulb to be dimmed to.' + - 'Useful when the user has an LED bulb that reaches its maximum level before the ' + - 'dimmer value of 99 or when the user wants to limit the maximum brightness.', + 'The maximum level that the dimmer allows the bulb to be dimmed to.' + + 'Useful when the user has an LED bulb that reaches its maximum level before the ' + + 'dimmer value of 99 or when the user wants to limit the maximum brightness.', }, invertSwitch: { ID: 11, @@ -285,8 +290,8 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 1, description: - 'Inverts the orientation of the switch.' + - ' Useful when the switch is installed upside down. Essentially up becomes down and down becomes up.', + 'Inverts the orientation of the switch.' + + ' Useful when the switch is installed upside down. Essentially up becomes down and down becomes up.', }, autoTimerOff: { ID: 12, @@ -296,8 +301,8 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { unit: 'seconds', values: {Disabled: 0}, description: - 'Automatically turns the switch off after this many seconds.' + - ' When the switch is turned on a timer is started. When the timer expires, the switch is turned off. 0 = Auto off is disabled.', + 'Automatically turns the switch off after this many seconds.' + + ' When the switch is turned on a timer is started. When the timer expires, the switch is turned off. 0 = Auto off is disabled.', }, defaultLevelLocal: { ID: 13, @@ -305,8 +310,8 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 255, description: - 'Default level for the load when it is turned on at the switch.' + - ' A setting of 255 means that the switch will return to the level that it was on before it was turned off.', + 'Default level for the load when it is turned on at the switch.' + + ' A setting of 255 means that the switch will return to the level that it was on before it was turned off.', }, defaultLevelRemote: { ID: 14, @@ -314,23 +319,22 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 255, description: - 'Default level for the load when it is turned on from the hub.' + - ' A setting of 255 means that the switch will return to the level that it was on before it was turned off.', + 'Default level for the load when it is turned on from the hub.' + + ' A setting of 255 means that the switch will return to the level that it was on before it was turned off.', }, stateAfterPowerRestored: { ID: 15, dataType: Zcl.DataType.UINT8, min: 0, max: 255, - description: - 'The state the switch should return to when power is restored after power failure. 0 = off, 1-254 = level, 255 = previous.', + description: 'The state the switch should return to when power is restored after power failure. 0 = off, 1-254 = level, 255 = previous.', }, loadLevelIndicatorTimeout: { ID: 17, dataType: Zcl.DataType.UINT8, description: - 'Shows the level that the load is at for x number of seconds after the load is adjusted' + - ' and then returns to the Default LED state. 0 = Stay Off, 1-10 = seconds, 11 = Stay On.', + 'Shows the level that the load is at for x number of seconds after the load is adjusted' + + ' and then returns to the Default LED state. 0 = Stay Off, 1-10 = seconds, 11 = Stay On.', displayType: 'enum', values: { 'Stay Off': 0, @@ -353,7 +357,7 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { ID: 21, dataType: Zcl.DataType.BOOLEAN, displayType: 'enum', - values: {'Non Neutral': 0, 'Neutral': 1}, + values: {'Non Neutral': 0, Neutral: 1}, min: 0, max: 1, readOnly: true, @@ -380,7 +384,7 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { ID: 33, dataType: Zcl.DataType.BOOLEAN, displayType: 'enum', - values: {'No Alert': 0, 'Overheated': 1}, + values: {'No Alert': 0, Overheated: 1}, min: 0, max: 1, readOnly: true, @@ -404,9 +408,7 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { displayType: 'enum', min: 0, max: 9, - description: - 'This will set the button press delay. 0 = no delay (Disables Button Press Events),' + - 'Default = 500ms.', + description: 'This will set the button press delay. 0 = no delay (Disables Button Press Events),' + 'Default = 500ms.', }, deviceBindNumber: { ID: 51, @@ -418,22 +420,21 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { ID: 52, dataType: Zcl.DataType.BOOLEAN, displayType: 'enum', - values: {'Disabled': 0, 'Smart Bulb Mode': 1}, - description: - 'For use with Smart Bulbs that need constant power and are controlled via commands rather than power.', + values: {Disabled: 0, 'Smart Bulb Mode': 1}, + description: 'For use with Smart Bulbs that need constant power and are controlled via commands rather than power.', }, doubleTapUpToParam55: { ID: 53, dataType: Zcl.DataType.BOOLEAN, displayType: 'enum', - values: {'Disabled': 0, 'Enabled': 1}, + values: {Disabled: 0, Enabled: 1}, description: 'Enable or Disable setting level to parameter 55 on double-tap UP.', }, doubleTapDownToParam56: { ID: 54, dataType: Zcl.DataType.BOOLEAN, displayType: 'enum', - values: {'Disabled': 0, 'Enabled': 1}, + values: {Disabled: 0, Enabled: 1}, description: 'Enable or Disable setting level to parameter 56 on double-tap DOWN.', }, brightnessLevelForDoubleTapUp: { @@ -505,7 +506,8 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { dataType: Zcl.DataType.UINT8, displayType: 'enum', values: {'Old Behavior': 0, 'New Behavior': 1, 'Down Always Off': 2}, - description: 'Behavior of single tapping the on or off button. Old behavior turns the switch on or off. ' + + description: + 'Behavior of single tapping the on or off button. Old behavior turns the switch on or off. ' + 'New behavior cycles through the levels set by P131-133. Down Always Off is like the new behavior but ' + 'down always turns the switch off instead of going to next lower speed.', }, @@ -513,7 +515,7 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { ID: 130, dataType: Zcl.DataType.UINT8, displayType: 'enum', - values: {'Disabled': 0, 'Multi Tap': 1, 'Cycle': 2}, + values: {Disabled: 0, 'Multi Tap': 1, Cycle: 2}, description: 'Which mode to use when binding EP3 (config button) to another device (like a fan module).', }, lowLevelForFanControlMode: { @@ -559,14 +561,14 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { ID: 123, dataType: Zcl.DataType.BOOLEAN, displayType: 'enum', - values: {'Disabled': 0, 'Enabled': 1}, + values: {Disabled: 0, Enabled: 1}, description: 'Have unique scene numbers for scenes activated with the aux switch.', }, bindingOffToOnSyncLevel: { ID: 125, dataType: Zcl.DataType.BOOLEAN, displayType: 'enum', - values: {'Disabled': 0, 'Enabled': 1}, + values: {Disabled: 0, Enabled: 1}, description: 'Send Move_To_Level using Default Level with Off/On to bound devices.', }, localProtection: { @@ -590,8 +592,7 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { max: 1, values: {All: 0, One: 1}, dataType: Zcl.DataType.BOOLEAN, - description: - 'When the device is in On/Off mode, use full LED bar or just one LED.', + description: 'When the device is in On/Off mode, use full LED bar or just one LED.', displayType: 'enum', }, firmwareUpdateInProgressIndicator: { @@ -607,7 +608,7 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 255, description: - '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', + '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', }, defaultLed1ColorWhenOff: { ID: 61, @@ -615,23 +616,21 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 255, description: - '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', + '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', }, defaultLed1IntensityWhenOn: { ID: 62, dataType: Zcl.DataType.UINT8, min: 0, max: 101, - description: - 'Intesity of LED strip when on. 101 = Synchronized with default all LED strip intensity parameter.', + description: 'Intesity of LED strip when on. 101 = Synchronized with default all LED strip intensity parameter.', }, defaultLed1IntensityWhenOff: { ID: 63, dataType: Zcl.DataType.UINT8, min: 0, max: 101, - description: - 'Intesity of LED strip when off. 101 = Synchronized with default all LED strip intensity parameter.', + description: 'Intesity of LED strip when off. 101 = Synchronized with default all LED strip intensity parameter.', }, defaultLed2ColorWhenOn: { ID: 65, @@ -639,7 +638,7 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 255, description: - '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', + '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', }, defaultLed2ColorWhenOff: { ID: 66, @@ -647,23 +646,21 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 255, description: - '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', + '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', }, defaultLed2IntensityWhenOn: { ID: 67, dataType: Zcl.DataType.UINT8, min: 0, max: 101, - description: - 'Intesity of LED strip when on. 101 = Synchronized with default all LED strip intensity parameter.', + description: 'Intesity of LED strip when on. 101 = Synchronized with default all LED strip intensity parameter.', }, defaultLed2IntensityWhenOff: { ID: 68, dataType: Zcl.DataType.UINT8, min: 0, max: 101, - description: - 'Intesity of LED strip when off. 101 = Synchronized with default all LED strip intensity parameter.', + description: 'Intesity of LED strip when off. 101 = Synchronized with default all LED strip intensity parameter.', }, defaultLed3ColorWhenOn: { ID: 70, @@ -671,7 +668,7 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 255, description: - '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', + '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', }, defaultLed3ColorWhenOff: { ID: 71, @@ -679,23 +676,21 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 255, description: - '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', + '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', }, defaultLed3IntensityWhenOn: { ID: 72, dataType: Zcl.DataType.UINT8, min: 0, max: 101, - description: - 'Intesity of LED strip when on. 101 = Synchronized with default all LED strip intensity parameter.', + description: 'Intesity of LED strip when on. 101 = Synchronized with default all LED strip intensity parameter.', }, defaultLed3IntensityWhenOff: { ID: 73, dataType: Zcl.DataType.UINT8, min: 0, max: 101, - description: - 'Intesity of LED strip when off. 101 = Synchronized with default all LED strip intensity parameter.', + description: 'Intesity of LED strip when off. 101 = Synchronized with default all LED strip intensity parameter.', }, defaultLed4ColorWhenOn: { ID: 75, @@ -703,7 +698,7 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 255, description: - '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', + '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', }, defaultLed4ColorWhenOff: { ID: 76, @@ -711,23 +706,21 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 255, description: - '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', + '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', }, defaultLed4IntensityWhenOn: { ID: 77, dataType: Zcl.DataType.UINT8, min: 0, max: 101, - description: - 'Intesity of LED strip when on. 101 = Synchronized with default all LED strip intensity parameter.', + description: 'Intesity of LED strip when on. 101 = Synchronized with default all LED strip intensity parameter.', }, defaultLed4IntensityWhenOff: { ID: 78, dataType: Zcl.DataType.UINT8, min: 0, max: 101, - description: - 'Intesity of LED strip when off. 101 = Synchronized with default all LED strip intensity parameter.', + description: 'Intesity of LED strip when off. 101 = Synchronized with default all LED strip intensity parameter.', }, defaultLed5ColorWhenOn: { ID: 80, @@ -735,7 +728,7 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 255, description: - '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', + '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', }, defaultLed5ColorWhenOff: { ID: 81, @@ -743,23 +736,21 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 255, description: - '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', + '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', }, defaultLed5IntensityWhenOn: { ID: 82, dataType: Zcl.DataType.UINT8, min: 0, max: 101, - description: - 'Intesity of LED strip when on. 101 = Synchronized with default all LED strip intensity parameter.', + description: 'Intesity of LED strip when on. 101 = Synchronized with default all LED strip intensity parameter.', }, defaultLed5IntensityWhenOff: { ID: 83, dataType: Zcl.DataType.UINT8, min: 0, max: 101, - description: - 'Intesity of LED strip when off. 101 = Synchronized with default all LED strip intensity parameter.', + description: 'Intesity of LED strip when off. 101 = Synchronized with default all LED strip intensity parameter.', }, defaultLed6ColorWhenOn: { ID: 85, @@ -767,7 +758,7 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 255, description: - '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', + '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', }, defaultLed6ColorWhenOff: { ID: 86, @@ -775,23 +766,21 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 255, description: - '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', + '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', }, defaultLed6IntensityWhenOn: { ID: 87, dataType: Zcl.DataType.UINT8, min: 0, max: 101, - description: - 'Intesity of LED strip when on. 101 = Synchronized with default all LED strip intensity parameter.', + description: 'Intesity of LED strip when on. 101 = Synchronized with default all LED strip intensity parameter.', }, defaultLed6IntensityWhenOff: { ID: 88, dataType: Zcl.DataType.UINT8, min: 0, max: 101, - description: - 'Intesity of LED strip when off. 101 = Synchronized with default all LED strip intensity parameter.', + description: 'Intesity of LED strip when off. 101 = Synchronized with default all LED strip intensity parameter.', }, defaultLed7ColorWhenOn: { ID: 90, @@ -799,7 +788,7 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 255, description: - '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', + '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', }, defaultLed7ColorWhenOff: { ID: 91, @@ -807,29 +796,27 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 255, description: - '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', + '0-254:This is the color of the LED strip in a hex representation. 255:Synchronization with default all LED strip color parameter.', }, defaultLed7IntensityWhenOn: { ID: 92, dataType: Zcl.DataType.UINT8, min: 0, max: 101, - description: - 'Intesity of LED strip when on. 101 = Synchronized with default all LED strip intensity parameter.', + description: 'Intesity of LED strip when on. 101 = Synchronized with default all LED strip intensity parameter.', }, defaultLed7IntensityWhenOff: { ID: 93, dataType: Zcl.DataType.UINT8, min: 0, max: 101, - description: - 'Intesity of LED strip when off. 101 = Synchronized with default all LED strip intensity parameter.', + description: 'Intesity of LED strip when off. 101 = Synchronized with default all LED strip intensity parameter.', }, outputMode: { ID: 258, min: 0, max: 1, - values: {'Dimmer': 0, 'On/Off': 1}, + values: {Dimmer: 0, 'On/Off': 1}, dataType: Zcl.DataType.BOOLEAN, description: 'Use device as a Dimmer or an On/Off switch.', displayType: 'enum', @@ -840,7 +827,7 @@ const COMMON_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 1, description: 'Double-Tap the Config button to clear notifications.', - values: {'Enabled (Default)': 0, 'Disabled': 1}, + values: {'Enabled (Default)': 0, Disabled: 1}, displayType: 'enum', }, fanLedLevelType: { @@ -860,16 +847,14 @@ const VZM31_ATTRIBUTES: {[s: string]: Attribute} = { dataType: Zcl.DataType.UINT8, min: 0, max: 100, - description: - 'Percent power level change that will result in a new power report being sent. 0 = Disabled', + description: 'Percent power level change that will result in a new power report being sent. 0 = Disabled', }, periodicPowerAndEnergyReports: { ID: 19, min: 0, max: 32767, dataType: Zcl.DataType.UINT16, - description: - 'Time period between consecutive power & energy reports being sent (in seconds). The timer is reset after each report is sent.', + description: 'Time period between consecutive power & energy reports being sent (in seconds). The timer is reset after each report is sent.', }, activeEnergyReports: { ID: 20, @@ -877,8 +862,8 @@ const VZM31_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 32767, description: - 'Energy reports Energy level change which will result in sending a new energy report.' + - '0 = disabled, 1-32767 = 0.01kWh-327.67kWh. Default setting: 10 (0.1 kWh)', + 'Energy reports Energy level change which will result in sending a new energy report.' + + '0 = disabled, 1-32767 = 0.01kWh-327.67kWh. Default setting: 10 (0.1 kWh)', }, quickStartTime: { ID: 23, @@ -898,7 +883,7 @@ const VZM31_ATTRIBUTES: {[s: string]: Attribute} = { ID: 25, dataType: Zcl.DataType.BOOLEAN, displayType: 'enum', - values: {'Disabled (default)': 0, 'Enabled': 1}, + values: {'Disabled (default)': 0, Enabled: 1}, min: 0, max: 1, description: 'Increase level in non-neutral mode', @@ -916,21 +901,22 @@ const VZM31_ATTRIBUTES: {[s: string]: Attribute} = { min: 0, max: 1, description: - 'In neutral on/off setups, the default is to have a clicking sound to notify you that the relay ' + - 'is open or closed. You may disable this sound by creating a, “simulated” on/off where the switch ' + - 'only will turn onto 100 or off to 0.', + 'In neutral on/off setups, the default is to have a clicking sound to notify you that the relay ' + + 'is open or closed. You may disable this sound by creating a, “simulated” on/off where the switch ' + + 'only will turn onto 100 or off to 0.', values: {'Disabled (Click Sound On)': 0, 'Enabled (Click Sound Off)': 1}, displayType: 'enum', }, }; -const VZM35_ATTRIBUTES : {[s: string]: Attribute} = { +const VZM35_ATTRIBUTES: {[s: string]: Attribute} = { ...COMMON_ATTRIBUTES, minimumLevel: { ...COMMON_ATTRIBUTES.minimumLevel, - description: '1-84: The level corresponding to the fan is Low, Medium, High. ' + - '85-170: The level corresponding to the fan is Medium, Medium, High. '+ - '170-254: The level corresponding to the fan is High, High, High ', + description: + '1-84: The level corresponding to the fan is Low, Medium, High. ' + + '85-170: The level corresponding to the fan is Medium, Medium, High. ' + + '170-254: The level corresponding to the fan is High, High, High ', }, maximumLevel: { ...COMMON_ATTRIBUTES.maximumLevel, @@ -944,7 +930,7 @@ const VZM35_ATTRIBUTES : {[s: string]: Attribute} = { smartBulbMode: { ...COMMON_ATTRIBUTES.smartBulbMode, description: 'For use with Smart Fans that need constant power and are controlled via commands rather than power.', - values: {'Disabled': 0, 'Smart Fan Mode': 1}, + values: {Disabled: 0, 'Smart Fan Mode': 1}, }, quickStartTime: { ID: 23, @@ -976,12 +962,12 @@ const VZM35_ATTRIBUTES : {[s: string]: Attribute} = { ID: 121, dataType: Zcl.DataType.BOOLEAN, displayType: 'enum', - values: {'Disabled': 0, 'Enabled': 1}, + values: {Disabled: 0, Enabled: 1}, description: 'Enable or disable advanced timer mode to have the switch act like a bathroom fan timer', }, }; -const VZM36_ATTRIBUTES : {[s: string]: Attribute} = { +const VZM36_ATTRIBUTES: {[s: string]: Attribute} = { dimmingSpeedUpRemote_1: {...COMMON_ATTRIBUTES.dimmingSpeedUpRemote}, rampRateOffToOnRemote_1: {...COMMON_ATTRIBUTES.rampRateOffToOnRemote}, dimmingSpeedDownRemote_1: {...COMMON_ATTRIBUTES.dimmingSpeedDownRemote}, @@ -991,25 +977,24 @@ const VZM36_ATTRIBUTES : {[s: string]: Attribute} = { autoTimerOff_1: { ...COMMON_ATTRIBUTES.autoTimerOff, description: - 'Automatically turns the light off after this many seconds.' + - ' When the light is turned on a timer is started. When the timer expires, the light is turned off. 0 = Auto off is disabled.', + 'Automatically turns the light off after this many seconds.' + + ' When the light is turned on a timer is started. When the timer expires, the light is turned off. 0 = Auto off is disabled.', }, defaultLevelRemote_1: { ...COMMON_ATTRIBUTES.defaultLevelRemote, description: - 'Default level for the light when it is turned on from the hub.' + - ' A setting of 255 means that the light will return to the level that it was on before it was turned off.', + 'Default level for the light when it is turned on from the hub.' + + ' A setting of 255 means that the light will return to the level that it was on before it was turned off.', }, stateAfterPowerRestored_1: { ...COMMON_ATTRIBUTES.stateAfterPowerRestored, - description: - 'The state the light should return to when power is restored after power failure. 0 = off, 1-254 = level, 255 = previous.', + description: 'The state the light should return to when power is restored after power failure. 0 = off, 1-254 = level, 255 = previous.', }, higherOutputInNonNeutral_1: { ID: 25, dataType: Zcl.DataType.BOOLEAN, displayType: 'enum', - values: {'Disabled (default)': 0, 'Enabled': 1}, + values: {'Disabled (default)': 0, Enabled: 1}, min: 0, max: 1, description: 'Increase level in non-neutral mode for light.', @@ -1035,10 +1020,11 @@ const VZM36_ATTRIBUTES : {[s: string]: Attribute} = { values: {'Leading Edge': 0, 'Trailing Edge': 1}, min: 0, max: 1, - description: 'Leading Edge has a value of 0 and is the default value, whereas Trailing Edge has a value of ' + - '1. Please note that Trailing Edge is only available on neutral single-pole and neutral multi-way with an ' + - 'aux/add-on switch (multi-way with a dumb/existing switch and non-neutral setups are not supported and ' + - 'will default back to Leading Edge).', + description: + 'Leading Edge has a value of 0 and is the default value, whereas Trailing Edge has a value of ' + + '1. Please note that Trailing Edge is only available on neutral single-pole and neutral multi-way with an ' + + 'aux/add-on switch (multi-way with a dumb/existing switch and non-neutral setups are not supported and ' + + 'will default back to Leading Edge).', }, smartBulbMode_1: {...COMMON_ATTRIBUTES.smartBulbMode}, ledColorWhenOn_1: {...COMMON_ATTRIBUTES.ledColorWhenOn}, @@ -1049,30 +1035,30 @@ const VZM36_ATTRIBUTES : {[s: string]: Attribute} = { dimmingSpeedUpRemote_2: { ...COMMON_ATTRIBUTES.dimmingSpeedUpRemote, description: - 'This changes the speed that the fan ramps up when controlled from the hub. ' + - 'A setting of 0 turns the fan immediately on. Increasing the value slows down the transition speed. ' + - 'Every number represents 100ms. Default = 25 (2.5s)', + 'This changes the speed that the fan ramps up when controlled from the hub. ' + + 'A setting of 0 turns the fan immediately on. Increasing the value slows down the transition speed. ' + + 'Every number represents 100ms. Default = 25 (2.5s)', }, rampRateOffToOnRemote_2: { ...COMMON_ATTRIBUTES.rampRateOffToOnRemote, description: - 'This changes the speed that the fan turns on when controlled from the hub. ' + - 'A setting of 0 turns the fan immediately on. Increasing the value slows down the transition speed. ' + - 'Every number represents 100ms. Default = 127 - Keep in sync with dimmingSpeedUpRemote setting.', + 'This changes the speed that the fan turns on when controlled from the hub. ' + + 'A setting of 0 turns the fan immediately on. Increasing the value slows down the transition speed. ' + + 'Every number represents 100ms. Default = 127 - Keep in sync with dimmingSpeedUpRemote setting.', }, dimmingSpeedDownRemote_2: { ...COMMON_ATTRIBUTES.dimmingSpeedDownRemote, description: - 'This changes the speed that the fan ramps down when controlled from the hub. ' + - 'A setting of 0 turns the fan immediately off. Increasing the value slows down the transition speed. ' + - 'Every number represents 100ms. Default = 127 - Keep in sync with dimmingSpeedUpRemote setting.', + 'This changes the speed that the fan ramps down when controlled from the hub. ' + + 'A setting of 0 turns the fan immediately off. Increasing the value slows down the transition speed. ' + + 'Every number represents 100ms. Default = 127 - Keep in sync with dimmingSpeedUpRemote setting.', }, rampRateOnToOffRemote_2: { ...COMMON_ATTRIBUTES.rampRateOnToOffRemote, description: - 'This changes the speed that the fan turns off when controlled from the hub. ' + - 'A setting of \'instant\' turns the fan immediately off. Increasing the value slows down the transition speed. ' + - 'Every number represents 100ms. Default = 127 - Keep in sync with rampRateOffToOnRemote setting.', + 'This changes the speed that the fan turns off when controlled from the hub. ' + + "A setting of 'instant' turns the fan immediately off. Increasing the value slows down the transition speed. " + + 'Every number represents 100ms. Default = 127 - Keep in sync with rampRateOffToOnRemote setting.', }, minimumLevel_2: { ...COMMON_ATTRIBUTES.minimumLevel, @@ -1085,19 +1071,18 @@ const VZM36_ATTRIBUTES : {[s: string]: Attribute} = { autoTimerOff_2: { ...COMMON_ATTRIBUTES.autoTimerOff, description: - 'Automatically turns the fan off after this many seconds.' + - ' When the fan is turned on a timer is started. When the timer expires, the switch is turned off. 0 = Auto off is disabled.', + 'Automatically turns the fan off after this many seconds.' + + ' When the fan is turned on a timer is started. When the timer expires, the switch is turned off. 0 = Auto off is disabled.', }, defaultLevelRemote_2: { ...COMMON_ATTRIBUTES.defaultLevelRemote, description: - 'Default level for the fan when it is turned on from the hub.' + - ' A setting of 255 means that the fan will return to the level that it was on before it was turned off.', + 'Default level for the fan when it is turned on from the hub.' + + ' A setting of 255 means that the fan will return to the level that it was on before it was turned off.', }, stateAfterPowerRestored_2: { ...COMMON_ATTRIBUTES.stateAfterPowerRestored, - description: - 'The state the fan should return to when power is restored after power failure. 0 = off, 1-254 = level, 255 = previous.', + description: 'The state the fan should return to when power is restored after power failure. 0 = off, 1-254 = level, 255 = previous.', }, quickStartTime_2: { ID: 23, @@ -1111,7 +1096,7 @@ const VZM36_ATTRIBUTES : {[s: string]: Attribute} = { // overheat readonly smartBulbMode_2: { ...COMMON_ATTRIBUTES.smartBulbMode, - values: {'Disabled': 0, 'Smart Fan Mode': 1}, + values: {Disabled: 0, 'Smart Fan Mode': 1}, description: 'For use with Smart Fans that need constant power and are controlled via commands rather than power.', }, // remote protection readonly.. @@ -1123,71 +1108,73 @@ const VZM36_ATTRIBUTES : {[s: string]: Attribute} = { }; const tzLocal = { - inovelli_parameters: (ATTRIBUTES: {[s: string]: Attribute})=>({ - key: Object.keys(ATTRIBUTES).filter((a) => !ATTRIBUTES[a].readOnly), - convertSet: async (entity, key, value, meta) => { - // Check key to see if there is an endpoint postfix for the VZM36 - const keysplit = key.split('_'); - let entityToUse = entity; - if (keysplit.length === 2) { - entityToUse = meta.device.getEndpoint(Number(keysplit[1])); - } + inovelli_parameters: (ATTRIBUTES: {[s: string]: Attribute}) => + ({ + key: Object.keys(ATTRIBUTES).filter((a) => !ATTRIBUTES[a].readOnly), + convertSet: async (entity, key, value, meta) => { + // Check key to see if there is an endpoint postfix for the VZM36 + const keysplit = key.split('_'); + let entityToUse = entity; + if (keysplit.length === 2) { + entityToUse = meta.device.getEndpoint(Number(keysplit[1])); + } - if (!(key in ATTRIBUTES)) { - return; - } + if (!(key in ATTRIBUTES)) { + return; + } - const payload = { - [ATTRIBUTES[key].ID]: { - value: - ATTRIBUTES[key].displayType === 'enum' ? - // @ts-expect-error - ATTRIBUTES[key].values[value] : - value, - type: ATTRIBUTES[key].dataType, - }, - }; + const payload = { + [ATTRIBUTES[key].ID]: { + value: + ATTRIBUTES[key].displayType === 'enum' + ? // @ts-expect-error + ATTRIBUTES[key].values[value] + : value, + type: ATTRIBUTES[key].dataType, + }, + }; - await entityToUse.write('manuSpecificInovelli', payload, { - manufacturerCode: INOVELLI, - }); + await entityToUse.write('manuSpecificInovelli', payload, { + manufacturerCode: INOVELLI, + }); - return { - state: { - [key]: value, - }, - }; - }, - convertGet: async (entity, key, meta) => { - // Check key to see if there is an endpoint postfix for the VZM36 - const keysplit = key.split('_'); - let entityToUse = entity; - let keyToUse = key; - if (keysplit.length === 2) { - entityToUse = meta.device.getEndpoint(Number(keysplit[1])); - keyToUse = keysplit[0]; - } - await entityToUse.read('manuSpecificInovelli', [keyToUse], { - manufacturerCode: INOVELLI, - }); - }, - }) satisfies Tz.Converter, - inovelli_parameters_readOnly: (ATTRIBUTES: {[s: string]: Attribute})=>({ - key: Object.keys(ATTRIBUTES).filter((a) => ATTRIBUTES[a].readOnly), - convertGet: async (entity, key, meta) => { - // Check key to see if there is an endpoint postfix for the VZM36 - const keysplit = key.split('_'); - let entityToUse = entity; - let keyToUse = key; - if (keysplit.length === 2) { - entityToUse = meta.device.getEndpoint(Number(keysplit[1])); - keyToUse = keysplit[0]; - } - await entityToUse.read('manuSpecificInovelli', [keyToUse], { - manufacturerCode: INOVELLI, - }); - }, - }) satisfies Tz.Converter, + return { + state: { + [key]: value, + }, + }; + }, + convertGet: async (entity, key, meta) => { + // Check key to see if there is an endpoint postfix for the VZM36 + const keysplit = key.split('_'); + let entityToUse = entity; + let keyToUse = key; + if (keysplit.length === 2) { + entityToUse = meta.device.getEndpoint(Number(keysplit[1])); + keyToUse = keysplit[0]; + } + await entityToUse.read('manuSpecificInovelli', [keyToUse], { + manufacturerCode: INOVELLI, + }); + }, + }) satisfies Tz.Converter, + inovelli_parameters_readOnly: (ATTRIBUTES: {[s: string]: Attribute}) => + ({ + key: Object.keys(ATTRIBUTES).filter((a) => ATTRIBUTES[a].readOnly), + convertGet: async (entity, key, meta) => { + // Check key to see if there is an endpoint postfix for the VZM36 + const keysplit = key.split('_'); + let entityToUse = entity; + let keyToUse = key; + if (keysplit.length === 2) { + entityToUse = meta.device.getEndpoint(Number(keysplit[1])); + keyToUse = keysplit[0]; + } + await entityToUse.read('manuSpecificInovelli', [keyToUse], { + manufacturerCode: INOVELLI, + }); + }, + }) satisfies Tz.Converter, inovelli_led_effect: { key: ['led_effect'], convertSet: async (entity, key, values, meta) => { @@ -1241,68 +1228,34 @@ const tzLocal = { convertSet: async (entity, key, value, meta) => { const {message} = meta; const transition = utils.getTransition(entity, 'brightness', meta); - const turnsOffAtBrightness1 = utils.getMetaValue( - entity, - meta.mapped, - 'turnsOffAtBrightness1', - 'allEqual', - false, - ); - let state = message.hasOwnProperty('state') ? - // @ts-expect-error - message.state.toLowerCase() : - undefined; + const turnsOffAtBrightness1 = utils.getMetaValue(entity, meta.mapped, 'turnsOffAtBrightness1', 'allEqual', false); + let state = message.hasOwnProperty('state') + ? // @ts-expect-error + message.state.toLowerCase() + : undefined; let brightness = undefined; if (message.hasOwnProperty('brightness')) { brightness = Number(message.brightness); } else if (message.hasOwnProperty('brightness_percent')) { - brightness = utils.mapNumberRange( - Number(message.brightness_percent), - 0, - 100, - 0, - 255, - ); + brightness = utils.mapNumberRange(Number(message.brightness_percent), 0, 100, 0, 255); } - if ( - brightness !== undefined && - (isNaN(brightness) || brightness < 0 || brightness > 255) - ) { + if (brightness !== undefined && (isNaN(brightness) || brightness < 0 || brightness > 255)) { // Allow 255 value, changing this to 254 would be a breaking change. - throw new Error( - `Brightness value of message: '${JSON.stringify( - message, - )}' invalid, must be a number >= 0 and =< 254`, - ); + throw new Error(`Brightness value of message: '${JSON.stringify(message)}' invalid, must be a number >= 0 and =< 254`); } - if ( - state !== undefined && - ['on', 'off', 'toggle'].includes(state) === false - ) { - throw new Error( - `State value of message: '${JSON.stringify( - message, - )}' invalid, must be 'ON', 'OFF' or 'TOGGLE'`, - ); + if (state !== undefined && ['on', 'off', 'toggle'].includes(state) === false) { + throw new Error(`State value of message: '${JSON.stringify(message)}' invalid, must be 'ON', 'OFF' or 'TOGGLE'`); } - if ( - state === 'toggle' || - state === 'off' || - (brightness === undefined && state === 'on') - ) { + if (state === 'toggle' || state === 'off' || (brightness === undefined && state === 'on')) { if (transition.specified && transition.time > 0) { if (state === 'toggle') { state = meta.state.state === 'ON' ? 'off' : 'on'; } - if ( - state === 'off' && - meta.state.brightness && - meta.state.state === 'ON' - ) { + if (state === 'off' && meta.state.brightness && meta.state.state === 'ON') { // https://github.com/Koenkk/zigbee2mqtt/issues/2850#issuecomment-580365633 // We need to remember the state before turning the device off as we need to restore // it once we turn it on again. @@ -1312,26 +1265,14 @@ const tzLocal = { globalStore.putValue(entity, 'turnedOffWithTransition', true); } - const fallbackLevel = utils.getObjectProperty( - meta.state, - 'brightness', - 254, - ); - let level = - state === 'off' ? - 0 : - globalStore.getValue(entity, 'brightness', fallbackLevel); + const fallbackLevel = utils.getObjectProperty(meta.state, 'brightness', 254); + let level = state === 'off' ? 0 : globalStore.getValue(entity, 'brightness', fallbackLevel); if (state === 'on' && level === 0) { level = turnsOffAtBrightness1 ? 2 : 1; } const payload = {level, transtime: transition.time}; - await entity.command( - 'genLevelCtrl', - 'moveToLevelWithOnOff', - payload, - utils.getOptions(meta.mapped, entity), - ); + await entity.command('genLevelCtrl', 'moveToLevelWithOnOff', payload, utils.getOptions(meta.mapped, entity)); const result = {state: {state: state.toUpperCase()}}; // @ts-expect-error if (state === 'on') result.state.brightness = level; @@ -1344,19 +1285,10 @@ const tzLocal = { globalStore.putValue(entity, 'turnedOffWithTransition', true); } - const result = await inovelliOnOffConvertSet( - entity, - 'state', - state, - meta, - ); + const result = await inovelliOnOffConvertSet(entity, 'state', state, meta); // @ts-expect-error result.readAfterWriteTime = 0; - if ( - result.state && - result.state.state === 'ON' && - meta.state.brightness === 0 - ) { + if (result.state && result.state.state === 'ON' && meta.state.brightness === 0) { // @ts-expect-error result.state.brightness = 1; } @@ -1380,10 +1312,7 @@ const tzLocal = { utils.getOptions(meta.mapped, entity), ); - const defaultTransitionTime = await entity.read( - 'manuSpecificInovelli', - ['rampRateOnToOffRemote'], - ); + const defaultTransitionTime = await entity.read('manuSpecificInovelli', ['rampRateOnToOffRemote']); return { state: { @@ -1391,10 +1320,10 @@ const tzLocal = { brightness: Number(brightness), }, readAfterWriteTime: - transition.time === 0 ? - // @ts-expect-error - defaultTransitionTime.rampRateOnToOffRemote * 100 : - transition.time * 100, // need on speed + transition.time === 0 + ? // @ts-expect-error + defaultTransitionTime.rampRateOnToOffRemote * 100 + : transition.time * 100, // need on speed }; } }, @@ -1408,7 +1337,7 @@ const tzLocal = { } satisfies Tz.Converter, fan_mode: { key: ['fan_mode'], - convertSet: async (entity, key, value :string, meta) => { + convertSet: async (entity, key, value: string, meta) => { await entity.command( 'genLevelCtrl', 'moveToLevelWithOnOff', @@ -1432,25 +1361,13 @@ const tzLocal = { fan_state: { key: ['fan_state'], convertSet: async (entity, key, value, meta) => { - const state = meta.message.hasOwnProperty('fan_state')? - meta.message.fan_state.toString().toLowerCase() : - null; + const state = meta.message.hasOwnProperty('fan_state') ? meta.message.fan_state.toString().toLowerCase() : null; utils.validateValue(state, ['toggle', 'off', 'on']); - await entity.command( - 'genOnOff', - state, - {}, - utils.getOptions(meta.mapped, entity), - ); + await entity.command('genOnOff', state, {}, utils.getOptions(meta.mapped, entity)); if (state === 'toggle') { - const currentState = - meta.state[ - `state${meta.endpoint_name ? `_${meta.endpoint_name}` : ''}` - ]; - return currentState ? - {state: {fan_state: currentState === 'OFF' ? 'ON' : 'OFF'}}: - {}; + const currentState = meta.state[`state${meta.endpoint_name ? `_${meta.endpoint_name}` : ''}`]; + return currentState ? {state: {fan_state: currentState === 'OFF' ? 'ON' : 'OFF'}} : {}; } else { return {state: {fan_state: state.toString().toUpperCase()}}; } @@ -1533,44 +1450,44 @@ const tzLocal = { vzm36_breezeMode: { key: ['breezeMode'], convertSet: async (entity, key, values: BreezeModeValues, meta) => { - // Calculate the value.. + // Calculate the value.. let configValue = 0; let term = false; configValue += speedToInt(values.speed1); - configValue += Number(values.time1) / 5 * 4; + configValue += (Number(values.time1) / 5) * 4; let speed = speedToInt(values.speed2); if (speed !== 0) { configValue += speed * 64; - configValue += values.time2 / 5 * 256; + configValue += (values.time2 / 5) * 256; } else { term = true; } speed = speedToInt(values.speed3); - if (speed !== 0 && ! term) { + if (speed !== 0 && !term) { configValue += speed * 4096; - configValue += values.time3 / 5 * 16384; + configValue += (values.time3 / 5) * 16384; } else { term = true; } speed = speedToInt(values.speed4); - if (speed !== 0 && ! term) { + if (speed !== 0 && !term) { configValue += speed * 262144; - configValue += values.time4 / 5 * 1048576; + configValue += (values.time4 / 5) * 1048576; } else { term = true; } speed = speedToInt(values.speed5); - if (speed !== 0 && ! term) { + if (speed !== 0 && !term) { configValue += speed * 16777216; - configValue += values.time5 / 5 * 67108864; + configValue += (values.time5 / 5) * 67108864; } else { term = true; } @@ -1588,44 +1505,44 @@ const tzLocal = { breezeMode: { key: ['breezeMode'], convertSet: async (entity, key, values: BreezeModeValues, meta) => { - // Calculate the value.. + // Calculate the value.. let configValue = 0; let term = false; configValue += speedToInt(values.speed1); - configValue += Number(values.time1) / 5 * 4; + configValue += (Number(values.time1) / 5) * 4; let speed = speedToInt(values.speed2); if (speed !== 0) { configValue += speed * 64; - configValue += values.time2 / 5 * 256; + configValue += (values.time2 / 5) * 256; } else { term = true; } speed = speedToInt(values.speed3); - if (speed !== 0 && ! term) { + if (speed !== 0 && !term) { configValue += speed * 4096; - configValue += values.time3 / 5 * 16384; + configValue += (values.time3 / 5) * 16384; } else { term = true; } speed = speedToInt(values.speed4); - if (speed !== 0 && ! term) { + if (speed !== 0 && !term) { configValue += speed * 262144; - configValue += values.time4 / 5 * 1048576; + configValue += (values.time4 / 5) * 1048576; } else { term = true; } speed = speedToInt(values.speed5); - if (speed !== 0 && ! term) { + if (speed !== 0 && !term) { configValue += speed * 16777216; - configValue += values.time5 / 5 * 67108864; + configValue += (values.time5 / 5) * 67108864; } else { term = true; } @@ -1651,17 +1568,9 @@ const inovelliOnOffConvertSet = async (entity: Zh.Endpoint | Zh.Group, key: stri const state = meta.message.hasOwnProperty('state') ? meta.message.state.toLowerCase() : null; utils.validateValue(state, ['toggle', 'off', 'on']); - if ( - state === 'on' && - (meta.message.hasOwnProperty('on_time') || - meta.message.hasOwnProperty('off_wait_time')) - ) { - const onTime = meta.message.hasOwnProperty('on_time') ? - meta.message.on_time : - 0; - const offWaitTime = meta.message.hasOwnProperty('off_wait_time') ? - meta.message.off_wait_time : - 0; + if (state === 'on' && (meta.message.hasOwnProperty('on_time') || meta.message.hasOwnProperty('off_wait_time'))) { + const onTime = meta.message.hasOwnProperty('on_time') ? meta.message.on_time : 0; + const offWaitTime = meta.message.hasOwnProperty('off_wait_time') ? meta.message.off_wait_time : 0; if (typeof onTime !== 'number') { throw Error('The on_time value must be a number!'); @@ -1672,34 +1581,15 @@ const inovelliOnOffConvertSet = async (entity: Zh.Endpoint | Zh.Group, key: stri const payload = { ctrlbits: 0, - ontime: meta.message.hasOwnProperty('on_time') ? - Math.round(onTime * 10) : - 0xffff, - offwaittime: meta.message.hasOwnProperty('off_wait_time') ? - Math.round(offWaitTime * 10) : - 0xffff, + ontime: meta.message.hasOwnProperty('on_time') ? Math.round(onTime * 10) : 0xffff, + offwaittime: meta.message.hasOwnProperty('off_wait_time') ? Math.round(offWaitTime * 10) : 0xffff, }; - await entity.command( - 'genOnOff', - 'onWithTimedOff', - payload, - utils.getOptions(meta.mapped, entity), - ); + await entity.command('genOnOff', 'onWithTimedOff', payload, utils.getOptions(meta.mapped, entity)); } else { - await entity.command( - 'genOnOff', - state, - {}, - utils.getOptions(meta.mapped, entity), - ); + await entity.command('genOnOff', state, {}, utils.getOptions(meta.mapped, entity)); if (state === 'toggle') { - const currentState = - meta.state[ - `state${meta.endpoint_name ? `_${meta.endpoint_name}` : ''}` - ]; - return currentState ? - {state: {state: currentState === 'OFF' ? 'ON' : 'OFF'}} : - {}; + const currentState = meta.state[`state${meta.endpoint_name ? `_${meta.endpoint_name}` : ''}`]; + return currentState ? {state: {state: currentState === 'OFF' ? 'ON' : 'OFF'}} : {}; } else { return {state: {state: state.toUpperCase()}}; } @@ -1707,46 +1597,45 @@ const inovelliOnOffConvertSet = async (entity: Zh.Endpoint | Zh.Group, key: stri }; const fzLocal = { - inovelli: (ATTRIBUTES: {[s: string]: Attribute})=>({ - cluster: 'manuSpecificInovelli', - type: ['raw', 'readResponse', 'commandQueryNextImageRequest'], - convert: (model, msg, publish, options, meta) => { - if (msg.type === 'raw' && msg.endpoint.ID == 2 && msg.data[4] === 0x00) { - // Scene Event - // # byte 1 - msg.data[6] - // # 0 - pressed - // # 1 - released - // # 2 - held - // # 3 - 2x - // # 4 - 3x - // # 5 - 4x - // # 6 - 5x - - // # byte 2 - msg.data[5] - // # 1 - down button - // # 2 - up button - // # 3 - config button - - const button = buttonLookup[msg.data[5]]; - const action = clickLookup[msg.data[6]]; - return {action: `${button}_${action}`}; - } else if (msg.type === 'readResponse') { - return Object.keys(msg.data).reduce((p, c) => { - if (ATTRIBUTES[c] && ATTRIBUTES[c].displayType === 'enum') { - return { - ...p, - [c]: Object.keys(ATTRIBUTES[c].values).find( - (k) => ATTRIBUTES[c].values[k] === msg.data[c], - ), - }; - } - return {...p, [c]: msg.data[c]}; - }, {}); - } else { - return msg.data; - } - }, - }) satisfies Fz.Converter, + inovelli: (ATTRIBUTES: {[s: string]: Attribute}) => + ({ + cluster: 'manuSpecificInovelli', + type: ['raw', 'readResponse', 'commandQueryNextImageRequest'], + convert: (model, msg, publish, options, meta) => { + if (msg.type === 'raw' && msg.endpoint.ID == 2 && msg.data[4] === 0x00) { + // Scene Event + // # byte 1 - msg.data[6] + // # 0 - pressed + // # 1 - released + // # 2 - held + // # 3 - 2x + // # 4 - 3x + // # 5 - 4x + // # 6 - 5x + + // # byte 2 - msg.data[5] + // # 1 - down button + // # 2 - up button + // # 3 - config button + + const button = buttonLookup[msg.data[5]]; + const action = clickLookup[msg.data[6]]; + return {action: `${button}_${action}`}; + } else if (msg.type === 'readResponse') { + return Object.keys(msg.data).reduce((p, c) => { + if (ATTRIBUTES[c] && ATTRIBUTES[c].displayType === 'enum') { + return { + ...p, + [c]: Object.keys(ATTRIBUTES[c].values).find((k) => ATTRIBUTES[c].values[k] === msg.data[c]), + }; + } + return {...p, [c]: msg.data[c]}; + }, {}); + } else { + return msg.data; + } + }, + }) satisfies Fz.Converter, fan_mode: { cluster: 'genLevelCtrl', type: ['attributeReport', 'readResponse'], @@ -1822,11 +1711,11 @@ const fzLocal = { const s4 = breezemodes[(raw & bitmasks[6]) / 262144]; const s5 = breezemodes[(raw & bitmasks[8]) / 16777216]; - const d1 = (raw & bitmasks[1]) / 4 * 5; - const d2 = (raw & bitmasks[3]) / 256 * 5; - const d3 = (raw & bitmasks[5]) / 16384 * 5; - const d4 = (raw & bitmasks[7]) / 1048576 * 5; - const d5 = (raw & bitmasks[9]) / 67108864 * 5; + const d1 = ((raw & bitmasks[1]) / 4) * 5; + const d2 = ((raw & bitmasks[3]) / 256) * 5; + const d3 = ((raw & bitmasks[5]) / 16384) * 5; + const d4 = ((raw & bitmasks[7]) / 1048576) * 5; + const d5 = ((raw & bitmasks[9]) / 67108864) * 5; return { breeze_mode: { @@ -1903,17 +1792,9 @@ const exposesList: Expose[] = [ .numeric('color', ea.STATE_SET) .withValueMin(0) .withValueMax(255) - .withDescription( - 'Calculated by using a hue color circle(value/255*360) If color = 255 display white', - ), - ) - .withFeature( - e - .numeric('level', ea.STATE_SET) - .withValueMin(0) - .withValueMax(100) - .withDescription('Brightness of the LEDs'), + .withDescription('Calculated by using a hue color circle(value/255*360) If color = 255 display white'), ) + .withFeature(e.numeric('level', ea.STATE_SET).withValueMin(0).withValueMax(100).withDescription('Brightness of the LEDs')) .withFeature( e .numeric('duration', ea.STATE_SET) @@ -1921,17 +1802,13 @@ const exposesList: Expose[] = [ .withValueMax(255) .withDescription( '1-60 is in seconds calculated 61-120 is in minutes calculated by(value-60) ' + - 'Example a value of 65 would be 65-60 = 5 minutes - 120-254 Is in hours calculated by(value-120) ' + - 'Example a value of 132 would be 132-120 would be 12 hours. - 255 Indefinitely', + 'Example a value of 65 would be 65-60 = 5 minutes - 120-254 Is in hours calculated by(value-120) ' + + 'Example a value of 132 would be 132-120 would be 12 hours. - 255 Indefinitely', ), ), e .composite('individual_led_effect', 'individual_led_effect', ea.STATE_SET) - .withFeature( - e - .enum('led', ea.STATE_SET, ['1', '2', '3', '4', '5', '6', '7']) - .withDescription('Individual LED to target.'), - ) + .withFeature(e.enum('led', ea.STATE_SET, ['1', '2', '3', '4', '5', '6', '7']).withDescription('Individual LED to target.')) .withFeature( e .enum('effect', ea.STATE_SET, [ @@ -1953,17 +1830,9 @@ const exposesList: Expose[] = [ .numeric('color', ea.STATE_SET) .withValueMin(0) .withValueMax(255) - .withDescription( - 'Calculated by using a hue color circle(value/255*360) If color = 255 display white', - ), - ) - .withFeature( - e - .numeric('level', ea.STATE_SET) - .withValueMin(0) - .withValueMax(100) - .withDescription('Brightness of the LED'), + .withDescription('Calculated by using a hue color circle(value/255*360) If color = 255 display white'), ) + .withFeature(e.numeric('level', ea.STATE_SET).withValueMin(0).withValueMax(100).withDescription('Brightness of the LED')) .withFeature( e .numeric('duration', ea.STATE_SET) @@ -1971,8 +1840,8 @@ const exposesList: Expose[] = [ .withValueMax(255) .withDescription( '1-60 is in seconds calculated 61-120 is in minutes calculated by(value-60) ' + - 'Example a value of 65 would be 65-60 = 5 minutes - 120-254 Is in hours calculated by(value-120) ' + - ' Example a value of 132 would be 132-120 would be 12 hours. - 255 Indefinitely', + 'Example a value of 65 would be 65-60 = 5 minutes - 120-254 Is in hours calculated by(value-120) ' + + ' Example a value of 132 would be 132-120 would be 12 hours. - 255 Indefinitely', ), ), ]; @@ -2013,17 +1882,9 @@ const exposesListVZM35: Expose[] = [ .numeric('color', ea.STATE_SET) .withValueMin(0) .withValueMax(255) - .withDescription( - 'Calculated by using a hue color circle(value/255*360) If color = 255 display white', - ), - ) - .withFeature( - e - .numeric('level', ea.STATE_SET) - .withValueMin(0) - .withValueMax(100) - .withDescription('Brightness of the LEDs'), + .withDescription('Calculated by using a hue color circle(value/255*360) If color = 255 display white'), ) + .withFeature(e.numeric('level', ea.STATE_SET).withValueMin(0).withValueMax(100).withDescription('Brightness of the LEDs')) .withFeature( e .numeric('duration', ea.STATE_SET) @@ -2031,17 +1892,13 @@ const exposesListVZM35: Expose[] = [ .withValueMax(255) .withDescription( '1-60 is in seconds calculated 61-120 is in minutes calculated by(value-60) ' + - 'Example a value of 65 would be 65-60 = 5 minutes - 120-254 Is in hours calculated by(value-120) ' + - 'Example a value of 132 would be 132-120 would be 12 hours. - 255 Indefinitely', + 'Example a value of 65 would be 65-60 = 5 minutes - 120-254 Is in hours calculated by(value-120) ' + + 'Example a value of 132 would be 132-120 would be 12 hours. - 255 Indefinitely', ), ), e .composite('individual_led_effect', 'individual_led_effect', ea.STATE_SET) - .withFeature( - e - .enum('led', ea.STATE_SET, ['1', '2', '3', '4', '5', '6', '7']) - .withDescription('Individual LED to target.'), - ) + .withFeature(e.enum('led', ea.STATE_SET, ['1', '2', '3', '4', '5', '6', '7']).withDescription('Individual LED to target.')) .withFeature( e .enum('effect', ea.STATE_SET, [ @@ -2063,17 +1920,9 @@ const exposesListVZM35: Expose[] = [ .numeric('color', ea.STATE_SET) .withValueMin(0) .withValueMax(255) - .withDescription( - 'Calculated by using a hue color circle(value/255*360) If color = 255 display white', - ), - ) - .withFeature( - e - .numeric('level', ea.STATE_SET) - .withValueMin(0) - .withValueMax(100) - .withDescription('Brightness of the LED'), + .withDescription('Calculated by using a hue color circle(value/255*360) If color = 255 display white'), ) + .withFeature(e.numeric('level', ea.STATE_SET).withValueMin(0).withValueMax(100).withDescription('Brightness of the LED')) .withFeature( e .numeric('duration', ea.STATE_SET) @@ -2081,72 +1930,22 @@ const exposesListVZM35: Expose[] = [ .withValueMax(255) .withDescription( '1-60 is in seconds calculated 61-120 is in minutes calculated by(value-60) ' + - 'Example a value of 65 would be 65-60 = 5 minutes - 120-254 Is in hours calculated by(value-120) ' + - ' Example a value of 132 would be 132-120 would be 12 hours. - 255 Indefinitely', + 'Example a value of 65 would be 65-60 = 5 minutes - 120-254 Is in hours calculated by(value-120) ' + + ' Example a value of 132 would be 132-120 would be 12 hours. - 255 Indefinitely', ), ), e .composite('breeze mode', 'breezeMode', ea.STATE_SET) - .withFeature( - e - .enum('speed1', ea.STATE_SET, ['low', 'medium', 'high']) - .withDescription('Step 1 Speed'), - ) - .withFeature( - e - .numeric('time1', ea.STATE_SET) - .withValueMin(1) - .withValueMax(80) - .withDescription('Duration (s) for fan in Step 1 '), - ) - .withFeature( - e - .enum('speed2', ea.STATE_SET, ['low', 'medium', 'high']) - .withDescription('Step 2 Speed'), - ) - .withFeature( - e - .numeric('time2', ea.STATE_SET) - .withValueMin(1) - .withValueMax(80) - .withDescription('Duration (s) for fan in Step 2 '), - ) - .withFeature( - e - .enum('speed3', ea.STATE_SET, ['low', 'medium', 'high']) - .withDescription('Step 3 Speed'), - ) - .withFeature( - e - .numeric('time3', ea.STATE_SET) - .withValueMin(1) - .withValueMax(80) - .withDescription('Duration (s) for fan in Step 3 '), - ) - .withFeature( - e - .enum('speed4', ea.STATE_SET, ['low', 'medium', 'high']) - .withDescription('Step 4 Speed'), - ) - .withFeature( - e - .numeric('time4', ea.STATE_SET) - .withValueMin(1) - .withValueMax(80) - .withDescription('Duration (s) for fan in Step 4 '), - ) - .withFeature( - e - .enum('speed5', ea.STATE_SET, ['low', 'medium', 'high']) - .withDescription('Step 5 Speed'), - ) - .withFeature( - e - .numeric('time5', ea.STATE_SET) - .withValueMin(1) - .withValueMax(80) - .withDescription('Duration (s) for fan in Step 5 '), - ), + .withFeature(e.enum('speed1', ea.STATE_SET, ['low', 'medium', 'high']).withDescription('Step 1 Speed')) + .withFeature(e.numeric('time1', ea.STATE_SET).withValueMin(1).withValueMax(80).withDescription('Duration (s) for fan in Step 1 ')) + .withFeature(e.enum('speed2', ea.STATE_SET, ['low', 'medium', 'high']).withDescription('Step 2 Speed')) + .withFeature(e.numeric('time2', ea.STATE_SET).withValueMin(1).withValueMax(80).withDescription('Duration (s) for fan in Step 2 ')) + .withFeature(e.enum('speed3', ea.STATE_SET, ['low', 'medium', 'high']).withDescription('Step 3 Speed')) + .withFeature(e.numeric('time3', ea.STATE_SET).withValueMin(1).withValueMax(80).withDescription('Duration (s) for fan in Step 3 ')) + .withFeature(e.enum('speed4', ea.STATE_SET, ['low', 'medium', 'high']).withDescription('Step 4 Speed')) + .withFeature(e.numeric('time4', ea.STATE_SET).withValueMin(1).withValueMax(80).withDescription('Duration (s) for fan in Step 4 ')) + .withFeature(e.enum('speed5', ea.STATE_SET, ['low', 'medium', 'high']).withDescription('Step 5 Speed')) + .withFeature(e.numeric('time5', ea.STATE_SET).withValueMin(1).withValueMax(80).withDescription('Duration (s) for fan in Step 5 ')), ]; const exposesListVZM36: Expose[] = [ @@ -2154,97 +1953,18 @@ const exposesListVZM36: Expose[] = [ e.fan().withModes(Object.keys(fanModes)), // Breezee - e.composite('breeze mode', 'breezeMode', ea.STATE_SET) - .withFeature( - e - .enum('speed1', ea.STATE_SET, [ - 'low', - 'medium', - 'high', - ]) - .withDescription('Step 1 Speed'), - ) - .withFeature( - e - .numeric('time1', ea.STATE_SET) - .withValueMin(1) - .withValueMax(80) - .withDescription( - 'Duration (s) for fan in Step 1 ', - ), - ) - .withFeature( - e - .enum('speed2', ea.STATE_SET, [ - 'low', - 'medium', - 'high', - ]) - .withDescription('Step 2 Speed'), - ) - .withFeature( - e - .numeric('time2', ea.STATE_SET) - .withValueMin(1) - .withValueMax(80) - .withDescription( - 'Duration (s) for fan in Step 2 ', - ), - ) - .withFeature( - e - .enum('speed3', ea.STATE_SET, [ - 'low', - 'medium', - 'high', - ]) - .withDescription('Step 3 Speed'), - ) - .withFeature( - e - .numeric('time3', ea.STATE_SET) - .withValueMin(1) - .withValueMax(80) - .withDescription( - 'Duration (s) for fan in Step 3 ', - ), - ) - .withFeature( - e - .enum('speed4', ea.STATE_SET, [ - 'low', - 'medium', - 'high', - ]) - .withDescription('Step 4 Speed'), - ) - .withFeature( - e - .numeric('time4', ea.STATE_SET) - .withValueMin(1) - .withValueMax(80) - .withDescription( - 'Duration (s) for fan in Step 4 ', - ), - ) - .withFeature( - e - .enum('speed5', ea.STATE_SET, [ - 'low', - 'medium', - 'high', - ]) - .withDescription('Step 5 Speed'), - ) - .withFeature( - e - .numeric('time5', ea.STATE_SET) - .withValueMin(1) - .withValueMax(80) - .withDescription( - 'Duration (s) for fan in Step 5 ', - ), - ), + e + .composite('breeze mode', 'breezeMode', ea.STATE_SET) + .withFeature(e.enum('speed1', ea.STATE_SET, ['low', 'medium', 'high']).withDescription('Step 1 Speed')) + .withFeature(e.numeric('time1', ea.STATE_SET).withValueMin(1).withValueMax(80).withDescription('Duration (s) for fan in Step 1 ')) + .withFeature(e.enum('speed2', ea.STATE_SET, ['low', 'medium', 'high']).withDescription('Step 2 Speed')) + .withFeature(e.numeric('time2', ea.STATE_SET).withValueMin(1).withValueMax(80).withDescription('Duration (s) for fan in Step 2 ')) + .withFeature(e.enum('speed3', ea.STATE_SET, ['low', 'medium', 'high']).withDescription('Step 3 Speed')) + .withFeature(e.numeric('time3', ea.STATE_SET).withValueMin(1).withValueMax(80).withDescription('Duration (s) for fan in Step 3 ')) + .withFeature(e.enum('speed4', ea.STATE_SET, ['low', 'medium', 'high']).withDescription('Step 4 Speed')) + .withFeature(e.numeric('time4', ea.STATE_SET).withValueMin(1).withValueMax(80).withDescription('Duration (s) for fan in Step 4 ')) + .withFeature(e.enum('speed5', ea.STATE_SET, ['low', 'medium', 'high']).withDescription('Step 5 Speed')) + .withFeature(e.numeric('time5', ea.STATE_SET).withValueMin(1).withValueMax(80).withDescription('Duration (s) for fan in Step 5 ')), ]; // Populate exposes list from the attributes description @@ -2334,23 +2054,13 @@ const definitions: Definition[] = [ ota: ota.inovelli, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); - await reporting.bind(endpoint, coordinatorEndpoint, [ - 'seMetering', - 'haElectricalMeasurement', - 'genOnOff', - 'genLevelCtrl', - ]); + await reporting.bind(endpoint, coordinatorEndpoint, ['seMetering', 'haElectricalMeasurement', 'genOnOff', 'genLevelCtrl']); await reporting.onOff(endpoint); // Bind for Button Event Reporting const endpoint2 = device.getEndpoint(2); - await reporting.bind(endpoint2, coordinatorEndpoint, [ - 'manuSpecificInovelli', - ]); - await endpoint.read('haElectricalMeasurement', [ - 'acPowerMultiplier', - 'acPowerDivisor', - ]); + await reporting.bind(endpoint2, coordinatorEndpoint, ['manuSpecificInovelli']); + await endpoint.read('haElectricalMeasurement', ['acPowerMultiplier', 'acPowerDivisor']); await reporting.readMeteringMultiplierDivisor(endpoint); await reporting.activePower(endpoint, {min: 15, max: 3600, change: 1}); @@ -2366,12 +2076,7 @@ const definitions: Definition[] = [ model: 'VZM35-SN', vendor: 'Inovelli', description: 'Fan controller', - fromZigbee: [ - fzLocal.fan_state, - fzLocal.fan_mode, - fzLocal.breeze_mode, - fzLocal.inovelli(VZM35_ATTRIBUTES), - ], + fromZigbee: [fzLocal.fan_state, fzLocal.fan_mode, fzLocal.breeze_mode, fzLocal.inovelli(VZM35_ATTRIBUTES)], toZigbee: [ tzLocal.fan_state, tzLocal.fan_mode, @@ -2385,15 +2090,10 @@ const definitions: Definition[] = [ ota: ota.inovelli, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); - await reporting.bind(endpoint, coordinatorEndpoint, [ - 'genOnOff', - 'genLevelCtrl', - ]); + await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl']); // Bind for Button Event Reporting const endpoint2 = device.getEndpoint(2); - await reporting.bind(endpoint2, coordinatorEndpoint, [ - 'manuSpecificInovelli', - ]); + await reporting.bind(endpoint2, coordinatorEndpoint, ['manuSpecificInovelli']); }, }, { diff --git a/src/devices/insta.ts b/src/devices/insta.ts index fc75328f4d72e..b8792098cb861 100644 --- a/src/devices/insta.ts +++ b/src/devices/insta.ts @@ -1,10 +1,10 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; import * as ota from '../lib/ota'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; import * as utils from '../lib/utils'; const e = exposes.presets; @@ -14,10 +14,22 @@ const definitions: Definition[] = [ model: 'InstaRemote', vendor: 'Insta', description: 'ZigBee Light Link wall/handheld transmitter', - whiteLabel: [{vendor: 'Gira', model: '2430-100'}, {vendor: 'Gira', model: '2435-10'}, {vendor: 'Jung', model: 'ZLLCD5004M'}, - {vendor: 'Jung', model: 'ZLLLS5004M'}, {vendor: 'Jung', model: 'ZLLA5004M'}, {vendor: 'Jung', model: 'ZLLHS4'}], - fromZigbee: [legacy.fz.insta_scene_click, fz.command_on, fz.command_off_with_effect, legacy.fz.insta_down_hold, - legacy.fz.insta_up_hold, legacy.fz.insta_stop], + whiteLabel: [ + {vendor: 'Gira', model: '2430-100'}, + {vendor: 'Gira', model: '2435-10'}, + {vendor: 'Jung', model: 'ZLLCD5004M'}, + {vendor: 'Jung', model: 'ZLLLS5004M'}, + {vendor: 'Jung', model: 'ZLLA5004M'}, + {vendor: 'Jung', model: 'ZLLHS4'}, + ], + fromZigbee: [ + legacy.fz.insta_scene_click, + fz.command_on, + fz.command_off_with_effect, + legacy.fz.insta_down_hold, + legacy.fz.insta_up_hold, + legacy.fz.insta_stop, + ], exposes: [e.action(['select_0', 'select_1', 'select_2', 'select_3', 'select_4', 'select_5', 'on', 'off', 'down', 'up', 'stop'])], toZigbee: [], ota: ota.zigbeeOTA, @@ -31,7 +43,7 @@ const definitions: Definition[] = [ toZigbee: [tz.cover_state, tz.cover_position_tilt], exposes: [e.cover_position_tilt()], endpoint: (device) => { - return {'default': 6}; + return {default: 6}; }, configure: async (device, coordinatorEndpoint) => { await utils.sleep(10000); // https://github.com/Koenkk/zigbee-herdsman-converters/issues/2493 @@ -50,11 +62,15 @@ const definitions: Definition[] = [ fingerprint: [ // It seems several Insta devices use the same ModelID with a different endpoint configuration // This is the single "Switching Actuator Mini" - {manufacturerName: 'Insta GmbH', modelID: 'Generic UP Device', endpoints: [ - {ID: 1, profileID: 260, deviceID: 256, inputClusters: [0, 3, 4, 5, 6, 4096], outputClusters: [25]}, - {ID: 4, profileID: 260, deviceID: 261, inputClusters: [0, 3], outputClusters: [3, 4, 5, 6, 8, 25, 768]}, - {ID: 242, profileID: 41440, deviceID: 97}, - ]}, + { + manufacturerName: 'Insta GmbH', + modelID: 'Generic UP Device', + endpoints: [ + {ID: 1, profileID: 260, deviceID: 256, inputClusters: [0, 3, 4, 5, 6, 4096], outputClusters: [25]}, + {ID: 4, profileID: 260, deviceID: 261, inputClusters: [0, 3], outputClusters: [3, 4, 5, 6, 8, 25, 768]}, + {ID: 242, profileID: 41440, deviceID: 97}, + ], + }, ], zigbeeModel: ['NEXENTRO Switching Actuator', '57005000'], model: '57005000', @@ -79,25 +95,52 @@ const definitions: Definition[] = [ fingerprint: [ // It seems several Insta devices use the same ModelID with a different endpoint configuration // This is the "Pushbutton Interface 2-gang" - {manufacturerName: 'Insta GmbH', modelID: 'Generic UP Device', endpoints: [ - {ID: 4, profileID: 260, deviceID: 261, inputClusters: [0, 3], outputClusters: [3, 4, 5, 6, 8, 25, 768]}, - {ID: 5, profileID: 260, deviceID: 261, inputClusters: [0, 3], outputClusters: [3, 4, 5, 6, 8, 25, 768]}, - {ID: 7, profileID: 260, deviceID: 515, inputClusters: [0, 3], outputClusters: [3, 4, 25, 258]}, - {ID: 242, profileID: 41440, deviceID: 97}, - ]}, + { + manufacturerName: 'Insta GmbH', + modelID: 'Generic UP Device', + endpoints: [ + {ID: 4, profileID: 260, deviceID: 261, inputClusters: [0, 3], outputClusters: [3, 4, 5, 6, 8, 25, 768]}, + {ID: 5, profileID: 260, deviceID: 261, inputClusters: [0, 3], outputClusters: [3, 4, 5, 6, 8, 25, 768]}, + {ID: 7, profileID: 260, deviceID: 515, inputClusters: [0, 3], outputClusters: [3, 4, 25, 258]}, + {ID: 242, profileID: 41440, deviceID: 97}, + ], + }, ], zigbeeModel: ['NEXENTRO Pushbutton Interface', '57004000'], model: '57004000', vendor: 'Insta', description: 'Pushbutton Interface 2-gang 230V', - fromZigbee: [fz.command_on, fz.command_off, fz.command_toggle, fz.command_recall, fz.command_move, fz.command_stop, - fz.command_cover_open, fz.command_cover_close, fz.command_cover_stop], + fromZigbee: [ + fz.command_on, + fz.command_off, + fz.command_toggle, + fz.command_recall, + fz.command_move, + fz.command_stop, + fz.command_cover_open, + fz.command_cover_close, + fz.command_cover_stop, + ], toZigbee: [], - exposes: [e.action([ - 'on_e1', 'off_e1', 'toggle_e1', 'recall_*_e1', 'brightness_stop_e1', 'brightness_move_*_e1', - 'on_e2', 'off_e2', 'toggle_e2', 'recall_*_e2', 'brightness_stop_e2', 'brightness_move_*_e2', - 'close_cover', 'open_cover', 'stop_cover', - ])], + exposes: [ + e.action([ + 'on_e1', + 'off_e1', + 'toggle_e1', + 'recall_*_e1', + 'brightness_stop_e1', + 'brightness_move_*_e1', + 'on_e2', + 'off_e2', + 'toggle_e2', + 'recall_*_e2', + 'brightness_stop_e2', + 'brightness_move_*_e2', + 'close_cover', + 'open_cover', + 'stop_cover', + ]), + ], configure: async (device, coordinatorEndpoint) => { // Has Unknown power source, force it here. device.powerSource = 'Mains (single phase)'; @@ -106,7 +149,9 @@ const definitions: Definition[] = [ meta: {multiEndpoint: true}, endpoint: (device) => { return { - 'e1': 4, 'e2': 5, 'cover': 7, + e1: 4, + e2: 5, + cover: 7, }; }, }, diff --git a/src/devices/iolloi.ts b/src/devices/iolloi.ts index 54d8417a8c7c4..fd22dc6ca5d34 100644 --- a/src/devices/iolloi.ts +++ b/src/devices/iolloi.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/iotperfect.ts b/src/devices/iotperfect.ts index bfd07e54991e5..68b276d13a198 100644 --- a/src/devices/iotperfect.ts +++ b/src/devices/iotperfect.ts @@ -1,16 +1,18 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; const definitions: Definition[] = [ { - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_vrjkcam9'}, + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE200_vrjkcam9'}, {modelID: 'TS0601', manufacturerName: '_TZE200_d0ypnbvn'}, {modelID: 'TS0601', manufacturerName: '_TZE204_v5xjyphj'}, - {modelID: 'TS0601', manufacturerName: '_TZE204_d0ypnbvn'}], + {modelID: 'TS0601', manufacturerName: '_TZE204_d0ypnbvn'}, + ], model: 'PF-PM02D-TYZ', vendor: 'IOTPerfect', description: 'Smart water/gas valve', diff --git a/src/devices/iris.ts b/src/devices/iris.ts index 7b3b7e2635c5b..74505a443a035 100644 --- a/src/devices/iris.ts +++ b/src/devices/iris.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; import {forcePowerSource, onOff} from '../lib/modernExtend'; @@ -84,15 +84,14 @@ const definitions: Definition[] = [ description: 'Smart fob', fromZigbee: [fz.command_on_presence, fz.command_off, fz.battery, fz.checkin_presence], toZigbee: [], - exposes: [e.action(['on_1', 'off_1', 'on_2', 'off_2', 'on_3', 'off_3', 'on_4', 'off_4']), - e.battery(), e.presence()], + exposes: [e.action(['on_1', 'off_1', 'on_2', 'off_2', 'on_3', 'off_3', 'on_4', 'off_4']), e.battery(), e.presence()], meta: {battery: {voltageToPercentage: '3V_2100'}, multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { const endpoint1 = device.getEndpoint(1); await reporting.bind(endpoint1, coordinatorEndpoint, ['genOnOff', 'genPowerCfg', 'genPollCtrl']); await reporting.batteryVoltage(endpoint1); const interval = 100 - 10; // 100 seconds is default timeout so set interval to 10 seconds before - await endpoint1.write('genPollCtrl', {'checkinInterval': (interval * 4)}); + await endpoint1.write('genPollCtrl', {checkinInterval: interval * 4}); const endpoint2 = device.getEndpoint(2); await reporting.bind(endpoint2, coordinatorEndpoint, ['genOnOff']); const endpoint3 = device.getEndpoint(3); diff --git a/src/devices/istar.ts b/src/devices/istar.ts index 7e82463dc33cd..ec7d3ef357507 100644 --- a/src/devices/istar.ts +++ b/src/devices/istar.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/jasco.ts b/src/devices/jasco.ts index 9a6804ba46905..ad151c73b59f3 100644 --- a/src/devices/jasco.ts +++ b/src/devices/jasco.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; import fz from '../converters/fromZigbee'; import {electricityMeter, light, onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/javis.ts b/src/devices/javis.ts index 46a0c56f4bc22..5409dbd39548f 100644 --- a/src/devices/javis.ts +++ b/src/devices/javis.ts @@ -1,15 +1,17 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; const definitions: Definition[] = [ { zigbeeModel: ['JAVISLOCK'], - fingerprint: [{modelID: 'doorlock_5001', manufacturerName: 'Lmiot'}, - {modelID: 'E321V000A03', manufacturerName: 'Vensi'}], + fingerprint: [ + {modelID: 'doorlock_5001', manufacturerName: 'Lmiot'}, + {modelID: 'E321V000A03', manufacturerName: 'Vensi'}, + ], model: 'JS-SLK2-ZB', vendor: 'JAVIS', description: 'Intelligent biometric digital lock', @@ -19,21 +21,26 @@ const definitions: Definition[] = [ }, { zigbeeModel: ['JAVISSENSOR'], - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_lgstepha'}, + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE200_lgstepha'}, {modelID: 'TS0601', manufacturerName: '_TZE200_kagkgk0i'}, - {modelID: 'TS0601', manufacturerName: '_TZE200_i0b1dbqu'}], + {modelID: 'TS0601', manufacturerName: '_TZE200_i0b1dbqu'}, + ], model: 'JS-MC-SENSOR-ZB', vendor: 'JAVIS', description: 'Microwave sensor', fromZigbee: [legacy.fz.javis_microwave_sensor, fz.ignore_basic_report], toZigbee: [legacy.tz.javis_microwave_sensor], - exposes: [e.occupancy(), e.illuminance_lux(), + exposes: [ + e.occupancy(), + e.illuminance_lux(), e.binary('led_enable', ea.STATE_SET, true, false).withDescription('Enabled LED'), - e.enum('keep_time', ea.STATE_SET, ['0', '1', '2', '3', '4', '5', '6', '7']) + e + .enum('keep_time', ea.STATE_SET, ['0', '1', '2', '3', '4', '5', '6', '7']) .withDescription('PIR keep time 0:5s|1:30s|2:60s|3:180s|4:300s|5:600s|6:1200s|7:1800s'), e.enum('sensitivity', ea.STATE_SET, ['25', '50', '75', '100']), - e.numeric('illuminance_calibration', ea.STATE_SET).withDescription('Illuminance calibration') - .withValueMin(-10000).withValueMax(10000)], + e.numeric('illuminance_calibration', ea.STATE_SET).withDescription('Illuminance calibration').withValueMin(-10000).withValueMax(10000), + ], }, ]; diff --git a/src/devices/jethome.ts b/src/devices/jethome.ts index 932133f04c2db..a17f8f878033e 100644 --- a/src/devices/jethome.ts +++ b/src/devices/jethome.ts @@ -1,9 +1,9 @@ -import {Definition, Fz} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; +import * as ota from '../lib/ota'; import * as reporting from '../lib/reporting'; +import {Definition, Fz} from '../lib/types'; import * as utils from '../lib/utils'; -import * as ota from '../lib/ota'; const e = exposes.presets; @@ -13,9 +13,28 @@ const jetHome = { cluster: 'genMultistateInput', type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { - const actionLookup = {0: 'release', 1: 'single', 2: 'double', 3: 'triple', 4: 'hold', 256: 'release', 257: 'single', 258: 'double', - 259: 'triple', 260: 'hold', 512: 'release', 513: 'single', 514: 'double', 515: 'triple', 516: 'hold', 1024: 'release', - 1025: 'single', 1026: 'double', 1027: 'triple', 1028: 'hold'}; + const actionLookup = { + 0: 'release', + 1: 'single', + 2: 'double', + 3: 'triple', + 4: 'hold', + 256: 'release', + 257: 'single', + 258: 'double', + 259: 'triple', + 260: 'hold', + 512: 'release', + 513: 'single', + 514: 'double', + 515: 'triple', + 516: 'hold', + 1024: 'release', + 1025: 'single', + 1026: 'double', + 1027: 'triple', + 1028: 'hold', + }; const value = msg.data['presentValue']; const action = utils.getFromLookup(value, actionLookup); return {action: utils.postfixWithEndpointName(action, msg, model, meta)}; @@ -34,10 +53,23 @@ const definitions: Definition[] = [ toZigbee: [], ota: ota.jethome, exposes: [ - e.battery(), e.battery_voltage(), e.action( - ['release_in1', 'single_in1', 'double_in1', 'hold_in1', - 'release_in2', 'single_in2', 'double_in2', 'hold_in2', - 'release_in3', 'single_in3', 'double_in3', 'hold_in3'])], + e.battery(), + e.battery_voltage(), + e.action([ + 'release_in1', + 'single_in1', + 'double_in1', + 'hold_in1', + 'release_in2', + 'single_in2', + 'double_in2', + 'hold_in2', + 'release_in3', + 'single_in3', + 'double_in3', + 'hold_in3', + ]), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']); @@ -47,7 +79,9 @@ const definitions: Definition[] = [ meta: {multiEndpoint: true}, endpoint: (device) => { return { - 'in1': 1, 'in2': 2, 'in3': 3, + in1: 1, + in2: 2, + in3: 3, }; }, }, diff --git a/src/devices/jiawen.ts b/src/devices/jiawen.ts index 6bcc5913f9fb9..a7787aa5f4a0a 100644 --- a/src/devices/jiawen.ts +++ b/src/devices/jiawen.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/jumitech.ts b/src/devices/jumitech.ts index 84517443b8c99..0901d42c55911 100644 --- a/src/devices/jumitech.ts +++ b/src/devices/jumitech.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/jxuan.ts b/src/devices/jxuan.ts index b2f19789c1cd4..b2399aa394cee 100644 --- a/src/devices/jxuan.ts +++ b/src/devices/jxuan.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; import tz from '../converters/toZigbee'; import * as exposes from '../lib/exposes'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; import fz from '../converters/fromZigbee'; diff --git a/src/devices/kami.ts b/src/devices/kami.ts index 076f435b665d8..3a1d859f9caf8 100644 --- a/src/devices/kami.ts +++ b/src/devices/kami.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/keen_home.ts b/src/devices/keen_home.ts index b58b7afa1d1c5..9d6d425d7319d 100644 --- a/src/devices/keen_home.ts +++ b/src/devices/keen_home.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; import * as lumi from '../lib/lumi'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -24,14 +24,28 @@ const definitions: Definition[] = [ }, }, { - zigbeeModel: ['SV01-410-MP-1.0', 'SV01-410-MP-1.1', 'SV01-410-MP-1.4', 'SV01-410-MP-1.5', 'SV01-412-MP-1.0', 'SV01-412-MP-1.1', - 'SV01-412-MP-1.3', 'SV01-412-MP-1.4', 'SV01-610-MP-1.0', 'SV01-610-MP-1.1', 'SV01-612-MP-1.0', 'SV01-612-MP-1.1', 'SV01-612-MP-1.2', - 'SV01-610-MP-1.4', 'SV01-612-MP-1.4', 'SV01-612-EP-1.4'], + zigbeeModel: [ + 'SV01-410-MP-1.0', + 'SV01-410-MP-1.1', + 'SV01-410-MP-1.4', + 'SV01-410-MP-1.5', + 'SV01-412-MP-1.0', + 'SV01-412-MP-1.1', + 'SV01-412-MP-1.3', + 'SV01-412-MP-1.4', + 'SV01-610-MP-1.0', + 'SV01-610-MP-1.1', + 'SV01-612-MP-1.0', + 'SV01-612-MP-1.1', + 'SV01-612-MP-1.2', + 'SV01-610-MP-1.4', + 'SV01-612-MP-1.4', + 'SV01-612-EP-1.4', + ], model: 'SV01', vendor: 'Keen Home', description: 'Smart vent', - fromZigbee: [fz.cover_position_via_brightness, fz.temperature, fz.battery, fz.keen_home_smart_vent_pressure, - fz.ignore_onoff_report], + fromZigbee: [fz.cover_position_via_brightness, fz.temperature, fz.battery, fz.keen_home_smart_vent_pressure, fz.ignore_onoff_report], toZigbee: [tz.cover_via_brightness], meta: {battery: {dontDividePercentage: true}}, configure: async (device, coordinatorEndpoint) => { @@ -45,13 +59,21 @@ const definitions: Definition[] = [ exposes: [e.cover_position().setAccess('state', ea.ALL), e.temperature(), e.battery(), e.pressure()], }, { - zigbeeModel: ['SV02-410-MP-1.3', 'SV02-412-MP-1.3', 'SV02-610-MP-1.0', 'SV02-610-MP-1.3', 'SV02-612-MP-1.2', 'SV02-612-MP-1.3', - 'SV02-410-MP-1.0', 'SV02-410-MP-1.2', 'SV02-412-MP-1.2'], + zigbeeModel: [ + 'SV02-410-MP-1.3', + 'SV02-412-MP-1.3', + 'SV02-610-MP-1.0', + 'SV02-610-MP-1.3', + 'SV02-612-MP-1.2', + 'SV02-612-MP-1.3', + 'SV02-410-MP-1.0', + 'SV02-410-MP-1.2', + 'SV02-412-MP-1.2', + ], model: 'SV02', vendor: 'Keen Home', description: 'Smart vent', - fromZigbee: [fz.cover_position_via_brightness, fz.temperature, fz.battery, fz.keen_home_smart_vent_pressure, - fz.ignore_onoff_report], + fromZigbee: [fz.cover_position_via_brightness, fz.temperature, fz.battery, fz.keen_home_smart_vent_pressure, fz.ignore_onoff_report], toZigbee: [tz.cover_via_brightness], meta: {battery: {dontDividePercentage: true}}, configure: async (device, coordinatorEndpoint) => { diff --git a/src/devices/klikaanklikuit.ts b/src/devices/klikaanklikuit.ts index f554b4f6d4586..b7882eb6db5d9 100644 --- a/src/devices/klikaanklikuit.ts +++ b/src/devices/klikaanklikuit.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/kmpcil.ts b/src/devices/kmpcil.ts index 86cb332b98701..e71f13066b8f9 100644 --- a/src/devices/kmpcil.ts +++ b/src/devices/kmpcil.ts @@ -1,23 +1,28 @@ import {Zcl} from 'zigbee-herdsman'; -import {Definition, Fz, KeyValue, Publish} from '../lib/types'; -import * as exposes from '../lib/exposes'; + import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; import * as constants from '../lib/constants'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; import * as globalStore from '../lib/store'; +import {Definition, Fz, KeyValue, Publish} from '../lib/types'; import * as utils from '../lib/utils'; const e = exposes.presets; const ea = exposes.access; -const kmpcilOptions={ +const kmpcilOptions = { presence_timeout_dc: () => { - return e.numeric('presence_timeout_dc', ea.STATE).withValueMin(60).withDescription( - 'Time in seconds after which presence is cleared after detecting it (default 60 seconds) while in DC.'); + return e + .numeric('presence_timeout_dc', ea.STATE) + .withValueMin(60) + .withDescription('Time in seconds after which presence is cleared after detecting it (default 60 seconds) while in DC.'); }, presence_timeout_battery: () => { - return e.numeric('presence_timeout_battery', ea.STATE).withValueMin(120).withDescription( - 'Time in seconds after which presence is cleared after detecting it (default 420 seconds) while in Battery.'); + return e + .numeric('presence_timeout_battery', ea.STATE) + .withValueMin(120) + .withDescription('Time in seconds after which presence is cleared after detecting it (default 420 seconds) while in Battery.'); }, }; @@ -28,7 +33,7 @@ function handleKmpcilPresence(model: Definition, msg: Fz.Message, publish: Publi const useOptionsTimeoutDc = options && options.hasOwnProperty('presence_timeout_dc'); const timeoutDc = useOptionsTimeoutDc ? options.presence_timeout_dc : 60; - const mode = meta.state? meta.state['power_state'] : false; + const mode = meta.state ? meta.state['power_state'] : false; const timeout = Number(mode ? timeoutDc : timeoutBattery); // Stop existing timer because motion is detected and set a new one. @@ -47,7 +52,7 @@ const kmpcilConverters = { const payload = handleKmpcilPresence(model, msg, publish, options, meta); if (msg.data.hasOwnProperty('presentValue')) { const presentValue = msg.data['presentValue']; - payload.power_state = (presentValue & 0x01)> 0; + payload.power_state = (presentValue & 0x01) > 0; payload.occupancy = (presentValue & 0x04) > 0; payload.vibration = (presentValue & 0x02) > 0; } @@ -78,39 +83,77 @@ const definitions: Definition[] = [ model: 'KMPCIL_RES005', vendor: 'KMPCIL', description: 'Environment sensor', - exposes: [e.battery(), e.temperature(), e.humidity(), e.pressure(), e.illuminance().withAccess(ea.STATE_GET), - e.illuminance_lux().withAccess(ea.STATE_GET), e.occupancy(), e.switch()], - fromZigbee: [fz.battery, fz.temperature, fz.humidity, fz.pressure, fz.illuminance, fz.kmpcil_res005_occupancy, - fz.kmpcil_res005_on_off], + exposes: [ + e.battery(), + e.temperature(), + e.humidity(), + e.pressure(), + e.illuminance().withAccess(ea.STATE_GET), + e.illuminance_lux().withAccess(ea.STATE_GET), + e.occupancy(), + e.switch(), + ], + fromZigbee: [fz.battery, fz.temperature, fz.humidity, fz.pressure, fz.illuminance, fz.kmpcil_res005_occupancy, fz.kmpcil_res005_on_off], toZigbee: [tz.kmpcil_res005_on_off, tz.illuminance], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(8); - const binds = ['genPowerCfg', 'msTemperatureMeasurement', 'msRelativeHumidity', 'msPressureMeasurement', - 'msIlluminanceMeasurement', 'genBinaryInput', 'genBinaryOutput']; + const binds = [ + 'genPowerCfg', + 'msTemperatureMeasurement', + 'msRelativeHumidity', + 'msPressureMeasurement', + 'msIlluminanceMeasurement', + 'genBinaryInput', + 'genBinaryOutput', + ]; await reporting.bind(endpoint, coordinatorEndpoint, binds); await reporting.temperature(endpoint); await reporting.humidity(endpoint); - const payloadBattery = [{ - attribute: 'batteryPercentageRemaining', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}]; + const payloadBattery = [ + { + attribute: 'batteryPercentageRemaining', + minimumReportInterval: 1, + maximumReportInterval: 120, + reportableChange: 1, + }, + ]; await endpoint.configureReporting('genPowerCfg', payloadBattery); - const payload = [{attribute: 'measuredValue', minimumReportInterval: 5, maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 200}]; + const payload = [ + {attribute: 'measuredValue', minimumReportInterval: 5, maximumReportInterval: constants.repInterval.HOUR, reportableChange: 200}, + ]; await endpoint.configureReporting('msIlluminanceMeasurement', payload); - const payloadPressure = [{ - // 0 = measuredValue, override dataType from int16 to uint16 - // https://github.com/Koenkk/zigbee-herdsman/pull/191/files?file-filters%5B%5D=.ts#r456569398 - attribute: {ID: 0, type: Zcl.DataType.UINT16}, minimumReportInterval: 2, maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 3}]; + const payloadPressure = [ + { + // 0 = measuredValue, override dataType from int16 to uint16 + // https://github.com/Koenkk/zigbee-herdsman/pull/191/files?file-filters%5B%5D=.ts#r456569398 + attribute: {ID: 0, type: Zcl.DataType.UINT16}, + minimumReportInterval: 2, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 3, + }, + ]; await endpoint.configureReporting('msPressureMeasurement', payloadPressure); const options = {disableDefaultResponse: true}; await endpoint.write('genBinaryInput', {0x0051: {value: 0x01, type: 0x10}}, options); await endpoint.write('genBinaryInput', {0x0101: {value: 25, type: 0x23}}, options); - const payloadBinaryInput = [{ - attribute: 'presentValue', minimumReportInterval: 0, maximumReportInterval: 30, reportableChange: 1}]; + const payloadBinaryInput = [ + { + attribute: 'presentValue', + minimumReportInterval: 0, + maximumReportInterval: 30, + reportableChange: 1, + }, + ]; await endpoint.configureReporting('genBinaryInput', payloadBinaryInput); await endpoint.write('genBinaryOutput', {0x0051: {value: 0x01, type: 0x10}}, options); - const payloadBinaryOutput = [{ - attribute: 'presentValue', minimumReportInterval: 0, maximumReportInterval: 30, reportableChange: 1}]; + const payloadBinaryOutput = [ + { + attribute: 'presentValue', + minimumReportInterval: 0, + maximumReportInterval: 30, + reportableChange: 1, + }, + ]; await endpoint.configureReporting('genBinaryOutput', payloadBinaryOutput); }, }, @@ -120,8 +163,14 @@ const definitions: Definition[] = [ vendor: 'KMPCIL', description: 'Arrival sensor', fromZigbee: [kmpcilConverters.presence_binary_input, kmpcilConverters.presence_power, fz.temperature], - exposes: [e.battery(), e.presence(), e.binary('power_state', exposes.access.STATE, true, false), - e.occupancy(), e.vibration(), e.temperature()], + exposes: [ + e.battery(), + e.presence(), + e.binary('power_state', exposes.access.STATE, true, false), + e.occupancy(), + e.vibration(), + e.temperature(), + ], toZigbee: [], meta: {battery: {voltageToPercentage: '3V_1500_2800'}}, configure: async (device, coordinatorEndpoint) => { diff --git a/src/devices/konke.ts b/src/devices/konke.ts index 2132a903a0e41..0e15ffd3d4695 100644 --- a/src/devices/konke.ts +++ b/src/devices/konke.ts @@ -1,10 +1,10 @@ -import {Definition, Fz} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; -import * as utils from '../lib/utils'; -import * as reporting from '../lib/reporting'; import {deviceEndpoints, light, onOff} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition, Fz} from '../lib/types'; +import * as utils from '../lib/utils'; const e = exposes.presets; @@ -155,20 +155,14 @@ const definitions: Definition[] = [ model: 'KK-LP-Q02D', vendor: 'Konke', description: 'Light years switch 2 gangs', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2}}), - onOff({endpointNames: ['l1', 'l2']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2}}), onOff({endpointNames: ['l1', 'l2']})], }, { zigbeeModel: ['3AFE292000068623'], model: 'KK-LP-Q03D', vendor: 'Konke', description: 'Light years switch 3 gangs', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2, 'l3': 3}}), - onOff({endpointNames: ['l1', 'l2', 'l3']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2, l3: 3}}), onOff({endpointNames: ['l1', 'l2', 'l3']})], }, { zigbeeModel: ['3AFE2610010C0021'], diff --git a/src/devices/ksentry.ts b/src/devices/ksentry.ts index daf8163fc65ec..0e03fc3a72ac7 100644 --- a/src/devices/ksentry.ts +++ b/src/devices/ksentry.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/kurvia.ts b/src/devices/kurvia.ts index bb45e79e2c096..067a1142a1c2d 100644 --- a/src/devices/kurvia.ts +++ b/src/devices/kurvia.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; import tz from '../converters/toZigbee'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/kwikset.ts b/src/devices/kwikset.ts index 4f196774bed7a..9995c201158f8 100644 --- a/src/devices/kwikset.ts +++ b/src/devices/kwikset.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/lanesto.ts b/src/devices/lanesto.ts index d6a906cea9dd4..941d86215b86d 100644 --- a/src/devices/lanesto.ts +++ b/src/devices/lanesto.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/lds.ts b/src/devices/lds.ts index 264a2481c4be6..156f3bb9c30b5 100644 --- a/src/devices/lds.ts +++ b/src/devices/lds.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/led_trading.ts b/src/devices/led_trading.ts index 52c71a1fc8676..3df1bc291013c 100644 --- a/src/devices/led_trading.ts +++ b/src/devices/led_trading.ts @@ -1,11 +1,11 @@ -import {Definition, Fz} from '../lib/types'; -import * as reporting from '../lib/reporting'; -import * as exposes from '../lib/exposes'; -import * as utils from '../lib/utils'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import {deviceEndpoints, light, onOff} from '../lib/modernExtend'; +import * as exposes from '../lib/exposes'; import {logger} from '../lib/logger'; +import {deviceEndpoints, light, onOff} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition, Fz} from '../lib/types'; +import * as utils from '../lib/utils'; const NS = 'zhc:led_trading'; const e = exposes.presets; @@ -18,8 +18,16 @@ const fzLocal = { const commandID = msg.data.commandID; if (utils.hasAlreadyProcessedMessage(msg, model, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return; if (commandID === 224) return; - const lookup = {0x13: 'press_1', 0x14: 'press_2', 0x15: 'press_3', 0x16: 'press_4', - 0x1B: 'hold_1', 0x1C: 'hold_2', 0x1D: 'hold_3', 0x1E: 'hold_4'}; + const lookup = { + 0x13: 'press_1', + 0x14: 'press_2', + 0x15: 'press_3', + 0x16: 'press_4', + 0x1b: 'hold_1', + 0x1c: 'hold_2', + 0x1d: 'hold_3', + 0x1e: 'hold_4', + }; if (!lookup.hasOwnProperty(commandID)) { logger.error(`led_trading_9133: missing command '${commandID}'`, NS); } else { @@ -51,10 +59,7 @@ const definitions: Definition[] = [ model: '9134', vendor: 'LED-Trading', description: 'Powerstrip with 4 sockets and USB', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4, 'l5': 5}}), - onOff({endpointNames: ['l1', 'l2', 'l3', 'l4', 'l5']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2, l3: 3, l4: 4, l5: 5}}), onOff({endpointNames: ['l1', 'l2', 'l3', 'l4', 'l5']})], }, { zigbeeModel: ['HK-ZCC-ZLL-A'], diff --git a/src/devices/ledvance.ts b/src/devices/ledvance.ts index 43d2c3c8b36fa..a0127441bd6c9 100644 --- a/src/devices/ledvance.ts +++ b/src/devices/ledvance.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; import {ledvanceLight, ledvanceOnOff} from '../lib/ledvance'; import {forcePowerSource} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/leedarson.ts b/src/devices/leedarson.ts index 177b836b436ec..7036063ee67dd 100644 --- a/src/devices/leedarson.ts +++ b/src/devices/leedarson.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; -import * as reporting from '../lib/reporting'; import {light} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; @@ -69,10 +69,29 @@ const definitions: Definition[] = [ model: '6ARCZABZH', vendor: 'Leedarson', description: '4-Key Remote Controller', - fromZigbee: [fz.command_on, fz.command_off, legacy.fz.CCTSwitch_D0001_on_off, fz.CCTSwitch_D0001_levelctrl, - fz.CCTSwitch_D0001_lighting, fz.battery], - exposes: [e.battery(), e.action(['colortemp_up_release', 'colortemp_down_release', 'on', 'off', 'brightness_up', 'brightness_down', - 'colortemp_up', 'colortemp_down', 'colortemp_up_hold', 'colortemp_down_hold'])], + fromZigbee: [ + fz.command_on, + fz.command_off, + legacy.fz.CCTSwitch_D0001_on_off, + fz.CCTSwitch_D0001_levelctrl, + fz.CCTSwitch_D0001_lighting, + fz.battery, + ], + exposes: [ + e.battery(), + e.action([ + 'colortemp_up_release', + 'colortemp_down_release', + 'on', + 'off', + 'brightness_up', + 'brightness_down', + 'colortemp_up', + 'colortemp_down', + 'colortemp_up_hold', + 'colortemp_down_hold', + ]), + ], toZigbee: [], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); diff --git a/src/devices/legrand.ts b/src/devices/legrand.ts index ea4fa72a9cca3..b3954cf77fd1a 100644 --- a/src/devices/legrand.ts +++ b/src/devices/legrand.ts @@ -1,19 +1,21 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; -import * as ota from '../lib/ota'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; import {tzLegrand, fzLegrand, readInitialBatteryState, _067776, eLegrand, legrandOptions} from '../lib/legrand'; import {deviceEndpoints, electricityMeter, light, onOff} from '../lib/modernExtend'; +import * as ota from '../lib/ota'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; const definitions: Definition[] = [ { - zigbeeModel: [' Pocket remote\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000', - ' Wireless Scenes Command\u0000\u0000\u0000\u0000\u0000\u0000\u0000'], + zigbeeModel: [ + ' Pocket remote\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000', + ' Wireless Scenes Command\u0000\u0000\u0000\u0000\u0000\u0000\u0000', + ], model: '067755', vendor: 'Legrand', description: 'Wireless and batteryless 4 scenes control', @@ -29,8 +31,9 @@ const definitions: Definition[] = [ }, }, { - zigbeeModel: [' Dry contact\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'+ - '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'], + zigbeeModel: [ + ' Dry contact\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000', + ], model: '412173', vendor: 'Legrand', description: 'DIN dry contactor module', @@ -40,7 +43,9 @@ const definitions: Definition[] = [ fromZigbee: [fz.identify, fz.electrical_measurement, fzLegrand.cluster_fc01, fz.ignore_basic_report, fz.ignore_genOta], toZigbee: [tz.legrand_device_mode, tzLegrand.identify, tz.electrical_measurement_power], exposes: [ - e.power().withAccess(ea.STATE_GET), e.enum('device_mode', ea.ALL, ['switch', 'auto']) + e.power().withAccess(ea.STATE_GET), + e + .enum('device_mode', ea.ALL, ['switch', 'auto']) .withDescription('switch: allow on/off, auto will use wired action via C1/C2 on contactor for example with HC/HP'), ], configure: async (device, coordinatorEndpoint) => { @@ -54,8 +59,10 @@ const definitions: Definition[] = [ }, }, { - zigbeeModel: [' Contactor\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'+ - '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'], + zigbeeModel: [ + ' Contactor\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000', + ], model: '412171', vendor: 'Legrand', description: 'DIN contactor module', @@ -65,15 +72,19 @@ const definitions: Definition[] = [ fromZigbee: [fz.identify, fzLegrand.cluster_fc01, fz.ignore_basic_report, fz.ignore_genOta, fz.electrical_measurement], toZigbee: [tz.legrand_device_mode, tzLegrand.identify, tzLegrand.auto_mode, tz.electrical_measurement_power], exposes: [ - e.enum('device_mode', ea.ALL, ['switch', 'auto']) - .withDescription('Switch: allow manual on/off, auto uses contact\'s C1/C2 wired actions for Peak/Off-Peak electricity rates'), - e.enum('auto_mode', ea.STATE_SET, ['off', 'auto', 'on_override']) + e + .enum('device_mode', ea.ALL, ['switch', 'auto']) + .withDescription("Switch: allow manual on/off, auto uses contact's C1/C2 wired actions for Peak/Off-Peak electricity rates"), + e + .enum('auto_mode', ea.STATE_SET, ['off', 'auto', 'on_override']) .withDescription('Off/auto/on (override) (works only if device is set to "auto" mode)'), ], }, { - zigbeeModel: [' Teleruptor\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'+ - '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'], + zigbeeModel: [ + ' Teleruptor\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000', + ], model: '412170', vendor: 'Legrand', description: 'DIN smart relay for light control', @@ -83,7 +94,9 @@ const definitions: Definition[] = [ fromZigbee: [fz.identify, fz.electrical_measurement, fzLegrand.cluster_fc01, fz.ignore_basic_report, fz.ignore_genOta], toZigbee: [tz.legrand_device_mode, tzLegrand.identify, tz.electrical_measurement_power], exposes: [ - e.power().withAccess(ea.STATE_GET), e.enum('device_mode', ea.ALL, ['switch', 'auto']) + e.power().withAccess(ea.STATE_GET), + e + .enum('device_mode', ea.ALL, ['switch', 'auto']) .withDescription('switch: allow on/off, auto will use wired action via C1/C2 on teleruptor with buttons'), ], configure: async (device, coordinatorEndpoint) => { @@ -99,8 +112,15 @@ const definitions: Definition[] = [ vendor: 'Legrand', description: 'Wireless shutter switch', ota: ota.zigbeeOTA, - fromZigbee: [fz.identify, fz.ignore_basic_report, fz.command_cover_open, fz.command_cover_close, fz.command_cover_stop, fz.battery, - fz.legrand_binary_input_moving], + fromZigbee: [ + fz.identify, + fz.ignore_basic_report, + fz.command_cover_open, + fz.command_cover_close, + fz.command_cover_stop, + fz.battery, + fz.legrand_binary_input_moving, + ], toZigbee: [], exposes: [e.battery(), e.action(['identify', 'open', 'close', 'stop', 'moving', 'stopped'])], onEvent: async (type, data, device, options, state) => { @@ -121,8 +141,14 @@ const definitions: Definition[] = [ vendor: 'Legrand', description: 'Netatmo wired shutter switch', ota: ota.zigbeeOTA, - fromZigbee: [fz.ignore_basic_report, fz.cover_position_tilt, fz.legrand_binary_input_moving, fz.identify, - fzLegrand.cluster_fc01, fzLegrand.calibration_mode(false)], + fromZigbee: [ + fz.ignore_basic_report, + fz.cover_position_tilt, + fz.legrand_binary_input_moving, + fz.identify, + fzLegrand.cluster_fc01, + fzLegrand.calibration_mode(false), + ], toZigbee: [tz.cover_state, tz.cover_position_tilt, tzLegrand.identify, tzLegrand.led_mode, tzLegrand.calibration_mode(false)], exposes: (device, options) => { return [ @@ -151,10 +177,13 @@ const definitions: Definition[] = [ // - https://github.com/Koenkk/zigbee2mqtt/issues/16090 fingerprint: [ {modelID: ' Shutter switch with neutral\u0000\u0000\u0000', softwareBuildID: '001a'}, - {modelID: ' Shutter switch with neutral\u0000\u0000\u0000', softwareBuildID: - '00d\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u00000\u0012\u0002\u0000' + - '\t\u0007\u0000\u0018\u0002\u0003\b\u0000 \u00132\u0000\u0000\u0000\u0000X\u0002\n\u0000\u0000\u0000\u0000d' + - '\u0017\u0000\u0018\u0000'}, + { + modelID: ' Shutter switch with neutral\u0000\u0000\u0000', + softwareBuildID: + '00d\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u00000\u0012\u0002\u0000' + + '\t\u0007\u0000\u0018\u0002\u0003\b\u0000 \u00132\u0000\u0000\u0000\u0000X\u0002\n\u0000\u0000\u0000\u0000d' + + '\u0017\u0000\u0018\u0000', + }, ], model: '067776_inverted', vendor: 'Legrand', @@ -177,13 +206,21 @@ const definitions: Definition[] = [ description: 'Netatmo wired shutter switch with level control (NLLV)', whiteLabel: [ { - model: 'K4027C/L4027C/N4027C/NT4027C', vendor: 'BTicino', description: 'Shutter SW with level control', + model: 'K4027C/L4027C/N4027C/NT4027C', + vendor: 'BTicino', + description: 'Shutter SW with level control', fingerprint: [{hardwareVersion: 9}, {hardwareVersion: 13}], }, ], ota: ota.zigbeeOTA, - fromZigbee: [fz.ignore_basic_report, fz.cover_position_tilt, fz.legrand_binary_input_moving, fz.identify, - fzLegrand.cluster_fc01, fzLegrand.calibration_mode(true)], + fromZigbee: [ + fz.ignore_basic_report, + fz.cover_position_tilt, + fz.legrand_binary_input_moving, + fz.identify, + fzLegrand.cluster_fc01, + fzLegrand.calibration_mode(true), + ], toZigbee: [tz.cover_state, tz.cover_position_tilt, tzLegrand.identify, tzLegrand.led_mode, tzLegrand.calibration_mode(true)], exposes: (device, options) => { return [ @@ -216,10 +253,7 @@ const definitions: Definition[] = [ meta: {battery: {voltageToPercentage: '3V_2500'}, publishDuplicateTransaction: true}, fromZigbee: [fz.identify, fz.command_on, fz.command_off, fz.command_toggle, legacy.fz.cmd_move, legacy.fz.cmd_stop, fz.battery], toZigbee: [], - exposes: [ - e.battery(), - e.action(['identify', 'on', 'off', 'toggle', 'brightness_move_up', 'brightness_move_down', 'brightness_stop']), - ], + exposes: [e.battery(), e.action(['identify', 'on', 'off', 'toggle', 'brightness_move_up', 'brightness_move_down', 'brightness_stop'])], onEvent: readInitialBatteryState, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -235,10 +269,7 @@ const definitions: Definition[] = [ meta: {multiEndpoint: true, battery: {voltageToPercentage: '3V_2500'}, publishDuplicateTransaction: true}, fromZigbee: [fz.identify, fz.command_on, fz.command_off, fz.command_toggle, fz.command_move, fz.command_stop, fz.battery], toZigbee: [], - exposes: [ - e.battery(), - e.action(['identify', 'on', 'off', 'toggle', 'brightness_move_up', 'brightness_move_down', 'brightness_stop']), - ], + exposes: [e.battery(), e.action(['identify', 'on', 'off', 'toggle', 'brightness_move_up', 'brightness_move_down', 'brightness_stop'])], onEvent: readInitialBatteryState, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -258,10 +289,7 @@ const definitions: Definition[] = [ ota: ota.zigbeeOTA, fromZigbee: [fz.identify, fz.command_on, fz.command_off, fz.command_toggle, fz.battery], toZigbee: [], - exposes: [ - e.battery(), - e.action(['identify', 'on', 'off', 'toggle']), - ], + exposes: [e.battery(), e.action(['identify', 'on', 'off', 'toggle'])], onEvent: readInitialBatteryState, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -277,17 +305,26 @@ const definitions: Definition[] = [ fromZigbee: [fz.identify, fz.level_config, fz.lighting_ballast_configuration, fzLegrand.cluster_fc01], toZigbee: [tzLegrand.led_mode, tz.legrand_device_mode, tzLegrand.identify, tz.ballast_config, tz.level_config], exposes: [ - e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254) - .withDescription('Specifies the minimum brightness value'), - e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254) - .withDescription('Specifies the maximum brightness value'), - e.binary('device_mode', ea.ALL, 'dimmer_on', 'dimmer_off') - .withDescription('Allow the device to change brightness'), + e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254).withDescription('Specifies the minimum brightness value'), + e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254).withDescription('Specifies the maximum brightness value'), + e.binary('device_mode', ea.ALL, 'dimmer_on', 'dimmer_off').withDescription('Allow the device to change brightness'), eLegrand.ledInDark(), eLegrand.ledIfOn(), ], - extend: [light({configureReporting: true, levelConfig: {disabledFeatures: ['on_off_transition_time', 'on_transition_time', - 'off_transition_time', 'execute_if_off', 'current_level_startup']}})], + extend: [ + light({ + configureReporting: true, + levelConfig: { + disabledFeatures: [ + 'on_off_transition_time', + 'on_transition_time', + 'off_transition_time', + 'execute_if_off', + 'current_level_startup', + ], + }, + }), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genIdentify', 'genBinaryInput', 'lightingBallastCfg']); @@ -302,12 +339,9 @@ const definitions: Definition[] = [ fromZigbee: [fz.identify, fz.lighting_ballast_configuration, fzLegrand.cluster_fc01], toZigbee: [tzLegrand.led_mode, tz.legrand_device_mode, tzLegrand.identify, tz.ballast_config], exposes: [ - e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254) - .withDescription('Specifies the minimum brightness value'), - e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254) - .withDescription('Specifies the maximum brightness value'), - e.binary('device_mode', ea.ALL, 'dimmer_on', 'dimmer_off') - .withDescription('Allow the device to change brightness'), + e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254).withDescription('Specifies the minimum brightness value'), + e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254).withDescription('Specifies the maximum brightness value'), + e.binary('device_mode', ea.ALL, 'dimmer_on', 'dimmer_off').withDescription('Allow the device to change brightness'), eLegrand.ledInDark(), eLegrand.ledIfOn(), ], @@ -375,10 +409,7 @@ const definitions: Definition[] = [ meta: {battery: {voltageToPercentage: '3V_2500'}}, fromZigbee: [fz.legrand_scenes, fz.legrand_master_switch_center, fz.ignore_poll_ctrl, fz.battery], toZigbee: [], - exposes: [ - e.battery(), - e.action(['enter', 'leave', 'sleep', 'wakeup', 'center']), - ], + exposes: [e.battery(), e.action(['enter', 'leave', 'sleep', 'wakeup', 'center'])], onEvent: async (type, data, device, options, state) => { await readInitialBatteryState(type, data, device, options, state); if (data.type === 'commandCheckin' && data.cluster === 'genPollCtrl') { @@ -402,8 +433,15 @@ const definitions: Definition[] = [ {vendor: 'BTicino', description: 'DIN power consumption module', model: 'FC80GCS', fingerprint: [{modelID: ' Smart shedder module'}]}, ], ota: ota.zigbeeOTA, - fromZigbee: [fz.identify, fz.metering, fz.electrical_measurement, fz.ignore_basic_report, fz.ignore_genOta, - fz.legrand_power_alarm, fzLegrand.cluster_fc01], + fromZigbee: [ + fz.identify, + fz.metering, + fz.electrical_measurement, + fz.ignore_basic_report, + fz.ignore_genOta, + fz.legrand_power_alarm, + fzLegrand.cluster_fc01, + ], toZigbee: [tzLegrand.led_mode, tzLegrand.identify, tz.electrical_measurement_power, tz.legrand_power_alarm], exposes: [ e.power().withAccess(ea.STATE_GET), @@ -421,10 +459,14 @@ const definitions: Definition[] = [ if (type === 'deviceAnnounce') { for (const endpoint of device.endpoints) { for (const c of endpoint.configuredReportings) { - await endpoint.configureReporting(c.cluster.name, [{ - attribute: c.attribute.name, minimumReportInterval: c.minimumReportInterval, - maximumReportInterval: c.maximumReportInterval, reportableChange: c.reportableChange, - }]); + await endpoint.configureReporting(c.cluster.name, [ + { + attribute: c.attribute.name, + minimumReportInterval: c.minimumReportInterval, + maximumReportInterval: c.maximumReportInterval, + reportableChange: c.reportableChange, + }, + ]); } } } @@ -454,10 +496,7 @@ const definitions: Definition[] = [ meta: {battery: {voltageToPercentage: '3V_2500'}}, fromZigbee: [fz.legrand_scenes, fz.battery, fz.ignore_poll_ctrl, fz.legrand_master_switch_center], toZigbee: [], - exposes: [ - e.battery(), - e.action(['enter', 'leave', 'sleep', 'wakeup', 'center']), - ], + exposes: [e.battery(), e.action(['enter', 'leave', 'sleep', 'wakeup', 'center'])], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genIdentify', 'genPowerCfg']); @@ -471,11 +510,18 @@ const definitions: Definition[] = [ ota: ota.zigbeeOTA, fromZigbee: [fz.legrand_greenpower], toZigbee: [], - exposes: [e.action([ - 'home_arrival', 'home_departure', // ZLGP14 - 'press_1', 'press_2', 'press_3', 'press_4', // ZLGP15 - 'daytime_day', 'daytime_night', // ZLGP16 - ])], + exposes: [ + e.action([ + 'home_arrival', + 'home_departure', // ZLGP14 + 'press_1', + 'press_2', + 'press_3', + 'press_4', // ZLGP15 + 'daytime_day', + 'daytime_night', // ZLGP16 + ]), + ], }, { fingerprint: [{modelID: 'GreenPower_2', ieeeAddr: /^0x00000000005.....$/}], @@ -498,8 +544,9 @@ const definitions: Definition[] = [ exposes: [e.action(['stop', 'up', 'down'])], }, { - zigbeeModel: [' Cable outlet\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + - '\u0000\u0000'], + zigbeeModel: [ + ' Cable outlet\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000', + ], model: '064882', vendor: 'Legrand', description: 'Cable outlet with pilot wire and consumption measurement', @@ -512,7 +559,8 @@ const definitions: Definition[] = [ e.switch().withState('state', true, 'Works only when the pilot wire is deactivated'), e.power().withAccess(ea.STATE_GET), e.power_apparent(), - e.power_on_behavior() + e + .power_on_behavior() .withDescription('Controls the behavior when the device is powered on. Works only when the pilot wire is deactivated'), ], configure: async (device, coordinatorEndpoint) => { @@ -533,22 +581,37 @@ const definitions: Definition[] = [ fromZigbee: [fz.identify, fz.legrand_binary_input_on_off, fz.lighting_ballast_configuration, fzLegrand.cluster_fc01], toZigbee: [tzLegrand.identify, tz.legrand_device_mode, tzLegrand.led_mode, tz.ballast_config], exposes: [ - e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254) - .withDescription('Specifies the minimum brightness value').withEndpoint('left'), - e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254) - .withDescription('Specifies the maximum brightness value').withEndpoint('left'), - e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254) - .withDescription('Specifies the minimum brightness value').withEndpoint('right'), - e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254) - .withDescription('Specifies the maximum brightness value').withEndpoint('right'), - e.binary('device_mode', ea.ALL, 'dimmer_on', 'dimmer_off') - .withDescription('Allow the device to change brightness'), + e + .numeric('ballast_minimum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) + .withDescription('Specifies the minimum brightness value') + .withEndpoint('left'), + e + .numeric('ballast_maximum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) + .withDescription('Specifies the maximum brightness value') + .withEndpoint('left'), + e + .numeric('ballast_minimum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) + .withDescription('Specifies the minimum brightness value') + .withEndpoint('right'), + e + .numeric('ballast_maximum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) + .withDescription('Specifies the maximum brightness value') + .withEndpoint('right'), + e.binary('device_mode', ea.ALL, 'dimmer_on', 'dimmer_off').withDescription('Allow the device to change brightness'), e.switch().withEndpoint('left'), e.switch().withEndpoint('right'), eLegrand.ledInDark(), eLegrand.ledIfOn(), ], - extend: [deviceEndpoints({endpoints: {'left': 2, 'right': 1}}), light({configureReporting: true, endpointNames: ['left', 'right']})], + extend: [deviceEndpoints({endpoints: {left: 2, right: 1}}), light({configureReporting: true, endpointNames: ['left', 'right']})], }, { zigbeeModel: [' Mobile outlet\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'], @@ -558,11 +621,7 @@ const definitions: Definition[] = [ ota: ota.zigbeeOTA, fromZigbee: [fz.identify, fz.on_off, fz.electrical_measurement, fzLegrand.cluster_fc01], toZigbee: [tz.on_off, tzLegrand.led_mode, tzLegrand.identify], - exposes: [ - e.switch(), - e.action(['identify']), - e.power(), - ], + exposes: [e.switch(), e.action(['identify']), e.power()], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genIdentify', 'genOnOff', 'haElectricalMeasurement']); @@ -579,11 +638,7 @@ const definitions: Definition[] = [ ota: ota.zigbeeOTA, fromZigbee: [fz.on_off, fz.legrand_binary_input_on_off, fzLegrand.cluster_fc01], toZigbee: [tz.on_off, tzLegrand.led_mode], - exposes: [ - e.switch(), - eLegrand.ledInDark(), - eLegrand.ledIfOn(), - ], + exposes: [e.switch(), eLegrand.ledInDark(), eLegrand.ledIfOn()], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']); @@ -599,12 +654,9 @@ const definitions: Definition[] = [ fromZigbee: [fz.identify, fz.lighting_ballast_configuration, fzLegrand.cluster_fc01], toZigbee: [tzLegrand.led_mode, tz.legrand_device_mode, tzLegrand.identify, tz.ballast_config], exposes: [ - e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254) - .withDescription('Specifies the minimum brightness value'), - e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254) - .withDescription('Specifies the maximum brightness value'), - e.binary('device_mode', ea.ALL, 'dimmer_on', 'dimmer_off') - .withDescription('Allow the device to change brightness'), + e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254).withDescription('Specifies the minimum brightness value'), + e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254).withDescription('Specifies the maximum brightness value'), + e.binary('device_mode', ea.ALL, 'dimmer_on', 'dimmer_off').withDescription('Allow the device to change brightness'), eLegrand.ledInDark(), eLegrand.ledIfOn(), ], @@ -624,10 +676,7 @@ const definitions: Definition[] = [ meta: {battery: {voltageToPercentage: '3V_2500'}, publishDuplicateTransaction: true}, fromZigbee: [fz.identify, fz.command_on, fz.command_off, fz.command_toggle, legacy.fz.cmd_move, legacy.fz.cmd_stop, fz.battery], toZigbee: [], - exposes: [ - e.battery(), - e.action(['identify', 'on', 'off', 'toggle', 'brightness_move_up', 'brightness_move_down', 'brightness_stop']), - ], + exposes: [e.battery(), e.action(['identify', 'on', 'off', 'toggle', 'brightness_move_up', 'brightness_move_down', 'brightness_stop'])], onEvent: readInitialBatteryState, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -635,22 +684,14 @@ const definitions: Definition[] = [ }, }, { - zigbeeModel: [' Centralized ventilation SW', - ' Centralized ventilation SW\u0000\u0000\u0000\u0000', - ], + zigbeeModel: [' Centralized ventilation SW', ' Centralized ventilation SW\u0000\u0000\u0000\u0000'], model: '067766', vendor: 'Legrand', description: 'Centralized ventilation switch', ota: ota.zigbeeOTA, fromZigbee: [fz.identify, fz.on_off, fz.power_on_behavior, fzLegrand.cluster_fc01], toZigbee: [tz.on_off, tzLegrand.led_mode, tzLegrand.identify, tz.power_on_behavior], - exposes: [ - e.switch(), - e.action(['identify']), - eLegrand.ledInDark(), - eLegrand.ledIfOn(), - e.power_on_behavior(), - ], + exposes: [e.switch(), e.action(['identify']), eLegrand.ledInDark(), eLegrand.ledIfOn(), e.power_on_behavior()], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genIdentify', 'genOnOff']); diff --git a/src/devices/lellki.ts b/src/devices/lellki.ts index 43ffe1701c578..b20a4d262e303 100644 --- a/src/devices/lellki.ts +++ b/src/devices/lellki.ts @@ -1,17 +1,20 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; -import * as tuya from '../lib/tuya'; import {deviceEndpoints, onOff} from '../lib/modernExtend'; +import * as tuya from '../lib/tuya'; const definitions: Definition[] = [ { - fingerprint: [{modelID: 'TS011F', manufacturerName: '_TZ3000_air9m6af'}, {modelID: 'TS011F', manufacturerName: '_TZ3000_9djocypn'}, - {modelID: 'TS011F', manufacturerName: '_TZ3000_bppxj3sf'}], + fingerprint: [ + {modelID: 'TS011F', manufacturerName: '_TZ3000_air9m6af'}, + {modelID: 'TS011F', manufacturerName: '_TZ3000_9djocypn'}, + {modelID: 'TS011F', manufacturerName: '_TZ3000_bppxj3sf'}, + ], zigbeeModel: ['JZ-ZB-005', 'E220-KR5N0Z0-HA', 'E220-KR5N0Z0-HA'], model: 'WP33-EU/WP34-EU', vendor: 'LELLKI', @@ -21,7 +24,7 @@ const definitions: Definition[] = [ exposes: [e.power_on_behavior()], configure: tuya.configureMagicPacket, extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4, 'l5': 5}}), + deviceEndpoints({endpoints: {l1: 1, l2: 2, l3: 3, l4: 4, l5: 5}}), onOff({endpointNames: ['l1', 'l2', 'l3', 'l4', 'l5'], powerOnBehavior: false}), ], }, @@ -41,20 +44,14 @@ const definitions: Definition[] = [ model: 'JZ-ZB-003', vendor: 'LELLKI', description: '3 gang switch', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2, 'l3': 3}}), - onOff({endpointNames: ['l1', 'l2', 'l3']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2, l3: 3}}), onOff({endpointNames: ['l1', 'l2', 'l3']})], }, { zigbeeModel: ['JZ-ZB-002'], model: 'JZ-ZB-002', vendor: 'LELLKI', description: '2 gang touch switch', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2}}), - onOff({endpointNames: ['l1', 'l2']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2}}), onOff({endpointNames: ['l1', 'l2']})], }, { fingerprint: [{modelID: 'TS011F', manufacturerName: '_TZ3000_twqctvna'}], @@ -101,8 +98,7 @@ const definitions: Definition[] = [ model: 'WP30-EU', description: 'Power cord 4 sockets EU (with power monitoring)', vendor: 'LELLKI', - fromZigbee: [fz.on_off_force_multiendpoint, fz.electrical_measurement, fz.metering, fz.ignore_basic_report, - tuya.fz.power_outage_memory], + fromZigbee: [fz.on_off_force_multiendpoint, fz.electrical_measurement, fz.metering, fz.ignore_basic_report, tuya.fz.power_outage_memory], toZigbee: [tz.on_off, tuya.tz.power_on_behavior_1], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -116,10 +112,16 @@ const definitions: Definition[] = [ device.save(); }, options: [exposes.options.measurement_poll_interval()], - exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), - e.switch().withEndpoint('l3'), e.power(), e.current(), e.voltage(), - e.energy(), e.enum('power_outage_memory', ea.ALL, ['on', 'off', 'restore']) - .withDescription('Recover state after power outage')], + exposes: [ + e.switch().withEndpoint('l1'), + e.switch().withEndpoint('l2'), + e.switch().withEndpoint('l3'), + e.power(), + e.current(), + e.voltage(), + e.energy(), + e.enum('power_outage_memory', ea.ALL, ['on', 'off', 'restore']).withDescription('Recover state after power outage'), + ], endpoint: (device) => { return {l1: 1, l2: 2, l3: 3}; }, diff --git a/src/devices/letsled.ts b/src/devices/letsled.ts index 53eb9b5797371..57b2876050422 100644 --- a/src/devices/letsled.ts +++ b/src/devices/letsled.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/letv.ts b/src/devices/letv.ts index a3f0220157c39..4733cd9ffa6e5 100644 --- a/src/devices/letv.ts +++ b/src/devices/letv.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ @@ -10,10 +10,42 @@ const definitions: Definition[] = [ vendor: 'LeTV', description: '8key switch', fromZigbee: [fz.qlwz_letv8key_switch], - exposes: [e.action(['hold_up', 'single_up', 'double_up', 'tripple_up', 'hold_down', 'single_down', 'double_down', 'tripple_down', - 'hold_left', 'single_left', 'double_left', 'tripple_left', 'hold_right', 'single_right', 'double_right', 'tripple_right', - 'hold_center', 'single_center', 'double_center', 'tripple_center', 'hold_back', 'single_back', 'double_back', 'tripple_back', - 'hold_play', 'single_play', 'double_play', 'tripple_play', 'hold_voice', 'single_voice', 'double_voice', 'tripple_voice'])], + exposes: [ + e.action([ + 'hold_up', + 'single_up', + 'double_up', + 'tripple_up', + 'hold_down', + 'single_down', + 'double_down', + 'tripple_down', + 'hold_left', + 'single_left', + 'double_left', + 'tripple_left', + 'hold_right', + 'single_right', + 'double_right', + 'tripple_right', + 'hold_center', + 'single_center', + 'double_center', + 'tripple_center', + 'hold_back', + 'single_back', + 'double_back', + 'tripple_back', + 'hold_play', + 'single_play', + 'double_play', + 'tripple_play', + 'hold_voice', + 'single_voice', + 'double_voice', + 'tripple_voice', + ]), + ], toZigbee: [], }, ]; diff --git a/src/devices/leviton.ts b/src/devices/leviton.ts index 7b0edf4c3a00e..e6477aacb3871 100644 --- a/src/devices/leviton.ts +++ b/src/devices/leviton.ts @@ -1,11 +1,11 @@ -import {Definition, Fz} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; +import {light, onOff} from '../lib/modernExtend'; import * as reporting from '../lib/reporting'; +import {Definition, Fz} from '../lib/types'; import * as utils from '../lib/utils'; -import {light, onOff} from '../lib/modernExtend'; const e = exposes.presets; const ea = exposes.access; @@ -65,12 +65,25 @@ const definitions: Definition[] = [ vendor: 'Leviton', description: 'Omnistat2 wireless thermostat', fromZigbee: [legacy.fz.thermostat_att_report, fz.fan], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_local_temperature_calibration, tz.thermostat_occupancy, - tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint, tz.thermostat_occupied_cooling_setpoint, - tz.thermostat_unoccupied_cooling_setpoint, tz.thermostat_setpoint_raise_lower, tz.thermostat_remote_sensing, - tz.thermostat_control_sequence_of_operation, tz.thermostat_system_mode, tz.thermostat_weekly_schedule, - tz.thermostat_clear_weekly_schedule, tz.thermostat_relay_status_log, tz.thermostat_temperature_setpoint_hold, - tz.thermostat_temperature_setpoint_hold_duration, tz.fan_mode], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_local_temperature_calibration, + tz.thermostat_occupancy, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_unoccupied_heating_setpoint, + tz.thermostat_occupied_cooling_setpoint, + tz.thermostat_unoccupied_cooling_setpoint, + tz.thermostat_setpoint_raise_lower, + tz.thermostat_remote_sensing, + tz.thermostat_control_sequence_of_operation, + tz.thermostat_system_mode, + tz.thermostat_weekly_schedule, + tz.thermostat_clear_weekly_schedule, + tz.thermostat_relay_status_log, + tz.thermostat_temperature_setpoint_hold, + tz.thermostat_temperature_setpoint_hold_duration, + tz.fan_mode, + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(9); await reporting.bind(endpoint, coordinatorEndpoint, ['hvacThermostat', 'hvacFanCtrl']); @@ -83,10 +96,16 @@ const definitions: Definition[] = [ await reporting.fanMode(endpoint); }, exposes: [ - e.climate().withSetpoint('occupied_heating_setpoint', 10, 30, 1).withLocalTemperature() - .withSystemMode(['off', 'auto', 'heat', 'cool']).withFanMode(['auto', 'on', 'smart']) + e + .climate() + .withSetpoint('occupied_heating_setpoint', 10, 30, 1) + .withLocalTemperature() + .withSystemMode(['off', 'auto', 'heat', 'cool']) + .withFanMode(['auto', 'on', 'smart']) .withSetpoint('occupied_cooling_setpoint', 10, 30, 1) - .withLocalTemperatureCalibration().withPiHeatingDemand()], + .withLocalTemperatureCalibration() + .withPiHeatingDemand(), + ], }, { // Reference from a similar switch: https://gist.github.com/nebhead/dc5a0a827ec14eef6196ded4be6e2dd0 @@ -101,10 +120,9 @@ const definitions: Definition[] = [ exposes: [ // Note: ballast_power_on_level used to be here, but it doesn't appear to work properly with this device // If set, it's reset back to 0 when the device is turned off then back to 32 when turned on - e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254) - .withDescription('Specifies the minimum brightness value'), - e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254) - .withDescription('Specifies the maximum brightness value')], + e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254).withDescription('Specifies the minimum brightness value'), + e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254).withDescription('Specifies the maximum brightness value'), + ], }, ]; diff --git a/src/devices/lg.ts b/src/devices/lg.ts index 3af8bee5e6379..95db12398a30f 100644 --- a/src/devices/lg.ts +++ b/src/devices/lg.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/lidl.ts b/src/devices/lidl.ts index c7b92a8f4e52f..97dc0b406c79e 100644 --- a/src/devices/lidl.ts +++ b/src/devices/lidl.ts @@ -1,26 +1,28 @@ -import {Definition, Fz, Tz, KeyValue, Publish} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; import * as reporting from '../lib/reporting'; +import {Definition, Fz, Tz, KeyValue, Publish} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; -import * as tuya from '../lib/tuya'; -import * as globalStore from '../lib/store'; +import {battery, iasZoneAlarm} from '../lib/modernExtend'; import * as ota from '../lib/ota'; +import * as globalStore from '../lib/store'; +import * as tuya from '../lib/tuya'; import * as utils from '../lib/utils'; -import {battery, iasZoneAlarm} from '../lib/modernExtend'; const valueConverterLocal = { wateringState: { from: (value: number, meta: Fz.Meta, options: KeyValue, publish: Publish) => { const result = { state: value ? 'ON' : 'OFF', - ...(value ? {} : { - // ensure time_left is set to zero when it's OFF - time_left: 0, - }), + ...(value + ? {} + : { + // ensure time_left is set to zero when it's OFF + time_left: 0, + }), }; // prepare the time reporting for water scheduler @@ -39,7 +41,7 @@ const valueConverterLocal = { const timeslot = [1, 2, 3, 4, 5, 6] .map((slotNumber) => utils.getObjectProperty(meta.state, `schedule_slot_${slotNumber}`, {})) // @ts-expect-error - .find((ts) =>ts.state === 'ON' && ts.start_hour === now.getHours() && ts.start_minute === now.getMinutes() && ts.timer > 0); + .find((ts) => ts.state === 'ON' && ts.start_hour === now.getHours() && ts.start_minute === now.getMinutes() && ts.timer > 0); if (timeslot) { // @ts-expect-error @@ -64,7 +66,7 @@ const valueConverterLocal = { if ( // time slot execution is already completed - (Date.now() > (ts.timeslot_end_timestamp - 5000)) || + Date.now() > ts.timeslot_end_timestamp - 5000 || // scheduling was interrupted by turning watering on manually // @ts-expect-error (result.state === 'ON' && result.state != meta.state.state && meta.state.time_left > 0) @@ -82,7 +84,7 @@ const valueConverterLocal = { ts.iteration_start_timestamp = Date.now(); if (ts.timer > 1) { // report every minute - const interval = ts.iteration_inverval = setInterval(() => { + const interval = (ts.iteration_inverval = setInterval(() => { const now = Date.now(); const wateringEndTime = ts.iteration_start_timestamp + ts.timer * 60 * 1000; const timeLeftInMinutes = Math.round((wateringEndTime - now) / 1000 / 60); @@ -94,7 +96,7 @@ const valueConverterLocal = { time_left: timeLeftInMinutes, }); } - }, 60 * 1000); + }, 60 * 1000)); } // initial reporting result.time_left = ts.timer; @@ -110,16 +112,13 @@ const valueConverterLocal = { return { schedule_mode: scheduleValue === 0 ? 'OFF' : isWeekday ? 'WEEKDAY' : 'PERIODIC', schedule_periodic: !isWeekday ? scheduleValue : 0, - schedule_weekday: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'] - .reduce( - (scheduleMap, dayName, index) => ( - { - ...scheduleMap, - [dayName]: isWeekday && (scheduleValue & (1 << index)) > 0 ? 'ON' : 'OFF', - } - ), - {}, - ), + schedule_weekday: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'].reduce( + (scheduleMap, dayName, index) => ({ + ...scheduleMap, + [dayName]: isWeekday && (scheduleValue & (1 << index)) > 0 ? 'ON' : 'OFF', + }), + {}, + ), }; }, }, @@ -236,9 +235,7 @@ const definitions: Definition[] = [ }, options: [exposes.options.measurement_poll_interval().withDescription('Only the energy value is polled for this device.')], onEvent: (type, data, device, options) => tuya.onEventMeasurementPoll(type, data, device, options, false, true), - whiteLabel: [ - tuya.whitelabel('Lidl', 'HG08673-BS', 'Silvercrest smart plug with power monitoring (BS)', ['_TZ3000_3uimvkn6']), - ], + whiteLabel: [tuya.whitelabel('Lidl', 'HG08673-BS', 'Silvercrest smart plug with power monitoring (BS)', ['_TZ3000_3uimvkn6'])], }, { fingerprint: [{modelID: 'TS004F', manufacturerName: '_TZ3000_rco1yzb1'}], @@ -252,15 +249,16 @@ const definitions: Definition[] = [ await endpoint.read('genBasic', [0x0004, 0x000, 0x0001, 0x0005, 0x0007, 0xfffe]); await endpoint.read('genOnOff', ['tuyaOperationMode']); try { - await endpoint.read(0xE001, [0xD011]); - } catch (err) {/* do nothing */} + await endpoint.read(0xe001, [0xd011]); + } catch (err) { + /* do nothing */ + } await endpoint.read('genPowerCfg', ['batteryVoltage', 'batteryPercentageRemaining']); await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']); await reporting.batteryPercentageRemaining(endpoint); }, - exposes: [e.action( - ['on', 'off', 'brightness_stop', 'brightness_step_up', 'brightness_step_down', 'single', 'double']), e.battery()], + exposes: [e.action(['on', 'off', 'brightness_stop', 'brightness_step_up', 'brightness_step_down', 'single', 'double']), e.battery()], }, { fingerprint: [{modelID: 'TS0211', manufacturerName: '_TZ1800_ladpngdx'}], @@ -306,8 +304,9 @@ const definitions: Definition[] = [ model: 'FB20-002', vendor: 'Lidl', description: 'Livarno Lux switch and dimming light remote control', - exposes: [e.action(['on', 'off', 'brightness_stop', 'brightness_step_up', 'brightness_step_down', 'brightness_move_up', - 'brightness_move_down'])], + exposes: [ + e.action(['on', 'off', 'brightness_stop', 'brightness_step_up', 'brightness_step_down', 'brightness_move_up', 'brightness_move_down']), + ], fromZigbee: [fz.command_on, fz.command_off, fz.command_step, fz.command_move, fz.command_stop], toZigbee: [], }, @@ -316,8 +315,18 @@ const definitions: Definition[] = [ model: 'FB21-001', vendor: 'Lidl', description: 'Livarno Lux switch and dimming light remote control', - exposes: [e.action(['on', 'off', 'brightness_stop', 'brightness_step_up', 'brightness_step_down', 'brightness_move_up', - 'brightness_move_down', 'switch_scene'])], + exposes: [ + e.action([ + 'on', + 'off', + 'brightness_stop', + 'brightness_step_up', + 'brightness_step_down', + 'brightness_move_up', + 'brightness_move_down', + 'switch_scene', + ]), + ], fromZigbee: [fz.command_on, fz.command_off, fz.command_step, fz.command_move, fz.command_stop, fz.tuya_switch_scene], toZigbee: [], }, @@ -342,7 +351,7 @@ const definitions: Definition[] = [ } }, endpoint: (device) => { - return {'l1': 1, 'l2': 2, 'l3': 3}; + return {l1: 1, l2: 2, l3: 3}; }, }, { @@ -401,22 +410,30 @@ const definitions: Definition[] = [ exposes: [ e.battery(), tuya.exposes.switch(), - e.numeric('timer', ea.STATE_SET).withValueMin(1).withValueMax(599).withUnit('min') + e + .numeric('timer', ea.STATE_SET) + .withValueMin(1) + .withValueMax(599) + .withUnit('min') .withDescription('Auto off after specific time for manual watering.'), - e.numeric('time_left', ea.STATE).withUnit('min') - .withDescription('Remaining time until the watering turns off.'), - e.binary('frost_lock', ea.STATE, 'ON', 'OFF') + e.numeric('time_left', ea.STATE).withUnit('min').withDescription('Remaining time until the watering turns off.'), + e + .binary('frost_lock', ea.STATE, 'ON', 'OFF') .withDescription( 'Indicates if the frost guard is currently active. ' + - 'If the temperature drops below 5° C, device activates frost guard and disables irrigation. ' + - 'You need to reset the frost guard to activate irrigation again. Note: There is no way to enable frost guard manually.', + 'If the temperature drops below 5° C, device activates frost guard and disables irrigation. ' + + 'You need to reset the frost guard to activate irrigation again. Note: There is no way to enable frost guard manually.', ), e.enum('reset_frost_lock', ea.SET, ['RESET']).withDescription('Resets frost lock to make the device workable again.'), - e.enum('schedule_mode', ea.STATE, ['OFF', 'WEEKDAY', 'PERIODIC']) - .withDescription('Scheduling mode that is currently in use.'), - e.numeric('schedule_periodic', ea.STATE_SET).withValueMin(0).withValueMax(7).withUnit('day') + e.enum('schedule_mode', ea.STATE, ['OFF', 'WEEKDAY', 'PERIODIC']).withDescription('Scheduling mode that is currently in use.'), + e + .numeric('schedule_periodic', ea.STATE_SET) + .withValueMin(0) + .withValueMax(7) + .withUnit('day') .withDescription('Watering by periodic interval: Irrigate every n days'), - e.composite('schedule_weekday', 'schedule_weekday', ea.STATE_SET) + e + .composite('schedule_weekday', 'schedule_weekday', ea.STATE_SET) .withDescription('Watering by weekday: Irrigate individually for each day.') .withFeature(e.binary('monday', ea.STATE_SET, 'ON', 'OFF')) .withFeature(e.binary('tuesday', ea.STATE_SET, 'ON', 'OFF')) @@ -426,19 +443,44 @@ const definitions: Definition[] = [ .withFeature(e.binary('saturday', ea.STATE_SET, 'ON', 'OFF')) .withFeature(e.binary('sunday', ea.STATE_SET, 'ON', 'OFF')), ...[1, 2, 3, 4, 5, 6].map((timeSlotNumber) => - e.composite(`schedule_slot_${timeSlotNumber}`, `schedule_slot_${timeSlotNumber}`, ea.STATE_SET) + e + .composite(`schedule_slot_${timeSlotNumber}`, `schedule_slot_${timeSlotNumber}`, ea.STATE_SET) .withDescription(`Watering time slot ${timeSlotNumber}`) .withFeature(e.binary('state', ea.STATE_SET, 'ON', 'OFF').withDescription('On/off state of the time slot')) - .withFeature(e.numeric('start_hour', ea.STATE_SET).withUnit('h').withValueMin(0).withValueMax(23) - .withDescription('Starting time (hour)')) - .withFeature(e.numeric('start_minute', ea.STATE_SET).withUnit('min').withValueMin(0).withValueMax(59) - .withDescription('Starting time (minute)')) - .withFeature(e.numeric('timer', ea.STATE_SET).withUnit('min').withValueMin(1).withValueMax(599) - .withDescription('Auto off after specific time for scheduled watering.')) - .withFeature(e.numeric('pause', ea.STATE_SET).withUnit('min').withValueMin(0).withValueMax(599) - .withDescription('Pause after each iteration.')) - .withFeature(e.numeric('iterations', ea.STATE_SET).withValueMin(1).withValueMax(9) - .withDescription('Number of watering iterations. Works only if there is a pause.')), + .withFeature( + e.numeric('start_hour', ea.STATE_SET).withUnit('h').withValueMin(0).withValueMax(23).withDescription('Starting time (hour)'), + ) + .withFeature( + e + .numeric('start_minute', ea.STATE_SET) + .withUnit('min') + .withValueMin(0) + .withValueMax(59) + .withDescription('Starting time (minute)'), + ) + .withFeature( + e + .numeric('timer', ea.STATE_SET) + .withUnit('min') + .withValueMin(1) + .withValueMax(599) + .withDescription('Auto off after specific time for scheduled watering.'), + ) + .withFeature( + e + .numeric('pause', ea.STATE_SET) + .withUnit('min') + .withValueMin(0) + .withValueMax(599) + .withDescription('Pause after each iteration.'), + ) + .withFeature( + e + .numeric('iterations', ea.STATE_SET) + .withValueMin(1) + .withValueMax(9) + .withDescription('Number of watering iterations. Works only if there is a pause.'), + ), ), ], meta: { @@ -451,7 +493,7 @@ const definitions: Definition[] = [ [11, 'battery', tuya.valueConverter.raw], [108, 'frost_lock', tuya.valueConverter.onOff], // there is no state reporting for reset - [109, 'reset_frost_lock', tuya.valueConverterBasic.lookup({'RESET': tuya.enum(0)}), {optimistic: false}], + [109, 'reset_frost_lock', tuya.valueConverterBasic.lookup({RESET: tuya.enum(0)}), {optimistic: false}], [107, null, valueConverterLocal.wateringScheduleMode], [107, 'schedule_periodic', valueConverterLocal.wateringSchedulePeriodic], [107, 'schedule_weekday', valueConverterLocal.wateringScheduleWeekday], @@ -501,35 +543,67 @@ const definitions: Definition[] = [ vendor: 'Lidl', description: 'Silvercrest radiator valve with thermostat', fromZigbee: [fz.ignore_tuya_set_time, legacy.fromZigbee.zs_thermostat], - toZigbee: [legacy.toZigbee.zs_thermostat_current_heating_setpoint, legacy.toZigbee.zs_thermostat_child_lock, - legacy.toZigbee.zs_thermostat_comfort_temp, legacy.toZigbee.zs_thermostat_eco_temp, legacy.toZigbee.zs_thermostat_preset_mode, - legacy.toZigbee.zs_thermostat_system_mode, legacy.toZigbee.zs_thermostat_local_temperature_calibration, - legacy.toZigbee.zs_thermostat_current_heating_setpoint_auto, legacy.toZigbee.zs_thermostat_openwindow_time, - legacy.toZigbee.zs_thermostat_openwindow_temp, legacy.toZigbee.zs_thermostat_binary_one, legacy.toZigbee.zs_thermostat_binary_two, - legacy.toZigbee.zs_thermostat_away_setting, legacy.toZigbee.zs_thermostat_local_schedule], + toZigbee: [ + legacy.toZigbee.zs_thermostat_current_heating_setpoint, + legacy.toZigbee.zs_thermostat_child_lock, + legacy.toZigbee.zs_thermostat_comfort_temp, + legacy.toZigbee.zs_thermostat_eco_temp, + legacy.toZigbee.zs_thermostat_preset_mode, + legacy.toZigbee.zs_thermostat_system_mode, + legacy.toZigbee.zs_thermostat_local_temperature_calibration, + legacy.toZigbee.zs_thermostat_current_heating_setpoint_auto, + legacy.toZigbee.zs_thermostat_openwindow_time, + legacy.toZigbee.zs_thermostat_openwindow_temp, + legacy.toZigbee.zs_thermostat_binary_one, + legacy.toZigbee.zs_thermostat_binary_two, + legacy.toZigbee.zs_thermostat_away_setting, + legacy.toZigbee.zs_thermostat_local_schedule, + ], onEvent: tuya.onEventSetLocalTime, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genBasic']); }, exposes: [ - e.child_lock(), e.comfort_temperature(), e.eco_temperature(), e.battery_voltage(), - e.numeric('current_heating_setpoint_auto', ea.STATE_SET).withValueMin(0.5).withValueMax(29.5) - .withValueStep(0.5).withUnit('°C').withDescription('Temperature setpoint automatic'), - e.climate().withSetpoint('current_heating_setpoint', 0.5, 29.5, 0.5, ea.STATE_SET) - .withLocalTemperature(ea.STATE).withLocalTemperatureCalibration(-12.5, 5.5, 0.1, ea.STATE_SET) + e.child_lock(), + e.comfort_temperature(), + e.eco_temperature(), + e.battery_voltage(), + e + .numeric('current_heating_setpoint_auto', ea.STATE_SET) + .withValueMin(0.5) + .withValueMax(29.5) + .withValueStep(0.5) + .withUnit('°C') + .withDescription('Temperature setpoint automatic'), + e + .climate() + .withSetpoint('current_heating_setpoint', 0.5, 29.5, 0.5, ea.STATE_SET) + .withLocalTemperature(ea.STATE) + .withLocalTemperatureCalibration(-12.5, 5.5, 0.1, ea.STATE_SET) .withSystemMode(['off', 'heat', 'auto'], ea.STATE_SET) .withPreset(['schedule', 'manual', 'holiday', 'boost']), - e.numeric('detectwindow_temperature', ea.STATE_SET).withUnit('°C').withDescription('Open window detection temperature') - .withValueMin(-10).withValueMax(35), - e.numeric('detectwindow_timeminute', ea.STATE_SET).withUnit('min').withDescription('Open window time in minute') - .withValueMin(0).withValueMax(1000), + e + .numeric('detectwindow_temperature', ea.STATE_SET) + .withUnit('°C') + .withDescription('Open window detection temperature') + .withValueMin(-10) + .withValueMax(35), + e + .numeric('detectwindow_timeminute', ea.STATE_SET) + .withUnit('min') + .withDescription('Open window time in minute') + .withValueMin(0) + .withValueMax(1000), e.binary('binary_one', ea.STATE_SET, 'ON', 'OFF').withDescription('Unknown binary one'), e.binary('binary_two', ea.STATE_SET, 'ON', 'OFF').withDescription('Unknown binary two'), e.binary('away_mode', ea.STATE, 'ON', 'OFF').withDescription('Away mode'), - e.composite('away_setting', 'away_setting', ea.STATE_SET) - .withFeature(e.away_preset_days()).setAccess('away_preset_days', ea.ALL) - .withFeature(e.away_preset_temperature()).setAccess('away_preset_temperature', ea.ALL) + e + .composite('away_setting', 'away_setting', ea.STATE_SET) + .withFeature(e.away_preset_days()) + .setAccess('away_preset_days', ea.ALL) + .withFeature(e.away_preset_temperature()) + .setAccess('away_preset_temperature', ea.ALL) .withFeature(e.numeric('away_preset_year', ea.ALL).withUnit('year').withDescription('Start away year 20xx')) .withFeature(e.numeric('away_preset_month', ea.ALL).withUnit('month').withDescription('Start away month')) .withFeature(e.numeric('away_preset_day', ea.ALL).withUnit('day').withDescription('Start away day')) @@ -538,14 +612,49 @@ const definitions: Definition[] = [ ...['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'].map((day) => { const expose = e.composite(day, day, ea.STATE_SET); [1, 2, 3, 4, 5, 6, 7, 8, 9].forEach((i) => { - expose.withFeature(e.numeric(`${day}_temp_${i}`, ea.ALL).withValueMin(0.5) - .withValueMax(29.5).withValueStep(0.5).withUnit('°C').withDescription(`Temperature ${i}`)); - expose.withFeature(e.enum(`${day}_hour_${i}`, ea.STATE_SET, - ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', - '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', - '20', '21', '22', '23', '24']).withDescription(`Hour TO for temp ${i}`)); - expose.withFeature(e.enum(`${day}_minute_${i}`, ea.STATE_SET, ['00', '15', '30', '45']) - .withDescription(`Minute TO for temp ${i}`)); + expose.withFeature( + e + .numeric(`${day}_temp_${i}`, ea.ALL) + .withValueMin(0.5) + .withValueMax(29.5) + .withValueStep(0.5) + .withUnit('°C') + .withDescription(`Temperature ${i}`), + ); + expose.withFeature( + e + .enum(`${day}_hour_${i}`, ea.STATE_SET, [ + '00', + '01', + '02', + '03', + '04', + '05', + '06', + '07', + '08', + '09', + '10', + '11', + '12', + '13', + '14', + '15', + '16', + '17', + '18', + '19', + '20', + '21', + '22', + '23', + '24', + ]) + .withDescription(`Hour TO for temp ${i}`), + ); + expose.withFeature( + e.enum(`${day}_minute_${i}`, ea.STATE_SET, ['00', '15', '30', '45']).withDescription(`Minute TO for temp ${i}`), + ); }); return expose; }), diff --git a/src/devices/lifecontrol.ts b/src/devices/lifecontrol.ts index ded4ecaefd581..661513a3a2ac7 100644 --- a/src/devices/lifecontrol.ts +++ b/src/devices/lifecontrol.ts @@ -1,6 +1,4 @@ -import {Definition, ModernExtend, Fz, Expose, Configure, OnEvent} from '../lib/types'; import * as exposes from '../lib/exposes'; -import * as globalStore from '../lib/store'; import { battery, electricityMeter, @@ -11,23 +9,27 @@ import { setupConfigureForReading, setupConfigureForReporting, } from '../lib/modernExtend'; +import * as globalStore from '../lib/store'; +import {Definition, ModernExtend, Fz, Expose, Configure, OnEvent} from '../lib/types'; const e = exposes.presets; function airQuality(): ModernExtend { const exposes: Expose[] = [e.temperature(), e.humidity(), e.voc().withUnit('ppb'), e.eco2()]; - const fromZigbee: Fz.Converter[] = [{ - cluster: 'msTemperatureMeasurement', - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - const temperature = parseFloat(msg.data['measuredValue']) / 100.0; - const humidity = parseFloat(msg.data['minMeasuredValue']) / 100.0; - const eco2 = parseFloat(msg.data['maxMeasuredValue']); - const voc = parseFloat(msg.data['tolerance']); - return {temperature, humidity, eco2, voc}; + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'msTemperatureMeasurement', + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + const temperature = parseFloat(msg.data['measuredValue']) / 100.0; + const humidity = parseFloat(msg.data['minMeasuredValue']) / 100.0; + const eco2 = parseFloat(msg.data['maxMeasuredValue']); + const voc = parseFloat(msg.data['tolerance']); + return {temperature, humidity, eco2, voc}; + }, }, - }]; + ]; return {exposes, fromZigbee, isModernExtend: true}; } @@ -62,7 +64,7 @@ function electricityMeterPoll(): ModernExtend { } catch (error) { // Do nothing } - }, 10*1000); // Every 10 seconds + }, 10 * 1000); // Every 10 seconds globalStore.putValue(device, 'interval', interval); } }; @@ -76,20 +78,14 @@ const definitions: Definition[] = [ model: 'MCLH-07', vendor: 'LifeControl', description: 'Water leakage sensor', - extend: [ - iasZoneAlarm({zoneType: 'water_leak', zoneAttributes: ['alarm_1', 'tamper', 'battery_low']}), - battery({dontDividePercentage: true}), - ], + extend: [iasZoneAlarm({zoneType: 'water_leak', zoneAttributes: ['alarm_1', 'tamper', 'battery_low']}), battery({dontDividePercentage: true})], }, { zigbeeModel: ['Door_Sensor'], model: 'MCLH-04', vendor: 'LifeControl', description: 'Open and close sensor', - extend: [ - iasZoneAlarm({zoneType: 'contact', zoneAttributes: ['alarm_1', 'tamper', 'battery_low']}), - battery({dontDividePercentage: true}), - ], + extend: [iasZoneAlarm({zoneType: 'contact', zoneAttributes: ['alarm_1', 'tamper', 'battery_low']}), battery({dontDividePercentage: true})], }, { zigbeeModel: ['vivi ZLight'], @@ -103,31 +99,21 @@ const definitions: Definition[] = [ model: 'MCLH-03', vendor: 'LifeControl', description: 'Smart socket', - extend: [ - onOff({powerOnBehavior: false}), - electricityMeter({configureReporting: false}), - electricityMeterPoll(), - ], + extend: [onOff({powerOnBehavior: false}), electricityMeter({configureReporting: false}), electricityMeterPoll()], }, { zigbeeModel: ['Motion_Sensor'], model: 'MCLH-05', vendor: 'LifeControl', description: 'Motion sensor', - extend: [ - iasZoneAlarm({zoneType: 'occupancy', zoneAttributes: ['alarm_1', 'tamper', 'battery_low']}), - battery({dontDividePercentage: true}), - ], + extend: [iasZoneAlarm({zoneType: 'occupancy', zoneAttributes: ['alarm_1', 'tamper', 'battery_low']}), battery({dontDividePercentage: true})], }, { zigbeeModel: ['VOC_Sensor'], model: 'MCLH-08', vendor: 'LifeControl', description: 'Air quality sensor', - extend: [ - airQuality(), - battery({dontDividePercentage: true}), - ], + extend: [airQuality(), battery({dontDividePercentage: true})], }, ]; diff --git a/src/devices/lightsolutions.ts b/src/devices/lightsolutions.ts index 3bae80238d4f2..575eb25799d2f 100644 --- a/src/devices/lightsolutions.ts +++ b/src/devices/lightsolutions.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light, onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/linkind.ts b/src/devices/linkind.ts index 3c596d39561b3..369644ba5fff6 100644 --- a/src/devices/linkind.ts +++ b/src/devices/linkind.ts @@ -1,12 +1,12 @@ -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; const e = exposes.presets; const ea = exposes.access; +import {light, onOff} from '../lib/modernExtend'; import * as globalStore from '../lib/store'; import {Definition} from '../lib/types'; -import {light, onOff} from '../lib/modernExtend'; const definitions: Definition[] = [ { @@ -15,14 +15,18 @@ const definitions: Definition[] = [ vendor: 'Linkind', description: 'Security keypad battery', meta: {battery: {voltageToPercentage: '3V_2100'}}, - fromZigbee: [fz.command_arm_with_transaction, fz.battery, fz.ias_ace_occupancy_with_timeout, - fz.ias_smoke_alarm_1, fz.command_panic], - exposes: [e.battery(), e.battery_voltage(), e.battery_low(), e.occupancy(), e.tamper(), + fromZigbee: [fz.command_arm_with_transaction, fz.battery, fz.ias_ace_occupancy_with_timeout, fz.ias_smoke_alarm_1, fz.command_panic], + exposes: [ + e.battery(), + e.battery_voltage(), + e.battery_low(), + e.occupancy(), + e.tamper(), e.numeric('action_code', ea.STATE).withDescription('Pin code introduced.'), e.numeric('action_transaction', ea.STATE).withDescription('Last action transaction number.'), e.text('action_zone', ea.STATE).withDescription('Alarm zone. Default value 23'), - e.action([ - 'panic', 'disarm', 'arm_day_zones', 'arm_all_zones', 'exit_delay', 'entry_delay'])], + e.action(['panic', 'disarm', 'arm_day_zones', 'arm_all_zones', 'exit_delay', 'entry_delay']), + ], toZigbee: [tz.arm_mode], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -31,15 +35,19 @@ const definitions: Definition[] = [ await reporting.batteryVoltage(endpoint); }, onEvent: async (type, data, device) => { - if (type === 'message' && data.type === 'commandGetPanelStatus' && data.cluster === 'ssIasAce' && - globalStore.hasValue(device.getEndpoint(1), 'panelStatus')) { + if ( + type === 'message' && + data.type === 'commandGetPanelStatus' && + data.cluster === 'ssIasAce' && + globalStore.hasValue(device.getEndpoint(1), 'panelStatus') + ) { const payload = { panelstatus: globalStore.getValue(device.getEndpoint(1), 'panelStatus'), - secondsremain: 0x00, audiblenotif: 0x00, alarmstatus: 0x00, + secondsremain: 0x00, + audiblenotif: 0x00, + alarmstatus: 0x00, }; - await device.getEndpoint(1).commandResponse( - 'ssIasAce', 'getPanelStatusRsp', payload, {}, data.meta.zclTransactionSequenceNumber, - ); + await device.getEndpoint(1).commandResponse('ssIasAce', 'getPanelStatusRsp', payload, {}, data.meta.zclTransactionSequenceNumber); } }, }, @@ -48,12 +56,36 @@ const definitions: Definition[] = [ model: 'ZS230002', vendor: 'Linkind', description: '5-key smart bulb dimmer switch light remote control', - fromZigbee: [fz.command_on, fz.command_off, fz.command_step, fz.command_move, - fz.command_stop, fz.command_move_to_color_temp, fz.command_move_to_color, - fz.command_move_to_level, fz.command_move_color_temperature, fz.battery], - exposes: [e.battery(), e.battery_low(), e.action(['on', 'off', 'brightness_step_up', - 'brightness_step_down', 'color_temperature_move', 'color_move', 'brightness_move_up', 'brightness_move_down', 'brightness_stop', - 'brightness_move_to_level', 'color_temperature_move_up', 'color_temperature_move_down'])], + fromZigbee: [ + fz.command_on, + fz.command_off, + fz.command_step, + fz.command_move, + fz.command_stop, + fz.command_move_to_color_temp, + fz.command_move_to_color, + fz.command_move_to_level, + fz.command_move_color_temperature, + fz.battery, + ], + exposes: [ + e.battery(), + e.battery_low(), + e.action([ + 'on', + 'off', + 'brightness_step_up', + 'brightness_step_down', + 'color_temperature_move', + 'color_move', + 'brightness_move_up', + 'brightness_move_down', + 'brightness_stop', + 'brightness_move_to_level', + 'color_temperature_move_up', + 'color_temperature_move_down', + ]), + ], toZigbee: [], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -189,9 +221,14 @@ const definitions: Definition[] = [ description: 'Water leak sensor', fromZigbee: [fz.ias_water_leak_alarm_1, fz.battery], toZigbee: [tz.LS21001_alert_behaviour], - exposes: [e.water_leak(), e.battery_low(), e.battery(), - e.enum('alert_behaviour', ea.STATE_SET, ['siren_led', 'siren', 'led', 'nothing']) - .withDescription('Controls behaviour of led/siren on alarm')], + exposes: [ + e.water_leak(), + e.battery_low(), + e.battery(), + e + .enum('alert_behaviour', ea.STATE_SET, ['siren_led', 'siren', 'led', 'nothing']) + .withDescription('Controls behaviour of led/siren on alarm'), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']); diff --git a/src/devices/linptech.ts b/src/devices/linptech.ts index 96e43986e079e..a133501017239 100644 --- a/src/devices/linptech.ts +++ b/src/devices/linptech.ts @@ -1,8 +1,8 @@ +import fz from '../converters/fromZigbee'; import * as exposes from '../lib/exposes'; import * as tuya from '../lib/tuya'; -import * as utils from '../lib/utils'; -import fz from '../converters/fromZigbee'; import {KeyValue, Definition, Tz, Fz} from '../lib/types'; +import * as utils from '../lib/utils'; const e = exposes.presets; const ea = exposes.access; @@ -12,21 +12,21 @@ const tzLocal = { key: ['motion_detection_distance', 'motion_detection_sensitivity', 'static_detection_sensitivity'], convertSet: async (entity, key, value, meta) => { switch (key) { - case 'motion_detection_distance': { - utils.assertNumber(value, 'motion_detection_distance'); - await entity.write('manuSpecificTuya_2', {57355: {value, type: 0x21}}); - break; - } - case 'motion_detection_sensitivity': { - utils.assertNumber(value, 'motion_detection_sensitivity'); - await entity.write('manuSpecificTuya_2', {57348: {value, type: 0x20}}); - break; - } - case 'static_detection_sensitivity': { - utils.assertNumber(value, 'static_detection_sensitivity'); - await entity.write('manuSpecificTuya_2', {57349: {value, type: 0x20}}); - break; - } + case 'motion_detection_distance': { + utils.assertNumber(value, 'motion_detection_distance'); + await entity.write('manuSpecificTuya_2', {57355: {value, type: 0x21}}); + break; + } + case 'motion_detection_sensitivity': { + utils.assertNumber(value, 'motion_detection_sensitivity'); + await entity.write('manuSpecificTuya_2', {57348: {value, type: 0x20}}); + break; + } + case 'static_detection_sensitivity': { + utils.assertNumber(value, 'static_detection_sensitivity'); + await entity.write('manuSpecificTuya_2', {57349: {value, type: 0x20}}); + break; + } } }, } satisfies Tz.Converter, @@ -76,22 +76,39 @@ const definitions: Definition[] = [ toZigbee: [tzLocal.TS0225, tuya.tz.datapoints], configure: tuya.configureMagicPacket, exposes: [ - e.occupancy().withDescription('Presence state'), e.illuminance().withUnit('lx'), + e.occupancy().withDescription('Presence state'), + e.illuminance().withUnit('lx'), e.numeric('target_distance', ea.STATE).withDescription('Distance to target').withUnit('cm'), - e.numeric('motion_detection_distance', ea.STATE_SET).withValueMin(0).withValueMax(600) - .withValueStep(75).withDescription('Motion detection distance').withUnit('cm'), + e + .numeric('motion_detection_distance', ea.STATE_SET) + .withValueMin(0) + .withValueMax(600) + .withValueStep(75) + .withDescription('Motion detection distance') + .withUnit('cm'), e.numeric('presence_keep_time', ea.STATE).withDescription('Presence keep time').withUnit('min'), - e.numeric('motion_detection_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(5) - .withValueStep(1).withDescription('Motion detection sensitivity'), - e.numeric('static_detection_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(5) - .withValueStep(1).withDescription('Static detection sensitivity'), - e.numeric('fading_time', ea.STATE_SET).withValueMin(0).withValueMax(10000).withValueStep(1) - .withUnit('s').withDescription('Time after which the device will check again for presence'), + e + .numeric('motion_detection_sensitivity', ea.STATE_SET) + .withValueMin(0) + .withValueMax(5) + .withValueStep(1) + .withDescription('Motion detection sensitivity'), + e + .numeric('static_detection_sensitivity', ea.STATE_SET) + .withValueMin(0) + .withValueMax(5) + .withValueStep(1) + .withDescription('Static detection sensitivity'), + e + .numeric('fading_time', ea.STATE_SET) + .withValueMin(0) + .withValueMax(10000) + .withValueStep(1) + .withUnit('s') + .withDescription('Time after which the device will check again for presence'), ], meta: { - tuyaDatapoints: [ - [101, 'fading_time', tuya.valueConverter.raw], - ], + tuyaDatapoints: [[101, 'fading_time', tuya.valueConverter.raw]], }, }, ]; diff --git a/src/devices/livingwise.ts b/src/devices/livingwise.ts index 8821033083b2e..ca82ce22c90e1 100644 --- a/src/devices/livingwise.ts +++ b/src/devices/livingwise.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import {light, onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const e = exposes.presets; @@ -51,11 +51,31 @@ const definitions: Definition[] = [ vendor: 'LivingWise', description: 'Scene controller ', fromZigbee: [fz.orvibo_raw_2], - exposes: [e.action([ - 'button_1_click', 'button_1_hold', 'button_1_release', 'button_2_click', 'button_2_hold', 'button_2_release', - 'button_3_click', 'button_3_hold', 'button_3_release', 'button_4_click', 'button_4_hold', 'button_4_release', - 'button_5_click', 'button_5_hold', 'button_5_release', 'button_6_click', 'button_6_hold', 'button_6_release', - 'button_7_click', 'button_7_hold', 'button_7_release'])], + exposes: [ + e.action([ + 'button_1_click', + 'button_1_hold', + 'button_1_release', + 'button_2_click', + 'button_2_hold', + 'button_2_release', + 'button_3_click', + 'button_3_hold', + 'button_3_release', + 'button_4_click', + 'button_4_hold', + 'button_4_release', + 'button_5_click', + 'button_5_hold', + 'button_5_release', + 'button_6_click', + 'button_6_hold', + 'button_6_release', + 'button_7_click', + 'button_7_hold', + 'button_7_release', + ]), + ], toZigbee: [], }, ]; diff --git a/src/devices/livolo.ts b/src/devices/livolo.ts index 1115c36ee01e1..447c59bf1c3ef 100644 --- a/src/devices/livolo.ts +++ b/src/devices/livolo.ts @@ -1,8 +1,8 @@ -import {Definition, Zh} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as globalStore from '../lib/store'; +import {Definition, Zh} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -31,7 +31,7 @@ const definitions: Definition[] = [ fromZigbee: [fz.livolo_switch_state, fz.livolo_switch_state_raw, fz.livolo_new_switch_state_4gang], toZigbee: [tz.livolo_socket_switch_on_off], endpoint: (device) => { - return {'left': 6, 'right': 6, 'bottom_left': 6, 'bottom_right': 6}; + return {left: 6, right: 6, bottom_left: 6, bottom_right: 6}; }, configure: poll, onEvent: async (type, data, device) => { @@ -42,7 +42,7 @@ const definitions: Definition[] = [ if (['start', 'deviceAnnounce'].includes(type)) { await poll(device); if (!globalStore.hasValue(device, 'interval')) { - const interval = setInterval(async () => await poll(device), 300*1000); + const interval = setInterval(async () => await poll(device), 300 * 1000); globalStore.putValue(device, 'interval', interval); } } @@ -51,8 +51,14 @@ const definitions: Definition[] = [ if (data.data[0] === 0x7a && data.data[1] === 0xd1) { const endpoint = device.getEndpoint(6); if (dp === 0x01) { - const options = {manufacturerCode: 0x1ad2, disableDefaultResponse: true, disableResponse: true, - reservedBits: 3, direction: 1, writeUndiv: true}; + const options = { + manufacturerCode: 0x1ad2, + disableDefaultResponse: true, + disableResponse: true, + reservedBits: 3, + direction: 1, + writeUndiv: true, + }; const payload = {0x2002: {value: [0, 0, 0, 0, 0, 0, 0], type: 0x0e}}; await endpoint.readResponse('genPowerCfg', 0xe9, payload, options); } @@ -70,7 +76,7 @@ const definitions: Definition[] = [ exposes: [e.switch()], configure: poll, endpoint: (device) => { - return {'left': 6, 'right': 6}; + return {left: 6, right: 6}; }, onEvent: async (type, data, device) => { if (type === 'stop') { @@ -82,7 +88,7 @@ const definitions: Definition[] = [ if (!globalStore.hasValue(device, 'interval')) { const interval = setInterval(async () => { await poll(device); - }, 300*1000); // Every 300 seconds + }, 300 * 1000); // Every 300 seconds globalStore.putValue(device, 'interval', interval); } } @@ -98,7 +104,7 @@ const definitions: Definition[] = [ exposes: [e.switch().withEndpoint('left'), e.switch().withEndpoint('right')], configure: poll, endpoint: (device) => { - return {'left': 6, 'right': 6}; + return {left: 6, right: 6}; }, onEvent: async (type, data, device) => { if (type === 'stop') { @@ -110,7 +116,7 @@ const definitions: Definition[] = [ if (!globalStore.hasValue(device, 'interval')) { const interval = setInterval(async () => { await poll(device); - }, 300*1000); // Every 300 seconds + }, 300 * 1000); // Every 300 seconds globalStore.putValue(device, 'interval', interval); } } @@ -127,7 +133,7 @@ const definitions: Definition[] = [ exposes: [e.switch().withEndpoint('left'), e.switch().withEndpoint('right')], configure: poll, endpoint: (device) => { - return {'left': 6, 'right': 6}; + return {left: 6, right: 6}; }, onEvent: async (type, data, device) => { if (type === 'stop') { @@ -139,7 +145,7 @@ const definitions: Definition[] = [ if (!globalStore.hasValue(device, 'interval')) { const interval = setInterval(async () => { await poll(device); - }, 300*1000); // Every 300 seconds + }, 300 * 1000); // Every 300 seconds globalStore.putValue(device, 'interval', interval); } } @@ -164,7 +170,7 @@ const definitions: Definition[] = [ if (!globalStore.hasValue(device, 'interval')) { const interval = setInterval(async () => { await poll(device); - }, 300*1000); // Every 300 seconds + }, 300 * 1000); // Every 300 seconds globalStore.putValue(device, 'interval', interval); } } @@ -180,7 +186,7 @@ const definitions: Definition[] = [ exposes: [e.light_brightness()], configure: poll, endpoint: (device) => { - return {'left': 6, 'right': 6}; + return {left: 6, right: 6}; }, onEvent: async (type, data, device) => { if (type === 'stop') { @@ -191,7 +197,7 @@ const definitions: Definition[] = [ await poll(device); const interval = setInterval(async () => { await poll(device); - }, 300*1000); // Every 300 seconds + }, 300 * 1000); // Every 300 seconds globalStore.putValue(device, 'interval', interval); } }, @@ -205,17 +211,12 @@ const definitions: Definition[] = [ toZigbee: [tz.livolo_cover_state, tz.livolo_cover_position, tz.livolo_cover_options], exposes: [ e.cover_position().setAccess('position', ea.STATE_SET), - e.composite('options', 'options', ea.STATE_SET) + e + .composite('options', 'options', ea.STATE_SET) .withDescription('Motor options') - .withFeature(e.numeric('motor_speed', ea.STATE_SET) - .withValueMin(20) - .withValueMax(40) - .withDescription('Motor speed') - .withUnit('rpm')) - .withFeature(e.enum('motor_direction', ea.STATE_SET, ['FORWARD', 'REVERSE']) - .withDescription('Motor direction')), - e.binary('moving', ea.STATE, true, false) - .withDescription('Motor is moving'), + .withFeature(e.numeric('motor_speed', ea.STATE_SET).withValueMin(20).withValueMax(40).withDescription('Motor speed').withUnit('rpm')) + .withFeature(e.enum('motor_direction', ea.STATE_SET, ['FORWARD', 'REVERSE']).withDescription('Motor direction')), + e.binary('moving', ea.STATE, true, false).withDescription('Motor is moving'), ], configure: poll, onEvent: async (type, data, device) => { @@ -227,7 +228,7 @@ const definitions: Definition[] = [ await poll(device); const interval = setInterval(async () => { await poll(device); - }, 300*1000); // Every 300 seconds + }, 300 * 1000); // Every 300 seconds globalStore.putValue(device, 'interval', interval); } // This is needed while pairing in order to let the device know that the interview went right and prevent @@ -237,8 +238,14 @@ const definitions: Definition[] = [ if (data.data[0] === 0x7a && data.data[1] === 0xd1) { const endpoint = device.getEndpoint(6); if (dp === 0x02) { - const options = {manufacturerCode: 0x1ad2, disableDefaultResponse: true, disableResponse: true, - reservedBits: 3, direction: 1, writeUndiv: true}; + const options = { + manufacturerCode: 0x1ad2, + disableDefaultResponse: true, + disableResponse: true, + reservedBits: 3, + direction: 1, + writeUndiv: true, + }; const payload = {0x0802: {value: [data.data[3], 0, 0, 0, 0, 0, 0], type: data.data[2]}}; await endpoint.readResponse('genPowerCfg', 0xe9, payload, options); } @@ -251,9 +258,7 @@ const definitions: Definition[] = [ model: 'TI0001-pir', description: 'Zigbee motion Sensor', vendor: 'Livolo', - exposes: [ - e.occupancy(), - ], + exposes: [e.occupancy()], fromZigbee: [fz.livolo_pir_state], toZigbee: [], configure: poll, @@ -265,7 +270,7 @@ const definitions: Definition[] = [ if (['start', 'deviceAnnounce'].includes(type)) { await poll(device); if (!globalStore.hasValue(device, 'interval')) { - const interval = setInterval(async () => await poll(device), 10*1000); + const interval = setInterval(async () => await poll(device), 10 * 1000); globalStore.putValue(device, 'interval', interval); } } @@ -274,8 +279,14 @@ const definitions: Definition[] = [ if (data.data[0] === 0x7a && data.data[1] === 0xd1) { const endpoint = device.getEndpoint(6); if (dp === 0x01) { - const options = {manufacturerCode: 0x1ad2, disableDefaultResponse: true, disableResponse: true, - reservedBits: 3, direction: 1, writeUndiv: true}; + const options = { + manufacturerCode: 0x1ad2, + disableDefaultResponse: true, + disableResponse: true, + reservedBits: 3, + direction: 1, + writeUndiv: true, + }; const payload = {0x2002: {value: [0, 0, 0, 0, 0, 0, 0], type: 0x0e}}; await endpoint.readResponse('genPowerCfg', 0xe9, payload, options); } @@ -288,9 +299,7 @@ const definitions: Definition[] = [ model: 'TI0001-hygrometer', description: 'Zigbee Digital Humidity and Temperature Sensor', vendor: 'Livolo', - exposes: [ - e.humidity(), e.temperature(), - ], + exposes: [e.humidity(), e.temperature()], fromZigbee: [fz.livolo_hygrometer_state], toZigbee: [], configure: poll, @@ -302,7 +311,7 @@ const definitions: Definition[] = [ if (['start', 'deviceAnnounce'].includes(type)) { await poll(device); if (!globalStore.hasValue(device, 'interval')) { - const interval = setInterval(async () => await poll(device), 60*1000); + const interval = setInterval(async () => await poll(device), 60 * 1000); globalStore.putValue(device, 'interval', interval); } } @@ -311,8 +320,14 @@ const definitions: Definition[] = [ if (data.data[0] === 0x7a && data.data[1] === 0xd1) { const endpoint = device.getEndpoint(6); if (dp === 0x02) { - const options = {manufacturerCode: 0x1ad2, disableDefaultResponse: true, disableResponse: true, - reservedBits: 3, direction: 1, writeUndiv: true}; + const options = { + manufacturerCode: 0x1ad2, + disableDefaultResponse: true, + disableResponse: true, + reservedBits: 3, + direction: 1, + writeUndiv: true, + }; const payload = {0x2002: {value: [data.data[3], 0, 0, 0, 0, 0, 0], type: data.data[2]}}; await endpoint.readResponse('genPowerCfg', 0xe9, payload, options); } diff --git a/src/devices/lixee.ts b/src/devices/lixee.ts index bc37a672e3013..82661f66d96ab 100644 --- a/src/devices/lixee.ts +++ b/src/devices/lixee.ts @@ -1,17 +1,16 @@ -import {Definition, Fz, Tz, KeyValue, Zh} from '../lib/types'; -/* eslint-disable camelcase */ -/* eslint-disable max-len */ -import * as exposes from '../lib/exposes'; -import * as globalStore from '../lib/store'; +import fz from '../converters/fromZigbee'; import {repInterval} from '../lib/constants'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; -import fz from '../converters/fromZigbee'; +import * as globalStore from '../lib/store'; +import {Definition, Fz, Tz, KeyValue, Zh} from '../lib/types'; const ea = exposes.access; const e = exposes.presets; -import * as utils from '../lib/utils'; -import * as ota from '../lib/ota'; import {Buffer} from 'buffer'; + import {logger} from '../lib/logger'; +import * as ota from '../lib/ota'; +import * as utils from '../lib/utils'; const NS = 'zhc:lixee'; /* Start ZiPulses */ @@ -42,7 +41,7 @@ const tzSeMetering: Tz.Converter = { const payload = {768: {value: val, type: 48}}; await entity.write('seMetering', payload); await entity.read('seMetering', [key]); - return {state: {'unitOfMeasure': value}}; + return {state: {unitOfMeasure: value}}; } else { await entity.write('seMetering', { [key]: value, @@ -56,7 +55,6 @@ const tzSeMetering: Tz.Converter = { // }, }; - const fzZiPulses: Fz.Converter = { cluster: 'seMetering', type: ['attributeReport', 'readResponse'], @@ -77,7 +75,6 @@ const fzZiPulses: Fz.Converter = { }, }; - /* End ZiPulses */ const fzLocal = { @@ -116,7 +113,10 @@ const fzLocal = { ]; for (const at of elements) { - const at_snake = at.split(/(?=[A-Z])/).join('_').toLowerCase(); + const at_snake = at + .split(/(?=[A-Z])/) + .join('_') + .toLowerCase(); if (msg.data[at] != null) { result[at_snake] = msg.data[at]; } @@ -172,7 +172,10 @@ const fzLocal = { ]; const kWh_p = options && options.kWh_precision ? options.kWh_precision : 0; for (const at of elements) { - const at_snake = at.split(/(?=[A-Z])/).join('_').toLowerCase(); + const at_snake = at + .split(/(?=[A-Z])/) + .join('_') + .toLowerCase(); let val = msg.data[at]; if (val != null) { if (val.hasOwnProperty('type') && val.type === 'Buffer') { @@ -186,172 +189,172 @@ const fzLocal = { val = val.replace(/\s+/g, ' ').trim(); // Remove extra and leading spaces } switch (at) { - case 'activeEnergyOutD01': - case 'activeEnergyOutD02': - case 'activeEnergyOutD03': - case 'activeEnergyOutD04': - // @ts-expect-error - val = utils.precisionRound(val / 1000, kWh_p); // from Wh to kWh - break; - case 'relais': { - // relais is a decimal value representing the bits - // of 8 virtual dry contacts. - // 0 for an open relay - // 1 for a closed relay - // relais1 Hot water === legacy dry contact - // relais2 Main heater - // relais3 Secondary heater - // relais4 AC or Heat pump - // relais5 EV charge - // relais6 Storage or injection - // relais7 Unassigned - // relais8 Unassigned - const relais_breakout: KeyValue = {}; - for (let i = 0; i < 8; i++) { - relais_breakout[at_snake + (i+1)] = (val & (1<>> i; - } - result[at_snake + '_breakout'] = relais_breakout; - break; - } - case 'statusRegister': { - // val is a String representing hex. - // Must convert - const valhex = Number('0x' + val); - const statusRegister_breakout: KeyValue = {}; - // contact sec - statusRegister_breakout['contact_sec'] = (valhex & 0x1) == 1 ? 'ouvert' : 'ferme'; - // organe de coupure - switch ((valhex >>> 1) & 0x7) { - case 0: - statusRegister_breakout['organe_coupure'] = 'ferme'; - break; - case 1: - statusRegister_breakout['organe_coupure'] = 'surpuissance'; - break; - case 2: - statusRegister_breakout['organe_coupure'] = 'surtension'; - break; - case 3: - statusRegister_breakout['organe_coupure'] = 'delestage'; - break; - case 4: - statusRegister_breakout['organe_coupure'] = 'ordre_CPL_Euridis'; - break; - case 5: - statusRegister_breakout['organe_coupure'] = 'surchauffe_surcourant'; - break; - case 6: - statusRegister_breakout['organe_coupure'] = 'surchauffe_simple'; - break; - } - // etat cache borne distributeur - statusRegister_breakout['cache_borne_dist'] = ((valhex >>> 4) & 0x1) == 0 ? 'ferme' : 'ouvert'; - // bit 5 inutilise - // surtension sur une des phases - statusRegister_breakout['surtension_phase'] = (valhex >>> 6) & 0x1; - // depassement puissance de reference - statusRegister_breakout['depassement_ref_pow'] = (valhex >>> 7) & 0x1; - // consommateur ou producteur - statusRegister_breakout['producteur'] = (valhex >>> 8) & 0x1; - // sens de l'energie active - statusRegister_breakout['sens_energie_active'] = ((valhex >>> 9) & 0x1) == 0 ? 'positive' : 'negative'; - // tarif en cours sur le contrat fourniture - statusRegister_breakout['tarif_four'] = 'index_' + (((valhex >>> 10) & 0xF) + 1); - // tarif en cours sur le contrat distributeur - statusRegister_breakout['tarif_dist'] = 'index_' + (((valhex >>> 14) & 0x3) + 1); - // mode degrade de l'horloge - statusRegister_breakout['horloge'] = ((valhex >>> 16) & 0x1) == 0 ? 'correcte' : 'degradee'; - // TIC historique ou standard - statusRegister_breakout['type_tic'] = ((valhex >>> 17) & 0x1) == 0 ? 'historique' : 'standard'; - // bit 18 inutilise - // etat sortie communicateur Euridis - switch ((valhex >>> 19) & 0x3) { - case 0: - statusRegister_breakout['comm_euridis'] = 'desactivee'; - break; - case 1: - statusRegister_breakout['comm_euridis'] = 'activee sans securite'; - break; - case 3: - statusRegister_breakout['comm_euridis'] = 'activee avec securite'; - break; - } - // etat CPL - switch ((valhex >>> 21) & 0x3) { - case 0: - statusRegister_breakout['etat_cpl'] = 'nouveau_deverrouille'; - break; - case 1: - statusRegister_breakout['etat_cpl'] = 'nouveau_verrouille'; - break; - case 2: - statusRegister_breakout['etat_cpl'] = 'enregistre'; - break; - } - // synchronisation CPL - statusRegister_breakout['sync_cpl'] = ((valhex >>> 23) & 0x1) == 0 ? 'non_synchronise' : 'synchronise'; - // couleur du jour contrat TEMPO historique - switch ((valhex >>> 24) & 0x3) { - case 0: - statusRegister_breakout['tempo_jour'] = 'UNDEF'; - break; - case 1: - statusRegister_breakout['tempo_jour'] = 'BLEU'; - break; - case 2: - statusRegister_breakout['tempo_jour'] = 'BLANC'; - break; - case 3: - statusRegister_breakout['tempo_jour'] = 'ROUGE'; - break; - } - // couleur demain contrat TEMPO historique - switch ((valhex >>> 26) & 0x3) { - case 0: - statusRegister_breakout['tempo_demain'] = 'UNDEF'; + case 'activeEnergyOutD01': + case 'activeEnergyOutD02': + case 'activeEnergyOutD03': + case 'activeEnergyOutD04': + // @ts-expect-error + val = utils.precisionRound(val / 1000, kWh_p); // from Wh to kWh break; - case 1: - statusRegister_breakout['tempo_demain'] = 'BLEU'; - break; - case 2: - statusRegister_breakout['tempo_demain'] = 'BLANC'; - break; - case 3: - statusRegister_breakout['tempo_demain'] = 'ROUGE'; - break; - } - // preavis pointe mobile - switch ((valhex >>> 28) & 0x3) { - case 0: - statusRegister_breakout['preavis_pointe_mobile'] = 'AUCUN'; - break; - case 1: - statusRegister_breakout['preavis_pointe_mobile'] = 'PM1'; - break; - case 2: - statusRegister_breakout['preavis_pointe_mobile'] = 'PM2'; - break; - case 3: - statusRegister_breakout['preavis_pointe_mobile'] = 'PM3'; + case 'relais': { + // relais is a decimal value representing the bits + // of 8 virtual dry contacts. + // 0 for an open relay + // 1 for a closed relay + // relais1 Hot water === legacy dry contact + // relais2 Main heater + // relais3 Secondary heater + // relais4 AC or Heat pump + // relais5 EV charge + // relais6 Storage or injection + // relais7 Unassigned + // relais8 Unassigned + const relais_breakout: KeyValue = {}; + for (let i = 0; i < 8; i++) { + relais_breakout[at_snake + (i + 1)] = (val & (1 << i)) >>> i; + } + result[at_snake + '_breakout'] = relais_breakout; break; } - // pointe mobile - switch ((valhex >>> 30) & 0x3) { - case 0: - statusRegister_breakout['pointe_mobile'] = 'AUCUN'; - break; - case 1: - statusRegister_breakout['pointe_mobile'] = 'PM1'; - break; - case 2: - statusRegister_breakout['pointe_mobile'] = 'PM2'; - break; - case 3: - statusRegister_breakout['pointe_mobile'] = 'PM3'; - break; + case 'statusRegister': { + // val is a String representing hex. + // Must convert + const valhex = Number('0x' + val); + const statusRegister_breakout: KeyValue = {}; + // contact sec + statusRegister_breakout['contact_sec'] = (valhex & 0x1) == 1 ? 'ouvert' : 'ferme'; + // organe de coupure + switch ((valhex >>> 1) & 0x7) { + case 0: + statusRegister_breakout['organe_coupure'] = 'ferme'; + break; + case 1: + statusRegister_breakout['organe_coupure'] = 'surpuissance'; + break; + case 2: + statusRegister_breakout['organe_coupure'] = 'surtension'; + break; + case 3: + statusRegister_breakout['organe_coupure'] = 'delestage'; + break; + case 4: + statusRegister_breakout['organe_coupure'] = 'ordre_CPL_Euridis'; + break; + case 5: + statusRegister_breakout['organe_coupure'] = 'surchauffe_surcourant'; + break; + case 6: + statusRegister_breakout['organe_coupure'] = 'surchauffe_simple'; + break; + } + // etat cache borne distributeur + statusRegister_breakout['cache_borne_dist'] = ((valhex >>> 4) & 0x1) == 0 ? 'ferme' : 'ouvert'; + // bit 5 inutilise + // surtension sur une des phases + statusRegister_breakout['surtension_phase'] = (valhex >>> 6) & 0x1; + // depassement puissance de reference + statusRegister_breakout['depassement_ref_pow'] = (valhex >>> 7) & 0x1; + // consommateur ou producteur + statusRegister_breakout['producteur'] = (valhex >>> 8) & 0x1; + // sens de l'energie active + statusRegister_breakout['sens_energie_active'] = ((valhex >>> 9) & 0x1) == 0 ? 'positive' : 'negative'; + // tarif en cours sur le contrat fourniture + statusRegister_breakout['tarif_four'] = 'index_' + (((valhex >>> 10) & 0xf) + 1); + // tarif en cours sur le contrat distributeur + statusRegister_breakout['tarif_dist'] = 'index_' + (((valhex >>> 14) & 0x3) + 1); + // mode degrade de l'horloge + statusRegister_breakout['horloge'] = ((valhex >>> 16) & 0x1) == 0 ? 'correcte' : 'degradee'; + // TIC historique ou standard + statusRegister_breakout['type_tic'] = ((valhex >>> 17) & 0x1) == 0 ? 'historique' : 'standard'; + // bit 18 inutilise + // etat sortie communicateur Euridis + switch ((valhex >>> 19) & 0x3) { + case 0: + statusRegister_breakout['comm_euridis'] = 'desactivee'; + break; + case 1: + statusRegister_breakout['comm_euridis'] = 'activee sans securite'; + break; + case 3: + statusRegister_breakout['comm_euridis'] = 'activee avec securite'; + break; + } + // etat CPL + switch ((valhex >>> 21) & 0x3) { + case 0: + statusRegister_breakout['etat_cpl'] = 'nouveau_deverrouille'; + break; + case 1: + statusRegister_breakout['etat_cpl'] = 'nouveau_verrouille'; + break; + case 2: + statusRegister_breakout['etat_cpl'] = 'enregistre'; + break; + } + // synchronisation CPL + statusRegister_breakout['sync_cpl'] = ((valhex >>> 23) & 0x1) == 0 ? 'non_synchronise' : 'synchronise'; + // couleur du jour contrat TEMPO historique + switch ((valhex >>> 24) & 0x3) { + case 0: + statusRegister_breakout['tempo_jour'] = 'UNDEF'; + break; + case 1: + statusRegister_breakout['tempo_jour'] = 'BLEU'; + break; + case 2: + statusRegister_breakout['tempo_jour'] = 'BLANC'; + break; + case 3: + statusRegister_breakout['tempo_jour'] = 'ROUGE'; + break; + } + // couleur demain contrat TEMPO historique + switch ((valhex >>> 26) & 0x3) { + case 0: + statusRegister_breakout['tempo_demain'] = 'UNDEF'; + break; + case 1: + statusRegister_breakout['tempo_demain'] = 'BLEU'; + break; + case 2: + statusRegister_breakout['tempo_demain'] = 'BLANC'; + break; + case 3: + statusRegister_breakout['tempo_demain'] = 'ROUGE'; + break; + } + // preavis pointe mobile + switch ((valhex >>> 28) & 0x3) { + case 0: + statusRegister_breakout['preavis_pointe_mobile'] = 'AUCUN'; + break; + case 1: + statusRegister_breakout['preavis_pointe_mobile'] = 'PM1'; + break; + case 2: + statusRegister_breakout['preavis_pointe_mobile'] = 'PM2'; + break; + case 3: + statusRegister_breakout['preavis_pointe_mobile'] = 'PM3'; + break; + } + // pointe mobile + switch ((valhex >>> 30) & 0x3) { + case 0: + statusRegister_breakout['pointe_mobile'] = 'AUCUN'; + break; + case 1: + statusRegister_breakout['pointe_mobile'] = 'PM1'; + break; + case 2: + statusRegister_breakout['pointe_mobile'] = 'PM2'; + break; + case 3: + statusRegister_breakout['pointe_mobile'] = 'PM3'; + break; + } + result[at_snake + '_breakout'] = statusRegister_breakout; } - result[at_snake + '_breakout'] = statusRegister_breakout; - } } result[at_snake] = val; } @@ -383,40 +386,45 @@ const fzLocal = { ]; const kWh_p = options && options.kWh_precision ? options.kWh_precision : 0; for (const at of elements) { - const at_snake = at.split(/(?=[A-Z])/).join('_').toLowerCase(); + const at_snake = at + .split(/(?=[A-Z])/) + .join('_') + .toLowerCase(); const val = msg.data[at]; if (val != null) { result[at_snake] = val; // By default we assign raw value switch (at) { - // If we receive a Buffer, transform to human readable text - case 'meterSerialNumber': - case 'siteId': - if (Buffer.isBuffer(val)) { - result[at_snake] = val.toString(); - } - break; - case 'currentSummDelivered': - case 'currentSummReceived': - case 'currentTier1SummDelivered': - case 'currentTier2SummDelivered': - case 'currentTier3SummDelivered': - case 'currentTier4SummDelivered': - case 'currentTier5SummDelivered': - case 'currentTier6SummDelivered': - case 'currentTier7SummDelivered': - case 'currentTier8SummDelivered': - case 'currentTier9SummDelivered': - case 'currentTier10SummDelivered': - // @ts-expect-error - result[at_snake] = utils.precisionRound(((val[0] << 32) + val[1]) / 1000, kWh_p); // Wh to kWh - break; + // If we receive a Buffer, transform to human readable text + case 'meterSerialNumber': + case 'siteId': + if (Buffer.isBuffer(val)) { + result[at_snake] = val.toString(); + } + break; + case 'currentSummDelivered': + case 'currentSummReceived': + case 'currentTier1SummDelivered': + case 'currentTier2SummDelivered': + case 'currentTier3SummDelivered': + case 'currentTier4SummDelivered': + case 'currentTier5SummDelivered': + case 'currentTier6SummDelivered': + case 'currentTier7SummDelivered': + case 'currentTier8SummDelivered': + case 'currentTier9SummDelivered': + case 'currentTier10SummDelivered': + // @ts-expect-error + result[at_snake] = utils.precisionRound(((val[0] << 32) + val[1]) / 1000, kWh_p); // Wh to kWh + break; } } } // TODO: Check if all tarifs which doesn't publish "currentSummDelivered" use just Tier1 & Tier2 - if (result['current_summ_delivered'] == 0 && + if ( + result['current_summ_delivered'] == 0 && // @ts-expect-error - (result['current_tier1_summ_delivered'] > 0 || result['current_tier2_summ_delivered'] > 0)) { + (result['current_tier1_summ_delivered'] > 0 || result['current_tier2_summ_delivered'] > 0) + ) { // @ts-expect-error result['current_summ_delivered'] = result['current_tier1_summ_delivered'] + result['current_tier2_summ_delivered']; } @@ -425,73 +433,33 @@ const fzLocal = { } satisfies Fz.Converter, }; - // we are doing it with exclusion and not inclusion because the list is dynamic (based on zlinky mode), // and change based on that. Just some few attributes are useless, so we exclude them const tarifsDef = { histo_BASE: { fname: 'Historique - BASE', - currentTarf: 'BASE', excluded: [ - 'HCHC', - 'HCHP', - 'HHPHC', - 'EJPHN', - 'EJPHPM', - 'BBRHCJB', - 'BBRHPJB', - 'BBRHCJW', - 'BBRHPJW', - 'BBRHCJR', - 'BBRHPJR', - 'DEMAIN', - 'PEJP', - ], + currentTarf: 'BASE', + excluded: ['HCHC', 'HCHP', 'HHPHC', 'EJPHN', 'EJPHPM', 'BBRHCJB', 'BBRHPJB', 'BBRHCJW', 'BBRHPJW', 'BBRHCJR', 'BBRHPJR', 'DEMAIN', 'PEJP'], }, histo_HCHP: { fname: 'Historique - HCHP', - currentTarf: 'HC..', excluded: [ - 'BASE', - 'EJPHN', - 'EJPHPM', - 'BBRHCJB', - 'BBRHPJB', - 'BBRHCJW', - 'BBRHPJW', - 'BBRHCJR', - 'BBRHPJR', - 'DEMAIN', - 'PEJP', - ], + currentTarf: 'HC..', + excluded: ['BASE', 'EJPHN', 'EJPHPM', 'BBRHCJB', 'BBRHPJB', 'BBRHCJW', 'BBRHPJW', 'BBRHCJR', 'BBRHPJR', 'DEMAIN', 'PEJP'], }, histo_EJP: { fname: 'Historique - EJP', - currentTarf: 'EJP.', excluded: [ - 'BASE', - 'HCHC', - 'HCHP', - 'BBRHCJB', - 'BBRHPJB', - 'BBRHCJW', - 'BBRHPJW', - 'BBRHCJR', - 'BBRHPJR', - 'DEMAIN', - ], + currentTarf: 'EJP.', + excluded: ['BASE', 'HCHC', 'HCHP', 'BBRHCJB', 'BBRHPJB', 'BBRHCJW', 'BBRHPJW', 'BBRHCJR', 'BBRHPJR', 'DEMAIN'], }, histo_BBR: { fname: 'Historique - BBR', - currentTarf: 'BBR', excluded: [ - 'BASE', - 'HCHC', - 'HCHP', - 'EJPHN', - 'EJPHPM', - 'PEJP', - ], + currentTarf: 'BBR', + excluded: ['BASE', 'HCHC', 'HCHP', 'EJPHN', 'EJPHPM', 'PEJP'], }, stand_SEM_WE_LUNDI: { fname: 'Standard - Sem WE Lundi', - currentTarf: 'SEM WE LUNDI', excluded: [ + currentTarf: 'SEM WE LUNDI', + excluded: [ 'EASF07', 'EASF08', 'EASF09', @@ -513,7 +481,8 @@ const tarifsDef = { }, stand_SEM_WE_MERCR: { fname: 'Standard - Sem WE Mercredi', - currentTarf: 'SEM WE MERCREDI', excluded: [ + currentTarf: 'SEM WE MERCREDI', + excluded: [ 'EASF07', 'EASF08', 'EASF09', @@ -535,7 +504,8 @@ const tarifsDef = { }, stand_SEM_WE_VENDR: { fname: 'Standard - Sem WE Vendredi', - currentTarf: 'SEM WE VENDREDI', excluded: [ + currentTarf: 'SEM WE VENDREDI', + excluded: [ 'EASF07', 'EASF08', 'EASF09', @@ -584,7 +554,8 @@ const tarifsDef = { }, stand_HPHC: { fname: 'Standard - Heure Pleine Heure Creuse', - currentTarf: 'H PLEINE/CREUSE', excluded: [ + currentTarf: 'H PLEINE/CREUSE', + excluded: [ 'EASF03', 'EASF04', 'EASF05', @@ -609,7 +580,8 @@ const tarifsDef = { }, stand_H_SUPER_CREUSES: { fname: 'Standard - Heures Super Creuses', - currentTarf: 'H SUPER CREUSES', excluded: [ + currentTarf: 'H SUPER CREUSES', + excluded: [ 'EASF07', 'EASF08', 'EASF09', @@ -628,7 +600,8 @@ const tarifsDef = { }, stand_TEMPO: { fname: 'Standard - TEMPO', - currentTarf: 'TEMPO', excluded: [ + currentTarf: 'TEMPO', + excluded: [ 'EASF07', 'EASF08', 'EASF09', @@ -649,7 +622,8 @@ const tarifsDef = { }, stand_ZEN_FLEX: { fname: 'Standard - ZEN Flex', - currentTarf: 'ZEN Flex', excluded: [ + currentTarf: 'ZEN Flex', + excluded: [ 'EASF05', 'EASF06', 'EASF07', @@ -671,7 +645,6 @@ const tarifsDef = { }, }; - const linkyModeDef = { standard: 'standard', legacy: 'historique', @@ -690,132 +663,958 @@ const clustersDef = { _0x0B01: 'haMeterIdentification', // 0x0B01 }; - // full list available on https://github.com/fairecasoimeme/Zlinky_TIC/blob/master/README.md // Properties must be EAXCTLY ".split(/(?=[A-Z])/).join('_').toLowerCase()" of att const allPhaseData = [ - {cluster: clustersDef._0x0702, att: 'currentSummDelivered', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('EAST', ea.STATE).withUnit('kWh').withProperty('current_summ_delivered').withDescription('Total active power delivered')}, - {cluster: clustersDef._0x0702, att: 'currentSummReceived', reportable: true, report: {change: 100}, onlyProducer: true, exposes: e.numeric('EAIT', ea.STATE).withUnit('kWh').withProperty('current_summ_received').withDescription('Total active power injected')}, - {cluster: clustersDef._0x0702, att: 'currentTier1SummDelivered', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('EASF01', ea.STATE).withUnit('kWh').withProperty('current_tier1_summ_delivered').withDescription('Total provider active power delivered (index 01)')}, - {cluster: clustersDef._0x0702, att: 'currentTier2SummDelivered', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('EASF02', ea.STATE).withUnit('kWh').withProperty('current_tier2_summ_delivered').withDescription('Total provider active power delivered (index 02)')}, - {cluster: clustersDef._0x0702, att: 'currentTier3SummDelivered', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('EASF03', ea.STATE).withUnit('kWh').withProperty('current_tier3_summ_delivered').withDescription('Total provider active power delivered (index 03)')}, - {cluster: clustersDef._0x0702, att: 'currentTier4SummDelivered', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('EASF04', ea.STATE).withUnit('kWh').withProperty('current_tier4_summ_delivered').withDescription('Total provider active power delivered (index 04)')}, - {cluster: clustersDef._0x0702, att: 'currentTier5SummDelivered', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('EASF05', ea.STATE).withUnit('kWh').withProperty('current_tier5_summ_delivered').withDescription('Total provider active power delivered (index 05)')}, - {cluster: clustersDef._0x0702, att: 'currentTier6SummDelivered', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('EASF06', ea.STATE).withUnit('kWh').withProperty('current_tier6_summ_delivered').withDescription('Total provider active power delivered (index 06)')}, - {cluster: clustersDef._0x0702, att: 'currentTier7SummDelivered', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('EASF07', ea.STATE).withUnit('kWh').withProperty('current_tier7_summ_delivered').withDescription('Total provider active power delivered (index 07)')}, - {cluster: clustersDef._0x0702, att: 'currentTier8SummDelivered', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('EASF08', ea.STATE).withUnit('kWh').withProperty('current_tier8_summ_delivered').withDescription('Total provider active power delivered (index 08)')}, - {cluster: clustersDef._0x0702, att: 'currentTier9SummDelivered', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('EASF09', ea.STATE).withUnit('kWh').withProperty('current_tier9_summ_delivered').withDescription('Total provider active power delivered (index 09)')}, - {cluster: clustersDef._0x0702, att: 'currentTier10SummDelivered', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('EASF10', ea.STATE).withUnit('kWh').withProperty('current_tier10_summ_delivered').withDescription('Total provider active power delivered (index 10)')}, - {cluster: clustersDef._0x0702, att: 'meterSerialNumber', reportable: false, onlyProducer: false, exposes: e.text('ADSC', ea.STATE).withProperty('meter_serial_number').withDescription('Serial Number')}, - {cluster: clustersDef._0x0702, att: 'siteId', reportable: false, onlyProducer: false, exposes: e.text('PRM', ea.STATE).withProperty('site_id').withDescription('PRM number')}, - {cluster: clustersDef._0x0B01, att: 'availablePower', reportable: false, onlyProducer: false, exposes: e.numeric('PREF', ea.STATE).withUnit('kVA').withProperty('available_power').withDescription('Apparent power of reference')}, - {cluster: clustersDef._0x0B01, att: 'powerThreshold', reportable: false, onlyProducer: false, exposes: e.numeric('PCOUP', ea.STATE).withUnit('kVA').withProperty('power_threshold').withDescription('Apparent power threshold')}, - {cluster: clustersDef._0x0B01, att: 'softwareRevision', reportable: false, onlyProducer: false, exposes: e.numeric('VTIC', ea.STATE).withProperty('software_revision').withDescription('Customer tele-information protocol version')}, - {cluster: clustersDef._0x0B04, att: 'activePower', reportable: true, onlyProducer: false, exposes: e.numeric('CCASN', ea.STATE).withUnit('W').withProperty('active_power').withDescription('Current point of the active load curve drawn')}, - {cluster: clustersDef._0x0B04, att: 'activePowerPhB', reportable: true, onlyProducer: false, exposes: e.numeric('CCASN-1', ea.STATE).withUnit('W').withProperty('active_power_ph_b').withDescription('Previous point of the active load curve drawn')}, - {cluster: clustersDef._0x0B04, att: 'averageRmsVoltageMeasPeriod', reportable: true, onlyProducer: false, exposes: e.numeric('UMOY1', ea.STATE).withUnit('V').withProperty('average_rms_voltage_meas_period').withDescription('Average RMS voltage (phase 1)')}, - {cluster: clustersDef._0x0B04, att: 'totalReactivePower', reportable: true, onlyProducer: true, exposes: e.numeric('ERQ1', ea.STATE).withUnit('VArh').withProperty('total_reactive_power').withDescription('Total reactive power (Q1)')}, - {cluster: clustersDef._0x0B04, att: 'reactivePower', reportable: true, onlyProducer: true, exposes: e.numeric('ERQ2', ea.STATE).withUnit('VArh').withProperty('reactive_power').withDescription('Total reactive power (Q2)')}, - {cluster: clustersDef._0x0B04, att: 'reactivePowerPhB', reportable: true, onlyProducer: true, exposes: e.numeric('ERQ3', ea.STATE).withUnit('VArh').withProperty('reactive_power_ph_b').withDescription('Total reactive power (Q3)')}, - {cluster: clustersDef._0x0B04, att: 'reactivePowerPhC', reportable: true, onlyProducer: true, exposes: e.numeric('ERQ4', ea.STATE).withUnit('VArh').withProperty('reactive_power_ph_c').withDescription('Total reactive power (Q4)')}, - {cluster: clustersDef._0x0B04, att: 'rmsCurrent', reportable: true, onlyProducer: false, exposes: e.numeric('IRMS1', ea.STATE).withUnit('A').withProperty('rms_current').withDescription('RMS current')}, - {cluster: clustersDef._0x0B04, att: 'rmsVoltage', reportable: true, onlyProducer: false, exposes: e.numeric('URMS1', ea.STATE).withUnit('V').withProperty('rms_voltage').withDescription('RMS voltage')}, - {cluster: clustersDef._0xFF66, att: 'activeEnergyOutD01', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('EASD01', ea.STATE).withUnit('kWh').withProperty('active_energy_out_d01').withDescription('Active energy withdrawn Distributor (index 01)')}, - {cluster: clustersDef._0xFF66, att: 'activeEnergyOutD02', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('EASD02', ea.STATE).withUnit('kWh').withProperty('active_energy_out_d02').withDescription('Active energy withdrawn Distributor (index 02)')}, - {cluster: clustersDef._0xFF66, att: 'activeEnergyOutD03', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('EASD03', ea.STATE).withUnit('kWh').withProperty('active_energy_out_d03').withDescription('Active energy withdrawn Distributor (index 03)')}, - {cluster: clustersDef._0xFF66, att: 'activeEnergyOutD04', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('EASD04', ea.STATE).withUnit('kWh').withProperty('active_energy_out_d04').withDescription('Active energy withdrawn Distributor (index 04)')}, - {cluster: clustersDef._0xFF66, att: 'currentDate', reportable: false, onlyProducer: false, exposes: e.text('DATE', ea.STATE).withProperty('current_date').withDescription('Current date and time')}, - {cluster: clustersDef._0xFF66, att: 'currentIndexTarif', reportable: false, onlyProducer: false, exposes: e.numeric('NTARF', ea.STATE).withProperty('current_index_tarif').withDescription('Current tariff index number')}, - {cluster: clustersDef._0xFF66, att: 'currentPrice', reportable: false, onlyProducer: false, exposes: e.text('LTARF', ea.STATE).withProperty('current_price').withDescription('Current supplier price label')}, - {cluster: clustersDef._0xFF66, att: 'currentTarif', reportable: false, onlyProducer: false, exposes: e.text('NGTF', ea.STATE).withProperty('current_tarif').withDescription('Supplier pricing schedule name')}, - {cluster: clustersDef._0xFF66, att: 'daysNumberCurrentCalendar', reportable: false, onlyProducer: false, exposes: e.numeric('NJOURF', ea.STATE).withProperty('days_number_current_calendar').withDescription('Current day number supplier calendar')}, - {cluster: clustersDef._0xFF66, att: 'daysNumberNextCalendar', reportable: false, onlyProducer: false, exposes: e.numeric('NJOURF+1', ea.STATE).withProperty('days_number_next_calendar').withDescription('Next day number supplier calendar')}, - {cluster: clustersDef._0xFF66, att: 'daysProfileCurrentCalendar', reportable: false, onlyProducer: false, exposes: e.text('PJOURF+1', ea.STATE).withProperty('days_profile_current_calendar').withDescription('Profile of the next supplier calendar day')}, - {cluster: clustersDef._0xFF66, att: 'daysProfileNextCalendar', reportable: false, onlyProducer: false, exposes: e.text('PPOINTE1', ea.STATE).withProperty('days_profile_next_calendar').withDescription('Profile of the next check-in day')}, - {cluster: clustersDef._0xFF66, att: 'injectedActiveLoadN', reportable: true, onlyProducer: true, exposes: e.numeric('CCAIN', ea.STATE).withUnit('W').withProperty('injected_active_load_n').withDescription('Point n of the withdrawn active load curve')}, - {cluster: clustersDef._0xFF66, att: 'injectedActiveLoadN1', reportable: false, onlyProducer: true, exposes: e.numeric('CCAIN-1', ea.STATE).withUnit('W').withProperty('injected_active_load_n1').withDescription('Point n-1 of the withdrawn active load curve')}, - {cluster: clustersDef._0xFF66, att: 'injectedVA', reportable: true, onlyProducer: true, exposes: e.numeric('SINSTI', ea.STATE).withUnit('VA').withProperty('injected_v_a').withDescription('Instantaneous apparent power injected')}, - {cluster: clustersDef._0xFF66, att: 'injectedVAMaxN', reportable: true, onlyProducer: true, exposes: e.numeric('SMAXIN', ea.STATE).withUnit('VA').withProperty('injected_v_a_max_n').withDescription('Apparent power max. injected n')}, - {cluster: clustersDef._0xFF66, att: 'injectedVAMaxN1', reportable: false, onlyProducer: true, exposes: e.numeric('SMAXIN-1', ea.STATE).withUnit('VA').withProperty('injected_v_a_max_n1').withDescription('Apparent power max. injected n-1')}, - {cluster: clustersDef._0xFF66, att: 'message1', reportable: false, onlyProducer: false, exposes: e.text('MSG1', ea.STATE).withProperty('message1').withDescription('Message short')}, - {cluster: clustersDef._0xFF66, att: 'message2', reportable: false, onlyProducer: false, exposes: e.text('MSG2', ea.STATE).withProperty('message2').withDescription('Message ultra-short')}, - {cluster: clustersDef._0xFF66, att: 'relais', reportable: false, onlyProducer: false, exposes: e.numeric('RELAIS', ea.STATE).withProperty('relais')}, - {cluster: clustersDef._0xFF66, att: 'startMobilePoint1', reportable: false, onlyProducer: false, exposes: e.numeric('DPM1', ea.STATE).withProperty('start_mobile_point1').withDescription('Start mobile point 1')}, - {cluster: clustersDef._0xFF66, att: 'startMobilePoint2', reportable: false, onlyProducer: false, exposes: e.numeric('DPM2', ea.STATE).withProperty('start_mobile_point2').withDescription('Start mobile point 2')}, - {cluster: clustersDef._0xFF66, att: 'startMobilePoint3', reportable: false, onlyProducer: false, exposes: e.numeric('DPM3', ea.STATE).withProperty('start_mobile_point3').withDescription('Start mobile point 3')}, - {cluster: clustersDef._0xFF66, att: 'statusRegister', reportable: false, onlyProducer: false, exposes: e.text('STGE', ea.STATE).withProperty('status_register').withDescription('Register of Statutes')}, - {cluster: clustersDef._0xFF66, att: 'stopMobilePoint1', reportable: false, onlyProducer: false, exposes: e.numeric('FPM1', ea.STATE).withProperty('stop_mobile_point1').withDescription('Stop mobile point 1')}, - {cluster: clustersDef._0xFF66, att: 'stopMobilePoint2', reportable: false, onlyProducer: false, exposes: e.numeric('FPM2', ea.STATE).withProperty('stop_mobile_point2').withDescription('Stop mobile point 2')}, - {cluster: clustersDef._0xFF66, att: 'stopMobilePoint3', reportable: false, onlyProducer: false, exposes: e.numeric('FPM3', ea.STATE).withProperty('stop_mobile_point3').withDescription('Stop mobile point 3')}, + { + cluster: clustersDef._0x0702, + att: 'currentSummDelivered', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e.numeric('EAST', ea.STATE).withUnit('kWh').withProperty('current_summ_delivered').withDescription('Total active power delivered'), + }, + { + cluster: clustersDef._0x0702, + att: 'currentSummReceived', + reportable: true, + report: {change: 100}, + onlyProducer: true, + exposes: e.numeric('EAIT', ea.STATE).withUnit('kWh').withProperty('current_summ_received').withDescription('Total active power injected'), + }, + { + cluster: clustersDef._0x0702, + att: 'currentTier1SummDelivered', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e + .numeric('EASF01', ea.STATE) + .withUnit('kWh') + .withProperty('current_tier1_summ_delivered') + .withDescription('Total provider active power delivered (index 01)'), + }, + { + cluster: clustersDef._0x0702, + att: 'currentTier2SummDelivered', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e + .numeric('EASF02', ea.STATE) + .withUnit('kWh') + .withProperty('current_tier2_summ_delivered') + .withDescription('Total provider active power delivered (index 02)'), + }, + { + cluster: clustersDef._0x0702, + att: 'currentTier3SummDelivered', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e + .numeric('EASF03', ea.STATE) + .withUnit('kWh') + .withProperty('current_tier3_summ_delivered') + .withDescription('Total provider active power delivered (index 03)'), + }, + { + cluster: clustersDef._0x0702, + att: 'currentTier4SummDelivered', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e + .numeric('EASF04', ea.STATE) + .withUnit('kWh') + .withProperty('current_tier4_summ_delivered') + .withDescription('Total provider active power delivered (index 04)'), + }, + { + cluster: clustersDef._0x0702, + att: 'currentTier5SummDelivered', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e + .numeric('EASF05', ea.STATE) + .withUnit('kWh') + .withProperty('current_tier5_summ_delivered') + .withDescription('Total provider active power delivered (index 05)'), + }, + { + cluster: clustersDef._0x0702, + att: 'currentTier6SummDelivered', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e + .numeric('EASF06', ea.STATE) + .withUnit('kWh') + .withProperty('current_tier6_summ_delivered') + .withDescription('Total provider active power delivered (index 06)'), + }, + { + cluster: clustersDef._0x0702, + att: 'currentTier7SummDelivered', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e + .numeric('EASF07', ea.STATE) + .withUnit('kWh') + .withProperty('current_tier7_summ_delivered') + .withDescription('Total provider active power delivered (index 07)'), + }, + { + cluster: clustersDef._0x0702, + att: 'currentTier8SummDelivered', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e + .numeric('EASF08', ea.STATE) + .withUnit('kWh') + .withProperty('current_tier8_summ_delivered') + .withDescription('Total provider active power delivered (index 08)'), + }, + { + cluster: clustersDef._0x0702, + att: 'currentTier9SummDelivered', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e + .numeric('EASF09', ea.STATE) + .withUnit('kWh') + .withProperty('current_tier9_summ_delivered') + .withDescription('Total provider active power delivered (index 09)'), + }, + { + cluster: clustersDef._0x0702, + att: 'currentTier10SummDelivered', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e + .numeric('EASF10', ea.STATE) + .withUnit('kWh') + .withProperty('current_tier10_summ_delivered') + .withDescription('Total provider active power delivered (index 10)'), + }, + { + cluster: clustersDef._0x0702, + att: 'meterSerialNumber', + reportable: false, + onlyProducer: false, + exposes: e.text('ADSC', ea.STATE).withProperty('meter_serial_number').withDescription('Serial Number'), + }, + { + cluster: clustersDef._0x0702, + att: 'siteId', + reportable: false, + onlyProducer: false, + exposes: e.text('PRM', ea.STATE).withProperty('site_id').withDescription('PRM number'), + }, + { + cluster: clustersDef._0x0B01, + att: 'availablePower', + reportable: false, + onlyProducer: false, + exposes: e.numeric('PREF', ea.STATE).withUnit('kVA').withProperty('available_power').withDescription('Apparent power of reference'), + }, + { + cluster: clustersDef._0x0B01, + att: 'powerThreshold', + reportable: false, + onlyProducer: false, + exposes: e.numeric('PCOUP', ea.STATE).withUnit('kVA').withProperty('power_threshold').withDescription('Apparent power threshold'), + }, + { + cluster: clustersDef._0x0B01, + att: 'softwareRevision', + reportable: false, + onlyProducer: false, + exposes: e.numeric('VTIC', ea.STATE).withProperty('software_revision').withDescription('Customer tele-information protocol version'), + }, + { + cluster: clustersDef._0x0B04, + att: 'activePower', + reportable: true, + onlyProducer: false, + exposes: e + .numeric('CCASN', ea.STATE) + .withUnit('W') + .withProperty('active_power') + .withDescription('Current point of the active load curve drawn'), + }, + { + cluster: clustersDef._0x0B04, + att: 'activePowerPhB', + reportable: true, + onlyProducer: false, + exposes: e + .numeric('CCASN-1', ea.STATE) + .withUnit('W') + .withProperty('active_power_ph_b') + .withDescription('Previous point of the active load curve drawn'), + }, + { + cluster: clustersDef._0x0B04, + att: 'averageRmsVoltageMeasPeriod', + reportable: true, + onlyProducer: false, + exposes: e + .numeric('UMOY1', ea.STATE) + .withUnit('V') + .withProperty('average_rms_voltage_meas_period') + .withDescription('Average RMS voltage (phase 1)'), + }, + { + cluster: clustersDef._0x0B04, + att: 'totalReactivePower', + reportable: true, + onlyProducer: true, + exposes: e.numeric('ERQ1', ea.STATE).withUnit('VArh').withProperty('total_reactive_power').withDescription('Total reactive power (Q1)'), + }, + { + cluster: clustersDef._0x0B04, + att: 'reactivePower', + reportable: true, + onlyProducer: true, + exposes: e.numeric('ERQ2', ea.STATE).withUnit('VArh').withProperty('reactive_power').withDescription('Total reactive power (Q2)'), + }, + { + cluster: clustersDef._0x0B04, + att: 'reactivePowerPhB', + reportable: true, + onlyProducer: true, + exposes: e.numeric('ERQ3', ea.STATE).withUnit('VArh').withProperty('reactive_power_ph_b').withDescription('Total reactive power (Q3)'), + }, + { + cluster: clustersDef._0x0B04, + att: 'reactivePowerPhC', + reportable: true, + onlyProducer: true, + exposes: e.numeric('ERQ4', ea.STATE).withUnit('VArh').withProperty('reactive_power_ph_c').withDescription('Total reactive power (Q4)'), + }, + { + cluster: clustersDef._0x0B04, + att: 'rmsCurrent', + reportable: true, + onlyProducer: false, + exposes: e.numeric('IRMS1', ea.STATE).withUnit('A').withProperty('rms_current').withDescription('RMS current'), + }, + { + cluster: clustersDef._0x0B04, + att: 'rmsVoltage', + reportable: true, + onlyProducer: false, + exposes: e.numeric('URMS1', ea.STATE).withUnit('V').withProperty('rms_voltage').withDescription('RMS voltage'), + }, + { + cluster: clustersDef._0xFF66, + att: 'activeEnergyOutD01', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e + .numeric('EASD01', ea.STATE) + .withUnit('kWh') + .withProperty('active_energy_out_d01') + .withDescription('Active energy withdrawn Distributor (index 01)'), + }, + { + cluster: clustersDef._0xFF66, + att: 'activeEnergyOutD02', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e + .numeric('EASD02', ea.STATE) + .withUnit('kWh') + .withProperty('active_energy_out_d02') + .withDescription('Active energy withdrawn Distributor (index 02)'), + }, + { + cluster: clustersDef._0xFF66, + att: 'activeEnergyOutD03', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e + .numeric('EASD03', ea.STATE) + .withUnit('kWh') + .withProperty('active_energy_out_d03') + .withDescription('Active energy withdrawn Distributor (index 03)'), + }, + { + cluster: clustersDef._0xFF66, + att: 'activeEnergyOutD04', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e + .numeric('EASD04', ea.STATE) + .withUnit('kWh') + .withProperty('active_energy_out_d04') + .withDescription('Active energy withdrawn Distributor (index 04)'), + }, + { + cluster: clustersDef._0xFF66, + att: 'currentDate', + reportable: false, + onlyProducer: false, + exposes: e.text('DATE', ea.STATE).withProperty('current_date').withDescription('Current date and time'), + }, + { + cluster: clustersDef._0xFF66, + att: 'currentIndexTarif', + reportable: false, + onlyProducer: false, + exposes: e.numeric('NTARF', ea.STATE).withProperty('current_index_tarif').withDescription('Current tariff index number'), + }, + { + cluster: clustersDef._0xFF66, + att: 'currentPrice', + reportable: false, + onlyProducer: false, + exposes: e.text('LTARF', ea.STATE).withProperty('current_price').withDescription('Current supplier price label'), + }, + { + cluster: clustersDef._0xFF66, + att: 'currentTarif', + reportable: false, + onlyProducer: false, + exposes: e.text('NGTF', ea.STATE).withProperty('current_tarif').withDescription('Supplier pricing schedule name'), + }, + { + cluster: clustersDef._0xFF66, + att: 'daysNumberCurrentCalendar', + reportable: false, + onlyProducer: false, + exposes: e.numeric('NJOURF', ea.STATE).withProperty('days_number_current_calendar').withDescription('Current day number supplier calendar'), + }, + { + cluster: clustersDef._0xFF66, + att: 'daysNumberNextCalendar', + reportable: false, + onlyProducer: false, + exposes: e.numeric('NJOURF+1', ea.STATE).withProperty('days_number_next_calendar').withDescription('Next day number supplier calendar'), + }, + { + cluster: clustersDef._0xFF66, + att: 'daysProfileCurrentCalendar', + reportable: false, + onlyProducer: false, + exposes: e + .text('PJOURF+1', ea.STATE) + .withProperty('days_profile_current_calendar') + .withDescription('Profile of the next supplier calendar day'), + }, + { + cluster: clustersDef._0xFF66, + att: 'daysProfileNextCalendar', + reportable: false, + onlyProducer: false, + exposes: e.text('PPOINTE1', ea.STATE).withProperty('days_profile_next_calendar').withDescription('Profile of the next check-in day'), + }, + { + cluster: clustersDef._0xFF66, + att: 'injectedActiveLoadN', + reportable: true, + onlyProducer: true, + exposes: e + .numeric('CCAIN', ea.STATE) + .withUnit('W') + .withProperty('injected_active_load_n') + .withDescription('Point n of the withdrawn active load curve'), + }, + { + cluster: clustersDef._0xFF66, + att: 'injectedActiveLoadN1', + reportable: false, + onlyProducer: true, + exposes: e + .numeric('CCAIN-1', ea.STATE) + .withUnit('W') + .withProperty('injected_active_load_n1') + .withDescription('Point n-1 of the withdrawn active load curve'), + }, + { + cluster: clustersDef._0xFF66, + att: 'injectedVA', + reportable: true, + onlyProducer: true, + exposes: e.numeric('SINSTI', ea.STATE).withUnit('VA').withProperty('injected_v_a').withDescription('Instantaneous apparent power injected'), + }, + { + cluster: clustersDef._0xFF66, + att: 'injectedVAMaxN', + reportable: true, + onlyProducer: true, + exposes: e.numeric('SMAXIN', ea.STATE).withUnit('VA').withProperty('injected_v_a_max_n').withDescription('Apparent power max. injected n'), + }, + { + cluster: clustersDef._0xFF66, + att: 'injectedVAMaxN1', + reportable: false, + onlyProducer: true, + exposes: e + .numeric('SMAXIN-1', ea.STATE) + .withUnit('VA') + .withProperty('injected_v_a_max_n1') + .withDescription('Apparent power max. injected n-1'), + }, + { + cluster: clustersDef._0xFF66, + att: 'message1', + reportable: false, + onlyProducer: false, + exposes: e.text('MSG1', ea.STATE).withProperty('message1').withDescription('Message short'), + }, + { + cluster: clustersDef._0xFF66, + att: 'message2', + reportable: false, + onlyProducer: false, + exposes: e.text('MSG2', ea.STATE).withProperty('message2').withDescription('Message ultra-short'), + }, + { + cluster: clustersDef._0xFF66, + att: 'relais', + reportable: false, + onlyProducer: false, + exposes: e.numeric('RELAIS', ea.STATE).withProperty('relais'), + }, + { + cluster: clustersDef._0xFF66, + att: 'startMobilePoint1', + reportable: false, + onlyProducer: false, + exposes: e.numeric('DPM1', ea.STATE).withProperty('start_mobile_point1').withDescription('Start mobile point 1'), + }, + { + cluster: clustersDef._0xFF66, + att: 'startMobilePoint2', + reportable: false, + onlyProducer: false, + exposes: e.numeric('DPM2', ea.STATE).withProperty('start_mobile_point2').withDescription('Start mobile point 2'), + }, + { + cluster: clustersDef._0xFF66, + att: 'startMobilePoint3', + reportable: false, + onlyProducer: false, + exposes: e.numeric('DPM3', ea.STATE).withProperty('start_mobile_point3').withDescription('Start mobile point 3'), + }, + { + cluster: clustersDef._0xFF66, + att: 'statusRegister', + reportable: false, + onlyProducer: false, + exposes: e.text('STGE', ea.STATE).withProperty('status_register').withDescription('Register of Statutes'), + }, + { + cluster: clustersDef._0xFF66, + att: 'stopMobilePoint1', + reportable: false, + onlyProducer: false, + exposes: e.numeric('FPM1', ea.STATE).withProperty('stop_mobile_point1').withDescription('Stop mobile point 1'), + }, + { + cluster: clustersDef._0xFF66, + att: 'stopMobilePoint2', + reportable: false, + onlyProducer: false, + exposes: e.numeric('FPM2', ea.STATE).withProperty('stop_mobile_point2').withDescription('Stop mobile point 2'), + }, + { + cluster: clustersDef._0xFF66, + att: 'stopMobilePoint3', + reportable: false, + onlyProducer: false, + exposes: e.numeric('FPM3', ea.STATE).withProperty('stop_mobile_point3').withDescription('Stop mobile point 3'), + }, ].map((x) => { return {...x, linkyPhase: linkyPhaseDef.all, linkyMode: linkyModeDef.standard}; }); - const singlePhaseData = [ - {cluster: clustersDef._0x0B04, att: 'activePowerMax', reportable: true, onlyProducer: false, exposes: e.numeric('SMAXN', ea.STATE).withUnit('VA').withProperty('active_power_max').withDescription('Apparent power delivered peak')}, - {cluster: clustersDef._0x0B04, att: 'apparentPower', reportable: true, onlyProducer: false, exposes: e.numeric('SINSTS', ea.STATE).withUnit('VA').withProperty('apparent_power').withDescription('Immediate apparent power delivered')}, - {cluster: clustersDef._0xFF66, att: 'drawnVAMaxN1', reportable: false, onlyProducer: false, exposes: e.numeric('SMAXN-1', ea.STATE).withUnit('VA').withProperty('drawn_v_a_max_n1').withDescription('Apparent power max. draw-off n-1')}, + { + cluster: clustersDef._0x0B04, + att: 'activePowerMax', + reportable: true, + onlyProducer: false, + exposes: e.numeric('SMAXN', ea.STATE).withUnit('VA').withProperty('active_power_max').withDescription('Apparent power delivered peak'), + }, + { + cluster: clustersDef._0x0B04, + att: 'apparentPower', + reportable: true, + onlyProducer: false, + exposes: e.numeric('SINSTS', ea.STATE).withUnit('VA').withProperty('apparent_power').withDescription('Immediate apparent power delivered'), + }, + { + cluster: clustersDef._0xFF66, + att: 'drawnVAMaxN1', + reportable: false, + onlyProducer: false, + exposes: e.numeric('SMAXN-1', ea.STATE).withUnit('VA').withProperty('drawn_v_a_max_n1').withDescription('Apparent power max. draw-off n-1'), + }, ].map((x) => { return {...x, linkyPhase: linkyPhaseDef.single, linkyMode: linkyModeDef.standard}; }); const threePhasesData = [ - {cluster: clustersDef._0x0B04, att: 'activePowerMax', reportable: true, onlyProducer: false, exposes: e.numeric('SMAXN1', ea.STATE).withUnit('VA').withProperty('active_power_max').withDescription('Apparent power delivered peak (phase 1)')}, - {cluster: clustersDef._0x0B04, att: 'activePowerMaxPhB', reportable: true, onlyProducer: false, exposes: e.numeric('SMAXN2', ea.STATE).withUnit('VA').withProperty('active_power_max_ph_b').withDescription('Apparent power delivered peak (phase 2)')}, - {cluster: clustersDef._0x0B04, att: 'activePowerMaxPhC', reportable: true, onlyProducer: false, exposes: e.numeric('SMAXN3', ea.STATE).withUnit('VA').withProperty('active_power_max_ph_c').withDescription('Apparent power delivered peak (phase 3)')}, - {cluster: clustersDef._0x0B04, att: 'apparentPower', reportable: true, onlyProducer: false, exposes: e.numeric('SINSTS1', ea.STATE).withUnit('VA').withProperty('apparent_power').withDescription('Immediate apparent power delivered (phase 1)')}, - {cluster: clustersDef._0x0B04, att: 'apparentPowerPhB', reportable: true, onlyProducer: false, exposes: e.numeric('SINSTS2', ea.STATE).withUnit('VA').withProperty('apparent_power_ph_b').withDescription('Immediate apparent power delivered (phase 2)')}, - {cluster: clustersDef._0x0B04, att: 'apparentPowerPhC', reportable: true, onlyProducer: false, exposes: e.numeric('SINSTS3', ea.STATE).withUnit('VA').withProperty('apparent_power_ph_c').withDescription('Immediate apparent power delivered (phase 3)')}, - {cluster: clustersDef._0x0B04, att: 'totalApparentPower', reportable: true, onlyProducer: false, exposes: e.numeric('SINSTS', ea.STATE).withUnit('VA').withProperty('total_apparent_power').withDescription('Total immediate apparent power delivered')}, - {cluster: clustersDef._0x0B04, att: 'averageRmsVoltageMeasPeriodPhC', reportable: true, onlyProducer: false, exposes: e.numeric('UMOY3', ea.STATE).withUnit('V').withProperty('average_rms_voltage_meas_period_ph_c').withDescription('Average RMS voltage (phase 3)')}, - {cluster: clustersDef._0x0B04, att: 'averageRmsVoltageMeasurePeriodPhB', reportable: true, onlyProducer: false, exposes: e.numeric('UMOY2', ea.STATE).withUnit('V').withProperty('average_rms_voltage_measure_period_ph_b').withDescription('Average RMS voltage (phase 2)')}, - {cluster: clustersDef._0x0B04, att: 'rmsCurrentPhB', reportable: true, onlyProducer: false, exposes: e.numeric('IRMS2', ea.STATE).withUnit('A').withProperty('rms_current_ph_b').withDescription('RMS current (phase 2)')}, - {cluster: clustersDef._0x0B04, att: 'rmsCurrentPhC', reportable: true, onlyProducer: false, exposes: e.numeric('IRMS3', ea.STATE).withUnit('A').withProperty('rms_current_ph_c').withDescription('RMS current (phase 3)')}, - {cluster: clustersDef._0x0B04, att: 'rmsVoltagePhB', reportable: true, onlyProducer: false, exposes: e.numeric('URMS2', ea.STATE).withUnit('V').withProperty('rms_voltage_ph_b').withDescription('RMS voltage (phase 2)')}, - {cluster: clustersDef._0x0B04, att: 'rmsVoltagePhC', reportable: true, onlyProducer: false, exposes: e.numeric('URMS3', ea.STATE).withUnit('V').withProperty('rms_voltage_ph_c').withDescription('RMS voltage (phase 3)')}, - {cluster: clustersDef._0xFF66, att: 'drawnVAMaxN1', reportable: false, onlyProducer: false, exposes: e.numeric('SMAXN1-1', ea.STATE).withUnit('VA').withProperty('drawn_v_a_max_n1').withDescription('Apparent power max. draw-off n-1 (phase 1)')}, - {cluster: clustersDef._0xFF66, att: 'drawnVAMaxN1P2', reportable: false, onlyProducer: false, exposes: e.numeric('SMAXN2-1', ea.STATE).withUnit('VA').withProperty('drawn_v_a_max_n1_p2').withDescription('Apparent power max. draw-off n-1 (phase 2)')}, - {cluster: clustersDef._0xFF66, att: 'drawnVAMaxN1P3', reportable: false, onlyProducer: false, exposes: e.numeric('SMAXN3-1', ea.STATE).withUnit('VA').withProperty('drawn_v_a_max_n1_p3').withDescription('Apparent power max. draw-off n-1 (phase 3)')}, + { + cluster: clustersDef._0x0B04, + att: 'activePowerMax', + reportable: true, + onlyProducer: false, + exposes: e + .numeric('SMAXN1', ea.STATE) + .withUnit('VA') + .withProperty('active_power_max') + .withDescription('Apparent power delivered peak (phase 1)'), + }, + { + cluster: clustersDef._0x0B04, + att: 'activePowerMaxPhB', + reportable: true, + onlyProducer: false, + exposes: e + .numeric('SMAXN2', ea.STATE) + .withUnit('VA') + .withProperty('active_power_max_ph_b') + .withDescription('Apparent power delivered peak (phase 2)'), + }, + { + cluster: clustersDef._0x0B04, + att: 'activePowerMaxPhC', + reportable: true, + onlyProducer: false, + exposes: e + .numeric('SMAXN3', ea.STATE) + .withUnit('VA') + .withProperty('active_power_max_ph_c') + .withDescription('Apparent power delivered peak (phase 3)'), + }, + { + cluster: clustersDef._0x0B04, + att: 'apparentPower', + reportable: true, + onlyProducer: false, + exposes: e + .numeric('SINSTS1', ea.STATE) + .withUnit('VA') + .withProperty('apparent_power') + .withDescription('Immediate apparent power delivered (phase 1)'), + }, + { + cluster: clustersDef._0x0B04, + att: 'apparentPowerPhB', + reportable: true, + onlyProducer: false, + exposes: e + .numeric('SINSTS2', ea.STATE) + .withUnit('VA') + .withProperty('apparent_power_ph_b') + .withDescription('Immediate apparent power delivered (phase 2)'), + }, + { + cluster: clustersDef._0x0B04, + att: 'apparentPowerPhC', + reportable: true, + onlyProducer: false, + exposes: e + .numeric('SINSTS3', ea.STATE) + .withUnit('VA') + .withProperty('apparent_power_ph_c') + .withDescription('Immediate apparent power delivered (phase 3)'), + }, + { + cluster: clustersDef._0x0B04, + att: 'totalApparentPower', + reportable: true, + onlyProducer: false, + exposes: e + .numeric('SINSTS', ea.STATE) + .withUnit('VA') + .withProperty('total_apparent_power') + .withDescription('Total immediate apparent power delivered'), + }, + { + cluster: clustersDef._0x0B04, + att: 'averageRmsVoltageMeasPeriodPhC', + reportable: true, + onlyProducer: false, + exposes: e + .numeric('UMOY3', ea.STATE) + .withUnit('V') + .withProperty('average_rms_voltage_meas_period_ph_c') + .withDescription('Average RMS voltage (phase 3)'), + }, + { + cluster: clustersDef._0x0B04, + att: 'averageRmsVoltageMeasurePeriodPhB', + reportable: true, + onlyProducer: false, + exposes: e + .numeric('UMOY2', ea.STATE) + .withUnit('V') + .withProperty('average_rms_voltage_measure_period_ph_b') + .withDescription('Average RMS voltage (phase 2)'), + }, + { + cluster: clustersDef._0x0B04, + att: 'rmsCurrentPhB', + reportable: true, + onlyProducer: false, + exposes: e.numeric('IRMS2', ea.STATE).withUnit('A').withProperty('rms_current_ph_b').withDescription('RMS current (phase 2)'), + }, + { + cluster: clustersDef._0x0B04, + att: 'rmsCurrentPhC', + reportable: true, + onlyProducer: false, + exposes: e.numeric('IRMS3', ea.STATE).withUnit('A').withProperty('rms_current_ph_c').withDescription('RMS current (phase 3)'), + }, + { + cluster: clustersDef._0x0B04, + att: 'rmsVoltagePhB', + reportable: true, + onlyProducer: false, + exposes: e.numeric('URMS2', ea.STATE).withUnit('V').withProperty('rms_voltage_ph_b').withDescription('RMS voltage (phase 2)'), + }, + { + cluster: clustersDef._0x0B04, + att: 'rmsVoltagePhC', + reportable: true, + onlyProducer: false, + exposes: e.numeric('URMS3', ea.STATE).withUnit('V').withProperty('rms_voltage_ph_c').withDescription('RMS voltage (phase 3)'), + }, + { + cluster: clustersDef._0xFF66, + att: 'drawnVAMaxN1', + reportable: false, + onlyProducer: false, + exposes: e + .numeric('SMAXN1-1', ea.STATE) + .withUnit('VA') + .withProperty('drawn_v_a_max_n1') + .withDescription('Apparent power max. draw-off n-1 (phase 1)'), + }, + { + cluster: clustersDef._0xFF66, + att: 'drawnVAMaxN1P2', + reportable: false, + onlyProducer: false, + exposes: e + .numeric('SMAXN2-1', ea.STATE) + .withUnit('VA') + .withProperty('drawn_v_a_max_n1_p2') + .withDescription('Apparent power max. draw-off n-1 (phase 2)'), + }, + { + cluster: clustersDef._0xFF66, + att: 'drawnVAMaxN1P3', + reportable: false, + onlyProducer: false, + exposes: e + .numeric('SMAXN3-1', ea.STATE) + .withUnit('VA') + .withProperty('drawn_v_a_max_n1_p3') + .withDescription('Apparent power max. draw-off n-1 (phase 3)'), + }, ].map((x) => { return {...x, linkyPhase: linkyPhaseDef.three, linkyMode: linkyModeDef.standard}; }); const legacyData = [ - {linkyPhase: linkyPhaseDef.all, cluster: clustersDef._0x0702, att: 'activeRegisterTierDelivered', reportable: false, onlyProducer: false, exposes: e.text('PTEC', ea.STATE).withProperty('active_register_tier_delivered').withDescription('Current pricing period')}, - {linkyPhase: linkyPhaseDef.all, cluster: clustersDef._0x0702, att: 'currentSummDelivered', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('BASE', ea.STATE).withUnit('kWh').withProperty('current_summ_delivered').withDescription('Base index')}, - {linkyPhase: linkyPhaseDef.all, cluster: clustersDef._0x0702, att: 'currentTier1SummDelivered', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('BBRHCJB', ea.STATE).withUnit('kWh').withProperty('current_tier1_summ_delivered').withDescription('BBRHCJB index')}, - {linkyPhase: linkyPhaseDef.all, cluster: clustersDef._0x0702, att: 'currentTier1SummDelivered', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('EJPHN', ea.STATE).withUnit('kWh').withProperty('current_tier1_summ_delivered').withDescription('EJPHN index')}, - {linkyPhase: linkyPhaseDef.all, cluster: clustersDef._0x0702, att: 'currentTier1SummDelivered', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('HCHC', ea.STATE).withUnit('kWh').withProperty('current_tier1_summ_delivered').withDescription('HCHC index')}, - {linkyPhase: linkyPhaseDef.all, cluster: clustersDef._0x0702, att: 'currentTier2SummDelivered', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('BBRHPJB', ea.STATE).withUnit('kWh').withProperty('current_tier2_summ_delivered').withDescription('BBRHPJB index')}, - {linkyPhase: linkyPhaseDef.all, cluster: clustersDef._0x0702, att: 'currentTier2SummDelivered', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('EJPHPM', ea.STATE).withUnit('kWh').withProperty('current_tier2_summ_delivered').withDescription('EJPHPM index')}, - {linkyPhase: linkyPhaseDef.all, cluster: clustersDef._0x0702, att: 'currentTier2SummDelivered', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('HCHP', ea.STATE).withUnit('kWh').withProperty('current_tier2_summ_delivered').withDescription('HCHP index')}, - {linkyPhase: linkyPhaseDef.all, cluster: clustersDef._0x0702, att: 'currentTier3SummDelivered', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('BBRHCJW', ea.STATE).withUnit('kWh').withProperty('current_tier3_summ_delivered').withDescription('BBRHCJW index')}, - {linkyPhase: linkyPhaseDef.all, cluster: clustersDef._0x0702, att: 'currentTier4SummDelivered', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('BBRHPJW', ea.STATE).withUnit('kWh').withProperty('current_tier4_summ_delivered').withDescription('BBRHPJW index')}, - {linkyPhase: linkyPhaseDef.all, cluster: clustersDef._0x0702, att: 'currentTier5SummDelivered', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('BBRHCJR', ea.STATE).withUnit('kWh').withProperty('current_tier5_summ_delivered').withDescription('BBRHCJR index')}, - {linkyPhase: linkyPhaseDef.all, cluster: clustersDef._0x0702, att: 'currentTier6SummDelivered', reportable: true, report: {change: 100}, onlyProducer: false, exposes: e.numeric('BBRHPJR', ea.STATE).withUnit('kWh').withProperty('current_tier6_summ_delivered').withDescription('BBRHPJR index')}, - {linkyPhase: linkyPhaseDef.all, cluster: clustersDef._0x0702, att: 'meterSerialNumber', reportable: false, onlyProducer: false, exposes: e.text('ADCO', ea.STATE).withProperty('meter_serial_number').withDescription('Serial Number')}, - {linkyPhase: linkyPhaseDef.all, cluster: clustersDef._0x0B01, att: 'availablePower', reportable: false, onlyProducer: false, exposes: e.numeric('ISOUSC', ea.STATE).withUnit('A').withProperty('available_power').withDescription('Subscribed intensity level')}, - {linkyPhase: linkyPhaseDef.all, cluster: clustersDef._0x0B04, att: 'apparentPower', reportable: true, onlyProducer: false, exposes: e.numeric('PAPP', ea.STATE).withUnit('VA').withProperty('apparent_power').withDescription('Apparent power')}, - {linkyPhase: linkyPhaseDef.all, cluster: clustersDef._0xFF66, att: 'currentTarif', reportable: false, onlyProducer: false, exposes: e.text('OPTARIF', ea.STATE).withProperty('current_tarif').withDescription('Tarif option')}, - {linkyPhase: linkyPhaseDef.all, cluster: clustersDef._0xFF66, att: 'motDEtat', reportable: false, onlyProducer: false, exposes: e.text('MOTDETAT', ea.STATE).withProperty('MOTDETAT').withDescription('Meter Status Word')}, - {linkyPhase: linkyPhaseDef.all, cluster: clustersDef._0xFF66, att: 'scheduleHPHC', reportable: false, onlyProducer: false, exposes: e.numeric('HHPHC', ea.STATE).withProperty('schedule_h_p_h_c').withDescription('Schedule HPHC')}, - {linkyPhase: linkyPhaseDef.all, cluster: clustersDef._0xFF66, att: 'startNoticeEJP', reportable: true, onlyProducer: false, exposes: e.numeric('PEJP', ea.STATE).withUnit('min').withProperty('start_notice_e_j_p').withDescription('EJP start notice (30min)')}, - {linkyPhase: linkyPhaseDef.all, cluster: clustersDef._0xFF66, att: 'tomorrowColor', reportable: true, onlyProducer: false, exposes: e.text('DEMAIN', ea.STATE).withProperty('tomorrow_color').withDescription('Tomorrow color')}, - {linkyPhase: linkyPhaseDef.single, cluster: clustersDef._0x0B04, att: 'rmsCurrent', reportable: true, onlyProducer: false, exposes: e.numeric('IINST', ea.STATE).withUnit('A').withProperty('rms_current').withDescription('RMS current')}, - {linkyPhase: linkyPhaseDef.single, cluster: clustersDef._0x0B04, att: 'rmsCurrentMax', reportable: false, onlyProducer: false, exposes: e.numeric('IMAX', ea.STATE).withUnit('A').withProperty('rms_current_max').withDescription('RMS current peak')}, - {linkyPhase: linkyPhaseDef.single, cluster: clustersDef._0xFF66, att: 'warnDPS', reportable: true, onlyProducer: false, exposes: e.numeric('ADPS', ea.STATE).withUnit('A').withProperty('warn_d_p_s').withDescription('Subscribed Power Exceeded Warning')}, - {linkyPhase: linkyPhaseDef.three, cluster: clustersDef._0x0B04, att: 'activePowerMax', reportable: false, onlyProducer: false, exposes: e.numeric('PMAX', ea.STATE).withUnit('W').withProperty('active_power_max').withDescription('Three-phase power peak')}, - {linkyPhase: linkyPhaseDef.three, cluster: clustersDef._0x0B04, att: 'rmsCurrent', reportable: true, onlyProducer: false, exposes: e.numeric('IINST1', ea.STATE).withUnit('A').withProperty('rms_current').withDescription('RMS current (phase 1)')}, - {linkyPhase: linkyPhaseDef.three, cluster: clustersDef._0x0B04, att: 'rmsCurrentMax', reportable: false, onlyProducer: false, exposes: e.numeric('IMAX1', ea.STATE).withUnit('A').withProperty('rms_current_max').withDescription('RMS current peak (phase 1)')}, - {linkyPhase: linkyPhaseDef.three, cluster: clustersDef._0x0B04, att: 'rmsCurrentMaxPhB', reportable: false, onlyProducer: false, exposes: e.numeric('IMAX2', ea.STATE).withUnit('A').withProperty('rms_current_max_ph_b').withDescription('RMS current peak (phase 2)')}, - {linkyPhase: linkyPhaseDef.three, cluster: clustersDef._0x0B04, att: 'rmsCurrentMaxPhC', reportable: false, onlyProducer: false, exposes: e.numeric('IMAX3', ea.STATE).withUnit('A').withProperty('rms_current_max_ph_c').withDescription('RMS current peak (phase 3)')}, - {linkyPhase: linkyPhaseDef.three, cluster: clustersDef._0x0B04, att: 'rmsCurrentPhB', reportable: true, onlyProducer: false, exposes: e.numeric('IINST2', ea.STATE).withUnit('A').withProperty('rms_current_ph_b').withDescription('RMS current (phase 2)')}, - {linkyPhase: linkyPhaseDef.three, cluster: clustersDef._0x0B04, att: 'rmsCurrentPhC', reportable: true, onlyProducer: false, exposes: e.numeric('IINST3', ea.STATE).withUnit('A').withProperty('rms_current_ph_c').withDescription('RMS current (phase 3)')}, - {linkyPhase: linkyPhaseDef.three, cluster: clustersDef._0xFF66, att: 'presencePotential', reportable: false, onlyProducer: false, exposes: e.numeric('PPOT', ea.STATE).withProperty('presence_potential').withDescription('Presence of potentials')}, - {linkyPhase: linkyPhaseDef.three, cluster: clustersDef._0xFF66, att: 'warnDIR1', reportable: true, onlyProducer: false, exposes: e.numeric('ADIR1', ea.STATE).withUnit('A').withProperty('warn_d_i_r1').withDescription('Over Current Warning (phase 1)')}, - {linkyPhase: linkyPhaseDef.three, cluster: clustersDef._0xFF66, att: 'warnDIR2', reportable: true, onlyProducer: false, exposes: e.numeric('ADIR2', ea.STATE).withUnit('A').withProperty('warn_d_i_r2').withDescription('Over Current Warning (phase 2)')}, - {linkyPhase: linkyPhaseDef.three, cluster: clustersDef._0xFF66, att: 'warnDIR3', reportable: true, onlyProducer: false, exposes: e.numeric('ADIR3', ea.STATE).withUnit('A').withProperty('warn_d_i_r3').withDescription('Over Current Warning (phase 3)')}, + { + linkyPhase: linkyPhaseDef.all, + cluster: clustersDef._0x0702, + att: 'activeRegisterTierDelivered', + reportable: false, + onlyProducer: false, + exposes: e.text('PTEC', ea.STATE).withProperty('active_register_tier_delivered').withDescription('Current pricing period'), + }, + { + linkyPhase: linkyPhaseDef.all, + cluster: clustersDef._0x0702, + att: 'currentSummDelivered', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e.numeric('BASE', ea.STATE).withUnit('kWh').withProperty('current_summ_delivered').withDescription('Base index'), + }, + { + linkyPhase: linkyPhaseDef.all, + cluster: clustersDef._0x0702, + att: 'currentTier1SummDelivered', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e.numeric('BBRHCJB', ea.STATE).withUnit('kWh').withProperty('current_tier1_summ_delivered').withDescription('BBRHCJB index'), + }, + { + linkyPhase: linkyPhaseDef.all, + cluster: clustersDef._0x0702, + att: 'currentTier1SummDelivered', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e.numeric('EJPHN', ea.STATE).withUnit('kWh').withProperty('current_tier1_summ_delivered').withDescription('EJPHN index'), + }, + { + linkyPhase: linkyPhaseDef.all, + cluster: clustersDef._0x0702, + att: 'currentTier1SummDelivered', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e.numeric('HCHC', ea.STATE).withUnit('kWh').withProperty('current_tier1_summ_delivered').withDescription('HCHC index'), + }, + { + linkyPhase: linkyPhaseDef.all, + cluster: clustersDef._0x0702, + att: 'currentTier2SummDelivered', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e.numeric('BBRHPJB', ea.STATE).withUnit('kWh').withProperty('current_tier2_summ_delivered').withDescription('BBRHPJB index'), + }, + { + linkyPhase: linkyPhaseDef.all, + cluster: clustersDef._0x0702, + att: 'currentTier2SummDelivered', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e.numeric('EJPHPM', ea.STATE).withUnit('kWh').withProperty('current_tier2_summ_delivered').withDescription('EJPHPM index'), + }, + { + linkyPhase: linkyPhaseDef.all, + cluster: clustersDef._0x0702, + att: 'currentTier2SummDelivered', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e.numeric('HCHP', ea.STATE).withUnit('kWh').withProperty('current_tier2_summ_delivered').withDescription('HCHP index'), + }, + { + linkyPhase: linkyPhaseDef.all, + cluster: clustersDef._0x0702, + att: 'currentTier3SummDelivered', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e.numeric('BBRHCJW', ea.STATE).withUnit('kWh').withProperty('current_tier3_summ_delivered').withDescription('BBRHCJW index'), + }, + { + linkyPhase: linkyPhaseDef.all, + cluster: clustersDef._0x0702, + att: 'currentTier4SummDelivered', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e.numeric('BBRHPJW', ea.STATE).withUnit('kWh').withProperty('current_tier4_summ_delivered').withDescription('BBRHPJW index'), + }, + { + linkyPhase: linkyPhaseDef.all, + cluster: clustersDef._0x0702, + att: 'currentTier5SummDelivered', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e.numeric('BBRHCJR', ea.STATE).withUnit('kWh').withProperty('current_tier5_summ_delivered').withDescription('BBRHCJR index'), + }, + { + linkyPhase: linkyPhaseDef.all, + cluster: clustersDef._0x0702, + att: 'currentTier6SummDelivered', + reportable: true, + report: {change: 100}, + onlyProducer: false, + exposes: e.numeric('BBRHPJR', ea.STATE).withUnit('kWh').withProperty('current_tier6_summ_delivered').withDescription('BBRHPJR index'), + }, + { + linkyPhase: linkyPhaseDef.all, + cluster: clustersDef._0x0702, + att: 'meterSerialNumber', + reportable: false, + onlyProducer: false, + exposes: e.text('ADCO', ea.STATE).withProperty('meter_serial_number').withDescription('Serial Number'), + }, + { + linkyPhase: linkyPhaseDef.all, + cluster: clustersDef._0x0B01, + att: 'availablePower', + reportable: false, + onlyProducer: false, + exposes: e.numeric('ISOUSC', ea.STATE).withUnit('A').withProperty('available_power').withDescription('Subscribed intensity level'), + }, + { + linkyPhase: linkyPhaseDef.all, + cluster: clustersDef._0x0B04, + att: 'apparentPower', + reportable: true, + onlyProducer: false, + exposes: e.numeric('PAPP', ea.STATE).withUnit('VA').withProperty('apparent_power').withDescription('Apparent power'), + }, + { + linkyPhase: linkyPhaseDef.all, + cluster: clustersDef._0xFF66, + att: 'currentTarif', + reportable: false, + onlyProducer: false, + exposes: e.text('OPTARIF', ea.STATE).withProperty('current_tarif').withDescription('Tarif option'), + }, + { + linkyPhase: linkyPhaseDef.all, + cluster: clustersDef._0xFF66, + att: 'motDEtat', + reportable: false, + onlyProducer: false, + exposes: e.text('MOTDETAT', ea.STATE).withProperty('MOTDETAT').withDescription('Meter Status Word'), + }, + { + linkyPhase: linkyPhaseDef.all, + cluster: clustersDef._0xFF66, + att: 'scheduleHPHC', + reportable: false, + onlyProducer: false, + exposes: e.numeric('HHPHC', ea.STATE).withProperty('schedule_h_p_h_c').withDescription('Schedule HPHC'), + }, + { + linkyPhase: linkyPhaseDef.all, + cluster: clustersDef._0xFF66, + att: 'startNoticeEJP', + reportable: true, + onlyProducer: false, + exposes: e.numeric('PEJP', ea.STATE).withUnit('min').withProperty('start_notice_e_j_p').withDescription('EJP start notice (30min)'), + }, + { + linkyPhase: linkyPhaseDef.all, + cluster: clustersDef._0xFF66, + att: 'tomorrowColor', + reportable: true, + onlyProducer: false, + exposes: e.text('DEMAIN', ea.STATE).withProperty('tomorrow_color').withDescription('Tomorrow color'), + }, + { + linkyPhase: linkyPhaseDef.single, + cluster: clustersDef._0x0B04, + att: 'rmsCurrent', + reportable: true, + onlyProducer: false, + exposes: e.numeric('IINST', ea.STATE).withUnit('A').withProperty('rms_current').withDescription('RMS current'), + }, + { + linkyPhase: linkyPhaseDef.single, + cluster: clustersDef._0x0B04, + att: 'rmsCurrentMax', + reportable: false, + onlyProducer: false, + exposes: e.numeric('IMAX', ea.STATE).withUnit('A').withProperty('rms_current_max').withDescription('RMS current peak'), + }, + { + linkyPhase: linkyPhaseDef.single, + cluster: clustersDef._0xFF66, + att: 'warnDPS', + reportable: true, + onlyProducer: false, + exposes: e.numeric('ADPS', ea.STATE).withUnit('A').withProperty('warn_d_p_s').withDescription('Subscribed Power Exceeded Warning'), + }, + { + linkyPhase: linkyPhaseDef.three, + cluster: clustersDef._0x0B04, + att: 'activePowerMax', + reportable: false, + onlyProducer: false, + exposes: e.numeric('PMAX', ea.STATE).withUnit('W').withProperty('active_power_max').withDescription('Three-phase power peak'), + }, + { + linkyPhase: linkyPhaseDef.three, + cluster: clustersDef._0x0B04, + att: 'rmsCurrent', + reportable: true, + onlyProducer: false, + exposes: e.numeric('IINST1', ea.STATE).withUnit('A').withProperty('rms_current').withDescription('RMS current (phase 1)'), + }, + { + linkyPhase: linkyPhaseDef.three, + cluster: clustersDef._0x0B04, + att: 'rmsCurrentMax', + reportable: false, + onlyProducer: false, + exposes: e.numeric('IMAX1', ea.STATE).withUnit('A').withProperty('rms_current_max').withDescription('RMS current peak (phase 1)'), + }, + { + linkyPhase: linkyPhaseDef.three, + cluster: clustersDef._0x0B04, + att: 'rmsCurrentMaxPhB', + reportable: false, + onlyProducer: false, + exposes: e.numeric('IMAX2', ea.STATE).withUnit('A').withProperty('rms_current_max_ph_b').withDescription('RMS current peak (phase 2)'), + }, + { + linkyPhase: linkyPhaseDef.three, + cluster: clustersDef._0x0B04, + att: 'rmsCurrentMaxPhC', + reportable: false, + onlyProducer: false, + exposes: e.numeric('IMAX3', ea.STATE).withUnit('A').withProperty('rms_current_max_ph_c').withDescription('RMS current peak (phase 3)'), + }, + { + linkyPhase: linkyPhaseDef.three, + cluster: clustersDef._0x0B04, + att: 'rmsCurrentPhB', + reportable: true, + onlyProducer: false, + exposes: e.numeric('IINST2', ea.STATE).withUnit('A').withProperty('rms_current_ph_b').withDescription('RMS current (phase 2)'), + }, + { + linkyPhase: linkyPhaseDef.three, + cluster: clustersDef._0x0B04, + att: 'rmsCurrentPhC', + reportable: true, + onlyProducer: false, + exposes: e.numeric('IINST3', ea.STATE).withUnit('A').withProperty('rms_current_ph_c').withDescription('RMS current (phase 3)'), + }, + { + linkyPhase: linkyPhaseDef.three, + cluster: clustersDef._0xFF66, + att: 'presencePotential', + reportable: false, + onlyProducer: false, + exposes: e.numeric('PPOT', ea.STATE).withProperty('presence_potential').withDescription('Presence of potentials'), + }, + { + linkyPhase: linkyPhaseDef.three, + cluster: clustersDef._0xFF66, + att: 'warnDIR1', + reportable: true, + onlyProducer: false, + exposes: e.numeric('ADIR1', ea.STATE).withUnit('A').withProperty('warn_d_i_r1').withDescription('Over Current Warning (phase 1)'), + }, + { + linkyPhase: linkyPhaseDef.three, + cluster: clustersDef._0xFF66, + att: 'warnDIR2', + reportable: true, + onlyProducer: false, + exposes: e.numeric('ADIR2', ea.STATE).withUnit('A').withProperty('warn_d_i_r2').withDescription('Over Current Warning (phase 2)'), + }, + { + linkyPhase: linkyPhaseDef.three, + cluster: clustersDef._0xFF66, + att: 'warnDIR3', + reportable: true, + onlyProducer: false, + exposes: e.numeric('ADIR3', ea.STATE).withUnit('A').withProperty('warn_d_i_r3').withDescription('Over Current Warning (phase 3)'), + }, ].map((x) => { return {...x, linkyMode: linkyModeDef.legacy}; }); @@ -845,7 +1644,7 @@ function getCurrentConfig(device: Zh.Device, options: KeyValue) { // @ts-expect-error lMode.raiseError; // raise if undefined // @ts-expect-error - return (lMode >> bitLinkyMode & 1) == 1 ? valueTrue : valueFalse; + return ((lMode >> bitLinkyMode) & 1) == 1 ? valueTrue : valueFalse; } catch (err) { logger.warning(`Was not able to detect the Linky ` + targetOption + `. Default to ` + valueDefault, NS); return valueDefault; // default value in the worst case @@ -863,14 +1662,15 @@ function getCurrentConfig(device: Zh.Device, options: KeyValue) { } // Main filter - let myExpose = exposedData - .filter((e) => e.linkyMode == linkyMode && (e.linkyPhase == linkyPhase || e.linkyPhase == linkyPhaseDef.all) && (linkyProduction || !e.onlyProducer)); + let myExpose = exposedData.filter( + (e) => e.linkyMode == linkyMode && (e.linkyPhase == linkyPhase || e.linkyPhase == linkyPhaseDef.all) && (linkyProduction || !e.onlyProducer), + ); // Filter even more, based on our current tarif let currentTarf = ''; if (options && options.hasOwnProperty('tarif') && options['tarif'] != 'auto') { - currentTarf = Object.entries(tarifsDef).find(([k, v]) => (v.fname == options['tarif']))[1].currentTarf; + currentTarf = Object.entries(tarifsDef).find(([k, v]) => v.fname == options['tarif'])[1].currentTarf; } else { try { const lixAtts = endpoint.clusters[clustersDef._0xFF66].attributes; @@ -885,45 +1685,45 @@ function getCurrentConfig(device: Zh.Device, options: KeyValue) { logger.debug(`zlinky config: ` + linkyMode + `, ` + linkyPhase + `, ` + linkyProduction.toString() + `, ` + currentTarf, NS); switch (currentTarf) { - case linkyMode == linkyModeDef.legacy && tarifsDef.histo_BASE.currentTarf: - myExpose = myExpose.filter((a) => !tarifsDef.histo_BASE.excluded.includes(a.exposes.name)); - break; - case linkyMode == linkyModeDef.legacy && tarifsDef.histo_HCHP.currentTarf: - myExpose = myExpose.filter((a) => !tarifsDef.histo_HCHP.excluded.includes(a.exposes.name)); - break; - case linkyMode == linkyModeDef.legacy && tarifsDef.histo_EJP.currentTarf: - myExpose = myExpose.filter((a) => !tarifsDef.histo_EJP.excluded.includes(a.exposes.name)); - break; - // @ts-expect-error - case linkyMode == linkyModeDef.legacy && currentTarf && currentTarf.startsWith(tarifsDef.histo_BBR.currentTarf): - myExpose = myExpose.filter((a) => !tarifsDef.histo_BBR.excluded.includes(a.exposes.name)); - break; - case linkyMode == linkyModeDef.standard && tarifsDef.stand_SEM_WE_LUNDI.currentTarf: - myExpose = myExpose.filter((a) => !tarifsDef.stand_SEM_WE_LUNDI.excluded.includes(a.exposes.name)); - break; - case linkyMode == linkyModeDef.standard && tarifsDef.stand_SEM_WE_MERCR.currentTarf: - myExpose = myExpose.filter((a) => !tarifsDef.stand_SEM_WE_MERCR.excluded.includes(a.exposes.name)); - break; - case linkyMode == linkyModeDef.standard && tarifsDef.stand_SEM_WE_VENDR.currentTarf: - myExpose = myExpose.filter((a) => !tarifsDef.stand_SEM_WE_VENDR.excluded.includes(a.exposes.name)); - break; - case linkyMode == linkyModeDef.standard && tarifsDef.stand_HPHC.currentTarf: - myExpose = myExpose.filter((a) => !tarifsDef.stand_HPHC.excluded.includes(a.exposes.name)); - break; - case linkyMode == linkyModeDef.standard && tarifsDef.stand_BASE.currentTarf: - myExpose = myExpose.filter((a) => !tarifsDef.stand_BASE.excluded.includes(a.exposes.name)); - break; - case linkyMode == linkyModeDef.standard && tarifsDef.stand_H_SUPER_CREUSES.currentTarf: - myExpose = myExpose.filter((a) => !tarifsDef.stand_H_SUPER_CREUSES.excluded.includes(a.exposes.name)); - break; - case linkyMode == linkyModeDef.standard && tarifsDef.stand_TEMPO.currentTarf: - myExpose = myExpose.filter((a) => !tarifsDef.stand_TEMPO.excluded.includes(a.exposes.name)); - break; - case linkyMode == linkyModeDef.standard && tarifsDef.stand_ZEN_FLEX.currentTarf: - myExpose = myExpose.filter((a) => !tarifsDef.stand_ZEN_FLEX.excluded.includes(a.exposes.name)); - break; - default: - break; + case linkyMode == linkyModeDef.legacy && tarifsDef.histo_BASE.currentTarf: + myExpose = myExpose.filter((a) => !tarifsDef.histo_BASE.excluded.includes(a.exposes.name)); + break; + case linkyMode == linkyModeDef.legacy && tarifsDef.histo_HCHP.currentTarf: + myExpose = myExpose.filter((a) => !tarifsDef.histo_HCHP.excluded.includes(a.exposes.name)); + break; + case linkyMode == linkyModeDef.legacy && tarifsDef.histo_EJP.currentTarf: + myExpose = myExpose.filter((a) => !tarifsDef.histo_EJP.excluded.includes(a.exposes.name)); + break; + // @ts-expect-error + case linkyMode == linkyModeDef.legacy && currentTarf && currentTarf.startsWith(tarifsDef.histo_BBR.currentTarf): + myExpose = myExpose.filter((a) => !tarifsDef.histo_BBR.excluded.includes(a.exposes.name)); + break; + case linkyMode == linkyModeDef.standard && tarifsDef.stand_SEM_WE_LUNDI.currentTarf: + myExpose = myExpose.filter((a) => !tarifsDef.stand_SEM_WE_LUNDI.excluded.includes(a.exposes.name)); + break; + case linkyMode == linkyModeDef.standard && tarifsDef.stand_SEM_WE_MERCR.currentTarf: + myExpose = myExpose.filter((a) => !tarifsDef.stand_SEM_WE_MERCR.excluded.includes(a.exposes.name)); + break; + case linkyMode == linkyModeDef.standard && tarifsDef.stand_SEM_WE_VENDR.currentTarf: + myExpose = myExpose.filter((a) => !tarifsDef.stand_SEM_WE_VENDR.excluded.includes(a.exposes.name)); + break; + case linkyMode == linkyModeDef.standard && tarifsDef.stand_HPHC.currentTarf: + myExpose = myExpose.filter((a) => !tarifsDef.stand_HPHC.excluded.includes(a.exposes.name)); + break; + case linkyMode == linkyModeDef.standard && tarifsDef.stand_BASE.currentTarf: + myExpose = myExpose.filter((a) => !tarifsDef.stand_BASE.excluded.includes(a.exposes.name)); + break; + case linkyMode == linkyModeDef.standard && tarifsDef.stand_H_SUPER_CREUSES.currentTarf: + myExpose = myExpose.filter((a) => !tarifsDef.stand_H_SUPER_CREUSES.excluded.includes(a.exposes.name)); + break; + case linkyMode == linkyModeDef.standard && tarifsDef.stand_TEMPO.currentTarf: + myExpose = myExpose.filter((a) => !tarifsDef.stand_TEMPO.excluded.includes(a.exposes.name)); + break; + case linkyMode == linkyModeDef.standard && tarifsDef.stand_ZEN_FLEX.currentTarf: + myExpose = myExpose.filter((a) => !tarifsDef.stand_ZEN_FLEX.excluded.includes(a.exposes.name)); + break; + default: + break; } // Filter exposed attributes with user whitelist @@ -951,11 +1751,14 @@ const definitions: Definition[] = [ // docs generation let exposes; if (device == null && options == null) { - exposes = exposedData.map((e) => e.exposes) - .filter((value, index, self) => - index === self.findIndex((t) => ( - t.property === value.property // Remove duplicates - )), + exposes = exposedData + .map((e) => e.exposes) + .filter( + (value, index, self) => + index === + self.findIndex( + (t) => t.property === value.property, // Remove duplicates + ), ); } else { exposes = getCurrentConfig(device, options).map((e) => e.exposes); @@ -966,40 +1769,64 @@ const definitions: Definition[] = [ }, options: [ exposes.options.measurement_poll_interval(), - e.enum(`linky_mode`, ea.SET, ['auto', linkyModeDef.legacy, linkyModeDef.standard]) + e + .enum(`linky_mode`, ea.SET, ['auto', linkyModeDef.legacy, linkyModeDef.standard]) .withDescription(`Counter with TIC in mode standard or historique. May require restart (default: auto)`), - e.enum(`energy_phase`, ea.SET, ['auto', linkyPhaseDef.single, linkyPhaseDef.three]) + e + .enum(`energy_phase`, ea.SET, ['auto', linkyPhaseDef.single, linkyPhaseDef.three]) .withDescription(`Power with single or three phase. May require restart (default: auto)`), - e.enum(`production`, ea.SET, ['auto', 'true', 'false']).withDescription(`If you produce energy back to the grid (works ONLY when linky_mode: ${linkyModeDef.standard}, default: auto)`), - e.enum(`tarif`, ea.SET, [...Object.entries(tarifsDef).map(([k, v]) => (v.fname)), 'auto']) - .withDescription(`Overrides the automatic current tarif. This option will exclude unnecessary attributes. Open a issue to support more of them. Default: auto`), + e + .enum(`production`, ea.SET, ['auto', 'true', 'false']) + .withDescription(`If you produce energy back to the grid (works ONLY when linky_mode: ${linkyModeDef.standard}, default: auto)`), + e + .enum(`tarif`, ea.SET, [...Object.entries(tarifsDef).map(([k, v]) => v.fname), 'auto']) + .withDescription( + `Overrides the automatic current tarif. This option will exclude unnecessary attributes. Open a issue to support more of them. Default: auto`, + ), exposes.options.precision(`kWh`), - e.numeric(`measurement_poll_chunk`, ea.SET).withValueMin(1).withDescription(`During the poll, request multiple exposes to the Zlinky at once for reducing Zigbee network overload. Too much request at once could exceed device limit. Requires Z2M restart. Default: 1`), - e.text(`tic_command_whitelist`, ea.SET).withDescription(`List of TIC commands to be exposed (separated by comma). Reconfigure device after change. Default: all`), + e + .numeric(`measurement_poll_chunk`, ea.SET) + .withValueMin(1) + .withDescription( + `During the poll, request multiple exposes to the Zlinky at once for reducing Zigbee network overload. Too much request at once could exceed device limit. Requires Z2M restart. Default: 1`, + ), + e + .text(`tic_command_whitelist`, ea.SET) + .withDescription(`List of TIC commands to be exposed (separated by comma). Reconfigure device after change. Default: all`), ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, [ - clustersDef._0x0702, /* seMetering */ - clustersDef._0x0B01, /* haMeterIdentification */ - clustersDef._0x0B04, /* haElectricalMeasurement */ - clustersDef._0xFF66, /* liXeePrivate */ + clustersDef._0x0702 /* seMetering */, + clustersDef._0x0B01 /* haMeterIdentification */, + clustersDef._0x0B04 /* haElectricalMeasurement */, + clustersDef._0xFF66 /* liXeePrivate */, ]); - await endpoint.read('liXeePrivate', ['linkyMode', 'currentTarif'], {manufacturerCode: null}) - .catch((e) => { - // https://github.com/Koenkk/zigbee2mqtt/issues/11674 - logger.warning(`Failed to read zigbee attributes: ${e}`, NS); - }); + await endpoint.read('liXeePrivate', ['linkyMode', 'currentTarif'], {manufacturerCode: null}).catch((e) => { + // https://github.com/Koenkk/zigbee2mqtt/issues/11674 + logger.warning(`Failed to read zigbee attributes: ${e}`, NS); + }); const configReportings = []; const suscribeNew = getCurrentConfig(device, {}).filter((e) => e.reportable); - const unsuscribe = endpoint.configuredReportings - .filter((e) => !suscribeNew.some((r) => e.cluster.name == r.cluster && e.attribute.name == r.att)); + const unsuscribe = endpoint.configuredReportings.filter( + (e) => !suscribeNew.some((r) => e.cluster.name == r.cluster && e.attribute.name == r.att), + ); // Unsuscribe reports that doesn't correspond with the current config - (await Promise.allSettled(unsuscribe.map((e) => endpoint.configureReporting(e.cluster.name, reporting.payload(e.attribute.name, e.minimumReportInterval, 65535, e.reportableChange), {manufacturerCode: null})))) + ( + await Promise.allSettled( + unsuscribe.map((e) => + endpoint.configureReporting( + e.cluster.name, + reporting.payload(e.attribute.name, e.minimumReportInterval, 65535, e.reportableChange), + {manufacturerCode: null}, + ), + ), + ) + ) .filter((e) => e.status == 'rejected') .forEach((e) => { throw e.reason; @@ -1017,10 +1844,10 @@ const definitions: Definition[] = [ // @ts-expect-error params = {...params, ...e.report}; } - configReportings.push(endpoint - .configureReporting( - e.cluster, reporting.payload(params.att, params.min, params.max, params.change), - {manufacturerCode: null}), + configReportings.push( + endpoint.configureReporting(e.cluster, reporting.payload(params.att, params.min, params.max, params.change), { + manufacturerCode: null, + }), ); } (await Promise.allSettled(configReportings)) @@ -1033,11 +1860,10 @@ const definitions: Definition[] = [ onEvent: async (type, data, device, options) => { const endpoint = device.getEndpoint(1); if (type === 'start') { - endpoint.read('liXeePrivate', ['linkyMode', 'currentTarif'], {manufacturerCode: null}) - .catch((e) => { - // https://github.com/Koenkk/zigbee2mqtt/issues/11674 - logger.warning(`Failed to read zigbee attributes: ${e}`, NS); - }); + endpoint.read('liXeePrivate', ['linkyMode', 'currentTarif'], {manufacturerCode: null}).catch((e) => { + // https://github.com/Koenkk/zigbee2mqtt/issues/11674 + logger.warning(`Failed to read zigbee attributes: ${e}`, NS); + }); } else if (type === 'stop') { clearInterval(globalStore.getValue(device, 'interval')); globalStore.clearValue(device, 'interval'); @@ -1050,8 +1876,9 @@ const definitions: Definition[] = [ const setTimer = () => { const timer = setTimeout(async () => { try { - const currentExposes = getCurrentConfig(device, options) - .filter((e) => !endpoint.configuredReportings.some((r) => r.cluster.name == e.cluster && r.attribute.name == e.att)); + const currentExposes = getCurrentConfig(device, options).filter( + (e) => !endpoint.configuredReportings.some((r) => r.cluster.name == e.cluster && r.attribute.name == e.att), + ); for (const key in clustersDef) { if (Object.hasOwnProperty.call(clustersDef, key)) { @@ -1059,7 +1886,8 @@ const definitions: Definition[] = [ const cluster = clustersDef[key]; const targ = currentExposes.filter((e) => e.cluster == cluster).map((e) => e.att); if (targ.length) { - let i; let j; + let i; + let j; // Split array by chunks for (i = 0, j = targ.length; i < j; i += measurement_poll_chunk) { await endpoint @@ -1072,7 +1900,9 @@ const definitions: Definition[] = [ } } } - } catch (error) {/* Do nothing*/} + } catch (error) { + /* Do nothing*/ + } setTimer(); }, seconds * 1000); globalStore.putValue(device, 'interval', timer); @@ -1088,9 +1918,20 @@ const definitions: Definition[] = [ description: 'Lixee ZiPulses', fromZigbee: [fz.battery, fz.temperature, fz.metering, fzZiPulses], toZigbee: [tzSeMetering], - exposes: [e.battery(), e.battery_voltage(), e.temperature(), - e.numeric('multiplier', ea.STATE_SET).withValueMin(1).withValueMax(1000).withDescription('It is necessary to press the link button to update'), - e.numeric('divisor', ea.STATE_SET).withValueMin(1).withValueMax(1000).withDescription('It is necessary to press the link button to update'), + exposes: [ + e.battery(), + e.battery_voltage(), + e.temperature(), + e + .numeric('multiplier', ea.STATE_SET) + .withValueMin(1) + .withValueMax(1000) + .withDescription('It is necessary to press the link button to update'), + e + .numeric('divisor', ea.STATE_SET) + .withValueMin(1) + .withValueMax(1000) + .withDescription('It is necessary to press the link button to update'), e.enum('unitOfMeasure', ea.STATE_SET, unitsZiPulses).withDescription('It is necessary to press the link button to update'), e.numeric('energy', ea.STATE), ], diff --git a/src/devices/lonsonho.ts b/src/devices/lonsonho.ts index 9f608bf146fe6..ad09c37fa03c2 100644 --- a/src/devices/lonsonho.ts +++ b/src/devices/lonsonho.ts @@ -1,27 +1,34 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; +import {deviceEndpoints, light, onOff} from '../lib/modernExtend'; import * as reporting from '../lib/reporting'; import * as tuya from '../lib/tuya'; -import {deviceEndpoints, light, onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; const definitions: Definition[] = [ { - fingerprint: [{modelID: 'TS130F', manufacturerName: '_TZ3000_vd43bbfq'}, {modelID: 'TS130F', manufacturerName: '_TZ3000_fccpjz5z'}], + fingerprint: [ + {modelID: 'TS130F', manufacturerName: '_TZ3000_vd43bbfq'}, + {modelID: 'TS130F', manufacturerName: '_TZ3000_fccpjz5z'}, + ], model: 'QS-Zigbee-C01', vendor: 'Lonsonho', description: 'Curtain/blind motor controller', fromZigbee: [fz.cover_position_tilt, fz.tuya_cover_options], toZigbee: [tz.cover_state, tz.cover_position_tilt, tz.tuya_cover_calibration, tz.tuya_cover_reversal], meta: {coverInverted: true}, - exposes: [e.cover_position(), e.enum('moving', ea.STATE, ['UP', 'STOP', 'DOWN']), - e.binary('calibration', ea.ALL, 'ON', 'OFF'), e.binary('motor_reversal', ea.ALL, 'ON', 'OFF'), - e.numeric('calibration_time', ea.STATE).withUnit('s').withDescription('Calibration time')], + exposes: [ + e.cover_position(), + e.enum('moving', ea.STATE, ['UP', 'STOP', 'DOWN']), + e.binary('calibration', ea.ALL, 'ON', 'OFF'), + e.binary('motor_reversal', ea.ALL, 'ON', 'OFF'), + e.numeric('calibration_time', ea.STATE).withUnit('s').withDescription('Calibration time'), + ], }, { fingerprint: [{modelID: 'TS130F', manufacturerName: '_TZ3000_egq7y6pr'}], @@ -29,13 +36,16 @@ const definitions: Definition[] = [ vendor: 'Lonsonho', description: 'Curtain switch', fromZigbee: [fz.cover_position_tilt, tuya.fz.backlight_mode_low_medium_high, fz.tuya_cover_options], - toZigbee: [tz.cover_state, tz.cover_position_tilt, tz.tuya_cover_calibration, tz.tuya_cover_reversal, - tuya.tz.backlight_indicator_mode_1], + toZigbee: [tz.cover_state, tz.cover_position_tilt, tz.tuya_cover_calibration, tz.tuya_cover_reversal, tuya.tz.backlight_indicator_mode_1], meta: {coverInverted: true}, - exposes: [e.cover_position(), e.enum('moving', ea.STATE, ['UP', 'STOP', 'DOWN']), - e.binary('calibration', ea.ALL, 'ON', 'OFF'), e.binary('motor_reversal', ea.ALL, 'ON', 'OFF'), + exposes: [ + e.cover_position(), + e.enum('moving', ea.STATE, ['UP', 'STOP', 'DOWN']), + e.binary('calibration', ea.ALL, 'ON', 'OFF'), + e.binary('motor_reversal', ea.ALL, 'ON', 'OFF'), e.enum('backlight_mode', ea.ALL, ['LOW', 'MEDIUM', 'HIGH']), - e.numeric('calibration_time', ea.STATE).withUnit('s').withDescription('Calibration time')], + e.numeric('calibration_time', ea.STATE).withUnit('s').withDescription('Calibration time'), + ], }, { fingerprint: tuya.fingerprint('TS130F', ['_TZ3000_j1xl73iw', '_TZ3000_kmsbwdol', '_TZ3000_esynmmox', '_TZ3000_l6iqph4f', '_TZ3000_xdo0hj1k']), @@ -46,23 +56,26 @@ const definitions: Definition[] = [ toZigbee: [tz.cover_state, tz.cover_position_tilt, tz.tuya_cover_calibration, tz.tuya_cover_reversal], meta: {multiEndpoint: true, coverInverted: true}, endpoint: (device) => { - return {'left': 1, 'right': 2}; + return {left: 1, right: 2}; }, exposes: [ e.enum('moving', ea.STATE, ['UP', 'STOP', 'DOWN']).withEndpoint('left'), e.enum('moving', ea.STATE, ['UP', 'STOP', 'DOWN']).withEndpoint('right'), - e.numeric('calibration_time', ea.STATE).withUnit('s').withDescription('Calibration time') - .withEndpoint('left'), - e.numeric('calibration_time', ea.STATE).withUnit('s').withDescription('Calibration time') - .withEndpoint('right'), - e.cover_position().withEndpoint('left'), e.binary('calibration', ea.ALL, 'ON', 'OFF') - .withEndpoint('left'), e.binary('motor_reversal', ea.ALL, 'ON', 'OFF').withEndpoint('left'), - e.cover_position().withEndpoint('right'), e.binary('calibration', ea.ALL, 'ON', 'OFF') - .withEndpoint('right'), e.binary('motor_reversal', ea.ALL, 'ON', 'OFF').withEndpoint('right'), + e.numeric('calibration_time', ea.STATE).withUnit('s').withDescription('Calibration time').withEndpoint('left'), + e.numeric('calibration_time', ea.STATE).withUnit('s').withDescription('Calibration time').withEndpoint('right'), + e.cover_position().withEndpoint('left'), + e.binary('calibration', ea.ALL, 'ON', 'OFF').withEndpoint('left'), + e.binary('motor_reversal', ea.ALL, 'ON', 'OFF').withEndpoint('left'), + e.cover_position().withEndpoint('right'), + e.binary('calibration', ea.ALL, 'ON', 'OFF').withEndpoint('right'), + e.binary('motor_reversal', ea.ALL, 'ON', 'OFF').withEndpoint('right'), ], }, { - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_8vxj8khv'}, {modelID: 'TS0601', manufacturerName: '_TZE200_7tdtqgwv'}], + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE200_8vxj8khv'}, + {modelID: 'TS0601', manufacturerName: '_TZE200_7tdtqgwv'}, + ], model: 'X711A', vendor: 'Lonsonho', description: '1 gang switch', @@ -75,14 +88,13 @@ const definitions: Definition[] = [ model: 'X712A', vendor: 'Lonsonho', description: '2 gang switch', - exposes: [e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), - e.switch().withEndpoint('l2').setAccess('state', ea.STATE_SET)], + exposes: [e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l2').setAccess('state', ea.STATE_SET)], fromZigbee: [legacy.fz.tuya_switch, fz.ignore_time_read], toZigbee: [legacy.tz.tuya_switch_state], meta: {multiEndpoint: true}, endpoint: (device) => { // Endpoint selection is made in tuya_switch_state - return {'l1': 1, 'l2': 1}; + return {l1: 1, l2: 1}; }, }, { @@ -90,14 +102,17 @@ const definitions: Definition[] = [ model: 'X713A', vendor: 'Lonsonho', description: '3 gang switch', - exposes: [e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), - e.switch().withEndpoint('l2').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l3').setAccess('state', ea.STATE_SET)], + exposes: [ + e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), + e.switch().withEndpoint('l2').setAccess('state', ea.STATE_SET), + e.switch().withEndpoint('l3').setAccess('state', ea.STATE_SET), + ], fromZigbee: [legacy.fz.tuya_switch, fz.ignore_time_read], toZigbee: [legacy.tz.tuya_switch_state], meta: {multiEndpoint: true}, endpoint: (device) => { // Endpoint selection is made in tuya_switch_state - return {'l1': 1, 'l2': 1, 'l3': 1}; + return {l1: 1, l2: 1, l3: 1}; }, }, { @@ -120,7 +135,7 @@ const definitions: Definition[] = [ vendor: 'Lonsonho', description: '2 gang smart dimmer switch module with neutral', extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2}}), + deviceEndpoints({endpoints: {l1: 1, l2: 2}}), tuya.modernExtend.tuyaLight({minBrightness: 'attribute', endpointNames: ['l1', 'l2']}), ], meta: {multiEndpoint: true}, @@ -135,10 +150,7 @@ const definitions: Definition[] = [ model: 'QS-Zigbee-D02-TRIAC-2C-L', vendor: 'Lonsonho', description: '2 gang smart dimmer switch module without neutral', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2}}), - light({endpointNames: ['l1', 'l2'], configureReporting: true}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2}}), light({endpointNames: ['l1', 'l2'], configureReporting: true})], }, { zigbeeModel: ['Plug_01'], @@ -149,22 +161,29 @@ const definitions: Definition[] = [ }, { zigbeeModel: ['ZB-RGBCW'], - fingerprint: [{modelID: 'ZB-CL01', manufacturerName: 'eWeLight'}, {modelID: 'ZB-CL01', manufacturerName: 'eWeLink'}, - {modelID: 'ZB-CL02', manufacturerName: 'eWeLight'}, {modelID: 'ZB-CL01', manufacturerName: 'eWeLi\u0001\u0000\u0010'}, - {modelID: 'Z102LG03-1', manufacturerName: 'eWeLink'}], + fingerprint: [ + {modelID: 'ZB-CL01', manufacturerName: 'eWeLight'}, + {modelID: 'ZB-CL01', manufacturerName: 'eWeLink'}, + {modelID: 'ZB-CL02', manufacturerName: 'eWeLight'}, + {modelID: 'ZB-CL01', manufacturerName: 'eWeLi\u0001\u0000\u0010'}, + {modelID: 'Z102LG03-1', manufacturerName: 'eWeLink'}, + ], model: 'ZB-RGBCW', vendor: 'Lonsonho', description: 'Zigbee 3.0 LED-bulb, RGBW LED', extend: [light({colorTemp: {range: [153, 500], startup: false}, color: true, effect: false, powerOnBehavior: false})], }, { - fingerprint: [{modelID: 'TS0003', manufacturerName: '_TYZB01_zsl6z0pw'}, {modelID: 'TS0003', manufacturerName: '_TYZB01_uqkphoed'}], + fingerprint: [ + {modelID: 'TS0003', manufacturerName: '_TYZB01_zsl6z0pw'}, + {modelID: 'TS0003', manufacturerName: '_TYZB01_uqkphoed'}, + ], model: 'QS-Zigbee-S04-2C-LN', vendor: 'Lonsonho', description: '2 gang switch module with neutral wire', exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2')], endpoint: (device) => { - return {'l1': 1, 'l2': 2}; + return {l1: 1, l2: 2}; }, toZigbee: [tz.TYZB01_on_off], fromZigbee: [fz.on_off], @@ -183,16 +202,23 @@ const definitions: Definition[] = [ toZigbee: [tz.TYZB01_on_off], }, { - fingerprint: [{modelID: 'TS130F', manufacturerName: '_TZ3000_zirycpws'}, {modelID: 'TS130F', manufacturerName: '_TZ3210_ol1uhvza'}], + fingerprint: [ + {modelID: 'TS130F', manufacturerName: '_TZ3000_zirycpws'}, + {modelID: 'TS130F', manufacturerName: '_TZ3210_ol1uhvza'}, + ], model: 'QS-Zigbee-C03', vendor: 'Lonsonho', description: 'Curtain/blind motor controller', fromZigbee: [fz.cover_position_tilt, fz.tuya_cover_options], toZigbee: [tz.cover_state, tz.cover_position_tilt, tz.tuya_cover_calibration, tz.tuya_cover_reversal], meta: {coverInverted: true}, - exposes: [e.cover_position(), e.enum('moving', ea.STATE, ['UP', 'STOP', 'DOWN']), - e.binary('calibration', ea.ALL, 'ON', 'OFF'), e.binary('motor_reversal', ea.ALL, 'ON', 'OFF'), - e.numeric('calibration_time', ea.STATE).withUnit('s').withDescription('Calibration time')], + exposes: [ + e.cover_position(), + e.enum('moving', ea.STATE, ['UP', 'STOP', 'DOWN']), + e.binary('calibration', ea.ALL, 'ON', 'OFF'), + e.binary('motor_reversal', ea.ALL, 'ON', 'OFF'), + e.numeric('calibration_time', ea.STATE).withUnit('s').withDescription('Calibration time'), + ], }, { fingerprint: [{modelID: 'TS0603', manufacturerName: '_TZE600_wxq8dpha\u0000'}], @@ -211,7 +237,7 @@ const definitions: Definition[] = [ e.power_on_behavior().withAccess(ea.STATE_SET), ], endpoint: (device) => { - return {'l1': 1, 'l2': 1, 'l3': 1}; + return {l1: 1, l2: 1, l3: 1}; }, meta: { multiEndpoint: true, diff --git a/src/devices/ls.ts b/src/devices/ls.ts index 0640dabec687f..f7a14e58a3dea 100644 --- a/src/devices/ls.ts +++ b/src/devices/ls.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; import * as exposes from '../lib/exposes'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ @@ -14,9 +14,14 @@ const definitions: Definition[] = [ configure: light({colorTemp: {range: [153, 454]}, color: true}).configure[0], exposes: (device, options) => { if (!device) return [e.light_brightness_colortemp_colorxy([153, 454]), e.linkquality()]; - return [e.linkquality(), ...device.endpoints.filter((ep) => ep.ID !== 242).map((ep) => { - return e.light_brightness_colortemp_colorxy([153, 454]).withEndpoint(`l${ep.ID}`); - })]; + return [ + e.linkquality(), + ...device.endpoints + .filter((ep) => ep.ID !== 242) + .map((ep) => { + return e.light_brightness_colortemp_colorxy([153, 454]).withEndpoint(`l${ep.ID}`); + }), + ]; }, meta: {multiEndpoint: true}, endpoint: (device) => { diff --git a/src/devices/lubeez.ts b/src/devices/lubeez.ts index e564ef3da40e7..540d580405826 100644 --- a/src/devices/lubeez.ts +++ b/src/devices/lubeez.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/lumi.ts b/src/devices/lumi.ts index 80ff6bd4cdac5..67437674be0f3 100644 --- a/src/devices/lumi.ts +++ b/src/devices/lumi.ts @@ -1,28 +1,59 @@ -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; import * as constants from '../lib/constants'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; import { - light, numeric, binary, enumLookup, forceDeviceType, - temperature, humidity, forcePowerSource, quirkAddEndpointCluster, - quirkCheckinInterval, customTimeResponse, deviceEndpoints, battery, + light, + numeric, + binary, + enumLookup, + forceDeviceType, + temperature, + humidity, + forcePowerSource, + quirkAddEndpointCluster, + quirkCheckinInterval, + customTimeResponse, + deviceEndpoints, + battery, } from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; const e = exposes.presets; const ea = exposes.access; -import * as globalStore from '../lib/store'; import * as lumi from '../lib/lumi'; +import * as globalStore from '../lib/store'; const { - lumiAction, lumiOperationMode, lumiPowerOnBehavior, lumiZigbeeOTA, - lumiSwitchType, lumiAirQuality, lumiVoc, lumiDisplayUnit, lumiLight, - lumiOutageCountRestoreBindReporting, lumiElectricityMeter, lumiPower, - lumiOverloadProtection, lumiLedIndicator, lumiButtonLock, lumiMotorSpeed, - lumiOnOff, lumiLedDisabledNight, lumiFlipIndicatorLight, lumiPreventReset, - lumiClickMode, lumiSlider, lumiSetEventMode, lumiSwitchMode, lumiVibration, - lumiKnobRotation, lumiCommandMode, lumiBattery, + lumiAction, + lumiOperationMode, + lumiPowerOnBehavior, + lumiZigbeeOTA, + lumiSwitchType, + lumiAirQuality, + lumiVoc, + lumiDisplayUnit, + lumiLight, + lumiOutageCountRestoreBindReporting, + lumiElectricityMeter, + lumiPower, + lumiOverloadProtection, + lumiLedIndicator, + lumiButtonLock, + lumiMotorSpeed, + lumiOnOff, + lumiLedDisabledNight, + lumiFlipIndicatorLight, + lumiPreventReset, + lumiClickMode, + lumiSlider, + lumiSetEventMode, + lumiSwitchMode, + lumiVibration, + lumiKnobRotation, + lumiCommandMode, + lumiBattery, } = lumi.modernExtend; -import {Definition} from '../lib/types'; import {logger} from '../lib/logger'; +import {Definition} from '../lib/types'; const NS = 'zhc:lumi'; const {manufacturerCode} = lumi; @@ -81,9 +112,13 @@ const definitions: Definition[] = [ fromZigbee: [lumi.fromZigbee.lumi_contact, fz.ias_contact_alarm_1, lumi.fromZigbee.lumi_specific], toZigbee: [lumi.toZigbee.lumi_detection_distance], meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, - exposes: [e.contact(), e.battery(), e.battery_voltage(), + exposes: [ + e.contact(), + e.battery(), + e.battery_voltage(), e.tamper(), - e.enum('detection_distance', ea.ALL, ['10mm', '20mm', '30mm']) + e + .enum('detection_distance', ea.ALL, ['10mm', '20mm', '30mm']) .withDescription('The sensor will be considered "off" within the set distance. Please press the device button before setting'), ], extend: [quirkCheckinInterval('1_HOUR')], @@ -94,19 +129,24 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Smart lightstrip driver', fromZigbee: [lumi.fromZigbee.lumi_power, lumi.fromZigbee.lumi_specific, ...light({colorTemp: {range: undefined}, color: true}).fromZigbee], - toZigbee: [lumi.toZigbee.lumi_dimmer_mode, lumi.toZigbee.lumi_switch_power_outage_memory, - ...light({colorTemp: {range: undefined}, color: true}).toZigbee], - exposes: [e.power(), e.energy(), e.voltage(), e.device_temperature(), e.power_outage_memory(), + toZigbee: [ + lumi.toZigbee.lumi_dimmer_mode, + lumi.toZigbee.lumi_switch_power_outage_memory, + ...light({colorTemp: {range: undefined}, color: true}).toZigbee, + ], + exposes: [ + e.power(), + e.energy(), + e.voltage(), + e.device_temperature(), + e.power_outage_memory(), // When in rgbw mode, only one of color and colortemp will be valid, and l2 will be invalid // Do not control l2 in rgbw mode e.light_brightness_colortemp_colorxy([153, 370]).removeFeature('color_temp_startup').withEndpoint('l1'), e.light_brightness_colortemp([153, 370]).removeFeature('color_temp_startup').withEndpoint('l2'), - e.enum('dimmer_mode', ea.ALL, ['rgbw', 'dual_ct']) - .withDescription('Switch between rgbw mode or dual color temperature mode')], - extend: [ - deviceEndpoints({endpoints: {l1: 1, l2: 2}}), - lumiZigbeeOTA(), + e.enum('dimmer_mode', ea.ALL, ['rgbw', 'dual_ct']).withDescription('Switch between rgbw mode or dual color temperature mode'), ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2}}), lumiZigbeeOTA()], }, { zigbeeModel: ['lumi.light.aqcn02'], @@ -181,12 +221,17 @@ const definitions: Definition[] = [ {vendor: 'Xiaomi', model: 'YTC4040GL'}, {vendor: 'Xiaomi', model: 'YTC4006CN'}, {vendor: 'Xiaomi', model: 'YTC4017CN'}, - {vendor: 'Xiaomi', model: 'ZHTZ02LM'}], + {vendor: 'Xiaomi', model: 'ZHTZ02LM'}, + ], description: 'Mi wireless switch', meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, fromZigbee: [lumi.fromZigbee.lumi_basic, lumi.fromZigbee.lumi_action_WXKG01LM, lumi.legacyFromZigbee.WXKG01LM_click], - exposes: [e.battery(), e.action(['single', 'double', 'triple', 'quadruple', 'hold', 'release', 'many']), e.battery_voltage(), - e.power_outage_count(false)], + exposes: [ + e.battery(), + e.action(['single', 'double', 'triple', 'quadruple', 'hold', 'release', 'many']), + e.battery_voltage(), + e.power_outage_count(false), + ], toZigbee: [], extend: [quirkCheckinInterval('1_HOUR')], }, @@ -196,10 +241,20 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Wireless mini switch', meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, - exposes: [e.battery(), e.battery_voltage(), e.action(['single', 'double', 'triple', 'quadruple', 'hold', 'release']), - e.device_temperature(), e.power_outage_count()], - fromZigbee: [lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_action, lumi.fromZigbee.lumi_basic, - lumi.legacyFromZigbee.WXKG11LM_click, lumi.legacyFromZigbee.lumi_action_click_multistate], + exposes: [ + e.battery(), + e.battery_voltage(), + e.action(['single', 'double', 'triple', 'quadruple', 'hold', 'release']), + e.device_temperature(), + e.power_outage_count(), + ], + fromZigbee: [ + lumi.fromZigbee.lumi_action_multistate, + lumi.fromZigbee.lumi_action, + lumi.fromZigbee.lumi_basic, + lumi.legacyFromZigbee.WXKG11LM_click, + lumi.legacyFromZigbee.lumi_action_click_multistate, + ], toZigbee: [], extend: [quirkCheckinInterval('1_HOUR')], }, @@ -232,8 +287,13 @@ const definitions: Definition[] = [ description: 'Wireless remote switch (single rocker), 2018 model', meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, exposes: [e.battery(), e.action(['single', 'double', 'hold']), e.battery_voltage()], - fromZigbee: [lumi.fromZigbee.lumi_action, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_basic, - lumi.legacyFromZigbee.WXKG03LM_click, lumi.legacyFromZigbee.lumi_action_click_multistate], + fromZigbee: [ + lumi.fromZigbee.lumi_action, + lumi.fromZigbee.lumi_action_multistate, + lumi.fromZigbee.lumi_basic, + lumi.legacyFromZigbee.WXKG03LM_click, + lumi.legacyFromZigbee.lumi_action_click_multistate, + ], toZigbee: [], extend: [quirkCheckinInterval('1_HOUR'), lumiPreventReset()], }, @@ -244,9 +304,7 @@ const definitions: Definition[] = [ description: 'Wireless remote switch D1 (single rocker)', fromZigbee: [lumi.fromZigbee.lumi_basic, lumi.fromZigbee.lumi_action, lumi.fromZigbee.lumi_action_multistate], toZigbee: [], - exposes: [e.battery(), - e.action(['single', 'double', 'hold']), - e.battery_voltage()], + exposes: [e.battery(), e.action(['single', 'double', 'hold']), e.battery_voltage()], meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, extend: [quirkCheckinInterval('1_HOUR'), lumiPreventReset()], configure: async (device, coordinatorEndpoint) => { @@ -310,7 +368,7 @@ const definitions: Definition[] = [ }), enumLookup({ name: 'audio_sensitivity', - lookup: {'low': 0, 'medium': 1, 'high': 2}, + lookup: {low: 0, medium: 1, high: 2}, cluster: 'manuSpecificLumi', attribute: {ID: 0x051e, type: 0x20}, description: 'Audio sensitivity', @@ -318,7 +376,7 @@ const definitions: Definition[] = [ }), enumLookup({ name: 'audio_effect', - lookup: {'random': 0, 'blink': 1, 'rainbow': 2, 'wave': 3}, + lookup: {random: 0, blink: 1, rainbow: 2, wave: 3}, cluster: 'manuSpecificLumi', attribute: {ID: 0x051d, type: 0x23}, description: 'Audio effect', @@ -362,10 +420,28 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Wireless remote switch (double rocker), 2018 model', meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, - exposes: [e.battery(), e.action(['single_left', 'single_right', 'single_both', 'double_left', 'double_right', 'double_both', - 'hold_left', 'hold_right', 'hold_both']), e.battery_voltage()], - fromZigbee: [lumi.fromZigbee.lumi_action, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_basic, - lumi.legacyFromZigbee.WXKG02LM_click, lumi.legacyFromZigbee.WXKG02LM_click_multistate], + exposes: [ + e.battery(), + e.action([ + 'single_left', + 'single_right', + 'single_both', + 'double_left', + 'double_right', + 'double_both', + 'hold_left', + 'hold_right', + 'hold_both', + ]), + e.battery_voltage(), + ], + fromZigbee: [ + lumi.fromZigbee.lumi_action, + lumi.fromZigbee.lumi_action_multistate, + lumi.fromZigbee.lumi_basic, + lumi.legacyFromZigbee.WXKG02LM_click, + lumi.legacyFromZigbee.WXKG02LM_click_multistate, + ], toZigbee: [], extend: [quirkCheckinInterval('1_HOUR'), lumiPreventReset()], }, @@ -375,19 +451,31 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Smart wall switch (no neutral, single rocker), US', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_opple, - lumi.toZigbee.lumi_flip_indicator_light, lumi.toZigbee.lumi_switch_mode_switch, lumi.toZigbee.lumi_switch_power_outage_memory], - exposes: [e.switch(), e.action(['single', 'double']), - e.flip_indicator_light(), e.power_outage_memory(), + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_flip_indicator_light, + lumi.toZigbee.lumi_switch_mode_switch, + lumi.toZigbee.lumi_switch_power_outage_memory, + ], + exposes: [ + e.switch(), + e.action(['single', 'double']), + e.flip_indicator_light(), + e.power_outage_memory(), e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode'), - e.enum('mode_switch', ea.ALL, ['anti_flicker_mode', 'quick_mode']) - .withDescription('Anti flicker mode can be used to solve blinking issues of some lights.' + - 'Quick mode makes the device respond faster.'), - e.power_outage_count(), e.device_temperature().withAccess(ea.STATE)], + e + .enum('mode_switch', ea.ALL, ['anti_flicker_mode', 'quick_mode']) + .withDescription( + 'Anti flicker mode can be used to solve blinking issues of some lights.' + 'Quick mode makes the device respond faster.', + ), + e.power_outage_count(), + e.device_temperature().withAccess(ea.STATE), + ], configure: async (device, coordinatorEndpoint) => { const endpoint1 = device.getEndpoint(1); // set "event" mode - await endpoint1.write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); + await endpoint1.write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); device.powerSource = 'Mains (single phase)'; device.save(); }, @@ -400,35 +488,37 @@ const definitions: Definition[] = [ description: 'Smart wall switch (no neutral, double rocker), US', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific], toZigbee: [ - tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_opple, lumi.toZigbee.lumi_flip_indicator_light, - lumi.toZigbee.lumi_switch_power_outage_memory, lumi.toZigbee.lumi_switch_mode_switch], + tz.on_off, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_flip_indicator_light, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_switch_mode_switch, + ], exposes: [ e.switch().withEndpoint('top'), e.switch().withEndpoint('bottom'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for top button') - .withEndpoint('top'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for top button').withEndpoint('top'), + e + .enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) .withDescription('Decoupled mode for bottom button') .withEndpoint('bottom'), - e.enum('mode_switch', ea.ALL, ['anti_flicker_mode', 'quick_mode']) + e + .enum('mode_switch', ea.ALL, ['anti_flicker_mode', 'quick_mode']) .withDescription( - 'Anti flicker mode can be used to solve blinking issues of some lights.' + - 'Quick mode makes the device respond faster.', + 'Anti flicker mode can be used to solve blinking issues of some lights.' + 'Quick mode makes the device respond faster.', ), e.power_outage_count(), e.device_temperature().withAccess(ea.STATE), e.flip_indicator_light(), e.power_outage_memory(), - e.action(['single_top', 'single_bottom', 'single_both', 'double_top', 'double_bottom', 'double_both'])], + e.action(['single_top', 'single_bottom', 'single_both', 'double_top', 'double_bottom', 'double_both']), + ], meta: {multiEndpoint: true}, endpoint: (device) => { - return {'top': 1, 'bottom': 2}; + return {top: 1, bottom: 2}; }, configure: async (device, coordinatorEndpoint) => { - await device.getEndpoint(1).write( - 'manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}, - ); + await device.getEndpoint(1).write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); }, extend: [lumiZigbeeOTA(), lumiPreventReset()], }, @@ -438,17 +528,29 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Smart wall switch (with neutral, single rocker), US', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_power, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_power, lumi.toZigbee.lumi_switch_operation_mode_opple, lumi.toZigbee.lumi_switch_power_outage_memory, - lumi.toZigbee.lumi_flip_indicator_light], - exposes: [e.switch(), e.action(['single', 'double']), e.flip_indicator_light(), e.power_outage_count(), + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_power, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_flip_indicator_light, + ], + exposes: [ + e.switch(), + e.action(['single', 'double']), + e.flip_indicator_light(), + e.power_outage_count(), e.device_temperature().withAccess(ea.STATE), - e.power().withAccess(ea.STATE_GET), e.energy(), e.voltage(), e.power_outage_memory(), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode')], + e.power().withAccess(ea.STATE_GET), + e.energy(), + e.voltage(), + e.power_outage_memory(), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode'), + ], configure: async (device, coordinatorEndpoint) => { const endpoint1 = device.getEndpoint(1); // set "event" mode - await endpoint1.write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); + await endpoint1.write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); }, extend: [lumiZigbeeOTA(), lumiPreventReset()], }, @@ -459,15 +561,18 @@ const definitions: Definition[] = [ description: 'Smart wall switch (with neutral, double rocker), US', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_power, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific], toZigbee: [ - tz.on_off, lumi.toZigbee.lumi_power, lumi.toZigbee.lumi_switch_operation_mode_opple, lumi.toZigbee.lumi_switch_power_outage_memory, - lumi.toZigbee.lumi_flip_indicator_light], + tz.on_off, + lumi.toZigbee.lumi_power, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_flip_indicator_light, + ], exposes: [ e.switch().withEndpoint('top'), e.switch().withEndpoint('bottom'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for top button') - .withEndpoint('top'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for top button').withEndpoint('top'), + e + .enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) .withDescription('Decoupled mode for bottom button') .withEndpoint('bottom'), e.power_outage_count(), @@ -477,15 +582,14 @@ const definitions: Definition[] = [ e.energy(), e.voltage(), e.power_outage_memory(), - e.action(['single_top', 'single_bottom', 'single_both', 'double_top', 'double_bottom', 'double_both'])], + e.action(['single_top', 'single_bottom', 'single_both', 'double_top', 'double_bottom', 'double_both']), + ], meta: {multiEndpoint: true, multiEndpointSkip: ['power', 'energy']}, endpoint: (device) => { - return {'top': 1, 'bottom': 2}; + return {top: 1, bottom: 2}; }, configure: async (device, coordinatorEndpoint) => { - await device.getEndpoint(1).write( - 'manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}, - ); + await device.getEndpoint(1).write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); }, extend: [lumiZigbeeOTA(), lumiPreventReset()], }, @@ -495,20 +599,31 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Smart wall switch H1 (no neutral, single rocker)', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_specific, lumi.fromZigbee.lumi_action_multistate], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_opple, lumi.toZigbee.lumi_switch_power_outage_memory, - lumi.toZigbee.lumi_led_disabled_night, lumi.toZigbee.lumi_flip_indicator_light, lumi.toZigbee.lumi_switch_mode_switch], - exposes: [e.switch(), e.device_temperature(), e.power_outage_memory(), e.led_disabled_night(), e.flip_indicator_light(), + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_led_disabled_night, + lumi.toZigbee.lumi_flip_indicator_light, + lumi.toZigbee.lumi_switch_mode_switch, + ], + exposes: [ + e.switch(), + e.device_temperature(), + e.power_outage_memory(), + e.led_disabled_night(), + e.flip_indicator_light(), e.action(['single', 'double']), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode'), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode'), e.power_outage_count(), - e.enum('mode_switch', ea.ALL, ['anti_flicker_mode', 'quick_mode']) + e + .enum('mode_switch', ea.ALL, ['anti_flicker_mode', 'quick_mode']) .withDescription( - 'Anti flicker mode can be used to solve blinking issues of some lights.' + - 'Quick mode makes the device respond faster.'), + 'Anti flicker mode can be used to solve blinking issues of some lights.' + 'Quick mode makes the device respond faster.', + ), ], configure: async (device, coordinatorEndpoint) => { - await device.getEndpoint(1).write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); + await device.getEndpoint(1).write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); }, extend: [lumiZigbeeOTA(), lumiPreventReset()], }, @@ -518,26 +633,36 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Smart wall switch H1 (no neutral, double rocker)', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_opple, lumi.toZigbee.lumi_switch_power_outage_memory, - lumi.toZigbee.lumi_flip_indicator_light, lumi.toZigbee.lumi_led_disabled_night, lumi.toZigbee.lumi_switch_mode_switch], + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_flip_indicator_light, + lumi.toZigbee.lumi_led_disabled_night, + lumi.toZigbee.lumi_switch_mode_switch, + ], meta: {multiEndpoint: true}, endpoint: (device) => { - return {'left': 1, 'right': 2}; + return {left: 1, right: 2}; }, - exposes: [e.switch().withEndpoint('left'), e.switch().withEndpoint('right'), e.device_temperature(), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for left button').withEndpoint('left'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for right button').withEndpoint('right'), + exposes: [ + e.switch().withEndpoint('left'), + e.switch().withEndpoint('right'), + e.device_temperature(), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for left button').withEndpoint('left'), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for right button').withEndpoint('right'), e.action(['single_left', 'double_left', 'single_right', 'double_right', 'single_both', 'double_both']), - e.power_outage_memory(), e.flip_indicator_light(), e.led_disabled_night(), - e.enum('mode_switch', ea.ALL, ['anti_flicker_mode', 'quick_mode']) + e.power_outage_memory(), + e.flip_indicator_light(), + e.led_disabled_night(), + e + .enum('mode_switch', ea.ALL, ['anti_flicker_mode', 'quick_mode']) .withDescription( - 'Anti flicker mode can be used to solve blinking issues of some lights.' + - 'Quick mode makes the device respond faster.'), + 'Anti flicker mode can be used to solve blinking issues of some lights.' + 'Quick mode makes the device respond faster.', + ), ], configure: async (device, coordinatorEndpoint) => { - await device.getEndpoint(1).write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); + await device.getEndpoint(1).write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); }, extend: [lumiZigbeeOTA(), lumiPreventReset()], }, @@ -547,35 +672,55 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Smart wall switch H1 (no neutral, triple rocker)', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_opple, lumi.toZigbee.lumi_switch_power_outage_memory, - lumi.toZigbee.lumi_flip_indicator_light, lumi.toZigbee.lumi_led_disabled_night, lumi.toZigbee.lumi_switch_mode_switch], + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_flip_indicator_light, + lumi.toZigbee.lumi_led_disabled_night, + lumi.toZigbee.lumi_switch_mode_switch, + ], meta: {multiEndpoint: true}, endpoint: (device) => ({left: 1, center: 2, right: 3}), exposes: [ - e.switch().withEndpoint('left'), e.switch().withEndpoint('center'), e.switch().withEndpoint('right'), - e.power_outage_memory(), e.flip_indicator_light(), e.led_disabled_night(), e.power_outage_count(), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for left button') - .withEndpoint('left'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) + e.switch().withEndpoint('left'), + e.switch().withEndpoint('center'), + e.switch().withEndpoint('right'), + e.power_outage_memory(), + e.flip_indicator_light(), + e.led_disabled_night(), + e.power_outage_count(), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for left button').withEndpoint('left'), + e + .enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) .withDescription('Decoupled mode for center button') .withEndpoint('center'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for right button') - .withEndpoint('right'), - e.enum('mode_switch', ea.ALL, ['anti_flicker_mode', 'quick_mode']) + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for right button').withEndpoint('right'), + e + .enum('mode_switch', ea.ALL, ['anti_flicker_mode', 'quick_mode']) .withDescription( - 'Anti flicker mode can be used to solve blinking issues of some lights.' + - 'Quick mode makes the device respond faster.'), + 'Anti flicker mode can be used to solve blinking issues of some lights.' + 'Quick mode makes the device respond faster.', + ), e.device_temperature().withAccess(ea.STATE), e.action([ - 'single_left', 'double_left', 'single_center', 'double_center', 'single_right', 'double_right', - 'single_left_center', 'double_left_center', 'single_left_right', 'double_left_right', - 'single_center_right', 'double_center_right', 'single_all', 'double_all', + 'single_left', + 'double_left', + 'single_center', + 'double_center', + 'single_right', + 'double_right', + 'single_left_center', + 'double_left_center', + 'single_left_right', + 'double_left_right', + 'single_center_right', + 'double_center_right', + 'single_all', + 'double_all', ]), ], configure: async (device, coordinatorEndpoint) => { - await device.getEndpoint(1).write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); + await device.getEndpoint(1).write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); }, extend: [lumiPreventReset()], }, @@ -585,14 +730,26 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Smart wall switch H1 Pro (with neutral, single rocker)', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_power, lumi.fromZigbee.lumi_specific, lumi.fromZigbee.lumi_action_multistate], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_opple, lumi.toZigbee.lumi_switch_power_outage_memory, - lumi.toZigbee.lumi_led_disabled_night, lumi.toZigbee.lumi_flip_indicator_light], - exposes: [e.switch(), e.power(), e.energy(), e.voltage(), - e.device_temperature(), e.power_outage_memory(), e.led_disabled_night(), e.flip_indicator_light(), + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_led_disabled_night, + lumi.toZigbee.lumi_flip_indicator_light, + ], + exposes: [ + e.switch(), + e.power(), + e.energy(), + e.voltage(), + e.device_temperature(), + e.power_outage_memory(), + e.led_disabled_night(), + e.flip_indicator_light(), e.action(['single', 'double']), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode'), - e.power_outage_count()], + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode'), + e.power_outage_count(), + ], extend: [lumiZigbeeOTA(), lumiPreventReset()], }, { @@ -602,21 +759,31 @@ const definitions: Definition[] = [ description: 'Smart wall switch H1 Pro (with neutral, double rocker)', meta: {multiEndpoint: true, multiEndpointSkip: ['power', 'energy']}, endpoint: (device) => { - return {'left': 1, 'right': 2}; + return {left: 1, right: 2}; }, fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_power, lumi.fromZigbee.lumi_specific, lumi.fromZigbee.lumi_action_multistate], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_opple, lumi.toZigbee.lumi_switch_power_outage_memory, - lumi.toZigbee.lumi_led_disabled_night, lumi.toZigbee.lumi_flip_indicator_light], - exposes: [e.switch().withEndpoint('left'), e.switch().withEndpoint('right'), e.power(), e.energy(), e.voltage(), - e.device_temperature(), e.power_outage_memory(), e.led_disabled_night(), e.flip_indicator_light(), - e.action([ - 'single_left', 'single_right', 'single_both', - 'double_left', 'double_right', 'double_both']), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for left button').withEndpoint('left'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for right button').withEndpoint('right'), - e.power_outage_count()], + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_led_disabled_night, + lumi.toZigbee.lumi_flip_indicator_light, + ], + exposes: [ + e.switch().withEndpoint('left'), + e.switch().withEndpoint('right'), + e.power(), + e.energy(), + e.voltage(), + e.device_temperature(), + e.power_outage_memory(), + e.led_disabled_night(), + e.flip_indicator_light(), + e.action(['single_left', 'single_right', 'single_both', 'double_left', 'double_right', 'double_both']), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for left button').withEndpoint('left'), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for right button').withEndpoint('right'), + e.power_outage_count(), + ], extend: [lumiZigbeeOTA(), lumiPreventReset()], }, { @@ -626,25 +793,51 @@ const definitions: Definition[] = [ description: 'Smart wall switch H1 Pro (with neutral, triple rocker)', meta: {multiEndpoint: true}, endpoint: (device) => { - return {'left': 1, 'center': 2, 'right': 3}; + return {left: 1, center: 2, right: 3}; }, fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_power, lumi.fromZigbee.lumi_specific, lumi.fromZigbee.lumi_action_multistate], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_opple, lumi.toZigbee.lumi_switch_power_outage_memory, - lumi.toZigbee.lumi_led_disabled_night, lumi.toZigbee.lumi_flip_indicator_light], - exposes: [e.switch().withEndpoint('left'), e.switch().withEndpoint('right'), e.switch().withEndpoint('center'), - e.power(), e.energy(), e.voltage(), e.device_temperature(), e.power_outage_memory(), e.led_disabled_night(), e.flip_indicator_light(), + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_led_disabled_night, + lumi.toZigbee.lumi_flip_indicator_light, + ], + exposes: [ + e.switch().withEndpoint('left'), + e.switch().withEndpoint('right'), + e.switch().withEndpoint('center'), + e.power(), + e.energy(), + e.voltage(), + e.device_temperature(), + e.power_outage_memory(), + e.led_disabled_night(), + e.flip_indicator_light(), e.action([ - 'single_left', 'double_left', 'single_center', 'double_center', - 'single_right', 'double_right', 'single_left_center', 'double_left_center', - 'single_left_right', 'double_left_right', 'single_center_right', 'double_center_right', - 'single_all', 'double_all']), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for left button').withEndpoint('left'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for right button').withEndpoint('right'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for center button').withEndpoint('center'), - e.power_outage_count()], + 'single_left', + 'double_left', + 'single_center', + 'double_center', + 'single_right', + 'double_right', + 'single_left_center', + 'double_left_center', + 'single_left_right', + 'double_left_right', + 'single_center_right', + 'double_center_right', + 'single_all', + 'double_all', + ]), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for left button').withEndpoint('left'), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for right button').withEndpoint('right'), + e + .enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) + .withDescription('Decoupled mode for center button') + .withEndpoint('center'), + e.power_outage_count(), + ], extend: [lumiZigbeeOTA(), lumiPreventReset()], }, { @@ -653,18 +846,34 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Smart wall switch H1 EU (no neutral, single rocker)', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_opple, lumi.toZigbee.lumi_switch_power_outage_memory, - lumi.toZigbee.lumi_flip_indicator_light, lumi.toZigbee.lumi_led_disabled_night, lumi.toZigbee.lumi_switch_mode_switch], - exposes: [e.switch(), e.action(['single', 'double']), e.power_outage_memory(), e.flip_indicator_light(), - e.led_disabled_night(), e.power_outage_count(), e.device_temperature().withAccess(ea.STATE), + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_flip_indicator_light, + lumi.toZigbee.lumi_led_disabled_night, + lumi.toZigbee.lumi_switch_mode_switch, + ], + exposes: [ + e.switch(), + e.action(['single', 'double']), + e.power_outage_memory(), + e.flip_indicator_light(), + e.led_disabled_night(), + e.power_outage_count(), + e.device_temperature().withAccess(ea.STATE), e.operation_mode_select(['control_relay', 'decoupled']).withDescription('Switches between direct relay control and action sending only'), - e.mode_switch_select(['anti_flicker_mode', 'quick_mode']) - .withDescription('Features. Anti flicker mode can be used to solve blinking issues of some lights.' + - 'Quick mode makes the device respond faster.')], + e + .mode_switch_select(['anti_flicker_mode', 'quick_mode']) + .withDescription( + 'Features. Anti flicker mode can be used to solve blinking issues of some lights.' + + 'Quick mode makes the device respond faster.', + ), + ], configure: async (device, coordinatorEndpoint) => { const endpoint1 = device.getEndpoint(1); // set "event" mode - await endpoint1.write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); + await endpoint1.write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); }, extend: [lumiPreventReset()], }, @@ -674,27 +883,37 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Smart wall switch H1 EU (no neutral, double rocker)', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_opple, lumi.toZigbee.lumi_switch_power_outage_memory, - lumi.toZigbee.lumi_flip_indicator_light, lumi.toZigbee.lumi_led_disabled_night, lumi.toZigbee.lumi_switch_mode_switch], + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_flip_indicator_light, + lumi.toZigbee.lumi_led_disabled_night, + lumi.toZigbee.lumi_switch_mode_switch, + ], meta: {multiEndpoint: true}, endpoint: (_device) => { - return {'left': 1, 'right': 2}; + return {left: 1, right: 2}; }, - exposes: [e.switch().withEndpoint('left'), e.switch().withEndpoint('right'), e.power_outage_memory(), - e.flip_indicator_light(), e.led_disabled_night(), e.power_outage_count(), + exposes: [ + e.switch().withEndpoint('left'), + e.switch().withEndpoint('right'), + e.power_outage_memory(), + e.flip_indicator_light(), + e.led_disabled_night(), + e.power_outage_count(), e.device_temperature().withAccess(ea.STATE), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for left button') - .withEndpoint('left'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for right button') - .withEndpoint('right'), - e.enum('mode_switch', ea.ALL, ['anti_flicker_mode', 'quick_mode']) - .withDescription('Anti flicker mode can be used to solve blinking issues of some lights.' + - 'Quick mode makes the device respond faster.'), - e.action(['single_left', 'double_left', 'single_right', 'double_right', 'single_both', 'double_both'])], + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for left button').withEndpoint('left'), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for right button').withEndpoint('right'), + e + .enum('mode_switch', ea.ALL, ['anti_flicker_mode', 'quick_mode']) + .withDescription( + 'Anti flicker mode can be used to solve blinking issues of some lights.' + 'Quick mode makes the device respond faster.', + ), + e.action(['single_left', 'double_left', 'single_right', 'double_right', 'single_both', 'double_both']), + ], configure: async (device, coordinatorEndpoint) => { - await device.getEndpoint(1).write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); + await device.getEndpoint(1).write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); }, extend: [lumiPreventReset()], }, @@ -704,15 +923,30 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Smart wall switch H1 EU (with neutral, single rocker)', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_power, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_power, lumi.toZigbee.lumi_switch_operation_mode_opple, lumi.toZigbee.lumi_switch_power_outage_memory, - lumi.toZigbee.lumi_flip_indicator_light, lumi.toZigbee.lumi_led_disabled_night], - exposes: [e.switch(), e.action(['single', 'double']), e.power().withAccess(ea.STATE_GET), e.energy(), e.flip_indicator_light(), - e.power_outage_memory(), e.device_temperature().withAccess(ea.STATE), e.led_disabled_night(), e.power_outage_count(), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode')], + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_power, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_flip_indicator_light, + lumi.toZigbee.lumi_led_disabled_night, + ], + exposes: [ + e.switch(), + e.action(['single', 'double']), + e.power().withAccess(ea.STATE_GET), + e.energy(), + e.flip_indicator_light(), + e.power_outage_memory(), + e.device_temperature().withAccess(ea.STATE), + e.led_disabled_night(), + e.power_outage_count(), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode'), + ], configure: async (device, coordinatorEndpoint) => { const endpoint1 = device.getEndpoint(1); // set "event" mode - await endpoint1.write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); + await endpoint1.write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); }, extend: [lumiZigbeeOTA(), lumiPreventReset()], }, @@ -722,24 +956,36 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Smart wall switch H1 EU (with neutral, double rocker)', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_power, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_power, lumi.toZigbee.lumi_switch_operation_mode_opple, lumi.toZigbee.lumi_switch_power_outage_memory, - lumi.toZigbee.lumi_flip_indicator_light, lumi.toZigbee.lumi_led_disabled_night], + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_power, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_flip_indicator_light, + lumi.toZigbee.lumi_led_disabled_night, + ], meta: {multiEndpoint: true, multiEndpointSkip: ['power', 'energy']}, endpoint: (device) => { - return {'left': 1, 'right': 2}; + return {left: 1, right: 2}; }, - exposes: [e.switch().withEndpoint('left'), e.switch().withEndpoint('right'), e.power().withAccess(ea.STATE_GET), e.energy(), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for left button') - .withEndpoint('left'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for right button') - .withEndpoint('right'), + exposes: [ + e.switch().withEndpoint('left'), + e.switch().withEndpoint('right'), + e.power().withAccess(ea.STATE_GET), + e.energy(), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for left button').withEndpoint('left'), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for right button').withEndpoint('right'), e.action(['single_left', 'double_left', 'single_right', 'double_right', 'single_both', 'double_both']), - e.device_temperature().withAccess(ea.STATE), e.power_outage_memory(), e.flip_indicator_light(), - e.led_disabled_night(), e.power_outage_count()], + e.device_temperature().withAccess(ea.STATE), + e.power_outage_memory(), + e.flip_indicator_light(), + e.led_disabled_night(), + e.power_outage_count(), + ], configure: async (device, coordinatorEndpoint) => { const endpoint1 = device.getEndpoint(1); // set "event" mode - await endpoint1.write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); + await endpoint1.write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); }, extend: [lumiPreventReset()], }, @@ -748,16 +994,20 @@ const definitions: Definition[] = [ model: 'QBKG04LM', vendor: 'Aqara', description: 'Smart wall switch (no neutral, single rocker)', - fromZigbee: [lumi.fromZigbee.lumi_on_off, lumi.fromZigbee.lumi_action, lumi.fromZigbee.lumi_operation_mode_basic, - lumi.legacyFromZigbee.QBKG04LM_QBKG11LM_click], + fromZigbee: [ + lumi.fromZigbee.lumi_on_off, + lumi.fromZigbee.lumi_action, + lumi.fromZigbee.lumi_operation_mode_basic, + lumi.legacyFromZigbee.QBKG04LM_QBKG11LM_click, + ], exposes: [ - e.switch(), e.action(['release', 'hold', 'double', 'single', 'hold_release']), - e.enum('operation_mode', ea.STATE_SET, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode'), + e.switch(), + e.action(['release', 'hold', 'double', 'single', 'hold_release']), + e.enum('operation_mode', ea.STATE_SET, ['control_relay', 'decoupled']).withDescription('Decoupled mode'), ], toZigbee: [tz.on_off, {...lumi.toZigbee.lumi_switch_operation_mode_basic, convertGet: null}], endpoint: (device) => { - return {'system': 1, 'default': 2}; + return {system: 1, default: 2}; }, configure: async (device, coordinatorEndpoint) => { // Device advertises itself as Router but is an EndDevice @@ -771,20 +1021,28 @@ const definitions: Definition[] = [ model: 'QBKG11LM', vendor: 'Aqara', description: 'Smart wall switch (with neutral, single rocker)', - fromZigbee: [lumi.fromZigbee.lumi_action, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_on_off, - lumi.fromZigbee.lumi_basic, lumi.fromZigbee.lumi_operation_mode_basic, - fz.ignore_multistate_report, lumi.fromZigbee.lumi_power, lumi.legacyFromZigbee.QBKG04LM_QBKG11LM_click, + fromZigbee: [ + lumi.fromZigbee.lumi_action, + lumi.fromZigbee.lumi_action_multistate, + lumi.fromZigbee.lumi_on_off, + lumi.fromZigbee.lumi_basic, + lumi.fromZigbee.lumi_operation_mode_basic, + fz.ignore_multistate_report, + lumi.fromZigbee.lumi_power, + lumi.legacyFromZigbee.QBKG04LM_QBKG11LM_click, lumi.legacyFromZigbee.QBKG11LM_click, ], exposes: [ - e.switch(), e.power().withAccess(ea.STATE_GET), e.device_temperature(), e.energy(), + e.switch(), + e.power().withAccess(ea.STATE_GET), + e.device_temperature(), + e.energy(), e.action(['single', 'double', 'release', 'hold']), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode'), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode'), ], toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_basic, lumi.toZigbee.lumi_power], endpoint: (device) => { - return {'system': 1}; + return {system: 1}; }, extend: [lumiZigbeeOTA(), lumiPreventReset()], configure: async (device, coordinatorEndpoint) => { @@ -797,21 +1055,36 @@ const definitions: Definition[] = [ model: 'QBKG03LM', vendor: 'Aqara', description: 'Smart wall switch (no neutral, double rocker)', - fromZigbee: [lumi.fromZigbee.lumi_action, lumi.fromZigbee.lumi_on_off, - lumi.fromZigbee.lumi_operation_mode_basic, lumi.fromZigbee.lumi_basic, lumi.legacyFromZigbee.QBKG03LM_QBKG12LM_click, + fromZigbee: [ + lumi.fromZigbee.lumi_action, + lumi.fromZigbee.lumi_on_off, + lumi.fromZigbee.lumi_operation_mode_basic, + lumi.fromZigbee.lumi_basic, + lumi.legacyFromZigbee.QBKG03LM_QBKG12LM_click, lumi.legacyFromZigbee.QBKG03LM_buttons, ], exposes: [ e.switch().withEndpoint('left'), e.switch().withEndpoint('right'), e.device_temperature(), - e.action(['release_left', 'release_right', 'release_both', 'double_left', 'double_right', - 'single_left', 'single_right', 'hold_release_left', 'hold_release_left']), - e.enum('operation_mode', ea.STATE_SET, ['control_left_relay', 'control_right_relay', 'decoupled']) + e.action([ + 'release_left', + 'release_right', + 'release_both', + 'double_left', + 'double_right', + 'single_left', + 'single_right', + 'hold_release_left', + 'hold_release_left', + ]), + e + .enum('operation_mode', ea.STATE_SET, ['control_left_relay', 'control_right_relay', 'decoupled']) .withDescription('Operation mode for left button') .withEndpoint('left') .withCategory('config'), - e.enum('operation_mode', ea.STATE_SET, ['control_left_relay', 'control_right_relay', 'decoupled']) + e + .enum('operation_mode', ea.STATE_SET, ['control_left_relay', 'control_right_relay', 'decoupled']) .withDescription('Operation mode for right button') .withEndpoint('right') .withCategory('config'), @@ -819,7 +1092,7 @@ const definitions: Definition[] = [ toZigbee: [tz.on_off, {...lumi.toZigbee.lumi_switch_operation_mode_basic, convertGet: null}, lumi.toZigbee.lumi_power], meta: {multiEndpoint: true}, endpoint: (device) => { - return {'system': 1, 'left': 2, 'right': 3}; + return {system: 1, left: 2, right: 3}; }, configure: async (device, coordinatorEndpoint) => { // Device advertises itself as Router but is an EndDevice @@ -833,28 +1106,49 @@ const definitions: Definition[] = [ model: 'QBKG12LM', vendor: 'Aqara', description: 'Smart wall switch (with neutral, double rocker)', - fromZigbee: [lumi.fromZigbee.lumi_action, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_on_off, - lumi.fromZigbee.lumi_basic, lumi.fromZigbee.lumi_operation_mode_basic, lumi.fromZigbee.lumi_power, - lumi.legacyFromZigbee.QBKG03LM_QBKG12LM_click, lumi.legacyFromZigbee.QBKG12LM_click, + fromZigbee: [ + lumi.fromZigbee.lumi_action, + lumi.fromZigbee.lumi_action_multistate, + lumi.fromZigbee.lumi_on_off, + lumi.fromZigbee.lumi_basic, + lumi.fromZigbee.lumi_operation_mode_basic, + lumi.fromZigbee.lumi_power, + lumi.legacyFromZigbee.QBKG03LM_QBKG12LM_click, + lumi.legacyFromZigbee.QBKG12LM_click, ], exposes: [ e.switch().withEndpoint('left'), e.switch().withEndpoint('right'), - e.device_temperature(), e.energy(), + e.device_temperature(), + e.energy(), e.power().withAccess(ea.STATE_GET), - e.action(['single_left', 'single_right', 'single_both', 'double_left', 'double_right', 'double_both', - 'hold_left', 'hold_right', 'hold_both', 'release_left', 'release_right', 'release_both']), - e.enum('operation_mode', ea.ALL, ['control_left_relay', 'control_right_relay', 'decoupled']) + e.action([ + 'single_left', + 'single_right', + 'single_both', + 'double_left', + 'double_right', + 'double_both', + 'hold_left', + 'hold_right', + 'hold_both', + 'release_left', + 'release_right', + 'release_both', + ]), + e + .enum('operation_mode', ea.ALL, ['control_left_relay', 'control_right_relay', 'decoupled']) .withDescription('Operation mode for left button') .withEndpoint('left'), - e.enum('operation_mode', ea.ALL, ['control_left_relay', 'control_right_relay', 'decoupled']) + e + .enum('operation_mode', ea.ALL, ['control_left_relay', 'control_right_relay', 'decoupled']) .withDescription('Operation mode for right button') .withEndpoint('right'), ], meta: {multiEndpoint: true, multiEndpointSkip: ['power', 'energy']}, toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_basic, lumi.toZigbee.lumi_power], endpoint: (device) => { - return {'left': 1, 'right': 2, 'system': 1}; + return {left: 1, right: 2, system: 1}; }, extend: [lumiZigbeeOTA(), lumiPreventReset()], configure: async (device, coordinatorEndpoint) => { @@ -868,17 +1162,32 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Wireless remote switch D1 (double rocker)', meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, - fromZigbee: [lumi.fromZigbee.lumi_basic, lumi.fromZigbee.lumi_action, lumi.fromZigbee.lumi_action_multistate, - lumi.legacyFromZigbee.lumi_on_off_action, lumi.legacyFromZigbee.lumi_multistate_action, + fromZigbee: [ + lumi.fromZigbee.lumi_basic, + lumi.fromZigbee.lumi_action, + lumi.fromZigbee.lumi_action_multistate, + lumi.legacyFromZigbee.lumi_on_off_action, + lumi.legacyFromZigbee.lumi_multistate_action, ], toZigbee: [], endpoint: (device) => { return {left: 1, right: 2, both: 3}; }, - exposes: [e.battery(), e.battery_voltage(), e.action([ - 'single_left', 'single_right', 'single_both', - 'double_left', 'double_right', 'double_both', - 'hold_left', 'hold_right', 'hold_both'])], + exposes: [ + e.battery(), + e.battery_voltage(), + e.action([ + 'single_left', + 'single_right', + 'single_both', + 'double_left', + 'double_right', + 'double_both', + 'hold_left', + 'hold_right', + 'hold_both', + ]), + ], extend: [quirkCheckinInterval('1_HOUR'), lumiPreventReset()], }, { @@ -886,17 +1195,20 @@ const definitions: Definition[] = [ model: 'QBKG21LM', vendor: 'Aqara', description: 'Smart wall switch D1 (no neutral, single rocker)', - fromZigbee: [lumi.fromZigbee.lumi_on_off, lumi.fromZigbee.lumi_action, lumi.fromZigbee.lumi_operation_mode_basic, + fromZigbee: [ + lumi.fromZigbee.lumi_on_off, + lumi.fromZigbee.lumi_action, + lumi.fromZigbee.lumi_operation_mode_basic, lumi.legacyFromZigbee.QBKG04LM_QBKG11LM_click, ], exposes: [ - e.switch(), e.action(['release', 'hold', 'double', 'single', 'hold_release']), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode'), + e.switch(), + e.action(['release', 'hold', 'double', 'single', 'hold_release']), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode'), ], toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_basic], endpoint: (device) => { - return {'system': 1, 'default': 2}; + return {system: 1, default: 2}; }, extend: [lumiPreventReset()], configure: async (device, coordinatorEndpoint) => { @@ -911,25 +1223,40 @@ const definitions: Definition[] = [ model: 'QBKG22LM', vendor: 'Aqara', description: 'Smart wall switch D1 (no neutral, double rocker)', - fromZigbee: [lumi.fromZigbee.lumi_on_off, lumi.fromZigbee.lumi_action, lumi.fromZigbee.lumi_operation_mode_basic, - lumi.legacyFromZigbee.QBKG03LM_QBKG12LM_click, lumi.legacyFromZigbee.QBKG03LM_buttons, + fromZigbee: [ + lumi.fromZigbee.lumi_on_off, + lumi.fromZigbee.lumi_action, + lumi.fromZigbee.lumi_operation_mode_basic, + lumi.legacyFromZigbee.QBKG03LM_QBKG12LM_click, + lumi.legacyFromZigbee.QBKG03LM_buttons, ], exposes: [ e.switch().withEndpoint('left'), e.switch().withEndpoint('right'), - e.action(['release_left', 'release_right', 'release_both', 'double_left', 'double_right', - 'single_left', 'single_right', 'hold_release_left', 'hold_release_left']), - e.enum('operation_mode', ea.ALL, ['control_left_relay', 'control_right_relay', 'decoupled']) + e.action([ + 'release_left', + 'release_right', + 'release_both', + 'double_left', + 'double_right', + 'single_left', + 'single_right', + 'hold_release_left', + 'hold_release_left', + ]), + e + .enum('operation_mode', ea.ALL, ['control_left_relay', 'control_right_relay', 'decoupled']) .withDescription('Operation mode for left button') .withEndpoint('left'), - e.enum('operation_mode', ea.ALL, ['control_left_relay', 'control_right_relay', 'decoupled']) + e + .enum('operation_mode', ea.ALL, ['control_left_relay', 'control_right_relay', 'decoupled']) .withDescription('Operation mode for right button') .withEndpoint('right'), ], toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_basic], meta: {multiEndpoint: true}, endpoint: (device) => { - return {'system': 1, 'left': 2, 'right': 3}; + return {system: 1, left: 2, right: 3}; }, extend: [lumiPreventReset()], configure: async (device, coordinatorEndpoint) => { @@ -944,36 +1271,57 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Smart wall switch D1 (no neutral, triple rocker)', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_opple, - lumi.toZigbee.lumi_switch_power_outage_memory, lumi.toZigbee.lumi_led_disabled_night, - lumi.toZigbee.lumi_switch_mode_switch, lumi.toZigbee.lumi_flip_indicator_light], + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_led_disabled_night, + lumi.toZigbee.lumi_switch_mode_switch, + lumi.toZigbee.lumi_flip_indicator_light, + ], meta: {multiEndpoint: true}, endpoint: (device) => { - return {'left': 1, 'center': 2, 'right': 3}; + return {left: 1, center: 2, right: 3}; }, exposes: [ - e.switch().withEndpoint('left'), e.switch().withEndpoint('center'), e.switch().withEndpoint('right'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for left button') - .withEndpoint('left'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) + e.switch().withEndpoint('left'), + e.switch().withEndpoint('center'), + e.switch().withEndpoint('right'), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for left button').withEndpoint('left'), + e + .enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) .withDescription('Decoupled mode for center button') .withEndpoint('center'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for right button') - .withEndpoint('right'), - e.enum('mode_switch', ea.ALL, ['anti_flicker_mode', 'quick_mode']) - .withDescription('Anti flicker mode can be used to solve blinking issues of some lights.' + - 'Quick mode makes the device respond faster.'), - e.power_outage_memory(), e.led_disabled_night(), e.device_temperature().withAccess(ea.STATE), e.flip_indicator_light(), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for right button').withEndpoint('right'), + e + .enum('mode_switch', ea.ALL, ['anti_flicker_mode', 'quick_mode']) + .withDescription( + 'Anti flicker mode can be used to solve blinking issues of some lights.' + 'Quick mode makes the device respond faster.', + ), + e.power_outage_memory(), + e.led_disabled_night(), + e.device_temperature().withAccess(ea.STATE), + e.flip_indicator_light(), e.action([ - 'left_single', 'left_double', 'center_single', 'center_double', 'right_single', 'right_double', - 'single_left_center', 'double_left_center', 'single_left_right', 'double_left_right', - 'single_center_right', 'double_center_right', 'single_all', 'double_all']), + 'left_single', + 'left_double', + 'center_single', + 'center_double', + 'right_single', + 'right_double', + 'single_left_center', + 'double_left_center', + 'single_left_right', + 'double_left_right', + 'single_center_right', + 'double_center_right', + 'single_all', + 'double_all', + ]), e.power_outage_count(), ], configure: async (device, coordinatorEndpoint) => { - await device.getEndpoint(1).write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); + await device.getEndpoint(1).write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); }, extend: [lumiZigbeeOTA(), lumiPreventReset()], }, @@ -983,32 +1331,53 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Smart wall switch D1 (with neutral, triple rocker)', exposes: [ - e.switch().withEndpoint('left'), e.switch().withEndpoint('center'), e.switch().withEndpoint('right'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for left button') - .withEndpoint('left'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) + e.switch().withEndpoint('left'), + e.switch().withEndpoint('center'), + e.switch().withEndpoint('right'), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for left button').withEndpoint('left'), + e + .enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) .withDescription('Decoupled mode for center button') .withEndpoint('center'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for right button') - .withEndpoint('right'), - e.power().withAccess(ea.STATE), e.power_outage_memory(), e.led_disabled_night(), e.voltage(), e.energy(), - e.device_temperature().withAccess(ea.STATE), e.flip_indicator_light(), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for right button').withEndpoint('right'), + e.power().withAccess(ea.STATE), + e.power_outage_memory(), + e.led_disabled_night(), + e.voltage(), + e.energy(), + e.device_temperature().withAccess(ea.STATE), + e.flip_indicator_light(), e.action([ - 'single_left', 'double_left', 'single_center', 'double_center', 'single_right', 'double_right', - 'single_left_center', 'double_left_center', 'single_left_right', 'double_left_right', - 'single_center_right', 'double_center_right', 'single_all', 'double_all']), + 'single_left', + 'double_left', + 'single_center', + 'double_center', + 'single_right', + 'double_right', + 'single_left_center', + 'double_left_center', + 'single_left_right', + 'double_left_right', + 'single_center_right', + 'double_center_right', + 'single_all', + 'double_all', + ]), ], fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_power, lumi.fromZigbee.lumi_specific, lumi.fromZigbee.lumi_action_multistate], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_opple, lumi.toZigbee.lumi_switch_power_outage_memory, - lumi.toZigbee.lumi_led_disabled_night, lumi.toZigbee.lumi_flip_indicator_light], + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_led_disabled_night, + lumi.toZigbee.lumi_flip_indicator_light, + ], meta: {multiEndpoint: true}, endpoint: (device) => { - return {'left': 1, 'center': 2, 'right': 3}; + return {left: 1, center: 2, right: 3}; }, configure: async (device, coordinatorEndpoint) => { - await device.getEndpoint(1).write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); + await device.getEndpoint(1).write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); }, extend: [lumiZigbeeOTA(), lumiPreventReset()], }, @@ -1020,14 +1389,16 @@ const definitions: Definition[] = [ fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_power, lumi.fromZigbee.lumi_basic, lumi.fromZigbee.lumi_action_multistate], toZigbee: [tz.on_off, lumi.toZigbee.lumi_power, lumi.toZigbee.lumi_switch_operation_mode_basic], endpoint: (device) => { - return {'system': 1}; + return {system: 1}; }, exposes: [ - e.switch(), e.power().withAccess(ea.STATE_GET), - e.energy(), e.device_temperature().withAccess(ea.STATE), - e.voltage(), e.action(['single', 'release']), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode'), + e.switch(), + e.power().withAccess(ea.STATE_GET), + e.energy(), + e.device_temperature().withAccess(ea.STATE), + e.voltage(), + e.action(['single', 'release']), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode'), ], configure: async (device, coordinatorEndpoint) => { device.type = 'Router'; @@ -1041,25 +1412,31 @@ const definitions: Definition[] = [ model: 'QBKG24LM', vendor: 'Aqara', description: 'Smart wall switch D1 (with neutral, double rocker)', - fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_power, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_operation_mode_basic, - lumi.fromZigbee.lumi_specific, lumi.fromZigbee.lumi_basic], + fromZigbee: [ + fz.on_off, + lumi.fromZigbee.lumi_power, + lumi.fromZigbee.lumi_action_multistate, + lumi.fromZigbee.lumi_operation_mode_basic, + lumi.fromZigbee.lumi_specific, + lumi.fromZigbee.lumi_basic, + ], toZigbee: [tz.on_off, lumi.toZigbee.lumi_power, lumi.toZigbee.lumi_switch_operation_mode_basic], meta: {multiEndpoint: true, multiEndpointSkip: ['power', 'energy']}, endpoint: (device) => { - return {'left': 1, 'right': 2, 'system': 1}; + return {left: 1, right: 2, system: 1}; }, exposes: [ e.switch().withEndpoint('left'), e.switch().withEndpoint('right'), e.energy(), e.power().withAccess(ea.STATE_GET), - e.action([ - 'hold_left', 'single_left', 'double_left', 'single_right', 'double_right', 'single_both', 'double_both', - ]), - e.enum('operation_mode', ea.ALL, ['control_left_relay', 'decoupled']) + e.action(['hold_left', 'single_left', 'double_left', 'single_right', 'double_right', 'single_both', 'double_both']), + e + .enum('operation_mode', ea.ALL, ['control_left_relay', 'decoupled']) .withDescription('Decoupled mode for left button') .withEndpoint('left'), - e.enum('operation_mode', ea.ALL, ['control_right_relay', 'decoupled']) + e + .enum('operation_mode', ea.ALL, ['control_right_relay', 'decoupled']) .withDescription('Decoupled mode for right button') .withEndpoint('right'), ], @@ -1075,7 +1452,7 @@ const definitions: Definition[] = [ lumiOnOff({powerOutageMemory: 'binary', operationMode: true}), lumiLedDisabledNight(), lumiFlipIndicatorLight(), - lumiAction({actionLookup: {'single': 1, 'double': 2}}), + lumiAction({actionLookup: {single: 1, double: 2}}), lumiPreventReset(), ], }, @@ -1086,11 +1463,11 @@ const definitions: Definition[] = [ description: 'Smart wall switch T1 (no neutral, double rocker)', extend: [ lumiZigbeeOTA(), - deviceEndpoints({endpoints: {'left': 1, 'right': 2}}), + deviceEndpoints({endpoints: {left: 1, right: 2}}), lumiOnOff({powerOutageMemory: 'binary', operationMode: true, endpointNames: ['left', 'right']}), lumiLedDisabledNight(), lumiFlipIndicatorLight(), - lumiAction({actionLookup: {'single': 1, 'double': 2}, buttonLookup: {'left': 41, 'right': 42, 'both': 51}}), + lumiAction({actionLookup: {single: 1, double: 2}, buttonLookup: {left: 41, right: 42, both: 51}}), lumiPreventReset(), ], }, @@ -1104,7 +1481,7 @@ const definitions: Definition[] = [ lumiOnOff({powerOutageMemory: 'binary', operationMode: true}), lumiLedDisabledNight(), lumiFlipIndicatorLight(), - lumiAction({actionLookup: {'single': 1, 'double': 2}}), + lumiAction({actionLookup: {single: 1, double: 2}}), lumiElectricityMeter(), lumiPower(), lumiPreventReset(), @@ -1119,11 +1496,11 @@ const definitions: Definition[] = [ forceDeviceType({type: 'Router'}), forcePowerSource({powerSource: 'Mains (single phase)'}), lumiZigbeeOTA(), - deviceEndpoints({endpoints: {'left': 1, 'right': 2}}), + deviceEndpoints({endpoints: {left: 1, right: 2}}), lumiOnOff({powerOutageMemory: 'binary', operationMode: true, endpointNames: ['left', 'right']}), lumiLedDisabledNight(), lumiFlipIndicatorLight(), - lumiAction({actionLookup: {'single': 1, 'double': 2}, buttonLookup: {'left': 41, 'right': 42, 'both': 51}}), + lumiAction({actionLookup: {single: 1, double: 2}, buttonLookup: {left: 41, right: 42, both: 51}}), lumiElectricityMeter(), lumiPower(), lumiPreventReset(), @@ -1136,15 +1513,22 @@ const definitions: Definition[] = [ description: 'Smart wall switch T1 (no neutral, triple rocker)', extend: [ lumiZigbeeOTA(), - deviceEndpoints({endpoints: {'left': 1, 'center': 2, 'right': 3}}), + deviceEndpoints({endpoints: {left: 1, center: 2, right: 3}}), lumiOnOff({powerOutageMemory: 'binary', operationMode: true, endpointNames: ['left', 'center', 'right']}), lumiLedDisabledNight(), lumiFlipIndicatorLight(), - lumiAction({actionLookup: {'single': 1, 'double': 2}, buttonLookup: { - 'left': 41, 'center': 42, 'right': 43, - 'left_center': 51, 'left_right': 52, 'center_right': 53, - 'all': 61, - }}), + lumiAction({ + actionLookup: {single: 1, double: 2}, + buttonLookup: { + left: 41, + center: 42, + right: 43, + left_center: 51, + left_right: 52, + center_right: 53, + all: 61, + }, + }), lumiPreventReset(), ], }, @@ -1155,15 +1539,22 @@ const definitions: Definition[] = [ description: 'Smart wall switch T1 (with neutral, triple rocker)', extend: [ lumiZigbeeOTA(), - deviceEndpoints({endpoints: {'left': 1, 'center': 2, 'right': 3}}), + deviceEndpoints({endpoints: {left: 1, center: 2, right: 3}}), lumiOnOff({powerOutageMemory: 'binary', operationMode: true, endpointNames: ['left', 'center', 'right']}), lumiLedDisabledNight(), lumiFlipIndicatorLight(), - lumiAction({actionLookup: {'single': 1, 'double': 2}, buttonLookup: { - 'left': 41, 'center': 42, 'right': 43, - 'left_center': 51, 'left_right': 52, 'center_right': 53, - 'all': 61, - }}), + lumiAction({ + actionLookup: {single: 1, double: 2}, + buttonLookup: { + left: 41, + center: 42, + right: 43, + left_center: 51, + left_right: 52, + center_right: 53, + all: 61, + }, + }), lumiElectricityMeter(), lumiPower(), lumiPreventReset(), @@ -1211,8 +1602,7 @@ const definitions: Definition[] = [ ], fromZigbee: [lumi.fromZigbee.lumi_specific, fz.temperature, fz.humidity, lumi.fromZigbee.lumi_pressure, fz.battery], toZigbee: [], - exposes: [e.temperature(), e.humidity(), e.pressure(), e.device_temperature(), e.battery(), e.battery_voltage(), - e.power_outage_count(false)], + exposes: [e.temperature(), e.humidity(), e.pressure(), e.device_temperature(), e.battery(), e.battery_voltage(), e.power_outage_count(false)], meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -1247,8 +1637,15 @@ const definitions: Definition[] = [ meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, fromZigbee: [lumi.fromZigbee.lumi_basic, fz.occupancy_with_timeout, lumi.fromZigbee.lumi_illuminance], toZigbee: [], - exposes: [e.battery(), e.occupancy(), e.device_temperature(), e.battery_voltage(), e.illuminance_lux().withProperty('illuminance'), - e.illuminance().withUnit('lx').withDescription('Measured illuminance in lux'), e.power_outage_count(false)], + exposes: [ + e.battery(), + e.occupancy(), + e.device_temperature(), + e.battery_voltage(), + e.illuminance_lux().withProperty('illuminance'), + e.illuminance().withUnit('lx').withDescription('Measured illuminance in lux'), + e.power_outage_count(false), + ], extend: [quirkCheckinInterval('1_HOUR')], }, { @@ -1259,11 +1656,21 @@ const definitions: Definition[] = [ whiteLabel: [{vendor: 'Yandex', model: 'YNDX-00522'}], fromZigbee: [lumi.fromZigbee.lumi_occupancy_illuminance, lumi.fromZigbee.lumi_specific, fz.battery], toZigbee: [lumi.toZigbee.lumi_detection_interval], - exposes: [e.occupancy(), e.illuminance_lux().withProperty('illuminance'), + exposes: [ + e.occupancy(), + e.illuminance_lux().withProperty('illuminance'), e.illuminance().withUnit('lx').withDescription('Measured illuminance in lux'), - e.numeric('detection_interval', ea.ALL).withValueMin(2).withValueMax(65535).withUnit('s') + e + .numeric('detection_interval', ea.ALL) + .withValueMin(2) + .withValueMax(65535) + .withUnit('s') .withDescription('Time interval for detecting actions'), - e.device_temperature(), e.battery(), e.battery_voltage(), e.power_outage_count(false)], + e.device_temperature(), + e.battery(), + e.battery_voltage(), + e.power_outage_count(false), + ], meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -1279,10 +1686,20 @@ const definitions: Definition[] = [ description: 'High precision motion sensor', fromZigbee: [lumi.fromZigbee.lumi_occupancy, lumi.fromZigbee.lumi_specific, fz.battery], toZigbee: [lumi.toZigbee.lumi_detection_interval, lumi.toZigbee.lumi_motion_sensitivity], - exposes: [e.occupancy(), e.enum('motion_sensitivity', ea.ALL, ['low', 'medium', 'high']), - e.numeric('detection_interval', ea.ALL).withValueMin(2).withValueMax(65535).withUnit('s') + exposes: [ + e.occupancy(), + e.enum('motion_sensitivity', ea.ALL, ['low', 'medium', 'high']), + e + .numeric('detection_interval', ea.ALL) + .withValueMin(2) + .withValueMax(65535) + .withUnit('s') .withDescription('Time interval for detecting actions'), - e.device_temperature(), e.battery(), e.battery_voltage(), e.power_outage_count(false)], + e.device_temperature(), + e.battery(), + e.battery_voltage(), + e.power_outage_count(false), + ], meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -1300,16 +1717,29 @@ const definitions: Definition[] = [ description: 'Motion sensor P1', fromZigbee: [lumi.fromZigbee.lumi_occupancy_illuminance, lumi.fromZigbee.lumi_specific, fz.battery], toZigbee: [lumi.toZigbee.lumi_detection_interval, lumi.toZigbee.lumi_motion_sensitivity, lumi.toZigbee.lumi_trigger_indicator], - exposes: [e.occupancy(), e.illuminance_lux().withProperty('illuminance'), + exposes: [ + e.occupancy(), + e.illuminance_lux().withProperty('illuminance'), e.illuminance().withUnit('lx').withDescription('Measured illuminance in lux'), - e.motion_sensitivity_select(['low', 'medium', 'high']) + e + .motion_sensitivity_select(['low', 'medium', 'high']) .withDescription('Select motion sensitivity to use. Press pairing button right before changing this otherwise it will fail.'), - e.detection_interval().withDescription('Time interval between action detection. ' + - 'Press pairing button right before changing this otherwise it will fail.'), - e.trigger_indicator().withDescription('When this option is enabled then ' + - 'blue LED will blink once when motion is detected. ' + - 'Press pairing button right before changing this otherwise it will fail.'), - e.device_temperature(), e.battery(), e.battery_voltage()], + e + .detection_interval() + .withDescription( + 'Time interval between action detection. ' + 'Press pairing button right before changing this otherwise it will fail.', + ), + e + .trigger_indicator() + .withDescription( + 'When this option is enabled then ' + + 'blue LED will blink once when motion is detected. ' + + 'Press pairing button right before changing this otherwise it will fail.', + ), + e.device_temperature(), + e.battery(), + e.battery_voltage(), + ], meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -1327,11 +1757,21 @@ const definitions: Definition[] = [ description: 'Motion sensor E1', fromZigbee: [lumi.fromZigbee.lumi_occupancy_illuminance, lumi.fromZigbee.lumi_specific, fz.battery], toZigbee: [lumi.toZigbee.lumi_detection_interval], - exposes: [e.occupancy(), e.illuminance_lux().withProperty('illuminance'), + exposes: [ + e.occupancy(), + e.illuminance_lux().withProperty('illuminance'), e.illuminance().withUnit('lx').withDescription('Measured illuminance in lux'), - e.numeric('detection_interval', ea.ALL).withValueMin(2).withValueMax(65535).withUnit('s') + e + .numeric('detection_interval', ea.ALL) + .withValueMin(2) + .withValueMax(65535) + .withUnit('s') .withDescription('Time interval for detecting actions'), - e.device_temperature(), e.battery(), e.battery_voltage(), e.power_outage_count(false)], + e.device_temperature(), + e.battery(), + e.battery_voltage(), + e.power_outage_count(false), + ], meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -1347,55 +1787,85 @@ const definitions: Definition[] = [ description: 'Presence sensor FP1', fromZigbee: [lumi.fromZigbee.lumi_specific, lumi.fromZigbee.lumi_presence_region_events], toZigbee: [ - lumi.toZigbee.lumi_presence, lumi.toZigbee.lumi_monitoring_mode, - lumi.toZigbee.lumi_approach_distance, lumi.toZigbee.lumi_motion_sensitivity, - lumi.toZigbee.lumi_reset_nopresence_status, lumi.toZigbee.lumi_presence_region_upsert, lumi.toZigbee.lumi_presence_region_delete, - ], - exposes: [ - e.presence().withAccess(ea.STATE_GET), e.device_temperature(), e.power_outage_count(), - e.enum('presence_event', ea.STATE, ['enter', 'leave', 'left_enter', 'right_leave', 'right_enter', 'left_leave', - 'approach', 'away']).withDescription('Presence events: "enter", "leave", "left_enter", "right_leave", ' + - '"right_enter", "left_leave", "approach", "away"'), - e.enum('monitoring_mode', ea.ALL, ['undirected', 'left_right']).withDescription('Monitoring mode with or ' + - 'without considering right and left sides'), - e.enum('approach_distance', ea.ALL, ['far', 'medium', 'near']).withDescription('The distance at which the ' + - 'sensor detects approaching'), - e.enum('motion_sensitivity', ea.ALL, ['low', 'medium', 'high']).withDescription('Different sensitivities ' + - 'means different static human body recognition rate and response speed of occupied'), + lumi.toZigbee.lumi_presence, + lumi.toZigbee.lumi_monitoring_mode, + lumi.toZigbee.lumi_approach_distance, + lumi.toZigbee.lumi_motion_sensitivity, + lumi.toZigbee.lumi_reset_nopresence_status, + lumi.toZigbee.lumi_presence_region_upsert, + lumi.toZigbee.lumi_presence_region_delete, + ], + exposes: [ + e.presence().withAccess(ea.STATE_GET), + e.device_temperature(), + e.power_outage_count(), + e + .enum('presence_event', ea.STATE, ['enter', 'leave', 'left_enter', 'right_leave', 'right_enter', 'left_leave', 'approach', 'away']) + .withDescription( + 'Presence events: "enter", "leave", "left_enter", "right_leave", ' + '"right_enter", "left_leave", "approach", "away"', + ), + e + .enum('monitoring_mode', ea.ALL, ['undirected', 'left_right']) + .withDescription('Monitoring mode with or ' + 'without considering right and left sides'), + e + .enum('approach_distance', ea.ALL, ['far', 'medium', 'near']) + .withDescription('The distance at which the ' + 'sensor detects approaching'), + e + .enum('motion_sensitivity', ea.ALL, ['low', 'medium', 'high']) + .withDescription('Different sensitivities ' + 'means different static human body recognition rate and response speed of occupied'), e.enum('reset_nopresence_status', ea.SET, ['']).withDescription('Reset the status of no presence'), - e.enum('action', ea.STATE, ['region_*_enter', 'region_*_leave', 'region_*_occupied', - 'region_*_unoccupied']).withDescription('Most recent region event. Event template is "region__", ' + - 'where is region number (1-10), is one of "enter", "leave", "occupied", "unoccupied". ' + - '"enter" / "leave" events are usually triggered first, followed by "occupied" / "unoccupied" after a couple of seconds.'), - e.composite('region_upsert', 'region_upsert', ea.SET) + e + .enum('action', ea.STATE, ['region_*_enter', 'region_*_leave', 'region_*_occupied', 'region_*_unoccupied']) + .withDescription( + 'Most recent region event. Event template is "region__", ' + + 'where is region number (1-10), is one of "enter", "leave", "occupied", "unoccupied". ' + + '"enter" / "leave" events are usually triggered first, followed by "occupied" / "unoccupied" after a couple of seconds.', + ), + e + .composite('region_upsert', 'region_upsert', ea.SET) .withDescription( 'Definition of a new region to be added (or replace existing one). ' + - 'Creating or modifying a region requires you to define which zones of a 7x4 detection grid ' + - 'should be active for that zone. Regions can overlap, meaning that a zone can be defined ' + - 'in more than one region (eg. "zone x = 1 & y = 1" can be added to region 1 & 2). ' + - '"Zone x = 1 & y = 1" is the nearest zone on the right (from sensor\'s perspective, along the detection path).', + 'Creating or modifying a region requires you to define which zones of a 7x4 detection grid ' + + 'should be active for that zone. Regions can overlap, meaning that a zone can be defined ' + + 'in more than one region (eg. "zone x = 1 & y = 1" can be added to region 1 & 2). ' + + '"Zone x = 1 & y = 1" is the nearest zone on the right (from sensor\'s perspective, along the detection path).', ) .withFeature( - e.numeric('region_id', ea.SET) + e + .numeric('region_id', ea.SET) .withValueMin(lumi.presence.constants.region_config_regionId_min) .withValueMax(lumi.presence.constants.region_config_regionId_max), ) .withFeature( - e.list('zones', ea.SET, - e.composite('Zone position', 'zone_position', ea.SET) - .withFeature(e.numeric('x', ea.SET) - .withValueMin(lumi.presence.constants.region_config_zoneX_min) - .withValueMax(lumi.presence.constants.region_config_zoneX_max)) - .withFeature(e.numeric('y', ea.SET) - .withValueMin(lumi.presence.constants.region_config_zoneY_min) - .withValueMax(lumi.presence.constants.region_config_zoneY_max)), - ).withDescription('list of dictionaries in the format {"x": 1, "y": 1}, {"x": 2, "y": 1}'), + e + .list( + 'zones', + ea.SET, + e + .composite('Zone position', 'zone_position', ea.SET) + .withFeature( + e + .numeric('x', ea.SET) + .withValueMin(lumi.presence.constants.region_config_zoneX_min) + .withValueMax(lumi.presence.constants.region_config_zoneX_max), + ) + .withFeature( + e + .numeric('y', ea.SET) + .withValueMin(lumi.presence.constants.region_config_zoneY_min) + .withValueMax(lumi.presence.constants.region_config_zoneY_max), + ), + ) + .withDescription('list of dictionaries in the format {"x": 1, "y": 1}, {"x": 2, "y": 1}'), ), - e.composite('region_delete', 'region_delete', ea.SET) + e + .composite('region_delete', 'region_delete', ea.SET) .withDescription('Region definition to be deleted from the device.') - .withFeature(e.numeric('region_id', ea.SET) - .withValueMin(lumi.presence.constants.region_config_regionId_min) - .withValueMax(lumi.presence.constants.region_config_regionId_max), + .withFeature( + e + .numeric('region_id', ea.SET) + .withValueMin(lumi.presence.constants.region_config_regionId_min) + .withValueMax(lumi.presence.constants.region_config_regionId_max), ), ], configure: async (device, coordinatorEndpoint) => { @@ -1418,7 +1888,8 @@ const definitions: Definition[] = [ {vendor: 'Xiaomi', model: 'YTC4039GL'}, {vendor: 'Xiaomi', model: 'YTC4005CN'}, {vendor: 'Xiaomi', model: 'YTC4015CN'}, - {vendor: 'Xiaomi', model: 'ZHTZ02LM'}], + {vendor: 'Xiaomi', model: 'ZHTZ02LM'}, + ], description: 'Mi door and window sensor', meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, fromZigbee: [lumi.fromZigbee.lumi_basic, lumi.fromZigbee.lumi_contact], @@ -1449,8 +1920,15 @@ const definitions: Definition[] = [ meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, fromZigbee: [lumi.fromZigbee.lumi_basic, fz.ias_water_leak_alarm_1], toZigbee: [], - exposes: [e.battery(), e.water_leak(), e.battery_low(), e.battery_voltage(), e.device_temperature(), e.power_outage_count(false), - e.trigger_count()], + exposes: [ + e.battery(), + e.water_leak(), + e.battery_low(), + e.battery_voltage(), + e.device_temperature(), + e.power_outage_count(false), + e.trigger_count(), + ], extend: [quirkCheckinInterval('1_HOUR')], }, { @@ -1476,9 +1954,18 @@ const definitions: Definition[] = [ whiteLabel: [{vendor: 'Xiaomi', model: 'MFKZQ01LM'}], meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, fromZigbee: [lumi.fromZigbee.lumi_basic, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_action_analog], - exposes: [e.battery(), e.battery_voltage(), e.angle('action_angle'), e.device_temperature(), e.power_outage_count(false), - e.cube_side('action_from_side'), e.cube_side('action_side'), e.cube_side('action_to_side'), e.cube_side('side'), - e.action(['shake', 'throw', 'wakeup', 'fall', 'tap', 'slide', 'flip180', 'flip90', 'rotate_left', 'rotate_right'])], + exposes: [ + e.battery(), + e.battery_voltage(), + e.angle('action_angle'), + e.device_temperature(), + e.power_outage_count(false), + e.cube_side('action_from_side'), + e.cube_side('action_side'), + e.cube_side('action_to_side'), + e.cube_side('side'), + e.action(['shake', 'throw', 'wakeup', 'fall', 'tap', 'slide', 'flip180', 'flip90', 'rotate_left', 'rotate_right']), + ], toZigbee: [], extend: [quirkCheckinInterval('1_HOUR')], }, @@ -1499,24 +1986,42 @@ const definitions: Definition[] = [ vendor: 'Xiaomi', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_power, lumi.fromZigbee.lumi_basic, fz.ignore_occupancy_report, fz.ignore_illuminance_report], toZigbee: [tz.on_off, lumi.toZigbee.lumi_power], - exposes: [e.switch(), e.power().withAccess(ea.STATE_GET), e.energy(), e.device_temperature().withAccess(ea.STATE), - e.voltage()], + exposes: [e.switch(), e.power().withAccess(ea.STATE_GET), e.energy(), e.device_temperature().withAccess(ea.STATE), e.voltage()], }, { zigbeeModel: ['lumi.plug.mmeu01'], model: 'ZNCZ04LM', description: 'Mi smart plug EU', vendor: 'Xiaomi', - fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_power, lumi.fromZigbee.lumi_specific, fz.ignore_occupancy_report, fz.ignore_illuminance_report, - fz.ignore_time_read], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_power, lumi.toZigbee.lumi_switch_power_outage_memory, - lumi.toZigbee.lumi_auto_off, lumi.toZigbee.lumi_led_disabled_night, - lumi.toZigbee.lumi_overload_protection], - exposes: [ - e.switch(), e.power().withAccess(ea.STATE_GET), e.energy(), e.device_temperature().withAccess(ea.STATE), - e.voltage(), e.current(), e.consumer_connected(), e.led_disabled_night(), - e.power_outage_memory(), e.auto_off(20), - e.overload_protection(100, 2300)], + fromZigbee: [ + fz.on_off, + lumi.fromZigbee.lumi_power, + lumi.fromZigbee.lumi_specific, + fz.ignore_occupancy_report, + fz.ignore_illuminance_report, + fz.ignore_time_read, + ], + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_power, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_auto_off, + lumi.toZigbee.lumi_led_disabled_night, + lumi.toZigbee.lumi_overload_protection, + ], + exposes: [ + e.switch(), + e.power().withAccess(ea.STATE_GET), + e.energy(), + e.device_temperature().withAccess(ea.STATE), + e.voltage(), + e.current(), + e.consumer_connected(), + e.led_disabled_night(), + e.power_outage_memory(), + e.auto_off(20), + e.overload_protection(100, 2300), + ], extend: [lumiZigbeeOTA()], }, { @@ -1524,16 +2029,35 @@ const definitions: Definition[] = [ model: 'ZNCZ12LM', description: 'Smart plug US', vendor: 'Aqara', - fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_power, lumi.fromZigbee.lumi_specific, lumi.fromZigbee.lumi_basic, - fz.ignore_occupancy_report, fz.ignore_illuminance_report], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_power, lumi.toZigbee.lumi_switch_power_outage_memory, - lumi.toZigbee.lumi_auto_off, lumi.toZigbee.lumi_led_disabled_night, - lumi.toZigbee.lumi_overload_protection], + fromZigbee: [ + fz.on_off, + lumi.fromZigbee.lumi_power, + lumi.fromZigbee.lumi_specific, + lumi.fromZigbee.lumi_basic, + fz.ignore_occupancy_report, + fz.ignore_illuminance_report, + ], + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_power, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_auto_off, + lumi.toZigbee.lumi_led_disabled_night, + lumi.toZigbee.lumi_overload_protection, + ], exposes: [ - e.switch(), e.power().withAccess(ea.STATE_GET), e.energy(), e.device_temperature().withAccess(ea.STATE), - e.voltage(), e.current(), e.consumer_connected(), e.led_disabled_night(), - e.power_outage_memory(), e.auto_off(20), - e.overload_protection(100, 2300)], + e.switch(), + e.power().withAccess(ea.STATE_GET), + e.energy(), + e.device_temperature().withAccess(ea.STATE), + e.voltage(), + e.current(), + e.consumer_connected(), + e.led_disabled_night(), + e.power_outage_memory(), + e.auto_off(20), + e.overload_protection(100, 2300), + ], extend: [lumiZigbeeOTA()], }, { @@ -1542,10 +2066,23 @@ const definitions: Definition[] = [ description: 'Smart plug EU', vendor: 'Aqara', extend: [forceDeviceType({type: 'Router'}), lumiZigbeeOTA()], - fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_basic, fz.electrical_measurement, fz.metering, - lumi.fromZigbee.lumi_specific, lumi.fromZigbee.lumi_power, fz.device_temperature], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_power_outage_memory, lumi.toZigbee.lumi_led_disabled_night, - lumi.toZigbee.lumi_overload_protection, lumi.toZigbee.lumi_auto_off, lumi.toZigbee.lumi_socket_button_lock], + fromZigbee: [ + fz.on_off, + lumi.fromZigbee.lumi_basic, + fz.electrical_measurement, + fz.metering, + lumi.fromZigbee.lumi_specific, + lumi.fromZigbee.lumi_power, + fz.device_temperature, + ], + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_led_disabled_night, + lumi.toZigbee.lumi_overload_protection, + lumi.toZigbee.lumi_auto_off, + lumi.toZigbee.lumi_socket_button_lock, + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']); @@ -1594,10 +2131,20 @@ const definitions: Definition[] = [ globalStore.putValue(device, 'interval', interval); } }, - exposes: [e.switch(), e.power(), e.energy(), e.power_outage_memory(), e.voltage(), e.current(), + exposes: [ + e.switch(), + e.power(), + e.energy(), + e.power_outage_memory(), + e.voltage(), + e.current(), e.device_temperature().withDescription('Device temperature (polled every 30 min)'), - e.consumer_connected(), e.led_disabled_night(), e.overload_protection(100, 2300), - e.auto_off(20), e.button_lock()], + e.consumer_connected(), + e.led_disabled_night(), + e.overload_protection(100, 2300), + e.auto_off(20), + e.button_lock(), + ], }, { zigbeeModel: ['lumi.plug.aq1'], @@ -1605,11 +2152,23 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Smart plug', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_power, fz.ignore_occupancy_report, lumi.fromZigbee.lumi_basic], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_power, lumi.toZigbee.lumi_led_disabled_night, - lumi.toZigbee.lumi_switch_power_outage_memory, lumi.toZigbee.lumi_auto_off], - exposes: [e.switch(), e.power().withAccess(ea.STATE_GET), e.energy(), e.device_temperature(), e.voltage(), - e.power_outage_memory(), e.led_disabled_night(), - e.auto_off(30)], + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_power, + lumi.toZigbee.lumi_led_disabled_night, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_auto_off, + ], + exposes: [ + e.switch(), + e.power().withAccess(ea.STATE_GET), + e.energy(), + e.device_temperature(), + e.voltage(), + e.power_outage_memory(), + e.led_disabled_night(), + e.auto_off(30), + ], extend: [customTimeResponse('2000_LOCAL')], }, { @@ -1619,8 +2178,14 @@ const definitions: Definition[] = [ vendor: 'Aqara', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_power, lumi.fromZigbee.lumi_basic], toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_power_outage_memory, lumi.toZigbee.lumi_power], - exposes: [e.switch(), e.power().withAccess(ea.STATE_GET), e.energy(), e.device_temperature().withAccess(ea.STATE), - e.voltage(), e.power_outage_memory()], + exposes: [ + e.switch(), + e.power().withAccess(ea.STATE_GET), + e.energy(), + e.device_temperature().withAccess(ea.STATE), + e.voltage(), + e.power_outage_memory(), + ], extend: [lumiZigbeeOTA()], }, { @@ -1633,9 +2198,16 @@ const definitions: Definition[] = [ fromZigbee: [lumi.fromZigbee.lumi_basic, lumi.fromZigbee.lumi_smoke], toZigbee: [lumi.toZigbee.lumi_sensitivity, lumi.toZigbee.lumi_selftest], exposes: [ - e.smoke(), e.battery_low(), e.tamper(), e.battery(), e.enum('sensitivity', ea.STATE_SET, ['low', 'medium', 'high']), - e.numeric('smoke_density', ea.STATE), e.enum('selftest', ea.SET, ['']), e.battery_voltage(), - e.binary('test', ea.STATE, true, false).withDescription('Test mode activated'), e.device_temperature(), + e.smoke(), + e.battery_low(), + e.tamper(), + e.battery(), + e.enum('sensitivity', ea.STATE_SET, ['low', 'medium', 'high']), + e.numeric('smoke_density', ea.STATE), + e.enum('selftest', ea.SET, ['']), + e.battery_voltage(), + e.binary('test', ea.STATE, true, false).withDescription('Test mode activated'), + e.device_temperature(), e.power_outage_count(false), ], extend: [quirkCheckinInterval('1_HOUR')], @@ -1649,8 +2221,11 @@ const definitions: Definition[] = [ fromZigbee: [fz.ias_gas_alarm_1, lumi.fromZigbee.lumi_gas_sensitivity, lumi.fromZigbee.lumi_gas_density], toZigbee: [lumi.toZigbee.lumi_sensitivity, lumi.toZigbee.lumi_selftest], exposes: [ - e.gas(), e.tamper(), e.enum('sensitivity', ea.STATE_SET, ['low', 'medium', 'high']), - e.numeric('gas_density', ea.STATE), e.enum('selftest', ea.SET, ['']), + e.gas(), + e.tamper(), + e.enum('sensitivity', ea.STATE_SET, ['low', 'medium', 'high']), + e.numeric('gas_density', ea.STATE), + e.enum('selftest', ea.SET, ['']), ], configure: async (device, coordinatorEndpoint) => { device.powerSource = 'Mains (single phase)'; @@ -1665,31 +2240,54 @@ const definitions: Definition[] = [ description: 'Smart natural gas detector', whiteLabel: [{vendor: 'Aqara', model: 'JT-BZ-03AQ/A'}], fromZigbee: [lumi.fromZigbee.lumi_specific], - toZigbee: [lumi.toZigbee.lumi_alarm, lumi.toZigbee.lumi_density, lumi.toZigbee.lumi_gas_sensitivity, - lumi.toZigbee.lumi_selftest, lumi.toZigbee.lumi_buzzer, - lumi.toZigbee.lumi_buzzer_manual, lumi.toZigbee.lumi_linkage_alarm, - lumi.toZigbee.lumi_state, lumi.toZigbee.lumi_power_outage_count], - exposes: [e.gas().withAccess(ea.STATE_GET), + toZigbee: [ + lumi.toZigbee.lumi_alarm, + lumi.toZigbee.lumi_density, + lumi.toZigbee.lumi_gas_sensitivity, + lumi.toZigbee.lumi_selftest, + lumi.toZigbee.lumi_buzzer, + lumi.toZigbee.lumi_buzzer_manual, + lumi.toZigbee.lumi_linkage_alarm, + lumi.toZigbee.lumi_state, + lumi.toZigbee.lumi_power_outage_count, + ], + exposes: [ + e.gas().withAccess(ea.STATE_GET), e.numeric('gas_density', ea.STATE_GET).withUnit('%LEL').withDescription('Value of gas concentration'), - e.enum('gas_sensitivity', ea.ALL, ['10%LEL', '15%LEL']).withDescription('Gas concentration value at which ' + - 'an alarm is triggered ("10%LEL" is more sensitive than "15%LEL")'), - e.enum('selftest', ea.SET, ['selftest']).withDescription('Starts the self-test process (checking the indicator ' + - 'light and buzzer work properly)'), + e + .enum('gas_sensitivity', ea.ALL, ['10%LEL', '15%LEL']) + .withDescription('Gas concentration value at which ' + 'an alarm is triggered ("10%LEL" is more sensitive than "15%LEL")'), + e + .enum('selftest', ea.SET, ['selftest']) + .withDescription('Starts the self-test process (checking the indicator ' + 'light and buzzer work properly)'), e.binary('test', ea.STATE, true, false).withDescription('Self-test in progress'), - e.enum('buzzer', ea.SET, ['mute', 'alarm']).withDescription('The buzzer can be muted and alarmed manually. ' + - 'During a gas alarm, the buzzer can be manually muted for 10 minutes ("mute"), but cannot be unmuted manually ' + - 'before this timeout expires. The buzzer cannot be pre-muted, as this function only works during a gas alarm. ' + - 'During the absence of a gas alarm, the buzzer can be manually alarmed ("alarm") and disalarmed ("mute"), ' + - 'but for this "linkage_alarm" option must be enabled'), + e + .enum('buzzer', ea.SET, ['mute', 'alarm']) + .withDescription( + 'The buzzer can be muted and alarmed manually. ' + + 'During a gas alarm, the buzzer can be manually muted for 10 minutes ("mute"), but cannot be unmuted manually ' + + 'before this timeout expires. The buzzer cannot be pre-muted, as this function only works during a gas alarm. ' + + 'During the absence of a gas alarm, the buzzer can be manually alarmed ("alarm") and disalarmed ("mute"), ' + + 'but for this "linkage_alarm" option must be enabled', + ), e.binary('buzzer_manual_alarm', ea.STATE_GET, true, false).withDescription('Buzzer alarmed (manually)'), e.binary('buzzer_manual_mute', ea.STATE_GET, true, false).withDescription('Buzzer muted (manually)'), - e.binary('linkage_alarm', ea.ALL, true, false).withDescription('When this option is enabled and a gas ' + - 'alarm has occurred, then "linkage_alarm_state"=true, and when the gas alarm has ended or the buzzer has ' + - 'been manually muted, then "linkage_alarm_state"=false'), + e + .binary('linkage_alarm', ea.ALL, true, false) + .withDescription( + 'When this option is enabled and a gas ' + + 'alarm has occurred, then "linkage_alarm_state"=true, and when the gas alarm has ended or the buzzer has ' + + 'been manually muted, then "linkage_alarm_state"=false', + ), e.binary('linkage_alarm_state', ea.STATE, true, false).withDescription('"linkage_alarm" is triggered'), - e.binary('state', ea.STATE_GET, 'preparation', 'work').withDescription('"Preparation" or "work" ' + - '(measurement of the gas concentration value and triggering of an alarm are only performed in the "work" state)'), - e.power_outage_count().withAccess(ea.STATE_GET)], + e + .binary('state', ea.STATE_GET, 'preparation', 'work') + .withDescription( + '"Preparation" or "work" ' + + '(measurement of the gas concentration value and triggering of an alarm are only performed in the "work" state)', + ), + e.power_outage_count().withAccess(ea.STATE_GET), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await endpoint.write('manuSpecificLumi', {0x014b: {value: 1, type: 0x20}}, {manufacturerCode: manufacturerCode}); @@ -1711,29 +2309,51 @@ const definitions: Definition[] = [ description: 'Smart smoke detector', whiteLabel: [{vendor: 'Aqara', model: 'JY-GZ-03AQ'}], fromZigbee: [lumi.fromZigbee.lumi_specific, fz.battery], - toZigbee: [lumi.toZigbee.lumi_alarm, lumi.toZigbee.lumi_density, lumi.toZigbee.lumi_selftest, - lumi.toZigbee.lumi_buzzer, lumi.toZigbee.lumi_buzzer_manual, - lumi.toZigbee.lumi_heartbeat_indicator, lumi.toZigbee.lumi_linkage_alarm], - exposes: [e.smoke().withAccess(ea.STATE_GET), + toZigbee: [ + lumi.toZigbee.lumi_alarm, + lumi.toZigbee.lumi_density, + lumi.toZigbee.lumi_selftest, + lumi.toZigbee.lumi_buzzer, + lumi.toZigbee.lumi_buzzer_manual, + lumi.toZigbee.lumi_heartbeat_indicator, + lumi.toZigbee.lumi_linkage_alarm, + ], + exposes: [ + e.smoke().withAccess(ea.STATE_GET), e.numeric('smoke_density', ea.STATE_GET).withDescription('Value of smoke concentration'), e.numeric('smoke_density_dbm', ea.STATE_GET).withUnit('dB/m').withDescription('Value of smoke concentration in dB/m'), - e.enum('selftest', ea.SET, ['selftest']).withDescription('Starts the self-test process (checking the indicator ' + - 'light and buzzer work properly)'), + e + .enum('selftest', ea.SET, ['selftest']) + .withDescription('Starts the self-test process (checking the indicator ' + 'light and buzzer work properly)'), e.binary('test', ea.STATE, true, false).withDescription('Self-test in progress'), - e.enum('buzzer', ea.SET, ['mute', 'alarm']).withDescription('The buzzer can be muted and alarmed manually. ' + - 'During a smoke alarm, the buzzer can be manually muted for 80 seconds ("mute") and unmuted ("alarm"). ' + - 'The buzzer cannot be pre-muted, as this function only works during a smoke alarm. ' + - 'During the absence of a smoke alarm, the buzzer can be manually alarmed ("alarm") and disalarmed ("mute"), ' + - 'but for this "linkage_alarm" option must be enabled'), + e + .enum('buzzer', ea.SET, ['mute', 'alarm']) + .withDescription( + 'The buzzer can be muted and alarmed manually. ' + + 'During a smoke alarm, the buzzer can be manually muted for 80 seconds ("mute") and unmuted ("alarm"). ' + + 'The buzzer cannot be pre-muted, as this function only works during a smoke alarm. ' + + 'During the absence of a smoke alarm, the buzzer can be manually alarmed ("alarm") and disalarmed ("mute"), ' + + 'but for this "linkage_alarm" option must be enabled', + ), e.binary('buzzer_manual_alarm', ea.STATE_GET, true, false).withDescription('Buzzer alarmed (manually)'), e.binary('buzzer_manual_mute', ea.STATE_GET, true, false).withDescription('Buzzer muted (manually)'), - e.binary('heartbeat_indicator', ea.ALL, true, false).withDescription('When this option is enabled then in ' + - 'the normal monitoring state, the green indicator light flashes every 60 seconds'), - e.binary('linkage_alarm', ea.ALL, true, false).withDescription('When this option is enabled and a smoke ' + - 'alarm has occurred, then "linkage_alarm_state"=true, and when the smoke alarm has ended or the buzzer has ' + - 'been manually muted, then "linkage_alarm_state"=false'), + e + .binary('heartbeat_indicator', ea.ALL, true, false) + .withDescription( + 'When this option is enabled then in ' + 'the normal monitoring state, the green indicator light flashes every 60 seconds', + ), + e + .binary('linkage_alarm', ea.ALL, true, false) + .withDescription( + 'When this option is enabled and a smoke ' + + 'alarm has occurred, then "linkage_alarm_state"=true, and when the smoke alarm has ended or the buzzer has ' + + 'been manually muted, then "linkage_alarm_state"=false', + ), e.binary('linkage_alarm_state', ea.STATE, true, false).withDescription('"linkage_alarm" is triggered'), - e.battery(), e.battery_voltage(), e.power_outage_count(false)], + e.battery(), + e.battery_voltage(), + e.power_outage_count(false), + ], meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -1766,10 +2386,20 @@ const definitions: Definition[] = [ fromZigbee: [lumi.fromZigbee.lumi_basic, lumi.fromZigbee.lumi_vibration_analog], toZigbee: [lumi.toZigbee.lumi_vibration_sensitivity], exposes: [ - e.battery(), e.device_temperature(), e.vibration(), e.action(['vibration', 'tilt', 'drop']), - e.numeric('strength', ea.STATE), e.enum('sensitivity', ea.STATE_SET, ['low', 'medium', 'high']), - e.angle_axis('angle_x'), e.angle_axis('angle_y'), e.angle_axis('angle_z'), - e.x_axis(), e.y_axis(), e.z_axis(), e.battery_voltage(), e.power_outage_count(false), + e.battery(), + e.device_temperature(), + e.vibration(), + e.action(['vibration', 'tilt', 'drop']), + e.numeric('strength', ea.STATE), + e.enum('sensitivity', ea.STATE_SET, ['low', 'medium', 'high']), + e.angle_axis('angle_x'), + e.angle_axis('angle_y'), + e.angle_axis('angle_z'), + e.x_axis(), + e.y_axis(), + e.z_axis(), + e.battery_voltage(), + e.power_outage_count(false), ], extend: [quirkCheckinInterval('1_HOUR')], }, @@ -1796,18 +2426,23 @@ const definitions: Definition[] = [ fromZigbee: [lumi.fromZigbee.lumi_basic, lumi.fromZigbee.lumi_curtain_position, lumi.fromZigbee.lumi_curtain_position_tilt], toZigbee: [lumi.toZigbee.lumi_curtain_position_state, lumi.toZigbee.lumi_curtain_options], onEvent: async (type, data, device) => { - if (type === 'message' && data.type === 'attributeReport' && data.cluster === 'genBasic' && - data.data.hasOwnProperty('1028') && data.data['1028'] == 0) { + if ( + type === 'message' && + data.type === 'attributeReport' && + data.cluster === 'genBasic' && + data.data.hasOwnProperty('1028') && + data.data['1028'] == 0 + ) { // Try to read the position after the motor stops, the device occasionally report wrong data right after stopping // Might need to add delay, seems to be working without one but needs more tests. await device.getEndpoint(1).read('genAnalogOutput', ['presentValue']); } }, - exposes: [e.cover_position().setAccess('state', ea.ALL), - e.binary('running', ea.STATE, true, false) - .withDescription('Whether the motor is moving or not'), - e.enum('motor_state', ea.STATE, ['stopped', 'opening', 'closing']) - .withDescription('Motor state')], + exposes: [ + e.cover_position().setAccess('state', ea.ALL), + e.binary('running', ea.STATE, true, false).withDescription('Whether the motor is moving or not'), + e.enum('motor_state', ea.STATE, ['stopped', 'opening', 'closing']).withDescription('Motor state'), + ], extend: [lumiZigbeeOTA()], }, { @@ -1818,9 +2453,10 @@ const definitions: Definition[] = [ whiteLabel: [{vendor: 'Aqara', model: 'SRSC-M01'}], fromZigbee: [lumi.fromZigbee.lumi_basic, lumi.fromZigbee.lumi_curtain_position, lumi.fromZigbee.lumi_curtain_position_tilt], toZigbee: [lumi.toZigbee.lumi_curtain_position_state, lumi.toZigbee.lumi_curtain_options], - exposes: [e.cover_position().setAccess('state', ea.ALL), - e.binary('running', ea.STATE, true, false) - .withDescription('Whether the motor is moving or not')], + exposes: [ + e.cover_position().setAccess('state', ea.ALL), + e.binary('running', ea.STATE, true, false).withDescription('Whether the motor is moving or not'), + ], extend: [lumiZigbeeOTA()], }, { @@ -1830,9 +2466,10 @@ const definitions: Definition[] = [ vendor: 'Aqara', fromZigbee: [lumi.fromZigbee.lumi_basic, lumi.fromZigbee.lumi_curtain_position, lumi.fromZigbee.lumi_curtain_position_tilt], toZigbee: [lumi.toZigbee.lumi_curtain_position_state, lumi.toZigbee.lumi_curtain_options], - exposes: [e.cover_position().setAccess('state', ea.ALL), - e.binary('running', ea.STATE, true, false) - .withDescription('Whether the motor is moving or not')], + exposes: [ + e.cover_position().setAccess('state', ea.ALL), + e.binary('running', ea.STATE, true, false).withDescription('Whether the motor is moving or not'), + ], extend: [lumiZigbeeOTA()], }, { @@ -1840,8 +2477,12 @@ const definitions: Definition[] = [ model: 'ZNCLDJ12LM', vendor: 'Aqara', description: 'Curtain controller B1', - fromZigbee: [lumi.fromZigbee.lumi_basic, lumi.fromZigbee.lumi_curtain_position, - lumi.fromZigbee.lumi_curtain_position_tilt, lumi.fromZigbee.lumi_curtain_status], + fromZigbee: [ + lumi.fromZigbee.lumi_basic, + lumi.fromZigbee.lumi_curtain_position, + lumi.fromZigbee.lumi_curtain_position_tilt, + lumi.fromZigbee.lumi_curtain_status, + ], toZigbee: [lumi.toZigbee.lumi_curtain_position_state, lumi.toZigbee.lumi_curtain_options], onEvent: async (type, data, device) => { // The position (genAnalogOutput.presentValue) reported via an attribute contains an invalid value @@ -1850,11 +2491,13 @@ const definitions: Definition[] = [ await device.endpoints[0].read('genAnalogOutput', ['presentValue']); } }, - exposes: [e.cover_position().setAccess('state', ea.ALL), e.battery(), - e.binary('running', ea.STATE, true, false) - .withDescription('Whether the motor is moving or not'), - e.enum('motor_state', ea.STATE, ['closing', 'opening', 'stopped']) - .withDescription('The current state of the motor.'), e.power_outage_count()], + exposes: [ + e.cover_position().setAccess('state', ea.ALL), + e.battery(), + e.binary('running', ea.STATE, true, false).withDescription('Whether the motor is moving or not'), + e.enum('motor_state', ea.STATE, ['closing', 'opening', 'stopped']).withDescription('The current state of the motor.'), + e.power_outage_count(), + ], extend: [lumiZigbeeOTA()], }, { @@ -1874,15 +2517,14 @@ const definitions: Definition[] = [ lumi.toZigbee.lumi_curtain_reverse, lumi.toZigbee.lumi_curtain_limits_calibration_ZNCLDJ14LM, ], - exposes: [e.cover_position().setAccess('state', ea.ALL), - e.binary('reverse_direction', ea.ALL, true, false) - .withDescription('Whether the curtain direction is inverted'), - e.binary('hand_open', ea.ALL, true, false) - .withDescription('Pulling curtains by hand starts the motor'), - e.binary('running', ea.STATE, true, false) - .withDescription('Whether the motor is moving or not'), - e.enum('motor_state', ea.STATE, ['closing', 'opening', 'stopped']) - .withDescription('The current state of the motor.'), e.power_outage_count()], + exposes: [ + e.cover_position().setAccess('state', ea.ALL), + e.binary('reverse_direction', ea.ALL, true, false).withDescription('Whether the curtain direction is inverted'), + e.binary('hand_open', ea.ALL, true, false).withDescription('Pulling curtains by hand starts the motor'), + e.binary('running', ea.STATE, true, false).withDescription('Whether the motor is moving or not'), + e.enum('motor_state', ea.STATE, ['closing', 'opening', 'stopped']).withDescription('The current state of the motor.'), + e.power_outage_count(), + ], extend: [lumiZigbeeOTA()], }, { @@ -1891,25 +2533,34 @@ const definitions: Definition[] = [ description: 'Roller shade driver E1', vendor: 'Aqara', whiteLabel: [{vendor: 'Aqara', model: 'RSD-M01'}], - fromZigbee: [lumi.fromZigbee.lumi_curtain_position, lumi.fromZigbee.lumi_curtain_status, - fz.ignore_basic_report, lumi.fromZigbee.lumi_specific], - toZigbee: [lumi.toZigbee.lumi_curtain_position_state, lumi.toZigbee.lumi_curtain_battery, - lumi.toZigbee.lumi_curtain_charging_status], + fromZigbee: [ + lumi.fromZigbee.lumi_curtain_position, + lumi.fromZigbee.lumi_curtain_status, + fz.ignore_basic_report, + lumi.fromZigbee.lumi_specific, + ], + toZigbee: [lumi.toZigbee.lumi_curtain_position_state, lumi.toZigbee.lumi_curtain_battery, lumi.toZigbee.lumi_curtain_charging_status], onEvent: async (type, data, device) => { - if (type === 'message' && data.type === 'attributeReport' && data.cluster === 'genMultistateOutput' && - data.data.hasOwnProperty('presentValue') && data.data['presentValue'] > 1) { + if ( + type === 'message' && + data.type === 'attributeReport' && + data.cluster === 'genMultistateOutput' && + data.data.hasOwnProperty('presentValue') && + data.data['presentValue'] > 1 + ) { // Try to read the position after the motor stops, the device occasionally report wrong data right after stopping // Might need to add delay, seems to be working without one but needs more tests. await device.getEndpoint(1).read('genAnalogOutput', ['presentValue']); } }, - exposes: [e.cover_position().setAccess('state', ea.ALL), e.battery().withAccess(ea.STATE_GET), e.device_temperature(), - e.binary('charging_status', ea.STATE_GET, true, false) - .withDescription('The current charging status.'), - e.enum('motor_state', ea.STATE, ['closing', 'opening', 'stopped', 'blocked']) - .withDescription('The current state of the motor.'), - e.binary('running', ea.STATE, true, false) - .withDescription('Whether the motor is moving or not')], + exposes: [ + e.cover_position().setAccess('state', ea.ALL), + e.battery().withAccess(ea.STATE_GET), + e.device_temperature(), + e.binary('charging_status', ea.STATE_GET, true, false).withDescription('The current charging status.'), + e.enum('motor_state', ea.STATE, ['closing', 'opening', 'stopped', 'blocked']).withDescription('The current state of the motor.'), + e.binary('running', ea.STATE, true, false).withDescription('Whether the motor is moving or not'), + ], configure: async (device, coordinatorEndpoint) => { device.powerSource = 'Battery'; device.save(); @@ -1919,9 +2570,7 @@ const definitions: Definition[] = [ extend: [ quirkAddEndpointCluster({ endpointID: 1, - inputClusters: [ - 'manuSpecificLumi', - ], + inputClusters: ['manuSpecificLumi'], }), lumiZigbeeOTA(), lumiMotorSpeed(), @@ -1937,12 +2586,7 @@ const definitions: Definition[] = [ {vendor: 'Aqara', model: 'CM-M01R'}, ], description: 'Curtain driver E1', - fromZigbee: [ - fz.battery, - lumi.fromZigbee.lumi_curtain_position_tilt, - lumi.fromZigbee.lumi_specific, - fz.power_source, - ], + fromZigbee: [fz.battery, lumi.fromZigbee.lumi_curtain_position_tilt, lumi.fromZigbee.lumi_specific, fz.power_source], toZigbee: [ lumi.toZigbee.lumi_curtain_position_state, lumi.toZigbee.lumi_curtain_battery_voltage, @@ -1974,9 +2618,9 @@ const definitions: Definition[] = [ const endpoint = device.getEndpoint(1); // Read correct version to replace version advertised by `genBasic` and `genOta`: // https://github.com/Koenkk/zigbee2mqtt/issues/15745 - await endpoint.read('manuSpecificLumi', [0x00EE], {manufacturerCode: manufacturerCode}); + await endpoint.read('manuSpecificLumi', [0x00ee], {manufacturerCode: manufacturerCode}); await endpoint.read('genPowerCfg', ['batteryPercentageRemaining']); - await endpoint.read('manuSpecificLumi', [0x040B], {manufacturerCode: manufacturerCode}); + await endpoint.read('manuSpecificLumi', [0x040b], {manufacturerCode: manufacturerCode}); await endpoint.read('manuSpecificLumi', [0x0428], {manufacturerCode: manufacturerCode}); await endpoint.read('genBasic', ['powerSource']); await endpoint.read('closuresWindowCovering', ['currentPositionLiftPercentage']); @@ -1992,13 +2636,19 @@ const definitions: Definition[] = [ meta: {multiEndpoint: true}, toZigbee: [tz.on_off, lumi.toZigbee.lumi_interlock, lumi.toZigbee.lumi_power, lumi.toZigbee.lumi_switch_power_outage_memory], endpoint: (device) => { - return {'l1': 1, 'l2': 2}; + return {l1: 1, l2: 2}; }, - exposes: [e.power().withAccess(ea.STATE_GET), e.energy(), e.device_temperature(), e.voltage(), e.current(), - e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), e.power_outage_count(false), + exposes: [ + e.power().withAccess(ea.STATE_GET), + e.energy(), + e.device_temperature(), + e.voltage(), + e.current(), + e.switch().withEndpoint('l1'), + e.switch().withEndpoint('l2'), + e.power_outage_count(false), e.power_outage_memory(), - e.binary('interlock', ea.STATE_SET, true, false) - .withDescription('Enabling prevents both relais being on at the same time'), + e.binary('interlock', ea.STATE_SET, true, false).withDescription('Enabling prevents both relais being on at the same time'), ], extend: [lumiZigbeeOTA()], configure: async (device, coordinatorEndpoint) => { @@ -2016,14 +2666,20 @@ const definitions: Definition[] = [ toZigbee: [tz.on_off], meta: {multiEndpoint: true, multiEndpointSkip: ['power', 'energy']}, endpoint: (device) => { - return {'l1': 1, 'l2': 2}; + return {l1: 1, l2: 2}; }, - exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), - e.power(), e.current(), e.energy(), e.voltage(), e.device_temperature(), + exposes: [ + e.switch().withEndpoint('l1'), + e.switch().withEndpoint('l2'), + e.power(), + e.current(), + e.energy(), + e.voltage(), + e.device_temperature(), ], extend: [ lumiSwitchType(), - lumiPowerOnBehavior({lookup: {'on': 0, 'previous': 1, 'off': 2, 'toggle': 3}}), + lumiPowerOnBehavior({lookup: {on: 0, previous: 1, off: 2, toggle: 3}}), lumiOperationMode({description: 'Decoupled mode for 1st relay', endpointName: 'l1'}), lumiOperationMode({description: 'Decoupled mode for 2nd relay', endpointName: 'l2'}), lumiAction({endpointNames: ['l1', 'l2']}), @@ -2038,7 +2694,7 @@ const definitions: Definition[] = [ }), enumLookup({ name: 'mode', - lookup: {'power': 0, 'pulse': 1, 'dry': 3}, + lookup: {power: 0, pulse: 1, dry: 3}, cluster: 'manuSpecificLumi', attribute: {ID: 0x0289, type: 0x20}, description: 'Work mode: Power mode, Dry mode with impulse, Dry mode', @@ -2065,12 +2721,29 @@ const definitions: Definition[] = [ fromZigbee: [lumi.fromZigbee.lumi_basic, lumi.fromZigbee.lumi_door_lock_report, lumi.fromZigbee.lumi_door_lock_low_battery], toZigbee: [], exposes: [ - e.battery(), e.battery_voltage(), e.battery_low(), e.binary('state', ea.STATE, 'UNLOCK', 'LOCK'), + e.battery(), + e.battery_voltage(), + e.battery_low(), + e.binary('state', ea.STATE, 'UNLOCK', 'LOCK'), e.binary('reverse', ea.STATE, 'UNLOCK', 'LOCK'), e.enum('action', ea.STATE, [ - 'finger_not_match', 'password_not_match', 'reverse_lock', 'reverse_lock_cancel', 'locked', 'lock_opened', - 'finger_add', 'finger_delete', 'password_add', 'password_delete', 'lock_opened_inside', 'lock_opened_outside', - 'ring_bell', 'change_language_to', 'finger_open', 'password_open', 'door_closed', + 'finger_not_match', + 'password_not_match', + 'reverse_lock', + 'reverse_lock_cancel', + 'locked', + 'lock_opened', + 'finger_add', + 'finger_delete', + 'password_add', + 'password_delete', + 'lock_opened_inside', + 'lock_opened_outside', + 'ring_bell', + 'change_language_to', + 'finger_open', + 'password_open', + 'door_closed', ]), ], meta: {battery: {voltageToPercentage: '4LR6AA1_5v'}}, @@ -2092,9 +2765,23 @@ const definitions: Definition[] = [ e.binary('state', ea.STATE, 'UNLOCK', 'LOCK'), e.binary('reverse', ea.STATE, 'UNLOCK', 'LOCK'), e.enum('action', ea.STATE, [ - 'finger_not_match', 'password_not_match', 'reverse_lock', 'reverse_lock_cancel', 'locked', 'lock_opened', - 'finger_add', 'finger_delete', 'password_add', 'password_delete', 'lock_opened_inside', 'lock_opened_outside', - 'ring_bell', 'change_language_to', 'finger_open', 'password_open', 'door_closed', + 'finger_not_match', + 'password_not_match', + 'reverse_lock', + 'reverse_lock_cancel', + 'locked', + 'lock_opened', + 'finger_add', + 'finger_delete', + 'password_add', + 'password_delete', + 'lock_opened_inside', + 'lock_opened_outside', + 'ring_bell', + 'change_language_to', + 'finger_open', + 'password_open', + 'door_closed', ]), ], extend: [quirkCheckinInterval('1_HOUR')], @@ -2115,9 +2802,23 @@ const definitions: Definition[] = [ e.binary('state', ea.STATE, 'UNLOCK', 'LOCK'), e.binary('reverse', ea.STATE, 'UNLOCK', 'LOCK'), e.enum('action', ea.STATE, [ - 'finger_not_match', 'password_not_match', 'reverse_lock', 'reverse_lock_cancel', 'locked', 'lock_opened', - 'finger_add', 'finger_delete', 'password_add', 'password_delete', 'lock_opened_inside', 'lock_opened_outside', - 'ring_bell', 'change_language_to', 'finger_open', 'password_open', 'door_closed', + 'finger_not_match', + 'password_not_match', + 'reverse_lock', + 'reverse_lock_cancel', + 'locked', + 'lock_opened', + 'finger_add', + 'finger_delete', + 'password_add', + 'password_delete', + 'lock_opened_inside', + 'lock_opened_outside', + 'ring_bell', + 'change_language_to', + 'finger_open', + 'password_open', + 'door_closed', ]), ], extend: [quirkCheckinInterval('1_HOUR'), lumiZigbeeOTA()], @@ -2127,19 +2828,38 @@ const definitions: Definition[] = [ model: 'WXCJKG11LM', vendor: 'Aqara', description: 'Opple wireless switch (single band)', - fromZigbee: [lumi.fromZigbee.lumi_action_on, lumi.fromZigbee.lumi_action_off, fz.battery, - lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific], - exposes: [e.battery(), e.battery_voltage(), e.action([ - 'button_1_hold', 'button_1_release', 'button_1_single', 'button_1_double', 'button_1_triple', - 'button_2_hold', 'button_2_release', 'button_2_single', 'button_2_double', 'button_2_triple', - ]), e.enum('operation_mode', ea.ALL, ['command', 'event']) - .withDescription('Operation mode, select "command" to enable bindings (wake up the device before changing modes!)')], + fromZigbee: [ + lumi.fromZigbee.lumi_action_on, + lumi.fromZigbee.lumi_action_off, + fz.battery, + lumi.fromZigbee.lumi_action_multistate, + lumi.fromZigbee.lumi_specific, + ], + exposes: [ + e.battery(), + e.battery_voltage(), + e.action([ + 'button_1_hold', + 'button_1_release', + 'button_1_single', + 'button_1_double', + 'button_1_triple', + 'button_2_hold', + 'button_2_release', + 'button_2_single', + 'button_2_double', + 'button_2_triple', + ]), + e + .enum('operation_mode', ea.ALL, ['command', 'event']) + .withDescription('Operation mode, select "command" to enable bindings (wake up the device before changing modes!)'), + ], toZigbee: [lumi.toZigbee.lumi_operation_mode_opple], meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, extend: [quirkCheckinInterval('1_HOUR')], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); - await endpoint.write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode}); + await endpoint.write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode}); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genPowerCfg']); }, }, @@ -2148,25 +2868,51 @@ const definitions: Definition[] = [ model: 'WXCJKG12LM', vendor: 'Aqara', description: 'Opple wireless switch (double band)', - fromZigbee: [lumi.fromZigbee.lumi_action_on, lumi.fromZigbee.lumi_action_off, - lumi.fromZigbee.lumi_action_step, lumi.fromZigbee.lumi_action_step_color_temp, fz.battery, - lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific], - exposes: [e.battery(), e.battery_voltage(), e.action([ - 'button_1_hold', 'button_1_release', 'button_1_single', 'button_1_double', 'button_1_triple', - 'button_2_hold', 'button_2_release', 'button_2_single', 'button_2_double', 'button_2_triple', - 'button_3_hold', 'button_3_release', 'button_3_single', 'button_3_double', 'button_3_triple', - 'button_4_hold', 'button_4_release', 'button_4_single', 'button_4_double', 'button_4_triple', - ]), e.enum('operation_mode', ea.ALL, ['command', 'event']) - .withDescription('Operation mode, select "command" to enable bindings (wake up the device before changing modes!)')], + fromZigbee: [ + lumi.fromZigbee.lumi_action_on, + lumi.fromZigbee.lumi_action_off, + lumi.fromZigbee.lumi_action_step, + lumi.fromZigbee.lumi_action_step_color_temp, + fz.battery, + lumi.fromZigbee.lumi_action_multistate, + lumi.fromZigbee.lumi_specific, + ], + exposes: [ + e.battery(), + e.battery_voltage(), + e.action([ + 'button_1_hold', + 'button_1_release', + 'button_1_single', + 'button_1_double', + 'button_1_triple', + 'button_2_hold', + 'button_2_release', + 'button_2_single', + 'button_2_double', + 'button_2_triple', + 'button_3_hold', + 'button_3_release', + 'button_3_single', + 'button_3_double', + 'button_3_triple', + 'button_4_hold', + 'button_4_release', + 'button_4_single', + 'button_4_double', + 'button_4_triple', + ]), + e + .enum('operation_mode', ea.ALL, ['command', 'event']) + .withDescription('Operation mode, select "command" to enable bindings (wake up the device before changing modes!)'), + ], toZigbee: [lumi.toZigbee.lumi_operation_mode_opple], meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, extend: [quirkCheckinInterval('1_HOUR')], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); - await endpoint.write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode}); - await reporting.bind(endpoint, coordinatorEndpoint, [ - 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl', 'genPowerCfg', - ]); + await endpoint.write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode}); + await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'lightingColorCtrl', 'genPowerCfg']); }, }, { @@ -2174,29 +2920,65 @@ const definitions: Definition[] = [ model: 'WXCJKG13LM', vendor: 'Aqara', description: 'Opple wireless switch (triple band)', - fromZigbee: [lumi.fromZigbee.lumi_action_on, lumi.fromZigbee.lumi_action_off, - lumi.fromZigbee.lumi_action_step, lumi.fromZigbee.lumi_action_move, lumi.fromZigbee.lumi_action_stop, - lumi.fromZigbee.lumi_action_step_color_temp, lumi.fromZigbee.lumi_action_move_color_temp, - fz.battery, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific], - exposes: [e.battery(), e.battery_voltage(), e.action([ - 'button_1_hold', 'button_1_release', 'button_1_single', 'button_1_double', 'button_1_triple', - 'button_2_hold', 'button_2_release', 'button_2_single', 'button_2_double', 'button_2_triple', - 'button_3_hold', 'button_3_release', 'button_3_single', 'button_3_double', 'button_3_triple', - 'button_4_hold', 'button_4_release', 'button_4_single', 'button_4_double', 'button_4_triple', - 'button_5_hold', 'button_5_release', 'button_5_single', 'button_5_double', 'button_5_triple', - 'button_6_hold', 'button_6_release', 'button_6_single', 'button_6_double', 'button_6_triple', - ]), e.enum('operation_mode', ea.ALL, ['command', 'event']) - .withDescription('Operation mode, select "command" to enable bindings (wake up the device before changing modes!)'), - e.power_outage_count(false)], + fromZigbee: [ + lumi.fromZigbee.lumi_action_on, + lumi.fromZigbee.lumi_action_off, + lumi.fromZigbee.lumi_action_step, + lumi.fromZigbee.lumi_action_move, + lumi.fromZigbee.lumi_action_stop, + lumi.fromZigbee.lumi_action_step_color_temp, + lumi.fromZigbee.lumi_action_move_color_temp, + fz.battery, + lumi.fromZigbee.lumi_action_multistate, + lumi.fromZigbee.lumi_specific, + ], + exposes: [ + e.battery(), + e.battery_voltage(), + e.action([ + 'button_1_hold', + 'button_1_release', + 'button_1_single', + 'button_1_double', + 'button_1_triple', + 'button_2_hold', + 'button_2_release', + 'button_2_single', + 'button_2_double', + 'button_2_triple', + 'button_3_hold', + 'button_3_release', + 'button_3_single', + 'button_3_double', + 'button_3_triple', + 'button_4_hold', + 'button_4_release', + 'button_4_single', + 'button_4_double', + 'button_4_triple', + 'button_5_hold', + 'button_5_release', + 'button_5_single', + 'button_5_double', + 'button_5_triple', + 'button_6_hold', + 'button_6_release', + 'button_6_single', + 'button_6_double', + 'button_6_triple', + ]), + e + .enum('operation_mode', ea.ALL, ['command', 'event']) + .withDescription('Operation mode, select "command" to enable bindings (wake up the device before changing modes!)'), + e.power_outage_count(false), + ], toZigbee: [lumi.toZigbee.lumi_operation_mode_opple], meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, extend: [quirkCheckinInterval('1_HOUR')], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); - await endpoint.write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode}); - await reporting.bind(endpoint, coordinatorEndpoint, [ - 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl', 'genPowerCfg', - ]); + await endpoint.write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode}); + await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'lightingColorCtrl', 'genPowerCfg']); }, }, { @@ -2215,8 +2997,7 @@ const definitions: Definition[] = [ await reporting.illuminance(endpoint, {min: 15, max: constants.repInterval.HOUR, change: 500}); await endpoint.read('genPowerCfg', ['batteryVoltage']); }, - exposes: [e.battery(), e.battery_voltage(), e.illuminance().withAccess(ea.STATE_GET), - e.illuminance_lux().withAccess(ea.STATE_GET)], + exposes: [e.battery(), e.battery_voltage(), e.illuminance().withAccess(ea.STATE_GET), e.illuminance_lux().withAccess(ea.STATE_GET)], }, { zigbeeModel: ['lumi.light.acn128'], @@ -2306,10 +3087,24 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Single switch module T1 (with neutral)', // Ignore energy metering reports, rely on aqara_opple: https://github.com/Koenkk/zigbee2mqtt/issues/10709 - fromZigbee: [fz.on_off, fz.device_temperature, lumi.fromZigbee.lumi_specific, fz.ignore_metering, fz.ignore_electrical_measurement, - lumi.fromZigbee.lumi_power], - exposes: [e.switch(), e.energy(), e.power(), e.device_temperature(), e.power_outage_memory(), e.power_outage_count(), - e.switch_type(), e.voltage(), e.current(), + fromZigbee: [ + fz.on_off, + fz.device_temperature, + lumi.fromZigbee.lumi_specific, + fz.ignore_metering, + fz.ignore_electrical_measurement, + lumi.fromZigbee.lumi_power, + ], + exposes: [ + e.switch(), + e.energy(), + e.power(), + e.device_temperature(), + e.power_outage_memory(), + e.power_outage_count(), + e.switch_type(), + e.voltage(), + e.current(), ], toZigbee: [lumi.toZigbee.lumi_switch_type, tz.on_off, lumi.toZigbee.lumi_switch_power_outage_memory, lumi.toZigbee.lumi_led_disabled_night], configure: async (device, coordinatorEndpoint) => { @@ -2329,12 +3124,26 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Single switch module T1 (with neutral), CN', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_power, lumi.fromZigbee.lumi_specific], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_power, lumi.toZigbee.lumi_switch_type, - lumi.toZigbee.lumi_switch_power_outage_memory, lumi.toZigbee.lumi_led_disabled_night], - exposes: [e.switch(), e.power().withAccess(ea.STATE_GET), e.energy(), e.device_temperature().withAccess(ea.STATE), - e.voltage(), e.current(), e.power_outage_memory(), e.led_disabled_night(), e.switch_type()], + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_power, + lumi.toZigbee.lumi_switch_type, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_led_disabled_night, + ], + exposes: [ + e.switch(), + e.power().withAccess(ea.STATE_GET), + e.energy(), + e.device_temperature().withAccess(ea.STATE), + e.voltage(), + e.current(), + e.power_outage_memory(), + e.led_disabled_night(), + e.switch_type(), + ], configure: async (device, coordinatorEndpoint) => { - await device.getEndpoint(1).write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); + await device.getEndpoint(1).write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); device.powerSource = 'Mains (single phase)'; device.save(); }, @@ -2381,42 +3190,53 @@ const definitions: Definition[] = [ toZigbee: [tz.on_off, lumi.toZigbee.lumi_smart_panel_ZNCJMB14LM], meta: {multiEndpoint: true}, endpoint: (device) => { - return {'left': 1, 'center': 2, 'right': 3}; + return {left: 1, center: 2, right: 3}; }, - exposes: [e.switch().withEndpoint('left'), e.switch().withEndpoint('center'), + exposes: [ + e.switch().withEndpoint('left'), + e.switch().withEndpoint('center'), e.switch().withEndpoint('right'), e.binary('standby_enabled', ea.STATE_SET, true, false).withDescription('Enable standby'), e.enum('theme', ea.STATE_SET, ['classic', 'concise']).withDescription('Display theme'), e.enum('beep_volume', ea.STATE_SET, ['mute', 'low', 'medium', 'high']).withDescription('Beep volume'), - e.numeric('lcd_brightness', ea.STATE_SET).withValueMin(1).withValueMax(100).withUnit('%') + e + .numeric('lcd_brightness', ea.STATE_SET) + .withValueMin(1) + .withValueMax(100) + .withUnit('%') .withDescription('LCD brightness (will not persist if auto-brightness is enabled)'), e.enum('language', ea.STATE_SET, ['chinese', 'english']).withDescription('Interface language'), e.enum('screen_saver_style', ea.STATE_SET, ['classic', 'analog clock']).withDescription('Screen saver style'), - e.numeric('standby_time', ea.STATE_SET).withValueMin(0).withValueMax(65534).withUnit('s') - .withDescription('Display standby time'), + e.numeric('standby_time', ea.STATE_SET).withValueMin(0).withValueMax(65534).withUnit('s').withDescription('Display standby time'), e.enum('font_size', ea.STATE_SET, ['small', 'medium', 'large']).withDescription('Display font size'), e.binary('lcd_auto_brightness_enabled', ea.STATE_SET, true, false).withDescription('Enable LCD auto brightness'), e.enum('homepage', ea.STATE_SET, ['scene', 'feel', 'thermostat', 'switch']).withDescription('Default display homepage'), e.binary('screen_saver_enabled', ea.STATE_SET, true, false).withDescription('Enable screen saver'), - e.numeric('standby_lcd_brightness', ea.STATE_SET).withValueMin(1).withValueMax(100).withUnit('%') + e + .numeric('standby_lcd_brightness', ea.STATE_SET) + .withValueMin(1) + .withValueMax(100) + .withUnit('%') .withDescription('Standby LCD brightness'), - e.enum('available_switches', ea.STATE_SET, ['none', '1', '2', '3', '1 and 2', '1 and 3', '2 and 3', 'all']) + e + .enum('available_switches', ea.STATE_SET, ['none', '1', '2', '3', '1 and 2', '1 and 3', '2 and 3', 'all']) .withDescription('Control which switches are available in the switches screen (none disables switches screen)'), - e.composite('switch_1_text_icon', 'switch_1_text_icon', ea.STATE_SET).withDescription('Switch 1 text and icon') - .withFeature(e.enum('switch_1_icon', ea.STATE_SET, ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11']) - .withDescription('Icon')) - .withFeature(e.text('switch_1_text', ea.STATE_SET) - .withDescription('Text')), - e.composite('switch_2_text_icon', 'switch_2_text_icon', ea.STATE_SET).withDescription('Switch 2 text and icon') - .withFeature(e.enum('switch_2_icon', ea.STATE_SET, ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11']) - .withDescription('Icon')) - .withFeature(e.text('switch_2_text', ea.STATE_SET) - .withDescription('Text')), - e.composite('switch_3_text_icon', 'switch_3_text_icon', ea.STATE_SET).withDescription('Switch 3 text and icon') - .withFeature(e.enum('switch_3_icon', ea.STATE_SET, ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11']) - .withDescription('Icon')) - .withFeature(e.text('switch_3_text', ea.STATE_SET) - .withDescription('Text'))], + e + .composite('switch_1_text_icon', 'switch_1_text_icon', ea.STATE_SET) + .withDescription('Switch 1 text and icon') + .withFeature(e.enum('switch_1_icon', ea.STATE_SET, ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11']).withDescription('Icon')) + .withFeature(e.text('switch_1_text', ea.STATE_SET).withDescription('Text')), + e + .composite('switch_2_text_icon', 'switch_2_text_icon', ea.STATE_SET) + .withDescription('Switch 2 text and icon') + .withFeature(e.enum('switch_2_icon', ea.STATE_SET, ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11']).withDescription('Icon')) + .withFeature(e.text('switch_2_text', ea.STATE_SET).withDescription('Text')), + e + .composite('switch_3_text_icon', 'switch_3_text_icon', ea.STATE_SET) + .withDescription('Switch 3 text and icon') + .withFeature(e.enum('switch_3_icon', ea.STATE_SET, ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11']).withDescription('Icon')) + .withFeature(e.text('switch_3_text', ea.STATE_SET).withDescription('Text')), + ], configure: async (device, coordinatorEndpoint) => { await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff']); await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ['genOnOff']); @@ -2449,22 +3269,37 @@ const definitions: Definition[] = [ meta: {battery: {voltageToPercentage: '3V_2850_3000'}, multiEndpoint: true}, extend: [quirkCheckinInterval('1_HOUR')], exposes: [ - e.battery(), e.battery_voltage(), e.action([ - 'single_left', 'single_right', 'single_both', - 'double_left', 'double_right', 'double_both', - 'triple_left', 'triple_right', 'triple_both', - 'hold_left', 'hold_right', 'hold_both']), - e.enum('click_mode', ea.ALL, ['fast', 'multi']) - .withDescription('Click mode, fast: only supports single click which will be send immediately after clicking.' + - 'multi: supports more events like double and hold'), - e.enum('operation_mode', ea.ALL, ['command', 'event']) + e.battery(), + e.battery_voltage(), + e.action([ + 'single_left', + 'single_right', + 'single_both', + 'double_left', + 'double_right', + 'double_both', + 'triple_left', + 'triple_right', + 'triple_both', + 'hold_left', + 'hold_right', + 'hold_both', + ]), + e + .enum('click_mode', ea.ALL, ['fast', 'multi']) + .withDescription( + 'Click mode, fast: only supports single click which will be send immediately after clicking.' + + 'multi: supports more events like double and hold', + ), + e + .enum('operation_mode', ea.ALL, ['command', 'event']) .withDescription('Operation mode, select "command" to enable bindings (wake up the device before changing modes!)'), ], configure: async (device, coordinatorEndpoint) => { const endpoint1 = device.getEndpoint(1); const endpoint2 = device.getEndpoint(3); // set "event" mode - await endpoint1.write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode}); + await endpoint1.write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode}); // turn on the "multiple clicks" mode, otherwise the only "single click" events. // if value is 1 - there will be single clicks, 2 - multiple. await endpoint1.write('manuSpecificLumi', {0x0125: {value: 0x02, type: 0x20}}, {manufacturerCode: manufacturerCode}); @@ -2481,17 +3316,28 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Smart wall switch E1 (no neutral, single rocker)', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_opple, lumi.toZigbee.lumi_switch_power_outage_memory, - lumi.toZigbee.lumi_switch_mode_switch, lumi.toZigbee.lumi_flip_indicator_light], - exposes: [e.switch(), e.power_outage_memory(), e.action(['single', 'double']), - e.device_temperature(), e.flip_indicator_light(), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for button'), - e.enum('mode_switch', ea.ALL, ['anti_flicker_mode', 'quick_mode']) - .withDescription('Anti flicker mode can be used to solve blinking issues of some lights.' + - 'Quick mode makes the device respond faster.')], - configure: async (device, coordinatorEndpoint) => { - await device.getEndpoint(1).write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_switch_mode_switch, + lumi.toZigbee.lumi_flip_indicator_light, + ], + exposes: [ + e.switch(), + e.power_outage_memory(), + e.action(['single', 'double']), + e.device_temperature(), + e.flip_indicator_light(), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for button'), + e + .enum('mode_switch', ea.ALL, ['anti_flicker_mode', 'quick_mode']) + .withDescription( + 'Anti flicker mode can be used to solve blinking issues of some lights.' + 'Quick mode makes the device respond faster.', + ), + ], + configure: async (device, coordinatorEndpoint) => { + await device.getEndpoint(1).write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); }, extend: [lumiZigbeeOTA(), lumiPreventReset()], }, @@ -2501,28 +3347,34 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Smart wall switch E1 (no neutral, double rocker)', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_opple, lumi.toZigbee.lumi_switch_power_outage_memory, - lumi.toZigbee.lumi_switch_mode_switch, lumi.toZigbee.lumi_flip_indicator_light], + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_switch_mode_switch, + lumi.toZigbee.lumi_flip_indicator_light, + ], meta: {multiEndpoint: true}, endpoint: (device) => { - return {'left': 1, 'right': 2}; + return {left: 1, right: 2}; }, exposes: [ - e.switch().withEndpoint('left'), e.switch().withEndpoint('right'), e.device_temperature(), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for left button') - .withEndpoint('left'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for right button') - .withEndpoint('right'), - e.enum('mode_switch', ea.ALL, ['anti_flicker_mode', 'quick_mode']) - .withDescription('Anti flicker mode can be used to solve blinking issues of some lights.' + - 'Quick mode makes the device respond faster.'), + e.switch().withEndpoint('left'), + e.switch().withEndpoint('right'), + e.device_temperature(), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for left button').withEndpoint('left'), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for right button').withEndpoint('right'), + e + .enum('mode_switch', ea.ALL, ['anti_flicker_mode', 'quick_mode']) + .withDescription( + 'Anti flicker mode can be used to solve blinking issues of some lights.' + 'Quick mode makes the device respond faster.', + ), e.action(['single_left', 'double_left', 'single_right', 'double_right', 'single_both', 'double_both']), - e.power_outage_memory(), e.flip_indicator_light(), + e.power_outage_memory(), + e.flip_indicator_light(), ], configure: async (device, coordinatorEndpoint) => { - await device.getEndpoint(1).write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); + await device.getEndpoint(1).write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); }, extend: [lumiZigbeeOTA(), lumiPreventReset()], }, @@ -2539,12 +3391,7 @@ const definitions: Definition[] = [ quirkCheckinInterval('1_HOUR'), quirkAddEndpointCluster({ endpointID: 1, - inputClusters: [ - 'msTemperatureMeasurement', - 'msRelativeHumidity', - 'genAnalogInput', - 'manuSpecificLumi', - ], + inputClusters: ['msTemperatureMeasurement', 'msRelativeHumidity', 'genAnalogInput', 'manuSpecificLumi'], }), lumiAirQuality(), lumiVoc(), @@ -2567,25 +3414,28 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Smart wall switch E1 (with neutral, double rocker)', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_opple, lumi.toZigbee.lumi_switch_power_outage_memory, - lumi.toZigbee.lumi_flip_indicator_light], + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_flip_indicator_light, + ], meta: {multiEndpoint: true}, endpoint: (device) => { - return {'left': 1, 'right': 2}; + return {left: 1, right: 2}; }, exposes: [ - e.switch().withEndpoint('left'), e.switch().withEndpoint('right'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for left button') - .withEndpoint('left'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for right button') - .withEndpoint('right'), + e.switch().withEndpoint('left'), + e.switch().withEndpoint('right'), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for left button').withEndpoint('left'), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for right button').withEndpoint('right'), e.action(['single_left', 'double_left', 'single_right', 'double_right', 'single_both', 'double_both']), - e.power_outage_memory(), e.device_temperature(), e.flip_indicator_light(), + e.power_outage_memory(), + e.device_temperature(), + e.flip_indicator_light(), ], configure: async (device, coordinatorEndpoint) => { - await device.getEndpoint(1).write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); + await device.getEndpoint(1).write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); }, extend: [lumiZigbeeOTA(), lumiPreventReset()], }, @@ -2595,12 +3445,26 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Smart plug T1, CN', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_power, lumi.fromZigbee.lumi_specific], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_power_outage_memory, lumi.toZigbee.lumi_led_disabled_night, - lumi.toZigbee.lumi_overload_protection, lumi.toZigbee.lumi_socket_button_lock], - exposes: [e.switch(), e.power().withAccess(ea.STATE), e.energy(), e.device_temperature().withAccess(ea.STATE), - e.voltage(), e.current(), e.consumer_connected().withAccess(ea.STATE), - e.power_outage_memory(), e.led_disabled_night(), e.button_lock(), - e.overload_protection(100, 2500)], + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_led_disabled_night, + lumi.toZigbee.lumi_overload_protection, + lumi.toZigbee.lumi_socket_button_lock, + ], + exposes: [ + e.switch(), + e.power().withAccess(ea.STATE), + e.energy(), + e.device_temperature().withAccess(ea.STATE), + e.voltage(), + e.current(), + e.consumer_connected().withAccess(ea.STATE), + e.power_outage_memory(), + e.led_disabled_night(), + e.button_lock(), + e.overload_protection(100, 2500), + ], extend: [lumiZigbeeOTA()], }, { @@ -2609,12 +3473,22 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Smart wall switch E1 (with neutral, single rocker)', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_opple, lumi.toZigbee.lumi_switch_power_outage_memory, - lumi.toZigbee.lumi_flip_indicator_light], - exposes: [e.switch(), e.action(['single', 'double']), e.power_outage_memory(), e.device_temperature(), e.flip_indicator_light(), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode')], + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_flip_indicator_light, + ], + exposes: [ + e.switch(), + e.action(['single', 'double']), + e.power_outage_memory(), + e.device_temperature(), + e.flip_indicator_light(), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode'), + ], configure: async (device, coordinatorEndpoint) => { - await device.getEndpoint(1).write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); + await device.getEndpoint(1).write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); }, extend: [lumiZigbeeOTA(), lumiPreventReset()], }, @@ -2633,7 +3507,7 @@ const definitions: Definition[] = [ exposes: [e.battery(), e.battery_voltage(), e.action(['single', 'double', 'triple', 'quintuple', 'hold', 'release', 'many'])], configure: async (device, coordinatorEndpoint) => { const endpoint1 = device.getEndpoint(1); - await endpoint1.write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); + await endpoint1.write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); }, extend: [quirkCheckinInterval('1_HOUR'), lumiZigbeeOTA()], }, @@ -2645,12 +3519,21 @@ const definitions: Definition[] = [ fromZigbee: [fz.battery, fz.illuminance, lumi.fromZigbee.lumi_specific], toZigbee: [lumi.toZigbee.lumi_detection_period], meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, - exposes: [e.battery(), e.battery_voltage(), e.illuminance(), e.illuminance_lux(), - e.numeric('detection_period', exposes.access.ALL).withValueMin(1).withValueMax(59).withUnit('s') - .withDescription('Time interval in seconds to report after light changes')], + exposes: [ + e.battery(), + e.battery_voltage(), + e.illuminance(), + e.illuminance_lux(), + e + .numeric('detection_period', exposes.access.ALL) + .withValueMin(1) + .withValueMax(59) + .withUnit('s') + .withDescription('Time interval in seconds to report after light changes'), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); - await device.getEndpoint(1).write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); + await device.getEndpoint(1).write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); await endpoint.read('manuSpecificLumi', [0x0000], {manufacturerCode: manufacturerCode}); }, extend: [quirkCheckinInterval('1_HOUR'), lumiZigbeeOTA()], @@ -2661,19 +3544,34 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Smart wall outlet H1 (USB)', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_power, lumi.fromZigbee.lumi_specific], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_power_outage_memory, lumi.toZigbee.lumi_led_disabled_night, - lumi.toZigbee.lumi_button_switch_mode, lumi.toZigbee.lumi_overload_protection, lumi.toZigbee.lumi_socket_button_lock], + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_led_disabled_night, + lumi.toZigbee.lumi_button_switch_mode, + lumi.toZigbee.lumi_overload_protection, + lumi.toZigbee.lumi_socket_button_lock, + ], meta: {multiEndpoint: true, multiEndpointSkip: ['power', 'energy']}, endpoint: () => { - return {'relay': 1, 'usb': 2}; + return {relay: 1, usb: 2}; }, exposes: [ - e.switch().withEndpoint('relay'), e.switch().withEndpoint('usb'), - e.power().withAccess(ea.STATE), e.energy(), e.device_temperature().withAccess(ea.STATE), e.voltage(), - e.current(), e.power_outage_memory(), e.led_disabled_night(), e.button_lock(), - e.enum('button_switch_mode', exposes.access.ALL, ['relay', 'relay_and_usb']) + e.switch().withEndpoint('relay'), + e.switch().withEndpoint('usb'), + e.power().withAccess(ea.STATE), + e.energy(), + e.device_temperature().withAccess(ea.STATE), + e.voltage(), + e.current(), + e.power_outage_memory(), + e.led_disabled_night(), + e.button_lock(), + e + .enum('button_switch_mode', exposes.access.ALL, ['relay', 'relay_and_usb']) .withDescription('Control both relay and usb or only the relay with the physical switch button'), - e.overload_protection(100, 2500)], + e.overload_protection(100, 2500), + ], extend: [lumiZigbeeOTA()], }, { @@ -2697,13 +3595,25 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Smart wall outlet T1', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_power, lumi.fromZigbee.lumi_specific], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_power_outage_memory, lumi.toZigbee.lumi_led_disabled_night, - lumi.toZigbee.lumi_overload_protection, lumi.toZigbee.lumi_socket_button_lock], + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_led_disabled_night, + lumi.toZigbee.lumi_overload_protection, + lumi.toZigbee.lumi_socket_button_lock, + ], exposes: [ - e.switch(), e.power().withAccess(ea.STATE), e.energy(), - e.device_temperature().withAccess(ea.STATE), e.voltage(), - e.current(), e.power_outage_memory(), e.led_disabled_night(), e.button_lock(), - e.overload_protection(100, 2500)], + e.switch(), + e.power().withAccess(ea.STATE), + e.energy(), + e.device_temperature().withAccess(ea.STATE), + e.voltage(), + e.current(), + e.power_outage_memory(), + e.led_disabled_night(), + e.button_lock(), + e.overload_protection(100, 2500), + ], extend: [lumiZigbeeOTA()], }, { @@ -2715,12 +3625,12 @@ const definitions: Definition[] = [ quirkCheckinInterval('1_HOUR'), lumiPreventReset(), lumiCommandMode(), - lumiAction({actionLookup: {'hold': 0, 'single': 1, 'double': 2, 'release': 255}}), + lumiAction({actionLookup: {hold: 0, single: 1, double: 2, release: 255}}), lumiBattery({voltageToPercentage: '3V_2850_3000'}), lumiKnobRotation(), enumLookup({ name: 'sensitivity', - lookup: {'low': 720, 'medium': 360, 'high': 180}, + lookup: {low: 720, medium: 360, high: 180}, cluster: 'manuSpecificLumi', attribute: {ID: 0x0234, type: 0x21}, description: 'Rotation sensitivity', @@ -2737,10 +3647,17 @@ const definitions: Definition[] = [ extend: [quirkCheckinInterval('1_HOUR')], fromZigbee: [lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific], toZigbee: [lumi.toZigbee.lumi_switch_click_mode], - exposes: [e.battery(), e.battery_voltage(), e.action(['single', 'double', 'hold']), - e.enum('click_mode', ea.ALL, ['fast', 'multi']) - .withDescription('Click mode, fast: only supports single click which will be send immediately after clicking.' + - 'multi: supports more events like double and hold')], + exposes: [ + e.battery(), + e.battery_voltage(), + e.action(['single', 'double', 'hold']), + e + .enum('click_mode', ea.ALL, ['fast', 'multi']) + .withDescription( + 'Click mode, fast: only supports single click which will be send immediately after clicking.' + + 'multi: supports more events like double and hold', + ), + ], configure: async (device, coordinatorEndpoint) => { await device.getEndpoint(1).write('manuSpecificLumi', {0x0125: {value: 0x02, type: 0x20}}, {manufacturerCode: manufacturerCode}); }, @@ -2752,10 +3669,15 @@ const definitions: Definition[] = [ description: 'Wireless remote switch E1 (double rocker)', meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, extend: [quirkCheckinInterval('1_HOUR'), lumiPreventReset()], - exposes: [e.battery(), e.battery_voltage(), + exposes: [ + e.battery(), + e.battery_voltage(), e.action(['single_left', 'single_right', 'single_both', 'double_left', 'double_right', 'hold_left', 'hold_right']), - // eslint-disable-next-line max-len - e.enum('click_mode', ea.ALL, ['fast', 'multi']).withDescription('Click mode, fast: only supports single click which will be send immediately after clicking, multi: supports more events like double and hold'), + e + .enum('click_mode', ea.ALL, ['fast', 'multi']) + .withDescription( + 'Click mode, fast: only supports single click which will be send immediately after clicking, multi: supports more events like double and hold', + ), ], fromZigbee: [lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific], toZigbee: [lumi.toZigbee.lumi_switch_click_mode], @@ -2772,17 +3694,25 @@ const definitions: Definition[] = [ description: 'Wireless remote switch H1 (single rocker)', fromZigbee: [lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific, fz.command_toggle], toZigbee: [lumi.toZigbee.lumi_switch_click_mode, lumi.toZigbee.lumi_operation_mode_opple], - exposes: [e.battery(), e.battery_voltage(), e.action(['single', 'double', 'triple', 'hold']), - e.enum('click_mode', ea.ALL, ['fast', 'multi']) - .withDescription('Click mode, fast: only supports single click which will be send immediately after clicking.' + - 'multi: supports more events like double and hold'), - e.enum('operation_mode', ea.ALL, ['command', 'event']) - .withDescription('Operation mode, select "command" to enable bindings (wake up the device before changing modes!)')], + exposes: [ + e.battery(), + e.battery_voltage(), + e.action(['single', 'double', 'triple', 'hold']), + e + .enum('click_mode', ea.ALL, ['fast', 'multi']) + .withDescription( + 'Click mode, fast: only supports single click which will be send immediately after clicking.' + + 'multi: supports more events like double and hold', + ), + e + .enum('operation_mode', ea.ALL, ['command', 'event']) + .withDescription('Operation mode, select "command" to enable bindings (wake up the device before changing modes!)'), + ], meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, extend: [quirkCheckinInterval('1_HOUR')], configure: async (device, coordinatorEndpoint) => { const endpoint1 = device.getEndpoint(1); - await endpoint1.write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); + await endpoint1.write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); await endpoint1.read('manuSpecificLumi', [0x0125], {manufacturerCode: manufacturerCode}); }, }, @@ -2795,7 +3725,8 @@ const definitions: Definition[] = [ toZigbee: [lumi.toZigbee.lumi_trv, tz.thermostat_occupied_heating_setpoint], exposes: [ e.setup().withDescription('Indicates if the device is in setup mode (E11)'), - e.climate() + e + .climate() .withSetpoint('occupied_heating_setpoint', 5, 30, 0.5) .withLocalTemperature(ea.STATE, 'Current temperature measured by the internal or external sensor') .withSystemMode(['off', 'heat'], ea.ALL) @@ -2804,16 +3735,26 @@ const definitions: Definition[] = [ e.temperature_sensor_select(['internal', 'external']).withAccess(ea.ALL), e.external_temperature_input().withDescription('Input for remote temperature sensor (when sensor is set to external)'), e.calibrated().withDescription('Indicates if this valve is calibrated, use the calibrate option to calibrate'), - e.enum('calibrate', ea.ALL, ['calibrate']) - .withDescription('Calibrates the valve') - .withCategory('config'), - e.child_lock_bool(), e.window_detection_bool(), e.window_open(), e.valve_detection_bool(), - e.valve_alarm().withDescription('Notifies of a temperature control abnormality if valve detection is enabled ' + - '(e.g., thermostat not installed correctly, valve failure or incorrect calibration, ' + - 'incorrect link to external temperature sensor)'), - e.away_preset_temperature().withAccess(ea.ALL), e.battery_voltage(), e.battery(), - e.power_outage_count(), e.device_temperature(), e.schedule(), - e.schedule_settings() + e.enum('calibrate', ea.ALL, ['calibrate']).withDescription('Calibrates the valve').withCategory('config'), + e.child_lock_bool(), + e.window_detection_bool(), + e.window_open(), + e.valve_detection_bool(), + e + .valve_alarm() + .withDescription( + 'Notifies of a temperature control abnormality if valve detection is enabled ' + + '(e.g., thermostat not installed correctly, valve failure or incorrect calibration, ' + + 'incorrect link to external temperature sensor)', + ), + e.away_preset_temperature().withAccess(ea.ALL), + e.battery_voltage(), + e.battery(), + e.power_outage_count(), + e.device_temperature(), + e.schedule(), + e + .schedule_settings() .withDescription('Smart schedule configuration (default: mon,tue,wed,thu,fri|8:00,24.0|18:00,17.0|23:00,22.0|8:00,22.0)'), ], configure: async (device, coordinatorEndpoint) => { @@ -2839,23 +3780,39 @@ const definitions: Definition[] = [ e.numeric('feeding_size', ea.STATE).withDescription('Feeding size').withUnit('portion'), e.numeric('portions_per_day', ea.STATE).withDescription('Portions per day'), e.numeric('weight_per_day', ea.STATE).withDescription('Weight per day').withUnit('g'), - e.binary('error', ea.STATE, true, false) - .withDescription('Indicates whether there is an error with the feeder'), - e.list('schedule', ea.STATE_SET, e.composite('dayTime', 'dayTime', exposes.access.STATE_SET) - .withFeature(e.enum('days', exposes.access.STATE_SET, [ - 'everyday', 'workdays', 'weekend', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun', - 'mon-wed-fri-sun', 'tue-thu-sat'])) - .withFeature(e.numeric('hour', exposes.access.STATE_SET)) - .withFeature(e.numeric('minute', exposes.access.STATE_SET)) - .withFeature(e.numeric('size', exposes.access.STATE_SET)), - ).withDescription('Feeding schedule'), + e.binary('error', ea.STATE, true, false).withDescription('Indicates whether there is an error with the feeder'), + e + .list( + 'schedule', + ea.STATE_SET, + e + .composite('dayTime', 'dayTime', exposes.access.STATE_SET) + .withFeature( + e.enum('days', exposes.access.STATE_SET, [ + 'everyday', + 'workdays', + 'weekend', + 'mon', + 'tue', + 'wed', + 'thu', + 'fri', + 'sat', + 'sun', + 'mon-wed-fri-sun', + 'tue-thu-sat', + ]), + ) + .withFeature(e.numeric('hour', exposes.access.STATE_SET)) + .withFeature(e.numeric('minute', exposes.access.STATE_SET)) + .withFeature(e.numeric('size', exposes.access.STATE_SET)), + ) + .withDescription('Feeding schedule'), e.switch_().withState('led_indicator', true, 'Led indicator', ea.STATE_SET, 'ON', 'OFF'), e.child_lock(), e.enum('mode', ea.STATE_SET, ['schedule', 'manual']).withDescription('Feeding mode'), - e.numeric('serving_size', ea.STATE_SET).withValueMin(1).withValueMax(10).withDescription('One serving size') - .withUnit('portion'), - e.numeric('portion_weight', ea.STATE_SET).withValueMin(1).withValueMax(20).withDescription('Portion weight') - .withUnit('g'), + e.numeric('serving_size', ea.STATE_SET).withValueMin(1).withValueMax(10).withDescription('One serving size').withUnit('portion'), + e.numeric('portion_weight', ea.STATE_SET).withValueMin(1).withValueMax(20).withDescription('Portion weight').withUnit('g'), ], extend: [lumiZigbeeOTA()], configure: async (device, coordinatorEndpoint) => { @@ -2874,8 +3831,13 @@ const definitions: Definition[] = [ toZigbee: [], meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, extend: [quirkCheckinInterval('1_HOUR')], - exposes: [e.battery(), e.battery_voltage(), e.action(['single', 'double', 'hold', 'release']), - e.device_temperature(), e.power_outage_count()], + exposes: [ + e.battery(), + e.battery_voltage(), + e.action(['single', 'double', 'hold', 'release']), + e.device_temperature(), + e.power_outage_count(), + ], }, { zigbeeModel: ['lumi.remote.acn009'], @@ -2887,22 +3849,37 @@ const definitions: Definition[] = [ meta: {battery: {voltageToPercentage: '3V_2850_3000'}, multiEndpoint: true}, extend: [quirkCheckinInterval('1_HOUR')], exposes: [ - e.battery(), e.battery_voltage(), e.action([ - 'single_left', 'single_right', 'single_both', - 'double_left', 'double_right', 'double_both', - 'triple_left', 'triple_right', 'triple_both', - 'hold_left', 'hold_right', 'hold_both']), - e.enum('click_mode', ea.ALL, ['fast', 'multi']) - .withDescription('Click mode, fast: only supports single click which will be send immediately after clicking.' + - 'multi: supports more events like double and hold'), - e.enum('operation_mode', ea.ALL, ['command', 'event']) + e.battery(), + e.battery_voltage(), + e.action([ + 'single_left', + 'single_right', + 'single_both', + 'double_left', + 'double_right', + 'double_both', + 'triple_left', + 'triple_right', + 'triple_both', + 'hold_left', + 'hold_right', + 'hold_both', + ]), + e + .enum('click_mode', ea.ALL, ['fast', 'multi']) + .withDescription( + 'Click mode, fast: only supports single click which will be send immediately after clicking.' + + 'multi: supports more events like double and hold', + ), + e + .enum('operation_mode', ea.ALL, ['command', 'event']) .withDescription('Operation mode, select "command" to enable bindings (wake up the device before changing modes!)'), ], configure: async (device, coordinatorEndpoint) => { const endpoint1 = device.getEndpoint(1); const endpoint2 = device.getEndpoint(3); // set "event" mode - await endpoint1.write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode}); + await endpoint1.write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode}); // turn on the "multiple clicks" mode, otherwise the only "single click" events. // if value is 1 - there will be single clicks, 2 - multiple. await endpoint1.write('manuSpecificLumi', {0x0125: {value: 0x02, type: 0x20}}, {manufacturerCode: manufacturerCode}); @@ -2924,11 +3901,27 @@ const definitions: Definition[] = [ endpoint: (device) => { return {left: 1, right: 2, both: 3}; }, - exposes: [e.battery(), e.battery_voltage(), e.action([ - 'button_1_hold', 'button_1_release', 'button_1_single', 'button_1_double', 'button_1_triple', - 'button_2_hold', 'button_2_release', 'button_2_single', 'button_2_double', 'button_2_triple', - 'button_3_hold', 'button_3_release', 'button_3_single', 'button_3_double', 'button_3_triple', - ])], + exposes: [ + e.battery(), + e.battery_voltage(), + e.action([ + 'button_1_hold', + 'button_1_release', + 'button_1_single', + 'button_1_double', + 'button_1_triple', + 'button_2_hold', + 'button_2_release', + 'button_2_single', + 'button_2_double', + 'button_2_triple', + 'button_3_hold', + 'button_3_release', + 'button_3_single', + 'button_3_double', + 'button_3_triple', + ]), + ], extend: [quirkCheckinInterval('1_HOUR'), lumiZigbeeOTA()], }, { @@ -2956,7 +3949,7 @@ const definitions: Definition[] = [ extend: [ lumiZigbeeOTA(), lumiPreventReset(), - deviceEndpoints({endpoints: {'top': 1, 'bottom': 2}}), + deviceEndpoints({endpoints: {top: 1, bottom: 2}}), lumiOnOff({ operationMode: true, powerOutageMemory: 'enum', @@ -2979,12 +3972,13 @@ const definitions: Definition[] = [ extend: [ lumiZigbeeOTA(), lumiPreventReset(), - deviceEndpoints({endpoints: {'top': 1, 'center': 2, 'bottom': 3}}), + deviceEndpoints({endpoints: {top: 1, center: 2, bottom: 3}}), lumiOnOff({ operationMode: true, powerOutageMemory: 'enum', lockRelay: true, - endpointNames: ['top', 'center', 'bottom']}), + endpointNames: ['top', 'center', 'bottom'], + }), lumiAction({endpointNames: ['top', 'center', 'bottom']}), lumiElectricityMeter(), lumiPower(), @@ -3001,7 +3995,7 @@ const definitions: Definition[] = [ extend: [ lumiZigbeeOTA(), lumiPreventReset(), - deviceEndpoints({endpoints: {'top': 1, 'center': 2, 'bottom': 3, 'wireless': 4}}), + deviceEndpoints({endpoints: {top: 1, center: 2, bottom: 3, wireless: 4}}), lumiOnOff({ operationMode: true, powerOutageMemory: 'enum', @@ -3009,8 +4003,9 @@ const definitions: Definition[] = [ endpointNames: ['top', 'center', 'bottom'], }), lumiAction({ - actionLookup: {'hold': 0, 'single': 1, 'double': 2, 'release': 255}, - endpointNames: ['top', 'center', 'bottom', 'wireless']}), + actionLookup: {hold: 0, single: 1, double: 2, release: 255}, + endpointNames: ['top', 'center', 'bottom', 'wireless'], + }), lumiElectricityMeter(), lumiPower(), lumiLedDisabledNight(), @@ -3043,7 +4038,7 @@ const definitions: Definition[] = [ extend: [ lumiZigbeeOTA(), lumiPreventReset(), - deviceEndpoints({endpoints: {'top': 1, 'bottom': 2}}), + deviceEndpoints({endpoints: {top: 1, bottom: 2}}), lumiOnOff({ operationMode: true, powerOutageMemory: 'enum', @@ -3069,12 +4064,13 @@ const definitions: Definition[] = [ extend: [ lumiZigbeeOTA(), lumiPreventReset(), - deviceEndpoints({endpoints: {'top': 1, 'center': 2, 'bottom': 3}}), + deviceEndpoints({endpoints: {top: 1, center: 2, bottom: 3}}), lumiOnOff({ operationMode: true, powerOutageMemory: 'enum', lockRelay: true, - endpointNames: ['top', 'center', 'bottom']}), + endpointNames: ['top', 'center', 'bottom'], + }), lumiAction({ endpointNames: ['top', 'center', 'bottom'], extraActions: ['slider_single', 'slider_double', 'slider_hold', 'slider_up', 'slider_down'], @@ -3094,7 +4090,7 @@ const definitions: Definition[] = [ extend: [ lumiZigbeeOTA(), lumiPreventReset(), - deviceEndpoints({endpoints: {'top': 1, 'center': 2, 'bottom': 3, 'wireless': 4}}), + deviceEndpoints({endpoints: {top: 1, center: 2, bottom: 3, wireless: 4}}), lumiOnOff({ operationMode: true, powerOutageMemory: 'enum', @@ -3102,7 +4098,7 @@ const definitions: Definition[] = [ endpointNames: ['top', 'center', 'bottom'], }), lumiAction({ - actionLookup: {'hold': 0, 'single': 1, 'double': 2, 'release': 255}, + actionLookup: {hold: 0, single: 1, double: 2, release: 255}, endpointNames: ['top', 'center', 'bottom', 'wireless'], extraActions: ['slider_single', 'slider_double', 'slider_hold', 'slider_up', 'slider_down'], }), @@ -3122,7 +4118,7 @@ const definitions: Definition[] = [ lumiZigbeeOTA(), lumiPreventReset(), lumiOnOff({operationMode: true, powerOutageMemory: 'binary'}), - lumiAction({actionLookup: {'single': 1, 'double': 2}}), + lumiAction({actionLookup: {single: 1, double: 2}}), lumiElectricityMeter(), lumiPower(), lumiLedDisabledNight(), @@ -3138,25 +4134,44 @@ const definitions: Definition[] = [ description: 'Cube T1 Pro', meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, extend: [quirkCheckinInterval('1_HOUR'), lumiZigbeeOTA()], - fromZigbee: [lumi.fromZigbee.lumi_specific, lumi.fromZigbee.lumi_action_multistate, - lumi.fromZigbee.lumi_action_analog, fz.ignore_onoff_report], + fromZigbee: [ + lumi.fromZigbee.lumi_specific, + lumi.fromZigbee.lumi_action_multistate, + lumi.fromZigbee.lumi_action_analog, + fz.ignore_onoff_report, + ], toZigbee: [lumi.toZigbee.lumi_cube_operation_mode], exposes: [ e.battery(), e.battery_voltage(), e.power_outage_count(false), - e.enum('operation_mode', ea.SET, ['action_mode', 'scene_mode']) - .withDescription('[Soft Switch]: There is a configuration window, opens once an hour on itself, ' + - 'only during which the cube will respond to mode switch. ' + - 'Mode switch will be scheduled to take effect when the window becomes available. ' + - 'You can also give it a throw action (no backward motion) to force a respond! ' + - 'Otherwise, you may open lid and click LINK once to make the cube respond immediately. ' + - '[Hard Switch]: Open lid and click LINK button 5 times.'), + e + .enum('operation_mode', ea.SET, ['action_mode', 'scene_mode']) + .withDescription( + '[Soft Switch]: There is a configuration window, opens once an hour on itself, ' + + 'only during which the cube will respond to mode switch. ' + + 'Mode switch will be scheduled to take effect when the window becomes available. ' + + 'You can also give it a throw action (no backward motion) to force a respond! ' + + 'Otherwise, you may open lid and click LINK once to make the cube respond immediately. ' + + '[Hard Switch]: Open lid and click LINK button 5 times.', + ), e.cube_side('side'), - e.action([ - 'shake', 'throw', 'tap', 'slide', 'flip180', 'flip90', 'hold', 'side_up', - 'rotate_left', 'rotate_right', '1_min_inactivity', 'flip_to_side', - ]).withDescription('Triggered action'), + e + .action([ + 'shake', + 'throw', + 'tap', + 'slide', + 'flip180', + 'flip90', + 'hold', + 'side_up', + 'rotate_left', + 'rotate_right', + '1_min_inactivity', + 'flip_to_side', + ]) + .withDescription('Triggered action'), e.cube_side('action_from_side'), e.angle('action_angle'), ], @@ -3165,10 +4180,16 @@ const definitions: Definition[] = [ device.save(); const endpoint = device.getEndpoint(1); - await endpoint.write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, - disableDefaultResponse: true, disableResponse: true}); - await endpoint.read('manuSpecificLumi', [0x148], {manufacturerCode: manufacturerCode, - disableDefaultResponse: true, disableResponse: true}); + await endpoint.write( + 'manuSpecificLumi', + {mode: 1}, + {manufacturerCode: manufacturerCode, disableDefaultResponse: true, disableResponse: true}, + ); + await endpoint.read('manuSpecificLumi', [0x148], { + manufacturerCode: manufacturerCode, + disableDefaultResponse: true, + disableResponse: true, + }); }, }, { @@ -3177,31 +4198,49 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Smart wall switch E1 (with neutral, triple rocker)', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific], - toZigbee: [tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_opple, - lumi.toZigbee.lumi_switch_power_outage_memory, lumi.toZigbee.lumi_switch_mode_switch, - lumi.toZigbee.lumi_flip_indicator_light], + toZigbee: [ + tz.on_off, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_switch_mode_switch, + lumi.toZigbee.lumi_flip_indicator_light, + ], endpoint: (device) => { - return {'left': 1, 'center': 2, 'right': 3}; + return {left: 1, center: 2, right: 3}; }, meta: {multiEndpoint: true}, exposes: [ - e.switch().withEndpoint('left'), e.switch().withEndpoint('center'), e.switch().withEndpoint('right'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for left button') - .withEndpoint('left'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) + e.switch().withEndpoint('left'), + e.switch().withEndpoint('center'), + e.switch().withEndpoint('right'), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for left button').withEndpoint('left'), + e + .enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) .withDescription('Decoupled mode for center button') .withEndpoint('center'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for right button') - .withEndpoint('right'), - e.action(['single_left', 'double_left', 'single_center', 'double_center', 'single_right', 'double_right', - 'single_left_center', 'double_left_center', 'single_left_right', 'double_left_right', - 'single_center_right', 'double_center_right', 'single_all', 'double_all']), - e.power_outage_memory(), e.device_temperature(), e.flip_indicator_light(), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for right button').withEndpoint('right'), + e.action([ + 'single_left', + 'double_left', + 'single_center', + 'double_center', + 'single_right', + 'double_right', + 'single_left_center', + 'double_left_center', + 'single_left_right', + 'double_left_right', + 'single_center_right', + 'double_center_right', + 'single_all', + 'double_all', + ]), + e.power_outage_memory(), + e.device_temperature(), + e.flip_indicator_light(), ], configure: async (device, coordinatorEndpoint) => { - await device.getEndpoint(1).write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); + await device.getEndpoint(1).write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); }, extend: [lumiZigbeeOTA(), lumiPreventReset()], }, @@ -3212,15 +4251,23 @@ const definitions: Definition[] = [ description: 'Smart wall switch H1M (with neutral, single rocker)', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_power, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific], toZigbee: [ - tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_opple, lumi.toZigbee.lumi_switch_power_outage_memory, - lumi.toZigbee.lumi_led_disabled_night, lumi.toZigbee.lumi_flip_indicator_light, + tz.on_off, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_led_disabled_night, + lumi.toZigbee.lumi_flip_indicator_light, ], exposes: [ - e.switch(), e.power(), e.energy(), e.voltage(), e.device_temperature(), + e.switch(), + e.power(), + e.energy(), + e.voltage(), + e.device_temperature(), e.action(['single', 'double']), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode'), - e.power_outage_memory(), e.led_disabled_night(), e.flip_indicator_light(), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode'), + e.power_outage_memory(), + e.led_disabled_night(), + e.flip_indicator_light(), ], extend: [lumiZigbeeOTA(), lumiPreventReset()], }, @@ -3231,28 +4278,41 @@ const definitions: Definition[] = [ description: 'Smart wall switch H1M (with neutral, double rocker)', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific, lumi.fromZigbee.lumi_power], toZigbee: [ - tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_opple, lumi.toZigbee.lumi_switch_power_outage_memory, - lumi.toZigbee.lumi_led_disabled_night, lumi.toZigbee.lumi_flip_indicator_light, + tz.on_off, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_led_disabled_night, + lumi.toZigbee.lumi_flip_indicator_light, ], endpoint: (device) => { - return {'left': 1, 'right': 2}; + return {left: 1, right: 2}; }, meta: {multiEndpoint: true, multiEndpointSkip: ['power', 'energy']}, exposes: [ - e.power(), e.energy(), e.voltage(), - e.switch().withEndpoint('left'), e.switch().withEndpoint('right'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for left button') - .withEndpoint('left'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for right button') - .withEndpoint('right'), - e.action(['single_left', 'double_left', 'single_right', 'double_right', - 'single_left_right', 'double_left_right', 'single_all', 'double_all']), - e.power_outage_memory(), e.led_disabled_night(), e.device_temperature(), e.flip_indicator_light(), + e.power(), + e.energy(), + e.voltage(), + e.switch().withEndpoint('left'), + e.switch().withEndpoint('right'), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for left button').withEndpoint('left'), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for right button').withEndpoint('right'), + e.action([ + 'single_left', + 'double_left', + 'single_right', + 'double_right', + 'single_left_right', + 'double_left_right', + 'single_all', + 'double_all', + ]), + e.power_outage_memory(), + e.led_disabled_night(), + e.device_temperature(), + e.flip_indicator_light(), ], configure: async (device, coordinatorEndpoint) => { - await device.getEndpoint(1).write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); + await device.getEndpoint(1).write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); }, extend: [lumiZigbeeOTA(), lumiPreventReset()], }, @@ -3263,32 +4323,52 @@ const definitions: Definition[] = [ description: 'Smart wall switch H1M (with neutral, triple rocker)', fromZigbee: [fz.on_off, lumi.fromZigbee.lumi_action_multistate, lumi.fromZigbee.lumi_specific, lumi.fromZigbee.lumi_power], toZigbee: [ - tz.on_off, lumi.toZigbee.lumi_switch_operation_mode_opple, lumi.toZigbee.lumi_switch_power_outage_memory, - lumi.toZigbee.lumi_led_disabled_night, lumi.toZigbee.lumi_flip_indicator_light, + tz.on_off, + lumi.toZigbee.lumi_switch_operation_mode_opple, + lumi.toZigbee.lumi_switch_power_outage_memory, + lumi.toZigbee.lumi_led_disabled_night, + lumi.toZigbee.lumi_flip_indicator_light, ], endpoint: (device) => { - return {'left': 1, 'center': 2, 'right': 3}; + return {left: 1, center: 2, right: 3}; }, meta: {multiEndpoint: true, multiEndpointSkip: ['power', 'energy']}, exposes: [ - e.power(), e.energy(), e.voltage(), - e.switch().withEndpoint('left'), e.switch().withEndpoint('center'), e.switch().withEndpoint('right'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for left button') - .withEndpoint('left'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) + e.power(), + e.energy(), + e.voltage(), + e.switch().withEndpoint('left'), + e.switch().withEndpoint('center'), + e.switch().withEndpoint('right'), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for left button').withEndpoint('left'), + e + .enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) .withDescription('Decoupled mode for center button') .withEndpoint('center'), - e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) - .withDescription('Decoupled mode for right button') - .withEndpoint('right'), - e.action(['single_left', 'double_left', 'single_center', 'double_center', 'single_right', 'double_right', - 'single_left_center', 'double_left_center', 'single_left_right', 'double_left_right', - 'single_center_right', 'double_center_right', 'single_all', 'double_all']), - e.power_outage_memory(), e.led_disabled_night(), e.device_temperature(), e.flip_indicator_light(), + e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode for right button').withEndpoint('right'), + e.action([ + 'single_left', + 'double_left', + 'single_center', + 'double_center', + 'single_right', + 'double_right', + 'single_left_center', + 'double_left_center', + 'single_left_right', + 'double_left_right', + 'single_center_right', + 'double_center_right', + 'single_all', + 'double_all', + ]), + e.power_outage_memory(), + e.led_disabled_night(), + e.device_temperature(), + e.flip_indicator_light(), ], configure: async (device, coordinatorEndpoint) => { - await device.getEndpoint(1).write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); + await device.getEndpoint(1).write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); }, extend: [lumiZigbeeOTA(), lumiPreventReset()], }, @@ -3335,7 +4415,7 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Ceiling light T1', extend: [ - deviceEndpoints({endpoints: {'white': 1, 'rgb': 2}}), + deviceEndpoints({endpoints: {white: 1, rgb: 2}}), lumiLight({colorTemp: true, powerOutageMemory: 'light', endpointNames: ['white']}), lumiLight({colorTemp: true, deviceTemperature: false, powerOutageCount: false, color: {modes: ['xy', 'hs']}, endpointNames: ['rgb']}), lumiZigbeeOTA(), @@ -3347,7 +4427,7 @@ const definitions: Definition[] = [ vendor: 'Aqara', description: 'Ceiling light T1M', extend: [ - deviceEndpoints({endpoints: {'white': 1, 'rgb': 2}}), + deviceEndpoints({endpoints: {white: 1, rgb: 2}}), lumiLight({colorTemp: true, endpointNames: ['white']}), lumiLight({colorTemp: true, deviceTemperature: false, powerOutageCount: false, color: {modes: ['xy', 'hs']}, endpointNames: ['rgb']}), lumiZigbeeOTA(), @@ -3360,10 +4440,10 @@ const definitions: Definition[] = [ description: 'Smart rotary knob H1 (with neutral)', extend: [ lumiPreventReset(), - deviceEndpoints({endpoints: {'left': 1, 'center': 2, 'right': 3}}), + deviceEndpoints({endpoints: {left: 1, center: 2, right: 3}}), lumiOnOff({powerOutageMemory: 'binary', endpointNames: ['left', 'center', 'right']}), lumiCommandMode(), - lumiAction({actionLookup: {'hold': 0, 'single': 1, 'double': 2, 'release': 255}}), + lumiAction({actionLookup: {hold: 0, single: 1, double: 2, release: 255}}), lumiKnobRotation(), lumiElectricityMeter(), lumiPower(), diff --git a/src/devices/lupus.ts b/src/devices/lupus.ts index b3c6b18c77c0e..558fe21ffc13d 100644 --- a/src/devices/lupus.ts +++ b/src/devices/lupus.ts @@ -1,12 +1,12 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; -import * as ota from '../lib/ota'; import {deviceEndpoints, onOff} from '../lib/modernExtend'; +import * as ota from '../lib/ota'; const definitions: Definition[] = [ { @@ -55,10 +55,7 @@ const definitions: Definition[] = [ model: '12127', vendor: 'Lupus', description: '2 channel relay', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2}}), - onOff({endpointNames: ['l1', 'l2']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2}}), onOff({endpointNames: ['l1', 'l2']})], }, ]; diff --git a/src/devices/lutron.ts b/src/devices/lutron.ts index b7adf5d6697f4..ddcfa965ed9f1 100644 --- a/src/devices/lutron.ts +++ b/src/devices/lutron.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; import * as ota from '../lib/ota'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; diff --git a/src/devices/lux.ts b/src/devices/lux.ts index 046fc90d5ebde..804dfed910e0d 100644 --- a/src/devices/lux.ts +++ b/src/devices/lux.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ @@ -12,13 +12,24 @@ const definitions: Definition[] = [ vendor: 'LUX', description: 'KONOz thermostat', fromZigbee: [fz.battery, fz.thermostat, fz.fan, fz.thermostat_weekly_schedule], - toZigbee: [tz.thermostat_local_temperature, - tz.thermostat_occupancy, tz.thermostat_occupied_heating_setpoint, tz.thermostat_occupied_cooling_setpoint, - tz.thermostat_unoccupied_heating_setpoint, tz.thermostat_setpoint_raise_lower, tz.thermostat_running_state, - tz.fan_mode, tz.thermostat_system_mode, - tz.thermostat_weekly_schedule, tz.thermostat_clear_weekly_schedule, tz.thermostat_relay_status_log], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_occupancy, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_occupied_cooling_setpoint, + tz.thermostat_unoccupied_heating_setpoint, + tz.thermostat_setpoint_raise_lower, + tz.thermostat_running_state, + tz.fan_mode, + tz.thermostat_system_mode, + tz.thermostat_weekly_schedule, + tz.thermostat_clear_weekly_schedule, + tz.thermostat_relay_status_log, + ], exposes: [ - e.climate().withSetpoint('occupied_heating_setpoint', 10, 30, 0.05) + e + .climate() + .withSetpoint('occupied_heating_setpoint', 10, 30, 0.05) .withSetpoint('occupied_cooling_setpoint', 10, 30, 0.05) .withLocalTemperature() .withSystemMode(['off', 'heat', 'cool']) diff --git a/src/devices/lytko.ts b/src/devices/lytko.ts index 645f2649bdf23..3b4190c5a84e2 100644 --- a/src/devices/lytko.ts +++ b/src/devices/lytko.ts @@ -1,20 +1,19 @@ import {Zcl} from 'zigbee-herdsman'; -import {Definition, Fz, Tz, KeyValue} from '../lib/types'; -import * as exposes from '../lib/exposes'; + import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; import * as constants from '../lib/constants'; +import * as exposes from '../lib/exposes'; import * as ota from '../lib/ota'; +import * as reporting from '../lib/reporting'; +import {Definition, Fz, Tz, KeyValue} from '../lib/types'; import {precisionRound, getFromLookup, postfixWithEndpointName, getKey, toNumber} from '../lib/utils'; const e = exposes.presets; const ea = exposes.access; const manufacturerOptions = {manufacturerCode: 0x7777}; -const sensorTypes = [ - '3.3', '5', '6.8', '10', '12', '14.8', '15', '20', '33', '47', -]; +const sensorTypes = ['3.3', '5', '6.8', '10', '12', '14.8', '15', '20', '33', '47']; const fzLocal = { thermostat: { @@ -25,8 +24,7 @@ const fzLocal = { const result: KeyValue = {}; if (msg.data.hasOwnProperty('minSetpointDeadBand')) { - result[postfixWithEndpointName('min_setpoint_deadband', msg, model, meta)] = - precisionRound(msg.data['minSetpointDeadBand'], 2) / 10; + result[postfixWithEndpointName('min_setpoint_deadband', msg, model, meta)] = precisionRound(msg.data['minSetpointDeadBand'], 2) / 10; } // sensor type if (msg.data.hasOwnProperty('30464')) { @@ -50,8 +48,10 @@ const fzLocal = { result[postfixWithEndpointName('brightness_standby', msg, model, meta)] = msg.data['30465']; } if (msg.data.hasOwnProperty('keypadLockout')) { - result[postfixWithEndpointName('keypad_lockout', msg, model, meta)] = - getFromLookup(msg.data['keypadLockout'], constants.keypadLockoutMode); + result[postfixWithEndpointName('keypad_lockout', msg, model, meta)] = getFromLookup( + msg.data['keypadLockout'], + constants.keypadLockoutMode, + ); } return result; }, @@ -67,37 +67,37 @@ const tzLocal = { target_temp_first: 30465, }; switch (key) { - case 'sensor_type': - await entity.read('hvacThermostat', [lookup[key]], manufacturerOptions); - break; - case 'target_temp_first': - await entity.read('hvacThermostat', [lookup[key]], manufacturerOptions); - break; - case 'min_setpoint_deadband': - await entity.read('hvacThermostat', ['minSetpointDeadBand']); - break; - default: - break; + case 'sensor_type': + await entity.read('hvacThermostat', [lookup[key]], manufacturerOptions); + break; + case 'target_temp_first': + await entity.read('hvacThermostat', [lookup[key]], manufacturerOptions); + break; + case 'min_setpoint_deadband': + await entity.read('hvacThermostat', ['minSetpointDeadBand']); + break; + default: + break; } }, convertSet: async (entity, key, value, meta) => { let payload: KeyValue = {}; let newValue = value; switch (key) { - case 'sensor_type': - newValue = sensorTypes.indexOf(value as string); - payload = {30464: {'value': newValue, 'type': Zcl.DataType.ENUM8}}; - await entity.write('hvacThermostat', payload, manufacturerOptions); - break; - case 'target_temp_first': - payload = {30465: {'value': newValue, 'type': Zcl.DataType.BOOLEAN}}; - await entity.write('hvacThermostat', payload, manufacturerOptions); - break; - case 'min_setpoint_deadband': - await entity.write('hvacThermostat', {minSetpointDeadBand: Math.round(toNumber(value) * 10)}); - break; - default: - break; + case 'sensor_type': + newValue = sensorTypes.indexOf(value as string); + payload = {30464: {value: newValue, type: Zcl.DataType.ENUM8}}; + await entity.write('hvacThermostat', payload, manufacturerOptions); + break; + case 'target_temp_first': + payload = {30465: {value: newValue, type: Zcl.DataType.BOOLEAN}}; + await entity.write('hvacThermostat', payload, manufacturerOptions); + break; + case 'min_setpoint_deadband': + await entity.write('hvacThermostat', {minSetpointDeadBand: Math.round(toNumber(value) * 10)}); + break; + default: + break; } return {state: {[key]: value}}; }, @@ -110,30 +110,30 @@ const tzLocal = { brightness_standby: 30465, }; switch (key) { - case 'brightness': - await entity.read('hvacUserInterfaceCfg', [lookup[key]], manufacturerOptions); - break; - case 'brightness_standby': - await entity.read('hvacUserInterfaceCfg', [lookup[key]], manufacturerOptions); - break; - default: - break; + case 'brightness': + await entity.read('hvacUserInterfaceCfg', [lookup[key]], manufacturerOptions); + break; + case 'brightness_standby': + await entity.read('hvacUserInterfaceCfg', [lookup[key]], manufacturerOptions); + break; + default: + break; } }, convertSet: async (entity, key, value, meta) => { let payload: KeyValue = {}; const newValue = value; switch (key) { - case 'brightness': - payload = {30464: {'value': newValue, 'type': Zcl.DataType.ENUM8}}; - await entity.write('hvacUserInterfaceCfg', payload, manufacturerOptions); - break; - case 'brightness_standby': - payload = {30465: {'value': newValue, 'type': Zcl.DataType.ENUM8}}; - await entity.write('hvacUserInterfaceCfg', payload, manufacturerOptions); - break; - default: - break; + case 'brightness': + payload = {30464: {value: newValue, type: Zcl.DataType.ENUM8}}; + await entity.write('hvacUserInterfaceCfg', payload, manufacturerOptions); + break; + case 'brightness_standby': + payload = {30465: {value: newValue, type: Zcl.DataType.ENUM8}}; + await entity.write('hvacUserInterfaceCfg', payload, manufacturerOptions); + break; + default: + break; } return {state: {[key]: value}}; }, @@ -148,9 +148,17 @@ const definitions: Definition[] = [ ota: ota.zigbeeOTA, description: 'Single channel Zigbee thermostat', fromZigbee: [fz.humidity, fz.temperature, fz.thermostat, fzLocal.thermostat, fzLocal.thermostat_ui], - toZigbee: [tz.thermostat_keypad_lockout, tz.temperature, tz.thermostat_local_temperature, - tz.thermostat_system_mode, tz.thermostat_running_mode, tz.thermostat_occupied_heating_setpoint, - tz.thermostat_local_temperature_calibration, tzLocal.thermostat, tzLocal.thermostat_ui], + toZigbee: [ + tz.thermostat_keypad_lockout, + tz.temperature, + tz.thermostat_local_temperature, + tz.thermostat_system_mode, + tz.thermostat_running_mode, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_local_temperature_calibration, + tzLocal.thermostat, + tzLocal.thermostat_ui, + ], meta: {multiEndpoint: true}, endpoint: (device) => { return {l3: 3, l2: 2, l1: 1}; @@ -158,21 +166,44 @@ const definitions: Definition[] = [ exposes: [ e.temperature().withAccess(ea.STATE_GET).withEndpoint('l2'), e.humidity().withEndpoint('l2'), - e.climate().withLocalTemperature().withSetpoint('occupied_heating_setpoint', 15, 35, 0.5) - .withSystemMode(['off', 'heat']).withRunningMode(['off', 'heat']) - .withLocalTemperatureCalibration(-3.0, 3.0, 0.1).withEndpoint('l3'), - e.numeric('min_setpoint_deadband', ea.ALL).withUnit('C').withValueMax(3).withValueMin(0) - .withValueStep(0.1).withDescription('Hysteresis setting').withEndpoint('l3'), - e.enum('sensor_type', ea.ALL, sensorTypes).withDescription('Type of sensor. Sensor resistance value (kOhm)') + e + .climate() + .withLocalTemperature() + .withSetpoint('occupied_heating_setpoint', 15, 35, 0.5) + .withSystemMode(['off', 'heat']) + .withRunningMode(['off', 'heat']) + .withLocalTemperatureCalibration(-3.0, 3.0, 0.1) + .withEndpoint('l3'), + e + .numeric('min_setpoint_deadband', ea.ALL) + .withUnit('C') + .withValueMax(3) + .withValueMin(0) + .withValueStep(0.1) + .withDescription('Hysteresis setting') .withEndpoint('l3'), - e.binary('target_temp_first', ea.ALL, true, false).withDescription('Display current temperature or target temperature') + e.enum('sensor_type', ea.ALL, sensorTypes).withDescription('Type of sensor. Sensor resistance value (kOhm)').withEndpoint('l3'), + e + .binary('target_temp_first', ea.ALL, true, false) + .withDescription('Display current temperature or target temperature') .withEndpoint('l3'), - e.enum('keypad_lockout', ea.ALL, ['unlock', 'lock1']).withDescription('Enables/disables physical input on the device') + e.enum('keypad_lockout', ea.ALL, ['unlock', 'lock1']).withDescription('Enables/disables physical input on the device').withEndpoint('l1'), + e + .numeric('brightness', ea.ALL) + .withUnit('%') + .withValueMax(100) + .withValueMin(0) + .withValueStep(1) + .withDescription('Display brightness') + .withEndpoint('l1'), + e + .numeric('brightness_standby', ea.ALL) + .withUnit('%') + .withValueMax(100) + .withValueMin(0) + .withValueStep(1) + .withDescription('Display brightness in standby mode') .withEndpoint('l1'), - e.numeric('brightness', ea.ALL).withUnit('%').withValueMax(100).withValueMin(0).withValueStep(1) - .withDescription('Display brightness').withEndpoint('l1'), - e.numeric('brightness_standby', ea.ALL).withUnit('%').withValueMax(100).withValueMin(0).withValueStep(1) - .withDescription('Display brightness in standby mode').withEndpoint('l1'), ], configure: async (device, coordinatorEndpoint) => { const endpoint2 = device.getEndpoint(2); @@ -183,16 +214,20 @@ const definitions: Definition[] = [ await reporting.bind(endpoint3, coordinatorEndpoint, ['hvacThermostat']); await reporting.thermostatTemperature(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'localTemp', minimumReportInterval: 60, maximumReportInterval: 120, reportableChange: 50}]); + {attribute: 'localTemp', minimumReportInterval: 60, maximumReportInterval: 120, reportableChange: 50}, + ]); await reporting.thermostatOccupiedHeatingSetpoint(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'occupiedHeatingSetpoint', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 50}]); + {attribute: 'occupiedHeatingSetpoint', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 50}, + ]); await reporting.thermostatSystemMode(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'systemMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}]); + {attribute: 'systemMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}, + ]); await reporting.thermostatRunningMode(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'runningMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}]); + {attribute: 'runningMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}, + ]); await endpoint3.read('hvacThermostat', ['localTemp', 'occupiedHeatingSetpoint', 'systemMode', 'runningMode']); await endpoint3.read('hvacThermostat', [30464, 30465], manufacturerOptions); const endpoint1 = device.getEndpoint(1); @@ -207,28 +242,59 @@ const definitions: Definition[] = [ description: 'Single channel Zigbee thermostat', ota: ota.zigbeeOTA, fromZigbee: [fz.thermostat, fzLocal.thermostat, fzLocal.thermostat_ui], - toZigbee: [tz.thermostat_keypad_lockout, tz.temperature, tz.thermostat_local_temperature, - tz.thermostat_system_mode, tz.thermostat_running_mode, tz.thermostat_occupied_heating_setpoint, - tz.thermostat_local_temperature_calibration, tzLocal.thermostat, tzLocal.thermostat_ui], + toZigbee: [ + tz.thermostat_keypad_lockout, + tz.temperature, + tz.thermostat_local_temperature, + tz.thermostat_system_mode, + tz.thermostat_running_mode, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_local_temperature_calibration, + tzLocal.thermostat, + tzLocal.thermostat_ui, + ], meta: {multiEndpoint: true}, endpoint: (device) => { return {l3: 3, l1: 1}; }, exposes: [ - e.climate().withLocalTemperature().withSetpoint('occupied_heating_setpoint', 15, 35, 0.5) - .withSystemMode(['off', 'heat']).withRunningMode(['off', 'heat']).withLocalTemperatureCalibration(-3.0, 3.0, 0.1).withEndpoint('l3'), - e.numeric('min_setpoint_deadband', ea.ALL).withUnit('C').withValueMax(3).withValueMin(0) - .withValueStep(0.1).withDescription('Hysteresis setting').withEndpoint('l3'), - e.enum('sensor_type', ea.ALL, sensorTypes).withDescription('Type of sensor. Sensor resistance value (kOhm)') + e + .climate() + .withLocalTemperature() + .withSetpoint('occupied_heating_setpoint', 15, 35, 0.5) + .withSystemMode(['off', 'heat']) + .withRunningMode(['off', 'heat']) + .withLocalTemperatureCalibration(-3.0, 3.0, 0.1) .withEndpoint('l3'), - e.binary('target_temp_first', ea.ALL, true, false).withDescription('Display current temperature or target temperature') + e + .numeric('min_setpoint_deadband', ea.ALL) + .withUnit('C') + .withValueMax(3) + .withValueMin(0) + .withValueStep(0.1) + .withDescription('Hysteresis setting') .withEndpoint('l3'), - e.enum('keypad_lockout', ea.ALL, ['unlock', 'lock1']).withDescription('Enables/disables physical input on the device') - .withEndpoint('l1'), - e.numeric('brightness', ea.ALL).withUnit('%').withValueMax(100).withValueMin(0).withValueStep(1).withDescription('Display brightness') + e.enum('sensor_type', ea.ALL, sensorTypes).withDescription('Type of sensor. Sensor resistance value (kOhm)').withEndpoint('l3'), + e + .binary('target_temp_first', ea.ALL, true, false) + .withDescription('Display current temperature or target temperature') + .withEndpoint('l3'), + e.enum('keypad_lockout', ea.ALL, ['unlock', 'lock1']).withDescription('Enables/disables physical input on the device').withEndpoint('l1'), + e + .numeric('brightness', ea.ALL) + .withUnit('%') + .withValueMax(100) + .withValueMin(0) + .withValueStep(1) + .withDescription('Display brightness') .withEndpoint('l1'), - e.numeric('brightness_standby', ea.ALL) - .withUnit('%').withValueMax(100).withValueMin(0).withValueStep(1).withDescription('Display brightness in standby mode') + e + .numeric('brightness_standby', ea.ALL) + .withUnit('%') + .withValueMax(100) + .withValueMin(0) + .withValueStep(1) + .withDescription('Display brightness in standby mode') .withEndpoint('l1'), ], configure: async (device, coordinatorEndpoint) => { @@ -236,16 +302,20 @@ const definitions: Definition[] = [ await reporting.bind(endpoint3, coordinatorEndpoint, ['hvacThermostat']); await reporting.thermostatTemperature(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'localTemp', minimumReportInterval: 60, maximumReportInterval: 120, reportableChange: 50}]); + {attribute: 'localTemp', minimumReportInterval: 60, maximumReportInterval: 120, reportableChange: 50}, + ]); await reporting.thermostatOccupiedHeatingSetpoint(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'occupiedHeatingSetpoint', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 50}]); + {attribute: 'occupiedHeatingSetpoint', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 50}, + ]); await reporting.thermostatSystemMode(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'systemMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}]); + {attribute: 'systemMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}, + ]); await reporting.thermostatRunningMode(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'runningMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}]); + {attribute: 'runningMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}, + ]); await endpoint3.read('hvacThermostat', ['localTemp', 'occupiedHeatingSetpoint', 'systemMode', 'runningMode']); await endpoint3.read('hvacThermostat', [30464, 30465], manufacturerOptions); const endpoint1 = device.getEndpoint(1); @@ -260,19 +330,39 @@ const definitions: Definition[] = [ description: 'Single channel Zigbee thermostat without screen', ota: ota.zigbeeOTA, fromZigbee: [fz.thermostat, fzLocal.thermostat], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_system_mode, tz.thermostat_running_mode, - tz.thermostat_occupied_heating_setpoint, tz.thermostat_local_temperature_calibration, tzLocal.thermostat], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_system_mode, + tz.thermostat_running_mode, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_local_temperature_calibration, + tzLocal.thermostat, + ], meta: {multiEndpoint: true}, endpoint: (device) => { return {l3: 3, l1: 1}; }, exposes: [ - e.climate().withLocalTemperature().withSetpoint('occupied_heating_setpoint', 15, 35, 0.5).withSystemMode(['off', 'heat']) - .withRunningMode(['off', 'heat']).withLocalTemperatureCalibration(-3.0, 3.0, 0.1).withEndpoint('l3'), - e.numeric('min_setpoint_deadband', ea.ALL).withUnit('C').withValueMax(3).withValueMin(0).withValueStep(0.1) - .withDescription('Hysteresis setting').withEndpoint('l3'), + e + .climate() + .withLocalTemperature() + .withSetpoint('occupied_heating_setpoint', 15, 35, 0.5) + .withSystemMode(['off', 'heat']) + .withRunningMode(['off', 'heat']) + .withLocalTemperatureCalibration(-3.0, 3.0, 0.1) + .withEndpoint('l3'), + e + .numeric('min_setpoint_deadband', ea.ALL) + .withUnit('C') + .withValueMax(3) + .withValueMin(0) + .withValueStep(0.1) + .withDescription('Hysteresis setting') + .withEndpoint('l3'), e.enum('sensor_type', ea.ALL, sensorTypes).withDescription('Type of sensor. Sensor resistance value (kOhm)').withEndpoint('l3'), - e.binary('target_temp_first', ea.ALL, true, false).withDescription('Display current temperature or target temperature') + e + .binary('target_temp_first', ea.ALL, true, false) + .withDescription('Display current temperature or target temperature') .withEndpoint('l3'), ], configure: async (device, coordinatorEndpoint) => { @@ -280,16 +370,20 @@ const definitions: Definition[] = [ await reporting.bind(endpoint3, coordinatorEndpoint, ['hvacThermostat']); await reporting.thermostatTemperature(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'localTemp', minimumReportInterval: 60, maximumReportInterval: 120, reportableChange: 50}]); + {attribute: 'localTemp', minimumReportInterval: 60, maximumReportInterval: 120, reportableChange: 50}, + ]); await reporting.thermostatOccupiedHeatingSetpoint(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'occupiedHeatingSetpoint', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 50}]); + {attribute: 'occupiedHeatingSetpoint', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 50}, + ]); await reporting.thermostatSystemMode(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'systemMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}]); + {attribute: 'systemMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}, + ]); await reporting.thermostatRunningMode(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'runningMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}]); + {attribute: 'runningMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}, + ]); await endpoint3.read('hvacThermostat', ['localTemp', 'occupiedHeatingSetpoint', 'systemMode', 'runningMode']); }, }, @@ -300,9 +394,17 @@ const definitions: Definition[] = [ description: 'Dual channel Zigbee thermostat', ota: ota.zigbeeOTA, fromZigbee: [fz.humidity, fz.temperature, fz.thermostat, fzLocal.thermostat, fzLocal.thermostat_ui], - toZigbee: [tz.thermostat_keypad_lockout, tz.temperature, tz.thermostat_local_temperature, tz.thermostat_system_mode, - tz.thermostat_running_mode, tz.thermostat_occupied_heating_setpoint, tz.thermostat_local_temperature_calibration, - tzLocal.thermostat, tzLocal.thermostat_ui], + toZigbee: [ + tz.thermostat_keypad_lockout, + tz.temperature, + tz.thermostat_local_temperature, + tz.thermostat_system_mode, + tz.thermostat_running_mode, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_local_temperature_calibration, + tzLocal.thermostat, + tzLocal.thermostat_ui, + ], meta: {multiEndpoint: true}, endpoint: (device) => { return {l4: 4, l3: 3, l2: 2, l1: 1}; @@ -310,25 +412,65 @@ const definitions: Definition[] = [ exposes: [ e.temperature().withAccess(ea.STATE_GET).withEndpoint('l2'), e.humidity().withEndpoint('l2'), - e.climate().withLocalTemperature().withSetpoint('occupied_heating_setpoint', 15, 35, 0.5).withSystemMode(['off', 'heat']) - .withRunningMode(['off', 'heat']).withLocalTemperatureCalibration(-3.0, 3.0, 0.1).withEndpoint('l3'), - e.numeric('min_setpoint_deadband', ea.ALL).withUnit('C').withValueMax(3).withValueMin(0).withValueStep(0.1) - .withDescription('Hysteresis setting').withEndpoint('l3'), + e + .climate() + .withLocalTemperature() + .withSetpoint('occupied_heating_setpoint', 15, 35, 0.5) + .withSystemMode(['off', 'heat']) + .withRunningMode(['off', 'heat']) + .withLocalTemperatureCalibration(-3.0, 3.0, 0.1) + .withEndpoint('l3'), + e + .numeric('min_setpoint_deadband', ea.ALL) + .withUnit('C') + .withValueMax(3) + .withValueMin(0) + .withValueStep(0.1) + .withDescription('Hysteresis setting') + .withEndpoint('l3'), e.enum('sensor_type', ea.ALL, sensorTypes).withDescription('Type of sensor. Sensor resistance value (kOhm)').withEndpoint('l3'), - e.binary('target_temp_first', ea.ALL, true, false).withDescription('Display current temperature or target temperature') + e + .binary('target_temp_first', ea.ALL, true, false) + .withDescription('Display current temperature or target temperature') .withEndpoint('l3'), - e.climate().withLocalTemperature().withSetpoint('occupied_heating_setpoint', 15, 35, 0.5).withSystemMode(['off', 'heat']) - .withRunningMode(['off', 'heat']).withLocalTemperatureCalibration(-3.0, 3.0, 0.1).withEndpoint('l4'), - e.numeric('min_setpoint_deadband', ea.ALL).withUnit('C').withValueMax(3).withValueMin(0).withValueStep(0.1) - .withDescription('Hysteresis setting').withEndpoint('l4'), + e + .climate() + .withLocalTemperature() + .withSetpoint('occupied_heating_setpoint', 15, 35, 0.5) + .withSystemMode(['off', 'heat']) + .withRunningMode(['off', 'heat']) + .withLocalTemperatureCalibration(-3.0, 3.0, 0.1) + .withEndpoint('l4'), + e + .numeric('min_setpoint_deadband', ea.ALL) + .withUnit('C') + .withValueMax(3) + .withValueMin(0) + .withValueStep(0.1) + .withDescription('Hysteresis setting') + .withEndpoint('l4'), e.enum('sensor_type', ea.ALL, sensorTypes).withDescription('Type of sensor. Sensor resistance value (kOhm)').withEndpoint('l4'), - e.binary('target_temp_first', ea.ALL, true, false).withDescription('Display current temperature or target temperature') + e + .binary('target_temp_first', ea.ALL, true, false) + .withDescription('Display current temperature or target temperature') .withEndpoint('l4'), e.enum('keypad_lockout', ea.ALL, ['unlock', 'lock1']).withDescription('Enables/disables physical input on the device').withEndpoint('l1'), - e.numeric('brightness', ea.ALL).withUnit('%').withValueMax(100).withValueMin(0).withValueStep(1).withDescription('Display brightness') + e + .numeric('brightness', ea.ALL) + .withUnit('%') + .withValueMax(100) + .withValueMin(0) + .withValueStep(1) + .withDescription('Display brightness') + .withEndpoint('l1'), + e + .numeric('brightness_standby', ea.ALL) + .withUnit('%') + .withValueMax(100) + .withValueMin(0) + .withValueStep(1) + .withDescription('Display brightness in standby mode') .withEndpoint('l1'), - e.numeric('brightness_standby', ea.ALL).withUnit('%').withValueMax(100).withValueMin(0).withValueStep(1) - .withDescription('Display brightness in standby mode').withEndpoint('l1'), ], configure: async (device, coordinatorEndpoint) => { const endpoint2 = device.getEndpoint(2); @@ -339,32 +481,40 @@ const definitions: Definition[] = [ await reporting.bind(endpoint3, coordinatorEndpoint, ['hvacThermostat']); await reporting.thermostatTemperature(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'localTemp', minimumReportInterval: 60, maximumReportInterval: 120, reportableChange: 50}]); + {attribute: 'localTemp', minimumReportInterval: 60, maximumReportInterval: 120, reportableChange: 50}, + ]); await reporting.thermostatOccupiedHeatingSetpoint(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'occupiedHeatingSetpoint', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 50}]); + {attribute: 'occupiedHeatingSetpoint', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 50}, + ]); await reporting.thermostatSystemMode(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'systemMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}]); + {attribute: 'systemMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}, + ]); await reporting.thermostatRunningMode(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'runningMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}]); + {attribute: 'runningMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}, + ]); await endpoint3.read('hvacThermostat', ['localTemp', 'occupiedHeatingSetpoint', 'systemMode', 'runningMode']); await endpoint3.read('hvacThermostat', [30464, 30465], manufacturerOptions); const endpoint4 = device.getEndpoint(4); await reporting.bind(endpoint3, coordinatorEndpoint, ['hvacThermostat']); await reporting.thermostatTemperature(endpoint4); await endpoint4.configureReporting('hvacThermostat', [ - {attribute: 'localTemp', minimumReportInterval: 60, maximumReportInterval: 120, reportableChange: 50}]); + {attribute: 'localTemp', minimumReportInterval: 60, maximumReportInterval: 120, reportableChange: 50}, + ]); await reporting.thermostatOccupiedHeatingSetpoint(endpoint4); await endpoint4.configureReporting('hvacThermostat', [ - {attribute: 'occupiedHeatingSetpoint', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 50}]); + {attribute: 'occupiedHeatingSetpoint', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 50}, + ]); await reporting.thermostatSystemMode(endpoint4); await endpoint4.configureReporting('hvacThermostat', [ - {attribute: 'systemMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}]); + {attribute: 'systemMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}, + ]); await reporting.thermostatRunningMode(endpoint4); await endpoint4.configureReporting('hvacThermostat', [ - {attribute: 'runningMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}]); + {attribute: 'runningMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}, + ]); await endpoint4.read('hvacThermostat', ['localTemp', 'occupiedHeatingSetpoint', 'systemMode', 'runningMode']); await endpoint4.read('hvacThermostat', [30464, 30465], manufacturerOptions); const endpoint1 = device.getEndpoint(1); @@ -379,65 +529,121 @@ const definitions: Definition[] = [ description: 'Dual channel zigbee thermostat', ota: ota.zigbeeOTA, fromZigbee: [fz.thermostat, fzLocal.thermostat, fzLocal.thermostat_ui], - toZigbee: [tz.thermostat_keypad_lockout, tz.temperature, tz.thermostat_local_temperature, tz.thermostat_system_mode, - tz.thermostat_running_mode, tz.thermostat_occupied_heating_setpoint, tz.thermostat_local_temperature_calibration, - tzLocal.thermostat, tzLocal.thermostat_ui], + toZigbee: [ + tz.thermostat_keypad_lockout, + tz.temperature, + tz.thermostat_local_temperature, + tz.thermostat_system_mode, + tz.thermostat_running_mode, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_local_temperature_calibration, + tzLocal.thermostat, + tzLocal.thermostat_ui, + ], meta: {multiEndpoint: true}, endpoint: (device) => { return {l4: 4, l3: 3, l1: 1}; }, exposes: [ - e.climate().withLocalTemperature().withSetpoint('occupied_heating_setpoint', 15, 35, 0.5).withSystemMode(['off', 'heat']) - .withRunningMode(['off', 'heat']).withLocalTemperatureCalibration(-3.0, 3.0, 0.1).withEndpoint('l3'), - e.numeric('min_setpoint_deadband', ea.ALL).withUnit('C').withValueMax(3).withValueMin(0).withValueStep(0.1) - .withDescription('Hysteresis setting').withEndpoint('l3'), + e + .climate() + .withLocalTemperature() + .withSetpoint('occupied_heating_setpoint', 15, 35, 0.5) + .withSystemMode(['off', 'heat']) + .withRunningMode(['off', 'heat']) + .withLocalTemperatureCalibration(-3.0, 3.0, 0.1) + .withEndpoint('l3'), + e + .numeric('min_setpoint_deadband', ea.ALL) + .withUnit('C') + .withValueMax(3) + .withValueMin(0) + .withValueStep(0.1) + .withDescription('Hysteresis setting') + .withEndpoint('l3'), e.enum('sensor_type', ea.ALL, sensorTypes).withDescription('Type of sensor. Sensor resistance value (kOhm)').withEndpoint('l3'), - e.binary('target_temp_first', ea.ALL, true, false).withDescription('Display current temperature or target temperature') + e + .binary('target_temp_first', ea.ALL, true, false) + .withDescription('Display current temperature or target temperature') .withEndpoint('l3'), - e.climate().withLocalTemperature().withSetpoint('occupied_heating_setpoint', 15, 35, 0.5).withSystemMode(['off', 'heat']) - .withRunningMode(['off', 'heat']).withLocalTemperatureCalibration(-3.0, 3.0, 0.1).withEndpoint('l4'), - e.numeric('min_setpoint_deadband', ea.ALL).withUnit('C').withValueMax(3).withValueMin(0).withValueStep(0.1) - .withDescription('Hysteresis setting').withEndpoint('l4'), + e + .climate() + .withLocalTemperature() + .withSetpoint('occupied_heating_setpoint', 15, 35, 0.5) + .withSystemMode(['off', 'heat']) + .withRunningMode(['off', 'heat']) + .withLocalTemperatureCalibration(-3.0, 3.0, 0.1) + .withEndpoint('l4'), + e + .numeric('min_setpoint_deadband', ea.ALL) + .withUnit('C') + .withValueMax(3) + .withValueMin(0) + .withValueStep(0.1) + .withDescription('Hysteresis setting') + .withEndpoint('l4'), e.enum('sensor_type', ea.ALL, sensorTypes).withDescription('Type of sensor. Sensor resistance value (kOhm)').withEndpoint('l4'), - e.binary('target_temp_first', ea.ALL, true, false).withDescription('Display current temperature or target temperature') + e + .binary('target_temp_first', ea.ALL, true, false) + .withDescription('Display current temperature or target temperature') .withEndpoint('l4'), e.enum('keypad_lockout', ea.ALL, ['unlock', 'lock1']).withDescription('Enables/disables physical input on the device').withEndpoint('l1'), - e.numeric('brightness', ea.ALL).withUnit('%').withValueMax(100).withValueMin(0).withValueStep(1) - .withDescription('Display brightness').withEndpoint('l1'), - e.numeric('brightness_standby', ea.ALL).withUnit('%').withValueMax(100).withValueMin(0).withValueStep(1) - .withDescription('Display brightness in standby mode').withEndpoint('l1'), + e + .numeric('brightness', ea.ALL) + .withUnit('%') + .withValueMax(100) + .withValueMin(0) + .withValueStep(1) + .withDescription('Display brightness') + .withEndpoint('l1'), + e + .numeric('brightness_standby', ea.ALL) + .withUnit('%') + .withValueMax(100) + .withValueMin(0) + .withValueStep(1) + .withDescription('Display brightness in standby mode') + .withEndpoint('l1'), ], configure: async (device, coordinatorEndpoint) => { const endpoint3 = device.getEndpoint(3); await reporting.bind(endpoint3, coordinatorEndpoint, ['hvacThermostat']); await reporting.thermostatTemperature(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'localTemp', minimumReportInterval: 60, maximumReportInterval: 120, reportableChange: 50}]); + {attribute: 'localTemp', minimumReportInterval: 60, maximumReportInterval: 120, reportableChange: 50}, + ]); await reporting.thermostatOccupiedHeatingSetpoint(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'occupiedHeatingSetpoint', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 50}]); + {attribute: 'occupiedHeatingSetpoint', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 50}, + ]); await reporting.thermostatSystemMode(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'systemMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}]); + {attribute: 'systemMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}, + ]); await reporting.thermostatRunningMode(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'runningMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}]); + {attribute: 'runningMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}, + ]); await endpoint3.read('hvacThermostat', ['localTemp', 'occupiedHeatingSetpoint', 'systemMode', 'runningMode']); await endpoint3.read('hvacThermostat', [30464, 30465], manufacturerOptions); const endpoint4 = device.getEndpoint(4); await reporting.bind(endpoint3, coordinatorEndpoint, ['hvacThermostat']); await reporting.thermostatTemperature(endpoint4); await endpoint4.configureReporting('hvacThermostat', [ - {attribute: 'localTemp', minimumReportInterval: 60, maximumReportInterval: 120, reportableChange: 50}]); + {attribute: 'localTemp', minimumReportInterval: 60, maximumReportInterval: 120, reportableChange: 50}, + ]); await reporting.thermostatOccupiedHeatingSetpoint(endpoint4); await endpoint4.configureReporting('hvacThermostat', [ - {attribute: 'occupiedHeatingSetpoint', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 50}]); + {attribute: 'occupiedHeatingSetpoint', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 50}, + ]); await reporting.thermostatSystemMode(endpoint4); await endpoint4.configureReporting('hvacThermostat', [ - {attribute: 'systemMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}]); + {attribute: 'systemMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}, + ]); await reporting.thermostatRunningMode(endpoint4); await endpoint4.configureReporting('hvacThermostat', [ - {attribute: 'runningMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}]); + {attribute: 'runningMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}, + ]); await endpoint4.read('hvacThermostat', ['localTemp', 'occupiedHeatingSetpoint', 'systemMode', 'runningMode']); await endpoint4.read('hvacThermostat', [30464, 30465], manufacturerOptions); const endpoint1 = device.getEndpoint(1); @@ -452,26 +658,60 @@ const definitions: Definition[] = [ description: 'Dual channel Zigbee thermostat without screen', ota: ota.zigbeeOTA, fromZigbee: [fz.thermostat, fzLocal.thermostat], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_system_mode, tz.thermostat_running_mode, - tz.thermostat_occupied_heating_setpoint, tz.thermostat_local_temperature_calibration, tzLocal.thermostat], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_system_mode, + tz.thermostat_running_mode, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_local_temperature_calibration, + tzLocal.thermostat, + ], meta: {multiEndpoint: true}, endpoint: (device) => { return {l4: 4, l3: 3, l1: 1}; }, exposes: [ - e.climate().withLocalTemperature().withSetpoint('occupied_heating_setpoint', 15, 35, 0.5).withSystemMode(['off', 'heat']) - .withRunningMode(['off', 'heat']).withLocalTemperatureCalibration(-3.0, 3.0, 0.1).withEndpoint('l3'), - e.numeric('min_setpoint_deadband', ea.ALL).withUnit('C').withValueMax(3).withValueMin(0).withValueStep(0.1) - .withDescription('Hysteresis setting').withEndpoint('l3'), + e + .climate() + .withLocalTemperature() + .withSetpoint('occupied_heating_setpoint', 15, 35, 0.5) + .withSystemMode(['off', 'heat']) + .withRunningMode(['off', 'heat']) + .withLocalTemperatureCalibration(-3.0, 3.0, 0.1) + .withEndpoint('l3'), + e + .numeric('min_setpoint_deadband', ea.ALL) + .withUnit('C') + .withValueMax(3) + .withValueMin(0) + .withValueStep(0.1) + .withDescription('Hysteresis setting') + .withEndpoint('l3'), e.enum('sensor_type', ea.ALL, sensorTypes).withDescription('Type of sensor. Sensor resistance value (kOhm)').withEndpoint('l3'), - e.binary('target_temp_first', ea.ALL, true, false).withDescription('Display current temperature or target temperature') + e + .binary('target_temp_first', ea.ALL, true, false) + .withDescription('Display current temperature or target temperature') .withEndpoint('l3'), - e.climate().withLocalTemperature().withSetpoint('occupied_heating_setpoint', 15, 35, 0.5).withSystemMode(['off', 'heat']) - .withRunningMode(['off', 'heat']).withLocalTemperatureCalibration(-3.0, 3.0, 0.1).withEndpoint('l4'), - e.numeric('min_setpoint_deadband', ea.ALL).withUnit('C').withValueMax(3).withValueMin(0).withValueStep(0.1) - .withDescription('Hysteresis setting').withEndpoint('l4'), + e + .climate() + .withLocalTemperature() + .withSetpoint('occupied_heating_setpoint', 15, 35, 0.5) + .withSystemMode(['off', 'heat']) + .withRunningMode(['off', 'heat']) + .withLocalTemperatureCalibration(-3.0, 3.0, 0.1) + .withEndpoint('l4'), + e + .numeric('min_setpoint_deadband', ea.ALL) + .withUnit('C') + .withValueMax(3) + .withValueMin(0) + .withValueStep(0.1) + .withDescription('Hysteresis setting') + .withEndpoint('l4'), e.enum('sensor_type', ea.ALL, sensorTypes).withDescription('Type of sensor. Sensor resistance value (kOhm)').withEndpoint('l4'), - e.binary('target_temp_first', ea.ALL, true, false).withDescription('Display current temperature or target temperature') + e + .binary('target_temp_first', ea.ALL, true, false) + .withDescription('Display current temperature or target temperature') .withEndpoint('l4'), ], configure: async (device, coordinatorEndpoint) => { @@ -479,32 +719,40 @@ const definitions: Definition[] = [ await reporting.bind(endpoint3, coordinatorEndpoint, ['hvacThermostat']); await reporting.thermostatTemperature(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'localTemp', minimumReportInterval: 60, maximumReportInterval: 120, reportableChange: 50}]); + {attribute: 'localTemp', minimumReportInterval: 60, maximumReportInterval: 120, reportableChange: 50}, + ]); await reporting.thermostatOccupiedHeatingSetpoint(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'occupiedHeatingSetpoint', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 50}]); + {attribute: 'occupiedHeatingSetpoint', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 50}, + ]); await reporting.thermostatSystemMode(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'systemMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}]); + {attribute: 'systemMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}, + ]); await reporting.thermostatRunningMode(endpoint3); await endpoint3.configureReporting('hvacThermostat', [ - {attribute: 'runningMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}]); + {attribute: 'runningMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}, + ]); await endpoint3.read('hvacThermostat', ['localTemp', 'occupiedHeatingSetpoint', 'systemMode', 'runningMode']); await endpoint3.read('hvacThermostat', [30464, 30465], manufacturerOptions); const endpoint4 = device.getEndpoint(4); await reporting.bind(endpoint4, coordinatorEndpoint, ['hvacThermostat']); await reporting.thermostatTemperature(endpoint4); await endpoint4.configureReporting('hvacThermostat', [ - {attribute: 'localTemp', minimumReportInterval: 60, maximumReportInterval: 120, reportableChange: 50}]); + {attribute: 'localTemp', minimumReportInterval: 60, maximumReportInterval: 120, reportableChange: 50}, + ]); await reporting.thermostatOccupiedHeatingSetpoint(endpoint4); await endpoint4.configureReporting('hvacThermostat', [ - {attribute: 'occupiedHeatingSetpoint', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 50}]); + {attribute: 'occupiedHeatingSetpoint', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 50}, + ]); await reporting.thermostatSystemMode(endpoint4); await endpoint4.configureReporting('hvacThermostat', [ - {attribute: 'systemMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}]); + {attribute: 'systemMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}, + ]); await reporting.thermostatRunningMode(endpoint4); await endpoint4.configureReporting('hvacThermostat', [ - {attribute: 'runningMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}]); + {attribute: 'runningMode', minimumReportInterval: 1, maximumReportInterval: 120, reportableChange: 1}, + ]); await endpoint4.read('hvacThermostat', ['localTemp', 'occupiedHeatingSetpoint', 'systemMode', 'runningMode']); }, }, diff --git a/src/devices/m_elec.ts b/src/devices/m_elec.ts index 015afbeaf49f2..88ea2e1dba1ba 100644 --- a/src/devices/m_elec.ts +++ b/src/devices/m_elec.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light, onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/makegood.ts b/src/devices/makegood.ts index 0aab87cb5606f..b62d3c0fecdb5 100644 --- a/src/devices/makegood.ts +++ b/src/devices/makegood.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; import * as reporting from '../lib/reporting'; import * as tuya from '../lib/tuya'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { @@ -11,7 +11,7 @@ const definitions: Definition[] = [ extend: [tuya.modernExtend.tuyaOnOff({powerOutageMemory: true, indicatorMode: true, endpoints: ['l1', 'l2'], electricalMeasurements: true})], meta: {multiEndpointSkip: ['power', 'current', 'voltage', 'energy'], multiEndpoint: true}, endpoint: (device) => { - return {'l1': 1, 'l2': 2}; + return {l1: 1, l2: 2}; }, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); diff --git a/src/devices/matcall_bv.ts b/src/devices/matcall_bv.ts index 98a731730597f..e04a4c7735be5 100644 --- a/src/devices/matcall_bv.ts +++ b/src/devices/matcall_bv.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/meazon.ts b/src/devices/meazon.ts index dd229dbb9b8b7..2fc5591f5067b 100644 --- a/src/devices/meazon.ts +++ b/src/devices/meazon.ts @@ -1,17 +1,17 @@ import {Zcl} from 'zigbee-herdsman'; -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; + import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; import * as constants from '../lib/constants'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ { - zigbeeModel: ['101.301.001649', '101.301.001838', '101.301.001802', '101.301.001738', '101.301.001412', '101.301.001765', - '101.301.001814'], + zigbeeModel: ['101.301.001649', '101.301.001838', '101.301.001802', '101.301.001738', '101.301.001412', '101.301.001765', '101.301.001814'], model: 'MEAZON_BIZY_PLUG', vendor: 'Meazon', description: 'Bizy plug meter', @@ -24,8 +24,18 @@ const definitions: Definition[] = [ await reporting.onOff(endpoint, {min: 1, max: 0xfffe}); const options = {manufacturerCode: Zcl.ManufacturerCode.MEAZON_S_A, disableDefaultResponse: false}; await endpoint.write('seMetering', {0x1005: {value: 0x063e, type: 25}}, options); - await endpoint.configureReporting('seMetering', [{reportableChange: 1, attribute: {ID: 0x2000, type: 0x29}, - minimumReportInterval: 1, maximumReportInterval: constants.repInterval.MINUTES_5}], options); + await endpoint.configureReporting( + 'seMetering', + [ + { + reportableChange: 1, + attribute: {ID: 0x2000, type: 0x29}, + minimumReportInterval: 1, + maximumReportInterval: constants.repInterval.MINUTES_5, + }, + ], + options, + ); }, }, { @@ -43,8 +53,18 @@ const definitions: Definition[] = [ const options = {manufacturerCode: Zcl.ManufacturerCode.MEAZON_S_A, disableDefaultResponse: false}; await endpoint.write('seMetering', {0x1005: {value: 0x063e, type: 25}}, options); await reporting.onOff(endpoint); - await endpoint.configureReporting('seMetering', [{attribute: {ID: 0x2000, type: 0x29}, - minimumReportInterval: 1, maximumReportInterval: constants.repInterval.MINUTES_5, reportableChange: 1}], options); + await endpoint.configureReporting( + 'seMetering', + [ + { + attribute: {ID: 0x2000, type: 0x29}, + minimumReportInterval: 1, + maximumReportInterval: constants.repInterval.MINUTES_5, + reportableChange: 1, + }, + ], + options, + ); }, }, ]; diff --git a/src/devices/mercator.ts b/src/devices/mercator.ts index ddc4ebaded83f..70ce260d34aa3 100644 --- a/src/devices/mercator.ts +++ b/src/devices/mercator.ts @@ -1,6 +1,6 @@ -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; import * as tuya from '../lib/tuya'; import {Definition} from '../lib/types'; @@ -38,7 +38,9 @@ const definitions: Definition[] = [ await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']); try { await reporting.batteryPercentageRemaining(endpoint); - } catch (error) {/* Fails for some https://github.com/Koenkk/zigbee2mqtt/issues/13708*/} + } catch (error) { + /* Fails for some https://github.com/Koenkk/zigbee2mqtt/issues/13708*/ + } }, }, { @@ -65,7 +67,9 @@ const definitions: Definition[] = [ await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']); await reporting.batteryPercentageRemaining(endpoint); await reporting.batteryVoltage(endpoint); - } catch (error) {/* Fails for some*/} + } catch (error) { + /* Fails for some*/ + } }, }, { @@ -145,7 +149,7 @@ const definitions: Definition[] = [ description: 'Triple switch', extend: [tuya.modernExtend.tuyaOnOff({backlightModeLowMediumHigh: true, endpoints: ['left', 'center', 'right']})], endpoint: (device) => { - return {'left': 1, 'center': 2, 'right': 3}; + return {left: 1, center: 2, right: 3}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -159,8 +163,10 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'TS0501', manufacturerName: '_TZ3210_lzqq3u4r'}, - {modelID: 'TS0501', manufacturerName: '_TZ3210_4whigl8i'}], + fingerprint: [ + {modelID: 'TS0501', manufacturerName: '_TZ3210_lzqq3u4r'}, + {modelID: 'TS0501', manufacturerName: '_TZ3210_4whigl8i'}, + ], model: 'SSWF01G', vendor: 'Mercator Ikuü', description: 'AC fan controller', diff --git a/src/devices/miboxer.ts b/src/devices/miboxer.ts index 3b8761ad118c3..6648e53724ed9 100644 --- a/src/devices/miboxer.ts +++ b/src/devices/miboxer.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; +import {Definition} from '../lib/types'; const e = exposes.presets; import * as tuya from '../lib/tuya'; @@ -17,27 +17,36 @@ const definitions: Definition[] = [ model: 'FUT089Z', vendor: 'MiBoxer', description: 'RGB+CCT Remote', - fromZigbee: [fz.battery, fz.command_on, fz.command_off, fz.command_move_to_level, fz.command_move_to_color_temp, - fz.command_move_to_hue_and_saturation], + fromZigbee: [ + fz.battery, + fz.command_on, + fz.command_off, + fz.command_move_to_level, + fz.command_move_to_color_temp, + fz.command_move_to_hue_and_saturation, + ], toZigbee: [], - whiteLabel: [ - tuya.whitelabel('Ledron', 'YK-16', 'RGB+CCT Remote', ['_TZ3000_zwszqdpy']), + whiteLabel: [tuya.whitelabel('Ledron', 'YK-16', 'RGB+CCT Remote', ['_TZ3000_zwszqdpy'])], + exposes: [ + e.battery(), + e.battery_voltage(), + e.action(['on', 'off', 'brightness_move_to_level', 'color_temperature_move', 'move_to_hue_and_saturation']), ], - exposes: [e.battery(), e.battery_voltage(), e.action(['on', 'off', 'brightness_move_to_level', 'color_temperature_move', - 'move_to_hue_and_saturation'])], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await tuya.configureMagicPacket(device, coordinatorEndpoint); - await endpoint.command('genGroups', 'miboxerSetZones', {zones: [ - {zoneNum: 1, groupId: 101}, - {zoneNum: 2, groupId: 102}, - {zoneNum: 3, groupId: 103}, - {zoneNum: 4, groupId: 104}, - {zoneNum: 5, groupId: 105}, - {zoneNum: 6, groupId: 106}, - {zoneNum: 7, groupId: 107}, - {zoneNum: 8, groupId: 108}, - ]}); + await endpoint.command('genGroups', 'miboxerSetZones', { + zones: [ + {zoneNum: 1, groupId: 101}, + {zoneNum: 2, groupId: 102}, + {zoneNum: 3, groupId: 103}, + {zoneNum: 4, groupId: 104}, + {zoneNum: 5, groupId: 105}, + {zoneNum: 6, groupId: 106}, + {zoneNum: 7, groupId: 107}, + {zoneNum: 8, groupId: 108}, + ], + }); await endpoint.command('genBasic', 'tuyaSetup', {}, {disableDefaultResponse: true}); }, }, diff --git a/src/devices/micromatic.ts b/src/devices/micromatic.ts index bbf007b7b1272..d0d178042d7c7 100644 --- a/src/devices/micromatic.ts +++ b/src/devices/micromatic.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {electricityMeter, light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/modular.ts b/src/devices/modular.ts index a17582dd0a0e9..258945aa835a9 100644 --- a/src/devices/modular.ts +++ b/src/devices/modular.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/moes.ts b/src/devices/moes.ts index 1a6105e157cd1..f7b09decae8d6 100644 --- a/src/devices/moes.ts +++ b/src/devices/moes.ts @@ -1,14 +1,14 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; -import * as tuya from '../lib/tuya'; import * as reporting from '../lib/reporting'; +import * as tuya from '../lib/tuya'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; -import * as zosung from '../lib/zosung'; import {onOff, deviceEndpoints, actionEnumLookup, battery} from '../lib/modernExtend'; +import * as zosung from '../lib/zosung'; const fzZosung = zosung.fzZosung; const tzZosung = zosung.tzZosung; const ez = zosung.presetsZosung; @@ -16,20 +16,21 @@ const ez = zosung.presetsZosung; const exposesLocal = { hour: (name: string) => e.numeric(name, ea.STATE_SET).withUnit('h').withValueMin(0).withValueMax(23), minute: (name: string) => e.numeric(name, ea.STATE_SET).withUnit('m').withValueMin(0).withValueMax(59), - program_temperature: (name: string) => e.numeric(name, ea.STATE_SET).withUnit('°C') - .withValueMin(5).withValueMax(35).withValueStep(0.5), + program_temperature: (name: string) => e.numeric(name, ea.STATE_SET).withUnit('°C').withValueMin(5).withValueMax(35).withValueStep(0.5), }; const definitions: Definition[] = [ { - fingerprint: [{modelID: 'TS011F', manufacturerName: '_TZ3000_cymsnfvf'}, - {modelID: 'TS011F', manufacturerName: '_TZ3000_2xlvlnez'}], + fingerprint: [ + {modelID: 'TS011F', manufacturerName: '_TZ3000_cymsnfvf'}, + {modelID: 'TS011F', manufacturerName: '_TZ3000_2xlvlnez'}, + ], model: 'ZP-LZ-FR2U', vendor: 'Moes', description: 'Zigbee 3.0 dual USB wireless socket plug', extend: [tuya.modernExtend.tuyaOnOff({powerOutageMemory: true, indicatorMode: true, childLock: true, endpoints: ['l1', 'l2']})], endpoint: (device) => { - return {'l1': 1, 'l2': 2}; + return {l1: 1, l2: 2}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -41,8 +42,11 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'TS0121', manufacturerName: '_TYZB01_iuepbmpv'}, {modelID: 'TS011F', manufacturerName: '_TZ3000_zmy1waw6'}, - {modelID: 'TS011F', manufacturerName: '_TZ3000_bkfe0bab'}], + fingerprint: [ + {modelID: 'TS0121', manufacturerName: '_TYZB01_iuepbmpv'}, + {modelID: 'TS011F', manufacturerName: '_TZ3000_zmy1waw6'}, + {modelID: 'TS011F', manufacturerName: '_TZ3000_bkfe0bab'}, + ], model: 'MS-104Z', description: 'Smart light switch module (1 gang)', vendor: 'Moes', @@ -77,9 +81,7 @@ const definitions: Definition[] = [ await reporting.bind(endpoint2, coordinatorEndpoint, ['genOnOff']); await reporting.onOff(endpoint2); }, - whiteLabel: [ - tuya.whitelabel('KnockautX', 'FMS2C017', '2 gang switch', ['_TZ3000_iv6ph5tr']), - ], + whiteLabel: [tuya.whitelabel('KnockautX', 'FMS2C017', '2 gang switch', ['_TZ3000_iv6ph5tr'])], }, { zigbeeModel: ['TS0112'], @@ -94,34 +96,53 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_aoclfnxz'}, + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE200_aoclfnxz'}, {modelID: 'TS0601', manufacturerName: '_TZE200_ztvwu4nk'}, {modelID: 'TS0601', manufacturerName: '_TZE204_5toc8efa'}, {modelID: 'TS0601', manufacturerName: '_TZE200_5toc8efa'}, {modelID: 'TS0601', manufacturerName: '_TZE200_ye5jkfsb'}, {modelID: 'TS0601', manufacturerName: '_TZE204_aoclfnxz'}, {modelID: 'TS0601', manufacturerName: '_TZE200_u9bfwha0'}, - {modelID: 'TS0601', manufacturerName: '_TZE204_u9bfwha0'}], + {modelID: 'TS0601', manufacturerName: '_TZE204_u9bfwha0'}, + ], model: 'BHT-002-GCLZB', vendor: 'Moes', description: 'Moes BHT series Thermostat', fromZigbee: [legacy.fz.moes_thermostat], - toZigbee: [legacy.tz.moes_thermostat_child_lock, legacy.tz.moes_thermostat_current_heating_setpoint, legacy.tz.moes_thermostat_mode, - legacy.tz.moes_thermostat_standby, legacy.tz.moes_thermostat_sensor, legacy.tz.moes_thermostat_calibration, - legacy.tz.moes_thermostat_deadzone_temperature, legacy.tz.moes_thermostat_max_temperature_limit, - legacy.tz.moes_thermostat_min_temperature_limit, legacy.tz.moes_thermostat_program_schedule], - whiteLabel: [ - tuya.whitelabel('Moes', 'BHT-002/BHT-006', 'Smart heating thermostat', ['_TZE204_aoclfnxz']), + toZigbee: [ + legacy.tz.moes_thermostat_child_lock, + legacy.tz.moes_thermostat_current_heating_setpoint, + legacy.tz.moes_thermostat_mode, + legacy.tz.moes_thermostat_standby, + legacy.tz.moes_thermostat_sensor, + legacy.tz.moes_thermostat_calibration, + legacy.tz.moes_thermostat_deadzone_temperature, + legacy.tz.moes_thermostat_max_temperature_limit, + legacy.tz.moes_thermostat_min_temperature_limit, + legacy.tz.moes_thermostat_program_schedule, ], + whiteLabel: [tuya.whitelabel('Moes', 'BHT-002/BHT-006', 'Smart heating thermostat', ['_TZE204_aoclfnxz'])], exposes: (device, options) => { const heatingStepSize = device?.manufacturerName === '_TZE204_5toc8efa' ? 0.5 : 1; - return [e.linkquality(), e.child_lock(), e.deadzone_temperature(), e.max_temperature_limit().withValueMax(45), e.min_temperature_limit(), - e.climate().withSetpoint('current_heating_setpoint', 5, 45, heatingStepSize, ea.STATE_SET) - .withLocalTemperature(ea.STATE).withLocalTemperatureCalibration(-30, 30, 0.1, ea.STATE_SET) - .withSystemMode(['off', 'heat'], ea.STATE_SET).withRunningState(['idle', 'heat', 'cool'], ea.STATE) + return [ + e.linkquality(), + e.child_lock(), + e.deadzone_temperature(), + e.max_temperature_limit().withValueMax(45), + e.min_temperature_limit(), + e + .climate() + .withSetpoint('current_heating_setpoint', 5, 45, heatingStepSize, ea.STATE_SET) + .withLocalTemperature(ea.STATE) + .withLocalTemperatureCalibration(-30, 30, 0.1, ea.STATE_SET) + .withSystemMode(['off', 'heat'], ea.STATE_SET) + .withRunningState(['idle', 'heat', 'cool'], ea.STATE) .withPreset(['hold', 'program']), e.temperature_sensor_select(['IN', 'AL', 'OU']), - e.composite('program', 'program', ea.STATE_SET).withDescription('Time of day and setpoint to use when in program mode') + e + .composite('program', 'program', ea.STATE_SET) + .withDescription('Time of day and setpoint to use when in program mode') .withFeature(exposesLocal.hour('weekdays_p1_hour')) .withFeature(exposesLocal.minute('weekdays_p1_minute')) .withFeature(exposesLocal.program_temperature('weekdays_p1_temperature')) @@ -163,15 +184,20 @@ const definitions: Definition[] = [ onEvent: tuya.onEventSetLocalTime, }, { - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_amp6tsvy'}, {modelID: 'TS0601', manufacturerName: '_TZE200_tviaymwx'}], + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE200_amp6tsvy'}, + {modelID: 'TS0601', manufacturerName: '_TZE200_tviaymwx'}, + ], model: 'ZTS-EU_1gang', vendor: 'Moes', description: 'Wall touch light switch (1 gang)', - exposes: [e.switch().setAccess('state', ea.STATE_SET), - e.enum('indicate_light', ea.STATE_SET, Object.values(legacy.moesSwitch.indicateLight)) - .withDescription('Indicator light status'), - e.enum('power_on_behavior', ea.STATE_SET, Object.values(legacy.moesSwitch.powerOnBehavior)) - .withDescription('Controls the behavior when the device is powered on')], + exposes: [ + e.switch().setAccess('state', ea.STATE_SET), + e.enum('indicate_light', ea.STATE_SET, Object.values(legacy.moesSwitch.indicateLight)).withDescription('Indicator light status'), + e + .enum('power_on_behavior', ea.STATE_SET, Object.values(legacy.moesSwitch.powerOnBehavior)) + .withDescription('Controls the behavior when the device is powered on'), + ], fromZigbee: [legacy.fz.tuya_switch, legacy.fz.moes_switch], toZigbee: [legacy.tz.tuya_switch_state, legacy.tz.moes_switch], onEvent: tuya.onEventSetLocalTime, @@ -187,19 +213,21 @@ const definitions: Definition[] = [ model: 'ZTS-EU_2gang', vendor: 'Moes', description: 'Wall touch light switch (2 gang)', - exposes: [e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), + exposes: [ + e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l2').setAccess('state', ea.STATE_SET), - e.enum('indicate_light', ea.STATE_SET, Object.values(legacy.moesSwitch.indicateLight)) - .withDescription('Indicator light status'), - e.enum('power_on_behavior', ea.STATE_SET, Object.values(legacy.moesSwitch.powerOnBehavior)) - .withDescription('Controls the behavior when the device is powered on')], + e.enum('indicate_light', ea.STATE_SET, Object.values(legacy.moesSwitch.indicateLight)).withDescription('Indicator light status'), + e + .enum('power_on_behavior', ea.STATE_SET, Object.values(legacy.moesSwitch.powerOnBehavior)) + .withDescription('Controls the behavior when the device is powered on'), + ], fromZigbee: [fz.ignore_basic_report, legacy.fz.tuya_switch, legacy.fz.moes_switch], toZigbee: [legacy.tz.tuya_switch_state, legacy.tz.moes_switch], onEvent: tuya.onEventSetLocalTime, meta: {multiEndpoint: true}, endpoint: (device) => { // Endpoint selection is made in tuya_switch_state - return {'l1': 1, 'l2': 1}; + return {l1: 1, l2: 1}; }, configure: async (device, coordinatorEndpoint) => { await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff']); @@ -214,20 +242,22 @@ const definitions: Definition[] = [ model: 'ZTS-EU_3gang', vendor: 'Moes', description: 'Wall touch light switch (3 gang)', - exposes: [e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), + exposes: [ + e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l2').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l3').setAccess('state', ea.STATE_SET), - e.enum('indicate_light', ea.STATE_SET, Object.values(legacy.moesSwitch.indicateLight)) - .withDescription('Indicator light status'), - e.enum('power_on_behavior', ea.STATE_SET, Object.values(legacy.moesSwitch.powerOnBehavior)) - .withDescription('Controls the behavior when the device is powered on')], + e.enum('indicate_light', ea.STATE_SET, Object.values(legacy.moesSwitch.indicateLight)).withDescription('Indicator light status'), + e + .enum('power_on_behavior', ea.STATE_SET, Object.values(legacy.moesSwitch.powerOnBehavior)) + .withDescription('Controls the behavior when the device is powered on'), + ], fromZigbee: [fz.ignore_basic_report, legacy.fz.tuya_switch, legacy.fz.moes_switch], toZigbee: [legacy.tz.tuya_switch_state, legacy.tz.moes_switch], onEvent: tuya.onEventSetLocalTime, meta: {multiEndpoint: true}, endpoint: (device) => { // Endpoint selection is made in tuya_switch_state - return {'l1': 1, 'l2': 1, 'l3': 1}; + return {l1: 1, l2: 1, l3: 1}; }, configure: async (device, coordinatorEndpoint) => { await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff']); @@ -243,21 +273,23 @@ const definitions: Definition[] = [ model: 'ZTS-EU_4gang', vendor: 'Moes', description: 'Wall touch light switch (4 gang)', - exposes: [e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), + exposes: [ + e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l2').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l3').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l4').setAccess('state', ea.STATE_SET), - e.enum('indicate_light', ea.STATE_SET, Object.values(legacy.moesSwitch.indicateLight)) - .withDescription('Indicator light status'), - e.enum('power_on_behavior', ea.STATE_SET, Object.values(legacy.moesSwitch.powerOnBehavior)) - .withDescription('Controls the behavior when the device is powered on')], + e.enum('indicate_light', ea.STATE_SET, Object.values(legacy.moesSwitch.indicateLight)).withDescription('Indicator light status'), + e + .enum('power_on_behavior', ea.STATE_SET, Object.values(legacy.moesSwitch.powerOnBehavior)) + .withDescription('Controls the behavior when the device is powered on'), + ], fromZigbee: [fz.ignore_basic_report, legacy.fz.tuya_switch, legacy.fz.moes_switch], toZigbee: [legacy.tz.tuya_switch_state, legacy.tz.moes_switch], onEvent: tuya.onEventSetLocalTime, meta: {multiEndpoint: true}, endpoint: (device) => { // Endpoint selection is made in tuya_switch_state - return {'l1': 1, 'l2': 1, 'l3': 1, 'l4': 1}; + return {l1: 1, l2: 1, l3: 1, l4: 1}; }, configure: async (device, coordinatorEndpoint) => { await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff']); @@ -270,7 +302,10 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'TS0222', manufacturerName: '_TYZB01_kvwjujy9'}, {modelID: 'TS0222', manufacturerName: '_TYZB01_ftdkanlj'}], + fingerprint: [ + {modelID: 'TS0222', manufacturerName: '_TYZB01_kvwjujy9'}, + {modelID: 'TS0222', manufacturerName: '_TYZB01_ftdkanlj'}, + ], model: 'ZSS-ZK-THL', vendor: 'Moes', description: 'Smart temperature and humidity meter with display', @@ -287,41 +322,71 @@ const definitions: Definition[] = [ // OTA available but bricks device https://github.com/Koenkk/zigbee2mqtt/issues/18840 onEvent: tuya.onEventSetLocalTime, fromZigbee: [fz.ignore_basic_report, fz.ignore_tuya_set_time, legacy.fz.moesS_thermostat], - toZigbee: [legacy.tz.moesS_thermostat_current_heating_setpoint, legacy.tz.moesS_thermostat_child_lock, - legacy.tz.moesS_thermostat_window_detection, legacy.tz.moesS_thermostat_temperature_calibration, - legacy.tz.moesS_thermostat_boost_heating, legacy.tz.moesS_thermostat_boostHeatingCountdownTimeSet, - legacy.tz.moesS_thermostat_eco_temperature, legacy.tz.moesS_thermostat_max_temperature, - legacy.tz.moesS_thermostat_min_temperature, legacy.tz.moesS_thermostat_moesSecoMode, - legacy.tz.moesS_thermostat_preset, legacy.tz.moesS_thermostat_schedule_programming, - legacy.tz.moesS_thermostat_system_mode], + toZigbee: [ + legacy.tz.moesS_thermostat_current_heating_setpoint, + legacy.tz.moesS_thermostat_child_lock, + legacy.tz.moesS_thermostat_window_detection, + legacy.tz.moesS_thermostat_temperature_calibration, + legacy.tz.moesS_thermostat_boost_heating, + legacy.tz.moesS_thermostat_boostHeatingCountdownTimeSet, + legacy.tz.moesS_thermostat_eco_temperature, + legacy.tz.moesS_thermostat_max_temperature, + legacy.tz.moesS_thermostat_min_temperature, + legacy.tz.moesS_thermostat_moesSecoMode, + legacy.tz.moesS_thermostat_preset, + legacy.tz.moesS_thermostat_schedule_programming, + legacy.tz.moesS_thermostat_system_mode, + ], exposes: [ - e.battery(), e.child_lock(), e.eco_mode(), - e.eco_temperature().withValueMin(5), e.max_temperature().withValueMax(45), e.min_temperature().withValueMin(0), - e.valve_state(), e.position(), e.window_detection(), + e.battery(), + e.child_lock(), + e.eco_mode(), + e.eco_temperature().withValueMin(5), + e.max_temperature().withValueMax(45), + e.min_temperature().withValueMin(0), + e.valve_state(), + e.position(), + e.window_detection(), e.binary('window', ea.STATE, 'OPEN', 'CLOSED').withDescription('Window status closed or open '), - e.climate() - .withLocalTemperature(ea.STATE).withSetpoint('current_heating_setpoint', 0, 35, 1, ea.STATE_SET) + e + .climate() + .withLocalTemperature(ea.STATE) + .withSetpoint('current_heating_setpoint', 0, 35, 1, ea.STATE_SET) .withLocalTemperatureCalibration(-9, 9, 1, ea.STATE_SET) .withSystemMode(['heat'], ea.STATE_SET) .withRunningState(['idle', 'heat'], ea.STATE) - .withPreset(['programming', 'manual', 'temporary_manual', 'holiday'], - 'MANUAL MODE ☝ - In this mode, the device executes manual temperature setting. '+ - 'When the set temperature is lower than the "minimum temperature", the valve is closed (forced closed). ' + - 'PROGRAMMING MODE ⏱ - In this mode, the device executes a preset week programming temperature time and temperature. ' + - 'HOLIDAY MODE ⛱ - In this mode, for example, the vacation mode is set for 10 days and the temperature is set' + - 'to 15 degrees Celsius. After 10 days, the device will automatically switch to programming mode. ' + - 'TEMPORARY MANUAL MODE - In this mode, ☝ icon will flash. At this time, the device executes the manually set ' + - 'temperature and returns to the weekly programming mode in the next time period. '), - e.text('programming_mode', ea.STATE_SET).withDescription('PROGRAMMING MODE ⏱ - In this mode, ' + - 'the device executes a preset week programming temperature time and temperature. ' + - 'You can set up to 4 stages of temperature every for WEEKDAY ➀➁➂➃➄, SATURDAY ➅ and SUNDAY ➆.'), - e.binary('boost_heating', ea.STATE_SET, 'ON', 'OFF').withDescription('Boost Heating: press and hold "+" for 3 seconds, ' + - 'the device will enter the boost heating mode, and the ▷╵◁ will flash. The countdown will be displayed in the APP'), - e.numeric('boost_heating_countdown', ea.STATE).withUnit('min').withDescription('Countdown in minutes') - .withValueMin(0).withValueMax(15), - e.numeric('boost_heating_countdown_time_set', ea.STATE_SET).withUnit('s') - .withDescription('Boost Time Setting 0 sec - 900 sec, (default = 300 sec)').withValueMin(0) - .withValueMax(900).withValueStep(1)], + .withPreset( + ['programming', 'manual', 'temporary_manual', 'holiday'], + 'MANUAL MODE ☝ - In this mode, the device executes manual temperature setting. ' + + 'When the set temperature is lower than the "minimum temperature", the valve is closed (forced closed). ' + + 'PROGRAMMING MODE ⏱ - In this mode, the device executes a preset week programming temperature time and temperature. ' + + 'HOLIDAY MODE ⛱ - In this mode, for example, the vacation mode is set for 10 days and the temperature is set' + + 'to 15 degrees Celsius. After 10 days, the device will automatically switch to programming mode. ' + + 'TEMPORARY MANUAL MODE - In this mode, ☝ icon will flash. At this time, the device executes the manually set ' + + 'temperature and returns to the weekly programming mode in the next time period. ', + ), + e + .text('programming_mode', ea.STATE_SET) + .withDescription( + 'PROGRAMMING MODE ⏱ - In this mode, ' + + 'the device executes a preset week programming temperature time and temperature. ' + + 'You can set up to 4 stages of temperature every for WEEKDAY ➀➁➂➃➄, SATURDAY ➅ and SUNDAY ➆.', + ), + e + .binary('boost_heating', ea.STATE_SET, 'ON', 'OFF') + .withDescription( + 'Boost Heating: press and hold "+" for 3 seconds, ' + + 'the device will enter the boost heating mode, and the ▷╵◁ will flash. The countdown will be displayed in the APP', + ), + e.numeric('boost_heating_countdown', ea.STATE).withUnit('min').withDescription('Countdown in minutes').withValueMin(0).withValueMax(15), + e + .numeric('boost_heating_countdown_time_set', ea.STATE_SET) + .withUnit('s') + .withDescription('Boost Time Setting 0 sec - 900 sec, (default = 300 sec)') + .withValueMin(0) + .withValueMax(900) + .withValueStep(1), + ], }, { fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_nhyj64w2'}], @@ -331,8 +396,12 @@ const definitions: Definition[] = [ onEvent: tuya.onEventSetLocalTime, fromZigbee: [legacy.fz.moes_cover, fz.ignore_basic_report], toZigbee: [legacy.tz.moes_cover], - exposes: [e.cover_position().setAccess('position', ea.STATE_SET), e.enum('backlight', ea.STATE_SET, ['OFF', 'ON']), - e.enum('calibration', ea.STATE_SET, ['OFF', 'ON']), e.enum('motor_reversal', ea.STATE_SET, ['OFF', 'ON'])], + exposes: [ + e.cover_position().setAccess('position', ea.STATE_SET), + e.enum('backlight', ea.STATE_SET, ['OFF', 'ON']), + e.enum('calibration', ea.STATE_SET, ['OFF', 'ON']), + e.enum('motor_reversal', ea.STATE_SET, ['OFF', 'ON']), + ], }, { fingerprint: [ @@ -345,8 +414,12 @@ const definitions: Definition[] = [ vendor: 'Moes', description: 'Universal smart IR remote control', fromZigbee: [ - fzZosung.zosung_send_ir_code_00, fzZosung.zosung_send_ir_code_01, fzZosung.zosung_send_ir_code_02, - fzZosung.zosung_send_ir_code_03, fzZosung.zosung_send_ir_code_04, fzZosung.zosung_send_ir_code_05, + fzZosung.zosung_send_ir_code_00, + fzZosung.zosung_send_ir_code_01, + fzZosung.zosung_send_ir_code_02, + fzZosung.zosung_send_ir_code_03, + fzZosung.zosung_send_ir_code_04, + fzZosung.zosung_send_ir_code_05, fz.battery, ], toZigbee: [tzZosung.zosung_ir_code_to_send, tzZosung.zosung_learn_ir_code], @@ -366,9 +439,7 @@ const definitions: Definition[] = [ await reporting.batteryVoltage(endpoint); } }, - whiteLabel: [ - tuya.whitelabel('Tuya', 'iH-F8260', 'Universal smart IR remote control', ['_TZ3290_gnl5a6a5xvql7c2a']), - ], + whiteLabel: [tuya.whitelabel('Tuya', 'iH-F8260', 'Universal smart IR remote control', ['_TZ3290_gnl5a6a5xvql7c2a'])], }, { fingerprint: tuya.fingerprint('TS0049', ['_TZ3000_cjfmu5he']), @@ -418,14 +489,16 @@ const definitions: Definition[] = [ onEvent: tuya.onEventSetTime, configure: tuya.configureMagicPacket, exposes: [ - e.smoke(), e.battery(), tuya.exposes.batteryState(), + e.smoke(), + e.battery(), + tuya.exposes.batteryState(), e.binary('silence', ea.STATE_SET, 'ON', 'OFF'), e.enum('self_test', ea.STATE, ['checking', 'check_success', 'check_failure']), ], meta: { tuyaDatapoints: [ [1, 'smoke', tuya.valueConverter.trueFalse0], - [9, 'self_test', tuya.valueConverterBasic.lookup({'checking': 0, 'check_success': 1, 'check_failure': 2})], + [9, 'self_test', tuya.valueConverterBasic.lookup({checking: 0, check_success: 1, check_failure: 2})], [14, 'battery_state', tuya.valueConverter.batteryState], [15, 'battery', tuya.valueConverter.raw], [16, 'silence', tuya.valueConverter.onOff], @@ -433,36 +506,52 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'TS004F', manufacturerName: '_TZ3000_ja5osu5g'}, - {modelID: 'TS004F', manufacturerName: '_TZ3000_kjfzuycl'}], + fingerprint: [ + {modelID: 'TS004F', manufacturerName: '_TZ3000_ja5osu5g'}, + {modelID: 'TS004F', manufacturerName: '_TZ3000_kjfzuycl'}, + ], model: 'ERS-10TZBVB-AA', vendor: 'Moes', description: 'Smart button', - whiteLabel: [ - tuya.whitelabel('Loginovo', 'ZG-101ZL', 'Smart button', ['_TZ3000_ja5osu5g']), - ], + whiteLabel: [tuya.whitelabel('Loginovo', 'ZG-101ZL', 'Smart button', ['_TZ3000_ja5osu5g'])], fromZigbee: [ - fz.command_step, fz.command_on, fz.command_off, fz.command_move_to_color_temp, fz.command_move_to_level, - fz.tuya_multi_action, fz.tuya_operation_mode, fz.battery, + fz.command_step, + fz.command_on, + fz.command_off, + fz.command_move_to_color_temp, + fz.command_move_to_level, + fz.tuya_multi_action, + fz.tuya_operation_mode, + fz.battery, ], toZigbee: [tz.tuya_operation_mode], exposes: [ e.action([ - 'single', 'double', 'hold', 'brightness_move_to_level', 'color_temperature_move', - 'brightness_step_up', 'brightness_step_down', 'on', 'off', + 'single', + 'double', + 'hold', + 'brightness_move_to_level', + 'color_temperature_move', + 'brightness_step_up', + 'brightness_step_down', + 'on', + 'off', ]), e.battery(), - e.enum('operation_mode', ea.ALL, ['command', 'event']).withDescription( - 'Operation mode: "command" - for group control, "event" - for clicks'), + e + .enum('operation_mode', ea.ALL, ['command', 'event']) + .withDescription('Operation mode: "command" - for group control, "event" - for clicks'), ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await endpoint.read('genBasic', [0x0004, 0x000, 0x0001, 0x0005, 0x0007, 0xfffe]); - await endpoint.write('genOnOff', {'tuyaOperationMode': 1}); + await endpoint.write('genOnOff', {tuyaOperationMode: 1}); await endpoint.read('genOnOff', ['tuyaOperationMode']); try { - await endpoint.read(0xE001, [0xD011]); - } catch (err) {/* do nothing */} + await endpoint.read(0xe001, [0xd011]); + } catch (err) { + /* do nothing */ + } await endpoint.read('genPowerCfg', ['batteryVoltage', 'batteryPercentageRemaining']); await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']); @@ -484,10 +573,10 @@ const definitions: Definition[] = [ ], meta: { tuyaDatapoints: [ - [1, 'state', tuya.valueConverterBasic.lookup({'OPEN': tuya.enum(0), 'STOP': tuya.enum(1), 'CLOSE': tuya.enum(2)})], + [1, 'state', tuya.valueConverterBasic.lookup({OPEN: tuya.enum(0), STOP: tuya.enum(1), CLOSE: tuya.enum(2)})], [2, 'position', tuya.valueConverter.coverPosition], - [3, 'calibration', tuya.valueConverterBasic.lookup({'START': tuya.enum(0), 'END': tuya.enum(1)})], - [8, 'motor_steering', tuya.valueConverterBasic.lookup({'FORWARD': tuya.enum(0), 'BACKWARD': tuya.enum(1)})], + [3, 'calibration', tuya.valueConverterBasic.lookup({START: tuya.enum(0), END: tuya.enum(1)})], + [8, 'motor_steering', tuya.valueConverterBasic.lookup({FORWARD: tuya.enum(0), BACKWARD: tuya.enum(1)})], ], }, }, @@ -498,17 +587,20 @@ const definitions: Definition[] = [ description: 'Smart switch (light + sence)', extend: [ tuya.modernExtend.tuyaMagicPacket(), - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2, 'l3': 3}}), + deviceEndpoints({endpoints: {l1: 1, l2: 2, l3: 3}}), tuya.modernExtend.tuyaOnOff({endpoints: ['l1', 'l2', 'l3'], powerOnBehavior2: true, switchMode: true}), actionEnumLookup({ cluster: 'genOnOff', commands: ['commandTuyaAction'], attribute: 'value', - actionLookup: {'button': 0}, + actionLookup: {button: 0}, buttonLookup: { - '1_up': 4, '1_down': 1, - '2_up': 5, '2_down': 2, - '3_up': 6, '3_down': 3, + '1_up': 4, + '1_down': 1, + '2_up': 5, + '2_down': 2, + '3_up': 6, + '3_down': 3, }, }), tuya.modernExtend.tuyaLedIndicator(), diff --git a/src/devices/muller_licht.ts b/src/devices/muller_licht.ts index 20c817b541e23..e7165fcc494ae 100644 --- a/src/devices/muller_licht.ts +++ b/src/devices/muller_licht.ts @@ -1,11 +1,11 @@ -import {Definition, Zh} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; +import {light as lightDontUse, onOff, LightArgs} from '../lib/modernExtend'; import * as reporting from '../lib/reporting'; import * as tuya from '../lib/tuya'; -import {light as lightDontUse, onOff, LightArgs} from '../lib/modernExtend'; +import {Definition, Zh} from '../lib/types'; const e = exposes.presets; @@ -112,8 +112,10 @@ const definitions: Definition[] = [ extend: [mullerLichtLight({colorTemp: {range: undefined}, color: true})], }, { - fingerprint: [{modelID: 'TS0505B', manufacturerName: '_TZ3210_mntza0sw'}, - {modelID: 'TS0505B', manufacturerName: '_TZ3210_r0vzq1oj'}], + fingerprint: [ + {modelID: 'TS0505B', manufacturerName: '_TZ3210_mntza0sw'}, + {modelID: 'TS0505B', manufacturerName: '_TZ3210_r0vzq1oj'}, + ], model: '404062', vendor: 'Müller Licht', description: 'Kea RGB+CCT', @@ -126,11 +128,36 @@ const definitions: Definition[] = [ model: 'MLI-404011/MLI-404049', description: 'Tint remote control', vendor: 'Müller Licht', - fromZigbee: [fz.command_on, fz.command_off, fz.command_toggle, legacy.fz.tint404011_brightness_updown_click, - legacy.fz.tint404011_move_to_color_temp, legacy.fz.tint404011_move_to_color, fz.tint_scene, - legacy.fz.tint404011_brightness_updown_release, legacy.fz.tint404011_brightness_updown_hold], - exposes: [e.action(['on', 'off', 'brightness_step_up', 'brightness_step_down', 'brightness_move_up', 'brightness_move_down', - 'brightness_stop', 'color_temperature_move', 'color_move', 'scene_1', 'scene_2', 'scene_3', 'scene_4', 'scene_5', 'scene_6'])], + fromZigbee: [ + fz.command_on, + fz.command_off, + fz.command_toggle, + legacy.fz.tint404011_brightness_updown_click, + legacy.fz.tint404011_move_to_color_temp, + legacy.fz.tint404011_move_to_color, + fz.tint_scene, + legacy.fz.tint404011_brightness_updown_release, + legacy.fz.tint404011_brightness_updown_hold, + ], + exposes: [ + e.action([ + 'on', + 'off', + 'brightness_step_up', + 'brightness_step_down', + 'brightness_move_up', + 'brightness_move_down', + 'brightness_stop', + 'color_temperature_move', + 'color_move', + 'scene_1', + 'scene_2', + 'scene_3', + 'scene_4', + 'scene_5', + 'scene_6', + ]), + ], toZigbee: [], }, { @@ -139,8 +166,19 @@ const definitions: Definition[] = [ description: 'Tint dim remote control', vendor: 'Müller Licht', fromZigbee: [fz.command_on, fz.command_off, fz.command_step, fz.command_move, fz.command_stop, fz.command_recall, fz.command_store], - exposes: [e.action(['on', 'off', 'brightness_step_up', 'brightness_step_down', 'brightness_move_up', 'brightness_move_down', - 'brightness_stop', 'recall_1', 'store_1'])], + exposes: [ + e.action([ + 'on', + 'off', + 'brightness_step_up', + 'brightness_step_down', + 'brightness_move_up', + 'brightness_move_down', + 'brightness_stop', + 'recall_1', + 'store_1', + ]), + ], toZigbee: [], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -160,17 +198,48 @@ const definitions: Definition[] = [ model: '404022/404049C', description: 'Tint dim remote control', vendor: 'Müller Licht', - fromZigbee: [fz.command_on, fz.command_off, fz.command_step, fz.command_move, fz.command_stop, fz.command_move_to_color_temp, - fz.command_move_to_color, fz.tint_scene], - exposes: [e.action(['on', 'off', 'brightness_step_up', 'brightness_step_down', 'brightness_move_up', 'brightness_move_down', - 'brightness_stop', 'color_temperature_move', 'color_move', 'scene_1', 'scene_2', 'scene_3', 'scene_4', 'scene_5', 'scene_6'])], + fromZigbee: [ + fz.command_on, + fz.command_off, + fz.command_step, + fz.command_move, + fz.command_stop, + fz.command_move_to_color_temp, + fz.command_move_to_color, + fz.tint_scene, + ], + exposes: [ + e.action([ + 'on', + 'off', + 'brightness_step_up', + 'brightness_step_down', + 'brightness_move_up', + 'brightness_move_down', + 'brightness_stop', + 'color_temperature_move', + 'color_move', + 'scene_1', + 'scene_2', + 'scene_3', + 'scene_4', + 'scene_5', + 'scene_6', + ]), + ], toZigbee: [], configure: async (device, coordinatorEndpoint) => { device.powerSource = 'Battery'; device.save(); }, - whiteLabel: [{vendor: 'Müller Licht', model: '404049D', description: 'Tint dim remote control', - fingerprint: [{modelID: 'Remote Control', manufacturerName: 'MLI'}]}], + whiteLabel: [ + { + vendor: 'Müller Licht', + model: '404049D', + description: 'Tint dim remote control', + fingerprint: [{modelID: 'Remote Control', manufacturerName: 'MLI'}], + }, + ], }, { zigbeeModel: ['tint-ColorTemperature', 'tint-ColorTemperature2'], @@ -180,14 +249,20 @@ const definitions: Definition[] = [ extend: [mullerLichtLight({colorTemp: {range: [153, 555]}})], }, { - fingerprint: [{ - // Identify through fingerprint as modelID is the same as Sunricher ZG192910-4 - type: 'Router', manufacturerID: 4635, manufacturerName: 'MLI', modelID: 'CCT Lighting', - powerSource: 'Mains (single phase)', endpoints: [ - {ID: 1, profileID: 49246, deviceID: 544, inputClusters: [0, 3, 4, 5, 6, 8, 768, 2821, 4096], outputClusters: [25]}, - {ID: 242, profileID: 41440, deviceID: 102, inputClusters: [33], outputClusters: [33]}, - ], - }], + fingerprint: [ + { + // Identify through fingerprint as modelID is the same as Sunricher ZG192910-4 + type: 'Router', + manufacturerID: 4635, + manufacturerName: 'MLI', + modelID: 'CCT Lighting', + powerSource: 'Mains (single phase)', + endpoints: [ + {ID: 1, profileID: 49246, deviceID: 544, inputClusters: [0, 3, 4, 5, 6, 8, 768, 2821, 4096], outputClusters: [25]}, + {ID: 242, profileID: 41440, deviceID: 102, inputClusters: [33], outputClusters: [33]}, + ], + }, + ], model: '404031', vendor: 'Müller Licht', description: 'Tint Armaro', diff --git a/src/devices/namron.ts b/src/devices/namron.ts index 4a56dea5138e5..502956cc7df04 100644 --- a/src/devices/namron.ts +++ b/src/devices/namron.ts @@ -1,15 +1,16 @@ import {Zcl} from 'zigbee-herdsman'; -import {Definition, Fz, Tz, KeyValue} from '../lib/types'; -import * as exposes from '../lib/exposes'; + import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; import * as constants from '../lib/constants'; +import * as exposes from '../lib/exposes'; +import {forcePowerSource, light, onOff} from '../lib/modernExtend'; +import * as ota from '../lib/ota'; import * as reporting from '../lib/reporting'; import * as globalStore from '../lib/store'; -import * as ota from '../lib/ota'; -import * as utils from '../lib/utils'; -import {forcePowerSource, light, onOff} from '../lib/modernExtend'; import * as tuya from '../lib/tuya'; +import {Definition, Fz, Tz, KeyValue} from '../lib/types'; +import * as utils from '../lib/utils'; const ea = exposes.access; const e = exposes.presets; @@ -23,23 +24,28 @@ const fzLocal = { convert: (model, msg, publish, options, meta) => { const result: KeyValue = {}; const data = msg.data; - if (data.hasOwnProperty(0x1000)) { // OperateDisplayBrightnesss + if (data.hasOwnProperty(0x1000)) { + // OperateDisplayBrightnesss result.display_brightnesss = data[0x1000]; } - if (data.hasOwnProperty(0x1001)) { // DisplayAutoOffActivation + if (data.hasOwnProperty(0x1001)) { + // DisplayAutoOffActivation const lookup = {0: 'deactivated', 1: 'activated'}; result.display_auto_off = utils.getFromLookup(data[0x1001], lookup); } - if (data.hasOwnProperty(0x1004)) { // PowerUpStatus + if (data.hasOwnProperty(0x1004)) { + // PowerUpStatus const lookup = {0: 'manual', 1: 'last_state'}; result.power_up_status = utils.getFromLookup(data[0x1004], lookup); } - if (data.hasOwnProperty(0x1009)) { // WindowOpenCheck + if (data.hasOwnProperty(0x1009)) { + // WindowOpenCheck const lookup = {0: 'enable', 1: 'disable'}; result.window_open_check = utils.getFromLookup(data[0x1009], lookup); } - if (data.hasOwnProperty(0x100A)) { // Hysterersis - result.hysterersis = utils.precisionRound(data[0x100A], 2) / 10; + if (data.hasOwnProperty(0x100a)) { + // Hysterersis + result.hysterersis = utils.precisionRound(data[0x100a], 2) / 10; } return result; }, @@ -48,51 +54,48 @@ const fzLocal = { const tzLocal = { namron_panelheater: { - key: [ - 'display_brightnesss', 'display_auto_off', - 'power_up_status', 'window_open_check', 'hysterersis', - ], + key: ['display_brightnesss', 'display_auto_off', 'power_up_status', 'window_open_check', 'hysterersis'], convertSet: async (entity, key, value, meta) => { if (key === 'display_brightnesss') { const payload = {0x1000: {value: value, type: Zcl.DataType.ENUM8}}; await entity.write('hvacThermostat', payload, sunricherManufacturer); } else if (key === 'display_auto_off') { - const lookup = {'deactivated': 0, 'activated': 1}; + const lookup = {deactivated: 0, activated: 1}; const payload = {0x1001: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.ENUM8}}; await entity.write('hvacThermostat', payload, sunricherManufacturer); } else if (key === 'power_up_status') { - const lookup = {'manual': 0, 'last_state': 1}; + const lookup = {manual: 0, last_state: 1}; const payload = {0x1004: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.ENUM8}}; await entity.write('hvacThermostat', payload, sunricherManufacturer); - } else if (key==='window_open_check') { - const lookup = {'enable': 0, 'disable': 1}; + } else if (key === 'window_open_check') { + const lookup = {enable: 0, disable: 1}; const payload = {0x1009: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.ENUM8}}; await entity.write('hvacThermostat', payload, sunricherManufacturer); - } else if (key==='hysterersis') { - const payload = {0x100A: {value: utils.toNumber(value, 'hysterersis') * 10, type: 0x20}}; + } else if (key === 'hysterersis') { + const payload = {0x100a: {value: utils.toNumber(value, 'hysterersis') * 10, type: 0x20}}; await entity.write('hvacThermostat', payload, sunricherManufacturer); } }, convertGet: async (entity, key, meta) => { switch (key) { - case 'display_brightnesss': - await entity.read('hvacThermostat', [0x1000], sunricherManufacturer); - break; - case 'display_auto_off': - await entity.read('hvacThermostat', [0x1001], sunricherManufacturer); - break; - case 'power_up_status': - await entity.read('hvacThermostat', [0x1004], sunricherManufacturer); - break; - case 'window_open_check': - await entity.read('hvacThermostat', [0x1009], sunricherManufacturer); - break; - case 'hysterersis': - await entity.read('hvacThermostat', [0x100A], sunricherManufacturer); - break; + case 'display_brightnesss': + await entity.read('hvacThermostat', [0x1000], sunricherManufacturer); + break; + case 'display_auto_off': + await entity.read('hvacThermostat', [0x1001], sunricherManufacturer); + break; + case 'power_up_status': + await entity.read('hvacThermostat', [0x1004], sunricherManufacturer); + break; + case 'window_open_check': + await entity.read('hvacThermostat', [0x1009], sunricherManufacturer); + break; + case 'hysterersis': + await entity.read('hvacThermostat', [0x100a], sunricherManufacturer); + break; - default: // Unknown key - throw new Error(`Unhandled key toZigbee.namron_panelheater.convertGet ${key}`); + default: // Unknown key + throw new Error(`Unhandled key toZigbee.namron_panelheater.convertGet ${key}`); } }, } satisfies Tz.Converter, @@ -146,9 +149,7 @@ const definitions: Definition[] = [ exposes: [e.power(), e.current(), e.voltage(), e.energy(), e.switch()], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1) || device.getEndpoint(3); - const binds = [ - 'seMetering', 'haElectricalMeasurement', 'genOnOff', - ]; + const binds = ['seMetering', 'haElectricalMeasurement', 'genOnOff']; await reporting.bind(endpoint, coordinatorEndpoint, binds); await reporting.onOff(endpoint); // Metering @@ -203,12 +204,31 @@ const definitions: Definition[] = [ vendor: 'Namron', description: 'Zigbee 4 channel switch K8 (white)', fromZigbee: [fz.command_on, fz.command_off, fz.battery, fz.command_move, fz.command_stop], - exposes: [e.battery(), e.action([ - 'on_l1', 'off_l1', 'brightness_move_up_l1', 'brightness_move_down_l1', 'brightness_stop_l1', - 'on_l2', 'off_l2', 'brightness_move_up_l2', 'brightness_move_down_l2', 'brightness_stop_l2', - 'on_l3', 'off_l3', 'brightness_move_up_l3', 'brightness_move_down_l3', 'brightness_stop_l3', - 'on_l4', 'off_l4', 'brightness_move_up_l4', 'brightness_move_down_l4', 'brightness_stop_l4', - ])], + exposes: [ + e.battery(), + e.action([ + 'on_l1', + 'off_l1', + 'brightness_move_up_l1', + 'brightness_move_down_l1', + 'brightness_stop_l1', + 'on_l2', + 'off_l2', + 'brightness_move_up_l2', + 'brightness_move_down_l2', + 'brightness_stop_l2', + 'on_l3', + 'off_l3', + 'brightness_move_up_l3', + 'brightness_move_down_l3', + 'brightness_stop_l3', + 'on_l4', + 'off_l4', + 'brightness_move_up_l4', + 'brightness_move_down_l4', + 'brightness_stop_l4', + ]), + ], toZigbee: [], meta: {multiEndpoint: true}, endpoint: (device) => { @@ -224,11 +244,31 @@ const definitions: Definition[] = [ fromZigbee: [fz.command_on, fz.command_off, fz.battery, fz.command_move, fz.command_stop], toZigbee: [], meta: {multiEndpoint: true}, - exposes: [e.battery(), e.action([ - 'on_l1', 'off_l1', 'brightness_move_up_l1', 'brightness_move_down_l1', 'brightness_stop_l1', - 'on_l2', 'off_l2', 'brightness_move_up_l2', 'brightness_move_down_l2', 'brightness_stop_l2', - 'on_l3', 'off_l3', 'brightness_move_up_l3', 'brightness_move_down_l3', 'brightness_stop_l3', - 'on_l4', 'off_l4', 'brightness_move_up_l4', 'brightness_move_down_l4', 'brightness_stop_l4'])], + exposes: [ + e.battery(), + e.action([ + 'on_l1', + 'off_l1', + 'brightness_move_up_l1', + 'brightness_move_down_l1', + 'brightness_stop_l1', + 'on_l2', + 'off_l2', + 'brightness_move_up_l2', + 'brightness_move_down_l2', + 'brightness_stop_l2', + 'on_l3', + 'off_l3', + 'brightness_move_up_l3', + 'brightness_move_down_l3', + 'brightness_stop_l3', + 'on_l4', + 'off_l4', + 'brightness_move_up_l4', + 'brightness_move_down_l4', + 'brightness_stop_l4', + ]), + ], endpoint: (device) => { return {l1: 1, l2: 2, l3: 3, l4: 4}; }, @@ -265,8 +305,10 @@ const definitions: Definition[] = [ vendor: 'Namron', description: 'Zigbee 1 channel switch K4', fromZigbee: [fz.command_on, fz.command_off, fz.battery, fz.command_move, fz.command_stop, fz.command_step], - exposes: [e.battery(), e.action([ - 'on', 'off', 'brightness_move_up', 'brightness_move_down', 'brightness_stop', 'brightness_step_up', 'brightness_step_down'])], + exposes: [ + e.battery(), + e.action(['on', 'off', 'brightness_move_up', 'brightness_move_down', 'brightness_stop', 'brightness_step_up', 'brightness_step_down']), + ], toZigbee: [], }, { @@ -276,8 +318,21 @@ const definitions: Definition[] = [ description: 'Zigbee 2 channel switch K4 (white)', fromZigbee: [fz.command_on, fz.command_off, fz.battery, fz.command_move, fz.command_stop], meta: {multiEndpoint: true}, - exposes: [e.battery(), e.action(['on_l1', 'off_l1', 'brightness_move_up_l1', 'brightness_move_down_l1', 'brightness_stop_l1', - 'on_l2', 'off_l2', 'brightness_move_up_l2', 'brightness_move_down_l2', 'brightness_stop_l2'])], + exposes: [ + e.battery(), + e.action([ + 'on_l1', + 'off_l1', + 'brightness_move_up_l1', + 'brightness_move_down_l1', + 'brightness_stop_l1', + 'on_l2', + 'off_l2', + 'brightness_move_up_l2', + 'brightness_move_down_l2', + 'brightness_stop_l2', + ]), + ], toZigbee: [], endpoint: (device) => { return {l1: 1, l2: 2}; @@ -289,11 +344,17 @@ const definitions: Definition[] = [ model: '4512726', vendor: 'Namron', description: 'Zigbee 4 in 1 dimmer', - fromZigbee: [fz.battery, fz.command_on, fz.command_off, fz.command_move_to_level, fz.command_move_to_color_temp, - fz.command_move_to_hue, fz.ignore_genOta], + fromZigbee: [ + fz.battery, + fz.command_on, + fz.command_off, + fz.command_move_to_level, + fz.command_move_to_color_temp, + fz.command_move_to_hue, + fz.ignore_genOta, + ], toZigbee: [], - exposes: [e.battery(), e.battery_voltage(), - e.action(['on', 'off', 'brightness_move_to_level', 'color_temperature_move', 'move_to_hue'])], + exposes: [e.battery(), e.battery_voltage(), e.action(['on', 'off', 'brightness_move_to_level', 'color_temperature_move', 'move_to_hue'])], meta: {battery: {dontDividePercentage: true}}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -311,8 +372,21 @@ const definitions: Definition[] = [ description: 'Zigbee 2 channel switch K4 (black)', fromZigbee: [fz.command_on, fz.command_off, fz.battery, fz.command_move, fz.command_stop], meta: {multiEndpoint: true}, - exposes: [e.battery(), e.action(['on_l1', 'off_l1', 'brightness_move_up_l1', 'brightness_move_down_l1', 'brightness_stop_l1', - 'on_l2', 'off_l2', 'brightness_move_up_l2', 'brightness_move_down_l2', 'brightness_stop_l2'])], + exposes: [ + e.battery(), + e.action([ + 'on_l1', + 'off_l1', + 'brightness_move_up_l1', + 'brightness_move_down_l1', + 'brightness_stop_l1', + 'on_l2', + 'off_l2', + 'brightness_move_up_l2', + 'brightness_move_down_l2', + 'brightness_stop_l2', + ]), + ], toZigbee: [], endpoint: (device) => { return {l1: 1, l2: 2}; @@ -324,12 +398,33 @@ const definitions: Definition[] = [ model: '4512706', vendor: 'Namron', description: 'Remote control', - fromZigbee: [fz.command_on, fz.command_off, fz.command_step, fz.command_step_color_temperature, fz.command_recall, - fz.command_move_to_color_temp, fz.battery, fz.command_move_to_hue], - exposes: [e.battery(), e.action([ - 'on', 'off', 'brightness_step_up', 'brightness_step_down', 'color_temperature_step_up', - 'color_temperature_step_down', 'recall_*', 'color_temperature_move', - 'move_to_hue_l1', 'move_to_hue_l2', 'move_to_hue_l3', 'move_to_hue_l4'])], + fromZigbee: [ + fz.command_on, + fz.command_off, + fz.command_step, + fz.command_step_color_temperature, + fz.command_recall, + fz.command_move_to_color_temp, + fz.battery, + fz.command_move_to_hue, + ], + exposes: [ + e.battery(), + e.action([ + 'on', + 'off', + 'brightness_step_up', + 'brightness_step_down', + 'color_temperature_step_up', + 'color_temperature_step_down', + 'recall_*', + 'color_temperature_move', + 'move_to_hue_l1', + 'move_to_hue_l2', + 'move_to_hue_l3', + 'move_to_hue_l4', + ]), + ], toZigbee: [], meta: {multiEndpoint: true}, endpoint: (device) => { @@ -344,12 +439,32 @@ const definitions: Definition[] = [ fromZigbee: [fz.command_on, fz.command_off, fz.battery, fz.command_move, fz.command_stop, fz.command_recall], toZigbee: [], ota: ota.zigbeeOTA, - exposes: [e.battery(), e.action([ - 'on_l1', 'off_l1', 'brightness_move_up_l1', 'brightness_move_down_l1', 'brightness_stop_l1', - 'on_l2', 'off_l2', 'brightness_move_up_l2', 'brightness_move_down_l2', 'brightness_stop_l2', - 'on_l3', 'off_l3', 'brightness_move_up_l3', 'brightness_move_down_l3', 'brightness_stop_l3', - 'on_l4', 'off_l4', 'brightness_move_up_l4', 'brightness_move_down_l4', 'brightness_stop_l4', - 'recall_*'])], + exposes: [ + e.battery(), + e.action([ + 'on_l1', + 'off_l1', + 'brightness_move_up_l1', + 'brightness_move_down_l1', + 'brightness_stop_l1', + 'on_l2', + 'off_l2', + 'brightness_move_up_l2', + 'brightness_move_down_l2', + 'brightness_stop_l2', + 'on_l3', + 'off_l3', + 'brightness_move_up_l3', + 'brightness_move_down_l3', + 'brightness_stop_l3', + 'on_l4', + 'off_l4', + 'brightness_move_up_l4', + 'brightness_move_down_l4', + 'brightness_stop_l4', + 'recall_*', + ]), + ], meta: {multiEndpoint: true}, endpoint: (device) => { return {l1: 1, l2: 2, l3: 3, l4: 4}; @@ -416,63 +531,82 @@ const definitions: Definition[] = [ model: '4512737/4512738', vendor: 'Namron', description: 'Touch thermostat', - fromZigbee: [fz.thermostat, fz.namron_thermostat, fz.metering, fz.electrical_measurement, - fz.namron_hvac_user_interface], - toZigbee: [tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint, tz.thermostat_occupancy, - tz.thermostat_local_temperature_calibration, tz.thermostat_local_temperature, tz.thermostat_outdoor_temperature, - tz.thermostat_system_mode, tz.thermostat_control_sequence_of_operation, tz.thermostat_running_state, - tz.namron_thermostat_child_lock, tz.namron_thermostat], + fromZigbee: [fz.thermostat, fz.namron_thermostat, fz.metering, fz.electrical_measurement, fz.namron_hvac_user_interface], + toZigbee: [ + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_unoccupied_heating_setpoint, + tz.thermostat_occupancy, + tz.thermostat_local_temperature_calibration, + tz.thermostat_local_temperature, + tz.thermostat_outdoor_temperature, + tz.thermostat_system_mode, + tz.thermostat_control_sequence_of_operation, + tz.thermostat_running_state, + tz.namron_thermostat_child_lock, + tz.namron_thermostat, + ], exposes: [ e.local_temperature(), - e.numeric('outdoor_temperature', ea.STATE_GET).withUnit('°C') - .withDescription('Current temperature measured from the floor sensor'), - e.climate() + e.numeric('outdoor_temperature', ea.STATE_GET).withUnit('°C').withDescription('Current temperature measured from the floor sensor'), + e + .climate() .withSetpoint('occupied_heating_setpoint', 0, 40, 0.1) .withLocalTemperature() .withLocalTemperatureCalibration(-3, 3, 0.1) .withSystemMode(['off', 'auto', 'dry', 'heat']) .withRunningState(['idle', 'heat']), - e.binary('away_mode', ea.ALL, 'ON', 'OFF') - .withDescription('Enable/disable away mode'), - e.binary('child_lock', ea.ALL, 'LOCK', 'UNLOCK') - .withDescription('Enables/disables physical input on the device'), - e.power(), e.current(), e.voltage(), e.energy(), - e.enum('lcd_brightness', ea.ALL, ['low', 'mid', 'high']) - .withDescription('OLED brightness when operating the buttons. Default: Medium.'), - e.enum('button_vibration_level', ea.ALL, ['off', 'low', 'high']) - .withDescription('Key beep volume and vibration level. Default: Low.'), - e.enum('floor_sensor_type', ea.ALL, ['10k', '15k', '50k', '100k', '12k']) + e.binary('away_mode', ea.ALL, 'ON', 'OFF').withDescription('Enable/disable away mode'), + e.binary('child_lock', ea.ALL, 'LOCK', 'UNLOCK').withDescription('Enables/disables physical input on the device'), + e.power(), + e.current(), + e.voltage(), + e.energy(), + e.enum('lcd_brightness', ea.ALL, ['low', 'mid', 'high']).withDescription('OLED brightness when operating the buttons. Default: Medium.'), + e.enum('button_vibration_level', ea.ALL, ['off', 'low', 'high']).withDescription('Key beep volume and vibration level. Default: Low.'), + e + .enum('floor_sensor_type', ea.ALL, ['10k', '15k', '50k', '100k', '12k']) .withDescription('Type of the external floor sensor. Default: NTC 10K/25.'), - e.enum('sensor', ea.ALL, ['air', 'floor', 'both']) - .withDescription('The sensor used for heat control. Default: Room Sensor.'), - e.enum('powerup_status', ea.ALL, ['default', 'last_status']) - .withDescription('The mode after a power reset. Default: Previous Mode.'), - e.numeric('floor_sensor_calibration', ea.ALL) + e.enum('sensor', ea.ALL, ['air', 'floor', 'both']).withDescription('The sensor used for heat control. Default: Room Sensor.'), + e.enum('powerup_status', ea.ALL, ['default', 'last_status']).withDescription('The mode after a power reset. Default: Previous Mode.'), + e + .numeric('floor_sensor_calibration', ea.ALL) .withUnit('°C') - .withValueMin(-3).withValueMax(3).withValueStep(0.1) + .withValueMin(-3) + .withValueMax(3) + .withValueStep(0.1) .withDescription('The tempearatue calibration for the external floor sensor, between -3 and 3 in 0.1°C. Default: 0.'), - e.numeric('dry_time', ea.ALL) + e + .numeric('dry_time', ea.ALL) .withUnit('min') - .withValueMin(5).withValueMax(100) + .withValueMin(5) + .withValueMax(100) .withDescription('The duration of Dry Mode, between 5 and 100 minutes. Default: 5.'), - e.enum('mode_after_dry', ea.ALL, ['off', 'manual', 'auto', 'away']) - .withDescription('The mode after Dry Mode. Default: Auto.'), - e.enum('temperature_display', ea.ALL, ['room', 'floor']) - .withDescription('The temperature on the display. Default: Room Temperature.'), - e.numeric('window_open_check', ea.ALL) + e.enum('mode_after_dry', ea.ALL, ['off', 'manual', 'auto', 'away']).withDescription('The mode after Dry Mode. Default: Auto.'), + e.enum('temperature_display', ea.ALL, ['room', 'floor']).withDescription('The temperature on the display. Default: Room Temperature.'), + e + .numeric('window_open_check', ea.ALL) .withUnit('°C') - .withValueMin(0).withValueMax(4).withValueStep(0.5) + .withValueMin(0) + .withValueMax(4) + .withValueStep(0.5) .withDescription('The threshold to detect window open, between 1.5 and 4 in 0.5 °C. Default: 0 (disabled).'), - e.numeric('hysterersis', ea.ALL) + e + .numeric('hysterersis', ea.ALL) .withUnit('°C') - .withValueMin(0.5).withValueMax(5).withValueStep(0.1) + .withValueMin(0.5) + .withValueMax(5) + .withValueStep(0.1) .withDescription('Hysteresis setting, between 0.5 and 5 in 0.1 °C. Default: 0.5.'), e.enum('display_auto_off_enabled', ea.ALL, ['enabled', 'disabled']), - e.numeric('alarm_airtemp_overvalue', ea.ALL) + e + .numeric('alarm_airtemp_overvalue', ea.ALL) .withUnit('°C') - .withValueMin(0).withValueMax(35) - .withDescription('Floor temperature over heating threshold, range is 0-35, unit is 1ºC, ' + - '0 means this function is disabled, default value is 27.'), + .withValueMin(0) + .withValueMax(35) + .withDescription( + 'Floor temperature over heating threshold, range is 0-35, unit is 1ºC, ' + + '0 means this function is disabled, default value is 27.', + ), ], onEvent: async (type, data, device, options) => { const endpoint = device.getEndpoint(1); @@ -484,11 +618,12 @@ const definitions: Definition[] = [ const interval = setInterval(async () => { try { // Device does not asks for the time with binding, therefore we write the time every 24 hours - const time = Math.round(((new Date()).getTime() - constants.OneJanuary2000) / 1000 + ((new Date()) - .getTimezoneOffset() * -1) * 60); + const time = Math.round((new Date().getTime() - constants.OneJanuary2000) / 1000 + new Date().getTimezoneOffset() * -1 * 60); const values = {time: time}; await endpoint.write('genTime', values); - } catch (error) {/* Do nothing*/} + } catch (error) { + /* Do nothing*/ + } }, hours24); globalStore.putValue(device, 'time', interval); } @@ -496,8 +631,15 @@ const definitions: Definition[] = [ configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const binds = [ - 'genBasic', 'genIdentify', 'hvacThermostat', 'seMetering', 'haElectricalMeasurement', 'genAlarms', - 'msOccupancySensing', 'genTime', 'hvacUserInterfaceCfg', + 'genBasic', + 'genIdentify', + 'hvacThermostat', + 'seMetering', + 'haElectricalMeasurement', + 'genAlarms', + 'msOccupancySensing', + 'genTime', + 'hvacUserInterfaceCfg', ]; await reporting.bind(endpoint, coordinatorEndpoint, binds); @@ -519,157 +661,185 @@ const definitions: Definition[] = [ // OperateDisplayLcdBrightnesss await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1000, type: 0x30}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }], + [ + { + attribute: {ID: 0x1000, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ], sunricherManufacturer, ); // ButtonVibrationLevel await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1001, type: 0x30}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }], + [ + { + attribute: {ID: 0x1001, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ], sunricherManufacturer, ); // FloorSensorType await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1002, type: 0x30}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }], + [ + { + attribute: {ID: 0x1002, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ], sunricherManufacturer, ); // ControlType await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1003, type: 0x30}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }], + [ + { + attribute: {ID: 0x1003, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ], sunricherManufacturer, ); // PowerUpStatus await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1004, type: 0x30}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }], + [ + { + attribute: {ID: 0x1004, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ], sunricherManufacturer, ); // FloorSensorCalibration await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1005, type: 0x28}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 0, - }], + [ + { + attribute: {ID: 0x1005, type: 0x28}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 0, + }, + ], sunricherManufacturer, ); // DryTime await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1006, type: 0x20}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 0, - }], + [ + { + attribute: {ID: 0x1006, type: 0x20}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 0, + }, + ], sunricherManufacturer, ); // ModeAfterDry await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1007, type: 0x30}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }], + [ + { + attribute: {ID: 0x1007, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ], sunricherManufacturer, ); // TemperatureDisplay await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1008, type: 0x30}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }], + [ + { + attribute: {ID: 0x1008, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ], sunricherManufacturer, ); // WindowOpenCheck await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1009, type: 0x20}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 0, - }], + [ + { + attribute: {ID: 0x1009, type: 0x20}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 0, + }, + ], sunricherManufacturer, ); // Hysterersis await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x100A, type: 0x20}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 0, - }], + [ + { + attribute: {ID: 0x100a, type: 0x20}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 0, + }, + ], sunricherManufacturer, ); // DisplayAutoOffEnable await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x100B, type: 0x30}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }], + [ + { + attribute: {ID: 0x100b, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ], sunricherManufacturer, ); // AlarmAirTempOverValue await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x2001, type: 0x20}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 0, - }], + [ + { + attribute: {ID: 0x2001, type: 0x20}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 0, + }, + ], sunricherManufacturer, ); // Away Mode Set await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x2002, type: 0x30}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }], + [ + { + attribute: {ID: 0x2002, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ], sunricherManufacturer, ); @@ -677,7 +847,7 @@ const definitions: Definition[] = [ await endpoint.read('hvacThermostat', ['systemMode', 'runningState', 'occupiedHeatingSetpoint']); await endpoint.read('hvacThermostat', [0x1000, 0x1001, 0x1002, 0x1003], sunricherManufacturer); await endpoint.read('hvacThermostat', [0x1004, 0x1005, 0x1006, 0x1007], sunricherManufacturer); - await endpoint.read('hvacThermostat', [0x1008, 0x1009, 0x100A, 0x100B], sunricherManufacturer); + await endpoint.read('hvacThermostat', [0x1008, 0x1009, 0x100a, 0x100b], sunricherManufacturer); await endpoint.read('hvacThermostat', [0x2001, 0x2002], sunricherManufacturer); }, ota: ota.zigbeeOTA, @@ -689,10 +859,15 @@ const definitions: Definition[] = [ description: 'Multiprise with 4 AC outlets and 2 USB super charging ports (16A)', fromZigbee: [fz.on_off], toZigbee: [tz.on_off], - exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), e.switch().withEndpoint('l3'), - e.switch().withEndpoint('l4'), e.switch().withEndpoint('l5')], + exposes: [ + e.switch().withEndpoint('l1'), + e.switch().withEndpoint('l2'), + e.switch().withEndpoint('l3'), + e.switch().withEndpoint('l4'), + e.switch().withEndpoint('l5'), + ], endpoint: (device) => { - return {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4, 'l5': 5}; + return {l1: 1, l2: 2, l3: 3, l4: 4, l5: 5}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -711,10 +886,22 @@ const definitions: Definition[] = [ description: 'Panel heater 400/600/800/1000 W', ota: ota.zigbeeOTA, fromZigbee: [fz.thermostat, fz.metering, fz.electrical_measurement, fzLocal.namron_panelheater, fz.namron_hvac_user_interface], - toZigbee: [tz.thermostat_occupied_heating_setpoint, tz.thermostat_local_temperature_calibration, tz.thermostat_system_mode, - tz.thermostat_running_state, tz.thermostat_local_temperature, tzLocal.namron_panelheater, tz.namron_thermostat_child_lock], - exposes: [e.power(), e.current(), e.voltage(), e.energy(), - e.climate() + toZigbee: [ + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_local_temperature_calibration, + tz.thermostat_system_mode, + tz.thermostat_running_state, + tz.thermostat_local_temperature, + tzLocal.namron_panelheater, + tz.namron_thermostat_child_lock, + ], + exposes: [ + e.power(), + e.current(), + e.voltage(), + e.energy(), + e + .climate() .withSetpoint('occupied_heating_setpoint', 5, 35, 0.5) .withLocalTemperature() // Unit also supports Auto, but i haven't added support the scheduler yet @@ -723,27 +910,37 @@ const definitions: Definition[] = [ .withLocalTemperatureCalibration(-3, 3, 0.1) .withRunningState(['idle', 'heat']), // Namron proprietary stuff - e.binary('child_lock', ea.ALL, 'LOCK', 'UNLOCK') - .withDescription('Enables/disables physical input on the device'), - e.numeric('hysterersis', ea.ALL) + e.binary('child_lock', ea.ALL, 'LOCK', 'UNLOCK').withDescription('Enables/disables physical input on the device'), + e + .numeric('hysterersis', ea.ALL) .withUnit('°C') - .withValueMin(0.5).withValueMax(2).withValueStep(0.1) + .withValueMin(0.5) + .withValueMax(2) + .withValueStep(0.1) .withDescription('Hysteresis setting, default: 0.5'), - e.numeric('display_brightnesss', ea.ALL) - .withValueMin(1).withValueMax(7).withValueStep(1) + e + .numeric('display_brightnesss', ea.ALL) + .withValueMin(1) + .withValueMax(7) + .withValueStep(1) .withDescription('Adjust brightness of display values 1(Low)-7(High)'), - e.enum('display_auto_off', ea.ALL, ['deactivated', 'activated']) - .withDescription('Enable / Disable display auto off'), - e.enum('power_up_status', ea.ALL, ['manual', 'last_state']) + e.enum('display_auto_off', ea.ALL, ['deactivated', 'activated']).withDescription('Enable / Disable display auto off'), + e + .enum('power_up_status', ea.ALL, ['manual', 'last_state']) .withDescription('The mode after a power reset. Default: Previous Mode. See instructions for information about manual'), - e.enum('window_open_check', ea.ALL, ['enable', 'disable']) - .withDescription('Turn on/off window check mode'), + e.enum('window_open_check', ea.ALL, ['enable', 'disable']).withDescription('Turn on/off window check mode'), ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const binds = [ - 'genBasic', 'genIdentify', 'hvacThermostat', 'seMetering', 'haElectricalMeasurement', 'genAlarms', - 'genTime', 'hvacUserInterfaceCfg', + 'genBasic', + 'genIdentify', + 'hvacThermostat', + 'seMetering', + 'haElectricalMeasurement', + 'genAlarms', + 'genTime', + 'hvacUserInterfaceCfg', ]; // Reporting @@ -767,62 +964,72 @@ const definitions: Definition[] = [ // display_brightnesss await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1000, type: 0x30}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }], + [ + { + attribute: {ID: 0x1000, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ], sunricherManufacturer, ); // display_auto_off await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1001, type: 0x30}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }], + [ + { + attribute: {ID: 0x1001, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ], sunricherManufacturer, ); // power_up_status await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1004, type: 0x30}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }], + [ + { + attribute: {ID: 0x1004, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ], sunricherManufacturer, ); // window_open_check await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1009, type: 0x30}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }], + [ + { + attribute: {ID: 0x1009, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ], sunricherManufacturer, ); // hysterersis await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x100A, type: 0x20}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }], + [ + { + attribute: {ID: 0x100a, type: 0x20}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ], sunricherManufacturer, ); await endpoint.read('hvacThermostat', ['systemMode', 'runningState', 'occupiedHeatingSetpoint']); await endpoint.read('hvacUserInterfaceCfg', ['keypadLockout']); - await endpoint.read('hvacThermostat', [0x1000, 0x1001, 0x1004, 0x1009, 0x100A], sunricherManufacturer); + await endpoint.read('hvacThermostat', [0x1000, 0x1001, 0x1004, 0x1009, 0x100a], sunricherManufacturer); await reporting.bind(endpoint, coordinatorEndpoint, binds); }, @@ -975,11 +1182,31 @@ const definitions: Definition[] = [ fromZigbee: [fz.battery, fz.command_on, fz.command_off, fz.command_move, fz.command_stop], toZigbee: [], meta: {multiEndpoint: true}, - exposes: [e.battery(), e.action([ - 'on_l1', 'off_l1', 'brightness_move_up_l1', 'brightness_move_down_l1', 'brightness_stop_l1', - 'on_l2', 'off_l2', 'brightness_move_up_l2', 'brightness_move_down_l2', 'brightness_stop_l2', - 'on_l3', 'off_l3', 'brightness_move_up_l3', 'brightness_move_down_l3', 'brightness_stop_l3', - 'on_l4', 'off_l4', 'brightness_move_up_l4', 'brightness_move_down_l4', 'brightness_stop_l4'])], + exposes: [ + e.battery(), + e.action([ + 'on_l1', + 'off_l1', + 'brightness_move_up_l1', + 'brightness_move_down_l1', + 'brightness_stop_l1', + 'on_l2', + 'off_l2', + 'brightness_move_up_l2', + 'brightness_move_down_l2', + 'brightness_stop_l2', + 'on_l3', + 'off_l3', + 'brightness_move_up_l3', + 'brightness_move_down_l3', + 'brightness_stop_l3', + 'on_l4', + 'off_l4', + 'brightness_move_up_l4', + 'brightness_move_down_l4', + 'brightness_stop_l4', + ]), + ], endpoint: (device) => { return {l1: 1, l2: 2, l3: 3, l4: 4}; }, @@ -992,7 +1219,9 @@ const definitions: Definition[] = [ description: 'Zigbee 2 channel switch', fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, fz.power_on_behavior, fz.ignore_genOta], toZigbee: [tz.on_off, tz.power_on_behavior], - exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), + exposes: [ + e.switch().withEndpoint('l1'), + e.switch().withEndpoint('l2'), e.power_on_behavior(['off', 'on', 'previous']), e.energy(), e.numeric('voltage_l1', ea.STATE).withUnit('V').withDescription('Phase 1 voltage'), @@ -1000,9 +1229,10 @@ const definitions: Definition[] = [ e.numeric('current_l1', ea.STATE).withUnit('A').withDescription('Phase 1 current'), e.numeric('current_l2', ea.STATE).withUnit('A').withDescription('Phase 2 current'), e.numeric('power_l1', ea.STATE).withUnit('W').withDescription('Phase 1 power'), - e.numeric('power_l2', ea.STATE).withUnit('W').withDescription('Phase 2 power')], + e.numeric('power_l2', ea.STATE).withUnit('W').withDescription('Phase 2 power'), + ], endpoint: (device) => { - return {'l1': 1, 'l2': 2}; + return {l1: 1, l2: 2}; }, meta: {multiEndpoint: true, publishDuplicateTransaction: true, multiEndpointSkip: ['power', 'energy']}, configure: async (device, coordinatorEndpoint) => { @@ -1061,20 +1291,24 @@ const definitions: Definition[] = [ configure: tuya.configureMagicPacket, options: [], exposes: [ - e.enum('mode', ea.STATE_SET, ['regulator', 'thermostat']) + e + .enum('mode', ea.STATE_SET, ['regulator', 'thermostat']) .withDescription( 'Controls how the operating mode of the device. Possible values:' + - ' regulator (open-loop controller), thermostat (control with target temperature)', + ' regulator (open-loop controller), thermostat (control with target temperature)', ), - e.enum('regulator_period', ea.STATE_SET, ['15min', '30min', '45min', '60min', '90min']) + e + .enum('regulator_period', ea.STATE_SET, ['15min', '30min', '45min', '60min', '90min']) .withLabel('Regulator cycle duration') .withDescription('Regulator cycle duration. Not applicable when in thermostat mode.'), - e.numeric('regulator_set_point', ea.STATE_SET) + e + .numeric('regulator_set_point', ea.STATE_SET) .withUnit('%') .withDescription('Desired heating set point (%) when in regulator mode.') .withValueMin(0) .withValueMax(95), - e.climate() + e + .climate() .withSystemMode(['off', 'heat'], ea.STATE_SET, 'Whether the thermostat is turned on or off') .withPreset(['manual', 'home', 'away']) .withLocalTemperature(ea.STATE) @@ -1086,26 +1320,31 @@ const definitions: Definition[] = [ e.energy(), e.voltage(), e.temperature_sensor_select(['air_sensor', 'floor_sensor', 'both']), - e.numeric('local_temperature', ea.STATE) + e + .numeric('local_temperature', ea.STATE) .withUnit('°C') .withDescription('Current temperature measured with internal sensor') .withValueStep(1), - e.numeric('local_temperature_floor', ea.STATE) + e + .numeric('local_temperature_floor', ea.STATE) .withUnit('°C') .withDescription('Current temperature measured on the external sensor (floor)') .withValueStep(1), e.child_lock(), - e.window_detection() - .withLabel('Open window detection'), - e.numeric('hysteresis', ea.STATE_SET) + e.window_detection().withLabel('Open window detection'), + e + .numeric('hysteresis', ea.STATE_SET) .withUnit('°C') - .withDescription('The offset from the target temperature in which the temperature has to ' + - 'change for the heating state to change. This is to prevent erratically turning on/off ' + - 'when the temperature is close to the target.') + .withDescription( + 'The offset from the target temperature in which the temperature has to ' + + 'change for the heating state to change. This is to prevent erratically turning on/off ' + + 'when the temperature is close to the target.', + ) .withValueMin(1) .withValueMax(9) .withValueStep(1), - e.numeric('max_temperature_protection', ea.STATE_SET) + e + .numeric('max_temperature_protection', ea.STATE_SET) .withUnit('°C') .withDescription('Max guarding temperature') .withValueMin(20) @@ -1121,15 +1360,23 @@ const definitions: Definition[] = [ [28, 'local_temperature_calibration', tuya.valueConverter.localTempCalibration2], [30, 'child_lock', tuya.valueConverter.lockUnlock], [101, 'local_temperature_floor', tuya.valueConverter.raw], - [102, 'sensor', tuya.valueConverterBasic.lookup( - {air_sensor: tuya.enum(0), floor_sensor: tuya.enum(1), both: tuya.enum(2)})], + [102, 'sensor', tuya.valueConverterBasic.lookup({air_sensor: tuya.enum(0), floor_sensor: tuya.enum(1), both: tuya.enum(2)})], [103, 'hysteresis', tuya.valueConverter.raw], [104, 'running_state', tuya.valueConverterBasic.lookup({idle: false, heat: true})], [106, 'window_detection', tuya.valueConverter.onOff], [107, 'max_temperature_protection', tuya.valueConverter.raw], - [108, 'mode', tuya.valueConverterBasic.lookup({'regulator': tuya.enum(0), 'thermostat': tuya.enum(1)})], - [109, 'regulator_period', tuya.valueConverterBasic.lookup({ - '15min': tuya.enum(0), '30min': tuya.enum(1), '45min': tuya.enum(2), '60min': tuya.enum(3), '90min': tuya.enum(4)})], + [108, 'mode', tuya.valueConverterBasic.lookup({regulator: tuya.enum(0), thermostat: tuya.enum(1)})], + [ + 109, + 'regulator_period', + tuya.valueConverterBasic.lookup({ + '15min': tuya.enum(0), + '30min': tuya.enum(1), + '45min': tuya.enum(2), + '60min': tuya.enum(3), + '90min': tuya.enum(4), + }), + ], [110, 'regulator_set_point', tuya.valueConverter.raw], [120, 'current', tuya.valueConverter.divideBy10], [121, 'voltage', tuya.valueConverter.raw], diff --git a/src/devices/nanoleaf.ts b/src/devices/nanoleaf.ts index 25ad6ac5e893f..f1a57ab3dbf82 100644 --- a/src/devices/nanoleaf.ts +++ b/src/devices/nanoleaf.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/neo.ts b/src/devices/neo.ts index 82df0763bd55a..0121f6e631698 100644 --- a/src/devices/neo.ts +++ b/src/devices/neo.ts @@ -1,5 +1,5 @@ -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; const e = exposes.presets; const ea = exposes.access; @@ -16,10 +16,17 @@ const definitions: Definition[] = [ fromZigbee: [legacy.fz.neo_t_h_alarm, fz.ignore_basic_report, fz.ignore_tuya_set_time], toZigbee: [legacy.tz.neo_t_h_alarm], exposes: [ - e.temperature(), e.humidity(), e.binary('humidity_alarm', ea.STATE_SET, true, false), e.battery_low(), + e.temperature(), + e.humidity(), + e.binary('humidity_alarm', ea.STATE_SET, true, false), + e.battery_low(), e.binary('temperature_alarm', ea.STATE_SET, true, false), e.binary('alarm', ea.STATE_SET, true, false), - e.enum('melody', ea.STATE_SET, Array.from(Array(18).keys()).map((x)=>(x+1).toString())), + e.enum( + 'melody', + ea.STATE_SET, + Array.from(Array(18).keys()).map((x) => (x + 1).toString()), + ), e.numeric('duration', ea.STATE_SET).withUnit('s').withValueMin(0).withValueMax(1800), e.numeric('temperature_min', ea.STATE_SET).withUnit('°C').withValueMin(-20).withValueMax(80), e.numeric('temperature_max', ea.STATE_SET).withUnit('°C').withValueMin(-20).withValueMax(80), @@ -32,7 +39,7 @@ const definitions: Definition[] = [ configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await endpoint.command('manuSpecificTuya', 'dataQuery', {}); - await endpoint.command('manuSpecificTuya', 'mcuVersionRequest', {'seq': 0x0002}); + await endpoint.command('manuSpecificTuya', 'mcuVersionRequest', {seq: 0x0002}); }, }, { @@ -46,7 +53,11 @@ const definitions: Definition[] = [ exposes: [ e.battery_low(), e.binary('alarm', ea.STATE_SET, true, false), - e.enum('melody', ea.STATE_SET, Array.from(Array(18).keys()).map((x)=>(x+1).toString())), + e.enum( + 'melody', + ea.STATE_SET, + Array.from(Array(18).keys()).map((x) => (x + 1).toString()), + ), e.numeric('duration', ea.STATE_SET).withUnit('s').withValueMin(0).withValueMax(1800), e.enum('volume', ea.STATE_SET, ['low', 'medium', 'high']), e.numeric('battpercentage', ea.STATE).withUnit('%'), @@ -55,7 +66,7 @@ const definitions: Definition[] = [ configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await endpoint.command('manuSpecificTuya', 'dataQuery', {}); - await endpoint.command('manuSpecificTuya', 'mcuVersionRequest', {'seq': 0x0002}); + await endpoint.command('manuSpecificTuya', 'mcuVersionRequest', {seq: 0x0002}); }, }, { @@ -66,9 +77,15 @@ const definitions: Definition[] = [ fromZigbee: [legacy.fz.neo_nas_pd07, fz.ignore_tuya_set_time], toZigbee: [legacy.tz.neo_nas_pd07], onEvent: tuya.onEventSetTime, - exposes: [e.occupancy(), e.humidity(), e.temperature(), e.tamper(), e.battery_low(), + exposes: [ + e.occupancy(), + e.humidity(), + e.temperature(), + e.tamper(), + e.battery_low(), e.enum('power_type', ea.STATE, ['battery_full', 'battery_high', 'battery_medium', 'battery_low', 'usb']), - e.enum('alarm', ea.STATE, ['over_temperature', 'over_humidity', 'below_min_temperature', 'below_min_humdity', 'off']) + e + .enum('alarm', ea.STATE, ['over_temperature', 'over_humidity', 'below_min_temperature', 'below_min_humdity', 'off']) .withDescription('Temperature/humidity alarm status'), e.numeric('temperature_min', ea.STATE_SET).withUnit('°C').withValueMin(-20).withValueMax(80), e.numeric('temperature_max', ea.STATE_SET).withUnit('°C').withValueMin(-20).withValueMax(80), @@ -81,7 +98,7 @@ const definitions: Definition[] = [ configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await tuya.configureMagicPacket(device, coordinatorEndpoint); - await endpoint.command('manuSpecificTuya', 'mcuVersionRequest', {'seq': 0x0002}); + await endpoint.command('manuSpecificTuya', 'mcuVersionRequest', {seq: 0x0002}); }, }, { @@ -99,22 +116,37 @@ const definitions: Definition[] = [ e.binary('tamper_alarm', ea.STATE, 'ON', 'OFF').withDescription('Indicates whether the device is tampered'), e.enum('alarm_melody', ea.STATE_SET, ['melody_1', 'melody_2', 'melody_3']).withDescription('Alarm sound effect'), e.enum('alarm_mode', ea.STATE_SET, ['alarm_sound', 'alarm_light', 'alarm_sound_light']).withDescription('Alarm mode'), - e.numeric('alarm_time', ea.STATE_SET).withValueMin(1).withValueMax(60).withValueStep(1).withUnit('min') + e + .numeric('alarm_time', ea.STATE_SET) + .withValueMin(1) + .withValueMax(60) + .withValueStep(1) + .withUnit('min') .withDescription('Alarm duration in minutes'), e.binary('charging', ea.STATE, true, false).withDescription('Charging status'), e.battery(), ], meta: { tuyaDatapoints: [ - [1, 'alarm_state', tuya.valueConverterBasic.lookup({'alarm_sound': tuya.enum(0), 'alarm_light': tuya.enum(1), - 'alarm_sound_light': tuya.enum(2), 'no_alarm': tuya.enum(3)})], + [ + 1, + 'alarm_state', + tuya.valueConverterBasic.lookup({ + alarm_sound: tuya.enum(0), + alarm_light: tuya.enum(1), + alarm_sound_light: tuya.enum(2), + no_alarm: tuya.enum(3), + }), + ], [13, 'alarm_switch', tuya.valueConverter.onOff], [101, 'tamper_alarm_switch', tuya.valueConverter.onOff], [20, 'tamper_alarm', tuya.valueConverter.onOff], - [21, 'alarm_melody', tuya.valueConverterBasic.lookup({'melody_1': tuya.enum(0), 'melody_2': tuya.enum(1), - 'melody_3': tuya.enum(2)})], - [102, 'alarm_mode', tuya.valueConverterBasic.lookup({'alarm_sound': tuya.enum(0), 'alarm_light': tuya.enum(1), - 'alarm_sound_light': tuya.enum(2)})], + [21, 'alarm_melody', tuya.valueConverterBasic.lookup({melody_1: tuya.enum(0), melody_2: tuya.enum(1), melody_3: tuya.enum(2)})], + [ + 102, + 'alarm_mode', + tuya.valueConverterBasic.lookup({alarm_sound: tuya.enum(0), alarm_light: tuya.enum(1), alarm_sound_light: tuya.enum(2)}), + ], [7, 'alarm_time', tuya.valueConverter.raw], [6, 'charging', tuya.valueConverter.raw], [15, 'battery', tuya.valueConverter.raw], @@ -136,10 +168,20 @@ const definitions: Definition[] = [ e.enum('status', ea.STATE, ['off', 'auto', 'disabled']).withDescription('Status'), e.numeric('countdown', ea.STATE_SET).withUnit('min').withValueMin(1).withValueMax(240).withDescription('Countdown'), e.numeric('countdown_left', ea.STATE).withUnit('min').withValueMin(1).withValueMax(240).withDescription('Countdown left'), - e.numeric('water_current', ea.STATE).withUnit('L/min').withValueMin(0).withValueMax(3785.41).withValueStep(0.001) + e + .numeric('water_current', ea.STATE) + .withUnit('L/min') + .withValueMin(0) + .withValueMax(3785.41) + .withValueStep(0.001) .withDescription('Current water flow (L/min)'), e.numeric('battery_percentage', ea.STATE).withUnit('%').withValueMin(0).withValueMax(100).withDescription('Battery percentage'), - e.numeric('water_total', ea.STATE).withUnit('L').withValueMin(0).withValueMax(378541.0).withValueStep(0.001) + e + .numeric('water_total', ea.STATE) + .withUnit('L') + .withValueMin(0) + .withValueMax(378541.0) + .withValueStep(0.001) .withDescription('Total water flow (L)'), e.binary('fault', ea.STATE, 'DETECTED', 'NOT_DETECTED').withDescription('Fault status'), e.enum('weather_delay', ea.STATE_SET, ['24h', '48h', '72h', 'cancel']).withDescription('Weather delay'), @@ -147,7 +189,11 @@ const definitions: Definition[] = [ e.binary('switch_enabled', ea.STATE_SET, 'ON', 'OFF').withDescription('Switch enabled'), e.numeric('smart_irrigation', ea.STATE).withDescription('Smart irrigation'), e.binary('total_flow_reset_switch', ea.STATE_SET, 'ON', 'OFF').withDescription('Total flow reset switch'), - e.numeric('quantitative_watering', ea.STATE_SET).withUnit('L').withValueMin(0).withValueMax(10000) + e + .numeric('quantitative_watering', ea.STATE_SET) + .withUnit('L') + .withValueMin(0) + .withValueMax(10000) .withDescription('Quantitative watering'), e.binary('flow_switch', ea.STATE_SET, 'ON', 'OFF').withDescription('Flow switch'), e.binary('child_lock', ea.STATE_SET, 'ON', 'OFF').withDescription('Child lock'), diff --git a/src/devices/net2grid.ts b/src/devices/net2grid.ts index 24bb8a2504b73..31ce7e90d9741 100644 --- a/src/devices/net2grid.ts +++ b/src/devices/net2grid.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/netvox.ts b/src/devices/netvox.ts index 4aa7817aee8ed..14e256a4411fe 100644 --- a/src/devices/netvox.ts +++ b/src/devices/netvox.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/nexelec.ts b/src/devices/nexelec.ts index a9d6738bfa649..f1e40fb949ca8 100644 --- a/src/devices/nexelec.ts +++ b/src/devices/nexelec.ts @@ -1,12 +1,12 @@ -import {Definition} from '../lib/types'; import {temperature, humidity, co2, battery, identify} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { zigbeeModel: ['Air Quality Sensor Nexelec'], - model: 'Open\'R', + model: "Open'R", vendor: 'Nexelec', - description: 'Open\'R CO2, Temperature and Humidity sensor', + description: "Open'R CO2, Temperature and Humidity sensor", extend: [temperature(), humidity(), co2(), battery(), identify()], }, ]; diff --git a/src/devices/niko.ts b/src/devices/niko.ts index d94540af3b2a5..b124dcb40ea13 100644 --- a/src/devices/niko.ts +++ b/src/devices/niko.ts @@ -1,9 +1,9 @@ -import {Definition, Fz, Tz, KeyValue} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as utils from '../lib/utils'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition, Fz, Tz, KeyValue} from '../lib/types'; +import * as utils from '../lib/utils'; const e = exposes.presets; const ea = exposes.access; @@ -30,33 +30,36 @@ const local = { if (msg.data.hasOwnProperty('switchAction')) { // NOTE: a single press = two separate values reported, 16 followed by 64 // a hold/release cycle = three separate values, 16, 32, and 48 - const actionMap: KeyValue = (model.model == '552-721X1') ? { - 16: null, - 64: 'single', - 32: 'hold', - 48: 'release', - 256: null, - 1024: 'single_ext', - 512: 'hold_ext', - 768: 'release_ext', - } : { - 16: null, - 64: 'single_left', - 32: 'hold_left', - 48: 'release_left', - 256: null, - 1024: 'single_left_ext', - 512: 'hold_left_ext', - 768: 'release_left_ext', - 4096: null, - 16384: 'single_right', - 8192: 'hold_right', - 12288: 'release_right', - 65536: null, - 262144: 'single_right_ext', - 131072: 'hold_right_ext', - 196608: 'release_right_ext', - }; + const actionMap: KeyValue = + model.model == '552-721X1' + ? { + 16: null, + 64: 'single', + 32: 'hold', + 48: 'release', + 256: null, + 1024: 'single_ext', + 512: 'hold_ext', + 768: 'release_ext', + } + : { + 16: null, + 64: 'single_left', + 32: 'hold_left', + 48: 'release_left', + 256: null, + 1024: 'single_left_ext', + 512: 'hold_left_ext', + 768: 'release_left_ext', + 4096: null, + 16384: 'single_right', + 8192: 'hold_right', + 12288: 'release_right', + 65536: null, + 262144: 'single_right_ext', + 131072: 'hold_right_ext', + 196608: 'release_right_ext', + }; state['action'] = actionMap[msg.data.switchAction]; } @@ -69,10 +72,10 @@ const local = { convert: (model, msg, publish, options, meta) => { const state: KeyValue = {}; if (msg.data.hasOwnProperty('outletLedState')) { - state['led_enable'] = (msg.data['outletLedState'] == 1); + state['led_enable'] = msg.data['outletLedState'] == 1; } if (msg.data.hasOwnProperty('outletLedColor')) { - state['led_state'] = (msg.data['outletLedColor'] == 255 ? 'ON' : 'OFF'); + state['led_state'] = msg.data['outletLedColor'] == 255 ? 'ON' : 'OFF'; } return state; }, @@ -83,10 +86,10 @@ const local = { convert: (model, msg, publish, options, meta) => { const state: KeyValue = {}; if (msg.data.hasOwnProperty('outletChildLock')) { - state['child_lock'] = (msg.data['outletChildLock'] == 0 ? 'LOCK' : 'UNLOCK'); + state['child_lock'] = msg.data['outletChildLock'] == 0 ? 'LOCK' : 'UNLOCK'; } if (msg.data.hasOwnProperty('outletLedState')) { - state['led_enable'] = (msg.data['outletLedState'] == 1); + state['led_enable'] = msg.data['outletLedState'] == 1; } return state; }, @@ -107,7 +110,7 @@ const local = { await utils.enforceEndpoint(entity, key, meta).write( 'manuSpecificNiko1', // @ts-expect-error - {'switchOperationMode': operationModeLookup[value]}, + {switchOperationMode: operationModeLookup[value]}, ); // @ts-expect-error return {state: {operation_mode: value.toLowerCase()}}; @@ -115,18 +118,15 @@ const local = { }, convertGet: async (entity, key, meta) => { utils.assertEndpoint(entity); - await utils.enforceEndpoint(entity, key, meta).read( - 'manuSpecificNiko1', - ['switchOperationMode'], - ); + await utils.enforceEndpoint(entity, key, meta).read('manuSpecificNiko1', ['switchOperationMode']); }, } satisfies Tz.Converter, switch_led_enable: { key: ['led_enable'], convertSet: async (entity, key, value, meta) => { - await entity.write('manuSpecificNiko1', {'outletLedState': ((value) ? 1 : 0)}); + await entity.write('manuSpecificNiko1', {outletLedState: value ? 1 : 0}); await entity.read('manuSpecificNiko1', ['outletLedColor']); - return {state: {led_enable: ((value) ? true : false)}}; + return {state: {led_enable: value ? true : false}}; }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificNiko1', ['outletLedState']); @@ -136,8 +136,8 @@ const local = { key: ['led_state'], convertSet: async (entity, key, value, meta) => { utils.assertString(value, key); - await entity.write('manuSpecificNiko1', {'outletLedColor': ((value.toLowerCase() === 'off') ? 0 : 255)}); - return {state: {led_state: ((value.toLowerCase() === 'off') ? 'OFF' : 'ON')}}; + await entity.write('manuSpecificNiko1', {outletLedColor: value.toLowerCase() === 'off' ? 0 : 255}); + return {state: {led_state: value.toLowerCase() === 'off' ? 'OFF' : 'ON'}}; }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificNiko1', ['outletLedColor']); @@ -147,8 +147,8 @@ const local = { key: ['child_lock'], convertSet: async (entity, key, value, meta) => { utils.assertString(value, key); - await entity.write('manuSpecificNiko1', {'outletChildLock': ((value.toLowerCase() === 'lock') ? 0 : 1)}); - return {state: {child_lock: ((value.toLowerCase() === 'lock') ? 'LOCK' : 'UNLOCK')}}; + await entity.write('manuSpecificNiko1', {outletChildLock: value.toLowerCase() === 'lock' ? 0 : 1}); + return {state: {child_lock: value.toLowerCase() === 'lock' ? 'LOCK' : 'UNLOCK'}}; }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificNiko1', ['outletChildLock']); @@ -157,8 +157,8 @@ const local = { outlet_led_enable: { key: ['led_enable'], convertSet: async (entity, key, value, meta) => { - await entity.write('manuSpecificNiko1', {'outletLedState': ((value) ? 1 : 0)}); - return {state: {led_enable: ((value) ? true : false)}}; + await entity.write('manuSpecificNiko1', {outletLedState: value ? 1 : 0}); + return {state: {led_enable: value ? true : false}}; }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificNiko1', ['outletLedState']); @@ -174,10 +174,7 @@ const definitions: Definition[] = [ vendor: 'Niko', description: 'Connected socket outlet', fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, local.fz.outlet], - toZigbee: [ - tz.on_off, tz.electrical_measurement_power, tz.currentsummdelivered, - local.tz.outlet_child_lock, local.tz.outlet_led_enable, - ], + toZigbee: [tz.on_off, tz.electrical_measurement_power, tz.currentsummdelivered, local.tz.outlet_child_lock, local.tz.outlet_led_enable], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement', 'seMetering']); @@ -200,7 +197,9 @@ const definitions: Definition[] = [ }, exposes: [ e.switch(), - e.power().withAccess(ea.STATE_GET), e.current(), e.voltage(), + e.power().withAccess(ea.STATE_GET), + e.current(), + e.voltage(), e.energy().withAccess(ea.STATE_GET), e.binary('child_lock', ea.ALL, 'LOCK', 'UNLOCK').withDescription('Enables/disables physical input on the device'), e.binary('led_enable', ea.ALL, true, false).withDescription('Enable LED'), @@ -224,9 +223,10 @@ const definitions: Definition[] = [ await reporting.currentSummDelivered(endpoint, {min: 60, change: 1}); }, exposes: [ - e.switch(), e.power().withAccess(ea.STATE_GET), e.energy().withAccess(ea.STATE_GET), - e.enum('power_on_behavior', ea.ALL, ['off', 'previous', 'on']) - .withDescription('Controls the behaviour when the device is powered on'), + e.switch(), + e.power().withAccess(ea.STATE_GET), + e.energy().withAccess(ea.STATE_GET), + e.enum('power_on_behavior', ea.ALL, ['off', 'previous', 'on']).withDescription('Controls the behaviour when the device is powered on'), ], }, { @@ -247,9 +247,10 @@ const definitions: Definition[] = [ await reporting.currentSummDelivered(endpoint, {min: 60, change: 1}); }, exposes: [ - e.switch(), e.power().withAccess(ea.STATE_GET), e.energy().withAccess(ea.STATE_GET), - e.enum('power_on_behavior', ea.ALL, ['off', 'previous', 'on']) - .withDescription('Controls the behaviour when the device is powered on'), + e.switch(), + e.power().withAccess(ea.STATE_GET), + e.energy().withAccess(ea.STATE_GET), + e.enum('power_on_behavior', ea.ALL, ['off', 'previous', 'on']).withDescription('Controls the behaviour when the device is powered on'), ], }, { @@ -283,10 +284,7 @@ const definitions: Definition[] = [ }, exposes: [ e.switch(), - e.action([ - 'single', 'hold', 'release', - 'single_ext', 'hold_ext', 'release_ext', - ]), + e.action(['single', 'hold', 'release', 'single_ext', 'hold_ext', 'release_ext']), e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']), e.binary('led_enable', ea.ALL, true, false).withDescription('Enable LED'), e.binary('led_state', ea.ALL, 'ON', 'OFF').withDescription('LED State'), @@ -300,9 +298,9 @@ const definitions: Definition[] = [ fromZigbee: [fz.on_off, local.fz.switch_operation_mode, local.fz.switch_action, local.fz.switch_status_led], toZigbee: [tz.on_off, local.tz.switch_operation_mode, local.tz.switch_led_enable, local.tz.switch_led_state], endpoint: (device) => { - return {'l1': 1, 'l2': 2}; + return {l1: 1, l2: 2}; }, - meta: {multiEndpointEnforce: {'operation_mode': 1}, multiEndpoint: true}, + meta: {multiEndpointEnforce: {operation_mode: 1}, multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { const ep1 = device.getEndpoint(1); const ep2 = device.getEndpoint(2); @@ -314,12 +312,21 @@ const definitions: Definition[] = [ await ep2.read('manuSpecificNiko1', ['switchOperationMode', 'outletLedState', 'outletLedColor']); }, exposes: [ - e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), + e.switch().withEndpoint('l1'), + e.switch().withEndpoint('l2'), e.action([ - 'single_left', 'hold_left', 'release_left', - 'single_left_ext', 'hold_left_ext', 'release_left_ext', - 'single_right', 'hold_right', 'release_right', - 'single_right_ext', 'hold_right_ext', 'release_right_ext', + 'single_left', + 'hold_left', + 'release_left', + 'single_left_ext', + 'hold_left_ext', + 'release_left_ext', + 'single_right', + 'hold_right', + 'release_right', + 'single_right_ext', + 'hold_right_ext', + 'release_right_ext', ]), e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']), e.binary('led_enable', ea.ALL, true, false).withEndpoint('l1').withDescription('Enable LED'), @@ -391,8 +398,18 @@ const definitions: Definition[] = [ await reporting.bind(ep2, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl']); }, exposes: [ - e.action(['on_left', 'off_left', 'on_right', 'off_right', 'brightness_move_up_left', 'brightness_move_up_right', - 'brightness_move_down_left', 'brightness_move_down_right', 'brightness_stop_left', 'brightness_stop_right']), + e.action([ + 'on_left', + 'off_left', + 'on_right', + 'off_right', + 'brightness_move_up_left', + 'brightness_move_up_right', + 'brightness_move_down_left', + 'brightness_move_down_right', + 'brightness_stop_left', + 'brightness_stop_right', + ]), e.battery(), ], }, @@ -419,11 +436,28 @@ const definitions: Definition[] = [ await reporting.bind(ep4, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl']); }, exposes: [ - e.action(['on_top_left', 'off_top_left', 'on_bottom_left', 'off_bottom_left', 'on_top_right', 'off_top_right', - 'on_bottom_right', 'off_bottom_right', 'brightness_move_up_top_left', 'brightness_move_up_bottom_left', - 'brightness_move_up_top_right', 'brightness_move_up_bottom_right', 'brightness_move_down_top_left', - 'brightness_move_down_bottom_left', 'brightness_move_down_top_right', 'brightness_move_down_bottom_right', - 'brightness_stop_top_left', 'brightness_stop_bottom_left', 'brightness_stop_top_right', 'brightness_stop_bottom_right']), + e.action([ + 'on_top_left', + 'off_top_left', + 'on_bottom_left', + 'off_bottom_left', + 'on_top_right', + 'off_top_right', + 'on_bottom_right', + 'off_bottom_right', + 'brightness_move_up_top_left', + 'brightness_move_up_bottom_left', + 'brightness_move_up_top_right', + 'brightness_move_up_bottom_right', + 'brightness_move_down_top_left', + 'brightness_move_down_bottom_left', + 'brightness_move_down_top_right', + 'brightness_move_down_bottom_right', + 'brightness_stop_top_left', + 'brightness_stop_bottom_left', + 'brightness_stop_top_right', + 'brightness_stop_bottom_right', + ]), e.battery(), ], }, diff --git a/src/devices/ninja_blocks.ts b/src/devices/ninja_blocks.ts index 8066c22a7ed07..b69886a2f7be9 100644 --- a/src/devices/ninja_blocks.ts +++ b/src/devices/ninja_blocks.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/niviss.ts b/src/devices/niviss.ts index b1280508e2bdb..a7ccff0329ae0 100644 --- a/src/devices/niviss.ts +++ b/src/devices/niviss.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/nodon.ts b/src/devices/nodon.ts index ddea594a2fc7d..ba951074c6081 100644 --- a/src/devices/nodon.ts +++ b/src/devices/nodon.ts @@ -1,72 +1,79 @@ import {Zcl} from 'zigbee-herdsman'; -import {Definition} from '../lib/types'; + import * as exposes from '../lib/exposes'; -import * as reporting from '../lib/reporting'; +import {battery, deviceEndpoints, humidity, numeric, NumericArgs, onOff, temperature, windowCovering} from '../lib/modernExtend'; import * as ota from '../lib/ota'; -import { - battery, deviceEndpoints, humidity, numeric, NumericArgs, onOff, temperature, windowCovering, -} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; -import tz from '../converters/toZigbee'; import fz from '../converters/fromZigbee'; +import tz from '../converters/toZigbee'; const nodonModernExtend = { - calibrationVerticalRunTimeUp: (args?: Partial) => numeric({ - name: 'calibration_vertical_run_time_up', - unit: '10 ms', - cluster: 'closuresWindowCovering', - attribute: {ID: 0x0001, type: Zcl.DataType.UINT16}, - valueMin: 0, - valueMax: 65535, - scale: 1, - access: 'ALL', - description: 'Manuel calibration: Set vertical run time up of the roller shutter. ' + - 'Do not change it if your roller shutter is already calibrated.', - zigbeeCommandOptions: {manufacturerCode: 0x128B}, - ...args, - }), - calibrationVerticalRunTimeDowm: (args?: Partial) => numeric({ - name: 'calibration_vertical_run_time_down', - unit: '10 ms', - cluster: 'closuresWindowCovering', - attribute: {ID: 0x0002, type: Zcl.DataType.UINT16}, - valueMin: 0, - valueMax: 65535, - scale: 1, - access: 'ALL', - description: 'Manuel calibration: Set vertical run time down of the roller shutter. ' + - 'Do not change it if your roller shutter is already calibrated.', - zigbeeCommandOptions: {manufacturerCode: 0x128B}, - ...args, - }), - calibrationRotationRunTimeUp: (args?: Partial) => numeric({ - name: 'calibration_rotation_run_time_up', - unit: 'ms', - cluster: 'closuresWindowCovering', - attribute: {ID: 0x0003, type: Zcl.DataType.UINT16}, - valueMin: 0, - valueMax: 65535, - scale: 1, - access: 'ALL', - description: 'Manuel calibration: Set rotation run time up of the roller shutter. ' + - 'Do not change it if your roller shutter is already calibrated.', - zigbeeCommandOptions: {manufacturerCode: 0x128B}, - ...args, - }), - calibrationRotationRunTimeDown: (args?: Partial) => numeric({ - name: 'calibration_rotation_run_time_down', - unit: 'ms', - cluster: 'closuresWindowCovering', - attribute: {ID: 0x0004, type: Zcl.DataType.UINT16}, - valueMin: 0, - valueMax: 65535, - scale: 1, - access: 'ALL', - description: 'Manuel calibration: Set rotation run time down of the roller shutter. ' + - 'Do not change it if your roller shutter is already calibrated.', - zigbeeCommandOptions: {manufacturerCode: 0x128B}, - ...args, - }), + calibrationVerticalRunTimeUp: (args?: Partial) => + numeric({ + name: 'calibration_vertical_run_time_up', + unit: '10 ms', + cluster: 'closuresWindowCovering', + attribute: {ID: 0x0001, type: Zcl.DataType.UINT16}, + valueMin: 0, + valueMax: 65535, + scale: 1, + access: 'ALL', + description: + 'Manuel calibration: Set vertical run time up of the roller shutter. ' + + 'Do not change it if your roller shutter is already calibrated.', + zigbeeCommandOptions: {manufacturerCode: 0x128b}, + ...args, + }), + calibrationVerticalRunTimeDowm: (args?: Partial) => + numeric({ + name: 'calibration_vertical_run_time_down', + unit: '10 ms', + cluster: 'closuresWindowCovering', + attribute: {ID: 0x0002, type: Zcl.DataType.UINT16}, + valueMin: 0, + valueMax: 65535, + scale: 1, + access: 'ALL', + description: + 'Manuel calibration: Set vertical run time down of the roller shutter. ' + + 'Do not change it if your roller shutter is already calibrated.', + zigbeeCommandOptions: {manufacturerCode: 0x128b}, + ...args, + }), + calibrationRotationRunTimeUp: (args?: Partial) => + numeric({ + name: 'calibration_rotation_run_time_up', + unit: 'ms', + cluster: 'closuresWindowCovering', + attribute: {ID: 0x0003, type: Zcl.DataType.UINT16}, + valueMin: 0, + valueMax: 65535, + scale: 1, + access: 'ALL', + description: + 'Manuel calibration: Set rotation run time up of the roller shutter. ' + + 'Do not change it if your roller shutter is already calibrated.', + zigbeeCommandOptions: {manufacturerCode: 0x128b}, + ...args, + }), + calibrationRotationRunTimeDown: (args?: Partial) => + numeric({ + name: 'calibration_rotation_run_time_down', + unit: 'ms', + cluster: 'closuresWindowCovering', + attribute: {ID: 0x0004, type: Zcl.DataType.UINT16}, + valueMin: 0, + valueMax: 65535, + scale: 1, + access: 'ALL', + description: + 'Manuel calibration: Set rotation run time down of the roller shutter. ' + + 'Do not change it if your roller shutter is already calibrated.', + zigbeeCommandOptions: {manufacturerCode: 0x128b}, + ...args, + }), }; const definitions: Definition[] = [ @@ -164,10 +171,7 @@ const definitions: Definition[] = [ model: 'SIN-4-2-20', vendor: 'NodOn', description: 'Lighting relay switch', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2}}), - onOff({endpointNames: ['l1', 'l2']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2}}), onOff({endpointNames: ['l1', 'l2']})], ota: ota.zigbeeOTA, }, { @@ -175,10 +179,7 @@ const definitions: Definition[] = [ model: 'SIN-4-2-20_PRO', vendor: 'NodOn', description: 'Lighting relay switch', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2}}), - onOff({endpointNames: ['l1', 'l2']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2}}), onOff({endpointNames: ['l1', 'l2']})], ota: ota.zigbeeOTA, }, { diff --git a/src/devices/nordtronic.ts b/src/devices/nordtronic.ts index a37e427871258..dd165d83f9cda 100644 --- a/src/devices/nordtronic.ts +++ b/src/devices/nordtronic.ts @@ -1,14 +1,5 @@ +import {light, onOff, electricityMeter, battery, identify, commandsOnOff, commandsLevelCtrl, commandsColorCtrl} from '../lib/modernExtend'; import {Definition} from '../lib/types'; -import { - light, - onOff, - electricityMeter, - battery, - identify, - commandsOnOff, - commandsLevelCtrl, - commandsColorCtrl, -} from '../lib/modernExtend'; const definitions: Definition[] = [ { diff --git a/src/devices/nous.ts b/src/devices/nous.ts index c845bc968207b..14537d94a4e78 100644 --- a/src/devices/nous.ts +++ b/src/devices/nous.ts @@ -1,8 +1,8 @@ -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; -import * as tuya from '../lib/tuya'; import * as reporting from '../lib/reporting'; +import * as tuya from '../lib/tuya'; import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -18,17 +18,17 @@ const definitions: Definition[] = [ exposes: [e.temperature(), e.humidity(), e.battery()], }, { - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_lve3dvpy'}, + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE200_lve3dvpy'}, {modelID: 'TS0601', manufacturerName: '_TZE200_c7emyjom'}, {modelID: 'TS0601', manufacturerName: '_TZE200_locansqn'}, {modelID: 'TS0601', manufacturerName: '_TZE200_qrztc3ev'}, {modelID: 'TS0601', manufacturerName: '_TZE200_snloy4rw'}, - {modelID: 'TS0601', manufacturerName: '_TZE200_eanjj2pa'}], + {modelID: 'TS0601', manufacturerName: '_TZE200_eanjj2pa'}, + ], model: 'SZ-T04', vendor: 'Nous', - whiteLabel: [ - tuya.whitelabel('Tuya', 'TH01Z', 'Temperature and humidity sensor with clock', ['_TZE200_locansqn']), - ], + whiteLabel: [tuya.whitelabel('Tuya', 'TH01Z', 'Temperature and humidity sensor with clock', ['_TZE200_locansqn'])], description: 'Temperature and humidity sensor with clock', fromZigbee: [legacy.fz.nous_lcd_temperature_humidity_sensor, fz.ignore_tuya_set_time], toZigbee: [legacy.tz.nous_lcd_temperature_humidity_sensor], @@ -38,27 +38,43 @@ const definitions: Definition[] = [ await reporting.bind(endpoint, coordinatorEndpoint, ['genBasic']); }, exposes: [ - e.temperature(), e.humidity(), e.battery(), - e.numeric('temperature_report_interval', ea.STATE_SET).withUnit('min').withValueMin(5).withValueMax(120).withValueStep(5) + e.temperature(), + e.humidity(), + e.battery(), + e + .numeric('temperature_report_interval', ea.STATE_SET) + .withUnit('min') + .withValueMin(5) + .withValueMax(120) + .withValueStep(5) .withDescription('Temperature Report interval'), - e.numeric('humidity_report_interval', ea.STATE_SET).withUnit('min').withValueMin(5).withValueMax(120).withValueStep(5) + e + .numeric('humidity_report_interval', ea.STATE_SET) + .withUnit('min') + .withValueMin(5) + .withValueMax(120) + .withValueStep(5) .withDescription('Humidity Report interval'), e.enum('temperature_unit_convert', ea.STATE_SET, ['celsius', 'fahrenheit']).withDescription('Current display unit'), - e.enum('temperature_alarm', ea.STATE, ['canceled', 'lower_alarm', 'upper_alarm']) - .withDescription('Temperature alarm status'), - e.numeric('max_temperature', ea.STATE_SET).withUnit('°C').withValueMin(-20).withValueMax(60) - .withDescription('Alarm temperature max'), - e.numeric('min_temperature', ea.STATE_SET).withUnit('°C').withValueMin(-20).withValueMax(60) - .withDescription('Alarm temperature min'), - e.numeric('temperature_sensitivity', ea.STATE_SET).withUnit('°C').withValueMin(0.1).withValueMax(50).withValueStep(0.1) + e.enum('temperature_alarm', ea.STATE, ['canceled', 'lower_alarm', 'upper_alarm']).withDescription('Temperature alarm status'), + e.numeric('max_temperature', ea.STATE_SET).withUnit('°C').withValueMin(-20).withValueMax(60).withDescription('Alarm temperature max'), + e.numeric('min_temperature', ea.STATE_SET).withUnit('°C').withValueMin(-20).withValueMax(60).withDescription('Alarm temperature min'), + e + .numeric('temperature_sensitivity', ea.STATE_SET) + .withUnit('°C') + .withValueMin(0.1) + .withValueMax(50) + .withValueStep(0.1) .withDescription('Temperature sensitivity'), - e.enum('humidity_alarm', ea.STATE, ['canceled', 'lower_alarm', 'upper_alarm']) - .withDescription('Humidity alarm status'), - e.numeric('max_humidity', ea.STATE_SET).withUnit('%').withValueMin(0).withValueMax(100) - .withDescription('Alarm humidity max'), - e.numeric('min_humidity', ea.STATE_SET).withUnit('%').withValueMin(0).withValueMax(100) - .withDescription('Alarm humidity min'), - e.numeric('humidity_sensitivity', ea.STATE_SET).withUnit('%').withValueMin(1).withValueMax(100).withValueStep(1) + e.enum('humidity_alarm', ea.STATE, ['canceled', 'lower_alarm', 'upper_alarm']).withDescription('Humidity alarm status'), + e.numeric('max_humidity', ea.STATE_SET).withUnit('%').withValueMin(0).withValueMax(100).withDescription('Alarm humidity max'), + e.numeric('min_humidity', ea.STATE_SET).withUnit('%').withValueMin(0).withValueMax(100).withDescription('Alarm humidity min'), + e + .numeric('humidity_sensitivity', ea.STATE_SET) + .withUnit('%') + .withValueMin(1) + .withValueMax(100) + .withValueStep(1) .withDescription('Humidity sensitivity'), ], }, @@ -79,16 +95,15 @@ const definitions: Definition[] = [ e.humidity(), e.battery(), e.enum('temperature_unit_convert', ea.STATE_SET, ['celsius', 'fahrenheit']).withDescription('Current display unit'), - e.enum('temperature_alarm', ea.STATE, ['canceled', 'lower_alarm', 'upper_alarm']) - .withDescription('Temperature alarm status'), - e.numeric('max_temperature', ea.STATE_SET) - .withUnit('°C').withValueMin(-20).withValueMax(60) - .withDescription('Alarm temperature max'), - e.numeric('min_temperature', ea.STATE_SET).withUnit('°C') - .withValueMin(-20).withValueMax(60) - .withDescription('Alarm temperature min'), - e.numeric('temperature_sensitivity', ea.STATE_SET) - .withUnit('°C').withValueMin(0.1).withValueMax(50).withValueStep(0.1) + e.enum('temperature_alarm', ea.STATE, ['canceled', 'lower_alarm', 'upper_alarm']).withDescription('Temperature alarm status'), + e.numeric('max_temperature', ea.STATE_SET).withUnit('°C').withValueMin(-20).withValueMax(60).withDescription('Alarm temperature max'), + e.numeric('min_temperature', ea.STATE_SET).withUnit('°C').withValueMin(-20).withValueMax(60).withDescription('Alarm temperature min'), + e + .numeric('temperature_sensitivity', ea.STATE_SET) + .withUnit('°C') + .withValueMin(0.1) + .withValueMax(50) + .withValueStep(0.1) .withDescription('Temperature sensitivity'), ], }, diff --git a/src/devices/novo.ts b/src/devices/novo.ts index 29327bf859ebb..0764d23bae0a3 100644 --- a/src/devices/novo.ts +++ b/src/devices/novo.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; diff --git a/src/devices/nue_3a.ts b/src/devices/nue_3a.ts index c1ccd24b9f80e..d6acb2b59fb35 100644 --- a/src/devices/nue_3a.ts +++ b/src/devices/nue_3a.ts @@ -1,11 +1,11 @@ -import {Definition, Fz, KeyValue} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; +import {deviceEndpoints, forcePowerSource, light, onOff} from '../lib/modernExtend'; import * as reporting from '../lib/reporting'; +import {Definition, Fz, KeyValue} from '../lib/types'; import * as utils from '../lib/utils'; -import {deviceEndpoints, forcePowerSource, light, onOff} from '../lib/modernExtend'; const e = exposes.presets; const ea = exposes.access; @@ -42,18 +42,17 @@ const definitions: Definition[] = [ vendor: 'Nue / 3A', description: 'Smart Zigbee 3.0 light controller', extend: [onOff({powerOnBehavior: false}), forcePowerSource({powerSource: 'Mains (single phase)'})], - whiteLabel: [{vendor: 'Zemismart', model: 'ZW-EU-01', description: 'Smart light relay - 1 gang'}, - {vendor: 'Moes', model: 'ZK-CH-2U', description: 'Plug with 2 USB ports'}], + whiteLabel: [ + {vendor: 'Zemismart', model: 'ZW-EU-01', description: 'Smart light relay - 1 gang'}, + {vendor: 'Moes', model: 'ZK-CH-2U', description: 'Plug with 2 USB ports'}, + ], }, { zigbeeModel: ['LXN59-2S7LX1.0'], model: 'LXN59-2S7LX1.0', vendor: 'Nue / 3A', description: 'Smart light relay - 2 gang', - extend: [ - deviceEndpoints({endpoints: {'left': 1, 'right': 2}}), - onOff({endpointNames: ['left', 'right']}), - ], + extend: [deviceEndpoints({endpoints: {left: 1, right: 2}}), onOff({endpointNames: ['left', 'right']})], whiteLabel: [{vendor: 'Zemismart', model: 'ZW-EU-02'}], }, { @@ -95,30 +94,21 @@ const definitions: Definition[] = [ model: 'HGZB-43', vendor: 'Nue / 3A', description: 'Smart light switch - 3 gang v2.0', - extend: [ - deviceEndpoints({endpoints: {'top': 1, 'center': 2, 'bottom': 3}}), - onOff({endpointNames: ['top', 'center', 'bottom']}), - ], + extend: [deviceEndpoints({endpoints: {top: 1, center: 2, bottom: 3}}), onOff({endpointNames: ['top', 'center', 'bottom']})], }, { zigbeeModel: ['LXN-4S27LX1.0'], model: 'HGZB-4S', vendor: 'Nue / 3A', description: 'Smart light switch - 4 gang v2.0', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4}}), - onOff({endpointNames: ['l1', 'l2', 'l3', 'l4']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2, l3: 3, l4: 4}}), onOff({endpointNames: ['l1', 'l2', 'l3', 'l4']})], }, { zigbeeModel: ['FB56+ZSW1IKJ1.7', 'FB56+ZSW1IKJ2.5', 'FB56+ZSW1IKJ2.7'], model: 'HGZB-043', vendor: 'Nue / 3A', description: 'Smart light switch - 3 gang', - extend: [ - deviceEndpoints({endpoints: {'top': 16, 'center': 17, 'bottom': 18}}), - onOff({endpointNames: ['top', 'center', 'bottom']}), - ], + extend: [deviceEndpoints({endpoints: {top: 16, center: 17, bottom: 18}}), onOff({endpointNames: ['top', 'center', 'bottom']})], }, { zigbeeModel: ['FB56+ZSW1JKJ2.7'], @@ -126,7 +116,7 @@ const definitions: Definition[] = [ vendor: 'Nue / 3A', description: 'Smart light switch - 4 gang v2.0', extend: [ - deviceEndpoints({endpoints: {'top_left': 16, 'top_right': 17, 'bottom_right': 18, 'bottom_left': 19}}), + deviceEndpoints({endpoints: {top_left: 16, top_right: 17, bottom_right: 18, bottom_left: 19}}), onOff({endpointNames: ['top_left', 'top_right', 'bottom_right', 'bottom_left']}), ], }, @@ -143,26 +133,25 @@ const definitions: Definition[] = [ model: 'HGZB-042', vendor: 'Nue / 3A', description: 'Smart light switch - 2 gang', - extend: [ - deviceEndpoints({endpoints: {'top': 16, 'bottom': 17}}), - onOff({endpointNames: ['top', 'bottom']}), - ], + extend: [deviceEndpoints({endpoints: {top: 16, bottom: 17}}), onOff({endpointNames: ['top', 'bottom']})], }, { fingerprint: [ - {type: 'Router', manufacturerName: '3A Smart Home DE', modelID: 'LXN-2S27LX1.0', endpoints: [ - {ID: 11, profileID: 49246, deviceID: 0, inputClusters: [0, 4, 3, 6, 5, 4096, 8], outputClusters: [25]}, - {ID: 12, profileID: 49246, deviceID: 0, inputClusters: [0, 4, 3, 6, 5, 8], outputClusters: [25]}, - ]}, + { + type: 'Router', + manufacturerName: '3A Smart Home DE', + modelID: 'LXN-2S27LX1.0', + endpoints: [ + {ID: 11, profileID: 49246, deviceID: 0, inputClusters: [0, 4, 3, 6, 5, 4096, 8], outputClusters: [25]}, + {ID: 12, profileID: 49246, deviceID: 0, inputClusters: [0, 4, 3, 6, 5, 8], outputClusters: [25]}, + ], + }, ], zigbeeModel: ['FNB56-ZSW02LX2.0'], model: 'HGZB-42', vendor: 'Nue / 3A', description: 'Smart light switch - 2 gang v2.0', - extend: [ - deviceEndpoints({endpoints: {'top': 11, 'bottom': 12}}), - onOff({endpointNames: ['top', 'bottom'], configureReporting: false}), - ], + extend: [deviceEndpoints({endpoints: {top: 11, bottom: 12}}), onOff({endpointNames: ['top', 'bottom'], configureReporting: false})], configure: async (device, coordinatorEndpoint) => { // ConfigureReporting for onOff fails // https://github.com/Koenkk/zigbee2mqtt/issues/20867 @@ -189,20 +178,14 @@ const definitions: Definition[] = [ model: 'MG-AUWS01', vendor: 'Nue / 3A', description: 'Smart Double GPO', - extend: [ - deviceEndpoints({endpoints: {'left': 11, 'right': 12}}), - onOff({endpointNames: ['left', 'right']}), - ], + extend: [deviceEndpoints({endpoints: {left: 11, right: 12}}), onOff({endpointNames: ['left', 'right']})], }, { zigbeeModel: ['LXN56-TS27LX1.2'], model: 'LXN56-TS27LX1.2', vendor: 'Nue / 3A', description: 'Smart double GPO', - extend: [ - deviceEndpoints({endpoints: {'left': 1, 'right': 2}}), - onOff({endpointNames: ['left', 'right']}), - ], + extend: [deviceEndpoints({endpoints: {left: 1, right: 2}}), onOff({endpointNames: ['left', 'right']})], }, { @@ -314,18 +297,20 @@ const definitions: Definition[] = [ }, { fingerprint: [ - {type: 'Router', manufacturerName: '3A Smart Home DE', modelID: 'LXN-2S27LX1.0', endpoints: [ - {ID: 1, profileID: 49246, deviceID: 0, inputClusters: [0, 4, 3, 6, 5, 4096, 8], outputClusters: [0]}, - {ID: 2, profileID: 49246, deviceID: 0, inputClusters: [0, 4, 3, 6, 5, 4096, 8], outputClusters: [0]}, - ]}, + { + type: 'Router', + manufacturerName: '3A Smart Home DE', + modelID: 'LXN-2S27LX1.0', + endpoints: [ + {ID: 1, profileID: 49246, deviceID: 0, inputClusters: [0, 4, 3, 6, 5, 4096, 8], outputClusters: [0]}, + {ID: 2, profileID: 49246, deviceID: 0, inputClusters: [0, 4, 3, 6, 5, 4096, 8], outputClusters: [0]}, + ], + }, ], model: 'NUE-AUWZO2', vendor: 'Nue / 3A', description: 'Smart Zigbee double power point', - extend: [ - deviceEndpoints({endpoints: {'left': 1, 'right': 2}}), - onOff({endpointNames: ['left', 'right']}), - ], + extend: [deviceEndpoints({endpoints: {left: 1, right: 2}}), onOff({endpointNames: ['left', 'right']})], }, { zigbeeModel: ['LXN56-1S27LX1.2', 'LXX60-FN27LX1.0'], @@ -333,7 +318,7 @@ const definitions: Definition[] = [ vendor: 'Nue / 3A', description: 'Smart fan light switch', extend: [ - deviceEndpoints({endpoints: {'button_light': 1, 'button_fan_high': 2, 'button_fan_med': 3, 'button_fan_low': 4}}), + deviceEndpoints({endpoints: {button_light: 1, button_fan_high: 2, button_fan_med: 3, button_fan_low: 4}}), onOff({endpointNames: ['button_light', 'button_fan_high', 'button_fan_med', 'button_fan_low']}), forcePowerSource({powerSource: 'Mains (single phase)'}), ], diff --git a/src/devices/nyce.ts b/src/devices/nyce.ts index e98a48ad10149..80b35077f3b62 100644 --- a/src/devices/nyce.ts +++ b/src/devices/nyce.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ @@ -38,8 +38,17 @@ const definitions: Definition[] = [ model: 'NCZ-3043-HA', vendor: 'Nyce', description: 'Ceiling motion sensor', - fromZigbee: [fz.occupancy, fz.humidity, fz.temperature, fz.ignore_basic_report, fz.ignore_genIdentify, fz.ignore_poll_ctrl, - fz.battery, fz.ignore_iaszone_report, fz.ias_occupancy_alarm_2], + fromZigbee: [ + fz.occupancy, + fz.humidity, + fz.temperature, + fz.ignore_basic_report, + fz.ignore_genIdentify, + fz.ignore_poll_ctrl, + fz.battery, + fz.ignore_iaszone_report, + fz.ias_occupancy_alarm_2, + ], toZigbee: [], exposes: [e.occupancy(), e.humidity(), e.temperature(), e.battery(), e.battery_low(), e.tamper()], }, @@ -48,8 +57,17 @@ const definitions: Definition[] = [ model: 'NCZ-3041-HA', vendor: 'Nyce', description: 'Wall motion sensor', - fromZigbee: [fz.occupancy, fz.humidity, fz.temperature, fz.ignore_basic_report, fz.ignore_genIdentify, fz.ignore_poll_ctrl, - fz.battery, fz.ignore_iaszone_report, fz.ias_occupancy_alarm_2], + fromZigbee: [ + fz.occupancy, + fz.humidity, + fz.temperature, + fz.ignore_basic_report, + fz.ignore_genIdentify, + fz.ignore_poll_ctrl, + fz.battery, + fz.ignore_iaszone_report, + fz.ias_occupancy_alarm_2, + ], toZigbee: [], meta: {battery: {dontDividePercentage: true}}, exposes: [e.occupancy(), e.humidity(), e.temperature(), e.battery(), e.battery_low(), e.tamper()], @@ -59,8 +77,17 @@ const definitions: Definition[] = [ model: 'NCZ-3045-HA', vendor: 'Nyce', description: 'Curtain motion sensor', - fromZigbee: [fz.occupancy, fz.humidity, fz.temperature, fz.ignore_basic_report, fz.ignore_genIdentify, fz.ignore_poll_ctrl, - fz.battery, fz.ignore_iaszone_report, fz.ias_occupancy_alarm_2], + fromZigbee: [ + fz.occupancy, + fz.humidity, + fz.temperature, + fz.ignore_basic_report, + fz.ignore_genIdentify, + fz.ignore_poll_ctrl, + fz.battery, + fz.ignore_iaszone_report, + fz.ias_occupancy_alarm_2, + ], toZigbee: [], meta: {battery: {dontDividePercentage: true}}, exposes: [e.occupancy(), e.humidity(), e.temperature(), e.battery(), e.battery_low(), e.tamper()], diff --git a/src/devices/onenuo.ts b/src/devices/onenuo.ts index 4089539d4e59f..fa25e00b8f7f4 100644 --- a/src/devices/onenuo.ts +++ b/src/devices/onenuo.ts @@ -15,28 +15,36 @@ const definitions: Definition[] = [ toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, exposes: [ - e.smoke(), e.battery(), tuya.exposes.silence(), tuya.exposes.selfTestResult(), - e.enum('smoke_state', ea.STATE, ['alarm', 'normal', 'detecting', 'unknown']).withLabel('Smoke state') + e.smoke(), + e.battery(), + tuya.exposes.silence(), + tuya.exposes.selfTestResult(), + e + .enum('smoke_state', ea.STATE, ['alarm', 'normal', 'detecting', 'unknown']) + .withLabel('Smoke state') .withDescription('Possible states: alarm, normal, detecting, unknown'), - e.enum('sensitivity', ea.STATE_SET, ['low', 'medium', 'high']).withLabel('Sensitivity') - .withDescription('Smoke detection sensitivity'), + e.enum('sensitivity', ea.STATE_SET, ['low', 'medium', 'high']).withLabel('Sensitivity').withDescription('Smoke detection sensitivity'), ], meta: { tuyaDatapoints: [ - [1, null, { - from: (v: number) => { - const lookup = {'alarm': tuya.enum(0), 'normal': tuya.enum(1), 'detecting': tuya.enum(2), 'unknown': tuya.enum(3)}; - const smokeState = Object.entries(lookup).find((i) => i[1].valueOf() === v)[0]; - return { - 'smoke': (smokeState === 'alarm'), - 'smoke_state': smokeState, - }; + [ + 1, + null, + { + from: (v: number) => { + const lookup = {alarm: tuya.enum(0), normal: tuya.enum(1), detecting: tuya.enum(2), unknown: tuya.enum(3)}; + const smokeState = Object.entries(lookup).find((i) => i[1].valueOf() === v)[0]; + return { + smoke: smokeState === 'alarm', + smoke_state: smokeState, + }; + }, }, - }], + ], [15, 'battery', tuya.valueConverter.raw], [16, 'silence', tuya.valueConverter.raw], - [101, 'self_test_result', tuya.valueConverterBasic.lookup({'failure': false, 'success': true})], - [102, 'sensitivity', tuya.valueConverterBasic.lookup({'low': tuya.enum(0), 'medium': tuya.enum(1), 'high': tuya.enum(2)})], + [101, 'self_test_result', tuya.valueConverterBasic.lookup({failure: false, success: true})], + [102, 'sensitivity', tuya.valueConverterBasic.lookup({low: tuya.enum(0), medium: tuya.enum(1), high: tuya.enum(2)})], ], }, }, diff --git a/src/devices/onesti.ts b/src/devices/onesti.ts index 54d65bde24d91..08848c28974a9 100644 --- a/src/devices/onesti.ts +++ b/src/devices/onesti.ts @@ -1,7 +1,8 @@ -import * as exposes from '../lib/exposes'; +import {Definition, Fz, KeyValue} from 'src/lib/types'; + import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import {Definition, Fz, KeyValue} from 'src/lib/types'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; const e = exposes.presets; const ea = exposes.access; @@ -27,14 +28,14 @@ const fzLocal = { if (msg.data['256'] !== undefined) { const hex = msg.data['256'].toString(16).padStart(8, '0'); const firstOctet = String(hex.substring(0, 2)); - const lookup: { [key: string]: string } = { + const lookup: {[key: string]: string} = { '00': 'zigbee', '02': 'keypad', '03': 'fingerprintsensor', '04': 'rfid', '0a': 'self', }; - result.last_action_source = lookup[firstOctet]||'unknown'; + result.last_action_source = lookup[firstOctet] || 'unknown'; const secondOctet = hex.substring(2, 4); const thirdOctet = hex.substring(4, 8); result.last_action_user = parseInt(thirdOctet, 16); @@ -55,15 +56,20 @@ const fzLocal = { } satisfies Fz.Converter, }; - const definitions: Definition[] = [ { zigbeeModel: ['easyCodeTouch_v1', 'EasyCodeTouch', 'EasyFingerTouch'], model: 'easyCodeTouch_v1', vendor: 'Onesti Products AS', description: 'Zigbee module for EasyAccess code touch series', - fromZigbee: [fzLocal.nimly_pro_lock_actions, fz.lock, fz.lock_operation_event, fz.battery, fz.lock_programming_event, - fz.easycodetouch_action], + fromZigbee: [ + fzLocal.nimly_pro_lock_actions, + fz.lock, + fz.lock_operation_event, + fz.battery, + fz.lock_programming_event, + fz.easycodetouch_action, + ], toZigbee: [tz.lock, tz.easycode_auto_relock, tz.lock_sound_volume, tz.pincode_lock], meta: {pinCodeCount: 1000}, configure: async (device, coordinatorEndpoint) => { @@ -75,12 +81,17 @@ const definitions: Definition[] = [ device.powerSource = 'Battery'; device.save(); }, - exposes: [e.lock(), e.battery(), e.sound_volume(), - e.enum('last_unlock_source', ea.STATE, ['zigbee', 'keypad', 'fingerprintsensor', 'rfid', - 'self', 'unknown']).withDescription('Last unlock source'), + exposes: [ + e.lock(), + e.battery(), + e.sound_volume(), + e + .enum('last_unlock_source', ea.STATE, ['zigbee', 'keypad', 'fingerprintsensor', 'rfid', 'self', 'unknown']) + .withDescription('Last unlock source'), e.text('last_unlock_user', ea.STATE).withDescription('Last unlock user').withDescription('Last unlock user'), - e.enum('last_lock_source', ea.STATE, ['zigbee', 'keypad', 'fingerprintsensor', 'rfid', - 'self', 'unknown']).withDescription('Last lock source'), + e + .enum('last_lock_source', ea.STATE, ['zigbee', 'keypad', 'fingerprintsensor', 'rfid', 'self', 'unknown']) + .withDescription('Last lock source'), e.text('last_lock_user', ea.STATE).withDescription('Last lock user'), e.text('last_used_pin_code', ea.STATE).withDescription('Last used pin code'), e.binary('auto_relock', ea.STATE_SET, true, false).withDescription('Auto relock after 7 seconds.'), @@ -92,8 +103,14 @@ const definitions: Definition[] = [ model: 'Nimly', vendor: 'Onesti Products AS', description: 'Zigbee module for Nimly Doorlock series', - fromZigbee: [fzLocal.nimly_pro_lock_actions, fz.lock, fz.lock_operation_event, fz.battery, fz.lock_programming_event, - fz.easycodetouch_action], + fromZigbee: [ + fzLocal.nimly_pro_lock_actions, + fz.lock, + fz.lock_operation_event, + fz.battery, + fz.lock_programming_event, + fz.easycodetouch_action, + ], toZigbee: [tz.lock, tz.easycode_auto_relock, tz.lock_sound_volume, tz.pincode_lock], meta: {pinCodeCount: 1000, battery: {dontDividePercentage: true}}, configure: async (device, coordinatorEndpoint) => { @@ -105,12 +122,17 @@ const definitions: Definition[] = [ device.powerSource = 'Battery'; device.save(); }, - exposes: [e.lock(), e.battery(), e.sound_volume(), - e.enum('last_unlock_source', ea.STATE, ['zigbee', 'keypad', 'fingerprintsensor', 'rfid', - 'self', 'unknown']).withDescription('Last unlock source'), + exposes: [ + e.lock(), + e.battery(), + e.sound_volume(), + e + .enum('last_unlock_source', ea.STATE, ['zigbee', 'keypad', 'fingerprintsensor', 'rfid', 'self', 'unknown']) + .withDescription('Last unlock source'), e.text('last_unlock_user', ea.STATE).withDescription('Last unlock user').withDescription('Last unlock user'), - e.enum('last_lock_source', ea.STATE, ['zigbee', 'keypad', 'fingerprintsensor', 'rfid', - 'self', 'unknown']).withDescription('Last lock source'), + e + .enum('last_lock_source', ea.STATE, ['zigbee', 'keypad', 'fingerprintsensor', 'rfid', 'self', 'unknown']) + .withDescription('Last lock source'), e.text('last_lock_user', ea.STATE).withDescription('Last lock user'), e.text('last_used_pin_code', ea.STATE).withDescription('Last used pin code'), e.binary('auto_relock', ea.STATE_SET, true, false).withDescription('Auto relock after 7 seconds.'), @@ -127,8 +149,13 @@ const definitions: Definition[] = [ exposes: [e.switch(), e.power(), e.current(), e.voltage(), e.energy(), e.device_temperature()], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(2); - await reporting.bind(endpoint, coordinatorEndpoint, ['genIdentify', 'genOnOff', 'genDeviceTempCfg', - 'haElectricalMeasurement', 'seMetering']); + await reporting.bind(endpoint, coordinatorEndpoint, [ + 'genIdentify', + 'genOnOff', + 'genDeviceTempCfg', + 'haElectricalMeasurement', + 'seMetering', + ]); await reporting.onOff(endpoint); await reporting.readEletricalMeasurementMultiplierDivisors(endpoint); await reporting.activePower(endpoint); diff --git a/src/devices/openlumi.ts b/src/devices/openlumi.ts index 3ebc9607331e6..37f041592a143 100644 --- a/src/devices/openlumi.ts +++ b/src/devices/openlumi.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import * as constants from '../lib/constants'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/orvibo.ts b/src/devices/orvibo.ts index a864aad06dda2..4805060c57b30 100644 --- a/src/devices/orvibo.ts +++ b/src/devices/orvibo.ts @@ -1,9 +1,9 @@ -import {Definition, Tz} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; import {deviceEndpoints, light, onOff, battery, humidity, temperature} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition, Tz} from '../lib/types'; const e = exposes.presets; @@ -53,8 +53,22 @@ const definitions: Definition[] = [ vendor: 'ORVIBO', description: 'Smart sticker switch', fromZigbee: [fz.orvibo_raw_1], - exposes: [e.action(['button_1_click', 'button_1_hold', 'button_1_release', 'button_2_click', 'button_2_hold', 'button_2_release', - 'button_3_click', 'button_3_hold', 'button_3_release', 'button_4_click', 'button_4_hold', 'button_4_release'])], + exposes: [ + e.action([ + 'button_1_click', + 'button_1_hold', + 'button_1_release', + 'button_2_click', + 'button_2_hold', + 'button_2_release', + 'button_3_click', + 'button_3_hold', + 'button_3_release', + 'button_4_click', + 'button_4_hold', + 'button_4_release', + ]), + ], toZigbee: [], }, { @@ -62,10 +76,7 @@ const definitions: Definition[] = [ model: 'T18W3Z', vendor: 'ORVIBO', description: 'Neutral smart switch 3 gang', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2, 'l3': 3}}), - onOff({endpointNames: ['l1', 'l2', 'l3']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2, l3: 3}}), onOff({endpointNames: ['l1', 'l2', 'l3']})], }, { zigbeeModel: ['fdd76effa0e146b4bdafa0c203a37192', 'c670e231d1374dbc9e3c6a9fffbd0ae6', '75a4bfe8ef9c4350830a25d13e3ab068'], @@ -88,20 +99,14 @@ const definitions: Definition[] = [ model: 'RL804QZB', vendor: 'ORVIBO', description: 'Multi-functional 3 gang relay', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2, 'l3': 3}}), - onOff({endpointNames: ['l1', 'l2', 'l3'], configureReporting: false}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2, l3: 3}}), onOff({endpointNames: ['l1', 'l2', 'l3'], configureReporting: false})], }, { zigbeeModel: ['396483ce8b3f4e0d8e9d79079a35a420'], model: 'CM10ZW', vendor: 'ORVIBO', description: 'Multi-functional 3 gang relay', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2, 'l3': 3}}), - onOff({endpointNames: ['l1', 'l2', 'l3']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2, l3: 3}}), onOff({endpointNames: ['l1', 'l2', 'l3']})], }, { zigbeeModel: ['b467083cfc864f5e826459e5d8ea6079'], @@ -155,20 +160,14 @@ const definitions: Definition[] = [ model: 'T30W3Z', vendor: 'ORVIBO', description: 'Smart light switch - 3 gang', - extend: [ - deviceEndpoints({endpoints: {'top': 1, 'center': 2, 'bottom': 3}}), - onOff({endpointNames: ['top', 'center', 'bottom']}), - ], + extend: [deviceEndpoints({endpoints: {top: 1, center: 2, bottom: 3}}), onOff({endpointNames: ['top', 'center', 'bottom']})], }, { zigbeeModel: ['074b3ffba5a045b7afd94c47079dd553'], model: 'T21W2Z', vendor: 'ORVIBO', description: 'Smart light switch - 2 gang', - extend: [ - deviceEndpoints({endpoints: {'top': 1, 'bottom': 2}}), - onOff({endpointNames: ['top', 'bottom']}), - ], + extend: [deviceEndpoints({endpoints: {top: 1, bottom: 2}}), onOff({endpointNames: ['top', 'bottom']})], }, { zigbeeModel: ['095db3379e414477ba6c2f7e0c6aa026'], @@ -200,20 +199,14 @@ const definitions: Definition[] = [ model: 'R11W2Z', vendor: 'ORVIBO', description: 'In wall switch - 2 gang', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2}}), - onOff({endpointNames: ['l1', 'l2']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2}}), onOff({endpointNames: ['l1', 'l2']})], }, { zigbeeModel: ['9ea4d5d8778d4f7089ac06a3969e784b', '83b9b27d5ffb4830bf35be5b1023623e', '2810c2403b9c4a5db62cc62d1030d95e'], model: 'R20W2Z', vendor: 'ORVIBO', description: 'In wall switch - 2 gang', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2}}), - onOff({endpointNames: ['l1', 'l2']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2}}), onOff({endpointNames: ['l1', 'l2']})], }, { zigbeeModel: ['131c854783bc45c9b2ac58088d09571c', 'b2e57a0f606546cd879a1a54790827d6', '585fdfb8c2304119a2432e9845cf2623'], @@ -280,30 +273,21 @@ const definitions: Definition[] = [ model: 'T40W2Z', vendor: 'ORVIBO', description: 'MixSwitch 2 gangs', - extend: [ - deviceEndpoints({endpoints: {'left': 1, 'right': 2}}), - onOff({endpointNames: ['left', 'right']}), - ], + extend: [deviceEndpoints({endpoints: {left: 1, right: 2}}), onOff({endpointNames: ['left', 'right']})], }, { zigbeeModel: ['e8d667cb184b4a2880dd886c23d00976'], model: 'T40W3Z', vendor: 'ORVIBO', description: 'MixSwitch 3 gangs', - extend: [ - deviceEndpoints({endpoints: {'left': 1, 'center': 2, 'right': 3}}), - onOff({endpointNames: ['left', 'center', 'right']}), - ], + extend: [deviceEndpoints({endpoints: {left: 1, center: 2, right: 3}}), onOff({endpointNames: ['left', 'center', 'right']})], }, { zigbeeModel: ['20513b10079f4cc68cffb8b0dc6d3277'], model: 'T40W4Z', vendor: 'ORVIBO', description: 'MixSwitch 4 gangs', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2, 'l4': 3, 'l5': 5, 'l6': 6}}), - onOff({endpointNames: ['l1', 'l2', 'l4', 'l5', 'l6']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2, l4: 3, l5: 5, l6: 6}}), onOff({endpointNames: ['l1', 'l2', 'l4', 'l5', 'l6']})], }, { zigbeeModel: ['bcb949e87e8c4ea6bc2803052dd8fbf5'], @@ -326,10 +310,7 @@ const definitions: Definition[] = [ model: 'T41W2Z', vendor: 'ORVIBO', description: 'MixSwitch 2 gang (without neutral wire)', - extend: [ - deviceEndpoints({endpoints: {'left': 1, 'right': 2}}), - onOff({endpointNames: ['left', 'right']}), - ], + extend: [deviceEndpoints({endpoints: {left: 1, right: 2}}), onOff({endpointNames: ['left', 'right']})], }, { zigbeeModel: ['cb7ce9fe2cb147e69c5ea700b39b3d5b'], @@ -350,10 +331,7 @@ const definitions: Definition[] = [ model: 'R30W3Z', vendor: 'ORVIBO', description: 'In-wall switch 3 gang', - extend: [ - deviceEndpoints({endpoints: {'left': 1, 'center': 2, 'right': 3}}), - onOff({endpointNames: ['left', 'center', 'right']}), - ], + extend: [deviceEndpoints({endpoints: {left: 1, center: 2, right: 3}}), onOff({endpointNames: ['left', 'center', 'right']})], }, { zigbeeModel: ['0e93fa9c36bb417a90ad5d8a184b683a'], diff --git a/src/devices/osram.ts b/src/devices/osram.ts index ffbb024794c43..3c76128564019 100644 --- a/src/devices/osram.ts +++ b/src/devices/osram.ts @@ -1,11 +1,11 @@ -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; +import {ledvanceLight, ledvanceFz, ledvanceOnOff} from '../lib/ledvance'; import * as legacy from '../lib/legacy'; +import {deviceEndpoints} from '../lib/modernExtend'; import * as ota from '../lib/ota'; import * as reporting from '../lib/reporting'; -import {ledvanceLight, ledvanceFz, ledvanceOnOff} from '../lib/ledvance'; import {Definition} from '../lib/types'; -import {deviceEndpoints} from '../lib/modernExtend'; const e = exposes.presets; @@ -171,7 +171,10 @@ const definitions: Definition[] = [ description: 'Smart+ plug', vendor: 'OSRAM', extend: [ledvanceOnOff({powerOnBehavior: false})], - whiteLabel: [{vendor: 'LEDVANCE', model: 'AB3257001NJ'}, {vendor: 'LEDVANCE', model: 'AC03360'}], + whiteLabel: [ + {vendor: 'LEDVANCE', model: 'AB3257001NJ'}, + {vendor: 'LEDVANCE', model: 'AC03360'}, + ], }, { zigbeeModel: ['LIGHTIFY PAR38 ON/OFF/DIM'], @@ -280,14 +283,33 @@ const definitions: Definition[] = [ model: 'AC0251100NJ/AC0251600NJ/AC0251700NJ', vendor: 'OSRAM', description: 'Smart+ switch mini', - fromZigbee: [legacy.fz.osram_lightify_switch_cmdOn, legacy.fz.osram_lightify_switch_cmdMoveWithOnOff, - legacy.fz.osram_lightify_switch_AC0251100NJ_cmdStop, legacy.fz.osram_lightify_switch_cmdMoveToColorTemp, - legacy.fz.osram_lightify_switch_cmdMoveHue, legacy.fz.osram_lightify_switch_cmdMoveToSaturation, - legacy.fz.osram_lightify_switch_cmdOff, legacy.fz.osram_lightify_switch_cmdMove, fz.battery, - legacy.fz.osram_lightify_switch_cmdMoveToLevelWithOnOff], - exposes: [e.battery(), e.action([ - 'on', 'brightness_move_up', 'brightness_move_down', 'brightness_stop', 'color_temperature_move', 'hue_move', 'hue_stop', - 'move_to_saturation', 'off', 'brightness_move_to_level'])], + fromZigbee: [ + legacy.fz.osram_lightify_switch_cmdOn, + legacy.fz.osram_lightify_switch_cmdMoveWithOnOff, + legacy.fz.osram_lightify_switch_AC0251100NJ_cmdStop, + legacy.fz.osram_lightify_switch_cmdMoveToColorTemp, + legacy.fz.osram_lightify_switch_cmdMoveHue, + legacy.fz.osram_lightify_switch_cmdMoveToSaturation, + legacy.fz.osram_lightify_switch_cmdOff, + legacy.fz.osram_lightify_switch_cmdMove, + fz.battery, + legacy.fz.osram_lightify_switch_cmdMoveToLevelWithOnOff, + ], + exposes: [ + e.battery(), + e.action([ + 'on', + 'brightness_move_up', + 'brightness_move_down', + 'brightness_stop', + 'color_temperature_move', + 'hue_move', + 'hue_stop', + 'move_to_saturation', + 'off', + 'brightness_move_to_level', + ]), + ], toZigbee: [], meta: {battery: {voltageToPercentage: '3V_2500'}}, ota: ota.ledvance, @@ -306,13 +328,34 @@ const definitions: Definition[] = [ model: '4058075816459', vendor: 'OSRAM', description: 'Smart+ switch', - exposes: [e.battery(), e.action(['left_top_click', 'left_bottom_click', 'right_top_click', 'right_bottom_click', 'left_top_hold', - 'left_bottom_hold', 'left_top_release', 'left_bottom_release', 'right_top_release', 'right_top_hold', - 'right_bottom_release', 'right_bottom_hold'])], - fromZigbee: [fz.battery, legacy.fz.osram_lightify_switch_AB371860355_cmdOn, legacy.fz.osram_lightify_switch_AB371860355_cmdOff, - legacy.fz.osram_lightify_switch_AB371860355_cmdStepColorTemp, legacy.fz.osram_lightify_switch_AB371860355_cmdMoveWithOnOff, - legacy.fz.osram_lightify_switch_AB371860355_cmdMove, legacy.fz.osram_lightify_switch_AB371860355_cmdStop, - legacy.fz.osram_lightify_switch_AB371860355_cmdMoveHue, legacy.fz.osram_lightify_switch_AB371860355_cmdMoveSat], + exposes: [ + e.battery(), + e.action([ + 'left_top_click', + 'left_bottom_click', + 'right_top_click', + 'right_bottom_click', + 'left_top_hold', + 'left_bottom_hold', + 'left_top_release', + 'left_bottom_release', + 'right_top_release', + 'right_top_hold', + 'right_bottom_release', + 'right_bottom_hold', + ]), + ], + fromZigbee: [ + fz.battery, + legacy.fz.osram_lightify_switch_AB371860355_cmdOn, + legacy.fz.osram_lightify_switch_AB371860355_cmdOff, + legacy.fz.osram_lightify_switch_AB371860355_cmdStepColorTemp, + legacy.fz.osram_lightify_switch_AB371860355_cmdMoveWithOnOff, + legacy.fz.osram_lightify_switch_AB371860355_cmdMove, + legacy.fz.osram_lightify_switch_AB371860355_cmdStop, + legacy.fz.osram_lightify_switch_AB371860355_cmdMoveHue, + legacy.fz.osram_lightify_switch_AB371860355_cmdMoveSat, + ], toZigbee: [], meta: {battery: {voltageToPercentage: '3V_2500'}}, ota: ota.ledvance, @@ -350,8 +393,10 @@ const definitions: Definition[] = [ extend: [ledvanceLight({colorTemp: {range: undefined}})], }, { - fingerprint: [{modelID: 'Zigbee 3.0 DALI CONV LI', endpoints: [{ID: 10}, {ID: 25}, {ID: 242}]}, - {modelID: 'Zigbee 3.0 DALI CONV LI\u0000', endpoints: [{ID: 10}, {ID: 25}, {ID: 242}]}], + fingerprint: [ + {modelID: 'Zigbee 3.0 DALI CONV LI', endpoints: [{ID: 10}, {ID: 25}, {ID: 242}]}, + {modelID: 'Zigbee 3.0 DALI CONV LI\u0000', endpoints: [{ID: 10}, {ID: 25}, {ID: 242}]}, + ], model: '4062172044776_2', vendor: 'OSRAM', description: 'Zigbee 3.0 DALI CONV LI dimmer for DALI-based luminaires (one device and pushbutton)', @@ -366,24 +411,28 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'Zigbee 3.0 DALI CONV LI', endpoints: [{ID: 10}, {ID: 11}, {ID: 242}]}, - {modelID: 'Zigbee 3.0 DALI CONV LI\u0000', endpoints: [{ID: 10}, {ID: 11}, {ID: 242}]}], + fingerprint: [ + {modelID: 'Zigbee 3.0 DALI CONV LI', endpoints: [{ID: 10}, {ID: 11}, {ID: 242}]}, + {modelID: 'Zigbee 3.0 DALI CONV LI\u0000', endpoints: [{ID: 10}, {ID: 11}, {ID: 242}]}, + ], model: '4062172044776_3', vendor: 'OSRAM', description: 'Zigbee 3.0 DALI CONV LI dimmer for DALI-based luminaires (with two devices)', extend: [ - deviceEndpoints({endpoints: {'l1': 10, 'l2': 11}}), + deviceEndpoints({endpoints: {l1: 10, l2: 11}}), ledvanceLight({configureReporting: true, endpointNames: ['l1', 'l2'], ota: ota.zigbeeOTA}), ], }, { - fingerprint: [{modelID: 'Zigbee 3.0 DALI CONV LI', endpoints: [{ID: 10}, {ID: 11}, {ID: 25}, {ID: 242}]}, - {modelID: 'Zigbee 3.0 DALI CONV LI\u0000', endpoints: [{ID: 10}, {ID: 11}, {ID: 25}, {ID: 242}]}], + fingerprint: [ + {modelID: 'Zigbee 3.0 DALI CONV LI', endpoints: [{ID: 10}, {ID: 11}, {ID: 25}, {ID: 242}]}, + {modelID: 'Zigbee 3.0 DALI CONV LI\u0000', endpoints: [{ID: 10}, {ID: 11}, {ID: 25}, {ID: 242}]}, + ], model: '4062172044776_4', vendor: 'OSRAM', description: 'Zigbee 3.0 DALI CONV LI dimmer for DALI-based luminaires (with two devices and pushbutton)', extend: [ - deviceEndpoints({endpoints: {'l1': 10, 'l2': 11, 's1': 25}}), + deviceEndpoints({endpoints: {l1: 10, l2: 11, s1: 25}}), ledvanceLight({configureReporting: true, endpointNames: ['l1', 'l2', 's1'], ota: ota.zigbeeOTA}), ], fromZigbee: [fz.command_toggle, fz.command_move, fz.command_stop], @@ -409,7 +458,7 @@ const definitions: Definition[] = [ description: 'Lightify pro push button controller (PBC)', meta: {multiEndpoint: true}, endpoint: (device) => { - return {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4}; + return {l1: 1, l2: 2, l3: 3, l4: 4}; }, fromZigbee: [ledvanceFz.pbc_level_to_action], exposes: [ diff --git a/src/devices/oujiabao.ts b/src/devices/oujiabao.ts index 3659683ed6655..706d27a1c1f36 100644 --- a/src/devices/oujiabao.ts +++ b/src/devices/oujiabao.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/owon.ts b/src/devices/owon.ts index 01aa42c42a9e8..762beecec1482 100644 --- a/src/devices/owon.ts +++ b/src/devices/owon.ts @@ -1,12 +1,12 @@ -import {Definition, Fz, KeyValue} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; import * as constants from '../lib/constants'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; import {battery, iasZoneAlarm} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; import * as tuya from '../lib/tuya'; +import {Definition, Fz, KeyValue} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -96,15 +96,18 @@ const fzLocal = { payload.frequency = msg.data['owonFrequency']; } // Issue #20719 summation manufacturer attributes are not well parsed - if (msg.data.hasOwnProperty('owonReactivePowerSum') || msg.data.hasOwnProperty('8451')) { // 0x2103 -> 8451 + if (msg.data.hasOwnProperty('owonReactivePowerSum') || msg.data.hasOwnProperty('8451')) { + // 0x2103 -> 8451 const value = msg.data['owonReactiveEnergySum'] || msg.data['8451']; payload.power_reactive = value; } - if (msg.data.hasOwnProperty('owonCurrentSum') || msg.data.hasOwnProperty('12547')) { // 0x3103 -> 12547 + if (msg.data.hasOwnProperty('owonCurrentSum') || msg.data.hasOwnProperty('12547')) { + // 0x3103 -> 12547 const data = msg.data['owonCurrentSum'] || msg.data['12547'] * factor; payload.current = data; } - if (msg.data.hasOwnProperty('owonReactiveEnergySum') || msg.data.hasOwnProperty('16643')) { // 0x4103 -> 16643 + if (msg.data.hasOwnProperty('owonReactiveEnergySum') || msg.data.hasOwnProperty('16643')) { + // 0x4103 -> 16643 const data = msg.data['owonReactiveEnergySum'] || msg.data['16643']; const value = (parseInt(data[0]) << 32) + parseInt(data[1]); payload.reactive_energy = value * factor; @@ -139,8 +142,7 @@ const definitions: Definition[] = [ await reporting.onOff(endpoint); await reporting.readMeteringMultiplierDivisor(endpoint); await reporting.instantaneousDemand(endpoint, {min: 5, max: constants.repInterval.MINUTES_5, change: 2}); // divider 1000: 2W - await reporting.currentSummDelivered(endpoint, {min: 5, max: constants.repInterval.MINUTES_5, - change: [10, 10]}); // divider 1000: 0,01kWh + await reporting.currentSummDelivered(endpoint, {min: 5, max: constants.repInterval.MINUTES_5, change: [10, 10]}); // divider 1000: 0,01kWh }, }, { @@ -158,8 +160,7 @@ const definitions: Definition[] = [ await reporting.onOff(endpoint); await reporting.readMeteringMultiplierDivisor(endpoint); await reporting.instantaneousDemand(endpoint, {min: 5, max: constants.repInterval.MINUTES_5, change: 2}); // divider 1000: 2W - await reporting.currentSummDelivered(endpoint, {min: 5, max: constants.repInterval.MINUTES_5, - change: [10, 10]}); // divider 1000: 0,01kWh + await reporting.currentSummDelivered(endpoint, {min: 5, max: constants.repInterval.MINUTES_5, change: [10, 10]}); // divider 1000: 0,01kWh // At least some white label devices, like the Oz Smart Things device, don't report a power source so we need to force it device.powerSource = 'Mains (single phase)'; @@ -180,8 +181,7 @@ const definitions: Definition[] = [ await reporting.onOff(endpoint); await reporting.readMeteringMultiplierDivisor(endpoint); await reporting.instantaneousDemand(endpoint, {min: 5, max: constants.repInterval.MINUTES_5, change: 2}); // divider 1000: 2W - await reporting.currentSummDelivered(endpoint, {min: 5, max: constants.repInterval.MINUTES_5, - change: [10, 10]}); // divider 1000: 0,01kWh + await reporting.currentSummDelivered(endpoint, {min: 5, max: constants.repInterval.MINUTES_5, change: [10, 10]}); // divider 1000: 0,01kWh }, }, { @@ -206,11 +206,9 @@ const definitions: Definition[] = [ model: 'PIR313-E', vendor: 'OWON', description: 'Motion sensor', - fromZigbee: [fz.battery, fz.ignore_basic_report, fz.ias_occupancy_alarm_1, fz.temperature, fz.humidity, - fz.occupancy_timeout, fz.illuminance], + fromZigbee: [fz.battery, fz.ignore_basic_report, fz.ias_occupancy_alarm_1, fz.temperature, fz.humidity, fz.occupancy_timeout, fz.illuminance], toZigbee: [], - exposes: [e.occupancy(), e.tamper(), e.battery_low(), e.illuminance(), e.illuminance_lux().withUnit('lx'), - e.temperature(), e.humidity()], + exposes: [e.occupancy(), e.tamper(), e.battery_low(), e.illuminance(), e.illuminance_lux().withUnit('lx'), e.temperature(), e.humidity()], configure: async (device, coordinatorEndpoint) => { const endpoint2 = device.getEndpoint(2); const endpoint3 = device.getEndpoint(3); @@ -231,12 +229,24 @@ const definitions: Definition[] = [ vendor: 'OWON', description: 'HVAC controller/IR blaster', fromZigbee: [fz.fan, fz.thermostat], - toZigbee: [tz.fan_mode, tz.thermostat_system_mode, tz.thermostat_occupied_heating_setpoint, - tz.thermostat_occupied_cooling_setpoint, tz.thermostat_ac_louver_position, tz.thermostat_local_temperature], - exposes: [e.climate().withSystemMode(['off', 'heat', 'cool', 'auto', 'dry', 'fan_only']) - .withSetpoint('occupied_heating_setpoint', 8, 30, 1).withSetpoint('occupied_cooling_setpoint', 8, 30, 1) - .withAcLouverPosition(['fully_open', 'fully_closed', 'half_open', 'quarter_open', 'three_quarters_open']) - .withLocalTemperature(), e.fan().withModes(['low', 'medium', 'high', 'on', 'auto'])], + toZigbee: [ + tz.fan_mode, + tz.thermostat_system_mode, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_occupied_cooling_setpoint, + tz.thermostat_ac_louver_position, + tz.thermostat_local_temperature, + ], + exposes: [ + e + .climate() + .withSystemMode(['off', 'heat', 'cool', 'auto', 'dry', 'fan_only']) + .withSetpoint('occupied_heating_setpoint', 8, 30, 1) + .withSetpoint('occupied_cooling_setpoint', 8, 30, 1) + .withAcLouverPosition(['fully_open', 'fully_closed', 'half_open', 'quarter_open', 'three_quarters_open']) + .withLocalTemperature(), + e.fan().withModes(['low', 'medium', 'high', 'on', 'auto']), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['hvacFanCtrl']); @@ -319,7 +329,11 @@ const definitions: Definition[] = [ } }, meta: {publishDuplicateTransaction: true}, - exposes: [e.current(), e.power(), e.power_reactive(), e.energy(), + exposes: [ + e.current(), + e.power(), + e.power_reactive(), + e.energy(), e.numeric('reactive_energy', ea.STATE).withUnit('kVArh').withDescription('Reactive energy for all phase'), e.numeric('voltage_l1', ea.STATE).withUnit('V').withDescription('Phase 1 voltage'), e.numeric('voltage_l2', ea.STATE).withUnit('V').withDescription('Phase 2 voltage'), @@ -350,28 +364,56 @@ const definitions: Definition[] = [ vendor: 'OWON', description: 'HVAC fan coil', fromZigbee: [fz.fan, fz.thermostat, fz.humidity, fz.occupancy, legacy.fz.hvac_user_interface], - toZigbee: [tz.fan_mode, - tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint, - tz.thermostat_occupied_cooling_setpoint, tz.thermostat_unoccupied_cooling_setpoint, - tz.thermostat_min_heat_setpoint_limit, tz.thermostat_max_heat_setpoint_limit, - tz.thermostat_min_cool_setpoint_limit, tz.thermostat_max_cool_setpoint_limit, + toZigbee: [ + tz.fan_mode, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_unoccupied_heating_setpoint, + tz.thermostat_occupied_cooling_setpoint, + tz.thermostat_unoccupied_cooling_setpoint, + tz.thermostat_min_heat_setpoint_limit, + tz.thermostat_max_heat_setpoint_limit, + tz.thermostat_min_cool_setpoint_limit, + tz.thermostat_max_cool_setpoint_limit, tz.thermostat_local_temperature, tz.thermostat_keypad_lockout, - tz.thermostat_system_mode, tz.thermostat_running_mode, tz.thermostat_running_state, tz.thermostat_programming_operation_mode], - exposes: [e.humidity(), e.occupancy(), - e.climate().withSystemMode(['off', 'heat', 'cool', 'fan_only', 'sleep']).withLocalTemperature() + tz.thermostat_system_mode, + tz.thermostat_running_mode, + tz.thermostat_running_state, + tz.thermostat_programming_operation_mode, + ], + exposes: [ + e.humidity(), + e.occupancy(), + e + .climate() + .withSystemMode(['off', 'heat', 'cool', 'fan_only', 'sleep']) + .withLocalTemperature() .withRunningMode(['off', 'heat', 'cool']) .withRunningState(['idle', 'heat', 'cool', 'fan_only']) - .withSetpoint('occupied_heating_setpoint', 5, 30, 0.5).withSetpoint('unoccupied_heating_setpoint', 5, 30, 0.5) - .withSetpoint('occupied_cooling_setpoint', 7, 35, 0.5).withSetpoint('unoccupied_cooling_setpoint', 7, 35, 0.5), + .withSetpoint('occupied_heating_setpoint', 5, 30, 0.5) + .withSetpoint('unoccupied_heating_setpoint', 5, 30, 0.5) + .withSetpoint('occupied_cooling_setpoint', 7, 35, 0.5) + .withSetpoint('unoccupied_cooling_setpoint', 7, 35, 0.5), e.fan().withModes(['low', 'medium', 'high', 'on', 'auto']), - e.programming_operation_mode(['setpoint', 'eco']), e.keypad_lockout(), - e.max_heat_setpoint_limit(5, 30, 0.5), e.min_heat_setpoint_limit(5, 30, 0.5), - e.max_cool_setpoint_limit(7, 35, 0.5), e.min_cool_setpoint_limit(7, 35, 0.5)], + e.programming_operation_mode(['setpoint', 'eco']), + e.keypad_lockout(), + e.max_heat_setpoint_limit(5, 30, 0.5), + e.min_heat_setpoint_limit(5, 30, 0.5), + e.max_cool_setpoint_limit(7, 35, 0.5), + e.min_cool_setpoint_limit(7, 35, 0.5), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); - const binds = ['genBasic', 'genIdentify', 'genGroups', 'hvacThermostat', 'hvacUserInterfaceCfg', 'hvacFanCtrl', - 'msTemperatureMeasurement', 'msOccupancySensing']; + const binds = [ + 'genBasic', + 'genIdentify', + 'genGroups', + 'hvacThermostat', + 'hvacUserInterfaceCfg', + 'hvacFanCtrl', + 'msTemperatureMeasurement', + 'msOccupancySensing', + ]; await reporting.bind(endpoint, coordinatorEndpoint, binds); await reporting.fanMode(endpoint); await reporting.bind(endpoint, coordinatorEndpoint, ['hvacThermostat']); @@ -384,9 +426,16 @@ const definitions: Definition[] = [ await reporting.humidity(endpoint, {min: 60, max: 600, change: 1}); await reporting.thermostatKeypadLockMode(endpoint); - await endpoint.read('hvacThermostat', ['systemMode', 'runningMode', 'runningState', - 'occupiedHeatingSetpoint', 'unoccupiedHeatingSetpoint', - 'occupiedCoolingSetpoint', 'unoccupiedCoolingSetpoint', 'localTemp']); + await endpoint.read('hvacThermostat', [ + 'systemMode', + 'runningMode', + 'runningState', + 'occupiedHeatingSetpoint', + 'unoccupiedHeatingSetpoint', + 'occupiedCoolingSetpoint', + 'unoccupiedCoolingSetpoint', + 'localTemp', + ]); await endpoint.read('msRelativeHumidity', ['measuredValue']); const endpoint2 = device.getEndpoint(2); @@ -417,8 +466,11 @@ const definitions: Definition[] = [ description: 'Zigbee remote dimmer', fromZigbee: [fz.battery, fz.command_toggle, fz.command_step, fz.command_step_color_temperature], toZigbee: [], - exposes: [e.battery(), e.battery_low(), e.action(['toggle', 'brightness_step_up', 'brightness_step_down', - 'color_temperature_step_up', 'color_temperature_step_down'])], + exposes: [ + e.battery(), + e.battery_low(), + e.action(['toggle', 'brightness_step_up', 'brightness_step_down', 'color_temperature_step_up', 'color_temperature_step_down']), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']); diff --git a/src/devices/ozsmartthings.ts b/src/devices/ozsmartthings.ts index 2ad968092715f..da6999ff15690 100644 --- a/src/devices/ozsmartthings.ts +++ b/src/devices/ozsmartthings.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import * as tuya from '../lib/tuya'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/paul_neuhaus.ts b/src/devices/paul_neuhaus.ts index 9ad905ce077a7..ce3270d91f13b 100644 --- a/src/devices/paul_neuhaus.ts +++ b/src/devices/paul_neuhaus.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import {light, onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const e = exposes.presets; @@ -11,12 +11,39 @@ const definitions: Definition[] = [ model: '100.462.31', vendor: 'Paul Neuhaus', description: 'Q-REMOTE', - fromZigbee: [fz.command_on, fz.command_off, fz.command_toggle, fz.command_step, fz.command_move_to_color_temp, fz.command_stop, - fz.command_move_to_color, fz.command_move, fz.command_color_loop_set, fz.command_ehanced_move_to_hue_and_saturation, - fz.tint_scene, fz.command_recall], - exposes: [e.action(['on', 'off', 'toggle', 'brightness_step_up', 'brightness_step_down', 'color_temperature_move', 'color_move', - 'brightness_stop', 'brightness_move_up', 'brightness_move_down', 'color_loop_set', 'enhanced_move_to_hue_and_saturation', - 'recall_*', 'scene_*']), e.action_group()], + fromZigbee: [ + fz.command_on, + fz.command_off, + fz.command_toggle, + fz.command_step, + fz.command_move_to_color_temp, + fz.command_stop, + fz.command_move_to_color, + fz.command_move, + fz.command_color_loop_set, + fz.command_ehanced_move_to_hue_and_saturation, + fz.tint_scene, + fz.command_recall, + ], + exposes: [ + e.action([ + 'on', + 'off', + 'toggle', + 'brightness_step_up', + 'brightness_step_down', + 'color_temperature_move', + 'color_move', + 'brightness_stop', + 'brightness_move_up', + 'brightness_move_down', + 'color_loop_set', + 'enhanced_move_to_hue_and_saturation', + 'recall_*', + 'scene_*', + ]), + e.action_group(), + ], toZigbee: [], }, { @@ -40,7 +67,7 @@ const definitions: Definition[] = [ description: 'Various RGBW lights (e.g. 100.110.39)', extend: [light({colorTemp: {range: undefined}, color: true})], endpoint: (device) => { - return {'default': 2}; + return {default: 2}; }, }, { @@ -76,12 +103,31 @@ const definitions: Definition[] = [ model: 'E0040006', vendor: 'Paul Neuhaus', description: 'Q RGBW remote controller', - fromZigbee: [fz.command_step, fz.command_ehanced_move_to_hue_and_saturation, fz.command_move_to_color_temp, - fz.command_on, fz.command_off, fz.command_color_loop_set], + fromZigbee: [ + fz.command_step, + fz.command_ehanced_move_to_hue_and_saturation, + fz.command_move_to_color_temp, + fz.command_on, + fz.command_off, + fz.command_color_loop_set, + ], toZigbee: [], - exposes: [e.action(['on', 'off', 'brightness_step_up', 'brightness_step_down', 'color_move', - 'color_temperature_move', 'brightness_stop', 'brightness_move_up', 'brightness_move_down', - 'color_loop_set', 'enhanced_move_to_hue_and_saturation']), e.action_group()], + exposes: [ + e.action([ + 'on', + 'off', + 'brightness_step_up', + 'brightness_step_down', + 'color_move', + 'color_temperature_move', + 'brightness_stop', + 'brightness_move_up', + 'brightness_move_down', + 'color_loop_set', + 'enhanced_move_to_hue_and_saturation', + ]), + e.action_group(), + ], }, { zigbeeModel: ['JZ-RGBW-Z01'], @@ -89,7 +135,7 @@ const definitions: Definition[] = [ vendor: 'Paul Neuhaus', description: 'Q-VIDAL RGBW ceiling lamp, 6032-55', endpoint: (device) => { - return {'default': 2}; + return {default: 2}; }, extend: [light({colorTemp: {range: undefined}, color: true})], }, diff --git a/src/devices/paulmann.ts b/src/devices/paulmann.ts index 9d5bb213b9e4a..258daf3d89a99 100644 --- a/src/devices/paulmann.ts +++ b/src/devices/paulmann.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import {light, onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const e = exposes.presets; @@ -13,8 +13,21 @@ const definitions: Definition[] = [ description: 'Smart switch 4 buttons white', fromZigbee: [fz.command_on, fz.command_off, fz.battery, fz.command_move, fz.command_stop], toZigbee: [], - exposes: [e.battery(), e.action(['on_1', 'off_1', 'on_2', 'off_2', 'brightness_move_up_1', 'brightness_move_down_1', - 'brightness_move_stop_1', 'brightness_move_up_2', 'brightness_move_down_2', 'brightness_move_stop_2'])], + exposes: [ + e.battery(), + e.action([ + 'on_1', + 'off_1', + 'on_2', + 'off_2', + 'brightness_move_up_1', + 'brightness_move_down_1', + 'brightness_move_stop_1', + 'brightness_move_up_2', + 'brightness_move_down_2', + 'brightness_move_stop_2', + ]), + ], meta: {multiEndpoint: true}, }, { @@ -24,8 +37,21 @@ const definitions: Definition[] = [ description: 'Smart switch 4 buttons white', fromZigbee: [fz.command_on, fz.command_off, fz.battery, fz.command_move, fz.command_stop], toZigbee: [], - exposes: [e.battery(), e.action(['on_1', 'off_1', 'on_2', 'off_2', 'brightness_move_up_1', 'brightness_move_down_1', - 'brightness_move_stop_1', 'brightness_move_up_2', 'brightness_move_down_2', 'brightness_move_stop_2'])], + exposes: [ + e.battery(), + e.action([ + 'on_1', + 'off_1', + 'on_2', + 'off_2', + 'brightness_move_up_1', + 'brightness_move_down_1', + 'brightness_move_stop_1', + 'brightness_move_up_2', + 'brightness_move_down_2', + 'brightness_move_stop_2', + ]), + ], meta: {multiEndpoint: true}, }, { @@ -186,13 +212,37 @@ const definitions: Definition[] = [ model: '500.67', vendor: 'Paulmann', description: 'RGB remote control', - fromZigbee: [fz.command_on, fz.command_off, fz.command_toggle, fz.command_step, fz.command_move_to_color_temp, - fz.command_move_to_color, fz.command_stop, fz.command_move, fz.command_color_loop_set, - fz.command_ehanced_move_to_hue_and_saturation, fz.tint_scene], + fromZigbee: [ + fz.command_on, + fz.command_off, + fz.command_toggle, + fz.command_step, + fz.command_move_to_color_temp, + fz.command_move_to_color, + fz.command_stop, + fz.command_move, + fz.command_color_loop_set, + fz.command_ehanced_move_to_hue_and_saturation, + fz.tint_scene, + ], toZigbee: [], - exposes: [e.action([ - 'on', 'off', 'toggle', 'brightness_step_up', 'brightness_step_down', 'color_temperature_move', 'color_move', 'brightness_stop', - 'brightness_move_down', 'brightness_move_up', 'color_loop_set', 'enhanced_move_to_hue_and_saturation', 'scene_*'])], + exposes: [ + e.action([ + 'on', + 'off', + 'toggle', + 'brightness_step_up', + 'brightness_step_down', + 'color_temperature_move', + 'color_move', + 'brightness_stop', + 'brightness_move_down', + 'brightness_move_up', + 'color_loop_set', + 'enhanced_move_to_hue_and_saturation', + 'scene_*', + ]), + ], }, { fingerprint: [{modelID: 'RGB', manufacturerName: 'Paulmann Licht GmbH'}], diff --git a/src/devices/peq.ts b/src/devices/peq.ts index 85e1b0739c3e4..d89c64113ea0d 100644 --- a/src/devices/peq.ts +++ b/src/devices/peq.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/perenio.ts b/src/devices/perenio.ts index b72794155bd6e..cb09742b96a62 100644 --- a/src/devices/perenio.ts +++ b/src/devices/perenio.ts @@ -1,26 +1,16 @@ -import {Definition, Fz, Tz, KeyValue} from '../lib/types'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition, Fz, Tz, KeyValue} from '../lib/types'; import * as utils from '../lib/utils'; const e = exposes.presets; const ea = exposes.access; import * as ota from '../lib/ota'; -const switchTypeValues = [ - 'maintained_state', - 'maintained_toggle', - 'momentary_state', - 'momentary_press', - 'momentary_release', -]; +const switchTypeValues = ['maintained_state', 'maintained_toggle', 'momentary_state', 'momentary_press', 'momentary_release']; -const defaultOnOffStateValues = [ - 'on', - 'off', - 'previous', -]; +const defaultOnOffStateValues = ['on', 'off', 'previous']; const fzPerenio = { diagnostic: { @@ -45,9 +35,9 @@ const fzPerenio = { const switchTypeLookup: KeyValue = { 0x0001: 'momentary_state', 0x0010: 'maintained_state', - 0x00CC: 'maintained_toggle', - 0x00CD: 'momentary_release', - 0x00DC: 'momentary_press', + 0x00cc: 'maintained_toggle', + 0x00cd: 'momentary_release', + 0x00dc: 'momentary_press', }; if (msg.data.hasOwnProperty('presentValue')) { const property = utils.postfixWithEndpointName('switch_type', msg, model, meta); @@ -129,11 +119,11 @@ const tzPerenio = { convertSet: async (entity, key, value, meta) => { utils.assertString(value, key); const switchTypeLookup: KeyValue = { - 'momentary_state': 0x0001, - 'maintained_state': 0x0010, - 'maintained_toggle': 0x00CC, - 'momentary_release': 0x00CD, - 'momentary_press': 0x00DC, + momentary_state: 0x0001, + maintained_state: 0x0010, + maintained_toggle: 0x00cc, + momentary_release: 0x00cd, + momentary_press: 0x00dc, }; await entity.write('genMultistateValue', {presentValue: switchTypeLookup[value]}, utils.getOptions(meta.mapped, entity)); return {state: {switch_type: value}}; @@ -147,11 +137,11 @@ const tzPerenio = { convertSet: async (entity, key, val, meta) => { utils.assertString(val, key); const powerOnStateLookup: KeyValue = { - 'off': 0, - 'on': 1, - 'previous': 2, + off: 0, + on: 1, + previous: 2, }; - await entity.write(64635, {0: {value: powerOnStateLookup[val], type: 0x20}}, {manufacturerCode: 0x007B}); + await entity.write(64635, {0: {value: powerOnStateLookup[val], type: 0x20}}, {manufacturerCode: 0x007b}); return {state: {default_on_off_state: val}}; }, convertGet: async (entity, key, meta) => { @@ -161,7 +151,7 @@ const tzPerenio = { alarms_reset: { key: ['alarm_voltage_min', 'alarm_voltage_max', 'alarm_power_max', 'alarm_consumed_energy'], convertSet: async (entity, key, val, meta) => { - await entity.write(64635, {1: {value: 0, type: 0x20}}, {manufacturerCode: 0x007B}); + await entity.write(64635, {1: {value: 0, type: 0x20}}, {manufacturerCode: 0x007b}); return {state: {alarm_voltage_min: false, alarm_voltage_max: false, alarm_power_max: false, alarm_consumed_energy: false}}; }, convertGet: async (entity, key, meta) => { @@ -172,35 +162,35 @@ const tzPerenio = { key: ['voltage_min', 'voltage_max', 'power_max', 'consumed_energy_limit'], convertSet: async (entity, key, val, meta) => { switch (key) { - case 'voltage_min': - await entity.write(64635, {4: {value: val, type: 0x21}}, {manufacturerCode: 0x007B}); - break; - case 'voltage_max': - await entity.write(64635, {5: {value: val, type: 0x21}}, {manufacturerCode: 0x007B}); - break; - case 'power_max': - await entity.write(64635, {11: {value: val, type: 0x21}}, {manufacturerCode: 0x007B}); - break; - case 'consumed_energy_limit': - await entity.write(64635, {15: {value: val, type: 0x21}}, {manufacturerCode: 0x007B}); - break; + case 'voltage_min': + await entity.write(64635, {4: {value: val, type: 0x21}}, {manufacturerCode: 0x007b}); + break; + case 'voltage_max': + await entity.write(64635, {5: {value: val, type: 0x21}}, {manufacturerCode: 0x007b}); + break; + case 'power_max': + await entity.write(64635, {11: {value: val, type: 0x21}}, {manufacturerCode: 0x007b}); + break; + case 'consumed_energy_limit': + await entity.write(64635, {15: {value: val, type: 0x21}}, {manufacturerCode: 0x007b}); + break; } return {state: {[key]: val}}; }, convertGet: async (entity, key, meta) => { switch (key) { - case 'voltage_min': - await entity.read(64635, [4]); - break; - case 'voltage_max': - await entity.read(64635, [5]); - break; - case 'power_max': - await entity.read(64635, [11]); - break; - case 'consumed_energy_limit': - await entity.read(64635, [15]); - break; + case 'voltage_min': + await entity.read(64635, [4]); + break; + case 'voltage_max': + await entity.read(64635, [5]); + break; + case 'power_max': + await entity.read(64635, [11]); + break; + case 'consumed_energy_limit': + await entity.read(64635, [15]); + break; } }, } satisfies Tz.Converter, @@ -309,23 +299,28 @@ const definitions: Definition[] = [ await reporting.bind(endpoint1, coordinatorEndpoint, ['genOnOff']); await reporting.bind(endpoint2, coordinatorEndpoint, ['genOnOff']); await reporting.bind(endpoint10, coordinatorEndpoint, ['haDiagnostic']); - const payload = [{ - attribute: 'onOff', - minimumReportInterval: 0, - maximumReportInterval: 3600, - reportableChange: 0, - }]; - const payloadDiagnostic = [{ - attribute: 'lastMessageLqi', - minimumReportInterval: 5, - maximumReportInterval: 60, - reportableChange: 0, - }, { - attribute: 'lastMessageRssi', - minimumReportInterval: 5, - maximumReportInterval: 60, - reportableChange: 0, - }]; + const payload = [ + { + attribute: 'onOff', + minimumReportInterval: 0, + maximumReportInterval: 3600, + reportableChange: 0, + }, + ]; + const payloadDiagnostic = [ + { + attribute: 'lastMessageLqi', + minimumReportInterval: 5, + maximumReportInterval: 60, + reportableChange: 0, + }, + { + attribute: 'lastMessageRssi', + minimumReportInterval: 5, + maximumReportInterval: 60, + reportableChange: 0, + }, + ]; await endpoint1.configureReporting('genOnOff', payload); await endpoint2.configureReporting('genOnOff', payload); await endpoint10.configureReporting('haDiagnostic', payloadDiagnostic); @@ -342,10 +337,8 @@ const definitions: Definition[] = [ e.switch().withEndpoint('l2'), e.power_on_behavior().withEndpoint('l2'), e.enum('switch_type', ea.ALL, switchTypeValues).withEndpoint('l2'), - e.numeric('last_message_lqi', ea.STATE).withUnit('lqi') - .withDescription('LQI seen by the device').withValueMin(0).withValueMax(255), - e.numeric('last_message_rssi', ea.STATE).withUnit('dB') - .withDescription('RSSI seen by the device').withValueMin(-128).withValueMax(127), + e.numeric('last_message_lqi', ea.STATE).withUnit('lqi').withDescription('LQI seen by the device').withValueMin(0).withValueMax(255), + e.numeric('last_message_rssi', ea.STATE).withUnit('dB').withDescription('RSSI seen by the device').withValueMin(-128).withValueMax(127), ], }, { @@ -358,31 +351,39 @@ const definitions: Definition[] = [ configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 64635]); - const payload = [{ - attribute: 'onOff', - minimumReportInterval: 1, - maximumReportInterval: 3600, - reportableChange: 0, - }]; + const payload = [ + { + attribute: 'onOff', + minimumReportInterval: 1, + maximumReportInterval: 3600, + reportableChange: 0, + }, + ]; await endpoint.configureReporting('genOnOff', payload); - await endpoint.configureReporting(64635, [{ - attribute: {ID: 0x000a, type: 0x21}, - minimumReportInterval: 5, - maximumReportInterval: 60, - reportableChange: 0, - }]); - await endpoint.configureReporting(64635, [{ - attribute: {ID: 0x000e, type: 0x23}, - minimumReportInterval: 5, - maximumReportInterval: 60, - reportableChange: 0, - }]); - await endpoint.configureReporting(64635, [{ - attribute: {ID: 0x0003, type: 0x21}, - minimumReportInterval: 5, - maximumReportInterval: 5, - reportableChange: 0, - }]); + await endpoint.configureReporting(64635, [ + { + attribute: {ID: 0x000a, type: 0x21}, + minimumReportInterval: 5, + maximumReportInterval: 60, + reportableChange: 0, + }, + ]); + await endpoint.configureReporting(64635, [ + { + attribute: {ID: 0x000e, type: 0x23}, + minimumReportInterval: 5, + maximumReportInterval: 60, + reportableChange: 0, + }, + ]); + await endpoint.configureReporting(64635, [ + { + attribute: {ID: 0x0003, type: 0x21}, + minimumReportInterval: 5, + maximumReportInterval: 5, + reportableChange: 0, + }, + ]); await endpoint.read(64635, [0, 1, 2, 3]); await endpoint.read(64635, [4, 5, 11, 15]); }, @@ -392,25 +393,27 @@ const definitions: Definition[] = [ e.numeric('rms_voltage', ea.STATE).withUnit('V').withDescription('RMS voltage'), e.numeric('active_power', ea.STATE).withUnit('W').withDescription('Active power'), e.numeric('consumed_energy', ea.STATE).withUnit('W*h').withDescription('Consumed energy'), - e.binary('alarm_voltage_min', ea.ALL, true, false) + e + .binary('alarm_voltage_min', ea.ALL, true, false) .withDescription('Indicates if the alarm is triggered on the voltage drop below the limit, allows to reset alarms'), - e.binary('alarm_voltage_max', ea.ALL, true, false) + e + .binary('alarm_voltage_max', ea.ALL, true, false) .withDescription('Indicates if the alarm is triggered on the voltage rise above the limit, allows to reset alarms'), - e.binary('alarm_power_max', ea.ALL, true, false) + e + .binary('alarm_power_max', ea.ALL, true, false) .withDescription('Indicates if the alarm is triggered on the active power rise above the limit, allows to reset alarms'), - e.binary('alarm_consumed_energy', ea.ALL, true, false) - .withDescription( - 'Indicates if the alarm is triggered when the consumption energy limit is reached, allows to reset alarms'), - e.numeric('voltage_min', ea.ALL).withValueMin(0).withValueMax(253) - .withDescription('Minimum allowable voltage limit for alarms.'), - e.numeric('voltage_max', ea.ALL).withValueMin(0).withValueMax(253) - .withDescription('Maximum allowable voltage limit for alarms.'), - e.numeric('power_max', ea.ALL).withValueMin(0).withValueMax(65534) - .withDescription('Maximum allowable power limit for alarms.'), - e.numeric('consumed_energy_limit', ea.ALL).withValueMin(0).withValueMax(65534) + e + .binary('alarm_consumed_energy', ea.ALL, true, false) + .withDescription('Indicates if the alarm is triggered when the consumption energy limit is reached, allows to reset alarms'), + e.numeric('voltage_min', ea.ALL).withValueMin(0).withValueMax(253).withDescription('Minimum allowable voltage limit for alarms.'), + e.numeric('voltage_max', ea.ALL).withValueMin(0).withValueMax(253).withDescription('Maximum allowable voltage limit for alarms.'), + e.numeric('power_max', ea.ALL).withValueMin(0).withValueMax(65534).withDescription('Maximum allowable power limit for alarms.'), + e + .numeric('consumed_energy_limit', ea.ALL) + .withValueMin(0) + .withValueMax(65534) .withDescription('Limit of electric energy consumption in kW*h. 0 value represents no limit'), - e.numeric('rssi', ea.STATE).withUnit('dB') - .withDescription('RSSI seen by the device').withValueMin(-128).withValueMax(127), + e.numeric('rssi', ea.STATE).withUnit('dB').withDescription('RSSI seen by the device').withValueMin(-128).withValueMax(127), ], ota: ota.zigbeeOTA, }, diff --git a/src/devices/philips.ts b/src/devices/philips.ts index 1cfe8ce04ab78..c235b11d9d4e2 100644 --- a/src/devices/philips.ts +++ b/src/devices/philips.ts @@ -1,13 +1,14 @@ import {Zcl} from 'zigbee-herdsman'; -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; + import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; +import {deviceEndpoints, quirkCheckinInterval, identify} from '../lib/modernExtend'; import * as ota from '../lib/ota'; -import * as reporting from '../lib/reporting'; import {philipsOnOff, philipsLight, philipsFz, philipsTz} from '../lib/philips'; -import {deviceEndpoints, quirkCheckinInterval, identify} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -1126,7 +1127,7 @@ const definitions: Definition[] = [ vendor: 'Philips', description: 'Hue Dymera indoor and outdoor wall light', extend: [ - deviceEndpoints({endpoints: {'top': 11, 'bottom': 12}}), + deviceEndpoints({endpoints: {top: 11, bottom: 12}}), philipsLight({colorTemp: {range: [153, 500]}, color: true, endpointNames: ['top', 'bottom']}), ], }, @@ -1137,7 +1138,7 @@ const definitions: Definition[] = [ description: 'Hue white and color ambiance E26/E27', extend: [philipsLight({colorTemp: {range: [153, 500]}, color: true})], endpoint: (device) => { - return {'default': 11}; + return {default: 11}; }, }, { @@ -2176,9 +2177,20 @@ const definitions: Definition[] = [ description: 'Hue wall switch module', fromZigbee: [fz.battery, fz.hue_wall_switch_device_mode, fz.hue_wall_switch, fz.command_toggle, fz.command_move, fz.command_stop], exposes: [ - e.battery(), e.action(['left_press', 'left_press_release', 'right_press', 'right_press_release', - 'left_hold', 'left_hold_release', 'right_hold', 'right_hold_release', 'toggle']), - e.enum('device_mode', ea.ALL, ['single_rocker', 'single_push_button', 'dual_rocker', 'dual_push_button'])], + e.battery(), + e.action([ + 'left_press', + 'left_press_release', + 'right_press', + 'right_press_release', + 'left_hold', + 'left_hold_release', + 'right_hold', + 'right_hold_release', + 'toggle', + ]), + e.enum('device_mode', ea.ALL, ['single_rocker', 'single_push_button', 'dual_rocker', 'dual_push_button']), + ], toZigbee: [tz.hue_wall_switch_device_mode], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -2193,12 +2205,36 @@ const definitions: Definition[] = [ model: '324131092621', vendor: 'Philips', description: 'Hue dimmer switch', - fromZigbee: [fz.ignore_command_on, fz.ignore_command_off, fz.ignore_command_step, fz.ignore_command_stop, - legacy.fz.hue_dimmer_switch, fz.battery], - exposes: [e.battery(), e.action(['on_press', 'on_press_release', 'on_hold', 'on_hold_release', 'up_press', - 'up_press_release', 'up_hold', 'up_hold_release', 'down_press', 'down_press_release', 'down_hold', - 'down_hold_release', 'off_press', 'off_press_release', 'off_hold', 'off_hold_release']), - e.action_duration()], + fromZigbee: [ + fz.ignore_command_on, + fz.ignore_command_off, + fz.ignore_command_step, + fz.ignore_command_stop, + legacy.fz.hue_dimmer_switch, + fz.battery, + ], + exposes: [ + e.battery(), + e.action([ + 'on_press', + 'on_press_release', + 'on_hold', + 'on_hold_release', + 'up_press', + 'up_press_release', + 'up_hold', + 'up_hold_release', + 'down_press', + 'down_press_release', + 'down_hold', + 'down_hold_release', + 'off_press', + 'off_press_release', + 'off_hold', + 'off_hold_release', + ]), + e.action_duration(), + ], toZigbee: [], configure: async (device, coordinatorEndpoint) => { const endpoint1 = device.getEndpoint(1); @@ -2206,12 +2242,12 @@ const definitions: Definition[] = [ const endpoint2 = device.getEndpoint(2); const options = {manufacturerCode: Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V, disableDefaultResponse: true}; - await endpoint2.write('genBasic', {0x0031: {value: 0x000B, type: 0x19}}, options); + await endpoint2.write('genBasic', {0x0031: {value: 0x000b, type: 0x19}}, options); await reporting.bind(endpoint2, coordinatorEndpoint, ['manuSpecificPhilips', 'genPowerCfg']); await reporting.batteryPercentageRemaining(endpoint2); }, endpoint: (device) => { - return {'ep1': 1, 'ep2': 2}; + return {ep1: 1, ep2: 2}; }, extend: [quirkCheckinInterval('1_HOUR')], ota: ota.zigbeeOTA, @@ -2221,17 +2257,44 @@ const definitions: Definition[] = [ model: '929002398602', vendor: 'Philips', description: 'Hue dimmer switch', - fromZigbee: [fz.ignore_command_on, fz.ignore_command_off, fz.ignore_command_step, fz.ignore_command_stop, - fz.hue_dimmer_switch, fz.battery, fz.command_recall], - exposes: [e.battery(), e.action(['on_press', 'on_hold', 'on_press_release', 'on_hold_release', - 'off_press', 'off_hold', 'off_press_release', 'off_hold_release', 'up_press', 'up_hold', 'up_press_release', 'up_hold_release', - 'down_press', 'down_hold', 'down_press_release', 'down_hold_release', 'recall_0', 'recall_1'])], + fromZigbee: [ + fz.ignore_command_on, + fz.ignore_command_off, + fz.ignore_command_step, + fz.ignore_command_stop, + fz.hue_dimmer_switch, + fz.battery, + fz.command_recall, + ], + exposes: [ + e.battery(), + e.action([ + 'on_press', + 'on_hold', + 'on_press_release', + 'on_hold_release', + 'off_press', + 'off_hold', + 'off_press_release', + 'off_hold_release', + 'up_press', + 'up_hold', + 'up_press_release', + 'up_hold_release', + 'down_press', + 'down_hold', + 'down_press_release', + 'down_hold_release', + 'recall_0', + 'recall_1', + ]), + ], toZigbee: [], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'manuSpecificPhilips', 'genPowerCfg']); const options = {manufacturerCode: Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V, disableDefaultResponse: true}; - await endpoint.write('genBasic', {0x0031: {value: 0x000B, type: 0x19}}, options); + await endpoint.write('genBasic', {0x0031: {value: 0x000b, type: 0x19}}, options); await reporting.batteryPercentageRemaining(endpoint); }, ota: ota.zigbeeOTA, @@ -2249,7 +2312,7 @@ const definitions: Definition[] = [ await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl']); const options = {manufacturerCode: Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V, disableDefaultResponse: true}; - await endpoint.write('genBasic', {0x0031: {value: 0x000B, type: 0x19}}, options); + await endpoint.write('genBasic', {0x0031: {value: 0x000b, type: 0x19}}, options); await reporting.bind(endpoint, coordinatorEndpoint, ['manuSpecificPhilips', 'genPowerCfg']); await reporting.batteryPercentageRemaining(endpoint); }, @@ -2260,15 +2323,28 @@ const definitions: Definition[] = [ model: '9290012607', vendor: 'Philips', description: 'Hue motion sensor', - fromZigbee: [fz.battery, fz.occupancy, fz.temperature, fz.occupancy_timeout, fz.illuminance, - fz.hue_motion_sensitivity, fz.hue_motion_led_indication], - exposes: [e.temperature(), e.occupancy(), e.battery(), e.illuminance_lux(), e.illuminance(), + fromZigbee: [ + fz.battery, + fz.occupancy, + fz.temperature, + fz.occupancy_timeout, + fz.illuminance, + fz.hue_motion_sensitivity, + fz.hue_motion_led_indication, + ], + exposes: [ + e.temperature(), + e.occupancy(), + e.battery(), + e.illuminance_lux(), + e.illuminance(), e.motion_sensitivity_select(['low', 'medium', 'high']), e.binary('led_indication', ea.ALL, true, false).withDescription('Blink green LED on motion detection'), - e.numeric('occupancy_timeout', ea.ALL).withUnit('s').withValueMin(0).withValueMax(65535)], + e.numeric('occupancy_timeout', ea.ALL).withUnit('s').withValueMin(0).withValueMax(65535), + ], toZigbee: [tz.occupancy_timeout, philipsTz.hue_motion_sensitivity, philipsTz.hue_motion_led_indication], endpoint: (device) => { - return {'default': 2, 'ep1': 1, 'ep2': 2}; + return {default: 2, ep1: 1, ep2: 2}; }, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(2); @@ -2289,15 +2365,28 @@ const definitions: Definition[] = [ model: '9290019758', vendor: 'Philips', description: 'Hue motion outdoor sensor', - fromZigbee: [fz.battery, fz.occupancy, fz.temperature, fz.illuminance, fz.occupancy_timeout, - fz.hue_motion_sensitivity, fz.hue_motion_led_indication], - exposes: [e.temperature(), e.occupancy(), e.battery(), e.illuminance_lux(), e.illuminance(), + fromZigbee: [ + fz.battery, + fz.occupancy, + fz.temperature, + fz.illuminance, + fz.occupancy_timeout, + fz.hue_motion_sensitivity, + fz.hue_motion_led_indication, + ], + exposes: [ + e.temperature(), + e.occupancy(), + e.battery(), + e.illuminance_lux(), + e.illuminance(), e.enum('motion_sensitivity', ea.ALL, ['low', 'medium', 'high']), e.binary('led_indication', ea.ALL, true, false).withDescription('Blink green LED on motion detection'), - e.numeric('occupancy_timeout', ea.ALL).withUnit('s').withValueMin(0).withValueMax(65535)], + e.numeric('occupancy_timeout', ea.ALL).withUnit('s').withValueMin(0).withValueMax(65535), + ], toZigbee: [tz.occupancy_timeout, philipsTz.hue_motion_sensitivity, philipsTz.hue_motion_led_indication], endpoint: (device) => { - return {'default': 2, 'ep1': 1, 'ep2': 2}; + return {default: 2, ep1: 1, ep2: 2}; }, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(2); @@ -2367,12 +2456,25 @@ const definitions: Definition[] = [ model: '9290030675', vendor: 'Philips', description: 'Hue motion sensor', - fromZigbee: [fz.battery, fz.occupancy, fz.temperature, fz.occupancy_timeout, fz.illuminance, - fz.hue_motion_sensitivity, fz.hue_motion_led_indication], - exposes: [e.temperature(), e.occupancy(), e.battery(), e.illuminance_lux(), e.illuminance(), + fromZigbee: [ + fz.battery, + fz.occupancy, + fz.temperature, + fz.occupancy_timeout, + fz.illuminance, + fz.hue_motion_sensitivity, + fz.hue_motion_led_indication, + ], + exposes: [ + e.temperature(), + e.occupancy(), + e.battery(), + e.illuminance_lux(), + e.illuminance(), e.enum('motion_sensitivity', ea.ALL, ['low', 'medium', 'high', 'very_high', 'max']), e.binary('led_indication', ea.ALL, true, false).withDescription('Blink green LED on motion detection'), - e.numeric('occupancy_timeout', ea.ALL).withUnit('s').withValueMin(0).withValueMax(65535)], + e.numeric('occupancy_timeout', ea.ALL).withUnit('s').withValueMin(0).withValueMax(65535), + ], toZigbee: [tz.occupancy_timeout, philipsTz.hue_motion_sensitivity, philipsTz.hue_motion_led_indication], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(2); @@ -2392,12 +2494,25 @@ const definitions: Definition[] = [ model: '9290030674', vendor: 'Philips', description: 'Hue motion outdoor sensor', - fromZigbee: [fz.battery, fz.occupancy, fz.temperature, fz.illuminance, fz.occupancy_timeout, - fz.hue_motion_sensitivity, fz.hue_motion_led_indication], - exposes: [e.temperature(), e.occupancy(), e.battery(), e.illuminance_lux(), e.illuminance(), + fromZigbee: [ + fz.battery, + fz.occupancy, + fz.temperature, + fz.illuminance, + fz.occupancy_timeout, + fz.hue_motion_sensitivity, + fz.hue_motion_led_indication, + ], + exposes: [ + e.temperature(), + e.occupancy(), + e.battery(), + e.illuminance_lux(), + e.illuminance(), e.enum('motion_sensitivity', ea.ALL, ['low', 'medium', 'high', 'very_high', 'max']), e.binary('led_indication', ea.ALL, true, false).withDescription('Blink green LED on motion detection'), - e.numeric('occupancy_timeout', ea.ALL).withUnit('s').withValueMin(0).withValueMax(65535)], + e.numeric('occupancy_timeout', ea.ALL).withUnit('s').withValueMin(0).withValueMax(65535), + ], toZigbee: [tz.occupancy_timeout, philipsTz.hue_motion_sensitivity, philipsTz.hue_motion_led_indication], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(2); @@ -2918,30 +3033,59 @@ const definitions: Definition[] = [ description: 'Hue Tap dial switch', fromZigbee: [fz.ignore_command_step, philipsFz.hue_tap_dial, fz.battery, fz.command_step], toZigbee: [], - exposes: [e.battery(), - e.action(['button_1_press', 'button_1_press_release', 'button_1_hold', 'button_1_hold_release', - 'button_2_press', 'button_2_press_release', 'button_2_hold', 'button_2_hold_release', - 'button_3_press', 'button_3_press_release', 'button_3_hold', 'button_3_hold_release', - 'button_4_press', 'button_4_press_release', 'button_4_hold', 'button_4_hold_release', - 'dial_rotate_left_step', 'dial_rotate_left_slow', 'dial_rotate_left_fast', - 'dial_rotate_right_step', 'dial_rotate_right_slow', 'dial_rotate_right_fast', - 'brightness_step_up', 'brightness_step_down']), + exposes: [ + e.battery(), + e.action([ + 'button_1_press', + 'button_1_press_release', + 'button_1_hold', + 'button_1_hold_release', + 'button_2_press', + 'button_2_press_release', + 'button_2_hold', + 'button_2_hold_release', + 'button_3_press', + 'button_3_press_release', + 'button_3_hold', + 'button_3_hold_release', + 'button_4_press', + 'button_4_press_release', + 'button_4_hold', + 'button_4_hold_release', + 'dial_rotate_left_step', + 'dial_rotate_left_slow', + 'dial_rotate_left_fast', + 'dial_rotate_right_step', + 'dial_rotate_right_slow', + 'dial_rotate_right_fast', + 'brightness_step_up', + 'brightness_step_down', + ]), e.enum('action_direction', ea.STATE, ['right', 'left']).withDescription('Direction in which the dial was turned'), - e.enum('action_type', ea.STATE, ['step', 'rotate']) + e + .enum('action_type', ea.STATE, ['step', 'rotate']) .withDescription('Type of the rotation, value in the first message is `step` and in the next messages value is `rotate`'), - e.numeric('action_time', ea.STATE) - .withDescription('value in seconds representing the amount of time the last action took').withValueMin(0).withValueMax(255), - e.numeric('brightness', ea.STATE) - .withDescription('Raw rotation state value of the dial which represents brightness from 0-255').withValueMin(0).withValueMax(255), - e.numeric('action_step_size', ea.STATE) + e + .numeric('action_time', ea.STATE) + .withDescription('value in seconds representing the amount of time the last action took') + .withValueMin(0) + .withValueMax(255), + e + .numeric('brightness', ea.STATE) + .withDescription('Raw rotation state value of the dial which represents brightness from 0-255') + .withValueMin(0) + .withValueMax(255), + e + .numeric('action_step_size', ea.STATE) .withDescription('amount of steps the last action took on the dial exposed as a posive value from 0-255') - .withValueMin(0).withValueMax(255), + .withValueMin(0) + .withValueMax(255), ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'manuSpecificPhilips', 'genPowerCfg']); const options = {manufacturerCode: Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V, disableDefaultResponse: true}; - await endpoint.write('genBasic', {0x0031: {value: 0x000B, type: 0x19}}, options); + await endpoint.write('genBasic', {0x0031: {value: 0x000b, type: 0x19}}, options); await reporting.batteryPercentageRemaining(endpoint); }, ota: ota.zigbeeOTA, diff --git a/src/devices/plaid.ts b/src/devices/plaid.ts index 16a5038260522..f20743e643ba7 100644 --- a/src/devices/plaid.ts +++ b/src/devices/plaid.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; import fz from '../converters/fromZigbee'; import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/plugwise.ts b/src/devices/plugwise.ts index cbdbb62ba2776..eddff3e945b6d 100644 --- a/src/devices/plugwise.ts +++ b/src/devices/plugwise.ts @@ -1,8 +1,8 @@ -import {Definition, Fz, Tz, KeyValue} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition, Fz, Tz, KeyValue} from '../lib/types'; import * as utils from '../lib/utils'; const e = exposes.presets; const ea = exposes.access; @@ -54,9 +54,8 @@ const tzLocal = { plugwise_calibrate_valve: { key: ['calibrate_valve'], convertSet: async (entity, key, value, meta) => { - await entity.command('hvacThermostat', 'plugwiseCalibrateValve', {}, - {srcEndpoint: 11, disableDefaultResponse: true}); - return {state: {'calibrate_valve': value}}; + await entity.command('hvacThermostat', 'plugwiseCalibrateValve', {}, {srcEndpoint: 11, disableDefaultResponse: true}); + return {state: {calibrate_valve: value}}; }, } satisfies Tz.Converter, plugwise_valve_position: { @@ -133,19 +132,25 @@ const definitions: Definition[] = [ await reporting.thermostatTemperature(endpoint); await reporting.thermostatPIHeatingDemand(endpoint); }, - exposes: [e.battery(), - e.numeric('pi_heating_demand', ea.STATE_GET).withValueMin(0).withValueMax(100).withUnit('%') + exposes: [ + e.battery(), + e + .numeric('pi_heating_demand', ea.STATE_GET) + .withValueMin(0) + .withValueMax(100) + .withUnit('%') .withDescription('Position of the valve (= demanded heat) where 0% is fully closed and 100% is fully open'), e.numeric('local_temperature', ea.STATE).withUnit('°C').withDescription('Current temperature measured on the device'), - e.numeric('valve_position', ea.ALL).withValueMin(0).withValueMax(100) - .withDescription('Directly control the radiator valve. The values range from 0 (valve ' + - 'closed) to 100 (valve fully open)'), - e.enum('force', ea.ALL, ['standard', 'high', 'very_high']) + e + .numeric('valve_position', ea.ALL) + .withValueMin(0) + .withValueMax(100) + .withDescription('Directly control the radiator valve. The values range from 0 (valve ' + 'closed) to 100 (valve fully open)'), + e + .enum('force', ea.ALL, ['standard', 'high', 'very_high']) .withDescription('How hard the motor pushes the valve. The closer to the boiler, the higher the force needed'), - e.enum('radio_strength', ea.ALL, ['normal', 'high']) - .withDescription('Transmits with higher power when range is not sufficient'), - e.binary('calibrate_valve', ea.STATE_SET, 'calibrate', 'idle') - .withDescription('Calibrates valve on next wakeup'), + e.enum('radio_strength', ea.ALL, ['normal', 'high']).withDescription('Transmits with higher power when range is not sufficient'), + e.binary('calibrate_valve', ea.STATE_SET, 'calibrate', 'idle').withDescription('Calibrates valve on next wakeup'), ], }, { @@ -154,18 +159,17 @@ const definitions: Definition[] = [ vendor: 'Plugwise', description: 'Lisa zone thermostat', fromZigbee: [fz.thermostat, fz.temperature, fz.battery], - toZigbee: [ - tz.thermostat_system_mode, - tz.thermostat_occupied_heating_setpoint, - ], + toZigbee: [tz.thermostat_system_mode, tz.thermostat_occupied_heating_setpoint], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genBasic', 'genPowerCfg', 'hvacThermostat']); await reporting.batteryPercentageRemaining(endpoint); await reporting.thermostatTemperature(endpoint); }, - exposes: [e.battery(), - e.climate() + exposes: [ + e.battery(), + e + .climate() .withSetpoint('occupied_heating_setpoint', 5, 30, 0.5, ea.ALL) .withLocalTemperature(ea.STATE) .withSystemMode(['off', 'auto'], ea.ALL), diff --git a/src/devices/profalux.ts b/src/devices/profalux.ts index 92eb9e7bb3fa0..022ea88ca9f41 100644 --- a/src/devices/profalux.ts +++ b/src/devices/profalux.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; import * as exposes from '../lib/exposes'; -import * as reporting from '../lib/reporting'; import {logger} from '../lib/logger'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const NS = 'zhc:profalux'; const e = exposes.presets; @@ -12,8 +12,12 @@ const ea = exposes.access; const definitions: Definition[] = [ { zigbeeModel: ['MAI-ZTS'], - fingerprint: [{manufacturerID: 4368, endpoints: [{ID: 1, profileID: 260, deviceID: 513, inputClusters: [0, 3, 21], - outputClusters: [3, 4, 5, 6, 8, 256, 64544, 64545]}]}], + fingerprint: [ + { + manufacturerID: 4368, + endpoints: [{ID: 1, profileID: 260, deviceID: 513, inputClusters: [0, 3, 21], outputClusters: [3, 4, 5, 6, 8, 256, 64544, 64545]}], + }, + ], model: 'NB102', vendor: 'Profalux', description: 'Cover remote', @@ -59,10 +63,9 @@ const definitions: Definition[] = [ }, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(2); - await endpoint.read('manuSpecificProfalux1', ['motorCoverType']) - .catch((e) => { - logger.warning(`Failed to read zigbee attributes: ${e}`, NS); - }); + await endpoint.read('manuSpecificProfalux1', ['motorCoverType']).catch((e) => { + logger.warning(`Failed to read zigbee attributes: ${e}`, NS); + }); const coverType = endpoint.getClusterAttributeValue('manuSpecificProfalux1', 'motorCoverType'); // logger.debug(`Profalux '${device.ieeeAddr}' setup as cover type '${coverType)}'`, NS); await reporting.bind(endpoint, coordinatorEndpoint, ['closuresWindowCovering']); @@ -80,8 +83,14 @@ const definitions: Definition[] = [ // expose closuresWindowCovering and need to use genLevelCtrl // instead. Sniffing a remote would be welcome to confirm that this // is the right thing to do. - fingerprint: [{manufacturerID: 4368, endpoints: [{ID: 1, profileID: 260, deviceID: 512, - inputClusters: [0, 3, 4, 5, 6, 8, 10, 21, 256, 64544, 64545], outputClusters: [3, 64544]}]}], + fingerprint: [ + { + manufacturerID: 4368, + endpoints: [ + {ID: 1, profileID: 260, deviceID: 512, inputClusters: [0, 3, 4, 5, 6, 8, 10, 21, 256, 64544, 64545], outputClusters: [3, 64544]}, + ], + }, + ], model: 'NSAV061', vendor: 'Profalux', description: 'Cover', @@ -111,11 +120,28 @@ const definitions: Definition[] = [ // the cover and don't seem to communicate with the coordinator, so // nothing is likely to be doable in Z2M. fingerprint: [ - {type: 'EndDevice', manufacturerName: 'Profalux', modelID: 'MAI-ZTS', manufacturerID: 4368, endpoints: [ - {ID: 1, profileID: 260, deviceID: 513, inputClusters: [0, 3, 21, 64514, 64544], outputClusters: [3, 4, 5, 6, 8, 256, 64544, 64545]}, - {ID: 2, profileID: 260, deviceID: 515, inputClusters: [0, 1, 3, 9, 21, 32, 64514, 64544], - outputClusters: [3, 4, 5, 25, 258, 64544, 64545]}, - ]}, + { + type: 'EndDevice', + manufacturerName: 'Profalux', + modelID: 'MAI-ZTS', + manufacturerID: 4368, + endpoints: [ + { + ID: 1, + profileID: 260, + deviceID: 513, + inputClusters: [0, 3, 21, 64514, 64544], + outputClusters: [3, 4, 5, 6, 8, 256, 64544, 64545], + }, + { + ID: 2, + profileID: 260, + deviceID: 515, + inputClusters: [0, 1, 3, 9, 21, 32, 64514, 64544], + outputClusters: [3, 4, 5, 25, 258, 64544, 64545], + }, + ], + }, ], model: 'MAI-ZTM20C', vendor: 'Profalux', diff --git a/src/devices/prolight.ts b/src/devices/prolight.ts index fb01da3834d4f..ec94585c208aa 100644 --- a/src/devices/prolight.ts +++ b/src/devices/prolight.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; import fz from '../converters/fromZigbee'; import * as exposes from '../lib/exposes'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const e = exposes.presets; @@ -47,11 +47,32 @@ const definitions: Definition[] = [ vendor: 'Prolight', description: 'Remote control', toZigbee: [], - fromZigbee: [fz.command_on, fz.command_off, fz.command_move_to_level, fz.command_move, fz.command_stop, - fz.command_move_to_color_temp, fz.command_move_to_color, fz.command_move_color_temperature, fz.battery], - exposes: [e.battery(), e.action(['on', 'off', 'color_temperature_move', 'color_temperature_move_up', - 'color_temperature_move_down', 'color_move', 'brightness_move_up', 'brightness_move_down', - 'brightness_stop', 'brightness_move_to_level'])], + fromZigbee: [ + fz.command_on, + fz.command_off, + fz.command_move_to_level, + fz.command_move, + fz.command_stop, + fz.command_move_to_color_temp, + fz.command_move_to_color, + fz.command_move_color_temperature, + fz.battery, + ], + exposes: [ + e.battery(), + e.action([ + 'on', + 'off', + 'color_temperature_move', + 'color_temperature_move_up', + 'color_temperature_move_down', + 'color_move', + 'brightness_move_up', + 'brightness_move_down', + 'brightness_stop', + 'brightness_move_to_level', + ]), + ], }, ]; diff --git a/src/devices/qa.ts b/src/devices/qa.ts index 32b01c864198a..3aaac8227ed72 100644 --- a/src/devices/qa.ts +++ b/src/devices/qa.ts @@ -1,11 +1,11 @@ +import fz from '../converters/fromZigbee'; +import tz from '../converters/toZigbee'; import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; -import * as tuya from '../lib/tuya'; +import {deviceEndpoints, actionEnumLookup, light} from '../lib/modernExtend'; import * as reporting from '../lib/reporting'; -import fz from '../converters/fromZigbee'; -import tz from '../converters/toZigbee'; +import * as tuya from '../lib/tuya'; import {Definition} from '../lib/types'; -import {deviceEndpoints, actionEnumLookup, light} from '../lib/modernExtend'; const e = exposes.presets; const ea = exposes.access; @@ -17,17 +17,20 @@ const definitions: Definition[] = [ description: '3 channel scene switch', extend: [ tuya.modernExtend.tuyaMagicPacket(), - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2, 'l3': 3}}), + deviceEndpoints({endpoints: {l1: 1, l2: 2, l3: 3}}), tuya.modernExtend.tuyaOnOff({endpoints: ['l1', 'l2', 'l3'], powerOnBehavior2: true, backlightModeOffOn: true}), actionEnumLookup({ cluster: 'genOnOff', commands: ['commandTuyaAction'], attribute: 'value', - actionLookup: {'button': 0}, + actionLookup: {button: 0}, buttonLookup: { - '1_up': 4, '1_down': 1, - '2_up': 5, '2_down': 2, - '3_up': 6, '3_down': 3, + '1_up': 4, + '1_down': 1, + '2_up': 5, + '2_down': 2, + '3_up': 6, + '3_down': 3, }, }), ], @@ -39,17 +42,20 @@ const definitions: Definition[] = [ description: '1 channel scene switch', extend: [ tuya.modernExtend.tuyaMagicPacket(), - deviceEndpoints({endpoints: {'l1': 1}}), + deviceEndpoints({endpoints: {l1: 1}}), tuya.modernExtend.tuyaOnOff({endpoints: ['l1'], powerOnBehavior2: true, backlightModeOffOn: true}), actionEnumLookup({ cluster: 'genOnOff', commands: ['commandTuyaAction'], attribute: 'value', - actionLookup: {'button': 0}, + actionLookup: {button: 0}, buttonLookup: { - '1_up': 4, '1_down': 1, - '2_up': 5, '2_down': 2, - '3_up': 6, '3_down': 3, + '1_up': 4, + '1_down': 1, + '2_up': 5, + '2_down': 2, + '3_up': 6, + '3_down': 3, }, }), ], @@ -61,17 +67,20 @@ const definitions: Definition[] = [ description: '2 channel scene switch', extend: [ tuya.modernExtend.tuyaMagicPacket(), - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2}}), + deviceEndpoints({endpoints: {l1: 1, l2: 2}}), tuya.modernExtend.tuyaOnOff({endpoints: ['l1', 'l2'], powerOnBehavior2: true, backlightModeOffOn: true}), actionEnumLookup({ cluster: 'genOnOff', commands: ['commandTuyaAction'], attribute: 'value', - actionLookup: {'button': 0}, + actionLookup: {button: 0}, buttonLookup: { - '1_up': 4, '1_down': 1, - '2_up': 5, '2_down': 2, - '3_up': 6, '3_down': 3, + '1_up': 4, + '1_down': 1, + '2_up': 5, + '2_down': 2, + '3_up': 6, + '3_down': 3, }, }), ], @@ -81,11 +90,7 @@ const definitions: Definition[] = [ model: 'QARZ1DC', vendor: 'QA', description: '1 channel switch', - extend: [ - tuya.modernExtend.tuyaMagicPacket(), - deviceEndpoints({endpoints: {'l1': 1}}), - tuya.modernExtend.tuyaOnOff({endpoints: ['l1']}), - ], + extend: [tuya.modernExtend.tuyaMagicPacket(), deviceEndpoints({endpoints: {l1: 1}}), tuya.modernExtend.tuyaOnOff({endpoints: ['l1']})], }, { fingerprint: tuya.fingerprint('TS0001', ['_TZ3000_gtdswg8k']), @@ -94,7 +99,7 @@ const definitions: Definition[] = [ description: '1 channel long range switch', extend: [ tuya.modernExtend.tuyaMagicPacket(), - deviceEndpoints({endpoints: {'l1': 1}}), + deviceEndpoints({endpoints: {l1: 1}}), tuya.modernExtend.tuyaOnOff({endpoints: ['l1'], switchType: true}), ], }, @@ -105,7 +110,7 @@ const definitions: Definition[] = [ description: '2 channel long range switch', extend: [ tuya.modernExtend.tuyaMagicPacket(), - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2}}), + deviceEndpoints({endpoints: {l1: 1, l2: 2}}), tuya.modernExtend.tuyaOnOff({endpoints: ['l1', 'l2'], switchType: true}), ], }, @@ -116,7 +121,7 @@ const definitions: Definition[] = [ description: '3 channel long range switch', extend: [ tuya.modernExtend.tuyaMagicPacket(), - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2, 'l3': 3}}), + deviceEndpoints({endpoints: {l1: 1, l2: 2, l3: 3}}), tuya.modernExtend.tuyaOnOff({endpoints: ['l1', 'l2', 'l3'], switchType: true}), ], }, @@ -127,67 +132,61 @@ const definitions: Definition[] = [ description: '4 channel long range switch', extend: [ tuya.modernExtend.tuyaMagicPacket(), - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4}}), + deviceEndpoints({endpoints: {l1: 1, l2: 2, l3: 3, l4: 4}}), tuya.modernExtend.tuyaOnOff({endpoints: ['l1', 'l2', 'l3', 'l4'], switchType: true}), ], }, { - fingerprint: tuya.fingerprint('TS0001', [ - '_TZ3000_dov0a3p1', - ]), + fingerprint: tuya.fingerprint('TS0001', ['_TZ3000_dov0a3p1']), model: 'QAT42Z1H', vendor: 'QA', description: '1 channel wall switch', extend: [ tuya.modernExtend.tuyaMagicPacket(), - deviceEndpoints({endpoints: {'l1': 1}}), + deviceEndpoints({endpoints: {l1: 1}}), tuya.modernExtend.tuyaOnOff({endpoints: ['l1'], backlightModeOffOn: true}), ], }, { - fingerprint: tuya.fingerprint('TS0002', [ - '_TZ3000_gkesadus', - ]), + fingerprint: tuya.fingerprint('TS0002', ['_TZ3000_gkesadus']), model: 'QAT42Z2H', vendor: 'QA', description: '2 channel wall switch', extend: [ tuya.modernExtend.tuyaMagicPacket(), - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2}}), + deviceEndpoints({endpoints: {l1: 1, l2: 2}}), tuya.modernExtend.tuyaOnOff({endpoints: ['l1', 'l2'], backlightModeOffOn: true}), ], }, { - fingerprint: tuya.fingerprint('TS0003', [ - '_TZ3000_pmsxmttq', '_TZ3000_0q5fjqgw', - ]), + fingerprint: tuya.fingerprint('TS0003', ['_TZ3000_pmsxmttq', '_TZ3000_0q5fjqgw']), model: 'QAT42Z3H', vendor: 'QA', description: '3 channel wall switch', extend: [ tuya.modernExtend.tuyaMagicPacket(), - deviceEndpoints({endpoints: {'left': 1, 'center': 2, 'right': 3}}), + deviceEndpoints({endpoints: {left: 1, center: 2, right: 3}}), tuya.modernExtend.tuyaOnOff({endpoints: ['left', 'center', 'right'], backlightModeOffOn: true}), ], }, { - fingerprint: tuya.fingerprint('TS0601', [ - '_TZE204_4cl0dzt4', - ]), + fingerprint: tuya.fingerprint('TS0601', ['_TZE204_4cl0dzt4']), model: 'QAT44Z6H', vendor: 'QA', description: '6 channel wall switch', - exposes: [e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), + exposes: [ + e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l2').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l3').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l4').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l5').setAccess('state', ea.STATE_SET), - e.switch().withEndpoint('l6').setAccess('state', ea.STATE_SET)], + e.switch().withEndpoint('l6').setAccess('state', ea.STATE_SET), + ], fromZigbee: [fz.ignore_basic_report, legacy.fz.tuya_switch], toZigbee: [legacy.tz.tuya_switch_state], meta: {multiEndpoint: true}, endpoint: (device) => { - return {'l1': 1, 'l2': 1, 'l3': 1, 'l4': 1, 'l5': 1, 'l6': 1}; + return {l1: 1, l2: 1, l3: 1, l4: 1, l5: 1, l6: 1}; }, configure: async (device, coordinatorEndpoint, logger) => { await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff']); @@ -202,21 +201,21 @@ const definitions: Definition[] = [ }, }, { - fingerprint: tuya.fingerprint('TS0601', [ - '_TZE204_kyzjsjo3', - ]), + fingerprint: tuya.fingerprint('TS0601', ['_TZE204_kyzjsjo3']), model: 'QAT44Z4H', vendor: 'QA', description: '4 channel wall switch', - exposes: [e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), + exposes: [ + e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l2').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l3').setAccess('state', ea.STATE_SET), - e.switch().withEndpoint('l4').setAccess('state', ea.STATE_SET)], + e.switch().withEndpoint('l4').setAccess('state', ea.STATE_SET), + ], fromZigbee: [fz.ignore_basic_report, legacy.fz.tuya_switch], toZigbee: [legacy.tz.tuya_switch_state], meta: {multiEndpoint: true}, endpoint: (device) => { - return {'l1': 1, 'l2': 1, 'l3': 1, 'l4': 1}; + return {l1: 1, l2: 1, l3: 1, l4: 1}; }, configure: async (device, coordinatorEndpoint, logger) => { await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff']); @@ -246,8 +245,9 @@ const definitions: Definition[] = [ description: 'Dimmer 2 channel', extend: [ tuya.modernExtend.tuyaMagicPacket(), - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2}}), - light({endpointNames: ['l1', 'l2'], powerOnBehavior: false, configureReporting: true, effect: false})], + deviceEndpoints({endpoints: {l1: 1, l2: 2}}), + light({endpointNames: ['l1', 'l2'], powerOnBehavior: false, configureReporting: true, effect: false}), + ], fromZigbee: [tuya.fz.power_on_behavior_1, fz.TS110E_switch_type, fz.TS110E, fz.on_off], toZigbee: [tz.TS110E_light_onoff_brightness, tuya.tz.power_on_behavior_1, tz.TS110E_options], exposes: [e.power_on_behavior(), tuya.exposes.switchType()], diff --git a/src/devices/qmotion.ts b/src/devices/qmotion.ts index 00085dfa91b2a..01b3983643fd3 100644 --- a/src/devices/qmotion.ts +++ b/src/devices/qmotion.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; import * as reporting from '../lib/reporting'; diff --git a/src/devices/qoto.ts b/src/devices/qoto.ts index 44e72db171944..4057a6e201ab4 100644 --- a/src/devices/qoto.ts +++ b/src/devices/qoto.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; import fz from '../converters/fromZigbee'; import * as exposes from '../lib/exposes'; -import * as tuya from '../lib/tuya'; import * as legacy from '../lib/legacy'; +import * as tuya from '../lib/tuya'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -16,15 +16,31 @@ const definitions: Definition[] = [ toZigbee: [legacy.tz.valve_state, legacy.tz.shutdown_timer, legacy.tz.valve_state_auto_shutdown], exposes: [ e.numeric('water_flow', ea.STATE).withUnit('%').withValueMin(0).withDescription('Current water flow in %.'), - e.numeric('last_watering_duration', ea.STATE).withUnit('sec').withValueMin(0) - .withDescription('Last watering duration in seconds.'), - e.numeric('remaining_watering_time', ea.STATE).withUnit('sec').withValueMin(0) + e.numeric('last_watering_duration', ea.STATE).withUnit('sec').withValueMin(0).withDescription('Last watering duration in seconds.'), + e + .numeric('remaining_watering_time', ea.STATE) + .withUnit('sec') + .withValueMin(0) .withDescription('Remaning watering time (for auto shutdown). Updates every minute, and every 10s in the last minute.'), - e.numeric('valve_state', ea.STATE_SET).withValueMin(0).withValueMax(100).withValueStep(5).withUnit('%') + e + .numeric('valve_state', ea.STATE_SET) + .withValueMin(0) + .withValueMax(100) + .withValueStep(5) + .withUnit('%') .withDescription('Set valve to %.'), - e.numeric('valve_state_auto_shutdown', ea.STATE_SET).withValueMin(0).withValueMax(100).withValueStep(5).withUnit('%') + e + .numeric('valve_state_auto_shutdown', ea.STATE_SET) + .withValueMin(0) + .withValueMax(100) + .withValueStep(5) + .withUnit('%') .withDescription('Set valve to % with auto shutdown. Must be set before setting the shutdown timer.'), - e.numeric('shutdown_timer', ea.STATE_SET).withValueMin(0).withValueMax(14400).withUnit('sec') + e + .numeric('shutdown_timer', ea.STATE_SET) + .withValueMin(0) + .withValueMax(14400) + .withUnit('sec') .withDescription('Auto shutdown in seconds. Must be set after setting valve state auto shutdown.'), e.battery(), ], diff --git a/src/devices/quotra.ts b/src/devices/quotra.ts index a09bb2c3cd43a..5dbe10470f010 100644 --- a/src/devices/quotra.ts +++ b/src/devices/quotra.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/rademacher.ts b/src/devices/rademacher.ts index 15a7ef6e29d00..d7251e6fac1c9 100644 --- a/src/devices/rademacher.ts +++ b/src/devices/rademacher.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/rgb_genie.ts b/src/devices/rgb_genie.ts index 6f6c3ed2bb5dd..5962159235cce 100644 --- a/src/devices/rgb_genie.ts +++ b/src/devices/rgb_genie.ts @@ -1,10 +1,10 @@ -import {Definition, Fz} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition, Fz} from '../lib/types'; const e = exposes.presets; -import * as utils from '../lib/utils'; import {light} from '../lib/modernExtend'; +import * as utils from '../lib/utils'; const fzLocal = { // ZB-1026 requires separate on/off converters since it re-uses the transaction number @@ -42,8 +42,7 @@ const definitions: Definition[] = [ model: 'ZB-5001', vendor: 'RGB Genie', description: 'Zigbee 3.0 remote control', - fromZigbee: [fz.command_recall, fzLocal.ZB1026_command_on, fzLocal.ZB1026_command_off, - fz.command_move, fz.command_stop, fz.battery], + fromZigbee: [fz.command_recall, fzLocal.ZB1026_command_on, fzLocal.ZB1026_command_off, fz.command_move, fz.command_stop, fz.battery], exposes: [e.battery(), e.action(['recall_*', 'on', 'off', 'brightness_stop', 'brightness_move_up', 'brightness_move_down'])], toZigbee: [], }, @@ -53,8 +52,19 @@ const definitions: Definition[] = [ vendor: 'RGB Genie', description: 'Micro remote and dimmer with single scene recall', fromZigbee: [fz.battery, fz.command_on, fz.command_off, fz.command_step, fz.command_move, fz.command_stop, fz.command_recall], - exposes: [e.battery(), e.action(['on', 'off', 'brightness_step_up', 'brightness_step_down', 'brightness_move_up', - 'brightness_move_down', 'brightness_stop', 'recall_*'])], + exposes: [ + e.battery(), + e.action([ + 'on', + 'off', + 'brightness_step_up', + 'brightness_step_down', + 'brightness_move_up', + 'brightness_move_down', + 'brightness_stop', + 'recall_*', + ]), + ], toZigbee: [], meta: {battery: {dontDividePercentage: true}}, configure: async (device, coordinatorEndpoint) => { @@ -69,12 +79,36 @@ const definitions: Definition[] = [ model: 'ZB-5122', vendor: 'RGB Genie', description: 'Micro remote and color dimmer with single scene recall', - fromZigbee: [fz.battery, fz.command_on, fz.command_off, fz.command_step, fz.command_move, - fz.command_stop, fz.command_recall, fz.command_move_to_color, fz.command_move_to_color_temp, fz.command_move_hue, - fz.command_move_color_temperature], - exposes: [e.battery(), e.action(['on', 'off', 'brightness_step_up', 'brightness_step_down', 'brightness_move_up', - 'brightness_move_down', 'brightness_stop', 'recall_*', 'color_temperature_move_up', 'color_temperature_move_down', - 'hue_move', 'hue_stop'])], + fromZigbee: [ + fz.battery, + fz.command_on, + fz.command_off, + fz.command_step, + fz.command_move, + fz.command_stop, + fz.command_recall, + fz.command_move_to_color, + fz.command_move_to_color_temp, + fz.command_move_hue, + fz.command_move_color_temperature, + ], + exposes: [ + e.battery(), + e.action([ + 'on', + 'off', + 'brightness_step_up', + 'brightness_step_down', + 'brightness_move_up', + 'brightness_move_down', + 'brightness_stop', + 'recall_*', + 'color_temperature_move_up', + 'color_temperature_move_down', + 'hue_move', + 'hue_stop', + ]), + ], toZigbee: [], }, { @@ -82,12 +116,36 @@ const definitions: Definition[] = [ model: 'ZB-3008', vendor: 'RGB Genie', description: '3 scene remote and dimmer ', - fromZigbee: [fz.command_recall, fz.command_move_hue, fz.command_move, fz.command_stop, fz.command_on, fz.command_off, - fz.command_move_to_color_temp, fz.command_move_to_color, fz.command_move_color_temperature], + fromZigbee: [ + fz.command_recall, + fz.command_move_hue, + fz.command_move, + fz.command_stop, + fz.command_on, + fz.command_off, + fz.command_move_to_color_temp, + fz.command_move_to_color, + fz.command_move_color_temperature, + ], toZigbee: [], - exposes: [e.action(['on', 'off', 'brightness_step_up', 'brightness_step_down', 'brightness_move_up', - 'brightness_move_down', 'brightness_stop', 'recall_*', 'hue_move', 'color_temperature_move', 'color_move', - 'color_temperature_move_up', 'color_temperature_move_down', 'hue_stop'])], + exposes: [ + e.action([ + 'on', + 'off', + 'brightness_step_up', + 'brightness_step_down', + 'brightness_move_up', + 'brightness_move_down', + 'brightness_stop', + 'recall_*', + 'hue_move', + 'color_temperature_move', + 'color_move', + 'color_temperature_move_up', + 'color_temperature_move_down', + 'hue_stop', + ]), + ], meta: {multiEndpoint: true}, }, { @@ -95,23 +153,73 @@ const definitions: Definition[] = [ model: 'ZB-3009', vendor: 'RGB Genie', description: '3 scene remote and dimmer ', - fromZigbee: [fz.command_recall, fz.command_move_hue, fz.command_move, fz.command_stop, fz.command_on, fz.command_off, - fz.command_move_to_color_temp, fz.command_move_to_color, fz.command_move_color_temperature], + fromZigbee: [ + fz.command_recall, + fz.command_move_hue, + fz.command_move, + fz.command_stop, + fz.command_on, + fz.command_off, + fz.command_move_to_color_temp, + fz.command_move_to_color, + fz.command_move_color_temperature, + ], toZigbee: [], - exposes: [e.action(['on', 'off', 'brightness_step_up', 'brightness_step_down', 'brightness_move_up', - 'brightness_move_down', 'brightness_stop', 'recall_*', 'hue_move', 'color_temperature_move', 'color_move', - 'color_temperature_move_up', 'color_temperature_move_down', 'hue_stop'])], + exposes: [ + e.action([ + 'on', + 'off', + 'brightness_step_up', + 'brightness_step_down', + 'brightness_move_up', + 'brightness_move_down', + 'brightness_stop', + 'recall_*', + 'hue_move', + 'color_temperature_move', + 'color_move', + 'color_temperature_move_up', + 'color_temperature_move_down', + 'hue_stop', + ]), + ], }, { zigbeeModel: ['RGBgenie ZB-5028'], model: 'ZB-5028', vendor: 'RGB Genie', description: 'RGB remote with 4 endpoints and 3 scene recalls', - fromZigbee: [fz.battery, fz.command_on, fz.command_off, fz.command_step, fz.command_move, fz.command_stop, fz.command_recall, - fz.command_move_hue, fz.command_move_to_color, fz.command_move_to_color_temp], - exposes: [e.battery(), e.action(['on', 'off', 'brightness_step_up', 'brightness_step_down', 'brightness_move_up', - 'brightness_move_down', 'brightness_stop', 'recall_1', 'recall_2', 'recall_3', 'hue_move', 'color_temperature_move', - 'color_move', 'hue_stop'])], + fromZigbee: [ + fz.battery, + fz.command_on, + fz.command_off, + fz.command_step, + fz.command_move, + fz.command_stop, + fz.command_recall, + fz.command_move_hue, + fz.command_move_to_color, + fz.command_move_to_color_temp, + ], + exposes: [ + e.battery(), + e.action([ + 'on', + 'off', + 'brightness_step_up', + 'brightness_step_down', + 'brightness_move_up', + 'brightness_move_down', + 'brightness_stop', + 'recall_1', + 'recall_2', + 'recall_3', + 'hue_move', + 'color_temperature_move', + 'color_move', + 'hue_stop', + ]), + ], toZigbee: [], meta: {multiEndpoint: true, battery: {dontDividePercentage: true}}, configure: async (device, coordinatorEndpoint) => { diff --git a/src/devices/robb.ts b/src/devices/robb.ts index f0290ce5382e9..a96be5faba064 100644 --- a/src/devices/robb.ts +++ b/src/devices/robb.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; import {deviceEndpoints, electricityMeter, light, onOff} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; @@ -42,10 +42,7 @@ const definitions: Definition[] = [ model: 'ROB_200-050-0', vendor: 'ROBB', description: '4 port switch with 2 usb ports (no metering)', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4, 'l5': 5}}), - onOff({endpointNames: ['l1', 'l2', 'l3', 'l4', 'l5']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2, l3: 3, l4: 4, l5: 5}}), onOff({endpointNames: ['l1', 'l2', 'l3', 'l4', 'l5']})], whiteLabel: [{vendor: 'Sunricher', model: 'SR-ZG9023A(EU)'}], }, { @@ -67,10 +64,7 @@ const definitions: Definition[] = [ model: 'ROB_200-011-0', vendor: 'ROBB', description: 'ZigBee AC phase-cut dimmer', - extend: [ - light({configureReporting: true}), - electricityMeter({current: {divisor: 1000}, voltage: {divisor: 10}, power: {divisor: 10}}), - ], + extend: [light({configureReporting: true}), electricityMeter({current: {divisor: 1000}, voltage: {divisor: 10}, power: {divisor: 10}})], }, { zigbeeModel: ['ROB_200-003-0'], @@ -84,7 +78,7 @@ const definitions: Definition[] = [ model: 'ROB_200-003-1', vendor: 'ROBB', description: 'Zigbee AC in wall switch (normal switch)', - extend: [onOff({'powerOnBehavior': false})], + extend: [onOff({powerOnBehavior: false})], }, { zigbeeModel: ['ROB_200-030-0'], @@ -98,11 +92,11 @@ const definitions: Definition[] = [ model: 'ROB_200-014-0', vendor: 'ROBB', description: 'ZigBee AC phase-cut rotary dimmer', - extend: [ - light({configureReporting: true}), - electricityMeter(), + extend: [light({configureReporting: true}), electricityMeter()], + whiteLabel: [ + {vendor: 'YPHIX', model: '50208695'}, + {vendor: 'Samotech', model: 'SM311'}, ], - whiteLabel: [{vendor: 'YPHIX', model: '50208695'}, {vendor: 'Samotech', model: 'SM311'}], }, { zigbeeModel: ['ZG2833K8_EU05', 'ROB_200-007-0'], @@ -110,11 +104,31 @@ const definitions: Definition[] = [ vendor: 'ROBB', description: 'Zigbee 8 button wall switch', fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.battery, fz.ignore_genOta], - exposes: [e.battery(), e.action([ - 'on_1', 'off_1', 'brightness_move_up_1', 'brightness_move_down_1', 'brightness_stop_1', - 'on_2', 'off_2', 'brightness_move_up_2', 'brightness_move_down_2', 'brightness_stop_2', - 'on_3', 'off_3', 'brightness_move_up_3', 'brightness_move_down_3', 'brightness_stop_3', - 'on_4', 'off_4', 'brightness_move_up_4', 'brightness_move_down_4', 'brightness_stop_4'])], + exposes: [ + e.battery(), + e.action([ + 'on_1', + 'off_1', + 'brightness_move_up_1', + 'brightness_move_down_1', + 'brightness_stop_1', + 'on_2', + 'off_2', + 'brightness_move_up_2', + 'brightness_move_down_2', + 'brightness_stop_2', + 'on_3', + 'off_3', + 'brightness_move_up_3', + 'brightness_move_down_3', + 'brightness_stop_3', + 'on_4', + 'off_4', + 'brightness_move_up_4', + 'brightness_move_down_4', + 'brightness_stop_4', + ]), + ], toZigbee: [], meta: {multiEndpoint: true}, whiteLabel: [{vendor: 'Sunricher', model: 'SR-ZG9001K8-DIM'}], @@ -142,11 +156,31 @@ const definitions: Definition[] = [ vendor: 'ROBB', description: 'Zigbee 8 button wall switch', fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.battery, fz.ignore_genOta], - exposes: [e.battery(), e.action([ - 'on_1', 'off_1', 'brightness_move_up_1', 'brightness_move_down_1', 'brightness_stop_1', - 'on_2', 'off_2', 'brightness_move_up_2', 'brightness_move_down_2', 'brightness_stop_2', - 'on_3', 'off_3', 'brightness_move_up_3', 'brightness_move_down_3', 'brightness_stop_3', - 'on_4', 'off_4', 'brightness_move_up_4', 'brightness_move_down_4', 'brightness_stop_4'])], + exposes: [ + e.battery(), + e.action([ + 'on_1', + 'off_1', + 'brightness_move_up_1', + 'brightness_move_down_1', + 'brightness_stop_1', + 'on_2', + 'off_2', + 'brightness_move_up_2', + 'brightness_move_down_2', + 'brightness_stop_2', + 'on_3', + 'off_3', + 'brightness_move_up_3', + 'brightness_move_down_3', + 'brightness_stop_3', + 'on_4', + 'off_4', + 'brightness_move_up_4', + 'brightness_move_down_4', + 'brightness_stop_4', + ]), + ], toZigbee: [], meta: {multiEndpoint: true}, }, @@ -156,9 +190,23 @@ const definitions: Definition[] = [ vendor: 'ROBB', description: 'Zigbee 4 button wall switch', fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.battery], - exposes: [e.battery(), e.action([ - 'on_1', 'off_1', 'stop_1', 'brightness_move_up_1', 'brightness_move_down_1', 'brightness_stop_1', - 'on_2', 'off_2', 'stop_2', 'brightness_move_up_2', 'brightness_move_down_2', 'brightness_stop_2'])], + exposes: [ + e.battery(), + e.action([ + 'on_1', + 'off_1', + 'stop_1', + 'brightness_move_up_1', + 'brightness_move_down_1', + 'brightness_stop_1', + 'on_2', + 'off_2', + 'stop_2', + 'brightness_move_up_2', + 'brightness_move_down_2', + 'brightness_stop_2', + ]), + ], toZigbee: [], meta: {multiEndpoint: true, battery: {dontDividePercentage: true}}, whiteLabel: [{vendor: 'Sunricher', model: 'SR-ZG9001K4-DIM2'}], @@ -169,8 +217,7 @@ const definitions: Definition[] = [ vendor: 'ROBB', description: 'Zigbee 2 button wall switch', fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.battery], - exposes: [e.battery(), e.action([ - 'on_1', 'off_1', 'stop_1', 'brightness_move_up_1', 'brightness_move_down_1', 'brightness_stop_1'])], + exposes: [e.battery(), e.action(['on_1', 'off_1', 'stop_1', 'brightness_move_up_1', 'brightness_move_down_1', 'brightness_stop_1'])], toZigbee: [], meta: {multiEndpoint: true, battery: {dontDividePercentage: true}}, whiteLabel: [{vendor: 'Sunricher', model: 'SR-ZG9001K2-DIM'}], @@ -195,8 +242,7 @@ const definitions: Definition[] = [ model: 'ROB_200-018-0', vendor: 'ROBB', description: 'ZigBee knob smart dimmer', - fromZigbee: [fz.command_on, fz.command_off, fz.command_move_to_level, fz.command_move_to_color_temp, fz.battery, - fz.command_move_to_color], + fromZigbee: [fz.command_on, fz.command_off, fz.command_move_to_level, fz.command_move_to_color_temp, fz.battery, fz.command_move_to_color], exposes: [e.battery(), e.action(['on', 'off', 'brightness_move_to_level', 'color_temperature_move', 'color_move'])], toZigbee: [], meta: {multiEndpoint: true, battery: {dontDividePercentage: true}}, @@ -211,8 +257,7 @@ const definitions: Definition[] = [ toZigbee: [tz.on_off], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); - await reporting.bind(endpoint, coordinatorEndpoint, - ['genOnOff', 'haElectricalMeasurement', 'seMetering', 'msTemperatureMeasurement']); + await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement', 'seMetering', 'msTemperatureMeasurement']); await reporting.onOff(endpoint); await reporting.readEletricalMeasurementMultiplierDivisors(endpoint); await reporting.readMeteringMultiplierDivisor(endpoint); @@ -233,8 +278,7 @@ const definitions: Definition[] = [ toZigbee: [tz.on_off], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); - await reporting.bind(endpoint, coordinatorEndpoint, - ['genOnOff', 'haElectricalMeasurement', 'seMetering', 'msTemperatureMeasurement']); + await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement', 'seMetering', 'msTemperatureMeasurement']); await reporting.onOff(endpoint); await reporting.readEletricalMeasurementMultiplierDivisors(endpoint); await reporting.readMeteringMultiplierDivisor(endpoint); @@ -251,14 +295,42 @@ const definitions: Definition[] = [ model: 'ROB_200-016-0', vendor: 'ROBB', description: 'RGB CCT DIM 3 in 1 Zigbee Remote', - fromZigbee: [fz.battery, fz.command_move_to_color, fz.command_move_to_color_temp, fz.command_move_hue, - fz.command_step, fz.command_recall, fz.command_on, fz.command_off, fz.command_toggle, fz.command_stop, - fz.command_move, fz.command_color_loop_set, fz.command_ehanced_move_to_hue_and_saturation], + fromZigbee: [ + fz.battery, + fz.command_move_to_color, + fz.command_move_to_color_temp, + fz.command_move_hue, + fz.command_step, + fz.command_recall, + fz.command_on, + fz.command_off, + fz.command_toggle, + fz.command_stop, + fz.command_move, + fz.command_color_loop_set, + fz.command_ehanced_move_to_hue_and_saturation, + ], toZigbee: [], - exposes: [e.battery(), e.action([ - 'color_move', 'color_temperature_move', 'hue_move', 'brightness_step_up', 'brightness_step_down', - 'recall_*', 'on', 'off', 'toggle', 'brightness_stop', 'brightness_move_up', 'brightness_move_down', - 'color_loop_set', 'enhanced_move_to_hue_and_saturation', 'hue_stop'])], + exposes: [ + e.battery(), + e.action([ + 'color_move', + 'color_temperature_move', + 'hue_move', + 'brightness_step_up', + 'brightness_step_down', + 'recall_*', + 'on', + 'off', + 'toggle', + 'brightness_stop', + 'brightness_move_up', + 'brightness_move_down', + 'color_loop_set', + 'enhanced_move_to_hue_and_saturation', + 'hue_stop', + ]), + ], }, { zigbeeModel: ['ROB_200-026-0'], @@ -269,7 +341,7 @@ const definitions: Definition[] = [ toZigbee: [tz.on_off, tz.power_on_behavior, tz.electrical_measurement_power], exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), e.energy()], endpoint: (device) => { - return {'l1': 1, 'l2': 2}; + return {l1: 1, l2: 2}; }, meta: {multiEndpoint: true, multiEndpointSkip: ['power', 'energy']}, configure: async (device, coordinatorEndpoint) => { diff --git a/src/devices/roome.ts b/src/devices/roome.ts index 1a1e443302141..b3233f259821d 100644 --- a/src/devices/roome.ts +++ b/src/devices/roome.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; import fz from '../converters/fromZigbee'; import * as exposes from '../lib/exposes'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/rtx.ts b/src/devices/rtx.ts index 3662f4b075024..aed41c38a5ef8 100644 --- a/src/devices/rtx.ts +++ b/src/devices/rtx.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; import * as tuya from '../lib/tuya'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -18,67 +18,103 @@ const definitions: Definition[] = [ description: 'Zigbee smart water valve', onEvent: tuya.onEventSetLocalTime, fromZigbee: [legacy.fz.ZVG1, fz.ignore_basic_report], - toZigbee: [legacy.tz.tuya_switch_state, legacy.tz.ZVG1_weather_delay, legacy.tz.ZVG1_timer, legacy.tz.ZVG1_cycle_timer, - legacy.tz.ZVG1_normal_schedule_timer], - exposes: [e.switch().setAccess('state', ea.STATE_SET), e.battery(), + toZigbee: [ + legacy.tz.tuya_switch_state, + legacy.tz.ZVG1_weather_delay, + legacy.tz.ZVG1_timer, + legacy.tz.ZVG1_cycle_timer, + legacy.tz.ZVG1_normal_schedule_timer, + ], + exposes: [ + e.switch().setAccess('state', ea.STATE_SET), + e.battery(), e.enum('weather_delay', ea.STATE_SET, ['disabled', '24h', '48h', '72h']), e.enum('timer_state', ea.STATE, ['disabled', 'active', 'enabled']), - e.numeric('timer', ea.STATE_SET).withValueMin(0).withValueMax(60).withUnit('min') - .withDescription('Auto off after specific time'), - e.numeric('timer_time_left', ea.STATE).withUnit('min') - .withDescription('Auto off timer time left'), - e.numeric('last_valve_open_duration', ea.STATE).withUnit('min') - .withDescription('Time the valve was open when state on'), - e.numeric('water_consumed', ea.STATE).withUnit('L') - .withDescription('Liters of water consumed'), - e.text('cycle_timer_1', ea.STATE_SET).withDescription('Format 08:00 / 20:00 / 15 / 60 / MoTuWeThFrSaSu / 1 (' + - '08:00 = start time ' + - '20:00 = end time ' + - '15 = irrigation duration in minutes ' + - '60 = pause duration in minutes ' + - 'MoTu..= active weekdays ' + - '1 = deactivate timer with 0)'), - e.text('cycle_timer_2', ea.STATE_SET).withDescription('Format 08:00 / 20:00 / 15 / 60 / MoTuWeThFrSaSu / 1 (' + - '08:00 = start time ' + - '20:00 = end time ' + - '15 = irrigation duration in minutes ' + - '60 = pause duration in minutes ' + - 'MoTu..= active weekdays ' + - '1 = deactivate timer with 0)'), - e.text('cycle_timer_3', ea.STATE_SET).withDescription('Format 08:00 / 20:00 / 15 / 60 / MoTuWeThFrSaSu / 1 (' + - '08:00 = start time ' + - '20:00 = end time ' + - '15 = irrigation duration in minutes ' + - '60 = pause duration in minutes ' + - 'MoTu..= active weekdays ' + - '1 = deactivate timer with 0)'), - e.text('cycle_timer_4', ea.STATE_SET).withDescription('Format 08:00 / 20:00 / 15 / 60 / MoTuWeThFrSaSu / 1 (' + - '08:00 = start time ' + - '20:00 = end time ' + - '15 = irrigation duration in minutes ' + - '60 = pause duration in minutes ' + - 'MoTu..= active weekdays ' + - '1 = deactivate timer with 0)'), - e.text('normal_schedule_timer_1', ea.STATE_SET).withDescription('Format 08:00 / 15 / MoTuWeThFrSaSu / 1 (' + - '08:00 = start time ' + - '15 = duration in minutes ' + - 'MoTu..= active weekdays ' + - '1 = deactivate timer with 0)'), - e.text('normal_schedule_timer_2', ea.STATE_SET).withDescription('Format 08:00 / 15 / MoTuWeThFrSaSu / 1 (' + - '08:00 = start time ' + - '15 = duration in minutes ' + - 'MoTu..= active weekdays ' + - '1 = deactivate timer with 0)'), - e.text('normal_schedule_timer_3', ea.STATE_SET).withDescription('Format 08:00 / 15 / MoTuWeThFrSaSu / 1 (' + - '08:00 = start time ' + - '15 = duration in minutes ' + - 'MoTu..= active weekdays ' + - '1 = deactivate timer with 0)'), - e.text('normal_schedule_timer_4', ea.STATE_SET).withDescription('Format 08:00 / 15 / MoTuWeThFrSaSu / 1 (' + - '08:00 = start time ' + - '15 = duration in minutes ' + - 'MoTu..= active weekdays ' + - '1 = deactivate timer with 0)')], + e.numeric('timer', ea.STATE_SET).withValueMin(0).withValueMax(60).withUnit('min').withDescription('Auto off after specific time'), + e.numeric('timer_time_left', ea.STATE).withUnit('min').withDescription('Auto off timer time left'), + e.numeric('last_valve_open_duration', ea.STATE).withUnit('min').withDescription('Time the valve was open when state on'), + e.numeric('water_consumed', ea.STATE).withUnit('L').withDescription('Liters of water consumed'), + e + .text('cycle_timer_1', ea.STATE_SET) + .withDescription( + 'Format 08:00 / 20:00 / 15 / 60 / MoTuWeThFrSaSu / 1 (' + + '08:00 = start time ' + + '20:00 = end time ' + + '15 = irrigation duration in minutes ' + + '60 = pause duration in minutes ' + + 'MoTu..= active weekdays ' + + '1 = deactivate timer with 0)', + ), + e + .text('cycle_timer_2', ea.STATE_SET) + .withDescription( + 'Format 08:00 / 20:00 / 15 / 60 / MoTuWeThFrSaSu / 1 (' + + '08:00 = start time ' + + '20:00 = end time ' + + '15 = irrigation duration in minutes ' + + '60 = pause duration in minutes ' + + 'MoTu..= active weekdays ' + + '1 = deactivate timer with 0)', + ), + e + .text('cycle_timer_3', ea.STATE_SET) + .withDescription( + 'Format 08:00 / 20:00 / 15 / 60 / MoTuWeThFrSaSu / 1 (' + + '08:00 = start time ' + + '20:00 = end time ' + + '15 = irrigation duration in minutes ' + + '60 = pause duration in minutes ' + + 'MoTu..= active weekdays ' + + '1 = deactivate timer with 0)', + ), + e + .text('cycle_timer_4', ea.STATE_SET) + .withDescription( + 'Format 08:00 / 20:00 / 15 / 60 / MoTuWeThFrSaSu / 1 (' + + '08:00 = start time ' + + '20:00 = end time ' + + '15 = irrigation duration in minutes ' + + '60 = pause duration in minutes ' + + 'MoTu..= active weekdays ' + + '1 = deactivate timer with 0)', + ), + e + .text('normal_schedule_timer_1', ea.STATE_SET) + .withDescription( + 'Format 08:00 / 15 / MoTuWeThFrSaSu / 1 (' + + '08:00 = start time ' + + '15 = duration in minutes ' + + 'MoTu..= active weekdays ' + + '1 = deactivate timer with 0)', + ), + e + .text('normal_schedule_timer_2', ea.STATE_SET) + .withDescription( + 'Format 08:00 / 15 / MoTuWeThFrSaSu / 1 (' + + '08:00 = start time ' + + '15 = duration in minutes ' + + 'MoTu..= active weekdays ' + + '1 = deactivate timer with 0)', + ), + e + .text('normal_schedule_timer_3', ea.STATE_SET) + .withDescription( + 'Format 08:00 / 15 / MoTuWeThFrSaSu / 1 (' + + '08:00 = start time ' + + '15 = duration in minutes ' + + 'MoTu..= active weekdays ' + + '1 = deactivate timer with 0)', + ), + e + .text('normal_schedule_timer_4', ea.STATE_SET) + .withDescription( + 'Format 08:00 / 15 / MoTuWeThFrSaSu / 1 (' + + '08:00 = start time ' + + '15 = duration in minutes ' + + 'MoTu..= active weekdays ' + + '1 = deactivate timer with 0)', + ), + ], }, { fingerprint: [{modelID: 'TS0202', manufacturerName: '_TZ3000_mwd3c2at'}], diff --git a/src/devices/salus_controls.ts b/src/devices/salus_controls.ts index d261de9a647cd..58f833d1cc155 100644 --- a/src/devices/salus_controls.ts +++ b/src/devices/salus_controls.ts @@ -1,10 +1,10 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; +import {electricityMeter, onOff} from '../lib/modernExtend'; import * as ota from '../lib/ota'; import * as reporting from '../lib/reporting'; -import {electricityMeter, onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/samotech.ts b/src/devices/samotech.ts index beba365085d16..c9c3755fe1959 100644 --- a/src/devices/samotech.ts +++ b/src/devices/samotech.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {deviceEndpoints, electricityMeter, light, onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/saswell.ts b/src/devices/saswell.ts index 5e539bcb78dd5..9a8287d6b7285 100644 --- a/src/devices/saswell.ts +++ b/src/devices/saswell.ts @@ -1,15 +1,16 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; -import * as tuya from '../lib/tuya'; import * as reporting from '../lib/reporting'; +import * as tuya from '../lib/tuya'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; const definitions: Definition[] = [ { - fingerprint: [{modelID: 'GbxAXL2\u0000', manufacturerName: '_TYST11_KGbxAXL2'}, + fingerprint: [ + {modelID: 'GbxAXL2\u0000', manufacturerName: '_TYST11_KGbxAXL2'}, {modelID: 'uhszj9s\u0000', manufacturerName: '_TYST11_zuhszj9s'}, {modelID: '88teujp\u0000', manufacturerName: '_TYST11_c88teujp'}, {modelID: 'w7cahqs\u0000', manufacturerName: '_TYST11_yw7cahqs'}, @@ -31,12 +32,24 @@ const definitions: Definition[] = [ model: 'SEA801-Zigbee/SEA802-Zigbee', vendor: 'Saswell', description: 'Thermostatic radiator valve', - whiteLabel: [{vendor: 'HiHome', model: 'WZB-TRVL'}, {vendor: 'Hama', model: '00176592'}, - {vendor: 'RTX', model: 'ZB-RT1'}, {vendor: 'SETTI+', model: 'TRV001'}], + whiteLabel: [ + {vendor: 'HiHome', model: 'WZB-TRVL'}, + {vendor: 'Hama', model: '00176592'}, + {vendor: 'RTX', model: 'ZB-RT1'}, + {vendor: 'SETTI+', model: 'TRV001'}, + ], fromZigbee: [legacy.fz.saswell_thermostat, fz.ignore_tuya_set_time, fz.ignore_basic_report, legacy.fz.tuya_thermostat_weekly_schedule_1], - toZigbee: [legacy.tz.saswell_thermostat_current_heating_setpoint, legacy.tz.saswell_thermostat_mode, legacy.tz.saswell_thermostat_away, - legacy.tz.saswell_thermostat_child_lock, legacy.tz.saswell_thermostat_window_detection, legacy.tz.saswell_thermostat_frost_detection, - legacy.tz.saswell_thermostat_calibration, legacy.tz.saswell_thermostat_anti_scaling, legacy.tz.tuya_thermostat_weekly_schedule], + toZigbee: [ + legacy.tz.saswell_thermostat_current_heating_setpoint, + legacy.tz.saswell_thermostat_mode, + legacy.tz.saswell_thermostat_away, + legacy.tz.saswell_thermostat_child_lock, + legacy.tz.saswell_thermostat_window_detection, + legacy.tz.saswell_thermostat_frost_detection, + legacy.tz.saswell_thermostat_calibration, + legacy.tz.saswell_thermostat_anti_scaling, + legacy.tz.tuya_thermostat_weekly_schedule, + ], // @ts-expect-error onEvent: (type, data, device) => !['_TZE200_c88teujp'].includes(device.manufacturerName) && tuya.onEventSetTime(type, data, device), meta: { @@ -50,13 +63,20 @@ const definitions: Definition[] = [ const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genBasic']); }, - exposes: [e.battery_low(), e.window_detection(), e.child_lock(), e.away_mode(), + exposes: [ + e.battery_low(), + e.window_detection(), + e.child_lock(), + e.away_mode(), e.binary('heating', ea.STATE, 'ON', 'OFF').withDescription('Device valve is open or closed (heating or not)'), - e.climate() - .withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.STATE_SET).withLocalTemperature(ea.STATE) + e + .climate() + .withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.STATE_SET) + .withLocalTemperature(ea.STATE) .withSystemMode(['off', 'heat', 'auto'], ea.STATE_SET) // Range is -6 - 6 and step 1: https://github.com/Koenkk/zigbee2mqtt/issues/11777 - .withLocalTemperatureCalibration(-6, 6, 1, ea.STATE_SET)], + .withLocalTemperatureCalibration(-6, 6, 1, ea.STATE_SET), + ], }, ]; diff --git a/src/devices/sber.ts b/src/devices/sber.ts index 46b57f3a91acb..b832296da60a4 100644 --- a/src/devices/sber.ts +++ b/src/devices/sber.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; import {battery, humidity, iasZoneAlarm, ignoreClusterReport, temperature} from '../lib/modernExtend'; import {modernExtend as tuyaModernExtend} from '../lib/tuya'; +import {Definition} from '../lib/types'; const {tuyaMagicPacket, tuyaOnOffActionLegacy} = tuyaModernExtend; const definitions: Definition[] = [ @@ -35,11 +35,11 @@ const definitions: Definition[] = [ tuyaOnOffActionLegacy({actions: ['single', 'double', 'hold']}), battery({percentageReporting: false}), /* - * reporting.batteryPercentageRemaining removed as it was causing devices to fall of the network - * every 1 hour, with light flashing when it happened, extremely short battery life, 2 presses for - * action to register: https://github.com/Koenkk/zigbee2mqtt/issues/8072 - * Initially wrapped in a try catch: https://github.com/Koenkk/zigbee2mqtt/issues/6313 - */ + * reporting.batteryPercentageRemaining removed as it was causing devices to fall of the network + * every 1 hour, with light flashing when it happened, extremely short battery life, 2 presses for + * action to register: https://github.com/Koenkk/zigbee2mqtt/issues/8072 + * Initially wrapped in a try catch: https://github.com/Koenkk/zigbee2mqtt/issues/6313 + */ ], }, { @@ -47,11 +47,7 @@ const definitions: Definition[] = [ model: 'SBDV-00079', vendor: 'Sber', description: 'Smart temperature and humidity sensor', - extend: [ - temperature(), - humidity(), - battery({voltage: true, voltageReporting: true}), - ], + extend: [temperature(), humidity(), battery({voltage: true, voltageReporting: true})], }, { fingerprint: [{modelID: 'TS0207', manufacturerName: '_TZ3000_c8bqthpo'}], diff --git a/src/devices/scanproducts.ts b/src/devices/scanproducts.ts index 91dfaea398f2d..8559e522e216d 100644 --- a/src/devices/scanproducts.ts +++ b/src/devices/scanproducts.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light, onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/schlage.ts b/src/devices/schlage.ts index a4189cfb320bb..9bebf80a64647 100644 --- a/src/devices/schlage.ts +++ b/src/devices/schlage.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/schneider_electric.ts b/src/devices/schneider_electric.ts index 052dbd701bef6..967b06634db4c 100644 --- a/src/devices/schneider_electric.ts +++ b/src/devices/schneider_electric.ts @@ -1,13 +1,10 @@ import {Zcl} from 'zigbee-herdsman'; -import {Definition, Fz, Tz, KeyValue, ModernExtend} from '../lib/types'; -import * as exposes from '../lib/exposes'; + import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; import * as constants from '../lib/constants'; -import * as reporting from '../lib/reporting'; -import * as utils from '../lib/utils'; -import * as ota from '../lib/ota'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; import { onOff, commandsOnOff, @@ -20,6 +17,10 @@ import { deviceEndpoints, deviceAddCustomCluster, } from '../lib/modernExtend'; +import * as ota from '../lib/ota'; +import * as reporting from '../lib/reporting'; +import {Definition, Fz, Tz, KeyValue, ModernExtend} from '../lib/types'; +import * as utils from '../lib/utils'; import {postfixWithEndpointName} from '../lib/utils'; const e = exposes.presets; const ea = exposes.access; @@ -32,10 +33,10 @@ function indicatorMode(endpoint?: string) { return enumLookup({ name: 'indicator_mode', lookup: { - 'reverse_with_load': 2, - 'consistent_with_load': 0, - 'always_off': 3, - 'always_on': 1, + reverse_with_load: 2, + consistent_with_load: 0, + always_off: 3, + always_on: 1, }, cluster: 'manuSpecificSchneiderLightSwitchConfiguration', attribute: 'ledIndication', @@ -48,10 +49,10 @@ function socketIndicatorMode() { return enumLookup({ name: 'indicator_mode', lookup: { - 'reverse_with_load': 0, - 'consistent_with_load': 1, - 'always_off': 2, - 'always_on': 3, + reverse_with_load: 0, + consistent_with_load: 1, + always_off: 2, + always_on: 3, }, cluster: 'manuSpecificSchneiderFanSwitchConfiguration', attribute: 'ledIndication', @@ -64,9 +65,9 @@ function fanIndicatorMode() { return enumLookup({ name: 'indicator_mode', lookup: { - 'always_on': 3, - 'on_with_timeout_but_as_locator': 4, - 'on_with_timeout': 5, + always_on: 3, + on_with_timeout_but_as_locator: 4, + on_with_timeout: 5, }, cluster: 'manuSpecificSchneiderFanSwitchConfiguration', attribute: 'ledIndication', @@ -79,10 +80,10 @@ function fanIndicatorOrientation() { return enumLookup({ name: 'indicator_orientation', lookup: { - 'horizontal_left': 2, - 'horizontal_right': 0, - 'vertical_top': 3, - 'vertical_bottom': 1, + horizontal_left: 2, + horizontal_right: 0, + vertical_top: 3, + vertical_bottom: 1, }, cluster: 'manuSpecificSchneiderFanSwitchConfiguration', attribute: 'ledOrientation', @@ -98,20 +99,20 @@ function switchActions(endpoint?: string) { return enumLookup({ name: 'switch_actions', lookup: { - 'light': 0, - 'light_opposite': 254, - 'dimmer': 1, - 'dimmer_opposite': 253, - 'standard_shutter': 2, - 'standard_shutter_opposite': 252, - 'schneider_shutter': 3, - 'schneider_shutter_opposite': 251, - 'scene': 4, - 'toggle_light': 5, - 'toggle_dimmer': 6, - 'alternate_light': 7, - 'alternate_dimmer': 8, - 'not_used': 127, + light: 0, + light_opposite: 254, + dimmer: 1, + dimmer_opposite: 253, + standard_shutter: 2, + standard_shutter_opposite: 252, + schneider_shutter: 3, + schneider_shutter_opposite: 251, + scene: 4, + toggle_light: 5, + toggle_dimmer: 6, + alternate_light: 7, + alternate_dimmer: 8, + not_used: 127, }, cluster: 'manuSpecificSchneiderLightSwitchConfiguration', attribute: 'switchActions', @@ -173,10 +174,10 @@ const schneiderElectricExtend = { return enumLookup({ name: 'indicator_mode', lookup: { - 'reverse_with_load': reverseWithLoad, - 'consistent_with_load': consistentWithLoad, - 'always_off': alwaysOff, - 'always_on': alwaysOn, + reverse_with_load: reverseWithLoad, + consistent_with_load: consistentWithLoad, + always_off: alwaysOff, + always_on: alwaysOn, }, cluster: 'visaConfiguration', attribute: 'indicatorMode', @@ -273,11 +274,21 @@ const schneiderElectricExtend = { e.enum('state', ea.SET, ['OPEN', 'CLOSE', 'STOP']).withDescription('State of the curtain').withEndpoint(endpointName), ), ...endpointNames.map((endpointName) => - e.numeric('position', ea.ALL).withValueMin(0).withValueMax(100).withUnit('%').withDescription('Position of the curtain') + e + .numeric('position', ea.ALL) + .withValueMin(0) + .withValueMax(100) + .withUnit('%') + .withDescription('Position of the curtain') .withEndpoint(endpointName), ), ...endpointNames.map((endpointName) => - e.numeric('transition', ea.ALL).withValueMin(0).withValueMax(300).withUnit('s').withDescription('Transition time in seconds') + e + .numeric('transition', ea.ALL) + .withValueMin(0) + .withValueMax(300) + .withUnit('s') + .withDescription('Transition time in seconds') .withEndpoint(endpointName), ), ], @@ -331,118 +342,120 @@ const fzLocal = { const commandID = msg.data.commandID; if (utils.hasAlreadyProcessedMessage(msg, model, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return; - const rxAfterTx = (msg.data.options & (1<<11)); + const rxAfterTx = msg.data.options & (1 << 11); const ret: KeyValue = {}; switch (commandID) { - case 0xA1: { - const attr = msg.data.commandFrame.attributes; - const clusterID = msg.data.commandFrame.clusterID; + case 0xa1: { + const attr = msg.data.commandFrame.attributes; + const clusterID = msg.data.commandFrame.clusterID; - switch (clusterID) { - case 2820: { // haElectricalMeasurement - const acCurrentDivisor = attr['acCurrentDivisor']; - const acVoltageDivisor = attr['acVoltageDivisor']; - const acFrequencyDivisor = attr['acFrequencyDivisor']; - const powerDivisor = attr['powerDivisor']; + switch (clusterID) { + case 2820: { + // haElectricalMeasurement + const acCurrentDivisor = attr['acCurrentDivisor']; + const acVoltageDivisor = attr['acVoltageDivisor']; + const acFrequencyDivisor = attr['acFrequencyDivisor']; + const powerDivisor = attr['powerDivisor']; - if (attr.hasOwnProperty('rmsVoltage')) { - ret['voltage_phase_a'] = attr['rmsVoltage'] / acVoltageDivisor; - } + if (attr.hasOwnProperty('rmsVoltage')) { + ret['voltage_phase_a'] = attr['rmsVoltage'] / acVoltageDivisor; + } - if (attr.hasOwnProperty('rmsVoltagePhB')) { - ret['voltage_phase_b'] = attr['rmsVoltagePhB'] / acVoltageDivisor; - } + if (attr.hasOwnProperty('rmsVoltagePhB')) { + ret['voltage_phase_b'] = attr['rmsVoltagePhB'] / acVoltageDivisor; + } - if (attr.hasOwnProperty('rmsVoltagePhC')) { - ret['voltage_phase_c'] = attr['rmsVoltagePhC'] / acVoltageDivisor; - } + if (attr.hasOwnProperty('rmsVoltagePhC')) { + ret['voltage_phase_c'] = attr['rmsVoltagePhC'] / acVoltageDivisor; + } - if (attr.hasOwnProperty('19200')) { - ret['voltage_phase_ab'] = attr['19200'] / acVoltageDivisor; - } + if (attr.hasOwnProperty('19200')) { + ret['voltage_phase_ab'] = attr['19200'] / acVoltageDivisor; + } - if (attr.hasOwnProperty('19456')) { - ret['voltage_phase_bc'] = attr['19456'] / acVoltageDivisor; - } + if (attr.hasOwnProperty('19456')) { + ret['voltage_phase_bc'] = attr['19456'] / acVoltageDivisor; + } - if (attr.hasOwnProperty('19712')) { - ret['voltage_phase_ca'] = attr['19712'] / acVoltageDivisor; - } + if (attr.hasOwnProperty('19712')) { + ret['voltage_phase_ca'] = attr['19712'] / acVoltageDivisor; + } - if (attr.hasOwnProperty('rmsCurrent')) { - ret['current_phase_a'] = attr['rmsCurrent'] / acCurrentDivisor; - } + if (attr.hasOwnProperty('rmsCurrent')) { + ret['current_phase_a'] = attr['rmsCurrent'] / acCurrentDivisor; + } - if (attr.hasOwnProperty('rmsCurrentPhB')) { - ret['current_phase_b'] = attr['rmsCurrentPhB'] / acCurrentDivisor; - } + if (attr.hasOwnProperty('rmsCurrentPhB')) { + ret['current_phase_b'] = attr['rmsCurrentPhB'] / acCurrentDivisor; + } - if (attr.hasOwnProperty('rmsCurrentPhC')) { - ret['current_phase_c'] = attr['rmsCurrentPhC'] / acCurrentDivisor; - } + if (attr.hasOwnProperty('rmsCurrentPhC')) { + ret['current_phase_c'] = attr['rmsCurrentPhC'] / acCurrentDivisor; + } - if (attr.hasOwnProperty('totalActivePower')) { - ret['power'] = attr['totalActivePower'] * 1000 / powerDivisor; - } + if (attr.hasOwnProperty('totalActivePower')) { + ret['power'] = (attr['totalActivePower'] * 1000) / powerDivisor; + } - if (attr.hasOwnProperty('totalApparentPower')) { - ret['power_apparent'] = attr['totalApparentPower'] * 1000 / powerDivisor; - } + if (attr.hasOwnProperty('totalApparentPower')) { + ret['power_apparent'] = (attr['totalApparentPower'] * 1000) / powerDivisor; + } - if (attr.hasOwnProperty('acFrequency')) { - ret['ac_frequency'] = attr['acFrequency'] / acFrequencyDivisor; - } + if (attr.hasOwnProperty('acFrequency')) { + ret['ac_frequency'] = attr['acFrequency'] / acFrequencyDivisor; + } - if (attr.hasOwnProperty('activePower')) { - ret['power_phase_a'] = attr['activePower'] * 1000 / powerDivisor; - } + if (attr.hasOwnProperty('activePower')) { + ret['power_phase_a'] = (attr['activePower'] * 1000) / powerDivisor; + } - if (attr.hasOwnProperty('activePowerPhB')) { - ret['power_phase_b'] = attr['activePowerPhB'] * 1000 / powerDivisor; - } + if (attr.hasOwnProperty('activePowerPhB')) { + ret['power_phase_b'] = (attr['activePowerPhB'] * 1000) / powerDivisor; + } - if (attr.hasOwnProperty('activePowerPhC')) { - ret['power_phase_c'] = attr['activePowerPhC'] * 1000 / powerDivisor; - } - break; - } - case 1794: { // seMetering - const divisor = attr['divisor']; + if (attr.hasOwnProperty('activePowerPhC')) { + ret['power_phase_c'] = (attr['activePowerPhC'] * 1000) / powerDivisor; + } + break; + } + case 1794: { + // seMetering + const divisor = attr['divisor']; - if (attr.hasOwnProperty('currentSummDelivered')) { - const val = attr['currentSummDelivered']; - ret['energy'] = ((parseInt(val[0]) << 32) + parseInt(val[1])) / divisor; - } + if (attr.hasOwnProperty('currentSummDelivered')) { + const val = attr['currentSummDelivered']; + ret['energy'] = ((parseInt(val[0]) << 32) + parseInt(val[1])) / divisor; + } - if (attr.hasOwnProperty('16652')) { - const val = attr['16652']; - ret['energy_phase_a'] = ((parseInt(val[0]) << 32) + parseInt(val[1])) / divisor; - } + if (attr.hasOwnProperty('16652')) { + const val = attr['16652']; + ret['energy_phase_a'] = ((parseInt(val[0]) << 32) + parseInt(val[1])) / divisor; + } - if (attr.hasOwnProperty('16908')) { - const val = attr['16908']; - ret['energy_phase_b'] = ((parseInt(val[0]) << 32) + parseInt(val[1])) / divisor; - } + if (attr.hasOwnProperty('16908')) { + const val = attr['16908']; + ret['energy_phase_b'] = ((parseInt(val[0]) << 32) + parseInt(val[1])) / divisor; + } - if (attr.hasOwnProperty('17164')) { - const val = attr['17164']; - ret['energy_phase_c'] = ((parseInt(val[0]) << 32) + parseInt(val[1])) / divisor; - } + if (attr.hasOwnProperty('17164')) { + const val = attr['17164']; + ret['energy_phase_c'] = ((parseInt(val[0]) << 32) + parseInt(val[1])) / divisor; + } - if (attr.hasOwnProperty('powerFactor')) { - ret['power_factor'] = attr['powerFactor']; + if (attr.hasOwnProperty('powerFactor')) { + ret['power_factor'] = attr['powerFactor']; + } + + break; + } } break; } - } - - break; - } - case 0xA3: - // Should handle this cluster as well - break; + case 0xa3: + // Should handle this cluster as well + break; } if (rxAfterTx) { @@ -454,18 +467,17 @@ const fzLocal = { tempMaster: msg.data.gppNwkAddr, tempMasterTx: networkParameters.channel - 11, srcID: msg.data.srcID, - gpdCmd: 0xFE, + gpdCmd: 0xfe, gpdPayload: { - commandID: 0xFE, + commandID: 0xfe, buffer: Buffer.alloc(1), // I hope it's zero initialised }, }; - await msg.endpoint.commandResponse('greenPower', 'response', payload, - { - srcEndpoint: 242, - disableDefaultResponse: true, - }); + await msg.endpoint.commandResponse('greenPower', 'response', payload, { + srcEndpoint: 242, + disableDefaultResponse: true, + }); } return ret; @@ -481,8 +493,10 @@ const definitions: Definition[] = [ description: 'Roller shutter module', fromZigbee: [fz.cover_position_tilt], toZigbee: [tz.cover_position_tilt, tz.cover_state, tzLocal.lift_duration], - exposes: [e.cover_position(), e.numeric('lift_duration', ea.STATE_SET).withUnit('s') - .withValueMin(0).withValueMax(300).withDescription('Duration of lift')], + exposes: [ + e.cover_position(), + e.numeric('lift_duration', ea.STATE_SET).withUnit('s').withValueMin(0).withValueMax(300).withDescription('Duration of lift'), + ], meta: {coverInverted: true}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(5); @@ -497,8 +511,10 @@ const definitions: Definition[] = [ description: 'Roller shutter', fromZigbee: [fz.cover_position_tilt], toZigbee: [tz.cover_position_tilt, tz.cover_state, tzLocal.lift_duration], - exposes: [e.cover_position(), e.numeric('lift_duration', ea.STATE_SET).withUnit('s') - .withValueMin(0).withValueMax(300).withDescription('Duration of lift')], + exposes: [ + e.cover_position(), + e.numeric('lift_duration', ea.STATE_SET).withUnit('s').withValueMin(0).withValueMax(300).withDescription('Duration of lift'), + ], meta: {coverInverted: true}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(5); @@ -511,14 +527,29 @@ const definitions: Definition[] = [ model: 'WV704R0A0902', vendor: 'Schneider Electric', description: 'Wiser radiator thermostat', - fromZigbee: [fz.ignore_basic_report, fz.ignore_haDiagnostic, fz.ignore_genOta, fz.ignore_zclversion_read, - legacy.fz.wiser_thermostat, legacy.fz.wiser_itrv_battery, fz.hvac_user_interface, fz.wiser_device_info], + fromZigbee: [ + fz.ignore_basic_report, + fz.ignore_haDiagnostic, + fz.ignore_genOta, + fz.ignore_zclversion_read, + legacy.fz.wiser_thermostat, + legacy.fz.wiser_itrv_battery, + fz.hvac_user_interface, + fz.wiser_device_info, + ], toZigbee: [tz.thermostat_occupied_heating_setpoint, tz.thermostat_keypad_lockout], meta: {battery: {voltageToPercentage: '3V_2500_3200'}}, exposes: [ - e.climate().withSetpoint('occupied_heating_setpoint', 5, 30, 0.5).withLocalTemperature(ea.STATE) - .withRunningState(['idle', 'heat'], ea.STATE).withPiHeatingDemand(), e.battery(), e.battery_voltage(), - e.keypad_lockout().withAccess(ea.ALL)], + e + .climate() + .withSetpoint('occupied_heating_setpoint', 5, 30, 0.5) + .withLocalTemperature(ea.STATE) + .withRunningState(['idle', 'heat'], ea.STATE) + .withPiHeatingDemand(), + e.battery(), + e.battery_voltage(), + e.keypad_lockout().withAccess(ea.ALL), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const binds = ['genBasic', 'genPowerCfg', 'hvacThermostat', 'haDiagnostic']; @@ -528,8 +559,14 @@ const definitions: Definition[] = [ await reporting.thermostatOccupiedHeatingSetpoint(endpoint); await reporting.thermostatPIHeatingDemand(endpoint); // bind of hvacUserInterfaceCfg fails with 'Table Full', does this have any effect? - await endpoint.configureReporting('hvacUserInterfaceCfg', [{attribute: 'keypadLockout', reportableChange: 1, - minimumReportInterval: constants.repInterval.MINUTE, maximumReportInterval: constants.repInterval.HOUR}]); + await endpoint.configureReporting('hvacUserInterfaceCfg', [ + { + attribute: 'keypadLockout', + reportableChange: 1, + minimumReportInterval: constants.repInterval.MINUTE, + maximumReportInterval: constants.repInterval.HOUR, + }, + ]); }, }, { @@ -537,10 +574,7 @@ const definitions: Definition[] = [ model: 'U202DST600ZB', vendor: 'Schneider Electric', description: 'EZinstall3 2 gang 2x300W dimmer module', - extend: [ - deviceEndpoints({endpoints: {'l1': 10, 'l2': 11}}), - light({endpointNames: ['l1', 'l2'], configureReporting: true}), - ], + extend: [deviceEndpoints({endpoints: {l1: 10, l2: 11}}), light({endpointNames: ['l1', 'l2'], configureReporting: true})], }, { zigbeeModel: ['PUCK/DIMMER/1'], @@ -552,12 +586,20 @@ const definitions: Definition[] = [ fromZigbee: [fz.wiser_lighting_ballast_configuration], toZigbee: [tz.ballast_config, tz.wiser_dimmer_mode], exposes: [ - e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254) + e + .numeric('ballast_minimum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) .withDescription('Specifies the minimum light output of the ballast'), - e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254) + e + .numeric('ballast_maximum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) .withDescription('Specifies the maximum light output of the ballast'), - e.enum('dimmer_mode', ea.ALL, ['auto', 'rc', 'rl', 'rl_led']) - .withDescription('Sets dimming mode to autodetect or fixed RC/RL/RL_LED mode (max load is reduced in RL_LED)')], + e + .enum('dimmer_mode', ea.ALL, ['auto', 'rc', 'rl', 'rl_led']) + .withDescription('Sets dimming mode to autodetect or fixed RC/RL/RL_LED mode (max load is reduced in RL_LED)'), + ], whiteLabel: [{vendor: 'Elko', model: 'EKO07090'}], }, { @@ -579,12 +621,20 @@ const definitions: Definition[] = [ fromZigbee: [fz.wiser_lighting_ballast_configuration], toZigbee: [tz.ballast_config, tz.wiser_dimmer_mode], exposes: [ - e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254) + e + .numeric('ballast_minimum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) .withDescription('Specifies the minimum light output of the ballast'), - e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254) + e + .numeric('ballast_maximum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) .withDescription('Specifies the maximum light output of the ballast'), - e.enum('dimmer_mode', ea.ALL, ['auto', 'rc', 'rl', 'rl_led']) - .withDescription('Sets dimming mode to autodetect or fixed RC/RL/RL_LED mode (max load is reduced in RL_LED)')], + e + .enum('dimmer_mode', ea.ALL, ['auto', 'rc', 'rl', 'rl_led']) + .withDescription('Sets dimming mode to autodetect or fixed RC/RL/RL_LED mode (max load is reduced in RL_LED)'), + ], }, { zigbeeModel: ['CCTFR6730'], @@ -592,7 +642,7 @@ const definitions: Definition[] = [ vendor: 'Schneider Electric', description: 'Wiser power micromodule', whiteLabel: [{vendor: 'Elko', model: 'EKO20004'}], - extend: [onOff({powerOnBehavior: true}), electricityMeter({'cluster': 'metering'}), identify()], + extend: [onOff({powerOnBehavior: true}), electricityMeter({cluster: 'metering'}), identify()], }, { zigbeeModel: ['NHROTARY/DIMMER/1'], @@ -601,13 +651,22 @@ const definitions: Definition[] = [ description: 'Rotary dimmer', fromZigbee: [fz.on_off, fz.brightness, fz.level_config, fz.wiser_lighting_ballast_configuration], toZigbee: [tz.light_onoff_brightness, tz.level_config, tz.ballast_config, tz.wiser_dimmer_mode], - exposes: [e.light_brightness().withLevelConfig(), - e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254) + exposes: [ + e.light_brightness().withLevelConfig(), + e + .numeric('ballast_minimum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) .withDescription('Specifies the minimum light output of the ballast'), - e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254) + e + .numeric('ballast_maximum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) .withDescription('Specifies the maximum light output of the ballast'), - e.enum('dimmer_mode', ea.ALL, ['auto', 'rc', 'rl', 'rl_led']) - .withDescription('Sets dimming mode to autodetect or fixed RC/RL/RL_LED mode (max load is reduced in RL_LED)')], + e + .enum('dimmer_mode', ea.ALL, ['auto', 'rc', 'rl', 'rl_led']) + .withDescription('Sets dimming mode to autodetect or fixed RC/RL/RL_LED mode (max load is reduced in RL_LED)'), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(3); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'lightingBallastCfg']); @@ -622,13 +681,22 @@ const definitions: Definition[] = [ description: 'Rotary dimmer', fromZigbee: [fz.on_off, fz.brightness, fz.level_config, fz.wiser_lighting_ballast_configuration], toZigbee: [tz.light_onoff_brightness, tz.level_config, tz.ballast_config, tz.wiser_dimmer_mode], - exposes: [e.light_brightness().withLevelConfig(), - e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254) + exposes: [ + e.light_brightness().withLevelConfig(), + e + .numeric('ballast_minimum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) .withDescription('Specifies the minimum light output of the ballast'), - e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254) + e + .numeric('ballast_maximum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) .withDescription('Specifies the maximum light output of the ballast'), - e.enum('dimmer_mode', ea.ALL, ['auto', 'rc', 'rl', 'rl_led']) - .withDescription('Sets dimming mode to autodetect or fixed RC/RL/RL_LED mode (max load is reduced in RL_LED)')], + e + .enum('dimmer_mode', ea.ALL, ['auto', 'rc', 'rl', 'rl_led']) + .withDescription('Sets dimming mode to autodetect or fixed RC/RL/RL_LED mode (max load is reduced in RL_LED)'), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(3); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'lightingBallastCfg']); @@ -643,13 +711,22 @@ const definitions: Definition[] = [ description: 'Push button dimmer', fromZigbee: [fz.on_off, fz.brightness, fz.level_config, fz.wiser_lighting_ballast_configuration], toZigbee: [tz.light_onoff_brightness, tz.level_config, tz.ballast_config, tz.wiser_dimmer_mode], - exposes: [e.light_brightness().withLevelConfig(), - e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254) + exposes: [ + e.light_brightness().withLevelConfig(), + e + .numeric('ballast_minimum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) .withDescription('Specifies the minimum light output of the ballast'), - e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254) + e + .numeric('ballast_maximum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) .withDescription('Specifies the maximum light output of the ballast'), - e.enum('dimmer_mode', ea.ALL, ['auto', 'rc', 'rl', 'rl_led']) - .withDescription('Sets dimming mode to autodetect or fixed RC/RL/RL_LED mode (max load is reduced in RL_LED)')], + e + .enum('dimmer_mode', ea.ALL, ['auto', 'rc', 'rl', 'rl_led']) + .withDescription('Sets dimming mode to autodetect or fixed RC/RL/RL_LED mode (max load is reduced in RL_LED)'), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(3); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'lightingBallastCfg']); @@ -681,11 +758,19 @@ const definitions: Definition[] = [ description: 'Push button dimmer', fromZigbee: [fz.on_off, fz.brightness, fz.level_config, fz.lighting_ballast_configuration], toZigbee: [tz.light_onoff_brightness, tz.level_config, tz.ballast_config], - exposes: [e.light_brightness().withLevelConfig(), - e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254) + exposes: [ + e.light_brightness().withLevelConfig(), + e + .numeric('ballast_minimum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) .withDescription('Specifies the minimum light output of the ballast'), - e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254) - .withDescription('Specifies the maximum light output of the ballast')], + e + .numeric('ballast_maximum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) + .withDescription('Specifies the maximum light output of the ballast'), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(3); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'lightingBallastCfg']); @@ -700,11 +785,19 @@ const definitions: Definition[] = [ description: 'Wiser 40/300-Series Module Dimmer', fromZigbee: [fz.on_off, fz.brightness, fz.level_config, fz.lighting_ballast_configuration], toZigbee: [tz.light_onoff_brightness, tz.level_config, tz.ballast_config], - exposes: [e.light_brightness(), - e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254) + exposes: [ + e.light_brightness(), + e + .numeric('ballast_minimum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) .withDescription('Specifies the minimum light output of the ballast'), - e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254) - .withDescription('Specifies the maximum light output of the ballast')], + e + .numeric('ballast_maximum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) + .withDescription('Specifies the maximum light output of the ballast'), + ], ota: ota.zigbeeOTA, extend: [indicatorMode('smart')], meta: {multiEndpoint: true}, @@ -715,7 +808,7 @@ const definitions: Definition[] = [ await reporting.brightness(endpoint); }, endpoint: (device) => { - return {'smart': 21}; + return {smart: 21}; }, }, { @@ -732,7 +825,7 @@ const definitions: Definition[] = [ await reporting.onOff(endpoint); }, endpoint: (device) => { - return {'smart': 21}; + return {smart: 21}; }, }, { @@ -749,7 +842,7 @@ const definitions: Definition[] = [ await reporting.onOff(endpoint); }, endpoint: (device) => { - return {'smart': 21}; + return {smart: 21}; }, }, { @@ -775,9 +868,12 @@ const definitions: Definition[] = [ description: 'Wiser smart plug', fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, fz.power_on_behavior], toZigbee: [tz.on_off, tz.power_on_behavior, tz.electrical_measurement_power], - exposes: [e.switch(), e.power().withAccess(ea.STATE_GET), e.energy(), - e.enum('power_on_behavior', ea.ALL, ['off', 'previous', 'on']) - .withDescription('Controls the behaviour when the device is powered on')], + exposes: [ + e.switch(), + e.power().withAccess(ea.STATE_GET), + e.energy(), + e.enum('power_on_behavior', ea.ALL, ['off', 'previous', 'on']).withDescription('Controls the behaviour when the device is powered on'), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement', 'seMetering']); @@ -808,11 +904,25 @@ const definitions: Definition[] = [ model: 'CCTFR6100Z3', vendor: 'Schneider Electric', description: 'Wiser radiator thermostat', - fromZigbee: [fz.ignore_basic_report, fz.ignore_haDiagnostic, fz.ignore_genOta, fz.ignore_zclversion_read, - legacy.fz.wiser_thermostat, legacy.fz.wiser_itrv_battery, fz.hvac_user_interface, fz.wiser_device_info], + fromZigbee: [ + fz.ignore_basic_report, + fz.ignore_haDiagnostic, + fz.ignore_genOta, + fz.ignore_zclversion_read, + legacy.fz.wiser_thermostat, + legacy.fz.wiser_itrv_battery, + fz.hvac_user_interface, + fz.wiser_device_info, + ], toZigbee: [tz.thermostat_occupied_heating_setpoint, tz.thermostat_keypad_lockout], - exposes: [e.climate().withSetpoint('occupied_heating_setpoint', 7, 30, 1).withLocalTemperature(ea.STATE) - .withRunningState(['idle', 'heat'], ea.STATE).withPiHeatingDemand()], + exposes: [ + e + .climate() + .withSetpoint('occupied_heating_setpoint', 7, 30, 1) + .withLocalTemperature(ea.STATE) + .withRunningState(['idle', 'heat'], ea.STATE) + .withPiHeatingDemand(), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const binds = ['genBasic', 'genPowerCfg', 'hvacThermostat', 'haDiagnostic']; @@ -822,8 +932,14 @@ const definitions: Definition[] = [ await reporting.thermostatOccupiedHeatingSetpoint(endpoint); await reporting.thermostatPIHeatingDemand(endpoint); // bind of hvacUserInterfaceCfg fails with 'Table Full', does this have any effect? - await endpoint.configureReporting('hvacUserInterfaceCfg', [{attribute: 'keypadLockout', reportableChange: 1, - minimumReportInterval: constants.repInterval.MINUTE, maximumReportInterval: constants.repInterval.HOUR}]); + await endpoint.configureReporting('hvacUserInterfaceCfg', [ + { + attribute: 'keypadLockout', + reportableChange: 1, + minimumReportInterval: constants.repInterval.MINUTE, + maximumReportInterval: constants.repInterval.HOUR, + }, + ]); }, }, { @@ -838,10 +954,7 @@ const definitions: Definition[] = [ model: 'U202SRY2KWZB', vendor: 'Schneider Electric', description: 'Ulti 240V 9.1 A 2 gangs relay switch impress switch module, amber LED', - extend: [ - deviceEndpoints({endpoints: {'l1': 10, 'l2': 11}}), - onOff({endpointNames: ['l1', 'l2']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 10, l2: 11}}), onOff({endpointNames: ['l1', 'l2']})], }, { zigbeeModel: ['1GANG/SHUTTER/1'], @@ -850,8 +963,10 @@ const definitions: Definition[] = [ description: 'Merten MEG5165 PlusLink Shutter insert with Merten Wiser System M Push Button (1fold)', fromZigbee: [fz.cover_position_tilt, fz.command_cover_close, fz.command_cover_open, fz.command_cover_stop], toZigbee: [tz.cover_position_tilt, tz.cover_state, tzLocal.lift_duration], - exposes: [e.cover_position(), e.numeric('lift_duration', ea.STATE_SET).withUnit('s') - .withValueMin(0).withValueMax(300).withDescription('Duration of lift')], + exposes: [ + e.cover_position(), + e.numeric('lift_duration', ea.STATE_SET).withUnit('s').withValueMin(0).withValueMax(300).withDescription('Duration of lift'), + ], meta: {coverInverted: true}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1) || device.getEndpoint(5); @@ -866,13 +981,22 @@ const definitions: Definition[] = [ description: 'Merten MEG5171 PlusLink Dimmer insert with Merten Wiser System M Push Button (1fold)', fromZigbee: [fz.on_off, fz.brightness, fz.level_config, fz.wiser_lighting_ballast_configuration], toZigbee: [tz.light_onoff_brightness, tz.level_config, tz.ballast_config, tz.wiser_dimmer_mode], - exposes: [e.light_brightness().withLevelConfig(), - e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254) + exposes: [ + e.light_brightness().withLevelConfig(), + e + .numeric('ballast_minimum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) .withDescription('Specifies the minimum light output of the ballast'), - e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254) + e + .numeric('ballast_maximum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) .withDescription('Specifies the maximum light output of the ballast'), - e.enum('dimmer_mode', ea.ALL, ['auto', 'rc', 'rl', 'rl_led']) - .withDescription('Sets dimming mode to autodetect or fixed RC/RL/RL_LED mode (max load is reduced in RL_LED)')], + e + .enum('dimmer_mode', ea.ALL, ['auto', 'rc', 'rl', 'rl_led']) + .withDescription('Sets dimming mode to autodetect or fixed RC/RL/RL_LED mode (max load is reduced in RL_LED)'), + ], extend: [indicatorMode(), switchActions()], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(3); @@ -888,13 +1012,22 @@ const definitions: Definition[] = [ description: 'Merten MEG5171 PlusLink Dimmer insert with Merten Wiser System M Push Button (2fold)', fromZigbee: [fz.on_off, fz.brightness, fz.level_config, fz.wiser_lighting_ballast_configuration], toZigbee: [tz.light_onoff_brightness, tz.level_config, tz.ballast_config, tz.wiser_dimmer_mode], - exposes: [e.light_brightness().withLevelConfig(), - e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254) + exposes: [ + e.light_brightness().withLevelConfig(), + e + .numeric('ballast_minimum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) .withDescription('Specifies the minimum light output of the ballast'), - e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254) + e + .numeric('ballast_maximum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) .withDescription('Specifies the maximum light output of the ballast'), - e.enum('dimmer_mode', ea.ALL, ['auto', 'rc', 'rl', 'rl_led']) - .withDescription('Sets dimming mode to autodetect or fixed RC/RL/RL_LED mode (max load is reduced in RL_LED)')], + e + .enum('dimmer_mode', ea.ALL, ['auto', 'rc', 'rl', 'rl_led']) + .withDescription('Sets dimming mode to autodetect or fixed RC/RL/RL_LED mode (max load is reduced in RL_LED)'), + ], extend: [indicatorMode('right'), indicatorMode('left'), switchActions('right'), switchActions('left')], meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -904,7 +1037,7 @@ const definitions: Definition[] = [ await reporting.brightness(endpoint); }, endpoint: (device) => { - return {'right': 21, 'left': 22}; + return {right: 21, left: 22}; }, }, { @@ -915,17 +1048,26 @@ const definitions: Definition[] = [ fromZigbee: [fz.wiser_lighting_ballast_configuration], toZigbee: [tz.ballast_config, tz.wiser_dimmer_mode], exposes: [ - e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254) + e + .numeric('ballast_minimum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) .withDescription('Specifies the minimum light output of the ballast'), - e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254) + e + .numeric('ballast_maximum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) .withDescription('Specifies the maximum light output of the ballast'), - e.enum('dimmer_mode', ea.ALL, ['auto', 'rc', 'rl', 'rl_led']) + e + .enum('dimmer_mode', ea.ALL, ['auto', 'rc', 'rl', 'rl_led']) .withDescription('Sets dimming mode to autodetect or fixed RC/RL/RL_LED mode (max load is reduced in RL_LED)'), ], extend: [ - deviceEndpoints({endpoints: {'left': 4, 'right': 3, 'left_btn': 22, 'right_btn': 21}}), + deviceEndpoints({endpoints: {left: 4, right: 3, left_btn: 22, right_btn: 21}}), light({endpointNames: ['left', 'right'], configureReporting: true}), - switchActions('left_btn'), switchActions('right_btn'), indicatorMode('left_btn'), + switchActions('left_btn'), + switchActions('right_btn'), + indicatorMode('left_btn'), ], }, { @@ -944,7 +1086,7 @@ const definitions: Definition[] = [ fromZigbee: [fz.on_off, fz.command_on, fz.command_off], toZigbee: [tz.on_off], endpoint: (device) => { - return {'l1': 1, 'l2': 2, 's1': 21, 's2': 22, 's3': 23, 's4': 24}; + return {l1: 1, l2: 2, s1: 21, s2: 22, s3: 23, s4: 24}; }, exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), e.action(['on_s*', 'off_s*'])], configure: async (device, coordinatorEndpoint) => { @@ -966,20 +1108,26 @@ const definitions: Definition[] = [ fromZigbee: [fz.schneider_lighting_ballast_configuration, fz.command_recall, fz.command_on, fz.command_off, fz.command_move, fz.command_stop], toZigbee: [tz.ballast_config, tz.schneider_dimmer_mode], endpoint: (device) => { - return {'l1': 3, 's1': 21, 's2': 22, 's3': 23, 's4': 24}; + return {l1: 3, s1: 21, s2: 22, s3: 23, s4: 24}; }, meta: {multiEndpoint: true}, extend: [light({endpointNames: ['l1'], configureReporting: true, levelConfig: {}})], exposes: [ - e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254) + e + .numeric('ballast_minimum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) .withDescription('Specifies the minimum light output of the ballast') .withEndpoint('l1'), - e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254) + e + .numeric('ballast_maximum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) .withDescription('Specifies the maximum light output of the ballast') .withEndpoint('l1'), - e.enum('dimmer_mode', ea.ALL, ['RC', 'RL']).withDescription('Controls Capacitive or Inductive Dimming Mode') - .withEndpoint('l1'), - e.action(['on', 'off', 'brightness_move_up', 'brightness_move_down', 'brightness_stop', 'recall_*'])], + e.enum('dimmer_mode', ea.ALL, ['RC', 'RL']).withDescription('Controls Capacitive or Inductive Dimming Mode').withEndpoint('l1'), + e.action(['on', 'off', 'brightness_move_up', 'brightness_move_down', 'brightness_stop', 'recall_*']), + ], configure: async (device, coordinatorEndpoint) => { // Configure the dimmer actuator endpoint const endpoint = device.getEndpoint(3); @@ -1017,13 +1165,27 @@ const definitions: Definition[] = [ fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.battery], toZigbee: [], endpoint: (device) => { - return {'top': 21, 'bottom': 22}; + return {top: 21, bottom: 22}; }, whiteLabel: [{vendor: 'Elko', model: 'EKO07117'}], meta: {multiEndpoint: true}, - exposes: [e.action(['on_top', 'off_top', 'on_bottom', 'off_bottom', 'brightness_move_up_top', 'brightness_stop_top', - 'brightness_move_down_top', 'brightness_stop_top', 'brightness_move_up_bottom', 'brightness_stop_bottom', - 'brightness_move_down_bottom', 'brightness_stop_bottom']), e.battery()], + exposes: [ + e.action([ + 'on_top', + 'off_top', + 'on_bottom', + 'off_bottom', + 'brightness_move_up_top', + 'brightness_stop_top', + 'brightness_move_down_top', + 'brightness_stop_top', + 'brightness_move_up_bottom', + 'brightness_stop_bottom', + 'brightness_move_down_bottom', + 'brightness_stop_bottom', + ]), + e.battery(), + ], configure: async (device, coordinatorEndpoint) => { // When in 2-gang operation mode, unit operates out of endpoints 21 and 22, otherwise just 21 const topButtonsEndpoint = device.getEndpoint(21); @@ -1039,13 +1201,27 @@ const definitions: Definition[] = [ vendor: 'Schneider Electric', description: 'Heating thermostat', fromZigbee: [fz.thermostat, fz.metering, fz.schneider_pilot_mode], - toZigbee: [tz.schneider_temperature_measured_value, tz.thermostat_system_mode, tz.thermostat_running_state, - tz.thermostat_local_temperature, tz.thermostat_occupied_heating_setpoint, tz.thermostat_control_sequence_of_operation, - tz.schneider_pilot_mode, tz.schneider_temperature_measured_value], - exposes: [e.power(), e.energy(), + toZigbee: [ + tz.schneider_temperature_measured_value, + tz.thermostat_system_mode, + tz.thermostat_running_state, + tz.thermostat_local_temperature, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_control_sequence_of_operation, + tz.schneider_pilot_mode, + tz.schneider_temperature_measured_value, + ], + exposes: [ + e.power(), + e.energy(), e.enum('schneider_pilot_mode', ea.ALL, ['contactor', 'pilot']).withDescription('Controls piloting mode'), - e.climate().withSetpoint('occupied_heating_setpoint', 4, 30, 0.5).withLocalTemperature() - .withSystemMode(['off', 'auto', 'heat']).withPiHeatingDemand()], + e + .climate() + .withSetpoint('occupied_heating_setpoint', 4, 30, 0.5) + .withLocalTemperature() + .withSystemMode(['off', 'auto', 'heat']) + .withPiHeatingDemand(), + ], configure: async (device, coordinatorEndpoint) => { const endpoint1 = device.getEndpoint(1); const endpoint2 = device.getEndpoint(2); @@ -1063,18 +1239,25 @@ const definitions: Definition[] = [ vendor: 'Schneider Electric', description: 'Temperature/Humidity measurement with thermostat interface', fromZigbee: [fz.battery, fz.schneider_temperature, fz.humidity, fz.thermostat, fz.schneider_ui_action], - toZigbee: [tz.schneider_thermostat_system_mode, tz.schneider_thermostat_occupied_heating_setpoint, - tz.schneider_thermostat_control_sequence_of_operation, tz.schneider_thermostat_pi_heating_demand, - tz.schneider_thermostat_keypad_lockout], - exposes: [e.keypad_lockout().withAccess(ea.STATE_SET), e.humidity(), e.battery(), e.battery_voltage(), + toZigbee: [ + tz.schneider_thermostat_system_mode, + tz.schneider_thermostat_occupied_heating_setpoint, + tz.schneider_thermostat_control_sequence_of_operation, + tz.schneider_thermostat_pi_heating_demand, + tz.schneider_thermostat_keypad_lockout, + ], + exposes: [ + e.keypad_lockout().withAccess(ea.STATE_SET), + e.humidity(), + e.battery(), + e.battery_voltage(), e.action(['screen_sleep', 'screen_wake', 'button_press_plus_down', 'button_press_center_down', 'button_press_minus_down']), - e.climate().withSetpoint('occupied_heating_setpoint', 4, 30, 0.5, ea.SET).withLocalTemperature(ea.STATE) - .withPiHeatingDemand(ea.SET)], + e.climate().withSetpoint('occupied_heating_setpoint', 4, 30, 0.5, ea.SET).withLocalTemperature(ea.STATE).withPiHeatingDemand(ea.SET), + ], meta: {battery: {dontDividePercentage: true}}, configure: async (device, coordinatorEndpoint) => { const endpoint1 = device.getEndpoint(1); - await reporting.bind(endpoint1, coordinatorEndpoint, - ['genPowerCfg', 'hvacThermostat', 'msTemperatureMeasurement', 'msRelativeHumidity']); + await reporting.bind(endpoint1, coordinatorEndpoint, ['genPowerCfg', 'hvacThermostat', 'msTemperatureMeasurement', 'msRelativeHumidity']); await reporting.temperature(endpoint1); await reporting.humidity(endpoint1); await reporting.batteryPercentageRemaining(endpoint1); @@ -1122,42 +1305,50 @@ const definitions: Definition[] = [ model: 'MEG5126-0300', vendor: 'Schneider Electric', description: 'Merten MEG5165 PlusLink relais insert with Merten Wiser System M push button (2fold)', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2}}), - onOff({endpointNames: ['l1', 'l2']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2}}), onOff({endpointNames: ['l1', 'l2']})], }, { zigbeeModel: ['EH-ZB-VACT'], model: 'EER53000', vendor: 'Schneider Electric', description: 'Wiser radiator thermostat (VACT)', - fromZigbee: [fz.ignore_basic_report, fz.ignore_genOta, fz.ignore_zclversion_read, fz.battery, fz.hvac_user_interface, - fz.wiser_smart_thermostat, fz.wiser_smart_thermostat_client, fz.wiser_smart_setpoint_command_client], - toZigbee: [tz.wiser_sed_thermostat_local_temperature_calibration, tz.wiser_sed_occupied_heating_setpoint, - tz.wiser_sed_thermostat_keypad_lockout, tz.wiser_vact_calibrate_valve, tz.wiser_sed_zone_mode], - exposes: [e.battery(), - e.binary('keypad_lockout', ea.STATE_SET, 'lock1', 'unlock') - .withDescription('Enables/disables physical input on the device'), - e.binary('calibrate_valve', ea.STATE_SET, 'calibrate', 'idle') - .withDescription('Calibrates valve on next wakeup'), - e.enum('valve_calibration_status', - ea.STATE, ['ongoing', 'successful', 'uncalibrated', 'failed_e1', 'failed_e2', 'failed_e3']), - e.enum('zone_mode', - ea.STATE_SET, ['manual', 'schedule', 'energy_saver', 'holiday']) - .withDescription('Icon shown on device displays'), - e.climate() + fromZigbee: [ + fz.ignore_basic_report, + fz.ignore_genOta, + fz.ignore_zclversion_read, + fz.battery, + fz.hvac_user_interface, + fz.wiser_smart_thermostat, + fz.wiser_smart_thermostat_client, + fz.wiser_smart_setpoint_command_client, + ], + toZigbee: [ + tz.wiser_sed_thermostat_local_temperature_calibration, + tz.wiser_sed_occupied_heating_setpoint, + tz.wiser_sed_thermostat_keypad_lockout, + tz.wiser_vact_calibrate_valve, + tz.wiser_sed_zone_mode, + ], + exposes: [ + e.battery(), + e.binary('keypad_lockout', ea.STATE_SET, 'lock1', 'unlock').withDescription('Enables/disables physical input on the device'), + e.binary('calibrate_valve', ea.STATE_SET, 'calibrate', 'idle').withDescription('Calibrates valve on next wakeup'), + e.enum('valve_calibration_status', ea.STATE, ['ongoing', 'successful', 'uncalibrated', 'failed_e1', 'failed_e2', 'failed_e3']), + e.enum('zone_mode', ea.STATE_SET, ['manual', 'schedule', 'energy_saver', 'holiday']).withDescription('Icon shown on device displays'), + e + .climate() .withSetpoint('occupied_heating_setpoint', 7, 30, 0.5, ea.STATE_SET) .withLocalTemperature(ea.STATE) .withLocalTemperatureCalibration(-12.8, 12.7, 0.1, ea.STATE_SET) - .withPiHeatingDemand()], + .withPiHeatingDemand(), + ], meta: {battery: {voltageToPercentage: '3V_2500'}}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(11); // Insert default values for client requested attributes - endpoint.saveClusterAttributeKeyValue('hvacThermostat', {minHeatSetpointLimit: 7*100}); - endpoint.saveClusterAttributeKeyValue('hvacThermostat', {maxHeatSetpointLimit: 30*100}); - endpoint.saveClusterAttributeKeyValue('hvacThermostat', {occupiedHeatingSetpoint: 20*100}); + endpoint.saveClusterAttributeKeyValue('hvacThermostat', {minHeatSetpointLimit: 7 * 100}); + endpoint.saveClusterAttributeKeyValue('hvacThermostat', {maxHeatSetpointLimit: 30 * 100}); + endpoint.saveClusterAttributeKeyValue('hvacThermostat', {occupiedHeatingSetpoint: 20 * 100}); endpoint.saveClusterAttributeKeyValue('hvacThermostat', {systemMode: 4}); // VACT needs binding to endpoint 11 due to some hardcoding in the device const coordinatorEndpointB = coordinatorEndpoint.getDevice().getEndpoint(11); @@ -1166,10 +1357,14 @@ const definitions: Definition[] = [ await reporting.batteryVoltage(endpoint); await reporting.thermostatTemperature(endpoint); await reporting.thermostatOccupiedHeatingSetpoint(endpoint); - await endpoint.configureReporting('hvacUserInterfaceCfg', [{attribute: 'keypadLockout', - minimumReportInterval: constants.repInterval.MINUTE, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 1}]); + await endpoint.configureReporting('hvacUserInterfaceCfg', [ + { + attribute: 'keypadLockout', + minimumReportInterval: constants.repInterval.MINUTE, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 1, + }, + ]); }, }, { @@ -1177,34 +1372,53 @@ const definitions: Definition[] = [ model: 'EER51000', vendor: 'Schneider Electric', description: 'Wiser thermostat (RTS)', - fromZigbee: [fz.ignore_basic_report, fz.ignore_genOta, fz.ignore_zclversion_read, fz.battery, fz.hvac_user_interface, - fz.wiser_smart_thermostat_client, fz.wiser_smart_setpoint_command_client, fz.schneider_temperature], + fromZigbee: [ + fz.ignore_basic_report, + fz.ignore_genOta, + fz.ignore_zclversion_read, + fz.battery, + fz.hvac_user_interface, + fz.wiser_smart_thermostat_client, + fz.wiser_smart_setpoint_command_client, + fz.schneider_temperature, + ], toZigbee: [tz.wiser_sed_zone_mode, tz.wiser_sed_occupied_heating_setpoint], - exposes: [e.battery(), - e.climate().withSetpoint('occupied_heating_setpoint', 7, 30, 0.5, ea.STATE_SET) - .withLocalTemperature(ea.STATE), - e.enum('zone_mode', - ea.STATE_SET, ['manual', 'schedule', 'energy_saver', 'holiday']) - .withDescription('Icon shown on device displays')], + exposes: [ + e.battery(), + e.climate().withSetpoint('occupied_heating_setpoint', 7, 30, 0.5, ea.STATE_SET).withLocalTemperature(ea.STATE), + e.enum('zone_mode', ea.STATE_SET, ['manual', 'schedule', 'energy_saver', 'holiday']).withDescription('Icon shown on device displays'), + ], meta: {battery: {voltageToPercentage: '4LR6AA1_5v'}}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(11); // Insert default values for client requested attributes - endpoint.saveClusterAttributeKeyValue('hvacThermostat', {minHeatSetpointLimit: 7*100}); - endpoint.saveClusterAttributeKeyValue('hvacThermostat', {maxHeatSetpointLimit: 30*100}); - endpoint.saveClusterAttributeKeyValue('hvacThermostat', {occupiedHeatingSetpoint: 20*100}); + endpoint.saveClusterAttributeKeyValue('hvacThermostat', {minHeatSetpointLimit: 7 * 100}); + endpoint.saveClusterAttributeKeyValue('hvacThermostat', {maxHeatSetpointLimit: 30 * 100}); + endpoint.saveClusterAttributeKeyValue('hvacThermostat', {occupiedHeatingSetpoint: 20 * 100}); endpoint.saveClusterAttributeKeyValue('hvacThermostat', {systemMode: 4}); // RTS needs binding to endpoint 11 due to some hardcoding in the device const coordinatorEndpointB = coordinatorEndpoint.getDevice().getEndpoint(11); - const binds = ['genBasic', 'genPowerCfg', 'genIdentify', 'genAlarms', 'genOta', 'hvacThermostat', - 'hvacUserInterfaceCfg', 'msTemperatureMeasurement']; + const binds = [ + 'genBasic', + 'genPowerCfg', + 'genIdentify', + 'genAlarms', + 'genOta', + 'hvacThermostat', + 'hvacUserInterfaceCfg', + 'msTemperatureMeasurement', + ]; await reporting.bind(endpoint, coordinatorEndpointB, binds); // Battery reports without config once a day, do the first read manually await endpoint.read('genPowerCfg', ['batteryVoltage']); - await endpoint.configureReporting('msTemperatureMeasurement', [{attribute: 'measuredValue', - minimumReportInterval: constants.repInterval.MINUTE, - maximumReportInterval: constants.repInterval.MINUTES_10, - reportableChange: 50}]); + await endpoint.configureReporting('msTemperatureMeasurement', [ + { + attribute: 'measuredValue', + minimumReportInterval: constants.repInterval.MINUTE, + maximumReportInterval: constants.repInterval.MINUTES_10, + reportableChange: 50, + }, + ]); }, }, { @@ -1212,21 +1426,28 @@ const definitions: Definition[] = [ model: 'EER50000', vendor: 'Schneider Electric', description: 'Wiser H-Relay (HACT)', - fromZigbee: [fz.ignore_basic_report, fz.ignore_genOta, fz.ignore_zclversion_read, fz.wiser_smart_thermostat, fz.metering, - fz.identify], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_occupied_heating_setpoint, tz.wiser_fip_setting, - tz.wiser_hact_config, tz.wiser_zone_mode, tz.identify], - exposes: [e.climate().withSetpoint('occupied_heating_setpoint', 7, 30, 0.5).withLocalTemperature(), - e.power(), e.energy(), + fromZigbee: [fz.ignore_basic_report, fz.ignore_genOta, fz.ignore_zclversion_read, fz.wiser_smart_thermostat, fz.metering, fz.identify], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_occupied_heating_setpoint, + tz.wiser_fip_setting, + tz.wiser_hact_config, + tz.wiser_zone_mode, + tz.identify, + ], + exposes: [ + e.climate().withSetpoint('occupied_heating_setpoint', 7, 30, 0.5).withLocalTemperature(), + e.power(), + e.energy(), e.enum('identify', ea.SET, ['0', '30', '60', '600', '900']).withDescription('Flash green tag for x seconds'), - e.enum('zone_mode', - ea.ALL, ['manual', 'schedule', 'energy_saver', 'holiday']), - e.enum('hact_config', - ea.ALL, ['unconfigured', 'setpoint_switch', 'setpoint_fip', 'fip_fip']) + e.enum('zone_mode', ea.ALL, ['manual', 'schedule', 'energy_saver', 'holiday']), + e + .enum('hact_config', ea.ALL, ['unconfigured', 'setpoint_switch', 'setpoint_fip', 'fip_fip']) .withDescription('Input (command) and output (control) behavior of actuator'), - e.enum('fip_setting', - ea.ALL, ['comfort', 'comfort_-1', 'comfort_-2', 'energy_saving', 'frost_protection', 'off']) - .withDescription('Output signal when operating in fil pilote mode (fip_fip)')], + e + .enum('fip_setting', ea.ALL, ['comfort', 'comfort_-1', 'comfort_-2', 'energy_saving', 'frost_protection', 'off']) + .withDescription('Output signal when operating in fil pilote mode (fip_fip)'), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(11); const binds = ['genBasic', 'genPowerCfg', 'hvacThermostat', 'msTemperatureMeasurement', 'seMetering']; @@ -1243,8 +1464,9 @@ const definitions: Definition[] = [ description: 'Wiser wireless switch 1-gang or 2-gang', extend: [ battery(), - deviceEndpoints({endpoints: {'right': 21, 'left': 22}}), - switchActions('right'), switchActions('left'), + deviceEndpoints({endpoints: {right: 21, left: 22}}), + switchActions('right'), + switchActions('left'), commandsOnOff({endpointNames: ['right', 'left']}), commandsLevelCtrl({endpointNames: ['right', 'left']}), ], @@ -1256,8 +1478,14 @@ const definitions: Definition[] = [ description: 'Zigbee smart socket with power meter', fromZigbee: [fz.on_off, fz.electrical_measurement, fz.EKO09738_metering, fz.power_on_behavior], toZigbee: [tz.on_off, tz.power_on_behavior], - exposes: [e.switch(), e.power(), e.energy(), e.enum('power_on_behavior', ea.ALL, ['off', 'previous', 'on']) - .withDescription('Controls the behaviour when the device is powered on'), e.current(), e.voltage()], + exposes: [ + e.switch(), + e.power(), + e.energy(), + e.enum('power_on_behavior', ea.ALL, ['off', 'previous', 'on']).withDescription('Controls the behaviour when the device is powered on'), + e.current(), + e.voltage(), + ], whiteLabel: [{vendor: 'Elko', model: 'EKO09738', description: 'SmartStikk'}], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(6); @@ -1275,8 +1503,14 @@ const definitions: Definition[] = [ description: 'Zigbee smart socket with power meter', fromZigbee: [fz.on_off, fz.electrical_measurement, fz.EKO09738_metering, fz.power_on_behavior], toZigbee: [tz.on_off, tz.power_on_behavior], - exposes: [e.switch(), e.power(), e.energy(), e.enum('power_on_behavior', ea.ALL, ['off', 'previous', 'on']) - .withDescription('Controls the behaviour when the device is powered on'), e.current(), e.voltage()], + exposes: [ + e.switch(), + e.power(), + e.energy(), + e.enum('power_on_behavior', ea.ALL, ['off', 'previous', 'on']).withDescription('Controls the behaviour when the device is powered on'), + e.current(), + e.voltage(), + ], extend: [socketIndicatorMode()], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(6); @@ -1294,9 +1528,14 @@ const definitions: Definition[] = [ description: 'LK FUGA wiser wireless socket outlet', fromZigbee: [fz.on_off, fz.electrical_measurement, fz.EKO09738_metering, fz.power_on_behavior], toZigbee: [tz.on_off, tz.power_on_behavior], - exposes: [e.switch(), e.power(), e.energy(), e.current(), e.voltage(), - e.enum('power_on_behavior', ea.ALL, ['off', 'previous', 'on']) - .withDescription('Controls the behaviour when the device is powered on')], + exposes: [ + e.switch(), + e.power(), + e.energy(), + e.current(), + e.voltage(), + e.enum('power_on_behavior', ea.ALL, ['off', 'previous', 'on']).withDescription('Controls the behaviour when the device is powered on'), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(6); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement', 'seMetering']); @@ -1313,12 +1552,21 @@ const definitions: Definition[] = [ vendor: 'Schneider Electric', description: 'LK FUGA Wiser wireless PIR with relay', fromZigbee: [fz.on_off, fz.illuminance, fz.occupancy, fz.occupancy_timeout], - exposes: [e.switch().withEndpoint('l1'), e.occupancy(), e.illuminance_lux(), e.illuminance(), - e.numeric('occupancy_timeout', ea.ALL).withUnit('s').withValueMin(0).withValueMax(3600) - .withDescription('Time in seconds after which occupancy is cleared after detecting it')], + exposes: [ + e.switch().withEndpoint('l1'), + e.occupancy(), + e.illuminance_lux(), + e.illuminance(), + e + .numeric('occupancy_timeout', ea.ALL) + .withUnit('s') + .withValueMin(0) + .withValueMax(3600) + .withDescription('Time in seconds after which occupancy is cleared after detecting it'), + ], toZigbee: [tz.on_off, tz.occupancy_timeout], endpoint: (device) => { - return {'default': 37, 'l1': 1, 'l2': 37}; + return {default: 37, l1: 1, l2: 37}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -1360,10 +1608,7 @@ const definitions: Definition[] = [ vendor: 'Schneider Electric', description: 'Dual connected smart socket', ota: ota.zigbeeOTA, - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2}}), - onOff({endpointNames: ['l1', 'l2']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2}}), onOff({endpointNames: ['l1', 'l2']})], }, { zigbeeModel: ['CCT592011_AS'], @@ -1396,18 +1641,12 @@ const definitions: Definition[] = [ e.numeric('voltage_phase_a', ea.STATE).withUnit('V').withDescription('Measured electrical potential value on phase A'), e.numeric('voltage_phase_b', ea.STATE).withUnit('V').withDescription('Measured electrical potential value on phase B'), e.numeric('voltage_phase_c', ea.STATE).withUnit('V').withDescription('Measured electrical potential value on phase C'), - e.numeric('voltage_phase_ab', ea.STATE) - .withUnit('V').withDescription('Measured electrical potential value between phase A and B'), - e.numeric('voltage_phase_bc', ea.STATE) - .withUnit('V').withDescription('Measured electrical potential value between phase B and C'), - e.numeric('voltage_phase_ca', ea.STATE) - .withUnit('V').withDescription('Measured electrical potential value between phase C and A'), - e.numeric('current_phase_a', ea.STATE) - .withUnit('A').withDescription('Instantaneous measured electrical current on phase A'), - e.numeric('current_phase_b', ea.STATE) - .withUnit('A').withDescription('Instantaneous measured electrical current on phase B'), - e.numeric('current_phase_c', ea.STATE) - .withUnit('A').withDescription('Instantaneous measured electrical current on phase C'), + e.numeric('voltage_phase_ab', ea.STATE).withUnit('V').withDescription('Measured electrical potential value between phase A and B'), + e.numeric('voltage_phase_bc', ea.STATE).withUnit('V').withDescription('Measured electrical potential value between phase B and C'), + e.numeric('voltage_phase_ca', ea.STATE).withUnit('V').withDescription('Measured electrical potential value between phase C and A'), + e.numeric('current_phase_a', ea.STATE).withUnit('A').withDescription('Instantaneous measured electrical current on phase A'), + e.numeric('current_phase_b', ea.STATE).withUnit('A').withDescription('Instantaneous measured electrical current on phase B'), + e.numeric('current_phase_c', ea.STATE).withUnit('A').withDescription('Instantaneous measured electrical current on phase C'), ], }, { @@ -1418,7 +1657,12 @@ const definitions: Definition[] = [ fromZigbee: [fz.temperature, fz.battery, fz.ias_enroll, fz.ias_smoke_alarm_1], toZigbee: [], ota: ota.zigbeeOTA, // local OTA updates are untested - exposes: [e.smoke(), e.battery_low(), e.tamper(), e.battery(), e.battery_voltage(), + exposes: [ + e.smoke(), + e.battery_low(), + e.tamper(), + e.battery(), + e.battery_voltage(), // the temperature readings are unreliable and may need more investigation. e.temperature(), ], @@ -1455,16 +1699,30 @@ const definitions: Definition[] = [ vendor: 'Schneider Electric', description: 'Smart thermostat', fromZigbee: [fz.stelpro_thermostat, fz.metering, fz.schneider_pilot_mode, fz.wiser_device_info, fz.hvac_user_interface], - toZigbee: [tz.thermostat_occupied_heating_setpoint, tz.thermostat_system_mode, tz.thermostat_running_state, - tz.thermostat_local_temperature, tz.thermostat_control_sequence_of_operation, tz.schneider_pilot_mode, - tz.schneider_thermostat_keypad_lockout, tz.thermostat_temperature_display_mode], - exposes: [e.binary('keypad_lockout', ea.STATE_SET, 'lock1', 'unlock') - .withDescription('Enables/disables physical input on the device'), - e.enum('schneider_pilot_mode', ea.ALL, ['contactor', 'pilot']).withDescription('Controls piloting mode'), - e.enum('temperature_display_mode', ea.ALL, ['celsius', 'fahrenheit']) - .withDescription('The temperature format displayed on the thermostat screen'), - e.climate().withSetpoint('occupied_heating_setpoint', 4, 30, 0.5).withLocalTemperature() - .withSystemMode(['off', 'heat']).withRunningState(['idle', 'heat']).withPiHeatingDemand()], + toZigbee: [ + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_system_mode, + tz.thermostat_running_state, + tz.thermostat_local_temperature, + tz.thermostat_control_sequence_of_operation, + tz.schneider_pilot_mode, + tz.schneider_thermostat_keypad_lockout, + tz.thermostat_temperature_display_mode, + ], + exposes: [ + e.binary('keypad_lockout', ea.STATE_SET, 'lock1', 'unlock').withDescription('Enables/disables physical input on the device'), + e.enum('schneider_pilot_mode', ea.ALL, ['contactor', 'pilot']).withDescription('Controls piloting mode'), + e + .enum('temperature_display_mode', ea.ALL, ['celsius', 'fahrenheit']) + .withDescription('The temperature format displayed on the thermostat screen'), + e + .climate() + .withSetpoint('occupied_heating_setpoint', 4, 30, 0.5) + .withLocalTemperature() + .withSystemMode(['off', 'heat']) + .withRunningState(['idle', 'heat']) + .withPiHeatingDemand(), + ], configure: async (device, coordinatorEndpoint) => { const endpoint1 = device.getEndpoint(1); const endpoint2 = device.getEndpoint(2); @@ -1481,17 +1739,31 @@ const definitions: Definition[] = [ vendor: 'Schneider Electric', description: 'Smart thermostat', fromZigbee: [fz.stelpro_thermostat, fz.metering, fz.schneider_pilot_mode, fz.wiser_device_info, fz.hvac_user_interface, fz.temperature], - toZigbee: [tz.thermostat_occupied_heating_setpoint, tz.thermostat_system_mode, tz.thermostat_running_state, - tz.thermostat_local_temperature, tz.thermostat_control_sequence_of_operation, tz.schneider_pilot_mode, - tz.schneider_thermostat_keypad_lockout, tz.thermostat_temperature_display_mode], - exposes: [e.binary('keypad_lockout', ea.STATE_SET, 'lock1', 'unlock') - .withDescription('Enables/disables physical input on the device'), - e.enum('schneider_pilot_mode', ea.ALL, ['contactor', 'pilot']).withDescription('Controls piloting mode'), - e.enum('temperature_display_mode', ea.ALL, ['celsius', 'fahrenheit']) - .withDescription('The temperature format displayed on the thermostat screen'), - e.climate().withSetpoint('occupied_heating_setpoint', 4, 30, 0.5).withLocalTemperature() - .withSystemMode(['off', 'heat']).withRunningState(['idle', 'heat']).withPiHeatingDemand(), - e.temperature()], + toZigbee: [ + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_system_mode, + tz.thermostat_running_state, + tz.thermostat_local_temperature, + tz.thermostat_control_sequence_of_operation, + tz.schneider_pilot_mode, + tz.schneider_thermostat_keypad_lockout, + tz.thermostat_temperature_display_mode, + ], + exposes: [ + e.binary('keypad_lockout', ea.STATE_SET, 'lock1', 'unlock').withDescription('Enables/disables physical input on the device'), + e.enum('schneider_pilot_mode', ea.ALL, ['contactor', 'pilot']).withDescription('Controls piloting mode'), + e + .enum('temperature_display_mode', ea.ALL, ['celsius', 'fahrenheit']) + .withDescription('The temperature format displayed on the thermostat screen'), + e + .climate() + .withSetpoint('occupied_heating_setpoint', 4, 30, 0.5) + .withLocalTemperature() + .withSystemMode(['off', 'heat']) + .withRunningState(['idle', 'heat']) + .withPiHeatingDemand(), + e.temperature(), + ], configure: async (device, coordinatorEndpoint) => { const endpoint1 = device.getEndpoint(1); const endpoint2 = device.getEndpoint(2); @@ -1509,17 +1781,31 @@ const definitions: Definition[] = [ vendor: 'Schneider Electric', description: 'Smart thermostat', fromZigbee: [fz.stelpro_thermostat, fz.metering, fz.schneider_pilot_mode, fz.wiser_device_info, fz.hvac_user_interface, fz.temperature], - toZigbee: [tz.thermostat_occupied_heating_setpoint, tz.thermostat_system_mode, tz.thermostat_running_state, - tz.thermostat_local_temperature, tz.thermostat_control_sequence_of_operation, tz.schneider_pilot_mode, - tz.schneider_thermostat_keypad_lockout, tz.thermostat_temperature_display_mode], - exposes: [e.binary('keypad_lockout', ea.STATE_SET, 'lock1', 'unlock') - .withDescription('Enables/disables physical input on the device'), - e.enum('schneider_pilot_mode', ea.ALL, ['contactor', 'pilot']).withDescription('Controls piloting mode'), - e.enum('temperature_display_mode', ea.ALL, ['celsius', 'fahrenheit']) - .withDescription('The temperature format displayed on the thermostat screen'), - e.climate().withSetpoint('occupied_heating_setpoint', 4, 30, 0.5).withLocalTemperature() - .withSystemMode(['off', 'heat']).withRunningState(['idle', 'heat']).withPiHeatingDemand(), - e.temperature()], + toZigbee: [ + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_system_mode, + tz.thermostat_running_state, + tz.thermostat_local_temperature, + tz.thermostat_control_sequence_of_operation, + tz.schneider_pilot_mode, + tz.schneider_thermostat_keypad_lockout, + tz.thermostat_temperature_display_mode, + ], + exposes: [ + e.binary('keypad_lockout', ea.STATE_SET, 'lock1', 'unlock').withDescription('Enables/disables physical input on the device'), + e.enum('schneider_pilot_mode', ea.ALL, ['contactor', 'pilot']).withDescription('Controls piloting mode'), + e + .enum('temperature_display_mode', ea.ALL, ['celsius', 'fahrenheit']) + .withDescription('The temperature format displayed on the thermostat screen'), + e + .climate() + .withSetpoint('occupied_heating_setpoint', 4, 30, 0.5) + .withLocalTemperature() + .withSystemMode(['off', 'heat']) + .withRunningState(['idle', 'heat']) + .withPiHeatingDemand(), + e.temperature(), + ], configure: async (device, coordinatorEndpoint) => { const endpoint1 = device.getEndpoint(1); const endpoint2 = device.getEndpoint(2); @@ -1536,9 +1822,11 @@ const definitions: Definition[] = [ model: 'MEG5126-0300_MEG5152-0000', vendor: 'Schneider Electric', description: 'Merten MEG5152 switch insert (2fold) with Merten System M push button (2fold)', - extend: [deviceEndpoints({'endpoints': {'left': 1, 'right': 2, 'left_sw': 21, 'right_sw': 22}}), identify(), - onOff({'powerOnBehavior': false, 'endpointNames': ['left', 'right']}), - commandsOnOff({'endpointNames': ['left_sw', 'right_sw']}), + extend: [ + deviceEndpoints({endpoints: {left: 1, right: 2, left_sw: 21, right_sw: 22}}), + identify(), + onOff({powerOnBehavior: false, endpointNames: ['left', 'right']}), + commandsOnOff({endpointNames: ['left_sw', 'right_sw']}), ], }, { @@ -1546,9 +1834,11 @@ const definitions: Definition[] = [ model: 'MEG5116-0300_MEG5162-0000', vendor: 'Schneider Electric', description: 'Merten MEG5162 switch insert (2fold) with Merten System M push button (1fold)', - extend: [deviceEndpoints({'endpoints': {'left': 1, 'right': 2, 'left_sw': 21}}), identify(), - onOff({'powerOnBehavior': false, 'endpointNames': ['left', 'right']}), - commandsOnOff({'endpointNames': ['left_sw']}), + extend: [ + deviceEndpoints({endpoints: {left: 1, right: 2, left_sw: 21}}), + identify(), + onOff({powerOnBehavior: false, endpointNames: ['left', 'right']}), + commandsOnOff({endpointNames: ['left_sw']}), ], }, { @@ -1556,9 +1846,11 @@ const definitions: Definition[] = [ model: 'MEG5116-0300_MEG5151-0000', vendor: 'Schneider Electric', description: 'Merten MEG5151 switch insert with Merten System M push button (1fold)', - extend: [deviceEndpoints({'endpoints': {'switch': 1, 'switch_sw': 21}}), identify(), - onOff({'powerOnBehavior': false}), - commandsOnOff({'endpointNames': ['switch_sw']}), + extend: [ + deviceEndpoints({endpoints: {switch: 1, switch_sw: 21}}), + identify(), + onOff({powerOnBehavior: false}), + commandsOnOff({endpointNames: ['switch_sw']}), ], }, { @@ -1567,7 +1859,7 @@ const definitions: Definition[] = [ vendor: 'Schneider Electric', description: 'Wiser AvatarOn 2G dimmer switch', extend: [ - deviceEndpoints({endpoints: {'l1': 10, 'l2': 11}}), + deviceEndpoints({endpoints: {l1: 10, l2: 11}}), light({ endpointNames: ['l1', 'l2'], effect: false, @@ -1597,7 +1889,7 @@ const definitions: Definition[] = [ vendor: 'Schneider Electric', description: 'Wiser AvatarOn 1G onoff switch', extend: [ - deviceEndpoints({endpoints: {'l1': 10}}), + deviceEndpoints({endpoints: {l1: 10}}), onOff({endpointNames: ['l1'], powerOnBehavior: false}), schneiderElectricExtend.addVisaConfigurationCluster(Zcl.DataType.UINT8), schneiderElectricExtend.visaConfigIndicatorLuminanceLevel(), @@ -1611,7 +1903,7 @@ const definitions: Definition[] = [ vendor: 'Schneider Electric', description: 'Wiser AvatarOn 2G onoff switch', extend: [ - deviceEndpoints({endpoints: {'l1': 10, 'l2': 11}}), + deviceEndpoints({endpoints: {l1: 10, l2: 11}}), onOff({endpointNames: ['l1', 'l2'], powerOnBehavior: false}), schneiderElectricExtend.addVisaConfigurationCluster(Zcl.DataType.UINT8), schneiderElectricExtend.visaConfigIndicatorLuminanceLevel(), @@ -1625,7 +1917,7 @@ const definitions: Definition[] = [ vendor: 'Schneider Electric', description: 'Wiser AvatarOn 2G onoff switch', extend: [ - deviceEndpoints({endpoints: {'l1': 10, 'l2': 11}}), + deviceEndpoints({endpoints: {l1: 10, l2: 11}}), onOff({endpointNames: ['l1', 'l2'], powerOnBehavior: false}), schneiderElectricExtend.addVisaConfigurationCluster(Zcl.DataType.UINT8), schneiderElectricExtend.visaIndicatorMode([0, 1, 2, 3]), @@ -1637,7 +1929,7 @@ const definitions: Definition[] = [ vendor: 'Schneider Electric', description: 'Wiser AvatarOn 3G onoff switch', extend: [ - deviceEndpoints({endpoints: {'l1': 10, 'l2': 11, 'l3': 12}}), + deviceEndpoints({endpoints: {l1: 10, l2: 11, l3: 12}}), onOff({endpointNames: ['l1', 'l2', 'l3'], powerOnBehavior: false}), schneiderElectricExtend.addVisaConfigurationCluster(Zcl.DataType.UINT8), schneiderElectricExtend.visaConfigIndicatorLuminanceLevel(), @@ -1651,7 +1943,7 @@ const definitions: Definition[] = [ vendor: 'Schneider Electric', description: 'Wiser AvatarOn 3G onoff switch', extend: [ - deviceEndpoints({endpoints: {'l1': 10, 'l2': 11, 'l3': 12}}), + deviceEndpoints({endpoints: {l1: 10, l2: 11, l3: 12}}), onOff({endpointNames: ['l1', 'l2', 'l3'], powerOnBehavior: false}), schneiderElectricExtend.addVisaConfigurationCluster(Zcl.DataType.UINT8), schneiderElectricExtend.visaIndicatorMode([0, 1, 2, 3]), @@ -1663,7 +1955,7 @@ const definitions: Definition[] = [ vendor: 'Schneider Electric', description: 'Wiser AvatarOn 2G curtain switch', extend: [ - deviceEndpoints({endpoints: {'l1': 10, 'l2': 11}}), + deviceEndpoints({endpoints: {l1: 10, l2: 11}}), schneiderElectricExtend.addVisaConfigurationCluster(Zcl.DataType.UINT8), schneiderElectricExtend.visaConfigIndicatorLuminanceLevel(), schneiderElectricExtend.visaConfigIndicatorColor(), diff --git a/src/devices/schwaiger.ts b/src/devices/schwaiger.ts index 6c53a3dc2f6c7..fe73608efa0dc 100644 --- a/src/devices/schwaiger.ts +++ b/src/devices/schwaiger.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; import {light} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; diff --git a/src/devices/seastar_intelligence.ts b/src/devices/seastar_intelligence.ts index d56a0cb766ae3..53f41dd44ea96 100644 --- a/src/devices/seastar_intelligence.ts +++ b/src/devices/seastar_intelligence.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { @@ -11,7 +11,7 @@ const definitions: Definition[] = [ endpoint: (device) => { // https://github.com/Koenkk/zigbee-herdsman-converters/issues/5463 const endpoint = device.endpoints.find((e) => e.inputClusters.includes(6)).ID; - return {'default': endpoint}; + return {default: endpoint}; }, }, ]; diff --git a/src/devices/securifi.ts b/src/devices/securifi.ts index c99bc396af606..d2c4986914a03 100644 --- a/src/devices/securifi.ts +++ b/src/devices/securifi.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as ota from '../lib/ota'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ @@ -19,8 +19,13 @@ const definitions: Definition[] = [ const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement']); endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', { - acVoltageMultiplier: 180, acVoltageDivisor: 39321, acCurrentMultiplier: 72, - acCurrentDivisor: 39321, acPowerMultiplier: 10255, acPowerDivisor: 39321}); + acVoltageMultiplier: 180, + acVoltageDivisor: 39321, + acCurrentMultiplier: 72, + acCurrentDivisor: 39321, + acPowerMultiplier: 10255, + acPowerDivisor: 39321, + }); await reporting.onOff(endpoint); await reporting.rmsVoltage(endpoint, {change: 110}); // Voltage reports in 0.00458V await reporting.rmsCurrent(endpoint, {change: 55}); // Current reports in 0.00183A diff --git a/src/devices/sengled.ts b/src/devices/sengled.ts index dfce2598e81b5..7c7a68334354f 100644 --- a/src/devices/sengled.ts +++ b/src/devices/sengled.ts @@ -1,10 +1,6 @@ -import {Definition, Expose, ModernExtend, Fz, KeyValueAny} from '../lib/types'; import {presets} from '../lib/exposes'; -import { - onOff, LightArgs, light as lightDontUse, electricityMeter, forcePowerSource, ota, - iasZoneAlarm, - battery, -} from '../lib/modernExtend'; +import {onOff, LightArgs, light as lightDontUse, electricityMeter, forcePowerSource, ota, iasZoneAlarm, battery} from '../lib/modernExtend'; +import {Definition, Expose, ModernExtend, Fz, KeyValueAny} from '../lib/types'; export function sengledLight(args?: LightArgs) { return lightDontUse({effect: false, powerOnBehavior: false, ...args}); @@ -13,33 +9,36 @@ export function sengledLight(args?: LightArgs) { export function sengledSwitchAction(): ModernExtend { const exposes: Expose[] = [presets.action(['on', 'up', 'down', 'off', 'on_double', 'on_long', 'off_double', 'off_long'])]; - const fromZigbee: Fz.Converter[] = [{ - cluster: 64528, - type: ['raw'], - convert: (model, msg, publish, options, meta) => { - // A list of commands the sixth digit in the raw data can map to - const lookup: KeyValueAny = { - 1: 'on', - 2: 'up', - // Two outputs for long press. The eighth digit outputs 1 for initial press then 2 for each - // LED blink (approx 1 second, repeating until release) - 3: 'down', // Same as above - 4: 'off', - 5: 'on_double', - 6: 'on_long', - 7: 'off_double', - 8: 'off_long', - }; + const fromZigbee: Fz.Converter[] = [ + { + cluster: 64528, + type: ['raw'], + convert: (model, msg, publish, options, meta) => { + // A list of commands the sixth digit in the raw data can map to + const lookup: KeyValueAny = { + 1: 'on', + 2: 'up', + // Two outputs for long press. The eighth digit outputs 1 for initial press then 2 for each + // LED blink (approx 1 second, repeating until release) + 3: 'down', // Same as above + 4: 'off', + 5: 'on_double', + 6: 'on_long', + 7: 'off_double', + 8: 'off_long', + }; - if (msg.data[7] === 2) { // If the 8th digit is 2 (implying long press) - // Append '_long' to the end of the action so the user knows it was a long press. - // This only applies to the up and down action - return {action: `${lookup[msg.data[5]]}_long`}; - } else { - return {action: lookup[msg.data[5]]}; // Just output the data from the above lookup list - } + if (msg.data[7] === 2) { + // If the 8th digit is 2 (implying long press) + // Append '_long' to the end of the action so the user knows it was a long press. + // This only applies to the up and down action + return {action: `${lookup[msg.data[5]]}_long`}; + } else { + return {action: lookup[msg.data[5]]}; // Just output the data from the above lookup list + } + }, }, - }]; + ]; return {exposes, fromZigbee, isModernExtend: true}; } @@ -50,21 +49,14 @@ const definitions: Definition[] = [ model: 'E13-N11', vendor: 'Sengled', description: 'Flood light with motion sensor light outdoor', - extend: [ - sengledLight(), - iasZoneAlarm({zoneType: 'occupancy', zoneAttributes: ['alarm_1']}), - ota(), - ], + extend: [sengledLight(), iasZoneAlarm({zoneType: 'occupancy', zoneAttributes: ['alarm_1']}), ota()], }, { zigbeeModel: ['E21-N13A'], model: 'E21-N13A', vendor: 'Sengled', description: 'Smart LED (A19)', - extend: [ - sengledLight(), - ota(), - ], + extend: [sengledLight(), ota()], }, { zigbeeModel: ['E21-N1EA'], @@ -95,83 +87,56 @@ const definitions: Definition[] = [ model: 'E1G-G8E', vendor: 'Sengled', description: 'Multicolor light strip (2M)', - extend: [ - sengledLight({colorTemp: {range: undefined}, color: {modes: ['xy']}}), - ota(), - ], + extend: [sengledLight({colorTemp: {range: undefined}, color: {modes: ['xy']}}), ota()], }, { zigbeeModel: ['E11-U21U31'], model: 'E11-U21U31', vendor: 'Sengled', description: 'Element touch (A19)', - extend: [ - sengledLight(), - ota(), - ], + extend: [sengledLight(), ota()], }, { zigbeeModel: ['E11-G13'], model: 'E11-G13', vendor: 'Sengled', description: 'Element classic (A19)', - extend: [ - forcePowerSource({powerSource: 'Mains (single phase)'}), - sengledLight(), - electricityMeter({cluster: 'metering'}), - ota(), - ], + extend: [forcePowerSource({powerSource: 'Mains (single phase)'}), sengledLight(), electricityMeter({cluster: 'metering'}), ota()], }, { zigbeeModel: ['E11-G23', 'E11-G33'], model: 'E11-G23/E11-G33', vendor: 'Sengled', description: 'Element classic (A60)', - extend: [ - sengledLight(), - ota(), - ], + extend: [sengledLight(), ota()], }, { zigbeeModel: ['E11-N13', 'E11-N13A', 'E11-N14', 'E11-N14A'], model: 'E11-N13/E11-N13A/E11-N14/E11-N14A', vendor: 'Sengled', description: 'Element extra bright (A19)', - extend: [ - sengledLight(), - ota(), - ], + extend: [sengledLight(), ota()], }, { zigbeeModel: ['Z01-CIA19NAE26'], model: 'Z01-CIA19NAE26', vendor: 'Sengled', description: 'Element touch (A19)', - extend: [ - sengledLight(), - ota(), - ], + extend: [sengledLight(), ota()], }, { zigbeeModel: ['Z01-A19NAE26'], model: 'Z01-A19NAE26', vendor: 'Sengled', description: 'Element plus (A19)', - extend: [ - sengledLight({colorTemp: {range: [154, 500]}, color: {modes: ['xy']}}), - electricityMeter({cluster: 'metering'}), - ota(), - ], + extend: [sengledLight({colorTemp: {range: [154, 500]}, color: {modes: ['xy']}}), electricityMeter({cluster: 'metering'}), ota()], }, { zigbeeModel: ['Z01-A60EAE27'], model: 'Z01-A60EAE27', vendor: 'Sengled', description: 'Element Plus (A60)', - extend: [ - sengledLight({colorTemp: {range: undefined}}), - ota(), - ], + extend: [sengledLight({colorTemp: {range: undefined}}), ota()], }, { zigbeeModel: ['E11-N1EA'], @@ -190,20 +155,14 @@ const definitions: Definition[] = [ model: 'E11-U2E', vendor: 'Sengled', description: 'Element color plus E27', - extend: [ - sengledLight({colorTemp: {range: undefined}, color: {modes: ['xy']}}), - ota(), - ], + extend: [sengledLight({colorTemp: {range: undefined}, color: {modes: ['xy']}}), ota()], }, { zigbeeModel: ['E11-U3E'], model: 'E11-U3E', vendor: 'Sengled', description: 'Element color plus B22', - extend: [ - sengledLight({colorTemp: {range: undefined}, color: {modes: ['xy']}}), - ota(), - ], + extend: [sengledLight({colorTemp: {range: undefined}, color: {modes: ['xy']}}), ota()], }, { zigbeeModel: ['E1F-N5E'], @@ -222,20 +181,14 @@ const definitions: Definition[] = [ model: 'E12-N14', vendor: 'Sengled', description: 'Element Classic (BR30)', - extend: [ - sengledLight(), - ota(), - ], + extend: [sengledLight(), ota()], }, { zigbeeModel: ['E1A-AC2'], model: 'E1ACA4ABE38A', vendor: 'Sengled', description: 'Element downlight smart LED bulb', - extend: [ - sengledLight(), - ota(), - ], + extend: [sengledLight(), ota()], }, { zigbeeModel: ['E1D-G73'], @@ -264,61 +217,42 @@ const definitions: Definition[] = [ model: 'E1C-NB6', vendor: 'Sengled', description: 'Smart plug', - extend: [ - onOff(), - ota(), - ], + extend: [onOff(), ota()], }, { zigbeeModel: ['E1C-NB7'], model: 'E1C-NB7', vendor: 'Sengled', description: 'Smart plug with energy tracker', - extend: [ - onOff({powerOnBehavior: false}), - electricityMeter({cluster: 'metering'}), - ], + extend: [onOff({powerOnBehavior: false}), electricityMeter({cluster: 'metering'})], }, { zigbeeModel: ['E1E-G7F'], model: 'E1E-G7F', vendor: 'Sengled', description: 'Smart switch', - extend: [ - sengledSwitchAction(), - ota(), - ], + extend: [sengledSwitchAction(), ota()], }, { zigbeeModel: ['E11-N1G'], model: 'E11-N1G', vendor: 'Sengled', description: 'Vintage LED edison bulb (ST19)', - extend: [ - sengledLight(), - ota(), - ], + extend: [sengledLight(), ota()], }, { zigbeeModel: ['E1F-N9G'], model: 'E1F-N9G', vendor: 'Sengled', description: 'Smart LED filament candle (E12)', - extend: [ - sengledLight(), - ota(), - ], + extend: [sengledLight(), ota()], }, { zigbeeModel: ['E21-N14A'], model: 'E21-N14A', vendor: 'Sengled', description: 'Smart light bulb, dimmable 5000K, E26/A19', - extend: [ - sengledLight(), - electricityMeter({cluster: 'metering'}), - ota(), - ], + extend: [sengledLight(), electricityMeter({cluster: 'metering'}), ota()], }, ]; diff --git a/src/devices/sercomm.ts b/src/devices/sercomm.ts index abde260780cb9..8f50059fe5bf4 100644 --- a/src/devices/sercomm.ts +++ b/src/devices/sercomm.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/shade_control.ts b/src/devices/shade_control.ts index 93707047a8d8e..e7edf55e5acf9 100644 --- a/src/devices/shade_control.ts +++ b/src/devices/shade_control.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/shenzhen_homa.ts b/src/devices/shenzhen_homa.ts index 765c63082dc1c..a11aa29c9fbf3 100644 --- a/src/devices/shenzhen_homa.ts +++ b/src/devices/shenzhen_homa.ts @@ -1,29 +1,32 @@ -import {Definition} from '../lib/types'; import {deviceEndpoints, light, onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { fingerprint: [ - {modelID: 'HOMA1001', endpoints: [ - {ID: 10, profileID: 49246, deviceID: 256, inputClusters: [0, 3, 4, 5, 6, 8], outputClusters: []}, - {ID: 11, profileID: 49246, deviceID: 528, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, - {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, - ]}, + { + modelID: 'HOMA1001', + endpoints: [ + {ID: 10, profileID: 49246, deviceID: 256, inputClusters: [0, 3, 4, 5, 6, 8], outputClusters: []}, + {ID: 11, profileID: 49246, deviceID: 528, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, + {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, + ], + }, ], model: 'HOMA1001_RGBW', vendor: 'Shenzhen Homa', description: 'Smart LED driver RGBW', - extend: [ - deviceEndpoints({endpoints: {'white': 10, 'rgb': 11}}), - light({endpointNames: ['white', 'rgb'], color: true}), - ], + extend: [deviceEndpoints({endpoints: {white: 10, rgb: 11}}), light({endpointNames: ['white', 'rgb'], color: true})], }, { fingerprint: [ - {modelID: 'HOMA1001', endpoints: [ - {ID: 11, profileID: 49246, deviceID: 528, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, - {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, - ]}, + { + modelID: 'HOMA1001', + endpoints: [ + {ID: 11, profileID: 49246, deviceID: 528, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, + {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, + ], + }, ], model: 'HOMA1001_RGB', vendor: 'Shenzhen Homa', @@ -32,10 +35,13 @@ const definitions: Definition[] = [ }, { fingerprint: [ - {modelID: 'HOMA1001', endpoints: [ - {ID: 11, profileID: 49246, deviceID: 544, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, - {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, - ]}, + { + modelID: 'HOMA1001', + endpoints: [ + {ID: 11, profileID: 49246, deviceID: 544, inputClusters: [0, 3, 4, 5, 6, 8, 768], outputClusters: []}, + {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, + ], + }, ], model: 'HOMA1001_CT', vendor: 'Shenzhen Homa', @@ -44,10 +50,13 @@ const definitions: Definition[] = [ }, { fingerprint: [ - {modelID: 'HOMA1001', endpoints: [ - {ID: 11, profileID: 49246, deviceID: 256, inputClusters: [0, 3, 4, 5, 6, 8], outputClusters: []}, - {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, - ]}, + { + modelID: 'HOMA1001', + endpoints: [ + {ID: 11, profileID: 49246, deviceID: 256, inputClusters: [0, 3, 4, 5, 6, 8], outputClusters: []}, + {ID: 13, profileID: 49246, deviceID: 57694, inputClusters: [4096], outputClusters: [4096]}, + ], + }, ], model: 'HOMA1001_SC', vendor: 'Shenzhen Homa', @@ -87,10 +96,7 @@ const definitions: Definition[] = [ model: 'HLC614-ZLL', vendor: 'Shenzhen Homa', description: '3 channel relay module', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2, 'l3': 3}}), - onOff({endpointNames: ['l1', 'l2', 'l3']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2, l3: 3}}), onOff({endpointNames: ['l1', 'l2', 'l3']})], }, { zigbeeModel: ['HOMA1064', '012'], diff --git a/src/devices/shinasystem.ts b/src/devices/shinasystem.ts index e3df218bb9327..77297d7edcf94 100644 --- a/src/devices/shinasystem.ts +++ b/src/devices/shinasystem.ts @@ -1,12 +1,12 @@ -import {Definition, Fz, Tz} from '../lib/types'; -import * as exposes from '../lib/exposes'; -import * as utils from '../lib/utils'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; import {onOff, numeric, enumLookup, deviceEndpoints} from '../lib/modernExtend'; import * as ota from '../lib/ota'; +import * as reporting from '../lib/reporting'; import * as globalStore from '../lib/store'; +import {Definition, Fz, Tz} from '../lib/types'; +import * as utils from '../lib/utils'; const e = exposes.presets; const ea = exposes.access; @@ -63,95 +63,95 @@ const tzLocal = { let payload = null; const endpoint = meta.device.endpoints.find((e) => e.supportsInputCluster('genAnalogInput')); switch (key) { - case 'rf_pairing_on': - payload = {'presentValue': 81}; - break; - case 'counting_freeze': - utils.assertString(value, key); - if (value.toLowerCase() === 'on') { - payload = {'presentValue': 82}; - await endpoint.write('genAnalogInput', payload); - return {state: {counting_freeze: 'ON'}}; - } else if (value.toLowerCase() === 'off') { - payload = {'presentValue': 84}; - await endpoint.write('genAnalogInput', payload); - return {state: {counting_freeze: 'OFF'}}; - } - break; - case 'tof_init': - payload = {'presentValue': 83}; - break; - case 'led_state': - if (value === 'enable') { - payload = {'presentValue': 86}; - await endpoint.write('genAnalogInput', payload); - return {state: {led_state: 'enable'}}; - } else if (value === 'disable') { - payload = {'presentValue': 87}; - await endpoint.write('genAnalogInput', payload); - return {state: {led_state: 'disable'}}; - } - break; - case 'rf_state': - if (value === 'enable') { - payload = {'presentValue': 88}; - await endpoint.write('genAnalogInput', payload); - return {state: {rf_state: 'enable'}}; - } else if (value === 'disable') { - payload = {'presentValue': 89}; - await endpoint.write('genAnalogInput', payload); - return {state: {rf_state: 'disable'}}; - } - break; - case 'transaction': - if (value === '0ms') { - payload = {'presentValue': 90}; - await endpoint.write('genAnalogInput', payload); - return {state: {transaction: '0ms'}}; - } else if (value === '200ms') { - payload = {'presentValue': 91}; - await endpoint.write('genAnalogInput', payload); - return {state: {transaction: '200ms'}}; - } else if (value === '400ms') { - payload = {'presentValue': 92}; - await endpoint.write('genAnalogInput', payload); - return {state: {transaction: '400ms'}}; - } else if (value === '600ms') { - payload = {'presentValue': 93}; - await endpoint.write('genAnalogInput', payload); - return {state: {transaction: '600ms'}}; - } else if (value === '800ms') { - payload = {'presentValue': 94}; - await endpoint.write('genAnalogInput', payload); - return {state: {transaction: '800ms'}}; - } else if (value === '1,000ms') { - payload = {'presentValue': 95}; - await endpoint.write('genAnalogInput', payload); - return {state: {transaction: '1,000ms'}}; - } - break; - case 'fast_in': - if (value === 'enable') { - payload = {'presentValue': 96}; - await endpoint.write('genAnalogInput', payload); - return {state: {fast_in: 'enable'}}; - } else if (value === 'disable') { - payload = {'presentValue': 97}; - await endpoint.write('genAnalogInput', payload); - return {state: {fast_in: 'disable'}}; - } - break; - case 'fast_out': - if (value === 'enable') { - payload = {'presentValue': 98}; - await endpoint.write('genAnalogInput', payload); - return {state: {fast_out: 'enable'}}; - } else if (value === 'disable') { - payload = {'presentValue': 99}; - await endpoint.write('genAnalogInput', payload); - return {state: {fast_out: 'disable'}}; - } - break; + case 'rf_pairing_on': + payload = {presentValue: 81}; + break; + case 'counting_freeze': + utils.assertString(value, key); + if (value.toLowerCase() === 'on') { + payload = {presentValue: 82}; + await endpoint.write('genAnalogInput', payload); + return {state: {counting_freeze: 'ON'}}; + } else if (value.toLowerCase() === 'off') { + payload = {presentValue: 84}; + await endpoint.write('genAnalogInput', payload); + return {state: {counting_freeze: 'OFF'}}; + } + break; + case 'tof_init': + payload = {presentValue: 83}; + break; + case 'led_state': + if (value === 'enable') { + payload = {presentValue: 86}; + await endpoint.write('genAnalogInput', payload); + return {state: {led_state: 'enable'}}; + } else if (value === 'disable') { + payload = {presentValue: 87}; + await endpoint.write('genAnalogInput', payload); + return {state: {led_state: 'disable'}}; + } + break; + case 'rf_state': + if (value === 'enable') { + payload = {presentValue: 88}; + await endpoint.write('genAnalogInput', payload); + return {state: {rf_state: 'enable'}}; + } else if (value === 'disable') { + payload = {presentValue: 89}; + await endpoint.write('genAnalogInput', payload); + return {state: {rf_state: 'disable'}}; + } + break; + case 'transaction': + if (value === '0ms') { + payload = {presentValue: 90}; + await endpoint.write('genAnalogInput', payload); + return {state: {transaction: '0ms'}}; + } else if (value === '200ms') { + payload = {presentValue: 91}; + await endpoint.write('genAnalogInput', payload); + return {state: {transaction: '200ms'}}; + } else if (value === '400ms') { + payload = {presentValue: 92}; + await endpoint.write('genAnalogInput', payload); + return {state: {transaction: '400ms'}}; + } else if (value === '600ms') { + payload = {presentValue: 93}; + await endpoint.write('genAnalogInput', payload); + return {state: {transaction: '600ms'}}; + } else if (value === '800ms') { + payload = {presentValue: 94}; + await endpoint.write('genAnalogInput', payload); + return {state: {transaction: '800ms'}}; + } else if (value === '1,000ms') { + payload = {presentValue: 95}; + await endpoint.write('genAnalogInput', payload); + return {state: {transaction: '1,000ms'}}; + } + break; + case 'fast_in': + if (value === 'enable') { + payload = {presentValue: 96}; + await endpoint.write('genAnalogInput', payload); + return {state: {fast_in: 'enable'}}; + } else if (value === 'disable') { + payload = {presentValue: 97}; + await endpoint.write('genAnalogInput', payload); + return {state: {fast_in: 'disable'}}; + } + break; + case 'fast_out': + if (value === 'enable') { + payload = {presentValue: 98}; + await endpoint.write('genAnalogInput', payload); + return {state: {fast_out: 'enable'}}; + } else if (value === 'disable') { + payload = {presentValue: 99}; + await endpoint.write('genAnalogInput', payload); + return {state: {fast_out: 'disable'}}; + } + break; } await endpoint.write('genAnalogInput', payload); }, @@ -159,7 +159,7 @@ const tzLocal = { GCM300Z_valve_status: { key: ['gas_valve_state'], convertSet: async (entity, key, value, meta) => { - const lookup = {'CLOSE': 'off'}; // open is not supported. + const lookup = {CLOSE: 'off'}; // open is not supported. const state = utils.getFromLookup(value, lookup); if (state != 'off') value = 'CLOSE'; else await entity.command('genOnOff', state, {}, utils.getOptions(meta.mapped, entity)); @@ -193,9 +193,12 @@ const definitions: Definition[] = [ const payload = reporting.payload('presentValue', 1, 600, 0); await endpoint.configureReporting('genAnalogInput', payload); }, - exposes: [e.battery(), e.battery_voltage(), + exposes: [ + e.battery(), + e.battery_voltage(), e.enum('status', ea.STATE, ['idle', 'in', 'out']).withDescription('Currently status'), - e.numeric('people', ea.ALL).withValueMin(0).withValueMax(50).withDescription('People count')], + e.numeric('people', ea.ALL).withValueMin(0).withValueMax(50).withDescription('People count'), + ], }, { zigbeeModel: ['CSM-300Z'], @@ -214,21 +217,22 @@ const definitions: Definition[] = [ const payload = reporting.payload('presentValue', 1, 600, 0); await endpoint.configureReporting('genAnalogInput', payload); }, - exposes: [e.battery(), e.battery_voltage(), + exposes: [ + e.battery(), + e.battery_voltage(), e.enum('status', ea.STATE, ['idle', 'in', 'out']).withDescription('Currently status'), e.numeric('people', ea.ALL).withValueMin(0).withValueMax(100).withDescription('People count'), e.enum('rf_pairing_on', ea.SET, ['run']).withDescription('Run RF pairing mode'), - e.binary('counting_freeze', ea.SET, 'ON', 'OFF') - .withDescription('Counting Freeze ON/OFF, not reporting people value when is ON'), + e.binary('counting_freeze', ea.SET, 'ON', 'OFF').withDescription('Counting Freeze ON/OFF, not reporting people value when is ON'), e.enum('tof_init', ea.SET, ['initial']).withDescription('ToF sensor initial'), e.binary('led_state', ea.SET, 'enable', 'disable').withDescription('Indicate LED enable/disable, default : enable'), e.binary('rf_state', ea.SET, 'enable', 'disable').withDescription('RF function enable/disable, default : disable'), - e.enum('transaction', ea.SET, ['0ms', '200ms', '400ms', '600ms', '800ms', '1,000ms']) + e + .enum('transaction', ea.SET, ['0ms', '200ms', '400ms', '600ms', '800ms', '1,000ms']) .withDescription('Transaction interval, default : 400ms'), - e.binary('fast_in', ea.SET, 'enable', 'disable') - .withDescription('Fast process enable/disable when people 0 to 1. default : enable'), - e.binary('fast_out', ea.SET, 'enable', 'disable') - .withDescription('Fast process enable/disable when people 1 to 0. default : enable')], + e.binary('fast_in', ea.SET, 'enable', 'disable').withDescription('Fast process enable/disable when people 0 to 1. default : enable'), + e.binary('fast_out', ea.SET, 'enable', 'disable').withDescription('Fast process enable/disable when people 1 to 0. default : enable'), + ], }, { zigbeeModel: ['USM-300Z'], @@ -248,8 +252,7 @@ const definitions: Definition[] = [ await reporting.humidity(endpoint, {min: 20, max: 300, change: 40}); await reporting.illuminance(endpoint, {min: 20, max: 3600, change: 10}); }, - exposes: [e.battery(), e.battery_voltage(), e.temperature(), e.humidity(), e.occupancy(), - e.illuminance_lux().withProperty('illuminance')], + exposes: [e.battery(), e.battery_voltage(), e.temperature(), e.humidity(), e.occupancy(), e.illuminance_lux().withProperty('illuminance')], }, { zigbeeModel: ['SBM300Z1'], @@ -263,10 +266,7 @@ const definitions: Definition[] = [ model: 'SBM300Z2', vendor: 'ShinaSystem', description: 'SiHAS IOT smart switch 2 gang', - extend: [ - deviceEndpoints({endpoints: {'top': 1, 'bottom': 2}}), - onOff({endpointNames: ['top', 'bottom'], powerOnBehavior: false}), - ], + extend: [deviceEndpoints({endpoints: {top: 1, bottom: 2}}), onOff({endpointNames: ['top', 'bottom'], powerOnBehavior: false})], }, { zigbeeModel: ['SBM300Z3'], @@ -274,7 +274,7 @@ const definitions: Definition[] = [ vendor: 'ShinaSystem', description: 'SiHAS IOT smart switch 3 gang', extend: [ - deviceEndpoints({endpoints: {'top': 1, 'center': 2, 'bottom': 3}}), + deviceEndpoints({endpoints: {top: 1, center: 2, bottom: 3}}), onOff({endpointNames: ['top', 'center', 'bottom'], powerOnBehavior: false}), ], }, @@ -284,7 +284,7 @@ const definitions: Definition[] = [ vendor: 'ShinaSystem', description: 'SiHAS IOT smart switch 4 gang', extend: [ - deviceEndpoints({endpoints: {'top_left': 1, 'bottom_left': 2, 'top_right': 3, 'bottom_right': 4}}), + deviceEndpoints({endpoints: {top_left: 1, bottom_left: 2, top_right: 3, bottom_right: 4}}), onOff({endpointNames: ['top_left', 'bottom_left', 'top_right', 'bottom_right'], powerOnBehavior: false}), ], }, @@ -294,7 +294,7 @@ const definitions: Definition[] = [ vendor: 'ShinaSystem', description: 'SiHAS IOT smart switch 5 gang', extend: [ - deviceEndpoints({endpoints: {'top_left': 1, 'center_left': 2, 'bottom_left': 3, 'top_right': 4, 'bottom_right': 5}}), + deviceEndpoints({endpoints: {top_left: 1, center_left: 2, bottom_left: 3, top_right: 4, bottom_right: 5}}), onOff({endpointNames: ['top_left', 'center_left', 'bottom_left', 'top_right', 'bottom_right'], powerOnBehavior: false}), ], }, @@ -304,7 +304,7 @@ const definitions: Definition[] = [ vendor: 'ShinaSystem', description: 'SiHAS IOT smart switch 6 gang', extend: [ - deviceEndpoints({endpoints: {'top_left': 1, 'center_left': 2, 'bottom_left': 3, 'top_right': 4, 'center_right': 5, 'bottom_right': 6}}), + deviceEndpoints({endpoints: {top_left: 1, center_left: 2, bottom_left: 3, top_right: 4, center_right: 5, bottom_right: 6}}), onOff({ endpointNames: ['top_left', 'center_left', 'bottom_left', 'top_right', 'center_right', 'bottom_right'], powerOnBehavior: false, @@ -366,8 +366,24 @@ const definitions: Definition[] = [ description: 'SiHAS remote control 4 button', fromZigbee: [fz.sihas_action, fz.battery], toZigbee: [], - exposes: [e.action(['1_single', '1_double', '1_long', '2_single', '2_double', '2_long', - '3_single', '3_double', '3_long', '4_single', '4_double', '4_long']), e.battery(), e.battery_voltage()], + exposes: [ + e.action([ + '1_single', + '1_double', + '1_long', + '2_single', + '2_double', + '2_long', + '3_single', + '3_double', + '3_long', + '4_single', + '4_double', + '4_long', + ]), + e.battery(), + e.battery_voltage(), + ], meta: {battery: {voltageToPercentage: '3V_2100'}, multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -408,7 +424,6 @@ const definitions: Definition[] = [ await reporting.batteryVoltage(endpoint, {min: 30, max: 21600, change: 1}); await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ['genOnOff']); }, - }, { zigbeeModel: ['SBM300ZB3'], @@ -417,8 +432,11 @@ const definitions: Definition[] = [ description: 'SiHAS remote control 3 button', fromZigbee: [fz.sihas_action, fz.battery], toZigbee: [], - exposes: [e.action(['1_single', '1_double', '1_long', '2_single', '2_double', '2_long', - '3_single', '3_double', '3_long']), e.battery(), e.battery_voltage()], + exposes: [ + e.action(['1_single', '1_double', '1_long', '2_single', '2_double', '2_long', '3_single', '3_double', '3_long']), + e.battery(), + e.battery_voltage(), + ], meta: {battery: {voltageToPercentage: '3V_2100'}, multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -436,8 +454,24 @@ const definitions: Definition[] = [ description: 'SiHAS remote control 4 button', fromZigbee: [fz.sihas_action, fz.battery], toZigbee: [], - exposes: [e.action(['1_single', '1_double', '1_long', '2_single', '2_double', '2_long', - '3_single', '3_double', '3_long', '4_single', '4_double', '4_long']), e.battery(), e.battery_voltage()], + exposes: [ + e.action([ + '1_single', + '1_double', + '1_long', + '2_single', + '2_double', + '2_long', + '3_single', + '3_double', + '3_long', + '4_single', + '4_double', + '4_long', + ]), + e.battery(), + e.battery_voltage(), + ], meta: {battery: {voltageToPercentage: '3V_2100'}, multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -489,8 +523,11 @@ const definitions: Definition[] = [ description: 'SiHAS remote control 3 button', fromZigbee: [fz.sihas_action, fz.battery], toZigbee: [], - exposes: [e.action(['1_single', '1_double', '1_long', '2_single', '2_double', '2_long', - '3_single', '3_double', '3_long']), e.battery(), e.battery_voltage()], + exposes: [ + e.action(['1_single', '1_double', '1_long', '2_single', '2_double', '2_long', '3_single', '3_double', '3_long']), + e.battery(), + e.battery_voltage(), + ], meta: {battery: {voltageToPercentage: '3V_2100'}, multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -508,8 +545,24 @@ const definitions: Definition[] = [ description: 'SiHAS remote control 4 button', fromZigbee: [fz.sihas_action, fz.battery], toZigbee: [], - exposes: [e.action(['1_single', '1_double', '1_long', '2_single', '2_double', '2_long', - '3_single', '3_double', '3_long', '4_single', '4_double', '4_long']), e.battery(), e.battery_voltage()], + exposes: [ + e.action([ + '1_single', + '1_double', + '1_long', + '2_single', + '2_double', + '2_long', + '3_single', + '3_double', + '3_long', + '4_single', + '4_double', + '4_long', + ]), + e.battery(), + e.battery_voltage(), + ], meta: {battery: {voltageToPercentage: '3V_2100'}, multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -545,16 +598,19 @@ const definitions: Definition[] = [ description: 'SiHAS energy monitor', fromZigbee: [fz.electrical_measurement, fz.metering, fz.temperature], toZigbee: [tz.metering_power, tz.currentsummdelivered, tz.frequency, tz.powerfactor, tz.acvoltage, tz.accurrent, tz.temperature], - exposes: [e.power().withAccess(ea.STATE_GET), e.energy().withAccess(ea.STATE_GET), - e.current().withAccess(ea.STATE_GET), e.voltage().withAccess(ea.STATE_GET), + exposes: [ + e.power().withAccess(ea.STATE_GET), + e.energy().withAccess(ea.STATE_GET), + e.current().withAccess(ea.STATE_GET), + e.voltage().withAccess(ea.STATE_GET), e.temperature().withAccess(ea.STATE_GET).withDescription('temperature of device internal mcu'), e.numeric('power_factor', ea.STATE_GET).withDescription('Measured electrical power factor'), - e.numeric('ac_frequency', ea.STATE_GET).withUnit('Hz').withDescription('Measured electrical ac frequency')], + e.numeric('ac_frequency', ea.STATE_GET).withUnit('Hz').withDescription('Measured electrical ac frequency'), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['haElectricalMeasurement', 'seMetering', 'msTemperatureMeasurement']); - await endpoint.read('haElectricalMeasurement', ['acVoltageMultiplier', 'acVoltageDivisor', 'acCurrentMultiplier', - 'acCurrentDivisor']); + await endpoint.read('haElectricalMeasurement', ['acVoltageMultiplier', 'acVoltageDivisor', 'acCurrentMultiplier', 'acCurrentDivisor']); await endpoint.read('seMetering', ['multiplier', 'divisor']); // await reporting.activePower(endpoint, {min: 1, max: 600, change: 5}); // no need, duplicate for power value. await reporting.instantaneousDemand(endpoint, {min: 1, max: 600, change: 5}); @@ -564,12 +620,14 @@ const definitions: Definition[] = [ await reporting.currentSummDelivered(endpoint, {min: 1, max: 600, change: [0, 5]}); await reporting.temperature(endpoint, {min: 20, max: 300, change: 10}); endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', {acFrequencyMultiplier: 1, acFrequencyDivisor: 10}); - await endpoint.configureReporting('haElectricalMeasurement', [{ - attribute: 'acFrequency', - minimumReportInterval: 10, - maximumReportInterval: 600, - reportableChange: 3, - }]); + await endpoint.configureReporting('haElectricalMeasurement', [ + { + attribute: 'acFrequency', + minimumReportInterval: 10, + maximumReportInterval: 600, + reportableChange: 3, + }, + ]); }, }, { @@ -580,16 +638,19 @@ const definitions: Definition[] = [ description: 'SiHAS 3phase energy monitor', fromZigbee: [fz.electrical_measurement, fz.metering, fz.temperature], toZigbee: [tz.metering_power, tz.currentsummdelivered, tz.frequency, tz.powerfactor, tz.acvoltage, tz.accurrent, tz.temperature], - exposes: [e.power().withAccess(ea.STATE_GET), e.energy().withAccess(ea.STATE_GET), - e.current().withAccess(ea.STATE_GET), e.voltage().withAccess(ea.STATE_GET), + exposes: [ + e.power().withAccess(ea.STATE_GET), + e.energy().withAccess(ea.STATE_GET), + e.current().withAccess(ea.STATE_GET), + e.voltage().withAccess(ea.STATE_GET), e.temperature().withAccess(ea.STATE_GET).withDescription('temperature of device internal mcu'), e.numeric('power_factor', ea.STATE_GET).withDescription('Measured electrical power factor'), - e.numeric('ac_frequency', ea.STATE_GET).withUnit('Hz').withDescription('Measured electrical ac frequency')], + e.numeric('ac_frequency', ea.STATE_GET).withUnit('Hz').withDescription('Measured electrical ac frequency'), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['haElectricalMeasurement', 'seMetering', 'msTemperatureMeasurement']); - await endpoint.read('haElectricalMeasurement', ['acVoltageMultiplier', 'acVoltageDivisor', 'acCurrentMultiplier', - 'acCurrentDivisor']); + await endpoint.read('haElectricalMeasurement', ['acVoltageMultiplier', 'acVoltageDivisor', 'acCurrentMultiplier', 'acCurrentDivisor']); await endpoint.read('seMetering', ['multiplier', 'divisor']); // await reporting.activePower(endpoint, {min: 1, max: 600, change: 5}); // no need, duplicate for power value. await reporting.instantaneousDemand(endpoint, {min: 1, max: 600, change: 5}); @@ -599,12 +660,14 @@ const definitions: Definition[] = [ await reporting.currentSummDelivered(endpoint, {min: 1, max: 600, change: [0, 5]}); await reporting.temperature(endpoint, {min: 20, max: 300, change: 10}); endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', {acFrequencyMultiplier: 1, acFrequencyDivisor: 10}); - await endpoint.configureReporting('haElectricalMeasurement', [{ - attribute: 'acFrequency', - minimumReportInterval: 10, - maximumReportInterval: 600, - reportableChange: 3, - }]); + await endpoint.configureReporting('haElectricalMeasurement', [ + { + attribute: 'acFrequency', + minimumReportInterval: 10, + maximumReportInterval: 600, + reportableChange: 3, + }, + ]); }, }, { @@ -622,9 +685,15 @@ const definitions: Definition[] = [ await reporting.batteryPercentageRemaining(endpoint, {min: 600, max: 21600, change: 1}); await reporting.doorState(endpoint); }, - exposes: [e.battery(), e.lock(), e.enum('door_state', ea.STATE, ['open', 'closed']).withDescription('Door status'), - e.lock_action(), e.lock_action_source_name(), e.lock_action_user(), - e.composite('pin_code', 'pin_code', ea.ALL) + exposes: [ + e.battery(), + e.lock(), + e.enum('door_state', ea.STATE, ['open', 'closed']).withDescription('Door status'), + e.lock_action(), + e.lock_action_source_name(), + e.lock_action_user(), + e + .composite('pin_code', 'pin_code', ea.ALL) .withFeature(e.numeric('user', ea.SET).withDescription('User ID can only number 1')) .withFeature(e.numeric('pin_code', ea.SET).withDescription('Pincode to set, set pincode(4 digit) to null to clear')), ], @@ -644,21 +713,28 @@ const definitions: Definition[] = [ await reporting.bind(endpoint, coordinatorEndpoint, binds); await reporting.batteryVoltage(endpoint, {min: 30, max: 21600, change: 1}); await reporting.occupancy(endpoint, {min: 1, max: 600, change: 1}); - const payload = [{ - attribute: 'zoneStatus', minimumReportInterval: 1, maximumReportInterval: 600, reportableChange: 1}]; + const payload = [ + { + attribute: 'zoneStatus', + minimumReportInterval: 1, + maximumReportInterval: 600, + reportableChange: 1, + }, + ]; await endpoint.configureReporting('ssIasZone', payload); await endpoint.read('msOccupancySensing', ['pirOToUDelay']); }, - exposes: [e.battery(), e.battery_voltage(), - e.binary('occupancy_in', ea.STATE, true, false) - .withDescription('Indicates whether "IN" Sensor of the device detected occupancy'), - e.binary('occupancy_out', ea.STATE, true, false) - .withDescription('Indicates whether "OUT" Sensor of the device detected occupancy'), - e.binary('occupancy_or', ea.STATE, true, false) - .withDescription('Indicates whether "IN or OUT" Sensor of the device detected occupancy'), - e.binary('occupancy_and', ea.STATE, true, false) + exposes: [ + e.battery(), + e.battery_voltage(), + e.binary('occupancy_in', ea.STATE, true, false).withDescription('Indicates whether "IN" Sensor of the device detected occupancy'), + e.binary('occupancy_out', ea.STATE, true, false).withDescription('Indicates whether "OUT" Sensor of the device detected occupancy'), + e.binary('occupancy_or', ea.STATE, true, false).withDescription('Indicates whether "IN or OUT" Sensor of the device detected occupancy'), + e + .binary('occupancy_and', ea.STATE, true, false) .withDescription('Indicates whether "IN and OUT" Sensor of the device detected occupancy'), - e.numeric('occupancy_timeout', ea.ALL).withUnit('s').withValueMin(0).withValueMax(3600)], + e.numeric('occupancy_timeout', ea.ALL).withUnit('s').withValueMin(0).withValueMax(3600), + ], }, { zigbeeModel: ['ISM300Z3'], @@ -667,10 +743,10 @@ const definitions: Definition[] = [ description: 'SiHAS IOT smart inner switch 3 gang', extend: [ onOff({endpointNames: ['l1', 'l2', 'l3'], powerOnBehavior: false}), - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2, 'l3': 3}}), + deviceEndpoints({endpoints: {l1: 1, l2: 2, l3: 3}}), enumLookup({ name: 'operation_mode', - lookup: {'auto': 0, 'push': 1, 'latch': 2}, + lookup: {auto: 0, push: 1, latch: 2}, cluster: 'genOnOff', attribute: {ID: 0x9000, type: 0x20}, description: 'switch type: "auto" - toggle by S/W, "push" - for momentary S/W, "latch" - sync S/W.', @@ -678,7 +754,7 @@ const definitions: Definition[] = [ }), enumLookup({ name: 'rf_pairing', - lookup: {'none': 0, 'l1': 1, 'l2': 2, 'l3': 3}, + lookup: {none: 0, l1: 1, l2: 2, l3: 3}, cluster: 'genOnOff', attribute: {ID: 0x9001, type: 0x20}, description: 'Enable RF pairing mode each button l1, l2, l3. It is supported only in repeat mode.', @@ -686,11 +762,11 @@ const definitions: Definition[] = [ }), enumLookup({ name: 'switch_3way_mode', - lookup: {'disable': 0, 'enable': 1}, + lookup: {disable: 0, enable: 1}, cluster: 'genOnOff', attribute: {ID: 0x900f, type: 0x20}, - description: 'If the 3-way switch setting is enabled, the 1st and 3rd switches are used. ' + - 'At this time, connect the remote switch to SW3.', + description: + 'If the 3-way switch setting is enabled, the 1st and 3rd switches are used. ' + 'At this time, connect the remote switch to SW3.', endpointName: 'l1', }), ], @@ -702,11 +778,7 @@ const definitions: Definition[] = [ description: 'SiHAS gas valve', fromZigbee: [fzLocal.GCM300Z_valve_status, fz.battery], toZigbee: [tzLocal.GCM300Z_valve_status], - exposes: [ - e.binary('gas_valve_state', ea.ALL, 'OPEN', 'CLOSE') - .withDescription('Valve state if open or closed'), - e.battery(), - ], + exposes: [e.binary('gas_valve_state', ea.ALL, 'OPEN', 'CLOSE').withDescription('Valve state if open or closed'), e.battery()], extend: [ numeric({ name: 'close_timeout', @@ -734,7 +806,7 @@ const definitions: Definition[] = [ }), enumLookup({ name: 'volume', - lookup: {'voice': 1, 'high': 2, 'low': 2}, + lookup: {voice: 1, high: 2, low: 2}, cluster: 'genOnOff', attribute: {ID: 0x9008, type: 0x20}, description: 'Values observed are `1` (voice), `2` (high) or `3` (low).', @@ -742,7 +814,7 @@ const definitions: Definition[] = [ }), enumLookup({ name: 'overheat_mode', - lookup: {'normal': 0, 'overheat': 1}, + lookup: {normal: 0, overheat: 1}, cluster: 'genOnOff', attribute: {ID: 0x9005, type: 0x20}, description: 'Temperature overheating condition.', @@ -770,7 +842,7 @@ const definitions: Definition[] = [ extend: [ enumLookup({ name: 'di_status', - lookup: {'Close': 0, 'Open': 1}, + lookup: {Close: 0, Open: 1}, cluster: 'genOnOff', attribute: {ID: 0x9009, type: 0x20}, description: 'Indicates whether the DI(Digital Input) is open or closed', @@ -780,32 +852,32 @@ const definitions: Definition[] = [ onOff({powerOnBehavior: false}), enumLookup({ name: 'di_type', - lookup: {'Button': 0, 'Door': 1}, + lookup: {Button: 0, Door: 1}, cluster: 'genOnOff', - attribute: {ID: 0x900A, type: 0x20}, + attribute: {ID: 0x900a, type: 0x20}, description: 'Set the DI(Digital Input) type to either a button or door sensor(latch) type', reporting: {min: 0, max: '1_HOUR', change: 1}, }), enumLookup({ name: 'do_type', - lookup: {'Pulse': 0, 'Latch': 1}, + lookup: {Pulse: 0, Latch: 1}, cluster: 'genOnOff', - attribute: {ID: 0x900B, type: 0x20}, + attribute: {ID: 0x900b, type: 0x20}, description: 'Set the DO(Digital Output) type to either a pulse or latch type', reporting: {min: 0, max: '1_HOUR', change: 1}, }), enumLookup({ name: 'di_do_link', - lookup: {'Off': 0, 'On': 1}, + lookup: {Off: 0, On: 1}, cluster: 'genOnOff', - attribute: {ID: 0x900C, type: 0x20}, + attribute: {ID: 0x900c, type: 0x20}, description: 'Configure DO linkage according to DI status. When set to ON, DO is output according to the DI input.', reporting: {min: 0, max: '1_HOUR', change: 1}, }), numeric({ name: 'do_pulse_time', cluster: 'genOnOff', - attribute: {ID: 0x900D, type: 0x21}, + attribute: {ID: 0x900d, type: 0x21}, description: 'When the DO output is set to pulse type, you can set the DO pulse time. The unit is milliseconds.', valueMin: 100, valueMax: 3000, @@ -821,20 +893,28 @@ const definitions: Definition[] = [ vendor: 'ShinaSystem', description: 'SiHAS Zigbee thermostat', fromZigbee: [fz.thermostat, fz.hvac_user_interface], - toZigbee: [tz.thermostat_system_mode, tz.thermostat_occupied_heating_setpoint, - tz.thermostat_occupied_cooling_setpoint, tz.thermostat_local_temperature, - tz.thermostat_keypad_lockout], + toZigbee: [ + tz.thermostat_system_mode, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_occupied_cooling_setpoint, + tz.thermostat_local_temperature, + tz.thermostat_keypad_lockout, + ], exposes: [ - e.climate() + e + .climate() .withSystemMode(['off', 'heat', 'cool']) .withLocalTemperature() .withSetpoint('occupied_heating_setpoint', 10, 70, 0.5) .withSetpoint('occupied_cooling_setpoint', 10, 70, 0.5), - e.enum('keypad_lockout', ea.ALL, ['unlock', 'lock1', 'lock2', 'lock3']) - .withDescription('Enables or disables the device’s buttons. ' + - 'Lock1 locks the temperature setting and the cooling/heating mode button input. ' + - 'Lock2 locks the power button input. ' + - 'Lock3 locks all button inputs.'), + e + .enum('keypad_lockout', ea.ALL, ['unlock', 'lock1', 'lock2', 'lock3']) + .withDescription( + 'Enables or disables the device’s buttons. ' + + 'Lock1 locks the temperature setting and the cooling/heating mode button input. ' + + 'Lock2 locks the power button input. ' + + 'Lock3 locks all button inputs.', + ), ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); diff --git a/src/devices/siglis.ts b/src/devices/siglis.ts index 117b3ab4241a0..dd26b9cbdb63d 100644 --- a/src/devices/siglis.ts +++ b/src/devices/siglis.ts @@ -1,14 +1,13 @@ -import {Definition, Fz, Tz, KeyValue, Zh} from '../lib/types'; /* eslint-disable linebreak-style */ import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition, Fz, Tz, KeyValue, Zh} from '../lib/types'; import * as utils from '../lib/utils'; const e = exposes.presets; const ea = exposes.access; - const zigfredEndpoint = 5; const buttonLookup = { @@ -73,10 +72,22 @@ const coverAndLightToZigbee: Tz.Converter = { }; const buttonEventExposes = e.action([ - 'button_1_single', 'button_1_double', 'button_1_hold', 'button_1_release', - 'button_2_single', 'button_2_double', 'button_2_hold', 'button_2_release', - 'button_3_single', 'button_3_double', 'button_3_hold', 'button_3_release', - 'button_4_single', 'button_4_double', 'button_4_hold', 'button_4_release', + 'button_1_single', + 'button_1_double', + 'button_1_hold', + 'button_1_release', + 'button_2_single', + 'button_2_double', + 'button_2_hold', + 'button_2_release', + 'button_3_single', + 'button_3_double', + 'button_3_hold', + 'button_3_release', + 'button_4_single', + 'button_4_double', + 'button_4_hold', + 'button_4_release', ]); function checkOption(device: Zh.Device, options: KeyValue, key: string) { @@ -117,14 +128,10 @@ const definitions: Definition[] = [ vendor: 'Siglis', description: 'zigfred uno smart in-wall switch', options: [ - e.enum(`front_surface_enabled`, ea.SET, ['auto', 'true', 'false']) - .withDescription('Front Surface LED enabled'), - e.enum(`relay_enabled`, ea.SET, ['auto', 'true', 'false']) - .withDescription('Relay enabled'), - e.enum(`dimmer_enabled`, ea.SET, ['auto', 'true', 'false']) - .withDescription('Dimmer enabled'), - e.enum(`dimmer_dimming_enabled`, ea.SET, ['auto', 'true', 'false']) - .withDescription('Dimmer dimmable'), + e.enum(`front_surface_enabled`, ea.SET, ['auto', 'true', 'false']).withDescription('Front Surface LED enabled'), + e.enum(`relay_enabled`, ea.SET, ['auto', 'true', 'false']).withDescription('Relay enabled'), + e.enum(`dimmer_enabled`, ea.SET, ['auto', 'true', 'false']).withDescription('Dimmer enabled'), + e.enum(`dimmer_dimming_enabled`, ea.SET, ['auto', 'true', 'false']).withDescription('Dimmer dimmable'), ], exposes: (device, options) => { const expose = []; @@ -176,9 +183,9 @@ const definitions: Definition[] = [ meta: {multiEndpoint: true}, endpoint: (device) => { return { - 'l1': zigfredEndpoint, - 'l2': 6, - 'l3': 7, + l1: zigfredEndpoint, + l2: 6, + l3: 7, }; }, configure: async (device, coordinatorEndpoint) => { @@ -221,32 +228,19 @@ const definitions: Definition[] = [ vendor: 'Siglis', description: 'zigfred plus smart in-wall switch', options: [ - e.enum(`front_surface_enabled`, ea.SET, ['auto', 'true', 'false']) - .withDescription('Front Surface LED enabled'), - e.enum(`dimmer_1_enabled`, ea.SET, ['auto', 'true', 'false']) - .withDescription('Dimmer 1 enabled'), - e.enum(`dimmer_1_dimming_enabled`, ea.SET, ['auto', 'true', 'false']) - .withDescription('Dimmer 1 dimmable'), - e.enum(`dimmer_2_enabled`, ea.SET, ['auto', 'true', 'false']) - .withDescription('Dimmer 2 enabled'), - e.enum(`dimmer_2_dimming_enabled`, ea.SET, ['auto', 'true', 'false']) - .withDescription('Dimmer 2 dimmable'), - e.enum(`dimmer_3_enabled`, ea.SET, ['auto', 'true', 'false']) - .withDescription('Dimmer 3 enabled'), - e.enum(`dimmer_3_dimming_enabled`, ea.SET, ['auto', 'true', 'false']) - .withDescription('Dimmer 3 dimmable'), - e.enum(`dimmer_4_enabled`, ea.SET, ['auto', 'true', 'false']) - .withDescription('Dimmer 4 enabled'), - e.enum(`dimmer_4_dimming_enabled`, ea.SET, ['auto', 'true', 'false']) - .withDescription('Dimmer 4 dimmable'), - e.enum(`cover_1_enabled`, ea.SET, ['auto', 'true', 'false']) - .withDescription('Cover 1 enabled'), - e.enum(`cover_1_tilt_enabled`, ea.SET, ['auto', 'true', 'false']) - .withDescription('Cover 1 tiltable'), - e.enum(`cover_2_enabled`, ea.SET, ['auto', 'true', 'false']) - .withDescription('Cover 2 enabled'), - e.enum(`cover_2_tilt_enabled`, ea.SET, ['auto', 'true', 'false']) - .withDescription('Cover 2 tiltable'), + e.enum(`front_surface_enabled`, ea.SET, ['auto', 'true', 'false']).withDescription('Front Surface LED enabled'), + e.enum(`dimmer_1_enabled`, ea.SET, ['auto', 'true', 'false']).withDescription('Dimmer 1 enabled'), + e.enum(`dimmer_1_dimming_enabled`, ea.SET, ['auto', 'true', 'false']).withDescription('Dimmer 1 dimmable'), + e.enum(`dimmer_2_enabled`, ea.SET, ['auto', 'true', 'false']).withDescription('Dimmer 2 enabled'), + e.enum(`dimmer_2_dimming_enabled`, ea.SET, ['auto', 'true', 'false']).withDescription('Dimmer 2 dimmable'), + e.enum(`dimmer_3_enabled`, ea.SET, ['auto', 'true', 'false']).withDescription('Dimmer 3 enabled'), + e.enum(`dimmer_3_dimming_enabled`, ea.SET, ['auto', 'true', 'false']).withDescription('Dimmer 3 dimmable'), + e.enum(`dimmer_4_enabled`, ea.SET, ['auto', 'true', 'false']).withDescription('Dimmer 4 enabled'), + e.enum(`dimmer_4_dimming_enabled`, ea.SET, ['auto', 'true', 'false']).withDescription('Dimmer 4 dimmable'), + e.enum(`cover_1_enabled`, ea.SET, ['auto', 'true', 'false']).withDescription('Cover 1 enabled'), + e.enum(`cover_1_tilt_enabled`, ea.SET, ['auto', 'true', 'false']).withDescription('Cover 1 tiltable'), + e.enum(`cover_2_enabled`, ea.SET, ['auto', 'true', 'false']).withDescription('Cover 2 enabled'), + e.enum(`cover_2_tilt_enabled`, ea.SET, ['auto', 'true', 'false']).withDescription('Cover 2 tiltable'), ], exposes: (device, options) => { const expose = []; @@ -292,25 +286,43 @@ const definitions: Definition[] = [ if (checkOption(device, options, 'cover_1_enabled')) { if (checkOption(device, options, 'cover_1_tilt_enabled')) { - expose.push(e.cover() - .setAccess('state', exposes.access.STATE_SET | exposes.access.STATE_GET) - .withPosition().withTilt().withEndpoint('l6')); + expose.push( + e + .cover() + .setAccess('state', exposes.access.STATE_SET | exposes.access.STATE_GET) + .withPosition() + .withTilt() + .withEndpoint('l6'), + ); } else { - expose.push(e.cover() - .setAccess('state', exposes.access.STATE_SET | exposes.access.STATE_GET) - .withPosition().withEndpoint('l6')); + expose.push( + e + .cover() + .setAccess('state', exposes.access.STATE_SET | exposes.access.STATE_GET) + .withPosition() + .withEndpoint('l6'), + ); } } if (checkOption(device, options, 'cover_2_enabled')) { if (checkOption(device, options, 'cover_2_tilt_enabled')) { - expose.push(e.cover() - .setAccess('state', exposes.access.STATE_SET | exposes.access.STATE_GET) - .withPosition().withTilt().withEndpoint('l7')); + expose.push( + e + .cover() + .setAccess('state', exposes.access.STATE_SET | exposes.access.STATE_GET) + .withPosition() + .withTilt() + .withEndpoint('l7'), + ); } else { - expose.push(e.cover() - .setAccess('state', exposes.access.STATE_SET | exposes.access.STATE_GET) - .withPosition().withEndpoint('l7')); + expose.push( + e + .cover() + .setAccess('state', exposes.access.STATE_SET | exposes.access.STATE_GET) + .withPosition() + .withEndpoint('l7'), + ); } } @@ -343,13 +355,13 @@ const definitions: Definition[] = [ meta: {multiEndpoint: true}, endpoint: (device) => { return { - 'l1': zigfredEndpoint, - 'l2': 7, - 'l3': 8, - 'l4': 9, - 'l5': 10, - 'l6': 11, - 'l7': 12, + l1: zigfredEndpoint, + l2: 7, + l3: 8, + l4: 9, + l5: 10, + l6: 11, + l7: 12, }; }, configure: async (device, coordinatorEndpoint) => { @@ -412,7 +424,8 @@ const definitions: Definition[] = [ setMetaOption( device, 'cover_1_tilt_enabled', - (await cover1Ep.read('closuresWindowCovering', ['windowCoveringType'])).windowCoveringType === 0x08); + (await cover1Ep.read('closuresWindowCovering', ['windowCoveringType'])).windowCoveringType === 0x08, + ); if (checkMetaOption(device, 'cover_1_tilt_enabled')) { await reporting.currentPositionTiltPercentage(cover1Ep); } @@ -429,7 +442,8 @@ const definitions: Definition[] = [ setMetaOption( device, 'cover_2_tilt_enabled', - (await cover2Ep.read('closuresWindowCovering', ['windowCoveringType'])).windowCoveringType === 0x08); + (await cover2Ep.read('closuresWindowCovering', ['windowCoveringType'])).windowCoveringType === 0x08, + ); if (checkMetaOption(device, 'cover_2_tilt_enabled')) { await reporting.currentPositionTiltPercentage(cover2Ep); } diff --git a/src/devices/sikom.ts b/src/devices/sikom.ts index 5ffe412256f0d..a1ad53aa49769 100644 --- a/src/devices/sikom.ts +++ b/src/devices/sikom.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -15,8 +15,10 @@ const definitions: Definition[] = [ description: 'Thermostat', fromZigbee: [fz.on_off, fz.thermostat], toZigbee: [tz.on_off, tz.thermostat_local_temperature, tz.thermostat_system_mode, tz.thermostat_occupied_heating_setpoint], - exposes: [e.climate().withLocalTemperature().withSetpoint('occupied_heating_setpoint', 5, 40, 0.5).withSystemMode(['off', 'auto', 'heat']), - e.binary('state', ea.ALL, 'ON', 'OFF').withDescription('Turn on or off.')], + exposes: [ + e.climate().withLocalTemperature().withSetpoint('occupied_heating_setpoint', 5, 40, 0.5).withSystemMode(['off', 'auto', 'heat']), + e.binary('state', ea.ALL, 'ON', 'OFF').withDescription('Turn on or off.'), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const binds = ['genOnOff', 'hvacThermostat']; diff --git a/src/devices/sinope.ts b/src/devices/sinope.ts index c44e70c1b232d..bedc8016daf4a 100644 --- a/src/devices/sinope.ts +++ b/src/devices/sinope.ts @@ -1,16 +1,17 @@ import {Zcl} from 'zigbee-herdsman'; -import * as exposes from '../lib/exposes'; + import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; import * as constants from '../lib/constants'; -import * as utils from '../lib/utils'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; import * as reporting from '../lib/reporting'; import {Definition, Fz, KeyValue, KeyValueAny, Tz} from '../lib/types'; +import * as utils from '../lib/utils'; const e = exposes.presets; const ea = exposes.access; -import {precisionRound} from '../lib/utils'; import {onOff, electricityMeter, light} from '../lib/modernExtend'; +import {precisionRound} from '../lib/utils'; const manuSinope = {manufacturerCode: Zcl.ManufacturerCode.SINOPE_TECHNOLOGIES}; @@ -23,7 +24,7 @@ const fzLocal = { const zoneStatus = msg.data.zoneStatus; return { water_leak: (zoneStatus & 1) > 0, - tamper: (zoneStatus & 1<<2) > 0, + tamper: (zoneStatus & (1 << 2)) > 0, }; }, } satisfies Fz.Converter, @@ -36,8 +37,7 @@ const fzLocal = { delete msg['running_state']; const result: KeyValue = {}; const occupancyLookup = {0: 'unoccupied', 1: 'occupied'}; - const cycleOutputLookup = {15: '15_sec', 300: '5_min', 600: '10_min', - 900: '15_min', 1200: '20_min', 1800: '30_min', 65535: 'off'}; + const cycleOutputLookup = {15: '15_sec', 300: '5_min', 600: '10_min', 900: '15_min', 1200: '20_min', 1800: '30_min', 65535: 'off'}; if (msg.data.hasOwnProperty('1024')) { result.thermostat_occupancy = utils.getFromLookup(msg.data['1024'], occupancyLookup); @@ -224,8 +224,16 @@ const fzLocal = { result.minimum_brightness = msg.data['minimumBrightness']; } if (msg.data.hasOwnProperty('actionReport')) { - const lookup = {1: 'up_clickdown', 2: 'up_single', 3: 'up_hold', 4: 'up_double', - 17: 'down_clickdown', 18: 'down_single', 19: 'down_hold', 20: 'down_double'}; + const lookup = { + 1: 'up_clickdown', + 2: 'up_single', + 3: 'up_hold', + 4: 'up_double', + 17: 'down_clickdown', + 18: 'down_single', + 19: 'down_hold', + 20: 'down_double', + }; result.action = utils.getFromLookup(msg.data['actionReport'], lookup); } if (msg.data.hasOwnProperty('keypadLockout')) { @@ -246,7 +254,7 @@ const tzLocal = { const sinopeOccupancy = {0: 'unoccupied', 1: 'occupied'}; const SinopeOccupancy = utils.getKey(sinopeOccupancy, value, value, Number); await entity.write('hvacThermostat', {SinopeOccupancy}, manuSinope); - return {state: {'thermostat_occupancy': value}}; + return {state: {thermostat_occupancy: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('hvacThermostat', ['SinopeOccupancy'], manuSinope); @@ -258,7 +266,7 @@ const tzLocal = { const sinopeBacklightParam = {0: 'on_demand', 1: 'sensing'}; const SinopeBacklight = utils.getKey(sinopeBacklightParam, value, value, Number); await entity.write('hvacThermostat', {SinopeBacklight}, manuSinope); - return {state: {'backlight_auto_dim': value}}; + return {state: {backlight_auto_dim: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('hvacThermostat', ['SinopeBacklight'], manuSinope); @@ -269,7 +277,7 @@ const tzLocal = { convertSet: async (entity, key, value, meta) => { const lookup = {'15_sec': 15, '5_min': 300, '10_min': 600, '15_min': 900, '20_min': 1200, '30_min': 1800}; await entity.write('hvacThermostat', {SinopeMainCycleOutput: utils.getFromLookup(value, lookup)}, manuSinope); - return {state: {'main_cycle_output': value}}; + return {state: {main_cycle_output: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('hvacThermostat', ['SinopeMainCycleOutput'], manuSinope); @@ -279,15 +287,16 @@ const tzLocal = { // TH1400ZB specific key: ['aux_cycle_output'], convertSet: async (entity, key, value, meta) => { - const lookup = {'off': 65535, '15_sec': 15, '5_min': 300, '10_min': 600, '15_min': 900, '20_min': 1200, '30_min': 1800}; + const lookup = {off: 65535, '15_sec': 15, '5_min': 300, '10_min': 600, '15_min': 900, '20_min': 1200, '30_min': 1800}; await entity.write('hvacThermostat', {SinopeAuxCycleOutput: utils.getFromLookup(value, lookup)}); - return {state: {'aux_cycle_output': value}}; + return {state: {aux_cycle_output: value}}; }, convertGet: async (entity, key, meta) => { await entity.read('hvacThermostat', ['SinopeAuxCycleOutput']); }, } satisfies Tz.Converter, - enable_outdoor_temperature: { // DEPRECATED: Use Second Display Mode or control via the timeout + enable_outdoor_temperature: { + // DEPRECATED: Use Second Display Mode or control via the timeout key: ['enable_outdoor_temperature'], convertSet: async (entity, key, value, meta) => { utils.assertString(value); @@ -306,7 +315,7 @@ const tzLocal = { second_display_mode: { key: ['second_display_mode'], convertSet: async (entity, key, value, meta) => { - const lookup = {'auto': 0, 'setpoint': 1, 'outdoor temp': 2}; + const lookup = {auto: 0, setpoint: 1, 'outdoor temp': 2}; await entity.write('manuSpecificSinope', {secondScreenBehavior: utils.getFromLookup(value, lookup)}); return {state: {second_display_mode: value}}; }, @@ -361,7 +370,7 @@ const tzLocal = { if (typeof value !== 'string') { return; } - const lookup = {'ambiant': 1, 'floor': 2}; + const lookup = {ambiant: 1, floor: 2}; value = value.toLowerCase(); // @ts-expect-error if (lookup.hasOwnProperty(value)) { @@ -380,7 +389,7 @@ const tzLocal = { // @ts-expect-error if ((value >= 5 && value <= 36) || value == 'off') { // @ts-expect-error - await entity.write('manuSpecificSinope', {ambiantMaxHeatSetpointLimit: (value == 'off' ? -32768 : value * 100)}); + await entity.write('manuSpecificSinope', {ambiantMaxHeatSetpointLimit: value == 'off' ? -32768 : value * 100}); return {readAfterWriteTime: 250, state: {ambiant_max_heat_setpoint: value}}; } }, @@ -395,7 +404,7 @@ const tzLocal = { // @ts-expect-error if ((value >= 5 && value <= 34) || value == 'off') { // @ts-expect-error - await entity.write('manuSpecificSinope', {floorMinHeatSetpointLimit: (value == 'off' ? -32768 : value * 100)}); + await entity.write('manuSpecificSinope', {floorMinHeatSetpointLimit: value == 'off' ? -32768 : value * 100}); return {readAfterWriteTime: 250, state: {floor_min_heat_setpoint: value}}; } }, @@ -410,7 +419,7 @@ const tzLocal = { // @ts-expect-error if ((value >= 7 && value <= 36) || value == 'off') { // @ts-expect-error - await entity.write('manuSpecificSinope', {floorMaxHeatSetpointLimit: (value == 'off' ? -32768 : value * 100)}); + await entity.write('manuSpecificSinope', {floorMaxHeatSetpointLimit: value == 'off' ? -32768 : value * 100}); return {readAfterWriteTime: 250, state: {floor_max_heat_setpoint: value}}; } }, @@ -525,11 +534,11 @@ const tzLocal = { // DM25x0ZB and SW2500ZB key: ['led_color_on'], convertSet: async (entity, key, value: KeyValueAny, meta) => { - const r = (value.r >= 0 && value.r <= 255) ? value.r : 0; - const g = (value.g >= 0 && value.g <= 255) ? value.g : 0; - const b = (value.b >= 0 && value.b <= 255) ? value.b : 0; + const r = value.r >= 0 && value.r <= 255 ? value.r : 0; + const g = value.g >= 0 && value.g <= 255 ? value.g : 0; + const b = value.b >= 0 && value.b <= 255 ? value.b : 0; - const valueHex = r + g * 256 + (b * 256 ** 2); + const valueHex = r + g * 256 + b * 256 ** 2; await entity.write('manuSpecificSinope', {ledColorOn: valueHex}); }, } satisfies Tz.Converter, @@ -537,9 +546,9 @@ const tzLocal = { // DM25x0ZB and SW2500ZB key: ['led_color_off'], convertSet: async (entity, key, value: KeyValueAny, meta) => { - const r = (value.r >= 0 && value.r <= 255) ? value.r : 0; - const g = (value.g >= 0 && value.g <= 255) ? value.g : 0; - const b = (value.b >= 0 && value.b <= 255) ? value.b : 0; + const r = value.r >= 0 && value.r <= 255 ? value.r : 0; + const g = value.g >= 0 && value.g <= 255 ? value.g : 0; + const b = value.b >= 0 && value.b <= 255 ? value.b : 0; const valueHex = r + g * 256 + b * 256 ** 2; await entity.write('manuSpecificSinope', {ledColorOff: valueHex}); @@ -577,7 +586,7 @@ const tzLocal = { // SW2500ZB key: ['keypad_lockout'], convertSet: async (entity, key, value, meta) => { - const lookup = {'unlock': 0, 'lock': 1}; + const lookup = {unlock: 0, lock: 1}; await entity.write('manuSpecificSinope', {keypadLockout: utils.getFromLookup(value, lookup)}); return {state: {keypad_lockout: value}}; }, @@ -603,63 +612,112 @@ const definitions: Definition[] = [ model: 'TH1123ZB', vendor: 'Sinopé', description: 'Zigbee line volt thermostat', - fromZigbee: [fzLocal.thermostat, fzLocal.sinope, legacy.fz.hvac_user_interface, - fz.electrical_measurement, fz.metering, fz.ignore_temperature_report], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint, - tz.thermostat_temperature_display_mode, tz.thermostat_keypad_lockout, tz.thermostat_system_mode, tzLocal.backlight_autodim, - tzLocal.thermostat_time, tzLocal.time_format, tzLocal.enable_outdoor_temperature, tzLocal.second_display_mode, - tzLocal.thermostat_outdoor_temperature, tzLocal.outdoor_temperature_timeout, tzLocal.thermostat_occupancy, - tzLocal.main_cycle_output, tz.electrical_measurement_power], + fromZigbee: [ + fzLocal.thermostat, + fzLocal.sinope, + legacy.fz.hvac_user_interface, + fz.electrical_measurement, + fz.metering, + fz.ignore_temperature_report, + ], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_unoccupied_heating_setpoint, + tz.thermostat_temperature_display_mode, + tz.thermostat_keypad_lockout, + tz.thermostat_system_mode, + tzLocal.backlight_autodim, + tzLocal.thermostat_time, + tzLocal.time_format, + tzLocal.enable_outdoor_temperature, + tzLocal.second_display_mode, + tzLocal.thermostat_outdoor_temperature, + tzLocal.outdoor_temperature_timeout, + tzLocal.thermostat_occupancy, + tzLocal.main_cycle_output, + tz.electrical_measurement_power, + ], exposes: [ - e.climate() + e + .climate() .withSetpoint('occupied_heating_setpoint', 5, 30, 0.5) .withSetpoint('unoccupied_heating_setpoint', 5, 30, 0.5) .withLocalTemperature() .withSystemMode(['off', 'heat'], ea.ALL, 'Mode of the thermostat') .withPiHeatingDemand() .withRunningState(['idle', 'heat'], ea.STATE), - e.enum('thermostat_occupancy', ea.ALL, ['unoccupied', 'occupied']) - .withDescription('Occupancy state of the thermostat'), - e.enum('second_display_mode', ea.ALL, ['auto', 'setpoint', 'outdoor temp']) - .withDescription('Displays the outdoor temperature and then returns to the set point in "auto" mode, or clears ' + - 'in "outdoor temp" mode when expired.'), - e.numeric('thermostat_outdoor_temperature', ea.ALL).withUnit('°C').withValueMin(-99.5).withValueMax(99.5).withValueStep(0.5) + e.enum('thermostat_occupancy', ea.ALL, ['unoccupied', 'occupied']).withDescription('Occupancy state of the thermostat'), + e + .enum('second_display_mode', ea.ALL, ['auto', 'setpoint', 'outdoor temp']) + .withDescription( + 'Displays the outdoor temperature and then returns to the set point in "auto" mode, or clears ' + + 'in "outdoor temp" mode when expired.', + ), + e + .numeric('thermostat_outdoor_temperature', ea.ALL) + .withUnit('°C') + .withValueMin(-99.5) + .withValueMax(99.5) + .withValueStep(0.5) .withDescription('Outdoor temperature for the secondary display'), - e.numeric('outdoor_temperature_timeout', ea.ALL).withUnit('s').withValueMin(30).withValueMax(64800) - .withPreset('15 min', 900, '15 minutes').withPreset('30 min', 1800, '30 minutes').withPreset('1 hour', 3600, '1 hour') + e + .numeric('outdoor_temperature_timeout', ea.ALL) + .withUnit('s') + .withValueMin(30) + .withValueMax(64800) + .withPreset('15 min', 900, '15 minutes') + .withPreset('30 min', 1800, '30 minutes') + .withPreset('1 hour', 3600, '1 hour') .withDescription('Time in seconds after which the outdoor temperature is considered to have expired'), - e.binary('enable_outdoor_temperature', ea.ALL, 'ON', 'OFF') + e + .binary('enable_outdoor_temperature', ea.ALL, 'ON', 'OFF') .withDescription('DEPRECATED: Use second_display_mode or control via outdoor_temperature_timeout'), - e.enum('temperature_display_mode', ea.ALL, ['celsius', 'fahrenheit']) + e + .enum('temperature_display_mode', ea.ALL, ['celsius', 'fahrenheit']) .withDescription('The temperature format displayed on the thermostat screen'), - e.enum('time_format', ea.ALL, ['24h', '12h']) - .withDescription('The time format featured on the thermostat display'), - e.enum('backlight_auto_dim', ea.ALL, ['on_demand', 'sensing']) - .withDescription('Control backlight dimming behavior'), - e.enum('keypad_lockout', ea.ALL, ['unlock', 'lock1']) - .withDescription('Enables or disables the device’s buttons'), - e.enum('main_cycle_output', ea.ALL, ['15_sec', '15_min']) - .withDescription('The length of the control cycle: 15_sec=normal 15_min=fan'), - e.power().withAccess(ea.STATE_GET), e.current(), e.voltage(), e.energy(), + e.enum('time_format', ea.ALL, ['24h', '12h']).withDescription('The time format featured on the thermostat display'), + e.enum('backlight_auto_dim', ea.ALL, ['on_demand', 'sensing']).withDescription('Control backlight dimming behavior'), + e.enum('keypad_lockout', ea.ALL, ['unlock', 'lock1']).withDescription('Enables or disables the device’s buttons'), + e.enum('main_cycle_output', ea.ALL, ['15_sec', '15_min']).withDescription('The length of the control cycle: 15_sec=normal 15_min=fan'), + e.power().withAccess(ea.STATE_GET), + e.current(), + e.voltage(), + e.energy(), ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const binds = [ - 'genBasic', 'genIdentify', 'genGroups', 'hvacThermostat', 'hvacUserInterfaceCfg', - 'msTemperatureMeasurement', 'haElectricalMeasurement', 'seMetering', - 'manuSpecificSinope']; + 'genBasic', + 'genIdentify', + 'genGroups', + 'hvacThermostat', + 'hvacUserInterfaceCfg', + 'msTemperatureMeasurement', + 'haElectricalMeasurement', + 'seMetering', + 'manuSpecificSinope', + ]; await reporting.bind(endpoint, coordinatorEndpoint, binds); await reporting.thermostatTemperature(endpoint); await reporting.thermostatPIHeatingDemand(endpoint); await reporting.thermostatOccupiedHeatingSetpoint(endpoint); - await reporting.temperature(endpoint, {min: 1, max: 0xFFFF}); // Disable default reporting - await endpoint.configureReporting('msTemperatureMeasurement', [{ - attribute: 'tolerance', minimumReportInterval: 1, maximumReportInterval: 0xFFFF, reportableChange: 1}]); + await reporting.temperature(endpoint, {min: 1, max: 0xffff}); // Disable default reporting + await endpoint.configureReporting('msTemperatureMeasurement', [ + { + attribute: 'tolerance', + minimumReportInterval: 1, + maximumReportInterval: 0xffff, + reportableChange: 1, + }, + ]); try { await reporting.thermostatSystemMode(endpoint); - } catch (error) {/* Not all support this */} + } catch (error) { + /* Not all support this */ + } await reporting.readMeteringMultiplierDivisor(endpoint); await reporting.currentSummDelivered(endpoint, {min: 10, max: 303, change: [1, 1]}); @@ -668,7 +726,7 @@ const definitions: Definition[] = [ await endpoint.read('haElectricalMeasurement', ['acPowerMultiplier', 'acPowerDivisor']); await reporting.activePower(endpoint, {min: 10, max: 305, change: 1}); // divider 1: 1W } catch (error) { - endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', {'acPowerMultiplier': 1, 'acPowerDivisor': 1}); + endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', {acPowerMultiplier: 1, acPowerDivisor: 1}); } await reporting.rmsCurrent(endpoint, {min: 10, max: 306, change: 100}); // divider 1000: 0.1Arms await reporting.rmsVoltage(endpoint, {min: 10, max: 307, change: 5}); // divider 10: 0.5Vrms @@ -679,63 +737,112 @@ const definitions: Definition[] = [ model: 'TH1124ZB', vendor: 'Sinopé', description: 'Zigbee line volt thermostat', - fromZigbee: [fzLocal.thermostat, fzLocal.sinope, legacy.fz.hvac_user_interface, - fz.electrical_measurement, fz.metering, fz.ignore_temperature_report], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint, - tz.thermostat_temperature_display_mode, tz.thermostat_keypad_lockout, tz.thermostat_system_mode, tzLocal.backlight_autodim, - tzLocal.thermostat_time, tzLocal.time_format, tzLocal.enable_outdoor_temperature, tzLocal.second_display_mode, - tzLocal.thermostat_outdoor_temperature, tzLocal.outdoor_temperature_timeout, tzLocal.thermostat_occupancy, - tzLocal.main_cycle_output, tz.electrical_measurement_power], + fromZigbee: [ + fzLocal.thermostat, + fzLocal.sinope, + legacy.fz.hvac_user_interface, + fz.electrical_measurement, + fz.metering, + fz.ignore_temperature_report, + ], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_unoccupied_heating_setpoint, + tz.thermostat_temperature_display_mode, + tz.thermostat_keypad_lockout, + tz.thermostat_system_mode, + tzLocal.backlight_autodim, + tzLocal.thermostat_time, + tzLocal.time_format, + tzLocal.enable_outdoor_temperature, + tzLocal.second_display_mode, + tzLocal.thermostat_outdoor_temperature, + tzLocal.outdoor_temperature_timeout, + tzLocal.thermostat_occupancy, + tzLocal.main_cycle_output, + tz.electrical_measurement_power, + ], exposes: [ - e.climate() + e + .climate() .withSetpoint('occupied_heating_setpoint', 5, 30, 0.5) .withSetpoint('unoccupied_heating_setpoint', 5, 30, 0.5) .withLocalTemperature() .withSystemMode(['off', 'heat'], ea.ALL, 'Mode of the thermostat') .withPiHeatingDemand() .withRunningState(['idle', 'heat'], ea.STATE), - e.enum('thermostat_occupancy', ea.ALL, ['unoccupied', 'occupied']) - .withDescription('Occupancy state of the thermostat'), - e.enum('second_display_mode', ea.ALL, ['auto', 'setpoint', 'outdoor temp']) - .withDescription('Displays the outdoor temperature and then returns to the set point in "auto" mode, or clears ' + - 'in "outdoor temp" mode when expired.'), - e.numeric('thermostat_outdoor_temperature', ea.ALL).withUnit('°C').withValueMin(-99.5).withValueMax(99.5).withValueStep(0.5) + e.enum('thermostat_occupancy', ea.ALL, ['unoccupied', 'occupied']).withDescription('Occupancy state of the thermostat'), + e + .enum('second_display_mode', ea.ALL, ['auto', 'setpoint', 'outdoor temp']) + .withDescription( + 'Displays the outdoor temperature and then returns to the set point in "auto" mode, or clears ' + + 'in "outdoor temp" mode when expired.', + ), + e + .numeric('thermostat_outdoor_temperature', ea.ALL) + .withUnit('°C') + .withValueMin(-99.5) + .withValueMax(99.5) + .withValueStep(0.5) .withDescription('Outdoor temperature for the secondary display'), - e.numeric('outdoor_temperature_timeout', ea.ALL).withUnit('s').withValueMin(30).withValueMax(64800) - .withPreset('15 min', 900, '15 minutes').withPreset('30 min', 1800, '30 minutes').withPreset('1 hour', 3600, '1 hour') + e + .numeric('outdoor_temperature_timeout', ea.ALL) + .withUnit('s') + .withValueMin(30) + .withValueMax(64800) + .withPreset('15 min', 900, '15 minutes') + .withPreset('30 min', 1800, '30 minutes') + .withPreset('1 hour', 3600, '1 hour') .withDescription('Time in seconds after which the outdoor temperature is considered to have expired'), - e.binary('enable_outdoor_temperature', ea.ALL, 'ON', 'OFF') + e + .binary('enable_outdoor_temperature', ea.ALL, 'ON', 'OFF') .withDescription('DEPRECATED: Use second_display_mode or control via outdoor_temperature_timeout'), - e.enum('temperature_display_mode', ea.ALL, ['celsius', 'fahrenheit']) + e + .enum('temperature_display_mode', ea.ALL, ['celsius', 'fahrenheit']) .withDescription('The temperature format displayed on the thermostat screen'), - e.enum('time_format', ea.ALL, ['24h', '12h']) - .withDescription('The time format featured on the thermostat display'), - e.enum('backlight_auto_dim', ea.ALL, ['on_demand', 'sensing']) - .withDescription('Control backlight dimming behavior'), - e.enum('keypad_lockout', ea.ALL, ['unlock', 'lock1']) - .withDescription('Enables or disables the device’s buttons'), - e.enum('main_cycle_output', ea.ALL, ['15_sec', '15_min']) - .withDescription('The length of the control cycle: 15_sec=normal 15_min=fan'), - e.power().withAccess(ea.STATE_GET), e.current(), e.voltage(), e.energy(), + e.enum('time_format', ea.ALL, ['24h', '12h']).withDescription('The time format featured on the thermostat display'), + e.enum('backlight_auto_dim', ea.ALL, ['on_demand', 'sensing']).withDescription('Control backlight dimming behavior'), + e.enum('keypad_lockout', ea.ALL, ['unlock', 'lock1']).withDescription('Enables or disables the device’s buttons'), + e.enum('main_cycle_output', ea.ALL, ['15_sec', '15_min']).withDescription('The length of the control cycle: 15_sec=normal 15_min=fan'), + e.power().withAccess(ea.STATE_GET), + e.current(), + e.voltage(), + e.energy(), ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const binds = [ - 'genBasic', 'genIdentify', 'genGroups', 'hvacThermostat', 'hvacUserInterfaceCfg', - 'msTemperatureMeasurement', 'haElectricalMeasurement', 'seMetering', - 'manuSpecificSinope']; + 'genBasic', + 'genIdentify', + 'genGroups', + 'hvacThermostat', + 'hvacUserInterfaceCfg', + 'msTemperatureMeasurement', + 'haElectricalMeasurement', + 'seMetering', + 'manuSpecificSinope', + ]; await reporting.bind(endpoint, coordinatorEndpoint, binds); await reporting.thermostatTemperature(endpoint); await reporting.thermostatPIHeatingDemand(endpoint); await reporting.thermostatOccupiedHeatingSetpoint(endpoint); - await reporting.temperature(endpoint, {min: 1, max: 0xFFFF}); // Disable default reporting - await endpoint.configureReporting('msTemperatureMeasurement', [{ - attribute: 'tolerance', minimumReportInterval: 1, maximumReportInterval: 0xFFFF, reportableChange: 1}]); + await reporting.temperature(endpoint, {min: 1, max: 0xffff}); // Disable default reporting + await endpoint.configureReporting('msTemperatureMeasurement', [ + { + attribute: 'tolerance', + minimumReportInterval: 1, + maximumReportInterval: 0xffff, + reportableChange: 1, + }, + ]); try { await reporting.thermostatSystemMode(endpoint); - } catch (error) {/* Not all support this */} + } catch (error) { + /* Not all support this */ + } await reporting.readMeteringMultiplierDivisor(endpoint); await reporting.currentSummDelivered(endpoint, {min: 10, max: 303, change: [1, 1]}); @@ -744,7 +851,7 @@ const definitions: Definition[] = [ await endpoint.read('haElectricalMeasurement', ['acPowerMultiplier', 'acPowerDivisor']); await reporting.activePower(endpoint, {min: 10, max: 305, change: 1}); // divider 1: 1W } catch (error) { - endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', {'acPowerMultiplier': 1, 'acPowerDivisor': 1}); + endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', {acPowerMultiplier: 1, acPowerDivisor: 1}); } await reporting.rmsCurrent(endpoint, {min: 10, max: 306, change: 100}); // divider 1000: 0.1Arms await reporting.rmsVoltage(endpoint, {min: 10, max: 307, change: 5}); // divider 10: 0.5Vrms @@ -755,68 +862,115 @@ const definitions: Definition[] = [ model: 'TH1123ZB-G2', vendor: 'Sinopé', description: 'Zigbee line volt thermostat', - fromZigbee: [fzLocal.thermostat, fzLocal.sinope, legacy.fz.hvac_user_interface, - fz.electrical_measurement, fz.metering, fz.ignore_temperature_report], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint, - tz.thermostat_temperature_display_mode, tz.thermostat_keypad_lockout, tz.thermostat_system_mode, tzLocal.backlight_autodim, - tzLocal.thermostat_time, tzLocal.time_format, tzLocal.enable_outdoor_temperature, tzLocal.second_display_mode, - tzLocal.thermostat_outdoor_temperature, tzLocal.outdoor_temperature_timeout, tzLocal.thermostat_occupancy, - tzLocal.main_cycle_output, tz.electrical_measurement_power], + fromZigbee: [ + fzLocal.thermostat, + fzLocal.sinope, + legacy.fz.hvac_user_interface, + fz.electrical_measurement, + fz.metering, + fz.ignore_temperature_report, + ], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_unoccupied_heating_setpoint, + tz.thermostat_temperature_display_mode, + tz.thermostat_keypad_lockout, + tz.thermostat_system_mode, + tzLocal.backlight_autodim, + tzLocal.thermostat_time, + tzLocal.time_format, + tzLocal.enable_outdoor_temperature, + tzLocal.second_display_mode, + tzLocal.thermostat_outdoor_temperature, + tzLocal.outdoor_temperature_timeout, + tzLocal.thermostat_occupancy, + tzLocal.main_cycle_output, + tz.electrical_measurement_power, + ], exposes: [ - e.climate() + e + .climate() .withSetpoint('occupied_heating_setpoint', 5, 30, 0.5) .withSetpoint('unoccupied_heating_setpoint', 5, 30, 0.5) .withLocalTemperature() .withSystemMode(['off', 'heat'], ea.ALL, 'Mode of the thermostat') .withPiHeatingDemand() .withRunningState(['idle', 'heat'], ea.STATE), - e.enum('thermostat_occupancy', ea.ALL, ['unoccupied', 'occupied']) - .withDescription('Occupancy state of the thermostat'), - e.enum('second_display_mode', ea.ALL, ['auto', 'setpoint', 'outdoor temp']) - .withDescription('Displays the outdoor temperature and then returns to the set point in "auto" mode, or clears ' + - 'in "outdoor temp" mode when expired.'), - e.numeric('thermostat_outdoor_temperature', ea.ALL).withUnit('°C').withValueMin(-99.5).withValueMax(99.5).withValueStep(0.5) + e.enum('thermostat_occupancy', ea.ALL, ['unoccupied', 'occupied']).withDescription('Occupancy state of the thermostat'), + e + .enum('second_display_mode', ea.ALL, ['auto', 'setpoint', 'outdoor temp']) + .withDescription( + 'Displays the outdoor temperature and then returns to the set point in "auto" mode, or clears ' + + 'in "outdoor temp" mode when expired.', + ), + e + .numeric('thermostat_outdoor_temperature', ea.ALL) + .withUnit('°C') + .withValueMin(-99.5) + .withValueMax(99.5) + .withValueStep(0.5) .withDescription('Outdoor temperature for the secondary display'), - e.numeric('outdoor_temperature_timeout', ea.ALL).withUnit('s').withValueMin(30).withValueMax(64800) - .withPreset('15 min', 900, '15 minutes').withPreset('30 min', 1800, '30 minutes').withPreset('1 hour', 3600, '1 hour') + e + .numeric('outdoor_temperature_timeout', ea.ALL) + .withUnit('s') + .withValueMin(30) + .withValueMax(64800) + .withPreset('15 min', 900, '15 minutes') + .withPreset('30 min', 1800, '30 minutes') + .withPreset('1 hour', 3600, '1 hour') .withDescription('Time in seconds after which the outdoor temperature is considered to have expired'), - e.binary('enable_outdoor_temperature', ea.ALL, 'ON', 'OFF') + e + .binary('enable_outdoor_temperature', ea.ALL, 'ON', 'OFF') .withDescription('DEPRECATED: Use second_display_mode or control via outdoor_temperature_timeout'), - e.enum('temperature_display_mode', ea.ALL, ['celsius', 'fahrenheit']) + e + .enum('temperature_display_mode', ea.ALL, ['celsius', 'fahrenheit']) .withDescription('The temperature format displayed on the thermostat screen'), - e.enum('time_format', ea.ALL, ['24h', '12h']) - .withDescription('The time format featured on the thermostat display'), - e.enum('backlight_auto_dim', ea.ALL, ['on_demand', 'sensing']) - .withDescription('Control backlight dimming behavior'), - e.enum('keypad_lockout', ea.ALL, ['unlock', 'lock1']) - .withDescription('Enables or disables the device’s buttons'), - e.enum('main_cycle_output', ea.ALL, ['15_sec', '15_min']) - .withDescription('The length of the control cycle: 15_sec=normal 15_min=fan'), - e.power().withAccess(ea.STATE_GET), e.current(), e.voltage(), e.energy(), + e.enum('time_format', ea.ALL, ['24h', '12h']).withDescription('The time format featured on the thermostat display'), + e.enum('backlight_auto_dim', ea.ALL, ['on_demand', 'sensing']).withDescription('Control backlight dimming behavior'), + e.enum('keypad_lockout', ea.ALL, ['unlock', 'lock1']).withDescription('Enables or disables the device’s buttons'), + e.enum('main_cycle_output', ea.ALL, ['15_sec', '15_min']).withDescription('The length of the control cycle: 15_sec=normal 15_min=fan'), + e.power().withAccess(ea.STATE_GET), + e.current(), + e.voltage(), + e.energy(), ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const binds = [ - 'genBasic', 'genIdentify', 'genGroups', 'hvacThermostat', 'hvacUserInterfaceCfg', - 'msTemperatureMeasurement', 'haElectricalMeasurement', 'seMetering', - 'manuSpecificSinope']; + 'genBasic', + 'genIdentify', + 'genGroups', + 'hvacThermostat', + 'hvacUserInterfaceCfg', + 'msTemperatureMeasurement', + 'haElectricalMeasurement', + 'seMetering', + 'manuSpecificSinope', + ]; await reporting.bind(endpoint, coordinatorEndpoint, binds); // This G2 version has limited memory space const thermostatDate = new Date(); const thermostatTimeSec = thermostatDate.getTime() / 1000; const thermostatTimezoneOffsetSec = thermostatDate.getTimezoneOffset() * 60; const currentTimeToDisplay = Math.round(thermostatTimeSec - thermostatTimezoneOffsetSec - 946684800); await endpoint.write('manuSpecificSinope', {currentTimeToDisplay}, manuSinope); - await endpoint.write('manuSpecificSinope', {'secondScreenBehavior': 0}, manuSinope); // Mode auto + await endpoint.write('manuSpecificSinope', {secondScreenBehavior: 0}, manuSinope); // Mode auto await reporting.thermostatTemperature(endpoint); await reporting.thermostatPIHeatingDemand(endpoint); await reporting.thermostatOccupiedHeatingSetpoint(endpoint); await reporting.thermostatSystemMode(endpoint); - await reporting.temperature(endpoint, {min: 1, max: 0xFFFF}); // Disable default reporting - await endpoint.configureReporting('msTemperatureMeasurement', [{ - attribute: 'tolerance', minimumReportInterval: 1, maximumReportInterval: 0xFFFF, reportableChange: 1}]); + await reporting.temperature(endpoint, {min: 1, max: 0xffff}); // Disable default reporting + await endpoint.configureReporting('msTemperatureMeasurement', [ + { + attribute: 'tolerance', + minimumReportInterval: 1, + maximumReportInterval: 0xffff, + reportableChange: 1, + }, + ]); await reporting.readMeteringMultiplierDivisor(endpoint); await reporting.currentSummDelivered(endpoint, {min: 10, max: 303, change: [1, 1]}); @@ -826,10 +980,12 @@ const definitions: Definition[] = [ await reporting.rmsVoltage(endpoint, {min: 10, max: 307, change: 5}); // divider 10: 0.5Vrms // Disable default reporting (not used by Sinope) - await reporting.thermostatRunningState(endpoint, {min: 1, max: 0xFFFF}); + await reporting.thermostatRunningState(endpoint, {min: 1, max: 0xffff}); try { await reporting.thermostatUnoccupiedHeatingSetpoint(endpoint); - } catch (error) {/* Do nothing */} + } catch (error) { + /* Do nothing */ + } }, }, { @@ -837,68 +993,115 @@ const definitions: Definition[] = [ model: 'TH1124ZB-G2', vendor: 'Sinopé', description: 'Zigbee line volt thermostat', - fromZigbee: [fzLocal.thermostat, fzLocal.sinope, legacy.fz.hvac_user_interface, - fz.electrical_measurement, fz.metering, fz.ignore_temperature_report], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint, - tz.thermostat_temperature_display_mode, tz.thermostat_keypad_lockout, tz.thermostat_system_mode, tzLocal.backlight_autodim, - tzLocal.thermostat_time, tzLocal.time_format, tzLocal.enable_outdoor_temperature, tzLocal.second_display_mode, - tzLocal.thermostat_outdoor_temperature, tzLocal.outdoor_temperature_timeout, tzLocal.thermostat_occupancy, - tzLocal.main_cycle_output, tz.electrical_measurement_power], + fromZigbee: [ + fzLocal.thermostat, + fzLocal.sinope, + legacy.fz.hvac_user_interface, + fz.electrical_measurement, + fz.metering, + fz.ignore_temperature_report, + ], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_unoccupied_heating_setpoint, + tz.thermostat_temperature_display_mode, + tz.thermostat_keypad_lockout, + tz.thermostat_system_mode, + tzLocal.backlight_autodim, + tzLocal.thermostat_time, + tzLocal.time_format, + tzLocal.enable_outdoor_temperature, + tzLocal.second_display_mode, + tzLocal.thermostat_outdoor_temperature, + tzLocal.outdoor_temperature_timeout, + tzLocal.thermostat_occupancy, + tzLocal.main_cycle_output, + tz.electrical_measurement_power, + ], exposes: [ - e.climate() + e + .climate() .withSetpoint('occupied_heating_setpoint', 5, 30, 0.5) .withSetpoint('unoccupied_heating_setpoint', 5, 30, 0.5) .withLocalTemperature() .withSystemMode(['off', 'heat'], ea.ALL, 'Mode of the thermostat') .withPiHeatingDemand() .withRunningState(['idle', 'heat'], ea.STATE), - e.enum('thermostat_occupancy', ea.ALL, ['unoccupied', 'occupied']) - .withDescription('Occupancy state of the thermostat'), - e.enum('second_display_mode', ea.ALL, ['auto', 'setpoint', 'outdoor temp']) - .withDescription('Displays the outdoor temperature and then returns to the set point in "auto" mode, or clears ' + - 'in "outdoor temp" mode when expired.'), - e.numeric('thermostat_outdoor_temperature', ea.ALL).withUnit('°C').withValueMin(-99.5).withValueMax(99.5).withValueStep(0.5) + e.enum('thermostat_occupancy', ea.ALL, ['unoccupied', 'occupied']).withDescription('Occupancy state of the thermostat'), + e + .enum('second_display_mode', ea.ALL, ['auto', 'setpoint', 'outdoor temp']) + .withDescription( + 'Displays the outdoor temperature and then returns to the set point in "auto" mode, or clears ' + + 'in "outdoor temp" mode when expired.', + ), + e + .numeric('thermostat_outdoor_temperature', ea.ALL) + .withUnit('°C') + .withValueMin(-99.5) + .withValueMax(99.5) + .withValueStep(0.5) .withDescription('Outdoor temperature for the secondary display'), - e.numeric('outdoor_temperature_timeout', ea.ALL).withUnit('s').withValueMin(30).withValueMax(64800) - .withPreset('15 min', 900, '15 minutes').withPreset('30 min', 1800, '30 minutes').withPreset('1 hour', 3600, '1 hour') + e + .numeric('outdoor_temperature_timeout', ea.ALL) + .withUnit('s') + .withValueMin(30) + .withValueMax(64800) + .withPreset('15 min', 900, '15 minutes') + .withPreset('30 min', 1800, '30 minutes') + .withPreset('1 hour', 3600, '1 hour') .withDescription('Time in seconds after which the outdoor temperature is considered to have expired'), - e.binary('enable_outdoor_temperature', ea.ALL, 'ON', 'OFF') + e + .binary('enable_outdoor_temperature', ea.ALL, 'ON', 'OFF') .withDescription('DEPRECATED: Use second_display_mode or control via outdoor_temperature_timeout'), - e.enum('temperature_display_mode', ea.ALL, ['celsius', 'fahrenheit']) + e + .enum('temperature_display_mode', ea.ALL, ['celsius', 'fahrenheit']) .withDescription('The temperature format displayed on the thermostat screen'), - e.enum('time_format', ea.ALL, ['24h', '12h']) - .withDescription('The time format featured on the thermostat display'), - e.enum('backlight_auto_dim', ea.ALL, ['on_demand', 'sensing']) - .withDescription('Control backlight dimming behavior'), - e.enum('keypad_lockout', ea.ALL, ['unlock', 'lock1']) - .withDescription('Enables or disables the device’s buttons'), - e.enum('main_cycle_output', ea.ALL, ['15_sec', '15_min']) - .withDescription('The length of the control cycle: 15_sec=normal 15_min=fan'), - e.power().withAccess(ea.STATE_GET), e.current(), e.voltage(), e.energy(), + e.enum('time_format', ea.ALL, ['24h', '12h']).withDescription('The time format featured on the thermostat display'), + e.enum('backlight_auto_dim', ea.ALL, ['on_demand', 'sensing']).withDescription('Control backlight dimming behavior'), + e.enum('keypad_lockout', ea.ALL, ['unlock', 'lock1']).withDescription('Enables or disables the device’s buttons'), + e.enum('main_cycle_output', ea.ALL, ['15_sec', '15_min']).withDescription('The length of the control cycle: 15_sec=normal 15_min=fan'), + e.power().withAccess(ea.STATE_GET), + e.current(), + e.voltage(), + e.energy(), ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const binds = [ - 'genBasic', 'genIdentify', 'genGroups', 'hvacThermostat', 'hvacUserInterfaceCfg', - 'msTemperatureMeasurement', 'haElectricalMeasurement', 'seMetering', - 'manuSpecificSinope']; + 'genBasic', + 'genIdentify', + 'genGroups', + 'hvacThermostat', + 'hvacUserInterfaceCfg', + 'msTemperatureMeasurement', + 'haElectricalMeasurement', + 'seMetering', + 'manuSpecificSinope', + ]; await reporting.bind(endpoint, coordinatorEndpoint, binds); // This G2 version has limited memory space const thermostatDate = new Date(); const thermostatTimeSec = thermostatDate.getTime() / 1000; const thermostatTimezoneOffsetSec = thermostatDate.getTimezoneOffset() * 60; const currentTimeToDisplay = Math.round(thermostatTimeSec - thermostatTimezoneOffsetSec - 946684800); await endpoint.write('manuSpecificSinope', {currentTimeToDisplay}, manuSinope); - await endpoint.write('manuSpecificSinope', {'secondScreenBehavior': 0}, manuSinope); // Mode auto + await endpoint.write('manuSpecificSinope', {secondScreenBehavior: 0}, manuSinope); // Mode auto await reporting.thermostatTemperature(endpoint); await reporting.thermostatPIHeatingDemand(endpoint); await reporting.thermostatOccupiedHeatingSetpoint(endpoint); await reporting.thermostatSystemMode(endpoint); - await reporting.temperature(endpoint, {min: 1, max: 0xFFFF}); // Disable default reporting - await endpoint.configureReporting('msTemperatureMeasurement', [{ - attribute: 'tolerance', minimumReportInterval: 1, maximumReportInterval: 0xFFFF, reportableChange: 1}]); + await reporting.temperature(endpoint, {min: 1, max: 0xffff}); // Disable default reporting + await endpoint.configureReporting('msTemperatureMeasurement', [ + { + attribute: 'tolerance', + minimumReportInterval: 1, + maximumReportInterval: 0xffff, + reportableChange: 1, + }, + ]); await reporting.readMeteringMultiplierDivisor(endpoint); await reporting.currentSummDelivered(endpoint, {min: 10, max: 303, change: [1, 1]}); @@ -908,10 +1111,12 @@ const definitions: Definition[] = [ await reporting.rmsVoltage(endpoint, {min: 10, max: 307, change: 5}); // divider 10: 0.5Vrms // Disable default reporting (not used by Sinope) - await reporting.thermostatRunningState(endpoint, {min: 1, max: 0xFFFF}); + await reporting.thermostatRunningState(endpoint, {min: 1, max: 0xffff}); try { await reporting.thermostatUnoccupiedHeatingSetpoint(endpoint); - } catch (error) {/* Do nothing */} + } catch (error) { + /* Do nothing */ + } }, }, { @@ -922,72 +1127,127 @@ const definitions: Definition[] = [ whiteLabel: [ {model: 'TH1320ZB-04', vendor: 'Sinopé', description: 'Zigbee smart floor heating thermostat', fingerprint: [{modelID: 'TH1320ZB-04'}]}, ], - fromZigbee: [fzLocal.thermostat, fzLocal.sinope, legacy.fz.hvac_user_interface, - fz.electrical_measurement, fz.metering, fz.ignore_temperature_report], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint, - tz.thermostat_temperature_display_mode, tz.thermostat_keypad_lockout, tz.thermostat_system_mode, tzLocal.backlight_autodim, - tzLocal.thermostat_time, tzLocal.time_format, tzLocal.enable_outdoor_temperature, tzLocal.second_display_mode, - tzLocal.thermostat_outdoor_temperature, tzLocal.outdoor_temperature_timeout, tzLocal.thermostat_occupancy, - tzLocal.floor_control_mode, tzLocal.ambiant_max_heat_setpoint, tzLocal.floor_min_heat_setpoint, - tzLocal.floor_max_heat_setpoint, tzLocal.temperature_sensor, tz.electrical_measurement_power], + fromZigbee: [ + fzLocal.thermostat, + fzLocal.sinope, + legacy.fz.hvac_user_interface, + fz.electrical_measurement, + fz.metering, + fz.ignore_temperature_report, + ], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_unoccupied_heating_setpoint, + tz.thermostat_temperature_display_mode, + tz.thermostat_keypad_lockout, + tz.thermostat_system_mode, + tzLocal.backlight_autodim, + tzLocal.thermostat_time, + tzLocal.time_format, + tzLocal.enable_outdoor_temperature, + tzLocal.second_display_mode, + tzLocal.thermostat_outdoor_temperature, + tzLocal.outdoor_temperature_timeout, + tzLocal.thermostat_occupancy, + tzLocal.floor_control_mode, + tzLocal.ambiant_max_heat_setpoint, + tzLocal.floor_min_heat_setpoint, + tzLocal.floor_max_heat_setpoint, + tzLocal.temperature_sensor, + tz.electrical_measurement_power, + ], exposes: [ - e.climate() + e + .climate() .withSetpoint('occupied_heating_setpoint', 5, 36, 0.5) .withSetpoint('unoccupied_heating_setpoint', 5, 36, 0.5) .withLocalTemperature() .withSystemMode(['off', 'heat'], ea.ALL, 'Mode of the thermostat') .withPiHeatingDemand() .withRunningState(['idle', 'heat'], ea.STATE), - e.enum('thermostat_occupancy', ea.ALL, ['unoccupied', 'occupied']) - .withDescription('Occupancy state of the thermostat'), - e.enum('second_display_mode', ea.ALL, ['auto', 'setpoint', 'outdoor temp']) - .withDescription('Displays the outdoor temperature and then returns to the set point in "auto" mode, or clears ' + - 'in "outdoor temp" mode when expired.'), - e.numeric('thermostat_outdoor_temperature', ea.ALL).withUnit('°C').withValueMin(-99.5).withValueMax(99.5).withValueStep(0.5) + e.enum('thermostat_occupancy', ea.ALL, ['unoccupied', 'occupied']).withDescription('Occupancy state of the thermostat'), + e + .enum('second_display_mode', ea.ALL, ['auto', 'setpoint', 'outdoor temp']) + .withDescription( + 'Displays the outdoor temperature and then returns to the set point in "auto" mode, or clears ' + + 'in "outdoor temp" mode when expired.', + ), + e + .numeric('thermostat_outdoor_temperature', ea.ALL) + .withUnit('°C') + .withValueMin(-99.5) + .withValueMax(99.5) + .withValueStep(0.5) .withDescription('Outdoor temperature for the secondary display'), - e.numeric('outdoor_temperature_timeout', ea.ALL).withUnit('s').withValueMin(30).withValueMax(64800) - .withPreset('15 min', 900, '15 minutes').withPreset('30 min', 1800, '30 minutes').withPreset('1 hour', 3600, '1 hour') + e + .numeric('outdoor_temperature_timeout', ea.ALL) + .withUnit('s') + .withValueMin(30) + .withValueMax(64800) + .withPreset('15 min', 900, '15 minutes') + .withPreset('30 min', 1800, '30 minutes') + .withPreset('1 hour', 3600, '1 hour') .withDescription('Time in seconds after which the outdoor temperature is considered to have expired'), - e.binary('enable_outdoor_temperature', ea.ALL, 'ON', 'OFF') + e + .binary('enable_outdoor_temperature', ea.ALL, 'ON', 'OFF') .withDescription('DEPRECATED: Use second_display_mode or control via outdoor_temperature_timeout'), - e.enum('temperature_display_mode', ea.ALL, ['celsius', 'fahrenheit']) + e + .enum('temperature_display_mode', ea.ALL, ['celsius', 'fahrenheit']) .withDescription('The temperature format displayed on the thermostat screen'), - e.enum('time_format', ea.ALL, ['24h', '12h']) - .withDescription('The time format featured on the thermostat display'), - e.enum('backlight_auto_dim', ea.ALL, ['on_demand', 'sensing']) - .withDescription('Control backlight dimming behavior'), - e.enum('keypad_lockout', ea.ALL, ['unlock', 'lock1']) - .withDescription('Enables or disables the device’s buttons'), - e.power().withAccess(ea.STATE_GET), e.current(), e.voltage(), e.energy()], + e.enum('time_format', ea.ALL, ['24h', '12h']).withDescription('The time format featured on the thermostat display'), + e.enum('backlight_auto_dim', ea.ALL, ['on_demand', 'sensing']).withDescription('Control backlight dimming behavior'), + e.enum('keypad_lockout', ea.ALL, ['unlock', 'lock1']).withDescription('Enables or disables the device’s buttons'), + e.power().withAccess(ea.STATE_GET), + e.current(), + e.voltage(), + e.energy(), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const binds = [ - 'genBasic', 'genIdentify', 'genGroups', 'hvacThermostat', 'hvacUserInterfaceCfg', - 'haElectricalMeasurement', 'msTemperatureMeasurement', 'seMetering', 'manuSpecificSinope']; + 'genBasic', + 'genIdentify', + 'genGroups', + 'hvacThermostat', + 'hvacUserInterfaceCfg', + 'haElectricalMeasurement', + 'msTemperatureMeasurement', + 'seMetering', + 'manuSpecificSinope', + ]; await reporting.bind(endpoint, coordinatorEndpoint, binds); await reporting.thermostatTemperature(endpoint); await reporting.thermostatPIHeatingDemand(endpoint); await reporting.thermostatOccupiedHeatingSetpoint(endpoint); try { await reporting.readMeteringMultiplierDivisor(endpoint); - } catch (error) {/* Do nothing*/} + } catch (error) { + /* Do nothing*/ + } try { await reporting.currentSummDelivered(endpoint, {min: 10, max: 303, change: [1, 1]}); - } catch (error) {/* Do nothing*/} + } catch (error) { + /* Do nothing*/ + } try { await endpoint.read('haElectricalMeasurement', ['acPowerMultiplier', 'acPowerDivisor']); await reporting.activePower(endpoint, {min: 10, max: 305, change: 1}); // divider 1: 1W } catch (error) { - endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', {'acPowerMultiplier': 1, 'acPowerDivisor': 1}); + endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', {acPowerMultiplier: 1, acPowerDivisor: 1}); } try { await endpoint.read('haElectricalMeasurement', ['acCurrentMultiplier', 'acCurrentDivisor']); await reporting.rmsCurrent(endpoint, {min: 10, max: 306, change: 100}); // divider 1000: 0.1Arms - } catch (error) {/* Do nothing*/} + } catch (error) { + /* Do nothing*/ + } try { await endpoint.read('haElectricalMeasurement', ['acVoltageMultiplier', 'acVoltageDivisor']); await reporting.rmsVoltage(endpoint, {min: 10, max: 307, change: 5}); // divider 10: 0.5Vrms - } catch (error) {/* Do nothing*/} + } catch (error) { + /* Do nothing*/ + } try { await reporting.thermostatKeypadLockMode(endpoint); @@ -995,11 +1255,13 @@ const definitions: Definition[] = [ // Not all support this: https://github.com/Koenkk/zigbee2mqtt/issues/3760 } - await endpoint.configureReporting('manuSpecificSinope', [{attribute: 'GFCiStatus', minimumReportInterval: 1, - maximumReportInterval: constants.repInterval.HOUR, reportableChange: 1}]); - await endpoint.configureReporting('manuSpecificSinope', [{attribute: 'floorLimitStatus', minimumReportInterval: 1, - maximumReportInterval: constants.repInterval.HOUR, reportableChange: 1}]); - await reporting.temperature(endpoint, {min: 1, max: 0xFFFF}); // disable reporting + await endpoint.configureReporting('manuSpecificSinope', [ + {attribute: 'GFCiStatus', minimumReportInterval: 1, maximumReportInterval: constants.repInterval.HOUR, reportableChange: 1}, + ]); + await endpoint.configureReporting('manuSpecificSinope', [ + {attribute: 'floorLimitStatus', minimumReportInterval: 1, maximumReportInterval: constants.repInterval.HOUR, reportableChange: 1}, + ]); + await reporting.temperature(endpoint, {min: 1, max: 0xffff}); // disable reporting }, }, { @@ -1007,18 +1269,45 @@ const definitions: Definition[] = [ model: 'TH1400ZB', vendor: 'Sinopé', description: 'Zigbee low volt thermostat', - fromZigbee: [fzLocal.thermostat, fzLocal.sinope, legacy.fz.hvac_user_interface, - fz.electrical_measurement, fz.metering, fz.ignore_temperature_report], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint, - tz.thermostat_temperature_display_mode, tz.thermostat_keypad_lockout, tz.thermostat_system_mode, tzLocal.backlight_autodim, - tzLocal.thermostat_time, tzLocal.time_format, tzLocal.enable_outdoor_temperature, tzLocal.second_display_mode, - tzLocal.thermostat_outdoor_temperature, tzLocal.outdoor_temperature_timeout, tzLocal.thermostat_occupancy, - tzLocal.floor_control_mode, tzLocal.ambiant_max_heat_setpoint, tzLocal.floor_min_heat_setpoint, - tzLocal.floor_max_heat_setpoint, tzLocal.temperature_sensor, tz.thermostat_min_heat_setpoint_limit, - tz.thermostat_max_heat_setpoint_limit, tzLocal.connected_load, tzLocal.aux_connected_load, tzLocal.main_cycle_output, - tzLocal.aux_cycle_output, tzLocal.pump_protection], + fromZigbee: [ + fzLocal.thermostat, + fzLocal.sinope, + legacy.fz.hvac_user_interface, + fz.electrical_measurement, + fz.metering, + fz.ignore_temperature_report, + ], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_unoccupied_heating_setpoint, + tz.thermostat_temperature_display_mode, + tz.thermostat_keypad_lockout, + tz.thermostat_system_mode, + tzLocal.backlight_autodim, + tzLocal.thermostat_time, + tzLocal.time_format, + tzLocal.enable_outdoor_temperature, + tzLocal.second_display_mode, + tzLocal.thermostat_outdoor_temperature, + tzLocal.outdoor_temperature_timeout, + tzLocal.thermostat_occupancy, + tzLocal.floor_control_mode, + tzLocal.ambiant_max_heat_setpoint, + tzLocal.floor_min_heat_setpoint, + tzLocal.floor_max_heat_setpoint, + tzLocal.temperature_sensor, + tz.thermostat_min_heat_setpoint_limit, + tz.thermostat_max_heat_setpoint_limit, + tzLocal.connected_load, + tzLocal.aux_connected_load, + tzLocal.main_cycle_output, + tzLocal.aux_cycle_output, + tzLocal.pump_protection, + ], exposes: [ - e.climate() + e + .climate() .withSetpoint('occupied_heating_setpoint', 5, 36, 0.5) .withSetpoint('unoccupied_heating_setpoint', 5, 36, 0.5) .withLocalTemperature() @@ -1027,61 +1316,96 @@ const definitions: Definition[] = [ .withRunningState(['idle', 'heat'], ea.STATE), e.max_heat_setpoint_limit(5, 36, 0.5), e.min_heat_setpoint_limit(5, 36, 0.5), - e.enum('thermostat_occupancy', ea.ALL, ['unoccupied', 'occupied']) - .withDescription('Occupancy state of the thermostat'), - e.enum('second_display_mode', ea.ALL, ['auto', 'setpoint', 'outdoor temp']) - .withDescription('Displays the outdoor temperature and then returns to the set point in "auto" mode, or clears ' + - 'in "outdoor temp" mode when expired.'), - e.numeric('thermostat_outdoor_temperature', ea.ALL).withUnit('°C').withValueMin(-99.5).withValueMax(99.5).withValueStep(0.5) + e.enum('thermostat_occupancy', ea.ALL, ['unoccupied', 'occupied']).withDescription('Occupancy state of the thermostat'), + e + .enum('second_display_mode', ea.ALL, ['auto', 'setpoint', 'outdoor temp']) + .withDescription( + 'Displays the outdoor temperature and then returns to the set point in "auto" mode, or clears ' + + 'in "outdoor temp" mode when expired.', + ), + e + .numeric('thermostat_outdoor_temperature', ea.ALL) + .withUnit('°C') + .withValueMin(-99.5) + .withValueMax(99.5) + .withValueStep(0.5) .withDescription('Outdoor temperature for the secondary display'), - e.numeric('outdoor_temperature_timeout', ea.ALL).withUnit('s').withValueMin(30).withValueMax(64800) - .withPreset('15 min', 900, '15 minutes').withPreset('30 min', 1800, '30 minutes').withPreset('1 hour', 3600, '1 hour') + e + .numeric('outdoor_temperature_timeout', ea.ALL) + .withUnit('s') + .withValueMin(30) + .withValueMax(64800) + .withPreset('15 min', 900, '15 minutes') + .withPreset('30 min', 1800, '30 minutes') + .withPreset('1 hour', 3600, '1 hour') .withDescription('Time in seconds after which the outdoor temperature is considered to have expired'), - e.binary('enable_outdoor_temperature', ea.ALL, 'ON', 'OFF') + e + .binary('enable_outdoor_temperature', ea.ALL, 'ON', 'OFF') .withDescription('DEPRECATED: Use second_display_mode or control via outdoor_temperature_timeout'), - e.enum('temperature_display_mode', ea.ALL, ['celsius', 'fahrenheit']) + e + .enum('temperature_display_mode', ea.ALL, ['celsius', 'fahrenheit']) .withDescription('The temperature format displayed on the thermostat screen'), - e.enum('time_format', ea.ALL, ['24h', '12h']) - .withDescription('The time format featured on the thermostat display'), - e.enum('backlight_auto_dim', ea.ALL, ['on_demand', 'sensing']) - .withDescription('The display backlight behavior'), - e.enum('keypad_lockout', ea.ALL, ['unlock', 'lock1']) - .withDescription('Enables or disables the device’s buttons'), - e.numeric('connected_load', ea.ALL) - .withUnit('W').withValueMin(1).withValueMax(20000) + e.enum('time_format', ea.ALL, ['24h', '12h']).withDescription('The time format featured on the thermostat display'), + e.enum('backlight_auto_dim', ea.ALL, ['on_demand', 'sensing']).withDescription('The display backlight behavior'), + e.enum('keypad_lockout', ea.ALL, ['unlock', 'lock1']).withDescription('Enables or disables the device’s buttons'), + e + .numeric('connected_load', ea.ALL) + .withUnit('W') + .withValueMin(1) + .withValueMax(20000) .withDescription('The power in watts of the electrical load connected to the device'), - e.enum('floor_control_mode', ea.ALL, ['ambiant', 'floor']) - .withDescription('Control mode using floor or ambient temperature'), - e.numeric('floor_max_heat_setpoint', ea.ALL) - .withUnit('°C').withValueMin(7).withValueMax(36).withValueStep(0.5) + e.enum('floor_control_mode', ea.ALL, ['ambiant', 'floor']).withDescription('Control mode using floor or ambient temperature'), + e + .numeric('floor_max_heat_setpoint', ea.ALL) + .withUnit('°C') + .withValueMin(7) + .withValueMax(36) + .withValueStep(0.5) .withPreset('off', 'off', 'Use minimum permitted value') .withDescription('The maximum floor temperature limit of the floor when in ambient control mode'), - e.numeric('floor_min_heat_setpoint', ea.ALL) - .withUnit('°C').withValueMin(5).withValueMax(34).withValueStep(0.5) + e + .numeric('floor_min_heat_setpoint', ea.ALL) + .withUnit('°C') + .withValueMin(5) + .withValueMax(34) + .withValueStep(0.5) .withPreset('off', 'off', 'Use minimum permitted value') .withDescription('The minimum floor temperature limit of the floor when in ambient control mode'), - e.numeric('ambiant_max_heat_setpoint', ea.ALL) - .withUnit('°C').withValueMin(5).withValueMax(36).withValueStep(0.5) + e + .numeric('ambiant_max_heat_setpoint', ea.ALL) + .withUnit('°C') + .withValueMin(5) + .withValueMax(36) + .withValueStep(0.5) .withPreset('off', 'off', 'Use minimum permitted value') .withDescription('The maximum ambient temperature limit when in floor control mode'), - e.enum('floor_temperature_sensor', ea.ALL, ['10k', '12k']) - .withDescription('The floor sensor'), - e.enum('main_cycle_output', ea.ALL, ['15_sec', '5_min', '10_min', '15_min', '20_min', '30_min']) + e.enum('floor_temperature_sensor', ea.ALL, ['10k', '12k']).withDescription('The floor sensor'), + e + .enum('main_cycle_output', ea.ALL, ['15_sec', '5_min', '10_min', '15_min', '20_min', '30_min']) .withDescription('The length of the control cycle according to the type of load connected to the thermostats'), - e.enum('aux_cycle_output', ea.ALL, ['off', '15_sec', '5_min', '10_min', '15_min', '20_min', '30_min']) + e + .enum('aux_cycle_output', ea.ALL, ['off', '15_sec', '5_min', '10_min', '15_min', '20_min', '30_min']) .withDescription('The length of the control cycle according to the type of auxiliary load connected to the thermostats'), - e.binary('pump_protection', ea.ALL, 'ON', 'OFF') - .withDescription('This function prevents the seizure of the pump'), - e.numeric('aux_connected_load', ea.ALL) - .withUnit('W').withValueMin(0).withValueMax(20000) + e.binary('pump_protection', ea.ALL, 'ON', 'OFF').withDescription('This function prevents the seizure of the pump'), + e + .numeric('aux_connected_load', ea.ALL) + .withUnit('W') + .withValueMin(0) + .withValueMax(20000) .withDescription('The power in watts of the heater connected to the auxiliary output of the thermostat'), ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const binds = [ - 'genBasic', 'genIdentify', 'genGroups', 'hvacThermostat', - 'hvacUserInterfaceCfg', 'msTemperatureMeasurement', 'manuSpecificSinope']; + 'genBasic', + 'genIdentify', + 'genGroups', + 'hvacThermostat', + 'hvacUserInterfaceCfg', + 'msTemperatureMeasurement', + 'manuSpecificSinope', + ]; await reporting.bind(endpoint, coordinatorEndpoint, binds); await reporting.thermostatTemperature(endpoint); await reporting.thermostatOccupiedHeatingSetpoint(endpoint); @@ -1089,16 +1413,36 @@ const definitions: Definition[] = [ try { await reporting.thermostatSystemMode(endpoint); - } catch (error) {/* Not all support this */} + } catch (error) { + /* Not all support this */ + } - await endpoint.read('hvacThermostat', ['occupiedHeatingSetpoint', 'localTemp', 'systemMode', 'pIHeatingDemand', - 'SinopeBacklight', 'maxHeatSetpointLimit', 'minHeatSetpointLimit', 'SinopeMainCycleOutput', 'SinopeAuxCycleOutput']); + await endpoint.read('hvacThermostat', [ + 'occupiedHeatingSetpoint', + 'localTemp', + 'systemMode', + 'pIHeatingDemand', + 'SinopeBacklight', + 'maxHeatSetpointLimit', + 'minHeatSetpointLimit', + 'SinopeMainCycleOutput', + 'SinopeAuxCycleOutput', + ]); await endpoint.read('hvacUserInterfaceCfg', ['keypadLockout', 'tempDisplayMode']); - await endpoint.read('manuSpecificSinope', ['timeFormatToDisplay', 'connectedLoad', 'auxConnectedLoad', 'floorControlMode', - 'floorMinHeatSetpointLimit', 'floorMaxHeatSetpointLimit', 'ambiantMaxHeatSetpointLimit', 'outdoorTempToDisplayTimeout', - 'temperatureSensor', 'pumpProtection']); + await endpoint.read('manuSpecificSinope', [ + 'timeFormatToDisplay', + 'connectedLoad', + 'auxConnectedLoad', + 'floorControlMode', + 'floorMinHeatSetpointLimit', + 'floorMaxHeatSetpointLimit', + 'ambiantMaxHeatSetpointLimit', + 'outdoorTempToDisplayTimeout', + 'temperatureSensor', + 'pumpProtection', + ]); - await reporting.temperature(endpoint, {min: 1, max: 0xFFFF}); // disable reporting + await reporting.temperature(endpoint, {min: 1, max: 0xffff}); // disable reporting }, }, { @@ -1106,47 +1450,76 @@ const definitions: Definition[] = [ model: 'TH1500ZB', vendor: 'Sinopé', description: 'Zigbee dual pole line volt thermostat', - fromZigbee: [fzLocal.thermostat, fzLocal.sinope, legacy.fz.hvac_user_interface, - fz.electrical_measurement, fz.metering, fz.ignore_temperature_report], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint, - tz.thermostat_temperature_display_mode, tz.thermostat_keypad_lockout, tz.thermostat_system_mode, tzLocal.backlight_autodim, - tzLocal.thermostat_time, tzLocal.time_format, tzLocal.enable_outdoor_temperature, tzLocal.second_display_mode, - tzLocal.thermostat_outdoor_temperature, tzLocal.outdoor_temperature_timeout, tzLocal.thermostat_occupancy], + fromZigbee: [ + fzLocal.thermostat, + fzLocal.sinope, + legacy.fz.hvac_user_interface, + fz.electrical_measurement, + fz.metering, + fz.ignore_temperature_report, + ], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_unoccupied_heating_setpoint, + tz.thermostat_temperature_display_mode, + tz.thermostat_keypad_lockout, + tz.thermostat_system_mode, + tzLocal.backlight_autodim, + tzLocal.thermostat_time, + tzLocal.time_format, + tzLocal.enable_outdoor_temperature, + tzLocal.second_display_mode, + tzLocal.thermostat_outdoor_temperature, + tzLocal.outdoor_temperature_timeout, + tzLocal.thermostat_occupancy, + ], exposes: [ - e.climate() + e + .climate() .withSetpoint('occupied_heating_setpoint', 5, 30, 0.5) .withSetpoint('unoccupied_heating_setpoint', 5, 30, 0.5) .withLocalTemperature() .withSystemMode(['off', 'heat'], ea.ALL, 'Mode of the thermostat') .withPiHeatingDemand() .withRunningState(['idle', 'heat'], ea.STATE), - e.enum('thermostat_occupancy', ea.ALL, ['unoccupied', 'occupied']) - .withDescription('Occupancy state of the thermostat'), - e.enum('second_display_mode', ea.ALL, ['auto', 'setpoint', 'outdoor temp']) - .withDescription('Displays the outdoor temperature and then returns to the set point in "auto" mode, or clears ' + - 'in "outdoor temp" mode when expired.'), - e.numeric('thermostat_outdoor_temperature', ea.ALL).withUnit('°C').withValueMin(-99.5).withValueMax(99.5).withValueStep(0.5) + e.enum('thermostat_occupancy', ea.ALL, ['unoccupied', 'occupied']).withDescription('Occupancy state of the thermostat'), + e + .enum('second_display_mode', ea.ALL, ['auto', 'setpoint', 'outdoor temp']) + .withDescription( + 'Displays the outdoor temperature and then returns to the set point in "auto" mode, or clears ' + + 'in "outdoor temp" mode when expired.', + ), + e + .numeric('thermostat_outdoor_temperature', ea.ALL) + .withUnit('°C') + .withValueMin(-99.5) + .withValueMax(99.5) + .withValueStep(0.5) .withDescription('Outdoor temperature for the secondary display'), - e.numeric('outdoor_temperature_timeout', ea.ALL).withUnit('s').withValueMin(30).withValueMax(64800) - .withPreset('15 min', 900, '15 minutes').withPreset('30 min', 1800, '30 minutes').withPreset('1 hour', 3600, '1 hour') + e + .numeric('outdoor_temperature_timeout', ea.ALL) + .withUnit('s') + .withValueMin(30) + .withValueMax(64800) + .withPreset('15 min', 900, '15 minutes') + .withPreset('30 min', 1800, '30 minutes') + .withPreset('1 hour', 3600, '1 hour') .withDescription('Time in seconds after which the outdoor temperature is considered to have expired'), - e.binary('enable_outdoor_temperature', ea.ALL, 'ON', 'OFF') + e + .binary('enable_outdoor_temperature', ea.ALL, 'ON', 'OFF') .withDescription('DEPRECATED: Use second_display_mode or control via outdoor_temperature_timeout'), - e.enum('temperature_display_mode', ea.ALL, ['celsius', 'fahrenheit']) + e + .enum('temperature_display_mode', ea.ALL, ['celsius', 'fahrenheit']) .withDescription('The temperature format displayed on the thermostat screen'), - e.enum('time_format', ea.ALL, ['24h', '12h']) - .withDescription('The time format featured on the thermostat display'), - e.enum('backlight_auto_dim', ea.ALL, ['on_demand', 'sensing']) - .withDescription('Control backlight dimming behavior'), - e.enum('keypad_lockout', ea.ALL, ['unlock', 'lock1']) - .withDescription('Enables or disables the device’s buttons'), + e.enum('time_format', ea.ALL, ['24h', '12h']).withDescription('The time format featured on the thermostat display'), + e.enum('backlight_auto_dim', ea.ALL, ['on_demand', 'sensing']).withDescription('Control backlight dimming behavior'), + e.enum('keypad_lockout', ea.ALL, ['unlock', 'lock1']).withDescription('Enables or disables the device’s buttons'), ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); - const binds = [ - 'genBasic', 'genIdentify', 'genGroups', - 'hvacThermostat', 'hvacUserInterfaceCfg', 'msTemperatureMeasurement']; + const binds = ['genBasic', 'genIdentify', 'genGroups', 'hvacThermostat', 'hvacUserInterfaceCfg', 'msTemperatureMeasurement']; await reporting.bind(endpoint, coordinatorEndpoint, binds); await reporting.thermostatTemperature(endpoint); await reporting.thermostatOccupiedHeatingSetpoint(endpoint); @@ -1159,31 +1532,52 @@ const definitions: Definition[] = [ vendor: 'Sinopé', description: 'Zigbee smart light switch', fromZigbee: [fz.on_off, fzLocal.sinope, fz.metering], - toZigbee: [tz.on_off, tzLocal.timer_seconds, tzLocal.led_intensity_on, tzLocal.led_intensity_off, - tzLocal.led_color_on, tzLocal.led_color_off, tzLocal.keypad_lockout, tzLocal.connected_load], - exposes: [e.switch(), + toZigbee: [ + tz.on_off, + tzLocal.timer_seconds, + tzLocal.led_intensity_on, + tzLocal.led_intensity_off, + tzLocal.led_color_on, + tzLocal.led_color_off, + tzLocal.keypad_lockout, + tzLocal.connected_load, + ], + exposes: [ + e.switch(), e.action(['up_single', 'up_double', 'up_hold', 'down_single', 'down_double', 'down_hold']), - e.numeric('timer_seconds', ea.ALL).withUnit('s').withValueMin(0).withValueMax(65535) - .withPreset('Disabled', 0, 'disabled').withDescription('Automatically turn off load after x seconds'), - e.numeric('led_intensity_on', ea.ALL).withUnit('%').withValueMin(0).withValueMax(100) + e + .numeric('timer_seconds', ea.ALL) + .withUnit('s') + .withValueMin(0) + .withValueMax(65535) + .withPreset('Disabled', 0, 'disabled') + .withDescription('Automatically turn off load after x seconds'), + e + .numeric('led_intensity_on', ea.ALL) + .withUnit('%') + .withValueMin(0) + .withValueMax(100) .withDescription('Control status LED intensity when load ON'), - e.numeric('led_intensity_off', ea.ALL).withUnit('%').withValueMin(0).withValueMax(100) + e + .numeric('led_intensity_off', ea.ALL) + .withUnit('%') + .withValueMin(0) + .withValueMax(100) .withDescription('Control status LED intensity when load OFF'), - e.composite('led_color_on', 'led_color_on', ea.SET) + e + .composite('led_color_on', 'led_color_on', ea.SET) .withFeature(e.numeric('r', ea.SET)) .withFeature(e.numeric('g', ea.SET)) .withFeature(e.numeric('b', ea.SET)) .withDescription('Control status LED color when load ON'), - e.composite('led_color_off', 'led_color_off', ea.SET) + e + .composite('led_color_off', 'led_color_off', ea.SET) .withFeature(e.numeric('r', ea.SET)) .withFeature(e.numeric('g', ea.SET)) .withFeature(e.numeric('b', ea.SET)) .withDescription('Control status LED color when load OFF'), - e.enum('keypad_lockout', ea.ALL, ['unlock', 'lock']) - .withDescription('Enables or disables the device’s buttons'), - e.numeric('connected_load', ea.ALL) - .withUnit('W').withValueMin(0).withValueMax(1800) - .withDescription('Load connected in watt'), + e.enum('keypad_lockout', ea.ALL, ['unlock', 'lock']).withDescription('Enables or disables the device’s buttons'), + e.numeric('connected_load', ea.ALL).withUnit('W').withValueMin(0).withValueMax(1800).withDescription('Load connected in watt'), e.energy(), ], configure: async (device, coordinatorEndpoint) => { @@ -1194,13 +1588,17 @@ const definitions: Definition[] = [ try { await reporting.readMeteringMultiplierDivisor(endpoint); await reporting.currentSummDelivered(endpoint, {min: 10, max: 300, change: [0, 10]}); - } catch (error) {/* Do nothing*/} - const payload = [{ - attribute: 'actionReport', - minimumReportInterval: 0, - maximumReportInterval: 0, - reportableChange: 0, - }]; + } catch (error) { + /* Do nothing*/ + } + const payload = [ + { + attribute: 'actionReport', + minimumReportInterval: 0, + maximumReportInterval: 0, + reportableChange: 0, + }, + ]; await endpoint.configureReporting('manuSpecificSinope', payload); }, }, @@ -1211,27 +1609,48 @@ const definitions: Definition[] = [ description: 'Zigbee smart dimmer', extend: [light({configureReporting: true})], fromZigbee: [fzLocal.sinope], - toZigbee: [tzLocal.timer_seconds, tzLocal.led_intensity_on, tzLocal.led_intensity_off, - tzLocal.minimum_brightness, tzLocal.led_color_on, tzLocal.led_color_off], + toZigbee: [ + tzLocal.timer_seconds, + tzLocal.led_intensity_on, + tzLocal.led_intensity_off, + tzLocal.minimum_brightness, + tzLocal.led_color_on, + tzLocal.led_color_off, + ], exposes: [ - e.numeric('timer_seconds', ea.ALL).withUnit('s').withValueMin(0).withValueMax(65535) - .withPreset('Disabled', 0, 'disabled').withDescription('Automatically turn off load after x seconds'), - e.numeric('led_intensity_on', ea.ALL).withUnit('%').withValueMin(0).withValueMax(100) + e + .numeric('timer_seconds', ea.ALL) + .withUnit('s') + .withValueMin(0) + .withValueMax(65535) + .withPreset('Disabled', 0, 'disabled') + .withDescription('Automatically turn off load after x seconds'), + e + .numeric('led_intensity_on', ea.ALL) + .withUnit('%') + .withValueMin(0) + .withValueMax(100) .withDescription('Control status LED intensity when load ON'), - e.numeric('led_intensity_off', ea.ALL).withUnit('%').withValueMin(0).withValueMax(100) + e + .numeric('led_intensity_off', ea.ALL) + .withUnit('%') + .withValueMin(0) + .withValueMax(100) .withDescription('Control status LED when load OFF'), - e.numeric('minimum_brightness', ea.ALL).withValueMin(0).withValueMax(3000) - .withDescription('Control minimum dimmer brightness'), - e.composite('led_color_on', 'led_color_on', ea.SET) + e.numeric('minimum_brightness', ea.ALL).withValueMin(0).withValueMax(3000).withDescription('Control minimum dimmer brightness'), + e + .composite('led_color_on', 'led_color_on', ea.SET) .withFeature(e.numeric('r', ea.SET)) .withFeature(e.numeric('g', ea.SET)) .withFeature(e.numeric('b', ea.SET)) .withDescription('Control status LED color when load ON'), - e.composite('led_color_off', 'led_color_off', ea.SET) + e + .composite('led_color_off', 'led_color_off', ea.SET) .withFeature(e.numeric('r', ea.SET)) .withFeature(e.numeric('g', ea.SET)) .withFeature(e.numeric('b', ea.SET)) - .withDescription('Control status LED color when load OFF')], + .withDescription('Control status LED color when load OFF'), + ], }, { zigbeeModel: ['DM2550ZB'], @@ -1240,27 +1659,48 @@ const definitions: Definition[] = [ description: 'Zigbee Adaptive phase smart dimmer', extend: [light({configureReporting: true})], fromZigbee: [fzLocal.sinope], - toZigbee: [tzLocal.timer_seconds, tzLocal.led_intensity_on, tzLocal.led_intensity_off, - tzLocal.minimum_brightness, tzLocal.led_color_on, tzLocal.led_color_off], + toZigbee: [ + tzLocal.timer_seconds, + tzLocal.led_intensity_on, + tzLocal.led_intensity_off, + tzLocal.minimum_brightness, + tzLocal.led_color_on, + tzLocal.led_color_off, + ], exposes: [ - e.numeric('timer_seconds', ea.ALL).withUnit('s').withValueMin(0).withValueMax(65535) - .withPreset('Disabled', 0, 'disabled').withDescription('Automatically turn off load after x seconds'), - e.numeric('led_intensity_on', ea.ALL).withUnit('%').withValueMin(0).withValueMax(100) + e + .numeric('timer_seconds', ea.ALL) + .withUnit('s') + .withValueMin(0) + .withValueMax(65535) + .withPreset('Disabled', 0, 'disabled') + .withDescription('Automatically turn off load after x seconds'), + e + .numeric('led_intensity_on', ea.ALL) + .withUnit('%') + .withValueMin(0) + .withValueMax(100) .withDescription('Control status LED intensity when load ON'), - e.numeric('led_intensity_off', ea.ALL).withUnit('%').withValueMin(0).withValueMax(100) + e + .numeric('led_intensity_off', ea.ALL) + .withUnit('%') + .withValueMin(0) + .withValueMax(100) .withDescription('Control status LED when load OFF'), - e.numeric('minimum_brightness', ea.ALL).withValueMin(0).withValueMax(3000) - .withDescription('Control minimum dimmer brightness'), - e.composite('led_color_on', 'led_color_on', ea.SET) + e.numeric('minimum_brightness', ea.ALL).withValueMin(0).withValueMax(3000).withDescription('Control minimum dimmer brightness'), + e + .composite('led_color_on', 'led_color_on', ea.SET) .withFeature(e.numeric('r', ea.SET)) .withFeature(e.numeric('g', ea.SET)) .withFeature(e.numeric('b', ea.SET)) .withDescription('Control status LED color when load ON'), - e.composite('led_color_off', 'led_color_off', ea.SET) + e + .composite('led_color_off', 'led_color_off', ea.SET) .withFeature(e.numeric('r', ea.SET)) .withFeature(e.numeric('g', ea.SET)) .withFeature(e.numeric('b', ea.SET)) - .withDescription('Control status LED color when load OFF')], + .withDescription('Control status LED color when load OFF'), + ], }, { zigbeeModel: ['SP2600ZB'], @@ -1415,26 +1855,27 @@ const definitions: Definition[] = [ model: 'VA4220ZB', vendor: 'Sinopé', description: 'Sedna smart water valve', - fromZigbee: [fz.ignore_iaszone_statuschange, fz.cover_position_via_brightness, fz.cover_state_via_onoff, - fz.battery, fz.metering], + fromZigbee: [fz.ignore_iaszone_statuschange, fz.cover_position_via_brightness, fz.cover_state_via_onoff, fz.battery, fz.metering], toZigbee: [tz.cover_via_brightness], meta: {battery: {voltageToPercentage: {min: 5400, max: 6800}}}, exposes: [e.valve_switch(), e.valve_position(), e.battery_low(), e.battery(), e.battery_voltage()], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); - const binds = [ - 'genBasic', 'genGroups', 'genOnOff', 'ssIasZone', 'genLevelCtrl', - 'genPowerCfg', 'seMetering', 'manuSpecificSinope']; + const binds = ['genBasic', 'genGroups', 'genOnOff', 'ssIasZone', 'genLevelCtrl', 'genPowerCfg', 'seMetering', 'manuSpecificSinope']; await reporting.bind(endpoint, coordinatorEndpoint, binds); await reporting.batteryPercentageRemaining(endpoint); await reporting.onOff(endpoint); await reporting.brightness(endpoint); // valve position try { await reporting.batteryVoltage(endpoint); - } catch (error) {/* Do Nothing */} + } catch (error) { + /* Do Nothing */ + } try { await reporting.batteryAlarmState(endpoint); - } catch (error) {/* Do Nothing */} + } catch (error) { + /* Do Nothing */ + } }, }, { @@ -1446,16 +1887,24 @@ const definitions: Definition[] = [ fromZigbee: [fzLocal.ias_water_leak_alarm, fzLocal.sinope, fz.temperature], toZigbee: [tzLocal.low_water_temp_protection], exposes: [ - e.numeric('low_water_temp_protection', ea.ALL).withUnit('°C').withValueMin(0).withValueMax(65).withValueStep(1) + e + .numeric('low_water_temp_protection', ea.ALL) + .withUnit('°C') + .withValueMin(0) + .withValueMax(65) + .withValueStep(1) .withDescription('Temperature at which water heating will resume automatically (default: 45°C)'), - e.water_leak(), e.temperature()], + e.water_leak(), + e.temperature(), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const binds = ['msTemperatureMeasurement', 'ssIasZone', 'manuSpecificSinope']; await reporting.bind(endpoint, coordinatorEndpoint, binds); await reporting.temperature(endpoint, {min: 60, max: 60, change: 0}); // divider 100: 0.1C - await endpoint.configureReporting('ssIasZone', [{attribute: 'zoneStatus', minimumReportInterval: 1, - maximumReportInterval: constants.repInterval.HOUR, reportableChange: 1}]); + await endpoint.configureReporting('ssIasZone', [ + {attribute: 'zoneStatus', minimumReportInterval: 1, maximumReportInterval: constants.repInterval.HOUR, reportableChange: 1}, + ]); }, }, { @@ -1465,9 +1914,12 @@ const definitions: Definition[] = [ description: 'Tank level monitor', fromZigbee: [fz.battery, fz.temperature, fzLocal.tank_level], toZigbee: [], - exposes: [e.battery_low(), e.battery(), e.temperature(), - e.numeric('tank_level', ea.STATE).withUnit('%').withValueMin(0).withValueMax(100) - .withDescription('Percent volume remaining in tank')], + exposes: [ + e.battery_low(), + e.battery(), + e.temperature(), + e.numeric('tank_level', ea.STATE).withUnit('%').withValueMin(0).withValueMax(100).withDescription('Percent volume remaining in tank'), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const binds = ['genPowerCfg', 'msTemperatureMeasurement', 'genAnalogInput']; diff --git a/src/devices/siterwell.ts b/src/devices/siterwell.ts index dcb0beafe8a4a..8774cef83a8a0 100644 --- a/src/devices/siterwell.ts +++ b/src/devices/siterwell.ts @@ -1,6 +1,6 @@ +import fz from '../converters/fromZigbee'; import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; -import fz from '../converters/fromZigbee'; import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -16,19 +16,35 @@ const definitions: Definition[] = [ {modelID: 'TS0601', manufacturerName: '_TZE200_jeaxp72v'}, {modelID: 'TS0601', manufacturerName: '_TZE200_owwdxjbx'}, {modelID: 'TS0601', manufacturerName: '_TZE200_2cs6g9i7'}, - {modelID: 'TS0601', manufacturerName: '_TZE200_04yfvweb'}], + {modelID: 'TS0601', manufacturerName: '_TZE200_04yfvweb'}, + ], model: 'GS361A-H04', vendor: 'Siterwell', description: 'Radiator valve with thermostat', fromZigbee: [legacy.fz.tuya_thermostat, fz.ignore_basic_report], - meta: {tuyaThermostatSystemMode: legacy.thermostatSystemModes4, tuyaThermostatPreset: legacy.thermostatPresets, - tuyaThermostatPresetToSystemMode: legacy.thermostatSystemModes4}, - toZigbee: [legacy.tz.tuya_thermostat_child_lock, legacy.tz.siterwell_thermostat_window_detection, legacy.tz.tuya_thermostat_valve_detection, - legacy.tz.tuya_thermostat_current_heating_setpoint, legacy.tz.tuya_thermostat_system_mode, legacy.tz.tuya_thermostat_auto_lock, - legacy.tz.tuya_thermostat_calibration, legacy.tz.tuya_thermostat_min_temp, legacy.tz.tuya_thermostat_max_temp, - legacy.tz.tuya_thermostat_comfort_temp, legacy.tz.tuya_thermostat_eco_temp, legacy.tz.tuya_thermostat_force, - legacy.tz.tuya_thermostat_preset, legacy.tz.tuya_thermostat_boost_time], - whiteLabel: [{vendor: 'Essentials', description: 'Smart home heizkörperthermostat premium', model: '120112'}, + meta: { + tuyaThermostatSystemMode: legacy.thermostatSystemModes4, + tuyaThermostatPreset: legacy.thermostatPresets, + tuyaThermostatPresetToSystemMode: legacy.thermostatSystemModes4, + }, + toZigbee: [ + legacy.tz.tuya_thermostat_child_lock, + legacy.tz.siterwell_thermostat_window_detection, + legacy.tz.tuya_thermostat_valve_detection, + legacy.tz.tuya_thermostat_current_heating_setpoint, + legacy.tz.tuya_thermostat_system_mode, + legacy.tz.tuya_thermostat_auto_lock, + legacy.tz.tuya_thermostat_calibration, + legacy.tz.tuya_thermostat_min_temp, + legacy.tz.tuya_thermostat_max_temp, + legacy.tz.tuya_thermostat_comfort_temp, + legacy.tz.tuya_thermostat_eco_temp, + legacy.tz.tuya_thermostat_force, + legacy.tz.tuya_thermostat_preset, + legacy.tz.tuya_thermostat_boost_time, + ], + whiteLabel: [ + {vendor: 'Essentials', description: 'Smart home heizkörperthermostat premium', model: '120112'}, {vendor: 'Tuya', description: 'Głowica termostatyczna', model: 'GTZ02'}, {vendor: 'Revolt', description: 'Thermostatic Radiator Valve Controller', model: 'NX-4911'}, {vendor: 'Unitec', description: 'Thermostatic Radiator Valve Controller', model: '30946'}, @@ -36,13 +52,21 @@ const definitions: Definition[] = [ {vendor: 'Nedis', description: 'Thermostatic Radiator Valve Controller', model: 'ZBHTR10WT'}, {vendor: 'TCP Smart', description: 'Smart Thermostatic Radiator Valve', model: 'TBUWTRV'}, {vendor: 'Brennenstuhl', description: 'Radiator Thermostat', model: 'HT CZ 01'}, - {vendor: 'Appartme', description: 'Głowica termostatyczna', model: 'APRM-04-001'}], - exposes: [e.child_lock(), e.window_detection(), e.battery(), e.valve_detection(), + {vendor: 'Appartme', description: 'Głowica termostatyczna', model: 'APRM-04-001'}, + ], + exposes: [ + e.child_lock(), + e.window_detection(), + e.battery(), + e.valve_detection(), e.position().withDescription('TRV valve position in %.'), - e.climate() - .withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.STATE_SET).withLocalTemperature(ea.STATE) + e + .climate() + .withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.STATE_SET) + .withLocalTemperature(ea.STATE) .withSystemMode(['off', 'auto', 'heat'], ea.STATE_SET) - .withRunningState(['idle', 'heat'], ea.STATE)], + .withRunningState(['idle', 'heat'], ea.STATE), + ], }, ]; diff --git a/src/devices/skydance.ts b/src/devices/skydance.ts index 8491f520f9cda..f64e4f766dd10 100644 --- a/src/devices/skydance.ts +++ b/src/devices/skydance.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; import * as tuya from '../lib/tuya'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -16,10 +16,7 @@ const definitions: Definition[] = [ description: 'Zigbee & RF 5 in 1 LED controller (DIM mode)', fromZigbee: [legacy.fz.tuya_light_wz5], toZigbee: [legacy.tz.tuya_dimmer_state, legacy.tz.tuya_light_wz5], - exposes: [ - e.light().withBrightness().setAccess('state', - ea.STATE_SET).setAccess('brightness', ea.STATE_SET), - ], + exposes: [e.light().withBrightness().setAccess('state', ea.STATE_SET).setAccess('brightness', ea.STATE_SET)], }, { fingerprint: [ @@ -34,8 +31,13 @@ const definitions: Definition[] = [ fromZigbee: [legacy.fz.tuya_light_wz5], toZigbee: [legacy.tz.tuya_dimmer_state, legacy.tz.tuya_light_wz5], exposes: [ - e.light().withBrightness().setAccess('state', - ea.STATE_SET).setAccess('brightness', ea.STATE_SET).withColorTemp([250, 454]).setAccess('color_temp', ea.STATE_SET), + e + .light() + .withBrightness() + .setAccess('state', ea.STATE_SET) + .setAccess('brightness', ea.STATE_SET) + .withColorTemp([250, 454]) + .setAccess('color_temp', ea.STATE_SET), ], whiteLabel: [ tuya.whitelabel('Ltech', 'TY-75-24-G2Z2_CCT', '150W 24V Zigbee CV tunable white LED driver', ['_TZE200_na98lvjp']), @@ -53,8 +55,13 @@ const definitions: Definition[] = [ fromZigbee: [legacy.fz.tuya_light_wz5], toZigbee: [legacy.tz.tuya_dimmer_state, legacy.tz.tuya_light_wz5], exposes: [ - e.light().withBrightness().setAccess('state', ea.STATE_SET).setAccess('brightness', - ea.STATE_SET).withColor(['hs']).setAccess('color_hs', ea.STATE_SET), + e + .light() + .withBrightness() + .setAccess('state', ea.STATE_SET) + .setAccess('brightness', ea.STATE_SET) + .withColor(['hs']) + .setAccess('color_hs', ea.STATE_SET), ], }, { @@ -75,10 +82,14 @@ const definitions: Definition[] = [ fromZigbee: [legacy.fz.tuya_light_wz5], toZigbee: [legacy.tz.tuya_dimmer_state, legacy.tz.tuya_light_wz5], exposes: [ - e.light().withBrightness().setAccess('state', ea.STATE_SET).setAccess('brightness', - ea.STATE_SET).withColor(['hs']).setAccess('color_hs', ea.STATE_SET), - e.numeric('white_brightness', ea.STATE_SET).withValueMin(0).withValueMax(254).withDescription( - 'White brightness of this light'), + e + .light() + .withBrightness() + .setAccess('state', ea.STATE_SET) + .setAccess('brightness', ea.STATE_SET) + .withColor(['hs']) + .setAccess('color_hs', ea.STATE_SET), + e.numeric('white_brightness', ea.STATE_SET).withValueMin(0).withValueMax(254).withDescription('White brightness of this light'), ], meta: {separateWhite: true}, }, @@ -93,11 +104,16 @@ const definitions: Definition[] = [ fromZigbee: [legacy.fz.tuya_light_wz5], toZigbee: [legacy.tz.tuya_dimmer_state, legacy.tz.tuya_light_wz5], exposes: [ - e.light().withBrightness().setAccess('state', ea.STATE_SET).setAccess('brightness', - ea.STATE_SET).withColor(['hs']).withColorTemp([250, 454]).setAccess('color_temp', - ea.STATE_SET).setAccess('color_hs', ea.STATE_SET), - e.numeric('white_brightness', ea.STATE_SET).withValueMin(0).withValueMax(254).withDescription( - 'White brightness of this light'), + e + .light() + .withBrightness() + .setAccess('state', ea.STATE_SET) + .setAccess('brightness', ea.STATE_SET) + .withColor(['hs']) + .withColorTemp([250, 454]) + .setAccess('color_temp', ea.STATE_SET) + .setAccess('color_hs', ea.STATE_SET), + e.numeric('white_brightness', ea.STATE_SET).withValueMin(0).withValueMax(254).withDescription('White brightness of this light'), ], meta: {separateWhite: true}, }, diff --git a/src/devices/slv.ts b/src/devices/slv.ts index dcce8d357cf16..c77b319cf9d66 100644 --- a/src/devices/slv.ts +++ b/src/devices/slv.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/smart9.ts b/src/devices/smart9.ts index e667b3e61312e..dfd48cd03b1f5 100644 --- a/src/devices/smart9.ts +++ b/src/devices/smart9.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/smart_home_pty.ts b/src/devices/smart_home_pty.ts index 95853563d3a8d..4f009b381fdad 100644 --- a/src/devices/smart_home_pty.ts +++ b/src/devices/smart_home_pty.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light, onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/smartenit.ts b/src/devices/smartenit.ts index 4ae2f3e314a0d..172590cbbaa20 100644 --- a/src/devices/smartenit.ts +++ b/src/devices/smartenit.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/smartthings.ts b/src/devices/smartthings.ts index 295ab4a2cc19d..f88feace6121e 100644 --- a/src/devices/smartthings.ts +++ b/src/devices/smartthings.ts @@ -1,12 +1,13 @@ import {Zcl} from 'zigbee-herdsman'; -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; + import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; import * as constants from '../lib/constants'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; import {electricityMeter, light, onOff} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -36,8 +37,12 @@ const definitions: Definition[] = [ vendor: 'SmartThings', description: 'Arrival sensor', fromZigbee: [fz.STS_PRS_251_presence, fz.battery, legacy.fz.STS_PRS_251_beeping], - exposes: [e.battery(), e.presence(), e.action(['beeping']), - e.enum('beep', ea.SET, ['2', '5', '10', '15', '30']).withDescription('Trigger beep for x seconds')], + exposes: [ + e.battery(), + e.presence(), + e.action(['beeping']), + e.enum('beep', ea.SET, ['2', '5', '10', '15', '30']).withDescription('Trigger beep for x seconds'), + ], toZigbee: [tz.STS_PRS_251_beep], meta: {battery: {voltageToPercentage: '3V_2500'}}, configure: async (device, coordinatorEndpoint) => { @@ -83,8 +88,7 @@ const definitions: Definition[] = [ configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const options = {manufacturerCode: Zcl.ManufacturerCode.CENTRALITE_SYSTEMS_INC}; - await reporting.bind(endpoint, coordinatorEndpoint, - ['msTemperatureMeasurement', 'genPowerCfg', 'manuSpecificSamsungAccelerometer']); + await reporting.bind(endpoint, coordinatorEndpoint, ['msTemperatureMeasurement', 'genPowerCfg', 'manuSpecificSamsungAccelerometer']); await reporting.temperature(endpoint); await reporting.batteryVoltage(endpoint); const payloadA = reporting.payload('acceleration', 10, constants.repInterval.MINUTE, 1); @@ -99,8 +103,7 @@ const definitions: Definition[] = [ device.powerSource = 'Battery'; device.save(); }, - exposes: [e.temperature(), e.contact(), e.battery_low(), e.tamper(), e.battery(), - e.moving(), e.x_axis(), e.y_axis(), e.z_axis()], + exposes: [e.temperature(), e.contact(), e.battery_low(), e.tamper(), e.battery(), e.moving(), e.x_axis(), e.y_axis(), e.z_axis()], }, { zigbeeModel: ['3200-Sgb'], @@ -166,7 +169,9 @@ const definitions: Definition[] = [ try { // https://github.com/Koenkk/zigbee2mqtt/issues/11706 await reporting.readEletricalMeasurementMultiplierDivisors(endpoint); - } catch (error) {/* Fails for some*/} + } catch (error) { + /* Fails for some*/ + } await reporting.activePower(endpoint); await reporting.rmsCurrent(endpoint); await reporting.rmsVoltage(endpoint, {change: 10}); @@ -269,8 +274,7 @@ const definitions: Definition[] = [ configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const options = {manufacturerCode: Zcl.ManufacturerCode.SMARTTHINGS_INC}; - await reporting.bind(endpoint, coordinatorEndpoint, - ['msTemperatureMeasurement', 'genPowerCfg', 'manuSpecificSamsungAccelerometer']); + await reporting.bind(endpoint, coordinatorEndpoint, ['msTemperatureMeasurement', 'genPowerCfg', 'manuSpecificSamsungAccelerometer']); await endpoint.write('manuSpecificSamsungAccelerometer', {0x0000: {value: 0x01, type: 0x20}}, options); await endpoint.write('manuSpecificSamsungAccelerometer', {0x0002: {value: 0x0276, type: 0x21}}, options); await reporting.temperature(endpoint); @@ -287,10 +291,7 @@ const definitions: Definition[] = [ device.powerSource = 'Battery'; device.save(); }, - exposes: [ - e.temperature(), e.contact(), e.battery_low(), e.tamper(), e.battery(), - e.moving(), e.x_axis(), e.y_axis(), e.z_axis(), - ], + exposes: [e.temperature(), e.contact(), e.battery_low(), e.tamper(), e.battery(), e.moving(), e.x_axis(), e.y_axis(), e.z_axis()], }, { zigbeeModel: ['multi'], @@ -302,8 +303,7 @@ const definitions: Definition[] = [ configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const options = {manufacturerCode: Zcl.ManufacturerCode.SAMJIN_CO_LTD}; - await reporting.bind(endpoint, coordinatorEndpoint, - ['msTemperatureMeasurement', 'genPowerCfg', 'manuSpecificSamsungAccelerometer']); + await reporting.bind(endpoint, coordinatorEndpoint, ['msTemperatureMeasurement', 'genPowerCfg', 'manuSpecificSamsungAccelerometer']); await endpoint.write('manuSpecificSamsungAccelerometer', {0x0000: {value: 0x14, type: 0x20}}, options); await reporting.temperature(endpoint); await reporting.batteryPercentageRemaining(endpoint); @@ -316,10 +316,7 @@ const definitions: Definition[] = [ const payloadZ = reporting.payload('z_axis', 10, constants.repInterval.HOUR, 5); await endpoint.configureReporting('manuSpecificSamsungAccelerometer', payloadZ, options); }, - exposes: [ - e.temperature(), e.contact(), e.battery_low(), e.tamper(), e.battery(), - e.moving(), e.x_axis(), e.y_axis(), e.z_axis(), - ], + exposes: [e.temperature(), e.contact(), e.battery_low(), e.tamper(), e.battery(), e.moving(), e.x_axis(), e.y_axis(), e.z_axis()], }, { zigbeeModel: ['3310-S'], @@ -336,17 +333,17 @@ const definitions: Definition[] = [ await reporting.bind(endpoint, coordinatorEndpoint, binds); await reporting.temperature(endpoint); - const payload = [{ - attribute: 'measuredValue', - minimumReportInterval: 10, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 10, - }]; - await endpoint.configureReporting( - 'manuSpecificCentraliteHumidity', - payload, - {manufacturerCode: Zcl.ManufacturerCode.CENTRALITE_SYSTEMS_INC}, - ); + const payload = [ + { + attribute: 'measuredValue', + minimumReportInterval: 10, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 10, + }, + ]; + await endpoint.configureReporting('manuSpecificCentraliteHumidity', payload, { + manufacturerCode: Zcl.ManufacturerCode.CENTRALITE_SYSTEMS_INC, + }); await reporting.batteryVoltage(endpoint); }, @@ -453,8 +450,13 @@ const definitions: Definition[] = [ model: 'IM6001-BTP01', vendor: 'SmartThings', description: 'Button', - fromZigbee: [fz.command_status_change_notification_action, legacy.fz.st_button_state, fz.battery, fz.temperature, - fz.ignore_iaszone_attreport], + fromZigbee: [ + fz.command_status_change_notification_action, + legacy.fz.st_button_state, + fz.battery, + fz.temperature, + fz.ignore_iaszone_attreport, + ], exposes: [e.action(['off', 'single', 'double', 'hold']), e.battery(), e.temperature()], toZigbee: [], configure: async (device, coordinatorEndpoint) => { diff --git a/src/devices/smartwings.ts b/src/devices/smartwings.ts index 4d2b95b2958c9..0bd17fc1cdad5 100644 --- a/src/devices/smartwings.ts +++ b/src/devices/smartwings.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/smlight.ts b/src/devices/smlight.ts index 68874dada953a..4a5386551c027 100644 --- a/src/devices/smlight.ts +++ b/src/devices/smlight.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; import fz from '../converters/fromZigbee'; import {forcePowerSource} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/sohan_electric.ts b/src/devices/sohan_electric.ts index a206d7d4cce12..dd1df7393764c 100644 --- a/src/devices/sohan_electric.ts +++ b/src/devices/sohan_electric.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; import fz from '../converters/fromZigbee'; import {onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/solaredge.ts b/src/devices/solaredge.ts index e713416d728cd..ad048d20b7630 100644 --- a/src/devices/solaredge.ts +++ b/src/devices/solaredge.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/somfy.ts b/src/devices/somfy.ts index e94cc4a2df517..b26da145099b1 100644 --- a/src/devices/somfy.ts +++ b/src/devices/somfy.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; diff --git a/src/devices/somgoms.ts b/src/devices/somgoms.ts index 2e0ce28b97e71..568d0cafc69ab 100644 --- a/src/devices/somgoms.ts +++ b/src/devices/somgoms.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; diff --git a/src/devices/sonoff.ts b/src/devices/sonoff.ts index 50cdb36901b07..beb9c83459712 100644 --- a/src/devices/sonoff.ts +++ b/src/devices/sonoff.ts @@ -1,19 +1,29 @@ import {Zcl} from 'zigbee-herdsman'; -import * as exposes from '../lib/exposes'; + import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; import * as constants from '../lib/constants'; -import * as reporting from '../lib/reporting'; +import {modernExtend as ewelinkModernExtend} from '../lib/ewelink'; +import * as exposes from '../lib/exposes'; +import {logger} from '../lib/logger'; import { - binary, enumLookup, forcePowerSource, numeric, onOff, - customTimeResponse, battery, ota, deviceAddCustomCluster, - temperature, humidity, bindCluster, + binary, + enumLookup, + forcePowerSource, + numeric, + onOff, + customTimeResponse, + battery, + ota, + deviceAddCustomCluster, + temperature, + humidity, + bindCluster, iasZoneAlarm, } from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; import {Definition, Fz, KeyValue, KeyValueAny, ModernExtend, Tz} from '../lib/types'; import * as utils from '../lib/utils'; -import {logger} from '../lib/logger'; -import {modernExtend as ewelinkModernExtend} from '../lib/ewelink'; const {ewelinkAction} = ewelinkModernExtend; const NS = 'zhc:sonoff'; @@ -39,9 +49,8 @@ const fzLocal = { }; const sonoffExtend = { - addCustomClusterEwelink: () => deviceAddCustomCluster( - 'customClusterEwelink', - { + addCustomClusterEwelink: () => + deviceAddCustomCluster('customClusterEwelink', { ID: 0xfc11, attributes: { radioPower: {ID: 0x0012, type: Zcl.DataType.INT16}, @@ -59,64 +68,75 @@ const sonoffExtend = { protocolData: {ID: 0x01, parameters: [{name: 'data', type: Zcl.BuffaloZclDataType.LIST_UINT8}]}, }, commandsResponse: {}, - }, - ), + }), inchingControlSet: (): ModernExtend => { const clusterName = 'customClusterEwelink'; const commandName = 'protocolData'; - const exposes = e.composite('inching_control_set', 'inching_control_set', ea.SET) - .withDescription('Device Inching function Settings. The device will automatically turn off (turn on) '+ - 'after each turn on (turn off) for a specified period of time.') + const exposes = e + .composite('inching_control_set', 'inching_control_set', ea.SET) + .withDescription( + 'Device Inching function Settings. The device will automatically turn off (turn on) ' + + 'after each turn on (turn off) for a specified period of time.', + ) .withFeature(e.binary('inching_control', ea.SET, 'ENABLE', 'DISABLE').withDescription('Enable/disable inching function.')) - .withFeature(e.numeric('inching_time', ea.SET).withDescription('Delay time for executing a inching action.') - .withUnit('seconds').withValueMin(0.5).withValueMax(3599.5).withValueStep(0.5)) + .withFeature( + e + .numeric('inching_time', ea.SET) + .withDescription('Delay time for executing a inching action.') + .withUnit('seconds') + .withValueMin(0.5) + .withValueMax(3599.5) + .withValueStep(0.5), + ) .withFeature(e.binary('inching_mode', ea.SET, 'ON', 'OFF').withDescription('Set inching off or inching on mode.').withValueToggle('ON')); const fromZigbee: Fz.Converter[] = []; - const toZigbee: Tz.Converter[] = [{ - key: ['inching_control_set'], - convertSet: async (entity, key, value, meta) => { - const inchingControl:string = 'inching_control'; - const inchingTime:string = 'inching_time'; - const inchingMode:string = 'inching_mode'; + const toZigbee: Tz.Converter[] = [ + { + key: ['inching_control_set'], + convertSet: async (entity, key, value, meta) => { + const inchingControl: string = 'inching_control'; + const inchingTime: string = 'inching_time'; + const inchingMode: string = 'inching_mode'; - const tmpTime = Number((Math.round(Number((value[inchingTime as keyof typeof value] * 2).toFixed(1)))).toFixed(1)); + const tmpTime = Number(Math.round(Number((value[inchingTime as keyof typeof value] * 2).toFixed(1))).toFixed(1)); - const payloadValue = []; - payloadValue[0] = 0x01; // Cmd - payloadValue[1] = 0x17; // SubCmd - payloadValue[2] = 0x07; // Length - payloadValue[3] = 0x80; // SeqNum + const payloadValue = []; + payloadValue[0] = 0x01; // Cmd + payloadValue[1] = 0x17; // SubCmd + payloadValue[2] = 0x07; // Length + payloadValue[3] = 0x80; // SeqNum - payloadValue[4] = 0x00; // Mode - if (value[inchingControl as keyof typeof value] != 'DISABLE') { - payloadValue[4] |= 0x80; - } - if (value[inchingMode as keyof typeof value] != 'OFF') { - payloadValue[4] |= 0x01; - } + payloadValue[4] = 0x00; // Mode + if (value[inchingControl as keyof typeof value] != 'DISABLE') { + payloadValue[4] |= 0x80; + } + if (value[inchingMode as keyof typeof value] != 'OFF') { + payloadValue[4] |= 0x01; + } - payloadValue[5] = 0x00; // Channel + payloadValue[5] = 0x00; // Channel - payloadValue[6] = tmpTime; // Timeout - payloadValue[7] = tmpTime >> 8; + payloadValue[6] = tmpTime; // Timeout + payloadValue[7] = tmpTime >> 8; - payloadValue[8] = 0x00; // Reserve - payloadValue[9] = 0x00; + payloadValue[8] = 0x00; // Reserve + payloadValue[9] = 0x00; - payloadValue[10] = 0x00; // CheckCode - for (let i = 0; i < (payloadValue[2] + 3); i++) { - payloadValue[10] ^= payloadValue[i]; - } + payloadValue[10] = 0x00; // CheckCode + for (let i = 0; i < payloadValue[2] + 3; i++) { + payloadValue[10] ^= payloadValue[i]; + } - await entity.command( - clusterName, - commandName, - {data: payloadValue}, - {manufacturerCode: Zcl.ManufacturerCode.SHENZHEN_COOLKIT_TECHNOLOGY_CO_LTD}, - ); - return {state: {[key]: value}}; + await entity.command( + clusterName, + commandName, + {data: payloadValue}, + {manufacturerCode: Zcl.ManufacturerCode.SHENZHEN_COOLKIT_TECHNOLOGY_CO_LTD}, + ); + return {state: {[key]: value}}; + }, }, - }]; + ]; return { exposes: [exposes], fromZigbee, @@ -125,12 +145,15 @@ const sonoffExtend = { }; }, weeklySchedule: (): ModernExtend => { - const exposes = e.composite('schedule', 'weekly_schedule', ea.STATE_SET) - .withDescription('The preset heating schedule to use when the system mode is set to "auto" (indicated with ⏲ on the TRV). ' + - 'Up to 6 transitions can be defined per day, where a transition is expressed in the format \'HH:mm/temperature\', each ' + - 'separated by a space. The first transition for each day must start at 00:00 and the valid temperature range is 4-35°C ' + - '(in 0.5°C steps). The temperature will be set at the time of the first transition until the time of the next transition, ' + - 'e.g. \'04:00/20 10:00/25\' will result in the temperature being set to 20°C at 04:00 until 10:00, when it will change to 25°C.') + const exposes = e + .composite('schedule', 'weekly_schedule', ea.STATE_SET) + .withDescription( + 'The preset heating schedule to use when the system mode is set to "auto" (indicated with ⏲ on the TRV). ' + + "Up to 6 transitions can be defined per day, where a transition is expressed in the format 'HH:mm/temperature', each " + + 'separated by a space. The first transition for each day must start at 00:00 and the valid temperature range is 4-35°C ' + + '(in 0.5°C steps). The temperature will be set at the time of the first transition until the time of the next transition, ' + + "e.g. '04:00/20 10:00/25' will result in the temperature being set to 20°C at 04:00 until 10:00, when it will change to 25°C.", + ) .withFeature(e.text('sunday', ea.STATE_SET)) .withFeature(e.text('monday', ea.STATE_SET)) .withFeature(e.text('tuesday', ea.STATE_SET)) @@ -139,97 +162,101 @@ const sonoffExtend = { .withFeature(e.text('friday', ea.STATE_SET)) .withFeature(e.text('saturday', ea.STATE_SET)); - const fromZigbee: Fz.Converter[] = [{ - cluster: 'hvacThermostat', - type: ['commandGetWeeklyScheduleRsp'], - convert: (model, msg, publish, options, meta) => { - const day = Object.entries(constants.thermostatDayOfWeek) - .find((d) => msg.data.dayofweek & 1<<+d[0])[1]; + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'hvacThermostat', + type: ['commandGetWeeklyScheduleRsp'], + convert: (model, msg, publish, options, meta) => { + const day = Object.entries(constants.thermostatDayOfWeek).find((d) => msg.data.dayofweek & (1 << +d[0]))[1]; - const transitions = msg.data.transitions - .map((t: { heatSetpoint: number, transitionTime: number }) => { - const totalMinutes = t.transitionTime; - const hours = totalMinutes / 60; - const rHours = Math.floor(hours); - const minutes = (hours - rHours) * 60; - const rMinutes = Math.round(minutes); - const strHours = rHours.toString().padStart(2, '0'); - const strMinutes = rMinutes.toString().padStart(2, '0'); + const transitions = msg.data.transitions + .map((t: {heatSetpoint: number; transitionTime: number}) => { + const totalMinutes = t.transitionTime; + const hours = totalMinutes / 60; + const rHours = Math.floor(hours); + const minutes = (hours - rHours) * 60; + const rMinutes = Math.round(minutes); + const strHours = rHours.toString().padStart(2, '0'); + const strMinutes = rMinutes.toString().padStart(2, '0'); - return `${strHours}:${strMinutes}/${t.heatSetpoint / 100}`; - }) - .sort() - .join(' '); + return `${strHours}:${strMinutes}/${t.heatSetpoint / 100}`; + }) + .sort() + .join(' '); - return { - weekly_schedule: { - ...meta.state.weekly_schedule as Record[], - [day]: transitions, - }, - }; + return { + weekly_schedule: { + ...(meta.state.weekly_schedule as Record[]), + [day]: transitions, + }, + }; + }, }, - }]; + ]; - const toZigbee: Tz.Converter[] = [{ - key: ['weekly_schedule'], - convertSet: async (entity, key, value, meta) => { - // Transition format: HH:mm/temperature - const transitionRegex = /^(0[0-9]|1[0-9]|2[0-3]):([0-5][0-9])\/(\d+(\.5)?)$/; + const toZigbee: Tz.Converter[] = [ + { + key: ['weekly_schedule'], + convertSet: async (entity, key, value, meta) => { + // Transition format: HH:mm/temperature + const transitionRegex = /^(0[0-9]|1[0-9]|2[0-3]):([0-5][0-9])\/(\d+(\.5)?)$/; - utils.assertObject(value, key); + utils.assertObject(value, key); - for (const dayOfWeekName of Object.keys(value)) { - const dayKey = utils.getKey(constants.thermostatDayOfWeek, dayOfWeekName.toLowerCase(), null); + for (const dayOfWeekName of Object.keys(value)) { + const dayKey = utils.getKey(constants.thermostatDayOfWeek, dayOfWeekName.toLowerCase(), null); - if (dayKey === null) { - throw new Error(`Invalid schedule: invalid day name, found: ${dayOfWeekName}`); - } + if (dayKey === null) { + throw new Error(`Invalid schedule: invalid day name, found: ${dayOfWeekName}`); + } - const dayOfWeekBit = Number(dayKey); + const dayOfWeekBit = Number(dayKey); - const transitions = value[dayOfWeekName].split(' ').sort(); + const transitions = value[dayOfWeekName].split(' ').sort(); - if (transitions.length > 6) { - throw new Error('Invalid schedule: days must have no more than 6 transitions'); - } + if (transitions.length > 6) { + throw new Error('Invalid schedule: days must have no more than 6 transitions'); + } - const payload: KeyValueAny = { - dayofweek: (1 << Number(dayOfWeekBit)), - numoftrans: transitions.length, - mode: (1 << 0), // heat - transitions: [], - }; + const payload: KeyValueAny = { + dayofweek: 1 << Number(dayOfWeekBit), + numoftrans: transitions.length, + mode: 1 << 0, // heat + transitions: [], + }; - for (const transition of transitions) { - const matches = transition.match(transitionRegex); + for (const transition of transitions) { + const matches = transition.match(transitionRegex); - if (!matches) { - throw new Error('Invalid schedule: transitions must be in format HH:mm/temperature (e.g. 12:00/15.5), ' + - 'found: ' + transition); - } + if (!matches) { + throw new Error( + 'Invalid schedule: transitions must be in format HH:mm/temperature (e.g. 12:00/15.5), ' + 'found: ' + transition, + ); + } + + const hour = parseInt(matches[1]); + const mins = parseInt(matches[2]); + const temp = parseFloat(matches[3]); - const hour = parseInt(matches[1]); - const mins = parseInt(matches[2]); - const temp = parseFloat(matches[3]); + if (temp < 4 || temp > 35) { + throw new Error(`Invalid schedule: temperature value must be between 4-35 (inclusive), found: ${temp}`); + } - if (temp < 4 || temp > 35) { - throw new Error(`Invalid schedule: temperature value must be between 4-35 (inclusive), found: ${temp}`); + payload.transitions.push({ + transitionTime: hour * 60 + mins, + heatSetpoint: Math.round(temp * 100), + }); } - payload.transitions.push({ - transitionTime: (hour * 60) + mins, - heatSetpoint: Math.round(temp * 100), - }); - } + if (payload.transitions[0].transitionTime !== 0) { + throw new Error('Invalid schedule: the first transition of each day should start at 00:00'); + } - if (payload.transitions[0].transitionTime !== 0) { - throw new Error('Invalid schedule: the first transition of each day should start at 00:00'); + await entity.command('hvacThermostat', 'setWeeklySchedule', payload, utils.getOptions(meta.mapped, entity)); } - - await entity.command('hvacThermostat', 'setWeeklySchedule', payload, utils.getOptions(meta.mapped, entity)); - } + }, }, - }]; + ]; return { exposes: [exposes], @@ -239,85 +266,108 @@ const sonoffExtend = { }; }, cyclicTimedIrrigation: (): ModernExtend => { - const exposes = e.composite('cyclic_timed_irrigation', 'cyclic_timed_irrigation', ea.ALL) + const exposes = e + .composite('cyclic_timed_irrigation', 'cyclic_timed_irrigation', ea.ALL) .withDescription('Smart water valve cycle timing irrigation') .withFeature(e.numeric('current_count', ea.STATE).withDescription('Number of times it has been executed').withUnit('times')) - .withFeature(e.numeric('total_number', ea.STATE_SET).withDescription('Total times of circulating irrigation').withUnit('tim' + - 'es').withValueMin(0).withValueMax(100)) - .withFeature(e.numeric('irrigation_duration', ea.STATE_SET).withDescription('Single irrigation duration').withUnit('second' + - 's').withValueMin(0).withValueMax(86400)) - .withFeature(e.numeric('irrigation_interval', ea.STATE_SET).withDescription('Time interval between two adjacent irrigatio' + - 'n').withUnit('seconds').withValueMin(0).withValueMax(86400)); - const fromZigbee: Fz.Converter[] = [{ - cluster: 'customClusterEwelink', - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - const attributeKey = 0x5008;// attr - if (attributeKey in msg.data ) { - // logger.debug(` from zigbee 0x5008 cluster ${msg.data[attributeKey]} `, NS); - // logger.debug(msg.data[attributeKey]); - const buffer = Buffer.from(msg.data[attributeKey]); - // logger.debug(`buffer====> ${buffer[0]} ${buffer[1]} ${buffer[2]} ${buffer[3]} ${buffer[4]} ${buffer[5]} `, NS); - // logger.debug(`buffer====> ${buffer[6]} ${buffer[7]} ${buffer[8]} ${buffer[9]} `, NS); - const currentCountBuffer = buffer[0]; - const totalNumberBuffer = buffer[1]; + .withFeature( + e + .numeric('total_number', ea.STATE_SET) + .withDescription('Total times of circulating irrigation') + .withUnit('tim' + 'es') + .withValueMin(0) + .withValueMax(100), + ) + .withFeature( + e + .numeric('irrigation_duration', ea.STATE_SET) + .withDescription('Single irrigation duration') + .withUnit('second' + 's') + .withValueMin(0) + .withValueMax(86400), + ) + .withFeature( + e + .numeric('irrigation_interval', ea.STATE_SET) + .withDescription('Time interval between two adjacent irrigatio' + 'n') + .withUnit('seconds') + .withValueMin(0) + .withValueMax(86400), + ); + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'customClusterEwelink', + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + const attributeKey = 0x5008; // attr + if (attributeKey in msg.data) { + // logger.debug(` from zigbee 0x5008 cluster ${msg.data[attributeKey]} `, NS); + // logger.debug(msg.data[attributeKey]); + const buffer = Buffer.from(msg.data[attributeKey]); + // logger.debug(`buffer====> ${buffer[0]} ${buffer[1]} ${buffer[2]} ${buffer[3]} ${buffer[4]} ${buffer[5]} `, NS); + // logger.debug(`buffer====> ${buffer[6]} ${buffer[7]} ${buffer[8]} ${buffer[9]} `, NS); + const currentCountBuffer = buffer[0]; + const totalNumberBuffer = buffer[1]; - const irrigationDurationBuffer = (buffer[2] <<24) | (buffer[3] << 16) | (buffer[4] << 8)|buffer[5]; + const irrigationDurationBuffer = (buffer[2] << 24) | (buffer[3] << 16) | (buffer[4] << 8) | buffer[5]; - const irrigationIntervalBuffer = (buffer[6] <<24) | (buffer[7] << 16) | (buffer[8] << 8)|buffer[9]; + const irrigationIntervalBuffer = (buffer[6] << 24) | (buffer[7] << 16) | (buffer[8] << 8) | buffer[9]; - // logger.debug(`currentCountBuffer ${currentCountBuffer}`, NS); - // logger.debug(`totalNumberOfTimesBuffer ${totalNumberBuffer}`, NS); - // logger.debug(`irrigationDurationBuffer ${irrigationDurationBuffer}`, NS); - // logger.debug(`irrigationIntervalBuffer ${irrigationIntervalBuffer}`, NS); + // logger.debug(`currentCountBuffer ${currentCountBuffer}`, NS); + // logger.debug(`totalNumberOfTimesBuffer ${totalNumberBuffer}`, NS); + // logger.debug(`irrigationDurationBuffer ${irrigationDurationBuffer}`, NS); + // logger.debug(`irrigationIntervalBuffer ${irrigationIntervalBuffer}`, NS); - return { - cyclic_timed_irrigation: { - current_count: currentCountBuffer, - total_number: totalNumberBuffer, - irrigation_duration: irrigationDurationBuffer, - irrigation_interval: irrigationIntervalBuffer, - }, - }; - } + return { + cyclic_timed_irrigation: { + current_count: currentCountBuffer, + total_number: totalNumberBuffer, + irrigation_duration: irrigationDurationBuffer, + irrigation_interval: irrigationIntervalBuffer, + }, + }; + } + }, }, - }]; - const toZigbee: Tz.Converter[] = [{ - key: ['cyclic_timed_irrigation'], - convertSet: async (entity, key, value, meta) => { - // logger.debug(`to zigbee cyclic_timed_irrigation ${key}`, NS); - // const currentCount:string = 'current_count'; - // logger.debug(`to zigbee cyclic_timed_irrigation ${value[currentCount as keyof typeof value]}`, NS); - const totalNumber:string = 'total_number'; - // logger.debug(`to zigbee cyclic_timed_irrigation ${value[totalNumber as keyof typeof value]}`, NS); - const irrigationDuration:string = 'irrigation_duration'; - // logger.debug(`to zigbee cyclic_timed_irrigation ${value[irrigationDuration as keyof typeof value]}`, NS); - const irrigationInterval:string = 'irrigation_interval'; - // logger.debug(`to zigbee cyclic_timed_irrigation ${value[irrigationInterval as keyof typeof value]}`, NS); + ]; + const toZigbee: Tz.Converter[] = [ + { + key: ['cyclic_timed_irrigation'], + convertSet: async (entity, key, value, meta) => { + // logger.debug(`to zigbee cyclic_timed_irrigation ${key}`, NS); + // const currentCount:string = 'current_count'; + // logger.debug(`to zigbee cyclic_timed_irrigation ${value[currentCount as keyof typeof value]}`, NS); + const totalNumber: string = 'total_number'; + // logger.debug(`to zigbee cyclic_timed_irrigation ${value[totalNumber as keyof typeof value]}`, NS); + const irrigationDuration: string = 'irrigation_duration'; + // logger.debug(`to zigbee cyclic_timed_irrigation ${value[irrigationDuration as keyof typeof value]}`, NS); + const irrigationInterval: string = 'irrigation_interval'; + // logger.debug(`to zigbee cyclic_timed_irrigation ${value[irrigationInterval as keyof typeof value]}`, NS); - const payloadValue = []; - payloadValue[0] = 0x0A; - payloadValue[1] = 0x00; - payloadValue[2] = value[totalNumber as keyof typeof value]; + const payloadValue = []; + payloadValue[0] = 0x0a; + payloadValue[1] = 0x00; + payloadValue[2] = value[totalNumber as keyof typeof value]; - payloadValue[3] = value[irrigationDuration as keyof typeof value] >> 24; - payloadValue[4] = value[irrigationDuration as keyof typeof value] >> 16; - payloadValue[5] = value[irrigationDuration as keyof typeof value] >> 8; - payloadValue[6] = value[irrigationDuration as keyof typeof value]; + payloadValue[3] = value[irrigationDuration as keyof typeof value] >> 24; + payloadValue[4] = value[irrigationDuration as keyof typeof value] >> 16; + payloadValue[5] = value[irrigationDuration as keyof typeof value] >> 8; + payloadValue[6] = value[irrigationDuration as keyof typeof value]; - payloadValue[7] = value[irrigationInterval as keyof typeof value] >> 24; - payloadValue[8] = value[irrigationInterval as keyof typeof value] >> 16; - payloadValue[9] = value[irrigationInterval as keyof typeof value] >> 8; - payloadValue[10] = value[irrigationInterval as keyof typeof value]; + payloadValue[7] = value[irrigationInterval as keyof typeof value] >> 24; + payloadValue[8] = value[irrigationInterval as keyof typeof value] >> 16; + payloadValue[9] = value[irrigationInterval as keyof typeof value] >> 8; + payloadValue[10] = value[irrigationInterval as keyof typeof value]; - const payload = {[0x5008]: {value: payloadValue, type: 0x42}}; - await entity.write('customClusterEwelink', payload, defaultResponseOptions); - return {state: {[key]: value}}; - }, - convertGet: async (entity, key, meta) => { - await entity.read('customClusterEwelink', [0x5008], defaultResponseOptions); + const payload = {[0x5008]: {value: payloadValue, type: 0x42}}; + await entity.write('customClusterEwelink', payload, defaultResponseOptions); + return {state: {[key]: value}}; + }, + convertGet: async (entity, key, meta) => { + await entity.read('customClusterEwelink', [0x5008], defaultResponseOptions); + }, }, - }]; + ]; return { exposes: [exposes], @@ -327,85 +377,108 @@ const sonoffExtend = { }; }, cyclicQuantitativeIrrigation: (): ModernExtend => { - const exposes = e.composite('cyclic_quantitative_irrigation', 'cyclic_quantitative_irrigation', ea.ALL) + const exposes = e + .composite('cyclic_quantitative_irrigation', 'cyclic_quantitative_irrigation', ea.ALL) .withDescription('Smart water valve circulating quantitative irrigation') .withFeature(e.numeric('current_count', ea.STATE).withDescription('Number of times it has been executed').withUnit('times')) - .withFeature(e.numeric('total_number', ea.STATE_SET).withDescription('Total times of circulating irrigation').withUnit('tim' + - 'es').withValueMin(0).withValueMax(100)) - .withFeature(e.numeric('irrigation_capacity', ea.STATE_SET).withDescription('Single irrigation capacity').withUnit('lite' + - 'r').withValueMin(0).withValueMax(6500)) - .withFeature(e.numeric('irrigation_interval', ea.STATE_SET).withDescription('Time interval between two adjacent irrigatio' + - 'n').withUnit('seconds').withValueMin(0).withValueMax(86400)); - const fromZigbee: Fz.Converter[] = [{ - cluster: 'customClusterEwelink', - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - const attributeKey = 0x5009;// attr - if (attributeKey in msg.data ) { - // logger.debug(` from zigbee 0x5009 cluster ${msg.data[attributeKey]} `, NS); - // logger.debug(msg.data[attributeKey]); - const buffer = Buffer.from(msg.data[attributeKey]); - // logger.debug(`buffer====> ${buffer[0]} ${buffer[1]} ${buffer[2]} ${buffer[3]} ${buffer[4]} ${buffer[5]} `, NS); - // logger.debug(`buffer====> ${buffer[6]} ${buffer[7]} ${buffer[8]} ${buffer[9]} `, NS); - const currentCountBuffer = buffer[0]; - const totalNumberBuffer = buffer[1]; + .withFeature( + e + .numeric('total_number', ea.STATE_SET) + .withDescription('Total times of circulating irrigation') + .withUnit('tim' + 'es') + .withValueMin(0) + .withValueMax(100), + ) + .withFeature( + e + .numeric('irrigation_capacity', ea.STATE_SET) + .withDescription('Single irrigation capacity') + .withUnit('lite' + 'r') + .withValueMin(0) + .withValueMax(6500), + ) + .withFeature( + e + .numeric('irrigation_interval', ea.STATE_SET) + .withDescription('Time interval between two adjacent irrigatio' + 'n') + .withUnit('seconds') + .withValueMin(0) + .withValueMax(86400), + ); + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'customClusterEwelink', + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + const attributeKey = 0x5009; // attr + if (attributeKey in msg.data) { + // logger.debug(` from zigbee 0x5009 cluster ${msg.data[attributeKey]} `, NS); + // logger.debug(msg.data[attributeKey]); + const buffer = Buffer.from(msg.data[attributeKey]); + // logger.debug(`buffer====> ${buffer[0]} ${buffer[1]} ${buffer[2]} ${buffer[3]} ${buffer[4]} ${buffer[5]} `, NS); + // logger.debug(`buffer====> ${buffer[6]} ${buffer[7]} ${buffer[8]} ${buffer[9]} `, NS); + const currentCountBuffer = buffer[0]; + const totalNumberBuffer = buffer[1]; - const irrigationCapacityBuffer = (buffer[2] <<24) | (buffer[3] << 16) | (buffer[4] << 8)|buffer[5]; + const irrigationCapacityBuffer = (buffer[2] << 24) | (buffer[3] << 16) | (buffer[4] << 8) | buffer[5]; - const irrigationIntervalBuffer = (buffer[6] <<24) | (buffer[7] << 16) | (buffer[8] << 8)|buffer[9]; + const irrigationIntervalBuffer = (buffer[6] << 24) | (buffer[7] << 16) | (buffer[8] << 8) | buffer[9]; - // logger.debug(`currentCountBuffer ${currentCountBuffer}`, NS); - // logger.debug(`totalNumberBuffer ${totalNumberBuffer}`, NS); - // logger.debug(`irrigationCapacityBuffer ${irrigationCapacityBuffer}`, NS); - // logger.debug(`irrigationIntervalBuffer ${irrigationIntervalBuffer}`, NS); + // logger.debug(`currentCountBuffer ${currentCountBuffer}`, NS); + // logger.debug(`totalNumberBuffer ${totalNumberBuffer}`, NS); + // logger.debug(`irrigationCapacityBuffer ${irrigationCapacityBuffer}`, NS); + // logger.debug(`irrigationIntervalBuffer ${irrigationIntervalBuffer}`, NS); - return { - cyclic_quantitative_irrigation: { - current_count: currentCountBuffer, - total_number: totalNumberBuffer, - irrigation_capacity: irrigationCapacityBuffer, - irrigation_interval: irrigationIntervalBuffer, - }, - }; - } + return { + cyclic_quantitative_irrigation: { + current_count: currentCountBuffer, + total_number: totalNumberBuffer, + irrigation_capacity: irrigationCapacityBuffer, + irrigation_interval: irrigationIntervalBuffer, + }, + }; + } + }, }, - }]; - const toZigbee: Tz.Converter[] = [{ - key: ['cyclic_quantitative_irrigation'], - convertSet: async (entity, key, value, meta) => { - // logger.debug(`to zigbee cyclic_Quantitative_irrigation ${key}`, NS); - // const currentCount:string = 'current_count'; - // logger.debug(`to zigbee cyclic_Quantitative_irrigation ${value[currentCount as keyof typeof value]}`, NS); - const totalNumber:string = 'total_number'; - // logger.debug(`to zigbee cyclic_Quantitative_irrigation ${value[totalNumber as keyof typeof value]}`, NS); - const irrigationCapacity:string = 'irrigation_capacity'; - // logger.debug(`to zigbee cyclic_Quantitative_irrigation ${value[irrigationCapacity as keyof typeof value]}`, NS); - const irrigationInterval:string = 'irrigation_interval'; - // logger.debug(`to zigbee cyclic_Quantitative_irrigation ${value[irrigationInterval as keyof typeof value]}`, NS); + ]; + const toZigbee: Tz.Converter[] = [ + { + key: ['cyclic_quantitative_irrigation'], + convertSet: async (entity, key, value, meta) => { + // logger.debug(`to zigbee cyclic_Quantitative_irrigation ${key}`, NS); + // const currentCount:string = 'current_count'; + // logger.debug(`to zigbee cyclic_Quantitative_irrigation ${value[currentCount as keyof typeof value]}`, NS); + const totalNumber: string = 'total_number'; + // logger.debug(`to zigbee cyclic_Quantitative_irrigation ${value[totalNumber as keyof typeof value]}`, NS); + const irrigationCapacity: string = 'irrigation_capacity'; + // logger.debug(`to zigbee cyclic_Quantitative_irrigation ${value[irrigationCapacity as keyof typeof value]}`, NS); + const irrigationInterval: string = 'irrigation_interval'; + // logger.debug(`to zigbee cyclic_Quantitative_irrigation ${value[irrigationInterval as keyof typeof value]}`, NS); - const payloadValue = []; - payloadValue[0] = 0x0A; - payloadValue[1] = 0x00; - payloadValue[2] = value[totalNumber as keyof typeof value]; + const payloadValue = []; + payloadValue[0] = 0x0a; + payloadValue[1] = 0x00; + payloadValue[2] = value[totalNumber as keyof typeof value]; - payloadValue[3] = value[irrigationCapacity as keyof typeof value] >> 24; - payloadValue[4] = value[irrigationCapacity as keyof typeof value] >> 16; - payloadValue[5] = value[irrigationCapacity as keyof typeof value] >> 8; - payloadValue[6] = value[irrigationCapacity as keyof typeof value]; + payloadValue[3] = value[irrigationCapacity as keyof typeof value] >> 24; + payloadValue[4] = value[irrigationCapacity as keyof typeof value] >> 16; + payloadValue[5] = value[irrigationCapacity as keyof typeof value] >> 8; + payloadValue[6] = value[irrigationCapacity as keyof typeof value]; - payloadValue[7] = value[irrigationInterval as keyof typeof value] >> 24; - payloadValue[8] = value[irrigationInterval as keyof typeof value] >> 16; - payloadValue[9] = value[irrigationInterval as keyof typeof value] >> 8; - payloadValue[10] = value[irrigationInterval as keyof typeof value]; + payloadValue[7] = value[irrigationInterval as keyof typeof value] >> 24; + payloadValue[8] = value[irrigationInterval as keyof typeof value] >> 16; + payloadValue[9] = value[irrigationInterval as keyof typeof value] >> 8; + payloadValue[10] = value[irrigationInterval as keyof typeof value]; - const payload = {[0x5009]: {value: payloadValue, type: 0x42}}; - await entity.write('customClusterEwelink', payload, defaultResponseOptions); - return {state: {[key]: value}}; - }, - convertGet: async (entity, key, meta) => { - await entity.read('customClusterEwelink', [0x5009], defaultResponseOptions); + const payload = {[0x5009]: {value: payloadValue, type: 0x42}}; + await entity.write('customClusterEwelink', payload, defaultResponseOptions); + return {state: {[key]: value}}; + }, + convertGet: async (entity, key, meta) => { + await entity.read('customClusterEwelink', [0x5009], defaultResponseOptions); + }, }, - }]; + ]; return { exposes: [exposes], @@ -417,46 +490,50 @@ const sonoffExtend = { externalSwitchTriggerMode: (): ModernExtend => { const clusterName = 'customClusterEwelink'; const attributeName = 'externalTriggerMode'; - const exposes = e.enum('external_trigger_mode', ea.ALL, ['edge', 'pulse', - 'following(off)', 'following(on)']).withDescription('External trigger mode, which can be one of edge, pulse, ' + - 'following(off), following(on). The appropriate triggering mode can be selected according to the type of ' + - 'external switch to achieve a better use experience.'); - const fromZigbee: Fz.Converter[] = [{ - cluster: clusterName, - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - const lookup:KeyValue = {'edge': 0, 'pulse': 1, 'following(off)': 2, 'following(on)': 130}; - // logger.debug(`from zigbee msg.data['externalTriggerMode'] ${msg.data['externalTriggerMode']}`, NS); - if (msg.data.hasOwnProperty('externalTriggerMode')) { - let switchType = 'edge'; - for (const name in lookup) { - if (lookup[name] === msg.data['externalTriggerMode']) { - switchType = name; - break; + const exposes = e + .enum('external_trigger_mode', ea.ALL, ['edge', 'pulse', 'following(off)', 'following(on)']) + .withDescription( + 'External trigger mode, which can be one of edge, pulse, ' + + 'following(off), following(on). The appropriate triggering mode can be selected according to the type of ' + + 'external switch to achieve a better use experience.', + ); + const fromZigbee: Fz.Converter[] = [ + { + cluster: clusterName, + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + const lookup: KeyValue = {edge: 0, pulse: 1, 'following(off)': 2, 'following(on)': 130}; + // logger.debug(`from zigbee msg.data['externalTriggerMode'] ${msg.data['externalTriggerMode']}`, NS); + if (msg.data.hasOwnProperty('externalTriggerMode')) { + let switchType = 'edge'; + for (const name in lookup) { + if (lookup[name] === msg.data['externalTriggerMode']) { + switchType = name; + break; + } } + // logger.debug(`form zigbee switchType ${switchType}`, NS); + return {['external_trigger_mode']: switchType}; } - // logger.debug(`form zigbee switchType ${switchType}`, NS); - return {['external_trigger_mode']: switchType}; - } - }, - }]; - const toZigbee: Tz.Converter[] = [{ - key: ['external_trigger_mode'], - convertSet: async (entity, key, value, meta) => { - utils.assertString(value, key); - value = value.toLowerCase(); - const lookup = {'edge': 0, 'pulse': 1, 'following(off)': 2, 'following(on)': 130}; - const tmpValue = utils.getFromLookup(value, lookup); - await entity.write( - clusterName, - {[attributeName]: tmpValue}, - defaultResponseOptions); - return {state: {[key]: value}}; + }, }, - convertGet: async (entity, key, meta) => { - await entity.read(clusterName, [attributeName], defaultResponseOptions); + ]; + const toZigbee: Tz.Converter[] = [ + { + key: ['external_trigger_mode'], + convertSet: async (entity, key, value, meta) => { + utils.assertString(value, key); + value = value.toLowerCase(); + const lookup = {edge: 0, pulse: 1, 'following(off)': 2, 'following(on)': 130}; + const tmpValue = utils.getFromLookup(value, lookup); + await entity.write(clusterName, {[attributeName]: tmpValue}, defaultResponseOptions); + return {state: {[key]: value}}; + }, + convertGet: async (entity, key, meta) => { + await entity.read(clusterName, [attributeName], defaultResponseOptions); + }, }, - }]; + ]; return { exposes: [exposes], fromZigbee, @@ -489,10 +566,7 @@ const definitions: Definition[] = [ model: 'ZBMINI-L', vendor: 'SONOFF', description: 'Zigbee smart switch (no neutral)', - extend: [ - onOff(), - ota(), - ], + extend: [onOff(), ota()], configure: async (device, coordinatorEndpoint) => { // Unbind genPollCtrl to prevent device from sending checkin message. // Zigbee-herdsmans responds to the checkin message which causes the device @@ -508,10 +582,7 @@ const definitions: Definition[] = [ model: 'ZBMINIL2', vendor: 'SONOFF', description: 'Zigbee smart switch (no neutral)', - extend: [ - onOff(), - ota(), - ], + extend: [onOff(), ota()], configure: async (device, coordinatorEndpoint) => { // Unbind genPollCtrl to prevent device from sending checkin message. // Zigbee-herdsmans responds to the checkin message which causes the device @@ -547,9 +618,10 @@ const definitions: Definition[] = [ // ModelID is from the temperature/humidity sensor (SNZB-02) but this is SNZB-04, wrong modelID in firmware? // https://github.com/Koenkk/zigbee-herdsman-converters/issues/1449 { - type: 'EndDevice', manufacturerName: 'eWeLink', modelID: 'TH01', endpoints: [ - {ID: 1, profileID: 260, deviceID: 1026, inputClusters: [0, 3, 1280, 1], outputClusters: [3]}, - ], + type: 'EndDevice', + manufacturerName: 'eWeLink', + modelID: 'TH01', + endpoints: [{ID: 1, profileID: 260, deviceID: 1026, inputClusters: [0, 3, 1280, 1], outputClusters: [3]}], }, ], zigbeeModel: ['DS01', 'SNZB-04'], @@ -588,19 +660,22 @@ const definitions: Definition[] = [ // ModelID is from the button (SNZB-01) but this is SNZB-02, wrong modelID in firmware? // https://github.com/Koenkk/zigbee2mqtt/issues/4338 { - type: 'EndDevice', manufacturerName: 'eWeLink', modelID: 'WB01', endpoints: [ - {ID: 1, profileID: 260, deviceID: 770, inputClusters: [0, 3, 1026, 1029, 1], outputClusters: [3]}, - ], + type: 'EndDevice', + manufacturerName: 'eWeLink', + modelID: 'WB01', + endpoints: [{ID: 1, profileID: 260, deviceID: 770, inputClusters: [0, 3, 1026, 1029, 1], outputClusters: [3]}], }, { - type: 'EndDevice', manufacturerName: 'eWeLink', modelID: '66666', endpoints: [ - {ID: 1, profileID: 260, deviceID: 770, inputClusters: [0, 3, 1026, 1029, 1], outputClusters: [3]}, - ], + type: 'EndDevice', + manufacturerName: 'eWeLink', + modelID: '66666', + endpoints: [{ID: 1, profileID: 260, deviceID: 770, inputClusters: [0, 3, 1026, 1029, 1], outputClusters: [3]}], }, { - type: 'EndDevice', manufacturerName: 'eWeLink', modelID: 'DS01', endpoints: [ - {ID: 1, profileID: 260, deviceID: 770, inputClusters: [0, 3, 1026, 1029, 1], outputClusters: [3]}, - ], + type: 'EndDevice', + manufacturerName: 'eWeLink', + modelID: 'DS01', + endpoints: [{ID: 1, profileID: 260, deviceID: 770, inputClusters: [0, 3, 1026, 1029, 1], outputClusters: [3]}], }, ], zigbeeModel: ['TH01'], @@ -620,7 +695,8 @@ const definitions: Definition[] = [ await reporting.humidity(endpoint, {min: 30, max: constants.repInterval.MINUTES_5, change: 100}); await reporting.batteryVoltage(endpoint, {min: 3600, max: 7200}); await reporting.batteryPercentageRemaining(endpoint, {min: 3600, max: 7200}); - } catch (e) {/* Not required for all: https://github.com/Koenkk/zigbee2mqtt/issues/5562 */ + } catch (e) { + /* Not required for all: https://github.com/Koenkk/zigbee2mqtt/issues/5562 */ logger.error(`Configure failed: ${e}`, NS); } }, @@ -641,15 +717,17 @@ const definitions: Definition[] = [ { fingerprint: [ { - type: 'EndDevice', manufacturerName: 'eWeLink', modelID: '66666', endpoints: [ - {ID: 1, profileID: 260, deviceID: 1026, inputClusters: [0, 3, 1280, 1], outputClusters: [3]}, - ], + type: 'EndDevice', + manufacturerName: 'eWeLink', + modelID: '66666', + endpoints: [{ID: 1, profileID: 260, deviceID: 1026, inputClusters: [0, 3, 1280, 1], outputClusters: [3]}], }, { // SNZB-O3 OUVOPO Wireless Motion Sensor (2023) - type: 'EndDevice', manufacturerName: 'eWeLink', modelID: 'SNZB-03', endpoints: [ - {ID: 1, profileID: 260, deviceID: 1026, inputClusters: [0, 3, 1280, 1], outputClusters: [3]}, - ], + type: 'EndDevice', + manufacturerName: 'eWeLink', + modelID: 'SNZB-03', + endpoints: [{ID: 1, profileID: 260, deviceID: 1026, inputClusters: [0, 3, 1280, 1], outputClusters: [3]}], }, ], zigbeeModel: ['MS01', 'MSO1'], @@ -682,10 +760,7 @@ const definitions: Definition[] = [ model: 'S40ZBTPB', vendor: 'SONOFF', description: '15A Zigbee smart plug', - extend: [ - onOff({powerOnBehavior: false, skipDuplicateTransaction: true}), - ota(), - ], + extend: [onOff({powerOnBehavior: false, skipDuplicateTransaction: true}), ota()], }, { zigbeeModel: ['DONGLE-E_R'], @@ -757,7 +832,7 @@ const definitions: Definition[] = [ extend: [ binary({ name: 'tamper', - cluster: 0xFC11, + cluster: 0xfc11, attribute: {ID: 0x2000, type: 0x20}, description: 'Tamper-proof status', valueOn: [true, 0x01], @@ -792,8 +867,8 @@ const definitions: Definition[] = [ }), enumLookup({ name: 'illumination', - lookup: {'dim': 0, 'bright': 1}, - cluster: 0xFC11, + lookup: {dim: 0, bright: 1}, + cluster: 0xfc11, attribute: {ID: 0x2001, type: 0x20}, zigbeeCommandOptions: {manufacturerCode: Zcl.ManufacturerCode.SHENZHEN_COOLKIT_TECHNOLOGY_CO_LTD}, description: 'Only updated when occupancy is detected', @@ -813,10 +888,7 @@ const definitions: Definition[] = [ model: 'SNZB-05P', vendor: 'SONOFF', description: 'Zigbee water sensor', - extend: [ - battery(), - iasZoneAlarm({zoneType: 'water_leak', zoneAttributes: ['alarm_1', 'battery_low']}), - ], + extend: [battery(), iasZoneAlarm({zoneType: 'water_leak', zoneAttributes: ['alarm_1', 'battery_low']})], }, { zigbeeModel: ['SNZB-06P'], @@ -836,15 +908,15 @@ const definitions: Definition[] = [ }), enumLookup({ name: 'occupancy_sensitivity', - lookup: {'low': 1, 'medium': 2, 'high': 3}, + lookup: {low: 1, medium: 2, high: 3}, cluster: 0x0406, attribute: {ID: 0x0022, type: 0x20}, description: 'Sensitivity of human presence detection', }), enumLookup({ name: 'illumination', - lookup: {'dim': 0, 'bright': 1}, - cluster: 0xFC11, + lookup: {dim: 0, bright: 1}, + cluster: 0xfc11, attribute: {ID: 0x2001, type: 0x20}, description: 'Only updated when occupancy is detected', zigbeeCommandOptions: {manufacturerCode: Zcl.ManufacturerCode.SHENZHEN_COOLKIT_TECHNOLOGY_CO_LTD}, @@ -859,7 +931,8 @@ const definitions: Definition[] = [ vendor: 'SONOFF', description: 'Zigbee thermostatic radiator valve', exposes: [ - e.climate() + e + .climate() .withSetpoint('occupied_heating_setpoint', 4, 35, 0.5) .withLocalTemperature() .withLocalTemperatureCalibration(-7.0, 7.0, 0.2) @@ -867,10 +940,7 @@ const definitions: Definition[] = [ .withRunningState(['idle', 'heat'], ea.STATE_GET), e.battery(), ], - fromZigbee: [ - fz.thermostat, - fz.battery, - ], + fromZigbee: [fz.thermostat, fz.battery], toZigbee: [ tz.thermostat_local_temperature, tz.thermostat_local_temperature_calibration, @@ -879,28 +949,25 @@ const definitions: Definition[] = [ tz.thermostat_running_state, ], extend: [ - deviceAddCustomCluster( - 'customSonoffTrvzb', - { - ID: 0xfc11, - attributes: { - childLock: {ID: 0x0000, type: Zcl.DataType.BOOLEAN}, - tamper: {ID: 0x2000, type: Zcl.DataType.UINT8}, - illumination: {ID: 0x2001, type: Zcl.DataType.UINT8}, - openWindow: {ID: 0x6000, type: Zcl.DataType.BOOLEAN}, - frostProtectionTemperature: {ID: 0x6002, type: Zcl.DataType.INT16}, - idleSteps: {ID: 0x6003, type: Zcl.DataType.UINT16}, - closingSteps: {ID: 0x6004, type: Zcl.DataType.UINT16}, - valveOpeningLimitVoltage: {ID: 0x6005, type: Zcl.DataType.UINT16}, - valveClosingLimitVoltage: {ID: 0x6006, type: Zcl.DataType.UINT16}, - valveMotorRunningVoltage: {ID: 0x6007, type: Zcl.DataType.UINT16}, - valveOpeningDegree: {ID: 0x600B, type: Zcl.DataType.UINT8}, - valveClosingDegree: {ID: 0x600C, type: Zcl.DataType.UINT8}, - }, - commands: {}, - commandsResponse: {}, + deviceAddCustomCluster('customSonoffTrvzb', { + ID: 0xfc11, + attributes: { + childLock: {ID: 0x0000, type: Zcl.DataType.BOOLEAN}, + tamper: {ID: 0x2000, type: Zcl.DataType.UINT8}, + illumination: {ID: 0x2001, type: Zcl.DataType.UINT8}, + openWindow: {ID: 0x6000, type: Zcl.DataType.BOOLEAN}, + frostProtectionTemperature: {ID: 0x6002, type: Zcl.DataType.INT16}, + idleSteps: {ID: 0x6003, type: Zcl.DataType.UINT16}, + closingSteps: {ID: 0x6004, type: Zcl.DataType.UINT16}, + valveOpeningLimitVoltage: {ID: 0x6005, type: Zcl.DataType.UINT16}, + valveClosingLimitVoltage: {ID: 0x6006, type: Zcl.DataType.UINT16}, + valveMotorRunningVoltage: {ID: 0x6007, type: Zcl.DataType.UINT16}, + valveOpeningDegree: {ID: 0x600b, type: Zcl.DataType.UINT8}, + valveClosingDegree: {ID: 0x600c, type: Zcl.DataType.UINT8}, }, - ), + commands: {}, + commandsResponse: {}, + }), binary({ name: 'child_lock', cluster: 'customSonoffTrvzb', @@ -921,8 +988,8 @@ const definitions: Definition[] = [ name: 'frost_protection_temperature', cluster: 'customSonoffTrvzb', attribute: 'frostProtectionTemperature', - description: 'Minimum temperature at which to automatically turn on the radiator, ' + - 'if system mode is off, to prevent pipes freezing.', + description: + 'Minimum temperature at which to automatically turn on the radiator, ' + 'if system mode is off, to prevent pipes freezing.', valueMin: 4.0, valueMax: 35.0, valueStep: 0.5, @@ -972,7 +1039,8 @@ const definitions: Definition[] = [ cluster: 'customSonoffTrvzb', attribute: 'valveOpeningDegree', entityCategory: 'config', - description: 'Valve open position (percentage) control. ' + + description: + 'Valve open position (percentage) control. ' + 'If the opening degree is set to 100%, the valve is fully open when it is opened. ' + 'If the opening degree is set to 0%, the valve is fully closed when it is opened, ' + 'and the default value is 100%. ' + @@ -987,7 +1055,8 @@ const definitions: Definition[] = [ cluster: 'customSonoffTrvzb', attribute: 'valveClosingDegree', entityCategory: 'config', - description: 'Valve closed position (percentage) control. ' + + description: + 'Valve closed position (percentage) control. ' + 'If the closing degree is set to 100%, the valve is fully closed when it is closed. ' + 'If the closing degree is set to 0%, the valve is fully opened when it is closed, ' + 'and the default value is 100%. ' + @@ -1008,7 +1077,7 @@ const definitions: Definition[] = [ await reporting.thermostatOccupiedHeatingSetpoint(endpoint); await reporting.thermostatSystemMode(endpoint); await endpoint.read('hvacThermostat', ['localTemperatureCalibration']); - await endpoint.read(0xFC11, [0x0000, 0x6000, 0x6002, 0x6003, 0x6004, 0x6005, 0x6006, 0x6007]); + await endpoint.read(0xfc11, [0x0000, 0x6000, 0x6002, 0x6003, 0x6004, 0x6005, 0x6006, 0x6007]); }, }, { @@ -1030,12 +1099,8 @@ const definitions: Definition[] = [ model: 'SWV', vendor: 'SONOFF', description: 'Zigbee smart water valve', - fromZigbee: [ - fz.flow, - ], - exposes: [ - e.numeric('flow', ea.STATE).withDescription('Current water flow').withUnit('m³/h'), - ], + fromZigbee: [fz.flow], + exposes: [e.numeric('flow', ea.STATE).withDescription('Current water flow').withUnit('m³/h')], extend: [ ota(), battery(), @@ -1047,9 +1112,9 @@ const definitions: Definition[] = [ sonoffExtend.addCustomClusterEwelink(), enumLookup({ name: 'current_device_status', - lookup: {'normal_state': 0, 'water_shortage': 1, 'water_leakage': 2, 'water_shortage & water_leakage': 3}, + lookup: {normal_state: 0, water_shortage: 1, water_leakage: 2, 'water_shortage & water_leakage': 3}, cluster: 'customClusterEwelink', - attribute: {ID: 0x500C, type: 0x20}, + attribute: {ID: 0x500c, type: 0x20}, description: 'The water valve is in normal state, water shortage or water leakage', access: 'STATE_GET', }), @@ -1061,7 +1126,7 @@ const definitions: Definition[] = [ await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'genOnOff']); await reporting.bind(endpoint, coordinatorEndpoint, ['msFlowMeasurement']); await reporting.onOff(endpoint, {min: 1, max: 1800, change: 0}); - await endpoint.read('customClusterEwelink', [0x500C]); + await endpoint.read('customClusterEwelink', [0x500c]); }, }, { diff --git a/src/devices/sowilo.ts b/src/devices/sowilo.ts index 5bc5abde91a8a..4a313f745765a 100644 --- a/src/devices/sowilo.ts +++ b/src/devices/sowilo.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/spotmau.ts b/src/devices/spotmau.ts index d71d77eb106af..a2930fa22531f 100644 --- a/src/devices/spotmau.ts +++ b/src/devices/spotmau.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {deviceEndpoints, onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { @@ -17,10 +17,7 @@ const definitions: Definition[] = [ model: 'SP-PS2-02', vendor: 'Spotmau', description: 'Smart wall switch - 2 gang', - extend: [ - deviceEndpoints({endpoints: {'left': 16, 'right': 17}}), - onOff({endpointNames: ['left', 'right']}), - ], + extend: [deviceEndpoints({endpoints: {left: 16, right: 17}}), onOff({endpointNames: ['left', 'right']})], }, ]; diff --git a/src/devices/stelpro.ts b/src/devices/stelpro.ts index de0809fd9ae42..4f5aff7e19b82 100644 --- a/src/devices/stelpro.ts +++ b/src/devices/stelpro.ts @@ -1,7 +1,7 @@ -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; import * as reporting from '../lib/reporting'; import {Definition, Fz} from '../lib/types'; const e = exposes.presets; @@ -35,12 +35,28 @@ const definitions: Definition[] = [ vendor: 'Stelpro', description: 'Hilo thermostat', fromZigbee: [fz.stelpro_thermostat, fz.hvac_user_interface, fzLocal.power, fzLocal.energy], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_occupancy, tz.thermostat_occupied_heating_setpoint, - tz.thermostat_temperature_display_mode, tz.thermostat_keypad_lockout, tz.thermostat_system_mode, - tz.thermostat_running_state, tz.stelpro_thermostat_outdoor_temperature], - exposes: [e.local_temperature(), e.keypad_lockout(), e.power(), e.energy(), - e.climate().withSetpoint('occupied_heating_setpoint', 5, 30, 0.5).withLocalTemperature() - .withSystemMode(['heat']).withRunningState(['idle', 'heat'])], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_occupancy, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_temperature_display_mode, + tz.thermostat_keypad_lockout, + tz.thermostat_system_mode, + tz.thermostat_running_state, + tz.stelpro_thermostat_outdoor_temperature, + ], + exposes: [ + e.local_temperature(), + e.keypad_lockout(), + e.power(), + e.energy(), + e + .climate() + .withSetpoint('occupied_heating_setpoint', 5, 30, 0.5) + .withLocalTemperature() + .withSystemMode(['heat']) + .withRunningState(['idle', 'heat']), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(25); const binds = ['genBasic', 'genIdentify', 'genGroups', 'hvacThermostat', 'hvacUserInterfaceCfg', 'msTemperatureMeasurement']; @@ -61,12 +77,27 @@ const definitions: Definition[] = [ vendor: 'Stelpro', description: 'Ki convector, line-voltage thermostat', fromZigbee: [legacy.fz.stelpro_thermostat, legacy.fz.hvac_user_interface], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_occupancy, tz.thermostat_occupied_heating_setpoint, - tz.thermostat_temperature_display_mode, tz.thermostat_keypad_lockout, tz.thermostat_system_mode, tz.thermostat_running_state, - tz.stelpro_thermostat_outdoor_temperature], - exposes: [e.local_temperature(), e.keypad_lockout(), - e.climate().withSetpoint('occupied_heating_setpoint', 5, 30, 0.5).withLocalTemperature() - .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat']).withPiHeatingDemand()], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_occupancy, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_temperature_display_mode, + tz.thermostat_keypad_lockout, + tz.thermostat_system_mode, + tz.thermostat_running_state, + tz.stelpro_thermostat_outdoor_temperature, + ], + exposes: [ + e.local_temperature(), + e.keypad_lockout(), + e + .climate() + .withSetpoint('occupied_heating_setpoint', 5, 30, 0.5) + .withLocalTemperature() + .withSystemMode(['off', 'auto', 'heat']) + .withRunningState(['idle', 'heat']) + .withPiHeatingDemand(), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(25); const binds = ['genBasic', 'genIdentify', 'genGroups', 'hvacThermostat', 'hvacUserInterfaceCfg', 'msTemperatureMeasurement']; @@ -79,12 +110,14 @@ const definitions: Definition[] = [ await reporting.thermostatPIHeatingDemand(endpoint); await reporting.thermostatKeypadLockMode(endpoint); // cluster 0x0201 attribute 0x401c - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'StelproSystemMode', - minimumReportInterval: constants.repInterval.MINUTE, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 1, - }]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: 'StelproSystemMode', + minimumReportInterval: constants.repInterval.MINUTE, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 1, + }, + ]); }, }, { @@ -93,12 +126,27 @@ const definitions: Definition[] = [ vendor: 'Stelpro', description: 'Ki, line-voltage thermostat', fromZigbee: [legacy.fz.stelpro_thermostat, legacy.fz.hvac_user_interface, fz.humidity], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_occupancy, tz.thermostat_occupied_heating_setpoint, - tz.thermostat_temperature_display_mode, tz.thermostat_keypad_lockout, tz.thermostat_system_mode, - tz.thermostat_running_state, tz.stelpro_thermostat_outdoor_temperature], - exposes: [e.local_temperature(), e.keypad_lockout(), e.humidity(), - e.climate().withSetpoint('occupied_heating_setpoint', 5, 30, 0.5).withLocalTemperature() - .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat'])], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_occupancy, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_temperature_display_mode, + tz.thermostat_keypad_lockout, + tz.thermostat_system_mode, + tz.thermostat_running_state, + tz.stelpro_thermostat_outdoor_temperature, + ], + exposes: [ + e.local_temperature(), + e.keypad_lockout(), + e.humidity(), + e + .climate() + .withSetpoint('occupied_heating_setpoint', 5, 30, 0.5) + .withLocalTemperature() + .withSystemMode(['off', 'auto', 'heat']) + .withRunningState(['idle', 'heat']), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(25); const binds = ['genBasic', 'genIdentify', 'genGroups', 'hvacThermostat', 'hvacUserInterfaceCfg', 'msTemperatureMeasurement']; @@ -111,12 +159,14 @@ const definitions: Definition[] = [ await reporting.thermostatPIHeatingDemand(endpoint); await reporting.thermostatKeypadLockMode(endpoint); // cluster 0x0201 attribute 0x401c - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'StelproSystemMode', - minimumReportInterval: constants.repInterval.MINUTE, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 1, - }]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: 'StelproSystemMode', + minimumReportInterval: constants.repInterval.MINUTE, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 1, + }, + ]); }, }, { @@ -125,16 +175,38 @@ const definitions: Definition[] = [ vendor: 'Stelpro', description: 'Maestro, line-voltage thermostat', fromZigbee: [legacy.fz.stelpro_thermostat, legacy.fz.hvac_user_interface, fz.humidity], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_occupancy, tz.thermostat_occupied_heating_setpoint, - tz.thermostat_temperature_display_mode, tz.thermostat_keypad_lockout, tz.thermostat_system_mode, tz.thermostat_running_state, - tz.stelpro_thermostat_outdoor_temperature], - exposes: [e.local_temperature(), e.keypad_lockout(), e.humidity(), - e.climate().withSetpoint('occupied_heating_setpoint', 5, 30, 0.5).withLocalTemperature() - .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat'])], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_occupancy, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_temperature_display_mode, + tz.thermostat_keypad_lockout, + tz.thermostat_system_mode, + tz.thermostat_running_state, + tz.stelpro_thermostat_outdoor_temperature, + ], + exposes: [ + e.local_temperature(), + e.keypad_lockout(), + e.humidity(), + e + .climate() + .withSetpoint('occupied_heating_setpoint', 5, 30, 0.5) + .withLocalTemperature() + .withSystemMode(['off', 'auto', 'heat']) + .withRunningState(['idle', 'heat']), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(25); - const binds = ['genBasic', 'genIdentify', 'genGroups', 'hvacThermostat', 'hvacUserInterfaceCfg', 'msRelativeHumidity', - 'msTemperatureMeasurement']; + const binds = [ + 'genBasic', + 'genIdentify', + 'genGroups', + 'hvacThermostat', + 'hvacUserInterfaceCfg', + 'msRelativeHumidity', + 'msTemperatureMeasurement', + ]; await reporting.bind(endpoint, coordinatorEndpoint, binds); // Those exact parameters (min/max/change) are required for reporting to work with Stelpro Maestro @@ -145,11 +217,14 @@ const definitions: Definition[] = [ await reporting.thermostatPIHeatingDemand(endpoint); await reporting.thermostatKeypadLockMode(endpoint); // cluster 0x0201 attribute 0x401c - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'StelproSystemMode', - minimumReportInterval: constants.repInterval.MINUTE, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 1}]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: 'StelproSystemMode', + minimumReportInterval: constants.repInterval.MINUTE, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 1, + }, + ]); }, }, { @@ -158,11 +233,24 @@ const definitions: Definition[] = [ vendor: 'Stelpro', description: 'ORLÉANS fan heater', fromZigbee: [fz.stelpro_thermostat, fz.hvac_user_interface], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_occupied_heating_setpoint, - tz.thermostat_temperature_display_mode, tz.thermostat_keypad_lockout, tz.thermostat_system_mode, tz.thermostat_running_state], - exposes: [e.local_temperature(), e.keypad_lockout(), - e.climate().withSetpoint('occupied_heating_setpoint', 5, 30, 0.5).withLocalTemperature() - .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat'])], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_temperature_display_mode, + tz.thermostat_keypad_lockout, + tz.thermostat_system_mode, + tz.thermostat_running_state, + ], + exposes: [ + e.local_temperature(), + e.keypad_lockout(), + e + .climate() + .withSetpoint('occupied_heating_setpoint', 5, 30, 0.5) + .withLocalTemperature() + .withSystemMode(['off', 'auto', 'heat']) + .withRunningState(['idle', 'heat']), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(25); const binds = ['genBasic', 'genIdentify', 'genGroups', 'hvacThermostat', 'hvacUserInterfaceCfg', 'msTemperatureMeasurement']; @@ -175,11 +263,14 @@ const definitions: Definition[] = [ await reporting.thermostatPIHeatingDemand(endpoint); await reporting.thermostatKeypadLockMode(endpoint); // cluster 0x0201 attribute 0x401c - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'StelproSystemMode', - minimumReportInterval: constants.repInterval.MINUTE, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 1}]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: 'StelproSystemMode', + minimumReportInterval: constants.repInterval.MINUTE, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 1, + }, + ]); }, }, { @@ -188,16 +279,38 @@ const definitions: Definition[] = [ vendor: 'Stelpro', description: 'Maestro, line-voltage thermostat', fromZigbee: [legacy.fz.stelpro_thermostat, legacy.fz.hvac_user_interface, fz.humidity], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_occupancy, tz.thermostat_occupied_heating_setpoint, - tz.thermostat_temperature_display_mode, tz.thermostat_keypad_lockout, tz.thermostat_system_mode, tz.thermostat_running_state, - tz.stelpro_thermostat_outdoor_temperature], - exposes: [e.local_temperature(), e.keypad_lockout(), e.humidity(), - e.climate().withSetpoint('occupied_heating_setpoint', 5, 30, 0.5).withLocalTemperature() - .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat'])], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_occupancy, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_temperature_display_mode, + tz.thermostat_keypad_lockout, + tz.thermostat_system_mode, + tz.thermostat_running_state, + tz.stelpro_thermostat_outdoor_temperature, + ], + exposes: [ + e.local_temperature(), + e.keypad_lockout(), + e.humidity(), + e + .climate() + .withSetpoint('occupied_heating_setpoint', 5, 30, 0.5) + .withLocalTemperature() + .withSystemMode(['off', 'auto', 'heat']) + .withRunningState(['idle', 'heat']), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(25); - const binds = ['genBasic', 'genIdentify', 'genGroups', 'hvacThermostat', 'hvacUserInterfaceCfg', 'msRelativeHumidity', - 'msTemperatureMeasurement']; + const binds = [ + 'genBasic', + 'genIdentify', + 'genGroups', + 'hvacThermostat', + 'hvacUserInterfaceCfg', + 'msRelativeHumidity', + 'msTemperatureMeasurement', + ]; await reporting.bind(endpoint, coordinatorEndpoint, binds); // Those exact parameters (min/max/change) are required for reporting to work with Stelpro Maestro @@ -208,11 +321,14 @@ const definitions: Definition[] = [ await reporting.thermostatPIHeatingDemand(endpoint); await reporting.thermostatKeypadLockMode(endpoint); // cluster 0x0201 attribute 0x401c - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'StelproSystemMode', - minimumReportInterval: constants.repInterval.MINUTE, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 1}]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: 'StelproSystemMode', + minimumReportInterval: constants.repInterval.MINUTE, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 1, + }, + ]); }, }, ]; diff --git a/src/devices/sunricher.ts b/src/devices/sunricher.ts index 82b4ca32c870c..f1ea784cd99de 100644 --- a/src/devices/sunricher.ts +++ b/src/devices/sunricher.ts @@ -1,13 +1,11 @@ import {Zcl} from 'zigbee-herdsman'; -import * as exposes from '../lib/exposes'; + import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; -import * as globalStore from '../lib/store'; import * as constants from '../lib/constants'; -import * as utils from '../lib/utils'; -import {Definition, Fz, Zh} from '../lib/types'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; +import {logger} from '../lib/logger'; import { deviceEndpoints, electricityMeter, @@ -24,7 +22,10 @@ import { commandsColorCtrl, commandsScenes, } from '../lib/modernExtend'; -import {logger} from '../lib/logger'; +import * as reporting from '../lib/reporting'; +import * as globalStore from '../lib/store'; +import {Definition, Fz, Zh} from '../lib/types'; +import * as utils from '../lib/utils'; const NS = 'zhc:sunricher'; const e = exposes.presets; @@ -64,10 +65,12 @@ const fzLocal = { async function syncTime(endpoint: Zh.Endpoint) { try { - const time = Math.round(((new Date()).getTime() - constants.OneJanuary2000) / 1000 + ((new Date()).getTimezoneOffset() * -1) * 60); + const time = Math.round((new Date().getTime() - constants.OneJanuary2000) / 1000 + new Date().getTimezoneOffset() * -1 * 60); const values = {time: time}; await endpoint.write('genTime', values); - } catch (error) {/* Do nothing*/} + } catch (error) { + /* Do nothing*/ + } } const definitions: Definition[] = [ @@ -105,20 +108,14 @@ const definitions: Definition[] = [ model: 'SR-ZG9023A-EU', vendor: 'Sunricher', description: '4 ports switch with 2 usb ports (no metering)', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4, 'l5': 5}}), - onOff({endpointNames: ['l1', 'l2', 'l3', 'l4', 'l5']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2, l3: 3, l4: 4, l5: 5}}), onOff({endpointNames: ['l1', 'l2', 'l3', 'l4', 'l5']})], }, { zigbeeModel: ['ON/OFF(2CH)'], model: 'UP-SA-9127D', vendor: 'Sunricher', description: 'LED-Trading 2 channel AC switch', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2}}), - onOff({endpointNames: ['l1', 'l2']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2}}), onOff({endpointNames: ['l1', 'l2']})], }, { fingerprint: [{modelID: 'ON/OFF(2CH)', softwareBuildID: '2.9.2_r54'}], @@ -127,10 +124,17 @@ const definitions: Definition[] = [ description: 'Zigbee 2 channel switch', fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, fz.power_on_behavior, fz.ignore_genOta], toZigbee: [tz.on_off, tz.power_on_behavior], - exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), e.power(), e.current(), - e.voltage(), e.energy(), e.power_on_behavior(['off', 'on', 'previous'])], + exposes: [ + e.switch().withEndpoint('l1'), + e.switch().withEndpoint('l2'), + e.power(), + e.current(), + e.voltage(), + e.energy(), + e.power_on_behavior(['off', 'on', 'previous']), + ], endpoint: (device) => { - return {'l1': 1, 'l2': 2}; + return {l1: 1, l2: 2}; }, meta: {multiEndpoint: true, multiEndpointSkip: ['power', 'energy', 'voltage', 'current']}, configure: async (device, coordinatorEndpoint) => { @@ -162,8 +166,7 @@ const definitions: Definition[] = [ description: 'Zigbee wall remote control for single color, 1 zone', fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.battery], toZigbee: [], - exposes: [e.battery(), e.action(['on', 'off', - 'brightness_move_up', 'brightness_move_down', 'brightness_move_stop'])], + exposes: [e.battery(), e.action(['on', 'off', 'brightness_move_up', 'brightness_move_down', 'brightness_move_stop'])], }, { zigbeeModel: ['ZGRC-KEY-007'], @@ -171,9 +174,23 @@ const definitions: Definition[] = [ vendor: 'Sunricher', description: 'Zigbee 2 button wall switch', fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.battery], - exposes: [e.battery(), e.action([ - 'on_1', 'off_1', 'stop_1', 'brightness_move_up_1', 'brightness_move_down_1', 'brightness_stop_1', - 'on_2', 'off_2', 'stop_2', 'brightness_move_up_2', 'brightness_move_down_2', 'brightness_stop_2'])], + exposes: [ + e.battery(), + e.action([ + 'on_1', + 'off_1', + 'stop_1', + 'brightness_move_up_1', + 'brightness_move_down_1', + 'brightness_stop_1', + 'on_2', + 'off_2', + 'stop_2', + 'brightness_move_up_2', + 'brightness_move_down_2', + 'brightness_stop_2', + ]), + ], toZigbee: [], meta: {multiEndpoint: true}, }, @@ -182,12 +199,32 @@ const definitions: Definition[] = [ model: '50208693', vendor: 'Sunricher', description: 'Zigbee wall remote control for RGBW, 1 zone with 2 scenes', - fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.battery, - fz.command_recall, fz.command_step, fz.command_move_to_color, fz.command_move_to_color_temp], + fromZigbee: [ + fz.command_on, + fz.command_off, + fz.command_move, + fz.command_stop, + fz.battery, + fz.command_recall, + fz.command_step, + fz.command_move_to_color, + fz.command_move_to_color_temp, + ], toZigbee: [], - exposes: [e.battery(), e.action(['on', 'off', - 'brightness_move_up', 'brightness_move_down', 'brightness_move_stop', 'brightness_step_up', 'brightness_step_down', - 'recall_1', 'recall_2'])], + exposes: [ + e.battery(), + e.action([ + 'on', + 'off', + 'brightness_move_up', + 'brightness_move_down', + 'brightness_move_stop', + 'brightness_step_up', + 'brightness_step_down', + 'recall_1', + 'recall_2', + ]), + ], }, { zigbeeModel: ['ZGRC-KEY-012'], @@ -196,12 +233,36 @@ const definitions: Definition[] = [ description: '5 zone remote and dimmer', fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.battery], toZigbee: [], - exposes: [e.battery(), e.action([ - 'on_1', 'off_1', 'brightness_move_up_1', 'brightness_move_down_1', 'brightness_stop_1', - 'on_2', 'off_2', 'brightness_move_up_2', 'brightness_move_down_2', 'brightness_stop_2', - 'on_3', 'off_3', 'brightness_move_up_3', 'brightness_move_down_3', 'brightness_stop_3', - 'on_4', 'off_4', 'brightness_move_up_4', 'brightness_move_down_4', 'brightness_stop_4', - 'on_5', 'off_5', 'brightness_move_up_5', 'brightness_move_down_5', 'brightness_stop_5'])], + exposes: [ + e.battery(), + e.action([ + 'on_1', + 'off_1', + 'brightness_move_up_1', + 'brightness_move_down_1', + 'brightness_stop_1', + 'on_2', + 'off_2', + 'brightness_move_up_2', + 'brightness_move_down_2', + 'brightness_stop_2', + 'on_3', + 'off_3', + 'brightness_move_up_3', + 'brightness_move_down_3', + 'brightness_stop_3', + 'on_4', + 'off_4', + 'brightness_move_up_4', + 'brightness_move_down_4', + 'brightness_stop_4', + 'on_5', + 'off_5', + 'brightness_move_up_5', + 'brightness_move_down_5', + 'brightness_stop_5', + ]), + ], meta: {multiEndpoint: true, battery: {dontDividePercentage: true}}, configure: async (device, coordinatorEndpoint) => { await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff']); @@ -216,9 +277,19 @@ const definitions: Definition[] = [ model: 'SR-ZG9001K12-DIM-Z4', vendor: 'Sunricher', description: '4 zone remote and dimmer', - fromZigbee: [fz.battery, fz.command_move, legacy.fz.ZGRC013_brightness_onoff, - legacy.fz.ZGRC013_brightness, fz.command_stop, legacy.fz.ZGRC013_brightness_stop, fz.command_on, - legacy.fz.ZGRC013_cmdOn, fz.command_off, legacy.fz.ZGRC013_cmdOff, fz.command_recall], + fromZigbee: [ + fz.battery, + fz.command_move, + legacy.fz.ZGRC013_brightness_onoff, + legacy.fz.ZGRC013_brightness, + fz.command_stop, + legacy.fz.ZGRC013_brightness_stop, + fz.command_on, + legacy.fz.ZGRC013_cmdOn, + fz.command_off, + legacy.fz.ZGRC013_cmdOff, + fz.command_recall, + ], exposes: [e.battery(), e.action(['brightness_move_up', 'brightness_move_down', 'brightness_stop', 'on', 'off', 'recall_*'])], toZigbee: [], whiteLabel: [{vendor: 'RGB Genie', model: 'ZGRC-KEY-013'}], @@ -236,8 +307,18 @@ const definitions: Definition[] = [ vendor: 'Sunricher', description: 'Zigbee wireless touch dimmer switch', fromZigbee: [fz.command_recall, fz.command_on, fz.command_off, fz.command_step, fz.command_move, fz.command_stop], - exposes: [e.action(['recall_*', 'on', 'off', 'brightness_stop', 'brightness_move_down', 'brightness_move_up', - 'brightness_step_down', 'brightness_step_up'])], + exposes: [ + e.action([ + 'recall_*', + 'on', + 'off', + 'brightness_stop', + 'brightness_move_down', + 'brightness_move_up', + 'brightness_step_down', + 'brightness_step_up', + ]), + ], toZigbee: [], }, { @@ -267,7 +348,10 @@ const definitions: Definition[] = [ vendor: 'Sunricher', description: 'ZigBee knob smart dimmer', extend: [light({configureReporting: true}), electricityMeter()], - whiteLabel: [{vendor: 'YPHIX', model: '50208695'}, {vendor: 'Samotech', model: 'SM311'}], + whiteLabel: [ + {vendor: 'YPHIX', model: '50208695'}, + {vendor: 'Samotech', model: 'SM311'}, + ], }, { zigbeeModel: ['ZG2835'], @@ -319,13 +403,41 @@ const definitions: Definition[] = [ model: 'ZG2819S-CCT', vendor: 'Sunricher', description: 'Zigbee handheld remote CCT 4 channels', - fromZigbee: [fz.battery, fz.command_move_to_color, fz.command_move_to_color_temp, fz.command_move_hue, - fz.command_step, fz.command_recall, fz.command_on, fz.command_off, fz.command_toggle, fz.command_stop, - fz.command_move, fz.command_color_loop_set, fz.command_ehanced_move_to_hue_and_saturation], - exposes: [e.battery(), e.action([ - 'color_move', 'color_temperature_move', 'hue_move', 'brightness_step_up', 'brightness_step_down', - 'recall_*', 'on', 'off', 'toggle', 'brightness_stop', 'brightness_move_up', 'brightness_move_down', - 'color_loop_set', 'enhanced_move_to_hue_and_saturation', 'hue_stop'])], + fromZigbee: [ + fz.battery, + fz.command_move_to_color, + fz.command_move_to_color_temp, + fz.command_move_hue, + fz.command_step, + fz.command_recall, + fz.command_on, + fz.command_off, + fz.command_toggle, + fz.command_stop, + fz.command_move, + fz.command_color_loop_set, + fz.command_ehanced_move_to_hue_and_saturation, + ], + exposes: [ + e.battery(), + e.action([ + 'color_move', + 'color_temperature_move', + 'hue_move', + 'brightness_step_up', + 'brightness_step_down', + 'recall_*', + 'on', + 'off', + 'toggle', + 'brightness_stop', + 'brightness_move_up', + 'brightness_move_down', + 'color_loop_set', + 'enhanced_move_to_hue_and_saturation', + 'hue_stop', + ]), + ], toZigbee: [], meta: {multiEndpoint: true}, endpoint: (device) => { @@ -348,8 +460,10 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'GreenPower_2', ieeeAddr: /^0x00000000010.....$/}, - {modelID: 'GreenPower_2', ieeeAddr: /^0x0000000001b.....$/}], + fingerprint: [ + {modelID: 'GreenPower_2', ieeeAddr: /^0x00000000010.....$/}, + {modelID: 'GreenPower_2', ieeeAddr: /^0x0000000001b.....$/}, + ], model: 'SR-ZGP2801K2-DIM', vendor: 'Sunricher', description: 'Pushbutton transmitter module', @@ -358,11 +472,13 @@ const definitions: Definition[] = [ exposes: [e.action(['press_on', 'press_off', 'hold_on', 'hold_off', 'release'])], }, { - fingerprint: [{modelID: 'GreenPower_2', ieeeAddr: /^0x000000005d5.....$/}, + fingerprint: [ + {modelID: 'GreenPower_2', ieeeAddr: /^0x000000005d5.....$/}, {modelID: 'GreenPower_2', ieeeAddr: /^0x0000000057e.....$/}, {modelID: 'GreenPower_2', ieeeAddr: /^0x000000001fa.....$/}, {modelID: 'GreenPower_2', ieeeAddr: /^0x0000000034b.....$/}, - {modelID: 'GreenPower_2', ieeeAddr: /^0x00000000f12.....$/}], + {modelID: 'GreenPower_2', ieeeAddr: /^0x00000000f12.....$/}, + ], model: 'SR-ZGP2801K4-DIM', vendor: 'Sunricher', description: 'Pushbutton transmitter module', @@ -377,8 +493,23 @@ const definitions: Definition[] = [ description: 'Pushbutton transmitter module', fromZigbee: [fzLocal.sunricher_SRZGP2801K45C], toZigbee: [], - exposes: [e.action(['press_on', 'press_off', 'press_high', 'press_low', 'hold_high', 'hold_low', 'high_low_release', - 'cw_ww_release', 'cw_dec_ww_inc', 'ww_inc_cw_dec', 'r_g_b', 'b_g_r', 'rgb_release'])], + exposes: [ + e.action([ + 'press_on', + 'press_off', + 'press_high', + 'press_low', + 'hold_high', + 'hold_low', + 'high_low_release', + 'cw_ww_release', + 'cw_dec_ww_inc', + 'ww_inc_cw_dec', + 'r_g_b', + 'b_g_r', + 'rgb_release', + ]), + ], }, { zigbeeModel: ['ZG9092', 'HK-LN-HEATER-A'], @@ -386,59 +517,77 @@ const definitions: Definition[] = [ vendor: 'Sunricher', description: 'Touch thermostat', fromZigbee: [fz.thermostat, fz.namron_thermostat, fz.metering, fz.electrical_measurement, fz.namron_hvac_user_interface], - toZigbee: [tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint, tz.thermostat_occupancy, - tz.thermostat_local_temperature_calibration, tz.thermostat_local_temperature, tz.thermostat_outdoor_temperature, - tz.thermostat_system_mode, tz.thermostat_control_sequence_of_operation, tz.thermostat_running_state, - tz.namron_thermostat, tz.namron_thermostat_child_lock], + toZigbee: [ + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_unoccupied_heating_setpoint, + tz.thermostat_occupancy, + tz.thermostat_local_temperature_calibration, + tz.thermostat_local_temperature, + tz.thermostat_outdoor_temperature, + tz.thermostat_system_mode, + tz.thermostat_control_sequence_of_operation, + tz.thermostat_running_state, + tz.namron_thermostat, + tz.namron_thermostat_child_lock, + ], exposes: [ - e.numeric('outdoor_temperature', ea.STATE_GET).withUnit('°C') - .withDescription('Current temperature measured from the floor sensor'), - e.climate() + e.numeric('outdoor_temperature', ea.STATE_GET).withUnit('°C').withDescription('Current temperature measured from the floor sensor'), + e + .climate() .withSetpoint('occupied_heating_setpoint', 0, 40, 0.1) .withSetpoint('unoccupied_heating_setpoint', 0, 40, 0.1) .withLocalTemperature() .withLocalTemperatureCalibration(-3, 3, 0.1) .withSystemMode(['off', 'auto', 'heat']) .withRunningState(['idle', 'heat']), - e.binary('away_mode', ea.ALL, 'ON', 'OFF') - .withDescription('Enable/disable away mode'), - e.binary('child_lock', ea.ALL, 'UNLOCK', 'LOCK') - .withDescription('Enables/disables physical input on the device'), - e.power(), e.current(), e.voltage(), e.energy(), - e.enum('lcd_brightness', ea.ALL, ['low', 'mid', 'high']) - .withDescription('OLED brightness when operating the buttons. Default: Medium.'), - e.enum('button_vibration_level', ea.ALL, ['off', 'low', 'high']) - .withDescription('Key beep volume and vibration level. Default: Low.'), - e.enum('floor_sensor_type', ea.ALL, ['10k', '15k', '50k', '100k', '12k']) + e.binary('away_mode', ea.ALL, 'ON', 'OFF').withDescription('Enable/disable away mode'), + e.binary('child_lock', ea.ALL, 'UNLOCK', 'LOCK').withDescription('Enables/disables physical input on the device'), + e.power(), + e.current(), + e.voltage(), + e.energy(), + e.enum('lcd_brightness', ea.ALL, ['low', 'mid', 'high']).withDescription('OLED brightness when operating the buttons. Default: Medium.'), + e.enum('button_vibration_level', ea.ALL, ['off', 'low', 'high']).withDescription('Key beep volume and vibration level. Default: Low.'), + e + .enum('floor_sensor_type', ea.ALL, ['10k', '15k', '50k', '100k', '12k']) .withDescription('Type of the external floor sensor. Default: NTC 10K/25.'), - e.enum('sensor', ea.ALL, ['air', 'floor', 'both']) - .withDescription('The sensor used for heat control. Default: Room Sensor.'), - e.enum('powerup_status', ea.ALL, ['default', 'last_status']) - .withDescription('The mode after a power reset. Default: Previous Mode.'), - e.numeric('floor_sensor_calibration', ea.ALL) + e.enum('sensor', ea.ALL, ['air', 'floor', 'both']).withDescription('The sensor used for heat control. Default: Room Sensor.'), + e.enum('powerup_status', ea.ALL, ['default', 'last_status']).withDescription('The mode after a power reset. Default: Previous Mode.'), + e + .numeric('floor_sensor_calibration', ea.ALL) .withUnit('°C') - .withValueMin(-3).withValueMax(3).withValueStep(0.1) + .withValueMin(-3) + .withValueMax(3) + .withValueStep(0.1) .withDescription('The tempearatue calibration for the external floor sensor, between -3 and 3 in 0.1°C. Default: 0.'), - e.numeric('dry_time', ea.ALL) + e + .numeric('dry_time', ea.ALL) .withUnit('min') - .withValueMin(5).withValueMax(100) + .withValueMin(5) + .withValueMax(100) .withDescription('The duration of Dry Mode, between 5 and 100 minutes. Default: 5.'), - e.enum('mode_after_dry', ea.ALL, ['off', 'manual', 'auto', 'away']) - .withDescription('The mode after Dry Mode. Default: Auto.'), - e.enum('temperature_display', ea.ALL, ['room', 'floor']) - .withDescription('The temperature on the display. Default: Room Temperature.'), - e.numeric('window_open_check', ea.ALL) + e.enum('mode_after_dry', ea.ALL, ['off', 'manual', 'auto', 'away']).withDescription('The mode after Dry Mode. Default: Auto.'), + e.enum('temperature_display', ea.ALL, ['room', 'floor']).withDescription('The temperature on the display. Default: Room Temperature.'), + e + .numeric('window_open_check', ea.ALL) .withUnit('°C') - .withValueMin(0).withValueMax(8).withValueStep(0.5) + .withValueMin(0) + .withValueMax(8) + .withValueStep(0.5) .withDescription('The threshold to detect window open, between 0.0 and 8.0 in 0.5 °C. Default: 0 (disabled).'), - e.numeric('hysterersis', ea.ALL) + e + .numeric('hysterersis', ea.ALL) .withUnit('°C') - .withValueMin(0.5).withValueMax(2).withValueStep(0.1) + .withValueMin(0.5) + .withValueMax(2) + .withValueStep(0.1) .withDescription('Hysteresis setting, between 0.5 and 2 in 0.1 °C. Default: 0.5.'), e.enum('display_auto_off_enabled', ea.ALL, ['disabled', 'enabled']), - e.numeric('alarm_airtemp_overvalue', ea.ALL) + e + .numeric('alarm_airtemp_overvalue', ea.ALL) .withUnit('°C') - .withValueMin(20).withValueMax(60) + .withValueMin(20) + .withValueMax(60) .withDescription('Room temperature alarm threshold, between 20 and 60 in °C. 0 means disabled. Default: 45.'), ], onEvent: async (type, data, device, options) => { @@ -456,8 +605,15 @@ const definitions: Definition[] = [ configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const binds = [ - 'genBasic', 'genIdentify', 'hvacThermostat', 'seMetering', 'haElectricalMeasurement', 'genAlarms', - 'msOccupancySensing', 'genTime', 'hvacUserInterfaceCfg', + 'genBasic', + 'genIdentify', + 'hvacThermostat', + 'seMetering', + 'haElectricalMeasurement', + 'genAlarms', + 'msOccupancySensing', + 'genTime', + 'hvacUserInterfaceCfg', ]; await reporting.bind(endpoint, coordinatorEndpoint, binds); @@ -473,12 +629,14 @@ const definitions: Definition[] = [ logger.debug(`Failed to setup keypadLockout reporting`, NS); } - await endpoint.configureReporting('hvacThermostat', [{ - attribute: 'occupancy', - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }]); + await endpoint.configureReporting('hvacThermostat', [ + { + attribute: 'occupancy', + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ]); await endpoint.read('haElectricalMeasurement', ['acVoltageMultiplier', 'acVoltageDivisor', 'acCurrentMultiplier']); await endpoint.read('haElectricalMeasurement', ['acCurrentDivisor']); @@ -496,155 +654,183 @@ const definitions: Definition[] = [ // OperateDisplayLcdBrightnesss await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1000, type: 0x30}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }], + [ + { + attribute: {ID: 0x1000, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ], options, ); // ButtonVibrationLevel await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1001, type: 0x30}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }], + [ + { + attribute: {ID: 0x1001, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ], options, ); // FloorSensorType await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1002, type: 0x30}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }], + [ + { + attribute: {ID: 0x1002, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ], options, ); // ControlType await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1003, type: 0x30}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }], + [ + { + attribute: {ID: 0x1003, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ], options, ); // PowerUpStatus await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1004, type: 0x30}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }], + [ + { + attribute: {ID: 0x1004, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ], options, ); // FloorSensorCalibration await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1005, type: 0x28}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 0, - }], + [ + { + attribute: {ID: 0x1005, type: 0x28}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 0, + }, + ], options, ); // DryTime await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1006, type: 0x20}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 0, - }], + [ + { + attribute: {ID: 0x1006, type: 0x20}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 0, + }, + ], options, ); // ModeAfterDry await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1007, type: 0x30}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }], + [ + { + attribute: {ID: 0x1007, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ], options, ); // TemperatureDisplay await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1008, type: 0x30}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }], + [ + { + attribute: {ID: 0x1008, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ], options, ); // WindowOpenCheck await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x1009, type: 0x20}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 0, - }], + [ + { + attribute: {ID: 0x1009, type: 0x20}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 0, + }, + ], options, ); // Hysterersis await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x100A, type: 0x20}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 0, - }], + [ + { + attribute: {ID: 0x100a, type: 0x20}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 0, + }, + ], options, ); // DisplayAutoOffEnable await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x100B, type: 0x30}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }], + [ + { + attribute: {ID: 0x100b, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ], options, ); // AlarmAirTempOverValue await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x2001, type: 0x20}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: 0, - }], + [ + { + attribute: {ID: 0x2001, type: 0x20}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: 0, + }, + ], options, ); // Away Mode Set await endpoint.configureReporting( 'hvacThermostat', - [{ - attribute: {ID: 0x2002, type: 0x30}, - minimumReportInterval: 0, - maximumReportInterval: constants.repInterval.HOUR, - reportableChange: null, - }], + [ + { + attribute: {ID: 0x2002, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null, + }, + ], options, ); @@ -655,12 +841,15 @@ const definitions: Definition[] = [ await endpoint.read('hvacThermostat', ['systemMode', 'runningState', 'occupiedHeatingSetpoint']); await endpoint.read('hvacThermostat', [0x1000, 0x1001, 0x1002, 0x1003], options); await endpoint.read('hvacThermostat', [0x1004, 0x1005, 0x1006, 0x1007], options); - await endpoint.read('hvacThermostat', [0x1008, 0x1009, 0x100A, 0x100B], options); + await endpoint.read('hvacThermostat', [0x1008, 0x1009, 0x100a, 0x100b], options); await endpoint.read('hvacThermostat', [0x2001, 0x2002], options); }, }, { - fingerprint: [{modelID: 'TERNCY-DC01', manufacturerName: 'Sunricher'}, {modelID: 'HK-SENSOR-CT-A', manufacturerName: 'Sunricher'}], + fingerprint: [ + {modelID: 'TERNCY-DC01', manufacturerName: 'Sunricher'}, + {modelID: 'HK-SENSOR-CT-A', manufacturerName: 'Sunricher'}, + ], model: 'SR-ZG9010A', vendor: 'Sunricher', description: 'Door windows sensor', diff --git a/src/devices/swann.ts b/src/devices/swann.ts index 983ee7c8ca588..8d2c12c77d0d0 100644 --- a/src/devices/swann.ts +++ b/src/devices/swann.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/sylvania.ts b/src/devices/sylvania.ts index 3495d855341f8..683b95d42dc8f 100644 --- a/src/devices/sylvania.ts +++ b/src/devices/sylvania.ts @@ -1,10 +1,10 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; +import {ledvanceLight, ledvanceOnOff} from '../lib/ledvance'; import * as legacy from '../lib/legacy'; import * as ota from '../lib/ota'; import * as reporting from '../lib/reporting'; -import {ledvanceLight, ledvanceOnOff} from '../lib/ledvance'; +import {Definition} from '../lib/types'; const e = exposes.presets; @@ -30,9 +30,14 @@ const definitions: Definition[] = [ model: '73743', vendor: 'Sylvania', description: 'Lightify Smart Dimming Switch', - fromZigbee: [legacy.fz.osram_lightify_switch_cmdOn, legacy.fz.osram_lightify_switch_cmdMoveWithOnOff, - legacy.fz.osram_lightify_switch_cmdOff, legacy.fz.osram_lightify_switch_cmdMove, - legacy.fz.osram_lightify_switch_73743_cmdStop, fz.battery], + fromZigbee: [ + legacy.fz.osram_lightify_switch_cmdOn, + legacy.fz.osram_lightify_switch_cmdMoveWithOnOff, + legacy.fz.osram_lightify_switch_cmdOff, + legacy.fz.osram_lightify_switch_cmdMove, + legacy.fz.osram_lightify_switch_73743_cmdStop, + fz.battery, + ], exposes: [e.battery(), e.action(['up', 'up_hold', 'down', 'down_hold', 'up_release', 'down_release'])], toZigbee: [], meta: {battery: {voltageToPercentage: '3V_2500'}}, diff --git a/src/devices/tapestry.ts b/src/devices/tapestry.ts index 99a99e07384b1..684ce2af4efe3 100644 --- a/src/devices/tapestry.ts +++ b/src/devices/tapestry.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/tci.ts b/src/devices/tci.ts index f0db7ba03ced5..f81cba9d0a1a3 100644 --- a/src/devices/tci.ts +++ b/src/devices/tci.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/technicolor.ts b/src/devices/technicolor.ts index 3f8c78b065b44..4a307751b5faf 100644 --- a/src/devices/technicolor.ts +++ b/src/devices/technicolor.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as globalStore from '../lib/store'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import * as globalStore from '../lib/store'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -14,17 +14,30 @@ const definitions: Definition[] = [ vendor: 'Technicolor', description: 'Xfinity security keypad', meta: {battery: {voltageToPercentage: '3V_2100'}}, - fromZigbee: [fz.command_arm_with_transaction, fz.temperature, fz.battery, fz.ias_occupancy_alarm_1, fz.identify, - fz.ias_contact_alarm_1, fz.ias_ace_occupancy_with_timeout], + fromZigbee: [ + fz.command_arm_with_transaction, + fz.temperature, + fz.battery, + fz.ias_occupancy_alarm_1, + fz.identify, + fz.ias_contact_alarm_1, + fz.ias_ace_occupancy_with_timeout, + ], toZigbee: [tz.arm_mode], - exposes: [e.battery(), e.battery_voltage(), e.occupancy(), e.battery_low(), e.tamper(), e.presence(), - e.contact(), e.temperature(), + exposes: [ + e.battery(), + e.battery_voltage(), + e.occupancy(), + e.battery_low(), + e.tamper(), + e.presence(), + e.contact(), + e.temperature(), e.numeric('action_code', ea.STATE).withDescription('Pin code introduced.'), e.numeric('action_transaction', ea.STATE).withDescription('Last action transaction number.'), e.text('action_zone', ea.STATE).withDescription('Alarm zone. Default value 0'), - e.action([ - 'disarm', 'arm_day_zones', 'identify', 'arm_night_zones', 'arm_all_zones', 'exit_delay', 'emergency', - ])], + e.action(['disarm', 'arm_day_zones', 'identify', 'arm_night_zones', 'arm_all_zones', 'exit_delay', 'emergency']), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const clusters = ['msTemperatureMeasurement', 'genPowerCfg', 'ssIasZone', 'ssIasAce', 'genBasic', 'genIdentify']; @@ -33,15 +46,19 @@ const definitions: Definition[] = [ await reporting.batteryVoltage(endpoint); }, onEvent: async (type, data, device) => { - if (type === 'message' && data.type === 'commandGetPanelStatus' && data.cluster === 'ssIasAce' && - globalStore.hasValue(device.getEndpoint(1), 'panelStatus')) { + if ( + type === 'message' && + data.type === 'commandGetPanelStatus' && + data.cluster === 'ssIasAce' && + globalStore.hasValue(device.getEndpoint(1), 'panelStatus') + ) { const payload = { panelstatus: globalStore.getValue(device.getEndpoint(1), 'panelStatus'), - secondsremain: 0x00, audiblenotif: 0x00, alarmstatus: 0x00, + secondsremain: 0x00, + audiblenotif: 0x00, + alarmstatus: 0x00, }; - await device.getEndpoint(1).commandResponse( - 'ssIasAce', 'getPanelStatusRsp', payload, {}, data.meta.zclTransactionSequenceNumber, - ); + await device.getEndpoint(1).commandResponse('ssIasAce', 'getPanelStatusRsp', payload, {}, data.meta.zclTransactionSequenceNumber); } }, }, diff --git a/src/devices/terncy.ts b/src/devices/terncy.ts index 5254d0042a5af..ad9c4d13139e3 100644 --- a/src/devices/terncy.ts +++ b/src/devices/terncy.ts @@ -1,11 +1,11 @@ -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; +import {deviceEndpoints, light, onOff} from '../lib/modernExtend'; import * as ota from '../lib/ota'; import * as reporting from '../lib/reporting'; import {Definition} from '../lib/types'; -import {deviceEndpoints, light, onOff} from '../lib/modernExtend'; const e = exposes.presets; const ea = exposes.access; @@ -16,10 +16,7 @@ const definitions: Definition[] = [ model: 'TERNCY-WS01', vendor: 'TERNCY', description: 'Smart light switch - 4 gang without neutral wire', - extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4}}), - onOff({endpointNames: ['l1', 'l2', 'l3', 'l4']}), - ], + extend: [deviceEndpoints({endpoints: {l1: 1, l2: 2, l3: 3, l4: 4}}), onOff({endpointNames: ['l1', 'l2', 'l3', 'l4']})], }, { zigbeeModel: ['DL001'], @@ -44,8 +41,7 @@ const definitions: Definition[] = [ vendor: 'TERNCY', description: 'Awareness switch', fromZigbee: [fz.terncy_temperature, fz.occupancy_with_timeout, fz.illuminance, fz.terncy_raw, legacy.fz.terncy_raw, fz.battery], - exposes: [e.temperature(), e.occupancy(), e.illuminance_lux(), e.illuminance(), - e.action(['single', 'double', 'triple', 'quadruple'])], + exposes: [e.temperature(), e.occupancy(), e.illuminance_lux(), e.illuminance(), e.action(['single', 'double', 'triple', 'quadruple'])], toZigbee: [], meta: {battery: {dontDividePercentage: true}}, }, @@ -58,8 +54,7 @@ const definitions: Definition[] = [ toZigbee: [], ota: ota.zigbeeOTA, meta: {battery: {dontDividePercentage: true}}, - exposes: [e.battery(), e.action(['single', 'double', 'triple', 'quadruple', 'rotate']), - e.text('direction', ea.STATE)], + exposes: [e.battery(), e.action(['single', 'double', 'triple', 'quadruple', 'rotate']), e.text('direction', ea.STATE)], }, { zigbeeModel: ['TERNCY-LS01'], diff --git a/src/devices/the_light_group.ts b/src/devices/the_light_group.ts index 581eda5ee8cd0..6092520494002 100644 --- a/src/devices/the_light_group.ts +++ b/src/devices/the_light_group.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; import {light} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; @@ -37,12 +37,31 @@ const definitions: Definition[] = [ vendor: 'The Light Group', description: 'SLC SmartOne Zigbee wall remote 4-channels', fromZigbee: [fz.command_on, fz.command_off, fz.battery, fz.command_move, fz.command_stop], - exposes: [e.battery(), e.action([ - 'on_l1', 'off_l1', 'brightness_move_up_l1', 'brightness_move_down_l1', 'brightness_stop_l1', - 'on_l2', 'off_l2', 'brightness_move_up_l2', 'brightness_move_down_l2', 'brightness_stop_l2', - 'on_l3', 'off_l3', 'brightness_move_up_l3', 'brightness_move_down_l3', 'brightness_stop_l3', - 'on_l4', 'off_l4', 'brightness_move_up_l4', 'brightness_move_down_l4', 'brightness_stop_l4', - ])], + exposes: [ + e.battery(), + e.action([ + 'on_l1', + 'off_l1', + 'brightness_move_up_l1', + 'brightness_move_down_l1', + 'brightness_stop_l1', + 'on_l2', + 'off_l2', + 'brightness_move_up_l2', + 'brightness_move_down_l2', + 'brightness_stop_l2', + 'on_l3', + 'off_l3', + 'brightness_move_up_l3', + 'brightness_move_down_l3', + 'brightness_stop_l3', + 'on_l4', + 'off_l4', + 'brightness_move_up_l4', + 'brightness_move_down_l4', + 'brightness_stop_l4', + ]), + ], toZigbee: [], meta: {multiEndpoint: true}, endpoint: (device) => { diff --git a/src/devices/third_reality.ts b/src/devices/third_reality.ts index 3048de4d3bf47..5fa53582eb838 100644 --- a/src/devices/third_reality.ts +++ b/src/devices/third_reality.ts @@ -1,11 +1,11 @@ -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; -import * as ota from '../lib/ota'; -import {Definition, Fz, KeyValue} from '../lib/types'; +import * as exposes from '../lib/exposes'; import {forcePowerSource, iasZoneAlarm, light, onOff} from '../lib/modernExtend'; import {temperature, humidity, battery} from '../lib/modernExtend'; +import * as ota from '../lib/ota'; +import * as reporting from '../lib/reporting'; +import {Definition, Fz, KeyValue} from '../lib/types'; const e = exposes.presets; @@ -161,7 +161,9 @@ const definitions: Definition[] = [ await reporting.currentPositionLiftPercentage(endpoint); try { await reporting.batteryPercentageRemaining(endpoint); - } catch (error) {/* Fails for some*/} + } catch (error) { + /* Fails for some*/ + } }, exposes: [e.cover_position(), e.battery()], }, @@ -230,7 +232,11 @@ const definitions: Definition[] = [ await reporting.readMeteringMultiplierDivisor(endpoint); endpoint.saveClusterAttributeKeyValue('seMetering', {divisor: 3600000, multiplier: 1}); endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', { - acVoltageMultiplier: 1, acVoltageDivisor: 10, acCurrentMultiplier: 1, acCurrentDivisor: 1000, acPowerMultiplier: 1, + acVoltageMultiplier: 1, + acVoltageDivisor: 10, + acCurrentMultiplier: 1, + acCurrentDivisor: 1000, + acPowerMultiplier: 1, acPowerDivisor: 10, }); device.save(); @@ -286,7 +292,11 @@ const definitions: Definition[] = [ await reporting.readMeteringMultiplierDivisor(endpoint); endpoint.saveClusterAttributeKeyValue('seMetering', {divisor: 3600000, multiplier: 1}); endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', { - acVoltageMultiplier: 1, acVoltageDivisor: 10, acCurrentMultiplier: 1, acCurrentDivisor: 1000, acPowerMultiplier: 1, + acVoltageMultiplier: 1, + acVoltageDivisor: 10, + acCurrentMultiplier: 1, + acCurrentDivisor: 1000, + acPowerMultiplier: 1, acPowerDivisor: 10, }); device.save(); diff --git a/src/devices/titan_products.ts b/src/devices/titan_products.ts index 429c9d539dc75..9d02c22900ebe 100644 --- a/src/devices/titan_products.ts +++ b/src/devices/titan_products.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/tlwglobal.ts b/src/devices/tlwglobal.ts index 56f3727d96d63..0a831c1eb3d64 100644 --- a/src/devices/tlwglobal.ts +++ b/src/devices/tlwglobal.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ // Tested working with firmware 2.5.3_r58: dimming, on/off, and effects give no diff --git a/src/devices/tplink.ts b/src/devices/tplink.ts index 844b40a0daf7d..451f8b6098fe8 100644 --- a/src/devices/tplink.ts +++ b/src/devices/tplink.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/trust.ts b/src/devices/trust.ts index d52be00cfff70..22ac8a76948da 100644 --- a/src/devices/trust.ts +++ b/src/devices/trust.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; -import * as reporting from '../lib/reporting'; import {light} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; @@ -37,8 +37,10 @@ const definitions: Definition[] = [ exposes: [e.water_leak(), e.battery_low(), e.tamper(), e.battery()], }, { - zigbeeModel: ['\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'+ - '\u0000\u0000\u0000\u0000\u0000', 'ZLL-NonColorController'], + zigbeeModel: [ + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000', + 'ZLL-NonColorController', + ], model: 'ZYCT-202', vendor: 'Trust', description: 'Remote control', @@ -57,11 +59,21 @@ const definitions: Definition[] = [ { fingerprint: [ // https://github.com/Koenkk/zigbee2mqtt/issues/8027#issuecomment-904783277 - {modelID: 'ZLL-ColorTempera', manufacturerName: 'Trust International B.V.\u0000', applicationVersion: 1, endpoints: [ - {ID: 1, profileID: 49246, deviceID: 544, - inputClusters: [0, 4, 3, 6, 8, 5, 768, 65535, 65535, 25], outputClusters: [0, 4, 3, 6, 8, 5, 768, 25]}, - {ID: 2, profileID: 49246, deviceID: 4096, inputClusters: [4096], outputClusters: [4096]}, - ]}, + { + modelID: 'ZLL-ColorTempera', + manufacturerName: 'Trust International B.V.\u0000', + applicationVersion: 1, + endpoints: [ + { + ID: 1, + profileID: 49246, + deviceID: 544, + inputClusters: [0, 4, 3, 6, 8, 5, 768, 65535, 65535, 25], + outputClusters: [0, 4, 3, 6, 8, 5, 768, 25], + }, + {ID: 2, profileID: 49246, deviceID: 4096, inputClusters: [4096], outputClusters: [4096]}, + ], + }, ], zigbeeModel: ['ZLL-ColorTempera', 'ZLL-ColorTemperature'], model: 'ZLED-TUNE9', @@ -72,18 +84,28 @@ const definitions: Definition[] = [ { fingerprint: [ // https://github.com/Koenkk/zigbee2mqtt/issues/8027#issuecomment-904783277 - {modelID: 'ZLL-ExtendedColo', manufacturerName: 'Trust International B.V.\u0000', applicationVersion: 1, endpoints: [ - {ID: 1, profileID: 49246, deviceID: 4096, inputClusters: [4096], outputClusters: [4096]}, - {ID: 2, profileID: 49246, deviceID: 528, - inputClusters: [0, 4, 3, 6, 8, 5, 768, 65535, 25], outputClusters: [0, 4, 3, 6, 8, 5, 768, 25]}, - ]}, + { + modelID: 'ZLL-ExtendedColo', + manufacturerName: 'Trust International B.V.\u0000', + applicationVersion: 1, + endpoints: [ + {ID: 1, profileID: 49246, deviceID: 4096, inputClusters: [4096], outputClusters: [4096]}, + { + ID: 2, + profileID: 49246, + deviceID: 528, + inputClusters: [0, 4, 3, 6, 8, 5, 768, 65535, 25], + outputClusters: [0, 4, 3, 6, 8, 5, 768, 25], + }, + ], + }, ], model: 'ZLED-RGB9', vendor: 'Trust', description: 'Smart RGB LED bulb', extend: [light({colorTemp: {range: [153, 500]}, color: true, powerOnBehavior: false})], endpoint: (device) => { - return {'default': 2}; + return {default: 2}; }, }, { diff --git a/src/devices/tubeszb.ts b/src/devices/tubeszb.ts index f19395878328b..18a3ba71e01fd 100644 --- a/src/devices/tubeszb.ts +++ b/src/devices/tubeszb.ts @@ -1,6 +1,6 @@ -import {Definition} from '../lib/types'; import fz from '../converters/fromZigbee'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/tuya.ts b/src/devices/tuya.ts index cb04fd33fe31a..acb758a726ea1 100644 --- a/src/devices/tuya.ts +++ b/src/devices/tuya.ts @@ -1,21 +1,33 @@ +import fz from '../converters/fromZigbee'; +import tz from '../converters/toZigbee'; +import * as libColor from '../lib/color'; +import {ColorMode, colorModeLookup} from '../lib/constants'; import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; -import * as tuya from '../lib/tuya'; +import {logger} from '../lib/logger'; +import { + onOff, + quirkCheckinInterval, + battery, + deviceEndpoints, + light, + iasZoneAlarm, + temperature, + humidity, + identify, + actionEnumLookup, + commandsOnOff, + commandsLevelCtrl, + electricityMeter, +} from '../lib/modernExtend'; import * as ota from '../lib/ota'; import * as reporting from '../lib/reporting'; -import * as libColor from '../lib/color'; -import * as utils from '../lib/utils'; -import * as zosung from '../lib/zosung'; import * as globalStore from '../lib/store'; -import {ColorMode, colorModeLookup} from '../lib/constants'; -import fz from '../converters/fromZigbee'; -import tz from '../converters/toZigbee'; +import * as tuya from '../lib/tuya'; import {KeyValue, Definition, Zh, Tz, Fz, Expose, KeyValueAny, KeyValueString, ModernExtend} from '../lib/types'; -import {onOff, quirkCheckinInterval, battery, deviceEndpoints, light, iasZoneAlarm, temperature, humidity, identify, - actionEnumLookup, commandsOnOff, commandsLevelCtrl, - electricityMeter} from '../lib/modernExtend'; -import {logger} from '../lib/logger'; +import * as utils from '../lib/utils'; import {addActionGroup, hasAlreadyProcessedMessage, postfixWithEndpointName} from '../lib/utils'; +import * as zosung from '../lib/zosung'; const NS = 'zhc:tuya'; const {tuyaLight} = tuya.modernExtend; @@ -28,10 +40,9 @@ const tzZosung = zosung.tzZosung; const ez = zosung.presetsZosung; const storeLocal = { - getPrivatePJ1203A: (device: Zh.Device) => { let priv = globalStore.getValue(device, 'private_state'); - if (priv===undefined) { + if (priv === undefined) { // // The PJ-1203A is sending quick sequences of messages containing a single datapoint. // A sequence occurs every `update_frequency` seconds (10s by default) @@ -93,45 +104,45 @@ const storeLocal = { pub_power_a: null, pub_power_b: null, - recompute_power_ab: function(result: KeyValueAny) { + recompute_power_ab: function (result: KeyValueAny) { let modified = false; if ('power_a' in result) { - this.pub_power_a = result.power_a * ( result.energy_flow_a == 'producing' ? -1 : 1 ); + this.pub_power_a = result.power_a * (result.energy_flow_a == 'producing' ? -1 : 1); modified = true; } if ('power_b' in result) { - this.pub_power_b = result.power_b * ( result.energy_flow_b == 'producing' ? -1 : 1 ); + this.pub_power_b = result.power_b * (result.energy_flow_b == 'producing' ? -1 : 1); modified = true; } if (modified) { - if ( this.pub_power_a!==null && this.pub_power_b!==null ) { + if (this.pub_power_a !== null && this.pub_power_b !== null) { // Cancel and reapply the scaling by 10 to avoid floating-point rounding errors // such as 79.8 - 37.1 = 42.699999999999996 - result['power_ab'] = Math.round(10*this.pub_power_a + 10*this.pub_power_b) / 10; + result['power_ab'] = Math.round(10 * this.pub_power_a + 10 * this.pub_power_b) / 10; } } }, - flush: function(result: KeyValueAny, channel: string, options: KeyValue) { - const sign = this['sign_'+channel]; - const power = this['power_'+channel]; - const current = this['current_'+channel]; - const powerFactor = this['power_factor_'+channel]; - this['sign_'+channel] = this['power_'+channel] = this['current_'+channel] = this['power_factor_'+channel] = null; + flush: function (result: KeyValueAny, channel: string, options: KeyValue) { + const sign = this['sign_' + channel]; + const power = this['power_' + channel]; + const current = this['current_' + channel]; + const powerFactor = this['power_factor_' + channel]; + this['sign_' + channel] = this['power_' + channel] = this['current_' + channel] = this['power_factor_' + channel] = null; // Only publish if the set is complete otherwise discard everything. - if ( sign!==null && power!==null && current!==null && powerFactor!==null ) { - const signedPowerKey = 'signed_power_'+channel; + if (sign !== null && power !== null && current !== null && powerFactor !== null) { + const signedPowerKey = 'signed_power_' + channel; const signedPower = options.hasOwnProperty(signedPowerKey) ? options[signedPowerKey] : false; if (signedPower) { - result['power_'+channel] = sign * power; - result['energy_flow_'+channel] = 'sign'; + result['power_' + channel] = sign * power; + result['energy_flow_' + channel] = 'sign'; } else { - result['power_'+channel] = power; - result['energy_flow_'+channel] = (sign>0) ? 'consuming' : 'producing'; + result['power_' + channel] = power; + result['energy_flow_' + channel] = sign > 0 ? 'consuming' : 'producing'; } - result['timestamp_'+channel] = this['timestamp_'+channel]; - result['current_'+channel] = current; - result['power_factor_'+channel] = powerFactor; + result['timestamp_' + channel] = this['timestamp_' + channel]; + result['current_' + channel] = current; + result['power_factor_' + channel] = powerFactor; this.recompute_power_ab(result); return true; } @@ -147,16 +158,16 @@ const storeLocal = { // // Also, the publication of a zero energy state is not delayed // when option late_energy_flow_a|b is set. - flushZero: function(result: KeyValueAny, channel:string, options: KeyValue) { - this['sign_'+channel] = +1; - this['power_'+channel] = 0; - this['timestamp_'+channel] = new Date().toISOString(); - this['current_'+channel] = 0; - this['power_factor_'+channel] = 100; + flushZero: function (result: KeyValueAny, channel: string, options: KeyValue) { + this['sign_' + channel] = +1; + this['power_' + channel] = 0; + this['timestamp_' + channel] = new Date().toISOString(); + this['current_' + channel] = 0; + this['power_factor_' + channel] = 100; this.flush(result, channel, options); }, - clear: function() { + clear: function () { priv.sign_a = null; priv.sign_b = null; priv.power_a = null; @@ -167,7 +178,7 @@ const storeLocal = { priv.power_factor_b = null; }, }; - globalStore.putValue(device, 'private_state', priv ); + globalStore.putValue(device, 'private_state', priv); } return priv; }, @@ -176,11 +187,11 @@ const storeLocal = { const convLocal = { energyFlowPJ1203A: (channel: string) => { return { - from: (v:number, meta: Fz.Meta, options: KeyValue) => { + from: (v: number, meta: Fz.Meta, options: KeyValue) => { const priv = storeLocal.getPrivatePJ1203A(meta.device); const result = {}; - priv['sign_'+channel] = (v==1) ? -1 : +1; - const lateEnergyFlowKey = 'late_energy_flow_'+channel; + priv['sign_' + channel] = v == 1 ? -1 : +1; + const lateEnergyFlowKey = 'late_energy_flow_' + channel; const lateEnergyFlow = options.hasOwnProperty(lateEnergyFlowKey) ? options[lateEnergyFlowKey] : false; if (lateEnergyFlow) { priv.flush(result, channel, options); @@ -192,12 +203,12 @@ const convLocal = { powerPJ1203A: (channel: string) => { return { - from: (v:number, meta: Fz.Meta, options: KeyValue) => { + from: (v: number, meta: Fz.Meta, options: KeyValue) => { const priv = storeLocal.getPrivatePJ1203A(meta.device); const result = {}; - priv['power_'+channel] = v/10; - priv['timestamp_'+channel] = new Date().toISOString(); - if (v==0) { + priv['power_' + channel] = v / 10; + priv['timestamp_' + channel] = new Date().toISOString(); + if (v == 0) { priv.flushZero(result, channel, options); return result; } @@ -211,8 +222,8 @@ const convLocal = { from: (v: number, meta: Fz.Meta, options: KeyValue) => { const priv = storeLocal.getPrivatePJ1203A(meta.device); const result = {}; - priv['current_'+channel] = v/1000; - if (v==0) { + priv['current_' + channel] = v / 1000; + if (v == 0) { priv.flushZero(result, channel, options); return result; } @@ -223,11 +234,11 @@ const convLocal = { powerFactorPJ1203A: (channel: string) => { return { - from: (v:number, meta: Fz.Meta, options: KeyValue) => { + from: (v: number, meta: Fz.Meta, options: KeyValue) => { const priv = storeLocal.getPrivatePJ1203A(meta.device); const result = {}; - priv['power_factor_'+channel] = v; - const lateEnergyFlowKey = 'late_energy_flow_'+channel; + priv['power_factor_' + channel] = v; + const lateEnergyFlowKey = 'late_energy_flow_' + channel; const lateEnergyFlow = options.hasOwnProperty(lateEnergyFlowKey) ? options[lateEnergyFlowKey] : false; if (!lateEnergyFlow) { priv.flush(result, channel, options); @@ -280,13 +291,13 @@ const tzLocal = { newState.color_mode = colorMode; // To switch between white mode and color mode, we have to send a special command: - const rgbMode = (colorMode == colorModeLookup[ColorMode.HS]); + const rgbMode = colorMode == colorModeLookup[ColorMode.HS]; await entity.command('lightingColorCtrl', 'tuyaRgbMode', {enable: rgbMode}); } // A transition time of 0 would be treated as about 1 second, probably some kind of fallback/default // transition time, so for "no transition" we use 1 (tenth of a second). - const transtime = typeof meta.message.transition === 'number' ? (meta.message.transition * 10) : 0.1; + const transtime = typeof meta.message.transition === 'number' ? meta.message.transition * 10 : 0.1; if (colorMode == colorModeLookup[ColorMode.ColorTemp]) { if ('brightness' in meta.message) { @@ -349,8 +360,12 @@ const tzLocal = { saturation: utils.mapNumberRange(newSettings.saturation, 0, 100, 0, 1000), }; // This command doesn't support a transition time - await entity.command('lightingColorCtrl', 'tuyaMoveToHueAndSaturationBrightness2', zclData, - utils.getOptions(meta.mapped, entity)); + await entity.command( + 'lightingColorCtrl', + 'tuyaMoveToHueAndSaturationBrightness2', + zclData, + utils.getOptions(meta.mapped, entity), + ); } } @@ -369,7 +384,7 @@ const tzLocal = { convertSet: async (entity, key, value, meta) => { const color = libColor.Color.fromConverterArg(value); const enableWhite = - (color.isRGB() && (color.rgb.red === 1 && color.rgb.green === 1 && color.rgb.blue === 1)) || + (color.isRGB() && color.rgb.red === 1 && color.rgb.green === 1 && color.rgb.blue === 1) || // Zigbee2MQTT frontend white value (color.isXY() && (color.xy.x === 0.3125 || color.xy.y === 0.32894736842105265)) || // Home Assistant white color picker value @@ -397,9 +412,9 @@ const tzLocal = { utils.assertString(value, 'light'); await entity.command('genOnOff', value.toLowerCase() === 'on' ? 'on' : 'off', {}, utils.getOptions(meta.mapped, entity)); } else if (key === 'duration') { - await entity.write('ssIasWd', {'maxDuration': value}, utils.getOptions(meta.mapped, entity)); + await entity.write('ssIasWd', {maxDuration: value}, utils.getOptions(meta.mapped, entity)); } else if (key === 'volume') { - const lookup: KeyValue = {'mute': 0, 'low': 10, 'medium': 30, 'high': 50}; + const lookup: KeyValue = {mute: 0, low: 10, medium: 30, high: 50}; utils.assertString(value, 'volume'); const lookupValue = lookup[value]; value = value.toLowerCase(); @@ -413,92 +428,99 @@ const tzLocal = { key: ['temperature_unit'], convertSet: async (entity, key, value, meta) => { switch (key) { - case 'temperature_unit': { - utils.assertString(value, 'temperature_unit'); - await entity.write('manuSpecificTuya_2', {'57355': {value: {'celsius': 0, 'fahrenheit': 1}[value], type: 48}}); - break; - } - default: // Unknown key - logger.warning(`Unhandled key ${key}`, NS); + case 'temperature_unit': { + utils.assertString(value, 'temperature_unit'); + await entity.write('manuSpecificTuya_2', {'57355': {value: {celsius: 0, fahrenheit: 1}[value], type: 48}}); + break; + } + default: // Unknown key + logger.warning(`Unhandled key ${key}`, NS); } }, } satisfies Tz.Converter, TS011F_threshold: { key: [ - 'temperature_threshold', 'temperature_breaker', 'power_threshold', 'power_breaker', - 'over_current_threshold', 'over_current_breaker', 'over_voltage_threshold', 'over_voltage_breaker', - 'under_voltage_threshold', 'under_voltage_breaker', + 'temperature_threshold', + 'temperature_breaker', + 'power_threshold', + 'power_breaker', + 'over_current_threshold', + 'over_current_breaker', + 'over_voltage_threshold', + 'over_voltage_breaker', + 'under_voltage_threshold', + 'under_voltage_breaker', ], convertSet: async (entity, key, value, meta) => { - const onOffLookup = {'on': 1, 'off': 0}; + const onOffLookup = {on: 1, off: 0}; switch (key) { - case 'temperature_threshold': { - const state = meta.state['temperature_breaker']; - const buf = Buffer.from([5, utils.getFromLookup(state, onOffLookup), 0, utils.toNumber(value, 'temperature_threshold')]); - await entity.command('manuSpecificTuya_3', 'setOptions2', {data: buf}); - break; - } - case 'temperature_breaker': { - const threshold = meta.state['temperature_threshold']; - const number = utils.toNumber(threshold, 'temperature_threshold'); - const buf = Buffer.from([5, utils.getFromLookup(value, onOffLookup), 0, number]); - await entity.command('manuSpecificTuya_3', 'setOptions2', {data: buf}); - break; - } - case 'power_threshold': { - const state = meta.state['power_breaker']; - const buf = Buffer.from([7, utils.getFromLookup(state, onOffLookup), 0, utils.toNumber(value, 'power_breaker')]); - await entity.command('manuSpecificTuya_3', 'setOptions2', {data: buf}); - break; - } - case 'power_breaker': { - const threshold = meta.state['power_threshold']; - const number = utils.toNumber(threshold, 'power_breaker'); - const buf = Buffer.from([7, utils.getFromLookup(value, onOffLookup), 0, number]); - await entity.command('manuSpecificTuya_3', 'setOptions2', {data: buf}); - break; - } - case 'over_current_threshold': { - const state = meta.state['over_current_breaker']; - const buf = Buffer.from([1, utils.getFromLookup(state, onOffLookup), 0, utils.toNumber(value, 'over_current_threshold')]); - await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf}); - break; - } - case 'over_current_breaker': { - const threshold = meta.state['over_current_threshold']; - const number = utils.toNumber(threshold, 'over_current_threshold'); - const buf = Buffer.from([1, utils.getFromLookup(value, onOffLookup), 0, number]); - await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf}); - break; - } - case 'over_voltage_threshold': { - const state = meta.state['over_voltage_breaker']; - const buf = Buffer.from([3, utils.getFromLookup(state, onOffLookup), 0, utils.toNumber(value, 'over_voltage_breaker')]); - await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf}); - break; - } - case 'over_voltage_breaker': { - const threshold = meta.state['over_voltage_threshold']; - const number = utils.toNumber(threshold, 'over_voltage_threshold'); - const buf = Buffer.from([3, utils.getFromLookup(value, onOffLookup), 0, number]); - await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf}); - break; - } - case 'under_voltage_threshold': { - const state = meta.state['under_voltage_breaker']; - const buf = Buffer.from([4, utils.getFromLookup(state, onOffLookup), 0, utils.toNumber(value, 'under_voltage_threshold')]); - await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf}); - break; - } - case 'under_voltage_breaker': { - const threshold = meta.state['under_voltage_threshold']; - const number = utils.toNumber(threshold, 'under_voltage_breaker'); - const buf = Buffer.from([4, utils.getFromLookup(value, onOffLookup), 0, number]); - await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf}); - break; - } - default: // Unknown key - logger.warning(`Unhandled key ${key}`, NS); + case 'temperature_threshold': { + const state = meta.state['temperature_breaker']; + const buf = Buffer.from([5, utils.getFromLookup(state, onOffLookup), 0, utils.toNumber(value, 'temperature_threshold')]); + await entity.command('manuSpecificTuya_3', 'setOptions2', {data: buf}); + break; + } + case 'temperature_breaker': { + const threshold = meta.state['temperature_threshold']; + const number = utils.toNumber(threshold, 'temperature_threshold'); + const buf = Buffer.from([5, utils.getFromLookup(value, onOffLookup), 0, number]); + await entity.command('manuSpecificTuya_3', 'setOptions2', {data: buf}); + break; + } + case 'power_threshold': { + const state = meta.state['power_breaker']; + const buf = Buffer.from([7, utils.getFromLookup(state, onOffLookup), 0, utils.toNumber(value, 'power_breaker')]); + await entity.command('manuSpecificTuya_3', 'setOptions2', {data: buf}); + break; + } + case 'power_breaker': { + const threshold = meta.state['power_threshold']; + const number = utils.toNumber(threshold, 'power_breaker'); + const buf = Buffer.from([7, utils.getFromLookup(value, onOffLookup), 0, number]); + await entity.command('manuSpecificTuya_3', 'setOptions2', {data: buf}); + break; + } + case 'over_current_threshold': { + const state = meta.state['over_current_breaker']; + const buf = Buffer.from([1, utils.getFromLookup(state, onOffLookup), 0, utils.toNumber(value, 'over_current_threshold')]); + await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf}); + break; + } + case 'over_current_breaker': { + const threshold = meta.state['over_current_threshold']; + const number = utils.toNumber(threshold, 'over_current_threshold'); + const buf = Buffer.from([1, utils.getFromLookup(value, onOffLookup), 0, number]); + await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf}); + break; + } + case 'over_voltage_threshold': { + const state = meta.state['over_voltage_breaker']; + const buf = Buffer.from([3, utils.getFromLookup(state, onOffLookup), 0, utils.toNumber(value, 'over_voltage_breaker')]); + await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf}); + break; + } + case 'over_voltage_breaker': { + const threshold = meta.state['over_voltage_threshold']; + const number = utils.toNumber(threshold, 'over_voltage_threshold'); + const buf = Buffer.from([3, utils.getFromLookup(value, onOffLookup), 0, number]); + await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf}); + break; + } + case 'under_voltage_threshold': { + const state = meta.state['under_voltage_breaker']; + const buf = Buffer.from([4, utils.getFromLookup(state, onOffLookup), 0, utils.toNumber(value, 'under_voltage_threshold')]); + await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf}); + break; + } + case 'under_voltage_breaker': { + const threshold = meta.state['under_voltage_threshold']; + const number = utils.toNumber(threshold, 'under_voltage_breaker'); + const buf = Buffer.from([4, utils.getFromLookup(value, onOffLookup), 0, number]); + await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf}); + break; + } + default: // Unknown key + logger.warning(`Unhandled key ${key}`, NS); } }, } satisfies Tz.Converter, @@ -580,7 +602,7 @@ const fzLocal = { TS011F_electrical_measurement: { ...fz.electrical_measurement, convert: async (model, msg, publish, options, meta) => { - const result = await fz.electrical_measurement.convert(model, msg, publish, options, meta) ?? {}; + const result = (await fz.electrical_measurement.convert(model, msg, publish, options, meta)) ?? {}; const lookup: KeyValueString = {power: 'activePower', current: 'rmsCurrent', voltage: 'rmsVoltage'}; // Wait 5 seconds before reporting a 0 value as this could be an invalid measurement. @@ -591,18 +613,23 @@ const fzLocal = { const value = result[key]; clearTimeout(globalStore.getValue(msg.endpoint, key)); if (value === 0) { - const configuredReporting = msg.endpoint.configuredReportings.find((c) => - c.cluster.name === 'haElectricalMeasurement' && c.attribute.name === lookup[key]); - const time = ((configuredReporting ? configuredReporting.minimumReportInterval : 5) * 2) + 1; - globalStore.putValue(msg.endpoint, key, setTimeout(() => { - const payload = {[key]: value}; - // Device takes a lot of time to report power 0 in some cases. When current == 0 we can assume power == 0 - // https://github.com/Koenkk/zigbee2mqtt/discussions/19680#discussioncomment-7868445 - if (key === 'current') { - payload.power = 0; - } - publish(payload); - }, time * 1000)); + const configuredReporting = msg.endpoint.configuredReportings.find( + (c) => c.cluster.name === 'haElectricalMeasurement' && c.attribute.name === lookup[key], + ); + const time = (configuredReporting ? configuredReporting.minimumReportInterval : 5) * 2 + 1; + globalStore.putValue( + msg.endpoint, + key, + setTimeout(() => { + const payload = {[key]: value}; + // Device takes a lot of time to report power 0 in some cases. When current == 0 we can assume power == 0 + // https://github.com/Koenkk/zigbee2mqtt/discussions/19680#discussioncomment-7868445 + if (key === 'current') { + payload.power = 0; + } + publish(payload); + }, time * 1000), + ); delete result[key]; } } @@ -628,7 +655,7 @@ const fzLocal = { let i = 0; while (i < len) { const key = value.readUInt8(i); - result[key] = [value.readUInt8(i+1), value.readUInt16BE(i+2)]; + result[key] = [value.readUInt8(i + 1), value.readUInt16BE(i + 2)]; i += 4; } return result; @@ -636,24 +663,24 @@ const fzLocal = { const lookup: KeyValue = {0: 'OFF', 1: 'ON'}; const command = msg.data[2]; const data = msg.data.slice(3); - if (command == 0xE6) { + if (command == 0xe6) { const value = splitToAttributes(data); return { - 'temperature_threshold': value[0x05][1], - 'temperature_breaker': lookup[value[0x05][0]], - 'power_threshold': value[0x07][1], - 'power_breaker': lookup[value[0x07][0]], + temperature_threshold: value[0x05][1], + temperature_breaker: lookup[value[0x05][0]], + power_threshold: value[0x07][1], + power_breaker: lookup[value[0x07][0]], }; } - if (command == 0xE7) { + if (command == 0xe7) { const value = splitToAttributes(data); return { - 'over_current_threshold': value[0x01][1], - 'over_current_breaker': lookup[value[0x01][0]], - 'over_voltage_threshold': value[0x03][1], - 'over_voltage_breaker': lookup[value[0x03][0]], - 'under_voltage_threshold': value[0x04][1], - 'under_voltage_breaker': lookup[value[0x04][0]], + over_current_threshold: value[0x01][1], + over_current_breaker: lookup[value[0x01][0]], + over_voltage_threshold: value[0x03][1], + over_voltage_breaker: lookup[value[0x03][0]], + under_voltage_threshold: value[0x04][1], + under_voltage_breaker: lookup[value[0x04][0]], }; } }, @@ -673,8 +700,8 @@ const fzLocal = { // if ( Math.random() < 0.05 ) return; const priv = storeLocal.getPrivatePJ1203A(meta.device); // Detect missing or re-ordered messages but allow duplicate messages (should we?). - const expectedSeq = (priv.last_seq+priv.seq_inc) & 0xFFFF; - if ( (msg.data.seq != expectedSeq) && (msg.data.seq != priv.last_seq) ) { + const expectedSeq = (priv.last_seq + priv.seq_inc) & 0xffff; + if (msg.data.seq != expectedSeq && msg.data.seq != priv.last_seq) { logger.debug(`[PJ1203A] Missing or re-ordered message detected: Got seq=${msg.data.seq}, expected ${priv.next_seq}`, NS); priv.clear(); } @@ -687,26 +714,23 @@ const fzLocal = { const modernExtendLocal = { dpTHZBSettings(): ModernExtend { - const exp = e.composite('auto_settings', 'auto_settings', ea.STATE_SET) - .withFeature( - e.enum('enabled', ea.STATE_SET, ['on', 'off', 'none']).withDescription('Enable auto settings'), - ) - .withFeature( - e.enum('temp_greater_then', ea.STATE_SET, ['on', 'off', 'none']).withDescription('Greater action'), - ) + const exp = e + .composite('auto_settings', 'auto_settings', ea.STATE_SET) + .withFeature(e.enum('enabled', ea.STATE_SET, ['on', 'off', 'none']).withDescription('Enable auto settings')) + .withFeature(e.enum('temp_greater_then', ea.STATE_SET, ['on', 'off', 'none']).withDescription('Greater action')) .withFeature( - e.numeric('temp_greater_value', ea.STATE_SET) + e + .numeric('temp_greater_value', ea.STATE_SET) .withValueMin(-20) .withValueMax(80) .withValueStep(0.1) .withUnit('*C') .withDescription('Temperature greater than value'), ) + .withFeature(e.enum('temp_lower_then', ea.STATE_SET, ['on', 'off', 'none']).withDescription('Lower action')) .withFeature( - e.enum('temp_lower_then', ea.STATE_SET, ['on', 'off', 'none']).withDescription('Lower action'), - ) - .withFeature( - e.numeric('temp_lower_value', ea.STATE_SET) + e + .numeric('temp_lower_value', ea.STATE_SET) .withValueMin(-20) .withValueMax(80) .withValueStep(0.1) @@ -734,9 +758,9 @@ const modernExtendLocal = { const loAction = buf[13]; result = { enabled: {0x00: 'on', 0x80: 'off'}[enabled], - temp_greater_then: (gr !== 0xFF) ? {0x01: 'on', 0x00: 'off'}[grAction] : 'none', + temp_greater_then: gr !== 0xff ? {0x01: 'on', 0x00: 'off'}[grAction] : 'none', temp_greater_value: grValue, - temp_lower_then: (lo !== 0xFF) ? {0x01: 'on', 0x00: 'off'}[loAction] : 'none', + temp_lower_then: lo !== 0xff ? {0x01: 'on', 0x00: 'off'}[loAction] : 'none', temp_lower_value: loValue, }; } @@ -745,18 +769,18 @@ const modernExtendLocal = { to: (value: KeyValueAny) => { let result = ''; if (value.enabled !== 'none') { - const enabled = utils.getFromLookup(value.enabled, {'on': 0x00, 'off': 0x80}); - const gr = (value.temp_greater_then == 'none') ? 0xFF : 0x00; - const grAction = utils.getFromLookup(value.temp_greater_then, {'on': 0x01, 'off': 0x00, 'none': 0x00}); - const lo = (value.temp_lower_then == 'none') ? 0xFF : 0x00; - const loAction = utils.getFromLookup(value.temp_lower_then, {'on': 0x01, 'off': 0x00, 'none': 0x00}); + const enabled = utils.getFromLookup(value.enabled, {on: 0x00, off: 0x80}); + const gr = value.temp_greater_then == 'none' ? 0xff : 0x00; + const grAction = utils.getFromLookup(value.temp_greater_then, {on: 0x01, off: 0x00, none: 0x00}); + const lo = value.temp_lower_then == 'none' ? 0xff : 0x00; + const loAction = utils.getFromLookup(value.temp_lower_then, {on: 0x01, off: 0x00, none: 0x00}); const buf = Buffer.alloc(13); buf.writeUInt8(enabled, 0); buf.writeUInt8(gr, 1); - buf.writeInt32LE(value.temp_greater_value*10, 2); + buf.writeInt32LE(value.temp_greater_value * 10, 2); buf.writeUInt8(grAction, 6); buf.writeUInt8(lo, 7); - buf.writeInt32LE(value.temp_lower_value*10, 8); + buf.writeInt32LE(value.temp_lower_value * 10, 8); buf.writeUInt8(loAction, 12); result = buf.toString('hex'); } @@ -768,7 +792,6 @@ const modernExtendLocal = { }, }; - const definitions: Definition[] = [ { zigbeeModel: ['TS0204'], @@ -837,7 +860,8 @@ const definitions: Definition[] = [ ], exposes: (device, options) => { const exps: Expose[] = [e.contact(), e.battery(), e.battery_voltage()]; - const noTamperModels = [ // manufacturerName for models without a tamper sensor + const noTamperModels = [ + // manufacturerName for models without a tamper sensor '_TZ3000_rcuyhwe3', // Tuya ZD06 '_TZ3000_2mbfxlzr', // Tuya MC500A '_TZ3000_n2egfsli', // Tuya 19DZT @@ -849,11 +873,7 @@ const definitions: Definition[] = [ if (!device || !noTamperModels.includes(device.manufacturerName)) { exps.push(e.tamper()); } - const noBatteryLowModels = [ - '_TZ3000_26fmupbb', - '_TZ3000_oxslv1c9', - '_TZ3000_osu834un', - ]; + const noBatteryLowModels = ['_TZ3000_26fmupbb', '_TZ3000_oxslv1c9', '_TZ3000_osu834un']; if (!device || !noBatteryLowModels.includes(device.manufacturerName)) { exps.push(e.battery_low()); } @@ -866,7 +886,9 @@ const definitions: Definition[] = [ await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']); await reporting.batteryPercentageRemaining(endpoint); await reporting.batteryVoltage(endpoint); - } catch (error) {/* Fails for some*/} + } catch (error) { + /* Fails for some*/ + } }, }, { @@ -880,18 +902,12 @@ const definitions: Definition[] = [ configure: tuya.configureMagicPacket, exposes: [e.action(['single', 'double', 'hold']), e.contact(), e.battery_low(), e.tamper(), e.battery(), e.battery_voltage()], meta: { - tuyaDatapoints: [ - [101, 'action', tuya.valueConverterBasic.lookup({'single': 0, 'double': 1, 'hold': 2})], - ], + tuyaDatapoints: [[101, 'action', tuya.valueConverterBasic.lookup({single: 0, double: 1, hold: 2})]], }, - whiteLabel: [ - tuya.whitelabel('Linkoze', 'LKDSZ001', 'Door sensor with scene switch', ['_TZ3210_jowhpxop']), - ], + whiteLabel: [tuya.whitelabel('Linkoze', 'LKDSZ001', 'Door sensor with scene switch', ['_TZ3210_jowhpxop'])], }, { - fingerprint: [ - {modelID: 'TS0021', manufacturerName: '_TZ3210_3ulg9kpo'}, - ], + fingerprint: [{modelID: 'TS0021', manufacturerName: '_TZ3210_3ulg9kpo'}], model: 'LKWSZ211', vendor: 'Tuya', description: 'Scene remote with 2 keys', @@ -900,25 +916,27 @@ const definitions: Definition[] = [ onEvent: tuya.onEventSetTime, configure: tuya.configureMagicPacket, exposes: [ - e.battery(), e.action([ - 'button_1_single', 'button_1_double', 'button_1_hold', - 'button_2_single', 'button_2_double', 'button_2_hold', - ]), + e.battery(), + e.action(['button_1_single', 'button_1_double', 'button_1_hold', 'button_2_single', 'button_2_double', 'button_2_hold']), ], meta: { tuyaDatapoints: [ - [1, 'action', + [ + 1, + 'action', tuya.valueConverterBasic.lookup({ - 'button_1_single': tuya.enum(0), - 'button_1_double': tuya.enum(1), - 'button_1_hold': tuya.enum(2), + button_1_single: tuya.enum(0), + button_1_double: tuya.enum(1), + button_1_hold: tuya.enum(2), }), ], - [2, 'action', + [ + 2, + 'action', tuya.valueConverterBasic.lookup({ - 'button_2_single': tuya.enum(0), - 'button_2_double': tuya.enum(1), - 'button_2_hold': tuya.enum(2), + button_2_single: tuya.enum(0), + button_2_double: tuya.enum(1), + button_2_hold: tuya.enum(2), }), ], [10, 'battery', tuya.valueConverter.raw], @@ -930,12 +948,14 @@ const definitions: Definition[] = [ ], }, { - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_bq5c8xfe'}, + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE200_bq5c8xfe'}, {modelID: 'TS0601', manufacturerName: '_TZE200_bjawzodf'}, {modelID: 'TS0601', manufacturerName: '_TZE200_qyflbnbj'}, {modelID: 'TS0601', manufacturerName: '_TZE200_vs0skpuc'}, {modelID: 'TS0601', manufacturerName: '_TZE200_44af8vyi'}, - {modelID: 'TS0601', manufacturerName: '_TZE200_zl1kmjqx'}], + {modelID: 'TS0601', manufacturerName: '_TZE200_zl1kmjqx'}, + ], model: 'TS0601_temperature_humidity_sensor_1', vendor: 'Tuya', description: 'Temperature & humidity sensor', @@ -959,35 +979,39 @@ const definitions: Definition[] = [ extend: [ tuya.modernExtend.tuyaMagicPacket(), tuya.modernExtend.combineActions([ - tuya.modernExtend.dpAction({dp: 1, lookup: {'scene_1': 0}}), - tuya.modernExtend.dpAction({dp: 2, lookup: {'scene_2': 0}}), - tuya.modernExtend.dpAction({dp: 3, lookup: {'scene_3': 0}}), - tuya.modernExtend.dpAction({dp: 4, lookup: {'scene_4': 0}}), + tuya.modernExtend.dpAction({dp: 1, lookup: {scene_1: 0}}), + tuya.modernExtend.dpAction({dp: 2, lookup: {scene_2: 0}}), + tuya.modernExtend.dpAction({dp: 3, lookup: {scene_3: 0}}), + tuya.modernExtend.dpAction({dp: 4, lookup: {scene_4: 0}}), ]), tuya.modernExtend.dpBinary({ name: 'vibration', - dp: 0x6c, type: tuya.dataTypes.enum, + dp: 0x6c, + type: tuya.dataTypes.enum, valueOn: ['ON', 1], valueOff: ['OFF', 0], description: 'Enable vibration', }), tuya.modernExtend.dpBinary({ name: 'approach', - dp: 0x6b, type: tuya.dataTypes.enum, + dp: 0x6b, + type: tuya.dataTypes.enum, valueOn: ['ON', 1], valueOff: ['OFF', 0], description: 'Enable approach detection', }), tuya.modernExtend.dpBinary({ name: 'illumination', - dp: 0x6a, type: tuya.dataTypes.enum, + dp: 0x6a, + type: tuya.dataTypes.enum, valueOn: ['ON', 1], valueOff: ['OFF', 0], description: 'Enable illumination detection', }), tuya.modernExtend.dpBinary({ name: 'backlight', - dp: 0x69, type: tuya.dataTypes.enum, + dp: 0x69, + type: tuya.dataTypes.enum, valueOn: ['ON', 1], valueOff: ['OFF', 0], description: 'Enable backlight', @@ -1002,32 +1026,39 @@ const definitions: Definition[] = [ extend: [ tuya.modernExtend.tuyaMagicPacket(), tuya.modernExtend.combineActions([ - tuya.modernExtend.dpAction({dp: 1, lookup: {'scene_1': 0}}), - tuya.modernExtend.dpAction({dp: 2, lookup: {'scene_2': 0}}), - tuya.modernExtend.dpAction({dp: 3, lookup: {'scene_3': 0}}), - tuya.modernExtend.dpAction({dp: 4, lookup: {'scene_4': 0}}), - tuya.modernExtend.dpAction({dp: 5, lookup: {'scene_5': 0}}), - tuya.modernExtend.dpAction({dp: 6, lookup: {'scene_6': 0}}), - tuya.modernExtend.dpAction({dp: 7, lookup: {'scene_7': 0}}), - tuya.modernExtend.dpAction({dp: 8, lookup: {'scene_8': 0}}), - tuya.modernExtend.dpAction({dp: 9, lookup: {'scene_9': 0}}), - tuya.modernExtend.dpAction({dp: 10, lookup: {'scene_10': 0}}), - tuya.modernExtend.dpAction({dp: 11, lookup: {'scene_11': 0}}), - tuya.modernExtend.dpAction({dp: 12, lookup: {'scene_12': 0}}), - tuya.modernExtend.dpAction({dp: 13, lookup: {'scene_13': 0}}), - tuya.modernExtend.dpAction({dp: 14, lookup: {'scene_14': 0}}), - tuya.modernExtend.dpAction({dp: 15, lookup: {'scene_15': 0}}), - tuya.modernExtend.dpAction({dp: 16, lookup: {'scene_16': 0}}), - tuya.modernExtend.dpAction({dp: 101, lookup: {'scene_17': 0}}), - tuya.modernExtend.dpAction({dp: 102, lookup: {'scene_18': 0}}), + tuya.modernExtend.dpAction({dp: 1, lookup: {scene_1: 0}}), + tuya.modernExtend.dpAction({dp: 2, lookup: {scene_2: 0}}), + tuya.modernExtend.dpAction({dp: 3, lookup: {scene_3: 0}}), + tuya.modernExtend.dpAction({dp: 4, lookup: {scene_4: 0}}), + tuya.modernExtend.dpAction({dp: 5, lookup: {scene_5: 0}}), + tuya.modernExtend.dpAction({dp: 6, lookup: {scene_6: 0}}), + tuya.modernExtend.dpAction({dp: 7, lookup: {scene_7: 0}}), + tuya.modernExtend.dpAction({dp: 8, lookup: {scene_8: 0}}), + tuya.modernExtend.dpAction({dp: 9, lookup: {scene_9: 0}}), + tuya.modernExtend.dpAction({dp: 10, lookup: {scene_10: 0}}), + tuya.modernExtend.dpAction({dp: 11, lookup: {scene_11: 0}}), + tuya.modernExtend.dpAction({dp: 12, lookup: {scene_12: 0}}), + tuya.modernExtend.dpAction({dp: 13, lookup: {scene_13: 0}}), + tuya.modernExtend.dpAction({dp: 14, lookup: {scene_14: 0}}), + tuya.modernExtend.dpAction({dp: 15, lookup: {scene_15: 0}}), + tuya.modernExtend.dpAction({dp: 16, lookup: {scene_16: 0}}), + tuya.modernExtend.dpAction({dp: 101, lookup: {scene_17: 0}}), + tuya.modernExtend.dpAction({dp: 102, lookup: {scene_18: 0}}), ]), ], }, { fingerprint: tuya.fingerprint('TS0601', [ - '_TZE200_yjjdcqsq', '_TZE200_9yapgbuv', '_TZE200_utkemkbs', '_TZE204_utkemkbs', - '_TZE204_9yapgbuv', '_TZE204_upagmta9', '_TZE200_cirvgep4', '_TZE200_upagmta9', - '_TZE204_yjjdcqsq', '_TZE204_cirvgep4', + '_TZE200_yjjdcqsq', + '_TZE200_9yapgbuv', + '_TZE200_utkemkbs', + '_TZE204_utkemkbs', + '_TZE204_9yapgbuv', + '_TZE204_upagmta9', + '_TZE200_cirvgep4', + '_TZE200_upagmta9', + '_TZE204_yjjdcqsq', + '_TZE204_cirvgep4', ]), model: 'TS0601_temperature_humidity_sensor_2', vendor: 'Tuya', @@ -1095,23 +1126,51 @@ const definitions: Definition[] = [ await device.getEndpoint(1).command('manuSpecificTuya', 'dataQuery', {}); }, exposes: [ - e.temperature(), e.humidity(), e.battery(), + e.temperature(), + e.humidity(), + e.battery(), e.enum('temperature_unit', ea.STATE_SET, ['celsius', 'fahrenheit']).withDescription('Temperature unit'), - e.numeric('max_temperature_alarm', ea.STATE_SET).withUnit('°C').withValueMin(-20).withValueMax(60) + e + .numeric('max_temperature_alarm', ea.STATE_SET) + .withUnit('°C') + .withValueMin(-20) + .withValueMax(60) .withDescription('Alarm temperature max'), - e.numeric('min_temperature_alarm', ea.STATE_SET).withUnit('°C').withValueMin(-20).withValueMax(60) + e + .numeric('min_temperature_alarm', ea.STATE_SET) + .withUnit('°C') + .withValueMin(-20) + .withValueMax(60) .withDescription('Alarm temperature min'), e.numeric('max_humidity_alarm', ea.STATE_SET).withUnit('%').withValueMin(0).withValueMax(100).withDescription('Alarm humidity max'), e.numeric('min_humidity_alarm', ea.STATE_SET).withUnit('%').withValueMin(0).withValueMax(100).withDescription('Alarm humidity min'), e.enum('temperature_alarm', ea.STATE_SET, ['lower_alarm', 'upper_alarm', 'cancel']).withDescription('Temperature alarm'), e.enum('humidity_alarm', ea.STATE_SET, ['lower_alarm', 'upper_alarm', 'cancel']).withDescription('Humidity alarm'), - e.numeric('temperature_periodic_report', ea.STATE_SET).withUnit('%').withValueMin(0).withValueMax(100) + e + .numeric('temperature_periodic_report', ea.STATE_SET) + .withUnit('%') + .withValueMin(0) + .withValueMax(100) .withDescription('Temp periodic report'), - e.numeric('humidity_periodic_report', ea.STATE_SET).withUnit('%').withValueMin(0).withValueMax(100) + e + .numeric('humidity_periodic_report', ea.STATE_SET) + .withUnit('%') + .withValueMin(0) + .withValueMax(100) .withDescription('Humidity periodic report'), - e.numeric('temperature_sensitivity', ea.STATE_SET).withUnit('°C').withValueMin(3).withValueMax(10).withValueStep(1) + e + .numeric('temperature_sensitivity', ea.STATE_SET) + .withUnit('°C') + .withValueMin(3) + .withValueMax(10) + .withValueStep(1) .withDescription('Sensitivity of temperature'), - e.numeric('humidity_sensitivity', ea.STATE_SET).withUnit('%').withValueMin(3).withValueMax(10).withValueStep(1) + e + .numeric('humidity_sensitivity', ea.STATE_SET) + .withUnit('%') + .withValueMin(3) + .withValueMax(10) + .withValueStep(1) .withDescription('Sensitivity of humidity'), ], meta: { @@ -1124,10 +1183,12 @@ const definitions: Definition[] = [ [11, 'min_temperature_alarm', tuya.valueConverter.divideBy10], [12, 'max_humidity_alarm', tuya.valueConverter.raw], [13, 'min_humidity_alarm', tuya.valueConverter.raw], - [14, 'temperature_alarm', tuya.valueConverterBasic.lookup( - {'lower_alarm': tuya.enum(0), 'upper_alarm': tuya.enum(1), 'cancel': tuya.enum(2)})], - [15, 'humidity_alarm', tuya.valueConverterBasic.lookup( - {'lower_alarm': tuya.enum(0), 'upper_alarm': tuya.enum(1), 'cancel': tuya.enum(2)})], + [ + 14, + 'temperature_alarm', + tuya.valueConverterBasic.lookup({lower_alarm: tuya.enum(0), upper_alarm: tuya.enum(1), cancel: tuya.enum(2)}), + ], + [15, 'humidity_alarm', tuya.valueConverterBasic.lookup({lower_alarm: tuya.enum(0), upper_alarm: tuya.enum(1), cancel: tuya.enum(2)})], [17, 'temperature_periodic_report', tuya.valueConverter.raw], [18, 'humidity_periodic_report', tuya.valueConverter.raw], [19, 'temperature_sensitivity', tuya.valueConverter.raw], @@ -1152,9 +1213,7 @@ const definitions: Definition[] = [ [8, 'humidity', tuya.valueConverter.raw], ], }, - whiteLabel: [ - tuya.whitelabel('Aubess', '1005005194831629', 'Contact, temperature and humidity sensor', ['_TZE200_nvups4nh']), - ], + whiteLabel: [tuya.whitelabel('Aubess', '1005005194831629', 'Contact, temperature and humidity sensor', ['_TZE200_nvups4nh'])], }, { fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_vzqtvljm'}], @@ -1166,11 +1225,13 @@ const definitions: Definition[] = [ exposes: [e.temperature(), e.humidity(), e.illuminance_lux(), e.battery()], }, { - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_8ygsuhe1'}, + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE200_8ygsuhe1'}, {modelID: 'TS0601', manufacturerName: '_TZE200_yvx5lh6k'}, {modelID: 'TS0601', manufacturerName: '_TZE200_ryfmq5rl'}, {modelID: 'TS0601', manufacturerName: '_TZE200_c2fmom5z'}, - {modelID: 'TS0601', manufacturerName: '_TZE200_mja3fuja'}], + {modelID: 'TS0601', manufacturerName: '_TZE200_mja3fuja'}, + ], model: 'TS0601_air_quality_sensor', vendor: 'Tuya', description: 'Air quality sensor', @@ -1202,8 +1263,14 @@ const definitions: Definition[] = [ description: 'Smart air house keeper', fromZigbee: [legacy.fromZigbee.tuya_air_quality], toZigbee: [], - exposes: [e.temperature(), e.humidity(), e.co2(), e.voc().withUnit('ppm'), e.formaldehyd().withUnit('µg/m³'), - e.pm25().withValueMin(0).withValueMax(999).withValueStep(1)], + exposes: [ + e.temperature(), + e.humidity(), + e.co2(), + e.voc().withUnit('ppm'), + e.formaldehyd().withUnit('µg/m³'), + e.pm25().withValueMin(0).withValueMax(999).withValueStep(1), + ], }, { fingerprint: tuya.fingerprint('TS0601', ['_TZE200_ogkdpgy2', '_TZE200_3ejwxpmu']), @@ -1251,12 +1318,15 @@ const definitions: Definition[] = [ toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, exposes: [ - e.gas(), tuya.exposes.gasValue().withUnit('LEL'), tuya.exposes.selfTest(), tuya.exposes.selfTestResult(), + e.gas(), + tuya.exposes.gasValue().withUnit('LEL'), + tuya.exposes.selfTest(), + tuya.exposes.selfTestResult(), tuya.exposes.silence(), - e.enum('alarm_ringtone', ea.STATE_SET, [ - 'melody_1', 'melody_2', 'melody_3', 'melody_4', 'melody_5']).withDescription('Ringtone of the alarm'), - e.numeric('alarm_time', ea.STATE_SET).withValueMin(1).withValueMax(180).withValueStep(1) - .withUnit('s').withDescription('Alarm time'), + e + .enum('alarm_ringtone', ea.STATE_SET, ['melody_1', 'melody_2', 'melody_3', 'melody_4', 'melody_5']) + .withDescription('Ringtone of the alarm'), + e.numeric('alarm_time', ea.STATE_SET).withValueMin(1).withValueMax(180).withValueStep(1).withUnit('s').withDescription('Alarm time'), e.binary('preheat', ea.STATE, true, false).withDescription('Indicates sensor preheat is active'), ], whiteLabel: [ @@ -1267,8 +1337,17 @@ const definitions: Definition[] = [ tuyaDatapoints: [ [1, 'gas', tuya.valueConverter.trueFalseEnum0], [2, 'gas_value', tuya.valueConverter.divideBy10], - [6, 'alarm_ringtone', tuya.valueConverterBasic.lookup({'melody_1': tuya.enum(0), 'melody_2': tuya.enum(1), - 'melody_3': tuya.enum(2), 'melody_4': tuya.enum(3), 'melody_5': tuya.enum(4)})], + [ + 6, + 'alarm_ringtone', + tuya.valueConverterBasic.lookup({ + melody_1: tuya.enum(0), + melody_2: tuya.enum(1), + melody_3: tuya.enum(2), + melody_4: tuya.enum(3), + melody_5: tuya.enum(4), + }), + ], [7, 'alarm_time', tuya.valueConverter.raw], [8, 'self_test', tuya.valueConverter.raw], [9, 'self_test_result', tuya.valueConverter.selfTestResult], @@ -1323,8 +1402,11 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'TS0001', manufacturerName: '_TZ3000_hktqahrq'}, {manufacturerName: '_TZ3000_hktqahrq'}, - {manufacturerName: '_TZ3000_q6a3tepg'}, {modelID: 'TS000F', manufacturerName: '_TZ3000_m9af2l6g'}, + fingerprint: [ + {modelID: 'TS0001', manufacturerName: '_TZ3000_hktqahrq'}, + {manufacturerName: '_TZ3000_hktqahrq'}, + {manufacturerName: '_TZ3000_q6a3tepg'}, + {modelID: 'TS000F', manufacturerName: '_TZ3000_m9af2l6g'}, {modelID: 'TS000F', manufacturerName: '_TZ3000_mx3vgyea'}, {modelID: 'TS000F', manufacturerName: '_TZ3000_skueekg3'}, {modelID: 'TS000F', manufacturerName: '_TZ3000_dlhhrhs8'}, @@ -1340,11 +1422,13 @@ const definitions: Definition[] = [ {modelID: 'TS0001', manufacturerName: '_TZ3000_fdxihpp7'}, {modelID: 'TS0001', manufacturerName: '_TZ3000_qsp2pwtf'}, {modelID: 'TS0001', manufacturerName: '_TZ3000_kycczpw8'}, - {modelID: 'TS0001', manufacturerName: '_TZ3000_46t1rvdu'}], + {modelID: 'TS0001', manufacturerName: '_TZ3000_46t1rvdu'}, + ], model: 'WHD02', vendor: 'Tuya', whiteLabel: [ - {vendor: 'Tuya', model: 'iHSW02'}, {vendor: 'Aubess', model: 'TMZ02'}, + {vendor: 'Tuya', model: 'iHSW02'}, + {vendor: 'Aubess', model: 'TMZ02'}, tuya.whitelabel('Tuya', 'QS-zigbee-S08-16A-RF', 'Wall switch module', ['_TZ3000_dlhhrhs8']), ], description: 'Wall switch module', @@ -1357,9 +1441,19 @@ const definitions: Definition[] = [ }, }, { - fingerprint: tuya.fingerprint('TS011F', ['_TZ3000_mvn6jl7x', '_TZ3000_raviyuvk', '_TYZB01_hlla45kx', '_TZ3000_92qd4sqa', - '_TZ3000_zwaadvus', '_TZ3000_k6fvknrr', '_TZ3000_6s5dc9lx', '_TZ3000_helyqdvs', '_TZ3000_rgpqqmbj', '_TZ3000_8nyaanzb', - '_TZ3000_iy2c3n6p']), + fingerprint: tuya.fingerprint('TS011F', [ + '_TZ3000_mvn6jl7x', + '_TZ3000_raviyuvk', + '_TYZB01_hlla45kx', + '_TZ3000_92qd4sqa', + '_TZ3000_zwaadvus', + '_TZ3000_k6fvknrr', + '_TZ3000_6s5dc9lx', + '_TZ3000_helyqdvs', + '_TZ3000_rgpqqmbj', + '_TZ3000_8nyaanzb', + '_TZ3000_iy2c3n6p', + ]), model: 'TS011F_2_gang_wall', vendor: 'Tuya', description: '2 gang wall outlet', @@ -1370,18 +1464,24 @@ const definitions: Definition[] = [ tuya.whitelabel('Rylike', 'RY-WS02Z', '2 gang socket outlet AU', ['_TZ3000_rgpqqmbj', '_TZ3000_8nyaanzb', '_TZ3000_iy2c3n6p']), ], endpoint: (device) => { - return {'l1': 1, 'l2': 2}; + return {l1: 1, l2: 2}; }, meta: {multiEndpoint: true, multiEndpointSkip: ['power_on_behavior']}, configure: tuya.configureMagicPacket, }, { - fingerprint: [{modelID: 'TS011F', manufacturerName: '_TZ3000_rk2yzt0u'}, - {modelID: 'TS0001', manufacturerName: '_TZ3000_o4cjetlm'}, {manufacturerName: '_TZ3000_o4cjetlm'}, - {modelID: 'TS0001', manufacturerName: '_TZ3000_iedbgyxt'}, {modelID: 'TS0001', manufacturerName: '_TZ3000_h3noz0a5'}, - {modelID: 'TS0001', manufacturerName: '_TYZB01_4tlksk8a'}, {modelID: 'TS0011', manufacturerName: '_TYZB01_rifa0wlb'}, - {modelID: 'TS0001', manufacturerName: '_TZ3000_5ucujjts'}, {modelID: 'TS0001', manufacturerName: '_TZ3000_h8ngtlxy'}, - {modelID: 'TS0001', manufacturerName: '_TZ3000_w0ypwa1f'}, {modelID: 'TS0001', manufacturerName: '_TZ3000_wpueorev'}, + fingerprint: [ + {modelID: 'TS011F', manufacturerName: '_TZ3000_rk2yzt0u'}, + {modelID: 'TS0001', manufacturerName: '_TZ3000_o4cjetlm'}, + {manufacturerName: '_TZ3000_o4cjetlm'}, + {modelID: 'TS0001', manufacturerName: '_TZ3000_iedbgyxt'}, + {modelID: 'TS0001', manufacturerName: '_TZ3000_h3noz0a5'}, + {modelID: 'TS0001', manufacturerName: '_TYZB01_4tlksk8a'}, + {modelID: 'TS0011', manufacturerName: '_TYZB01_rifa0wlb'}, + {modelID: 'TS0001', manufacturerName: '_TZ3000_5ucujjts'}, + {modelID: 'TS0001', manufacturerName: '_TZ3000_h8ngtlxy'}, + {modelID: 'TS0001', manufacturerName: '_TZ3000_w0ypwa1f'}, + {modelID: 'TS0001', manufacturerName: '_TZ3000_wpueorev'}, ], model: 'ZN231392', vendor: 'Tuya', @@ -1392,9 +1492,7 @@ const definitions: Definition[] = [ const endpoint = device.getEndpoint(1); await endpoint.read('genOnOff', ['onOff', 'moesStartUpOnOff']); }, - whiteLabel: [ - tuya.whitelabel('Nous', 'LZ3', 'Smart water/gas valve', ['_TZ3000_abjodzas']), - ], + whiteLabel: [tuya.whitelabel('Nous', 'LZ3', 'Smart water/gas valve', ['_TZ3000_abjodzas'])], }, { zigbeeModel: ['CK-BL702-AL-01(7009_Z102LG03-1)', 'CK-BL702-AL-01(7009_Z102LG04-2)'], @@ -1413,17 +1511,17 @@ const definitions: Definition[] = [ await tuya.configureMagicPacket(device, coordinatorEndpoint); await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff']); }, - whiteLabel: [ - tuya.whitelabel('Zemismart', 'ZM-H7', 'Hand wave wall smart switch', ['_TZ3000_jcqs2mrv']), - ], + whiteLabel: [tuya.whitelabel('Zemismart', 'ZM-H7', 'Hand wave wall smart switch', ['_TZ3000_jcqs2mrv'])], }, { zigbeeModel: ['TS0505B'], model: 'TS0505B_1', vendor: 'Tuya', description: 'Zigbee RGB+CCT light', - whiteLabel: [{vendor: 'Mercator Ikuü', model: 'SMD4106W-RGB-ZB'}, - {vendor: 'Tuya', model: 'A5C-21F7-01'}, {vendor: 'Mercator Ikuü', model: 'S9E27LED9W-RGB-Z'}, + whiteLabel: [ + {vendor: 'Mercator Ikuü', model: 'SMD4106W-RGB-ZB'}, + {vendor: 'Tuya', model: 'A5C-21F7-01'}, + {vendor: 'Mercator Ikuü', model: 'S9E27LED9W-RGB-Z'}, {vendor: 'Aldi', model: 'L122CB63H11A9.0W', description: 'LIGHTWAY smart home LED-lamp - bulb'}, {vendor: 'Lidl', model: '14153706L', description: 'Livarno smart LED ceiling light'}, {vendor: 'Zemismart', model: 'LXZB-ZB-09A', description: 'Zemismart LED Surface Mounted Downlight 9W RGBW'}, @@ -1470,8 +1568,7 @@ const definitions: Definition[] = [ }, }, { - fingerprint: tuya.fingerprint('TS0505B', - ['_TZ3210_iystcadi', '_TZ3210_mja6r5ix', '_TZ3210_it1u8ahz']), + fingerprint: tuya.fingerprint('TS0505B', ['_TZ3210_iystcadi', '_TZ3210_mja6r5ix', '_TZ3210_it1u8ahz']), model: 'TS0505B_2', vendor: 'Tuya', description: 'Zigbee RGB+CCT light', @@ -1506,10 +1603,7 @@ const definitions: Definition[] = [ model: 'TS0503B', vendor: 'Tuya', description: 'Zigbee RGB light', - whiteLabel: [ - {vendor: 'BTF-Lighting', model: 'C03Z'}, - tuya.whitelabel('MiBoxer', 'FUT037Z', 'RGB led controller', ['_TZ3210_778drfdt']), - ], + whiteLabel: [{vendor: 'BTF-Lighting', model: 'C03Z'}, tuya.whitelabel('MiBoxer', 'FUT037Z', 'RGB led controller', ['_TZ3210_778drfdt'])], extend: [tuya.modernExtend.tuyaLight({color: true})], }, { @@ -1538,9 +1632,7 @@ const definitions: Definition[] = [ description: 'Zigbee dimmer', vendor: 'Tuya', extend: [tuyaLight({configureReporting: true, effect: false})], - whiteLabel: [ - tuya.whitelabel('Tuya', 'L1(ZW)', 'Light dimmer 0-10V', ['_TZB210_rkgngb5o']), - ], + whiteLabel: [tuya.whitelabel('Tuya', 'L1(ZW)', 'Light dimmer 0-10V', ['_TZB210_rkgngb5o'])], }, { fingerprint: tuya.fingerprint('TS0501B', ['_TZB210_g01ie5wu']), @@ -1584,20 +1676,24 @@ const definitions: Definition[] = [ await reporting.batteryPercentageRemaining(endpoint); await reporting.batteryVoltage(endpoint); }, - exposes: [e.battery(), e.battery_voltage(), e.occupancy(), e.action(['single', 'double', 'hold']), - e.enum('light', ea.STATE, ['dark', 'bright'])], + exposes: [ + e.battery(), + e.battery_voltage(), + e.occupancy(), + e.action(['single', 'double', 'hold']), + e.enum('light', ea.STATE, ['dark', 'bright']), + ], meta: { tuyaDatapoints: [ - [102, 'light', tuya.valueConverterBasic.lookup({'dark': false, 'bright': true})], - [101, 'action', tuya.valueConverterBasic.lookup({'single': 0, 'double': 1, 'hold': 2})], + [102, 'light', tuya.valueConverterBasic.lookup({dark: false, bright: true})], + [101, 'action', tuya.valueConverterBasic.lookup({single: 0, double: 1, hold: 2})], ], }, - whiteLabel: [ - {vendor: 'Linkoze', model: 'LKMSZ001'}, - ], + whiteLabel: [{vendor: 'Linkoze', model: 'LKMSZ001'}], }, { - fingerprint: [{modelID: 'TS0202', manufacturerName: '_TYZB01_jytabjkb'}, + fingerprint: [ + {modelID: 'TS0202', manufacturerName: '_TYZB01_jytabjkb'}, {modelID: 'TS0202', manufacturerName: '_TZ3000_lltemgsf'}, {modelID: 'TS0202', manufacturerName: '_TYZB01_5nr7ncpl'}, {modelID: 'TS0202', manufacturerName: '_TZ3000_mg4dy6z6'}, @@ -1622,7 +1718,8 @@ const definitions: Definition[] = [ model: 'TS0202', vendor: 'Tuya', description: 'Motion sensor', - whiteLabel: [{vendor: 'Mercator Ikuü', model: 'SMA02P'}, + whiteLabel: [ + {vendor: 'Mercator Ikuü', model: 'SMA02P'}, {vendor: 'Tuya', model: 'TY-ZPR06'}, {vendor: 'Tesla Smart', model: 'TS0202'}, tuya.whitelabel('MiBoxer', 'PIR1-ZB', 'PIR sensor', ['_TZ3040_wqmtjsyk']), @@ -1651,7 +1748,9 @@ const definitions: Definition[] = [ try { await reporting.batteryPercentageRemaining(endpoint); await reporting.batteryVoltage(endpoint); - } catch (error) {/* Fails for some https://github.com/Koenkk/zigbee2mqtt/issues/13708 */} + } catch (error) { + /* Fails for some https://github.com/Koenkk/zigbee2mqtt/issues/13708 */ + } }, }, { @@ -1662,14 +1761,15 @@ const definitions: Definition[] = [ fromZigbee: [fz.ias_occupancy_alarm_1, fz.battery, fz.ignore_basic_report, fz.ZM35HQ_attr, legacy.fromZigbee.ZM35HQ_battery], toZigbee: [tz.ZM35HQ_attr], extend: [quirkCheckinInterval(15000)], - exposes: [e.occupancy(), e.battery_low(), e.battery(), + exposes: [ + e.occupancy(), + e.battery_low(), + e.battery(), e.enum('sensitivity', ea.ALL, ['low', 'medium', 'high']).withDescription('PIR sensor sensitivity'), e.enum('keep_time', ea.ALL, [30, 60, 120]).withDescription('PIR keep time in seconds'), ], configure: tuya.configureMagicPacket, - whiteLabel: [ - tuya.whitelabel('Aubess', '40ZH-O', 'Motion sensor', ['_TZ3040_msl6wxk9']), - ], + whiteLabel: [tuya.whitelabel('Aubess', '40ZH-O', 'Motion sensor', ['_TZ3040_msl6wxk9'])], }, { fingerprint: tuya.fingerprint('TS0202', ['_TZ3000_mcxw5ehu', '_TZ3000_6ygjfyll', '_TZ3040_6ygjfyll', '_TZ3000_msl6wxk9']), @@ -1679,7 +1779,11 @@ const definitions: Definition[] = [ fromZigbee: [fz.ias_occupancy_alarm_1, fz.ignore_basic_report, fz.ZM35HQ_attr, fz.battery], toZigbee: [tz.ZM35HQ_attr], extend: [quirkCheckinInterval(15000)], - exposes: [e.occupancy(), e.battery_low(), e.battery(), e.battery_voltage(), + exposes: [ + e.occupancy(), + e.battery_low(), + e.battery(), + e.battery_voltage(), e.enum('sensitivity', ea.ALL, ['low', 'medium', 'high']).withDescription('PIR sensor sensitivity'), e.enum('keep_time', ea.ALL, [30, 60, 120]).withDescription('PIR keep time in seconds'), ], @@ -1689,9 +1793,7 @@ const definitions: Definition[] = [ await reporting.batteryPercentageRemaining(endpoint); await reporting.batteryVoltage(endpoint); }, - whiteLabel: [ - tuya.whitelabel('Tuya', 'ZMS-102', 'Motion sensor', ['_TZ3000_msl6wxk9']), - ], + whiteLabel: [tuya.whitelabel('Tuya', 'ZMS-102', 'Motion sensor', ['_TZ3000_msl6wxk9'])], }, { fingerprint: tuya.fingerprint('TS0202', ['_TZ3000_o4mkahkc']), @@ -1701,7 +1803,12 @@ const definitions: Definition[] = [ fromZigbee: [fz.ias_occupancy_alarm_1, fz.ignore_basic_report, fz.ZM35HQ_attr, fz.battery], toZigbee: [tz.ZM35HQ_attr], extend: [quirkCheckinInterval(15000)], - exposes: [e.occupancy(), e.battery_low(), e.tamper(), e.battery(), e.battery_voltage(), + exposes: [ + e.occupancy(), + e.battery_low(), + e.tamper(), + e.battery(), + e.battery_voltage(), e.enum('sensitivity', ea.ALL, ['low', 'medium', 'high']).withDescription('PIR sensor sensitivity'), e.enum('keep_time', ea.ALL, [30, 60, 120]).withDescription('PIR keep time in seconds'), ], @@ -1713,14 +1820,16 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'TS0207', manufacturerName: '_TZ3000_m0vaazab'}, + fingerprint: [ + {modelID: 'TS0207', manufacturerName: '_TZ3000_m0vaazab'}, {modelID: 'TS0207', manufacturerName: '_TZ3000_ufttklsz'}, {modelID: 'TS0207', manufacturerName: '_TZ3000_nkkl7uzv'}, {modelID: 'TS0207', manufacturerName: '_TZ3000_misw04hq'}, {modelID: 'TS0207', manufacturerName: '_TZ3000_nlsszmzl'}, {modelID: 'TS0207', manufacturerName: '_TZ3000_gszjt2xx'}, {modelID: 'TS0207', manufacturerName: '_TZ3000_wlquqiiz'}, - {modelID: 'TS0207', manufacturerName: '_TZ3000_5k5vh43t'}], + {modelID: 'TS0207', manufacturerName: '_TZ3000_5k5vh43t'}, + ], model: 'TS0207_repeater', vendor: 'Tuya', description: 'Repeater', @@ -1752,7 +1861,8 @@ const definitions: Definition[] = [ }, exposes: (device, options) => { const exps: Expose[] = [e.water_leak(), e.battery_low(), e.battery()]; - const noTamperModels = [ // manufacturerName for models without a tamper sensor + const noTamperModels = [ + // manufacturerName for models without a tamper sensor '_TZ3000_mugyhz0q', // Tuya 899WZ '_TZ3000_k4ej3ww2', // Aubess IH-K665 '_TZ3000_kstbkt6a', // Aubess IH-K665 @@ -1791,7 +1901,7 @@ const definitions: Definition[] = [ extend: [tuya.modernExtend.tuyaOnOff()], exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2')], endpoint: (device) => { - return {'l1': 1, 'l2': 7}; + return {l1: 1, l2: 7}; }, meta: {multiEndpoint: true, disableDefaultResponse: true}, configure: async (device, coordinatorEndpoint) => { @@ -1838,19 +1948,42 @@ const definitions: Definition[] = [ }, }, { - fingerprint: tuya.fingerprint('TS0601', ['_TZE200_ip2akl4w', '_TZE200_1agwnems', '_TZE200_la2c2uo9', '_TZE200_579lguh2', - '_TZE200_vucankjx', '_TZE200_4mh6tyyo', '_TZE204_hlx9tnzb', '_TZE204_n9ctkb6j', '_TZE204_9qhuzgo0', '_TZE200_9cxuhakf', - '_TZE200_a0syesf5', '_TZE200_3p5ydos3', '_TZE200_swaamsoy', '_TZE200_ojzhk75b', '_TZE200_w4cryh2i', '_TZE200_dfxkcots', - '_TZE200_9i9dt8is', '_TZE200_ctq0k47x', '_TZE200_ebwgzdqq', '_TZE204_vevc4c6g', '_TZE200_0nauxa0p']), + fingerprint: tuya.fingerprint('TS0601', [ + '_TZE200_ip2akl4w', + '_TZE200_1agwnems', + '_TZE200_la2c2uo9', + '_TZE200_579lguh2', + '_TZE200_vucankjx', + '_TZE200_4mh6tyyo', + '_TZE204_hlx9tnzb', + '_TZE204_n9ctkb6j', + '_TZE204_9qhuzgo0', + '_TZE200_9cxuhakf', + '_TZE200_a0syesf5', + '_TZE200_3p5ydos3', + '_TZE200_swaamsoy', + '_TZE200_ojzhk75b', + '_TZE200_w4cryh2i', + '_TZE200_dfxkcots', + '_TZE200_9i9dt8is', + '_TZE200_ctq0k47x', + '_TZE200_ebwgzdqq', + '_TZE204_vevc4c6g', + '_TZE200_0nauxa0p', + ]), model: 'TS0601_dimmer_1_gang_1', vendor: 'Tuya', description: '1 gang smart dimmer', fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, - exposes: [tuya.exposes.lightBrightnessWithMinMax(), tuya.exposes.countdown(), tuya.exposes.lightType(), + exposes: [ + tuya.exposes.lightBrightnessWithMinMax(), + tuya.exposes.countdown(), + tuya.exposes.lightType(), e.power_on_behavior().withAccess(ea.STATE_SET), - tuya.exposes.backlightModeOffNormalInverted().withAccess(ea.STATE_SET)], + tuya.exposes.backlightModeOffNormalInverted().withAccess(ea.STATE_SET), + ], meta: { tuyaDatapoints: [ [1, 'state', tuya.valueConverter.onOff, {skip: tuya.skip.stateOnAndBrightnessPresent}], @@ -1888,9 +2021,13 @@ const definitions: Definition[] = [ fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, - exposes: [tuya.exposes.lightBrightness(), tuya.exposes.countdown(), tuya.exposes.lightType(), + exposes: [ + tuya.exposes.lightBrightness(), + tuya.exposes.countdown(), + tuya.exposes.lightType(), e.power_on_behavior().withAccess(ea.STATE_SET), - tuya.exposes.backlightModeOffNormalInverted().withAccess(ea.STATE_SET)], + tuya.exposes.backlightModeOffNormalInverted().withAccess(ea.STATE_SET), + ], meta: { tuyaDatapoints: [ [1, 'state', tuya.valueConverter.onOff, {skip: tuya.skip.stateOnAndBrightnessPresent}], @@ -1915,10 +2052,15 @@ const definitions: Definition[] = [ ], extend: [ tuya.modernExtend.tuyaMagicPacket(), - deviceEndpoints({endpoints: {'l1': 1, 'l2': 1}}), + deviceEndpoints({endpoints: {l1: 1, l2: 1}}), tuya.modernExtend.dpLight({ - state: {dp: 1, type: tuya.dataTypes.bool, valueOn: ['ON', true], valueOff: ['OFF', false], - skip: tuya.skip.stateOnAndBrightnessPresent}, + state: { + dp: 1, + type: tuya.dataTypes.bool, + valueOn: ['ON', true], + valueOff: ['OFF', false], + skip: tuya.skip.stateOnAndBrightnessPresent, + }, brightness: {dp: 2, type: tuya.dataTypes.number, scale: [0, 254, 0, 1000]}, min: {dp: 3, type: tuya.dataTypes.number, scale: [0, 254, 0, 1000]}, max: {dp: 5, type: tuya.dataTypes.number, scale: [0, 254, 0, 1000]}, @@ -1926,13 +2068,19 @@ const definitions: Definition[] = [ }), tuya.modernExtend.dpNumeric({ name: 'countdown', - dp: 6, type: tuya.dataTypes.number, + dp: 6, + type: tuya.dataTypes.number, expose: tuya.exposes.countdown(), endpoint: 'l1', }), tuya.modernExtend.dpLight({ - state: {dp: 7, type: tuya.dataTypes.bool, valueOn: ['ON', true], valueOff: ['OFF', false], - skip: tuya.skip.stateOnAndBrightnessPresent}, + state: { + dp: 7, + type: tuya.dataTypes.bool, + valueOn: ['ON', true], + valueOff: ['OFF', false], + skip: tuya.skip.stateOnAndBrightnessPresent, + }, brightness: {dp: 8, type: tuya.dataTypes.number, scale: [0, 254, 0, 1000]}, min: {dp: 9, type: tuya.dataTypes.number, scale: [0, 254, 0, 1000]}, max: {dp: 11, type: tuya.dataTypes.number, scale: [0, 254, 0, 1000]}, @@ -1940,15 +2088,18 @@ const definitions: Definition[] = [ }), tuya.modernExtend.dpNumeric({ name: 'countdown', - dp: 12, type: tuya.dataTypes.number, + dp: 12, + type: tuya.dataTypes.number, expose: tuya.exposes.countdown(), endpoint: 'l2', }), tuya.modernExtend.dpPowerOnBehavior({ - dp: 14, type: tuya.dataTypes.enum, + dp: 14, + type: tuya.dataTypes.enum, }), tuya.modernExtend.dpBacklightMode({ - dp: 21, type: tuya.dataTypes.enum, + dp: 21, + type: tuya.dataTypes.enum, }), ], }, @@ -1968,7 +2119,8 @@ const definitions: Definition[] = [ tuya.exposes.countdown().withEndpoint('l2'), tuya.exposes.countdown().withEndpoint('l3'), e.power_on_behavior().withAccess(ea.STATE_SET), - tuya.exposes.backlightModeOffNormalInverted().withAccess(ea.STATE_SET)], + tuya.exposes.backlightModeOffNormalInverted().withAccess(ea.STATE_SET), + ], meta: { multiEndpoint: true, tuyaDatapoints: [ @@ -1992,7 +2144,7 @@ const definitions: Definition[] = [ ], }, endpoint: (device) => { - return {'l1': 1, 'l2': 1, 'l3': 1}; + return {l1: 1, l2: 1, l3: 1}; }, whiteLabel: [ {vendor: 'Moes', model: 'ZS-EUD_3gang'}, @@ -2023,20 +2175,20 @@ const definitions: Definition[] = [ [1, 'state_l1', tuya.valueConverter.onOff, {skip: tuya.skip.stateOnAndBrightnessPresent}], [2, 'brightness_l1', tuya.valueConverter.scale0_254to0_1000], [3, 'min_brightness_l1', tuya.valueConverter.scale0_254to0_1000], - [4, 'light_type_l1', tuya.valueConverterBasic.lookup({'led': tuya.enum(0), 'incandescent': tuya.enum(1), 'halogen': tuya.enum(2)})], + [4, 'light_type_l1', tuya.valueConverterBasic.lookup({led: tuya.enum(0), incandescent: tuya.enum(1), halogen: tuya.enum(2)})], [5, 'max_brightness_l1', tuya.valueConverter.scale0_254to0_1000], [6, 'countdown_l1', tuya.valueConverter.countdown], [7, 'state_l2', tuya.valueConverter.onOff, {skip: tuya.skip.stateOnAndBrightnessPresent}], [8, 'brightness_l2', tuya.valueConverter.scale0_254to0_1000], [9, 'min_brightness_l2', tuya.valueConverter.scale0_254to0_1000], - [10, 'light_type_l2', tuya.valueConverterBasic.lookup({'led': tuya.enum(0), 'incandescent': tuya.enum(1), 'halogen': tuya.enum(2)})], + [10, 'light_type_l2', tuya.valueConverterBasic.lookup({led: tuya.enum(0), incandescent: tuya.enum(1), halogen: tuya.enum(2)})], [11, 'max_brightness_l2', tuya.valueConverter.scale0_254to0_1000], [12, 'countdown_l2', tuya.valueConverter.countdown], - [14, 'power_on_behavior', tuya.valueConverterBasic.lookup({'off': tuya.enum(0), 'on': tuya.enum(1), 'previous': tuya.enum(2)})], + [14, 'power_on_behavior', tuya.valueConverterBasic.lookup({off: tuya.enum(0), on: tuya.enum(1), previous: tuya.enum(2)})], ], }, endpoint: (device) => { - return {'l1': 1, 'l2': 1}; + return {l1: 1, l2: 1}; }, whiteLabel: [ {vendor: 'Moes', model: 'MS-105B-M'}, @@ -2054,24 +2206,24 @@ const definitions: Definition[] = [ exposes: [ tuya.exposes.lightBrightnessWithMinMax(), e.enum('power_on_behavior', ea.STATE_SET, ['off', 'on', 'previous']), - tuya.exposes.countdown(), tuya.exposes.lightType(), tuya.exposes.switchType(), + tuya.exposes.countdown(), + tuya.exposes.lightType(), + tuya.exposes.switchType(), ], meta: { tuyaDatapoints: [ [1, 'state', tuya.valueConverter.onOff, {skip: tuya.skip.stateOnAndBrightnessPresent}], [2, 'brightness', tuya.valueConverter.scale0_254to0_1000], [3, 'min_brightness', tuya.valueConverter.scale0_254to0_1000], - [4, 'light_type', tuya.valueConverterBasic.lookup({'led': tuya.enum(0), 'incandescent': tuya.enum(1), 'halogen': tuya.enum(2)})], + [4, 'light_type', tuya.valueConverterBasic.lookup({led: tuya.enum(0), incandescent: tuya.enum(1), halogen: tuya.enum(2)})], [4, 'light_type', tuya.valueConverter.lightType], [5, 'max_brightness', tuya.valueConverter.scale0_254to0_1000], [6, 'countdown', tuya.valueConverter.countdown], - [14, 'power_on_behavior', tuya.valueConverterBasic.lookup({'off': tuya.enum(0), 'on': tuya.enum(1), 'previous': tuya.enum(2)})], - [57, 'switch_type', tuya.valueConverterBasic.lookup({'toggle': tuya.enum(0), 'state': tuya.enum(1), 'momentary': tuya.enum(2)})], + [14, 'power_on_behavior', tuya.valueConverterBasic.lookup({off: tuya.enum(0), on: tuya.enum(1), previous: tuya.enum(2)})], + [57, 'switch_type', tuya.valueConverterBasic.lookup({toggle: tuya.enum(0), state: tuya.enum(1), momentary: tuya.enum(2)})], ], }, - whiteLabel: [ - {vendor: 'Moes', model: 'MS-105-M'}, - ], + whiteLabel: [{vendor: 'Moes', model: 'MS-105-M'}], }, { fingerprint: [ @@ -2145,7 +2297,7 @@ const definitions: Definition[] = [ ], }, endpoint: (device) => { - return {'l1': 1, 'l2': 1}; + return {l1: 1, l2: 1}; }, }, { @@ -2156,15 +2308,18 @@ const definitions: Definition[] = [ fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, - exposes: [tuya.exposes.lightBrightness().withMinBrightness().setAccess('min_brightness', ea.STATE_SET), tuya.exposes.lightType(), - tuya.exposes.indicatorModeNoneRelayPos()], + exposes: [ + tuya.exposes.lightBrightness().withMinBrightness().setAccess('min_brightness', ea.STATE_SET), + tuya.exposes.lightType(), + tuya.exposes.indicatorModeNoneRelayPos(), + ], meta: { tuyaDatapoints: [ [1, 'state', tuya.valueConverter.onOff, {skip: tuya.skip.stateOnAndBrightnessPresent}], [2, 'brightness', tuya.valueConverter.scale0_254to0_1000], [3, 'min_brightness', tuya.valueConverter.scale0_254to0_1000], [4, 'light_type', tuya.valueConverter.lightType], - [21, 'indicator_mode', tuya.valueConverterBasic.lookup({'none': 0, 'relay': 1, 'pos': 2})], + [21, 'indicator_mode', tuya.valueConverterBasic.lookup({none: 0, relay: 1, pos: 2})], ], }, whiteLabel: [ @@ -2178,7 +2333,10 @@ const definitions: Definition[] = [ vendor: 'Tuya', description: 'Socket module', extend: [tuya.modernExtend.tuyaOnOff()], - whiteLabel: [{vendor: 'LoraTap', model: 'RR400ZB'}, {vendor: 'LoraTap', model: 'SP400ZB'}], + whiteLabel: [ + {vendor: 'LoraTap', model: 'RR400ZB'}, + {vendor: 'LoraTap', model: 'SP400ZB'}, + ], configure: async (device, coordinatorEndpoint) => { await tuya.configureMagicPacket(device, coordinatorEndpoint); const endpoint = device.getEndpoint(1); @@ -2187,19 +2345,25 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'TS011F', manufacturerName: '_TZ3000_wxtp7c5y'}, - {modelID: 'TS011F', manufacturerName: '_TYZB01_mtunwanm'}], + fingerprint: [ + {modelID: 'TS011F', manufacturerName: '_TZ3000_wxtp7c5y'}, + {modelID: 'TS011F', manufacturerName: '_TYZB01_mtunwanm'}, + ], model: 'TS011F_wall_outlet', vendor: 'Tuya', description: 'In-wall outlet', extend: [tuya.modernExtend.tuyaOnOff()], - whiteLabel: [{vendor: 'Teekar', model: 'SWP86-01OG'}, + whiteLabel: [ + {vendor: 'Teekar', model: 'SWP86-01OG'}, tuya.whitelabel('ClickSmart+', 'CMA30035', '1 gang socket outlet', ['_TYZB01_mtunwanm']), - {vendor: 'BSEED', model: 'Zigbee Socket'}], + {vendor: 'BSEED', model: 'Zigbee Socket'}, + ], }, { - fingerprint: [{modelID: 'isltm67\u0000', manufacturerName: '_TYST11_pisltm67'}, - {modelID: 'TS0601', manufacturerName: '_TZE200_pisltm67'}], + fingerprint: [ + {modelID: 'isltm67\u0000', manufacturerName: '_TYST11_pisltm67'}, + {modelID: 'TS0601', manufacturerName: '_TZE200_pisltm67'}, + ], model: 'S-LUX-ZB', vendor: 'Tuya', description: 'Light sensor', @@ -2209,8 +2373,7 @@ const definitions: Definition[] = [ const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genBasic']); }, - exposes: [e.battery(), e.illuminance_lux(), e.linkquality(), - e.enum('brightness_level', ea.STATE, ['LOW', 'MEDIUM', 'HIGH'])], + exposes: [e.battery(), e.illuminance_lux(), e.linkquality(), e.enum('brightness_level', ea.STATE, ['LOW', 'MEDIUM', 'HIGH'])], }, { zigbeeModel: ['TS130F'], @@ -2218,8 +2381,14 @@ const definitions: Definition[] = [ vendor: 'Tuya', description: 'Curtain/blind switch', fromZigbee: [fz.cover_position_tilt, tuya.fz.indicator_mode, fz.tuya_cover_options, tuya.fz.backlight_mode_off_on], - toZigbee: [tz.cover_state, tz.cover_position_tilt, tz.tuya_cover_calibration, tz.tuya_cover_reversal, - tuya.tz.backlight_indicator_mode_2, tuya.tz.backlight_indicator_mode_1], + toZigbee: [ + tz.cover_state, + tz.cover_position_tilt, + tz.tuya_cover_calibration, + tz.tuya_cover_reversal, + tuya.tz.backlight_indicator_mode_2, + tuya.tz.backlight_indicator_mode_1, + ], meta: {coverInverted: true}, whiteLabel: [ {vendor: 'LoraTap', model: 'SC400'}, @@ -2231,10 +2400,13 @@ const definitions: Definition[] = [ tuya.whitelabel('Nous', 'B4Z', 'Curtain switch', ['_TZ3000_yruungrl']), ], exposes: (device) => { - const exps = [e.cover_position(), e.enum('moving', ea.STATE, ['UP', 'STOP', 'DOWN']), + const exps = [ + e.cover_position(), + e.enum('moving', ea.STATE, ['UP', 'STOP', 'DOWN']), e.binary('calibration', ea.ALL, 'ON', 'OFF'), e.binary('motor_reversal', ea.ALL, 'ON', 'OFF'), - e.numeric('calibration_time', ea.STATE).withUnit('s').withDescription('Calibration time')]; + e.numeric('calibration_time', ea.STATE).withUnit('s').withDescription('Calibration time'), + ]; if (!device || device.manufacturerName !== ('_TZ3210_xbpt8ewc' || '_TZ3000_1dd0d5yi')) { exps.push(tuya.exposes.indicatorMode(), tuya.exposes.backlightModeOffOn()); } @@ -2254,9 +2426,12 @@ const definitions: Definition[] = [ model: 'TS0601_switch', vendor: 'Tuya', description: '1, 2, 3 or 4 gang switch', - exposes: [e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), + exposes: [ + e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l2').setAccess('state', ea.STATE_SET), - e.switch().withEndpoint('l3').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l4').setAccess('state', ea.STATE_SET)], + e.switch().withEndpoint('l3').setAccess('state', ea.STATE_SET), + e.switch().withEndpoint('l4').setAccess('state', ea.STATE_SET), + ], fromZigbee: [fz.ignore_basic_report, legacy.fromZigbee.tuya_switch], toZigbee: [legacy.toZigbee.tuya_switch_state], meta: {multiEndpoint: true}, @@ -2275,12 +2450,18 @@ const definitions: Definition[] = [ }, endpoint: (device) => { // Endpoint selection is made in tuya_switch_state - return {'l1': 1, 'l2': 1, 'l3': 1, 'l4': 1}; + return {l1: 1, l2: 1, l3: 1, l4: 1}; }, }, { - fingerprint: tuya.fingerprint('TS0601', ['_TZE200_aqnazj70', '_TZE200_di3tfv5b', '_TZE200_mexisfik', '_TZE204_6wi2mope', '_TZE204_iik0pquw', - '_TZE204_aagrxlbd']), + fingerprint: tuya.fingerprint('TS0601', [ + '_TZE200_aqnazj70', + '_TZE200_di3tfv5b', + '_TZE200_mexisfik', + '_TZE204_6wi2mope', + '_TZE204_iik0pquw', + '_TZE204_aagrxlbd', + ]), model: 'TS0601_switch_4_gang_1', vendor: 'Tuya', description: '4 gang switch', @@ -2310,7 +2491,7 @@ const definitions: Definition[] = [ ], }, endpoint: (device) => { - return {'l1': 1, 'l2': 1, 'l3': 1, 'l4': 1}; + return {l1: 1, l2: 1, l3: 1, l4: 1}; }, }, { @@ -2329,7 +2510,7 @@ const definitions: Definition[] = [ tuya.exposes.switch().withEndpoint('l5'), ], endpoint: (device) => { - return {'l1': 1, 'l2': 1, 'l3': 1, 'l4': 1, 'l5': 1}; + return {l1: 1, l2: 1, l3: 1, l4: 1, l5: 1}; }, meta: { multiEndpoint: true, @@ -2365,11 +2546,9 @@ const definitions: Definition[] = [ tuya.exposes.switch().withEndpoint('l6'), ], endpoint: (device) => { - return {'l1': 1, 'l2': 1, 'l3': 1, 'l4': 1, 'l5': 1, 'l6': 1}; + return {l1: 1, l2: 1, l3: 1, l4: 1, l5: 1, l6: 1}; }, - whiteLabel: [ - tuya.whitelabel('Mercator Ikuü', 'SSW06G', '6 Gang switch', ['_TZE200_wnp4d4va']), - ], + whiteLabel: [tuya.whitelabel('Mercator Ikuü', 'SSW06G', '6 Gang switch', ['_TZE200_wnp4d4va'])], meta: { multiEndpoint: true, tuyaDatapoints: [ @@ -2390,9 +2569,7 @@ const definitions: Definition[] = [ exposes: [e.switch().setAccess('state', ea.STATE_SET)], fromZigbee: [fz.ignore_basic_report, legacy.fromZigbee.tuya_switch], toZigbee: [legacy.toZigbee.tuya_switch_state], - whiteLabel: [ - tuya.whitelabel('Shawader', 'SMKG-1KNL-US/TZB-W', '1 gang switch', ['_TZE204_ojtqawav']), - ], + whiteLabel: [tuya.whitelabel('Shawader', 'SMKG-1KNL-US/TZB-W', '1 gang switch', ['_TZE204_ojtqawav'])], configure: async (device, coordinatorEndpoint) => { await tuya.configureMagicPacket(device, coordinatorEndpoint); await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff']); @@ -2416,7 +2593,7 @@ const definitions: Definition[] = [ ], }, endpoint: (device) => { - return {'l1': 1}; + return {l1: 1}; }, }, { @@ -2424,8 +2601,7 @@ const definitions: Definition[] = [ model: 'TS0601_switch_2_gang', vendor: 'Tuya', description: '2 gang switch', - exposes: [e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), - e.switch().withEndpoint('l2').setAccess('state', ea.STATE_SET)], + exposes: [e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l2').setAccess('state', ea.STATE_SET)], fromZigbee: [fz.ignore_basic_report, legacy.fromZigbee.tuya_switch], toZigbee: [legacy.toZigbee.tuya_switch_state], meta: {multiEndpoint: true}, @@ -2436,7 +2612,7 @@ const definitions: Definition[] = [ }, endpoint: (device) => { // Endpoint selection is made in tuya_switch_state - return {'l1': 1, 'l2': 1}; + return {l1: 1, l2: 1}; }, }, { @@ -2444,10 +2620,7 @@ const definitions: Definition[] = [ model: 'MG-ZG02W', vendor: 'Tuya', description: '2 gang switch', - exposes: [ - e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), - e.switch().withEndpoint('l2').setAccess('state', ea.STATE_SET), - ], + exposes: [e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l2').setAccess('state', ea.STATE_SET)], fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, @@ -2459,22 +2632,26 @@ const definitions: Definition[] = [ ], }, endpoint: (device) => { - return {'l1': 1, 'l2': 1}; + return {l1: 1, l2: 1}; }, }, { - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_kyfqmmyl'}, + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE200_kyfqmmyl'}, {modelID: 'TS0601', manufacturerName: '_TZE200_2hf7x9n3'}, {modelID: 'TS0601', manufacturerName: '_TZE204_atpwqgml'}, {modelID: 'TS0601', manufacturerName: '_TZE200_bynnczcb'}, - {modelID: 'TS0601', manufacturerName: '_TZE200_atpwqgml'}], + {modelID: 'TS0601', manufacturerName: '_TZE200_atpwqgml'}, + ], model: 'TS0601_switch_3_gang', vendor: 'Tuya', description: '3 gang switch', whiteLabel: [{vendor: 'NOVADIGITAL', model: 'WS-US-ZB', description: 'Interruptor touch Zigbee 3 Teclas'}], - exposes: [e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), + exposes: [ + e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l2').setAccess('state', ea.STATE_SET), - e.switch().withEndpoint('l3').setAccess('state', ea.STATE_SET)], + e.switch().withEndpoint('l3').setAccess('state', ea.STATE_SET), + ], fromZigbee: [fz.ignore_basic_report, legacy.fromZigbee.tuya_switch], toZigbee: [legacy.toZigbee.tuya_switch_state], meta: {multiEndpoint: true}, @@ -2485,7 +2662,7 @@ const definitions: Definition[] = [ }, endpoint: (device) => { // Endpoint selection is made in tuya_switch_state - return {'l1': 1, 'l2': 1, 'l3': 1}; + return {l1: 1, l2: 1, l3: 1}; }, }, { @@ -2510,12 +2687,21 @@ const definitions: Definition[] = [ ], }, endpoint: (device) => { - return {'l1': 1, 'l2': 1, 'l3': 1}; + return {l1: 1, l2: 1, l3: 1}; }, }, { - fingerprint: tuya.fingerprint('TS0215A', ['_TZ3000_4fsgukof', '_TZ3000_wr2ucaj9', '_TZ3000_zsh6uat3', '_TZ3000_tj4pwzzm', - '_TZ3000_2izubafb', '_TZ3000_pkfazisv', '_TZ3000_0dumfk2z', '_TZ3000_ssp0maqm', '_TZ3000_p3fph1go']), + fingerprint: tuya.fingerprint('TS0215A', [ + '_TZ3000_4fsgukof', + '_TZ3000_wr2ucaj9', + '_TZ3000_zsh6uat3', + '_TZ3000_tj4pwzzm', + '_TZ3000_2izubafb', + '_TZ3000_pkfazisv', + '_TZ3000_0dumfk2z', + '_TZ3000_ssp0maqm', + '_TZ3000_p3fph1go', + ]), model: 'TS0215A_sos', vendor: 'Tuya', description: 'SOS button', @@ -2534,18 +2720,23 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'TS0215A', manufacturerName: '_TZ3000_p6ju8myv'}, + fingerprint: [ + {modelID: 'TS0215A', manufacturerName: '_TZ3000_p6ju8myv'}, {modelID: 'TS0215A', manufacturerName: '_TZ3000_0zrccfgx'}, {modelID: 'TS0215A', manufacturerName: '_TZ3000_fsiepnrh'}, {modelID: 'TS0215A', manufacturerName: '_TZ3000_ug1vtuzn'}, - {modelID: 'TS0215A', manufacturerName: '_TZ3000_eo3dttwe'}], + {modelID: 'TS0215A', manufacturerName: '_TZ3000_eo3dttwe'}, + ], model: 'TS0215A_remote', vendor: 'Tuya', description: 'Security remote control', fromZigbee: [fz.command_arm, fz.command_emergency, fz.battery], exposes: [e.battery(), e.action(['disarm', 'arm_day_zones', 'arm_night_zones', 'arm_all_zones', 'exit_delay', 'emergency'])], toZigbee: [], - whiteLabel: [{vendor: 'Woox', model: 'R7054'}, {vendor: 'Nedis', model: 'ZBRC10WT'}], + whiteLabel: [ + {vendor: 'Woox', model: 'R7054'}, + {vendor: 'Nedis', model: 'ZBRC10WT'}, + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'genTime', 'genBasic', 'ssIasAce', 'ssIasZone']); @@ -2593,8 +2784,12 @@ const definitions: Definition[] = [ tuya.whitelabel('Mercator Ikuü', 'SMI7040', 'Ford Batten Light', ['_TZ3000_zw7wr5uo']), {vendor: 'Mercator Ikuü', model: 'SMD9300', description: 'Donovan Panel Light'}, tuya.whitelabel('Aldi', 'F122SB62H22A4.5W', 'LIGHTWAY smart home LED-lamp - filament', ['_TZ3000_g1glzzfk']), - tuya.whitelabel('MiBoxer', 'FUT035Z+', 'Dual white LED controller', - ['_TZ3210_frm6149r', '_TZ3210_jtifm80b', '_TZ3210_xwqng7ol', '_TZB210_lmqquxus']), + tuya.whitelabel('MiBoxer', 'FUT035Z+', 'Dual white LED controller', [ + '_TZ3210_frm6149r', + '_TZ3210_jtifm80b', + '_TZ3210_xwqng7ol', + '_TZB210_lmqquxus', + ]), tuya.whitelabel('Lidl', '14156408L', 'Livarno Lux smart LED ceiling light', ['_TZ3210_c2iwpxf1']), ], extend: [tuya.modernExtend.tuyaLight({colorTemp: {range: [153, 500]}, configureReporting: true})], @@ -2670,15 +2865,10 @@ const definitions: Definition[] = [ toZigbee: [], exposes: [e.battery(), e.temperature(), e.humidity(), e.battery_voltage()], configure: tuya.configureMagicPacket, - whiteLabel: [ - tuya.whitelabel('Tuya', 'TH02Z', 'Temperature and humidity sensor', ['_TZ3000_fllyghyj', '_TZ3000_saiqcn0y']), - ], + whiteLabel: [tuya.whitelabel('Tuya', 'TH02Z', 'Temperature and humidity sensor', ['_TZ3000_fllyghyj', '_TZ3000_saiqcn0y'])], }, { - fingerprint: [ - ...tuya.fingerprint('TS0201', ['_TZ3000_dowj6gyi', '_TZ3000_8ybe88nf']), - {manufacturerName: '_TZ3000_zl1kmjqx'}, - ], + fingerprint: [...tuya.fingerprint('TS0201', ['_TZ3000_dowj6gyi', '_TZ3000_8ybe88nf']), {manufacturerName: '_TZ3000_zl1kmjqx'}], model: 'IH-K009', vendor: 'Tuya', description: 'Temperature & humidity sensor', @@ -2686,9 +2876,7 @@ const definitions: Definition[] = [ toZigbee: [], exposes: [e.battery(), e.temperature(), e.humidity(), e.battery_voltage()], configure: tuya.configureMagicPacket, - whiteLabel: [ - tuya.whitelabel('Tuya', 'RSH-HS06_1', 'Temperature & humidity sensor', ['_TZ3000_zl1kmjqx']), - ], + whiteLabel: [tuya.whitelabel('Tuya', 'RSH-HS06_1', 'Temperature & humidity sensor', ['_TZ3000_zl1kmjqx'])], }, { fingerprint: tuya.fingerprint('SM0201', ['_TYZB01_cbiezpds', '_TYZB01_zqvwka4k']), @@ -2706,7 +2894,7 @@ const definitions: Definition[] = [ description: '2 gang 2 usb wall outlet', extend: [tuya.modernExtend.tuyaOnOff({backlightModeLowMediumHigh: true, endpoints: ['l1', 'l2', 'l3', 'l4'], childLock: true})], endpoint: () => { - return {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4}; + return {l1: 1, l2: 2, l3: 3, l4: 4}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -2726,7 +2914,8 @@ const definitions: Definition[] = [ extend: [ deviceEndpoints({endpoints: {left: 1, right: 2}, multiEndpointSkip: ['current', 'voltage', 'power', 'energy']}), onOff({powerOnBehavior: false, endpointNames: ['l1', 'l2']}), - identify(), electricityMeter(), + identify(), + electricityMeter(), ], }, { @@ -2735,7 +2924,10 @@ const definitions: Definition[] = [ model: 'TS0041', vendor: 'Tuya', description: 'Wireless switch with 1 button', - whiteLabel: [{vendor: 'Smart9', model: 'S9TSZGB'}, {vendor: 'Lonsonho', model: 'TS0041'}, {vendor: 'Benexmart', model: 'ZM-sui1'}, + whiteLabel: [ + {vendor: 'Smart9', model: 'S9TSZGB'}, + {vendor: 'Lonsonho', model: 'TS0041'}, + {vendor: 'Benexmart', model: 'ZM-sui1'}, tuya.whitelabel('Tuya', 'SH-SC07', 'Button scene switch', ['_TZ3000_mrpevh8p']), tuya.whitelabel('Tuya', 'MINI-ZSB', 'Smart button', ['_TZ3000_qgwcxxws']), tuya.whitelabel('Nous', 'LZ4', 'Wireless switch button', ['_TZ3000_6km7djcm']), @@ -2756,8 +2948,11 @@ const definitions: Definition[] = [ model: 'TS0042', vendor: 'Tuya', description: 'Wireless switch with 2 buttons', - whiteLabel: [{vendor: 'Smart9', model: 'S9TSZGB'}, {vendor: 'Lonsonho', model: 'TS0042'}, - {vendor: 'ClickSmart+', model: 'CSPGM2075PW'}], + whiteLabel: [ + {vendor: 'Smart9', model: 'S9TSZGB'}, + {vendor: 'Lonsonho', model: 'TS0042'}, + {vendor: 'ClickSmart+', model: 'CSPGM2075PW'}, + ], exposes: [e.battery(), e.action(['1_single', '1_double', '1_hold', '2_single', '2_double', '2_hold'])], fromZigbee: [tuya.fz.on_off_action, fz.battery], toZigbee: [], @@ -2774,9 +2969,12 @@ const definitions: Definition[] = [ model: 'TS0043', vendor: 'Tuya', description: 'Wireless switch with 3 buttons', - whiteLabel: [{vendor: 'Smart9', model: 'S9TSZGB'}, {vendor: 'Lonsonho', model: 'TS0043'}, {vendor: 'LoraTap', model: 'SS600ZB'}], - exposes: [e.battery(), - e.action(['1_single', '1_double', '1_hold', '2_single', '2_double', '2_hold', '3_single', '3_double', '3_hold'])], + whiteLabel: [ + {vendor: 'Smart9', model: 'S9TSZGB'}, + {vendor: 'Lonsonho', model: 'TS0043'}, + {vendor: 'LoraTap', model: 'SS600ZB'}, + ], + exposes: [e.battery(), e.action(['1_single', '1_double', '1_hold', '2_single', '2_double', '2_hold', '3_single', '3_double', '3_hold'])], fromZigbee: [tuya.fz.on_off_action, fz.battery], toZigbee: [], configure: tuya.configureMagicPacket, @@ -2793,16 +2991,32 @@ const definitions: Definition[] = [ vendor: 'Tuya', description: 'Wireless switch with 4 buttons', whiteLabel: [ - {vendor: 'Lonsonho', model: 'TS0044'}, {vendor: 'Haozee', model: 'ESW-OZAA-EU'}, - {vendor: 'LoraTap', model: 'SS6400ZB'}, {vendor: 'Moes', model: 'ZT-SY-EU-G-4S-WH-MS'}, + {vendor: 'Lonsonho', model: 'TS0044'}, + {vendor: 'Haozee', model: 'ESW-OZAA-EU'}, + {vendor: 'LoraTap', model: 'SS6400ZB'}, + {vendor: 'Moes', model: 'ZT-SY-EU-G-4S-WH-MS'}, tuya.whitelabel('Moes', 'ZT-SR-EU4', 'Star Ring 4 Gang Scene Switch', ['_TZ3000_a4xycprs']), - tuya.whitelabel('Tuya', 'TS0044_1', 'Zigbee 4 button remote - 12 scene', - ['_TZ3000_dziaict4', '_TZ3000_mh9px7cq', '_TZ3000_j61x9rxn']), + tuya.whitelabel('Tuya', 'TS0044_1', 'Zigbee 4 button remote - 12 scene', ['_TZ3000_dziaict4', '_TZ3000_mh9px7cq', '_TZ3000_j61x9rxn']), tuya.whitelabel('Tuya', 'TM-YKQ004', 'Zigbee 4 button remote - 12 scene', ['_TZ3000_u3nv1jwk']), ], fromZigbee: [tuya.fz.on_off_action, fz.battery], - exposes: [e.battery(), e.action(['1_single', '1_double', '1_hold', '2_single', '2_double', '2_hold', - '3_single', '3_double', '3_hold', '4_single', '4_double', '4_hold'])], + exposes: [ + e.battery(), + e.action([ + '1_single', + '1_double', + '1_hold', + '2_single', + '2_double', + '2_hold', + '3_single', + '3_double', + '3_hold', + '4_single', + '4_double', + '4_hold', + ]), + ], toZigbee: [], configure: tuya.configureMagicPacket, /* @@ -2813,29 +3027,48 @@ const definitions: Definition[] = [ */ }, { - fingerprint: tuya.fingerprint('TS004F', ['_TZ3000_nuombroo', '_TZ3000_xabckq1v', '_TZ3000_czuyt8lz', - '_TZ3000_0ht8dnxj', '_TZ3000_b3mgfu0d']), + fingerprint: tuya.fingerprint('TS004F', ['_TZ3000_nuombroo', '_TZ3000_xabckq1v', '_TZ3000_czuyt8lz', '_TZ3000_0ht8dnxj', '_TZ3000_b3mgfu0d']), model: 'TS004F', vendor: 'Tuya', description: 'Wireless switch with 4 buttons', exposes: [ e.battery(), - e.enum('operation_mode', ea.ALL, ['command', 'event']).withDescription( - 'Operation mode: "command" - for group control, "event" - for clicks'), - e.action(['on', 'off', 'brightness_step_up', 'brightness_step_down', 'brightness_move_up', - 'brightness_move_down', '1_single', '1_double', '1_hold', '2_single', '2_double', '2_hold', - '3_single', '3_double', '3_hold', '4_single', '4_double', '4_hold'])], - fromZigbee: [fz.battery, tuya.fz.on_off_action, fz.tuya_operation_mode, - fz.command_on, fz.command_off, fz.command_step, fz.command_move], + e + .enum('operation_mode', ea.ALL, ['command', 'event']) + .withDescription('Operation mode: "command" - for group control, "event" - for clicks'), + e.action([ + 'on', + 'off', + 'brightness_step_up', + 'brightness_step_down', + 'brightness_move_up', + 'brightness_move_down', + '1_single', + '1_double', + '1_hold', + '2_single', + '2_double', + '2_hold', + '3_single', + '3_double', + '3_hold', + '4_single', + '4_double', + '4_hold', + ]), + ], + fromZigbee: [fz.battery, tuya.fz.on_off_action, fz.tuya_operation_mode, fz.command_on, fz.command_off, fz.command_step, fz.command_move], toZigbee: [tz.tuya_operation_mode], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await endpoint.read('genBasic', [0x0004, 0x000, 0x0001, 0x0005, 0x0007, 0xfffe]); - await endpoint.write('genOnOff', {'tuyaOperationMode': 1}); + await endpoint.write('genOnOff', {tuyaOperationMode: 1}); await endpoint.read('genOnOff', ['tuyaOperationMode']); try { - await endpoint.read(0xE001, [0xD011]); - } catch (err) {/* do nothing */} + await endpoint.read(0xe001, [0xd011]); + } catch (err) { + /* do nothing */ + } await endpoint.read('genPowerCfg', ['batteryVoltage', 'batteryPercentageRemaining']); await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']); for (const ep of [1, 2, 3, 4]) { @@ -2855,23 +3088,48 @@ const definitions: Definition[] = [ description: 'Wireless switch with 6 buttons', exposes: [ e.battery(), - e.enum('operation_mode', ea.ALL, ['command', 'event']).withDescription( - 'Operation mode: "command" - for group control, "event" - for clicks'), - e.action(['on', 'off', 'brightness_step_up', 'brightness_step_down', 'brightness_move_up', - 'brightness_move_down', '1_single', '1_double', '1_hold', '2_single', '2_double', '2_hold', - '3_single', '3_double', '3_hold', '4_single', '4_double', '4_hold', - '5_single', '5_double', '5_hold', '6_single', '6_double', '6_hold'])], - fromZigbee: [fz.battery, tuya.fz.on_off_action, fz.tuya_operation_mode, - fz.command_on, fz.command_off, fz.command_step, fz.command_move], + e + .enum('operation_mode', ea.ALL, ['command', 'event']) + .withDescription('Operation mode: "command" - for group control, "event" - for clicks'), + e.action([ + 'on', + 'off', + 'brightness_step_up', + 'brightness_step_down', + 'brightness_move_up', + 'brightness_move_down', + '1_single', + '1_double', + '1_hold', + '2_single', + '2_double', + '2_hold', + '3_single', + '3_double', + '3_hold', + '4_single', + '4_double', + '4_hold', + '5_single', + '5_double', + '5_hold', + '6_single', + '6_double', + '6_hold', + ]), + ], + fromZigbee: [fz.battery, tuya.fz.on_off_action, fz.tuya_operation_mode, fz.command_on, fz.command_off, fz.command_step, fz.command_move], toZigbee: [tz.tuya_operation_mode], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await endpoint.read('genBasic', [0x0004, 0x000, 0x0001, 0x0005, 0x0007, 0xfffe]); - await endpoint.write('genOnOff', {'tuyaOperationMode': 1}); + await endpoint.write('genOnOff', {tuyaOperationMode: 1}); await endpoint.read('genOnOff', ['tuyaOperationMode']); try { - await endpoint.read(0xE001, [0xD011]); - } catch (err) {/* do nothing */} + await endpoint.read(0xe001, [0xd011]); + } catch (err) { + /* do nothing */ + } await endpoint.read('genPowerCfg', ['batteryVoltage', 'batteryPercentageRemaining']); await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']); for (const ep of [1, 2, 3, 4, 5, 6]) { @@ -2894,14 +3152,16 @@ const definitions: Definition[] = [ onEvent: tuya.onEventSetTime, configure: tuya.configureMagicPacket, exposes: [ - e.climate() + e + .climate() .withLocalTemperature(ea.STATE) .withSystemMode(['off', 'cool', 'heat', 'fan_only'], ea.STATE_SET) .withFanMode(['low', 'medium', 'high', 'auto'], ea.STATE_SET) .withSetpoint('current_heating_setpoint', 5, 35, 1, ea.STATE_SET) .withPreset(['auto', 'manual']) .withLocalTemperatureCalibration(-3, 3, 1, ea.STATE_SET), - e.numeric('deadzone_temperature', ea.STATE_SET) + e + .numeric('deadzone_temperature', ea.STATE_SET) .withUnit('°C') .withValueMax(5) .withValueMin(1) @@ -2909,92 +3169,110 @@ const definitions: Definition[] = [ .withPreset('default', 1, 'Default value') .withDescription('The delta between local_temperature and current_heating_setpoint to trigger activity'), e.child_lock(), - e.text('schedule', ea.STATE_SET) - .withDescription('Schedule will work with "auto" preset. In this mode, the device executes ' + - 'a preset week programming temperature time and temperature. Schedule can contains 12 segments. ' + - 'All 12 segments should be defined. It should be defined in the following format: "hh:mm/tt". ' + - 'Segments should be divided by space symbol. ' + - 'Example: "06:00/20 11:30/21 13:30/22 17:30/23 06:00/24 12:00/23 14:30/22 17:30/21 06:00/19 12:30/20 14:30/21 18:30/20"'), + e + .text('schedule', ea.STATE_SET) + .withDescription( + 'Schedule will work with "auto" preset. In this mode, the device executes ' + + 'a preset week programming temperature time and temperature. Schedule can contains 12 segments. ' + + 'All 12 segments should be defined. It should be defined in the following format: "hh:mm/tt". ' + + 'Segments should be divided by space symbol. ' + + 'Example: "06:00/20 11:30/21 13:30/22 17:30/23 06:00/24 12:00/23 14:30/22 17:30/21 06:00/19 12:30/20 14:30/21 18:30/20"', + ), ], meta: { tuyaDatapoints: [ - [1, null, { - from: (v, meta) => { - return v === true ? - {state: 'ON', system_mode: meta.state.system_mode_device ? meta.state.system_mode_device : 'cool'} : - {state: 'OFF', system_mode: 'off'}; + [ + 1, + null, + { + from: (v, meta) => { + return v === true + ? {state: 'ON', system_mode: meta.state.system_mode_device ? meta.state.system_mode_device : 'cool'} + : {state: 'OFF', system_mode: 'off'}; + }, }, - }], - [null, 'system_mode', { - // Extend system_mode to support 'off' in addition to 'cool', 'heat' and 'fan_only' - to: async (v: string, meta) => { - const entity = meta.device.endpoints[0]; + ], + [ + null, + 'system_mode', + { + // Extend system_mode to support 'off' in addition to 'cool', 'heat' and 'fan_only' + to: async (v: string, meta) => { + const entity = meta.device.endpoints[0]; - // Power State - await tuya.sendDataPointBool(entity, 1, v !== 'off', 'dataRequest', 1); + // Power State + await tuya.sendDataPointBool(entity, 1, v !== 'off', 'dataRequest', 1); - switch (v) { - case 'cool': - await tuya.sendDataPointEnum(entity, 2, 0, 'dataRequest', 1); - break; - case 'heat': - await tuya.sendDataPointEnum(entity, 2, 1, 'dataRequest', 1); - break; - case 'fan_only': - await tuya.sendDataPointEnum(entity, 2, 2, 'dataRequest', 1); - break; - } + switch (v) { + case 'cool': + await tuya.sendDataPointEnum(entity, 2, 0, 'dataRequest', 1); + break; + case 'heat': + await tuya.sendDataPointEnum(entity, 2, 1, 'dataRequest', 1); + break; + case 'fan_only': + await tuya.sendDataPointEnum(entity, 2, 2, 'dataRequest', 1); + break; + } + }, }, - }], - [2, null, { - // Map system_mode back to both 'state' and 'system_mode' - from: (v: number, meta) => { - const modes = ['cool', 'heat', 'fan_only']; + ], + [ + 2, + null, + { + // Map system_mode back to both 'state' and 'system_mode' + from: (v: number, meta) => { + const modes = ['cool', 'heat', 'fan_only']; - return { - system_mode: modes[v], - system_mode_device: modes[v], - }; + return { + system_mode: modes[v], + system_mode_device: modes[v], + }; + }, }, - }], - [4, 'preset', tuya.valueConverterBasic.lookup({'manual': true, 'auto': false})], + ], + [4, 'preset', tuya.valueConverterBasic.lookup({manual: true, auto: false})], [16, 'current_heating_setpoint', tuya.valueConverter.raw], [24, 'local_temperature', tuya.valueConverter.divideBy10], [26, 'deadzone_temperature', tuya.valueConverter.raw], [27, 'local_temperature_calibration', tuya.valueConverter.localTemperatureCalibration], - [28, 'fan_mode', tuya.valueConverterBasic.lookup( - {'low': tuya.enum(0), 'medium': tuya.enum(1), 'high': tuya.enum(2), 'auto': tuya.enum(3)})], + [28, 'fan_mode', tuya.valueConverterBasic.lookup({low: tuya.enum(0), medium: tuya.enum(1), high: tuya.enum(2), auto: tuya.enum(3)})], [40, 'child_lock', tuya.valueConverter.lockUnlock], - [101, 'schedule', { - to: (v: string, meta) => { - const regex = /((?[01][0-9]|2[0-3]):(?[0-5][0-9])\/(?[0-3][0-9](\.[0,5]|)))/gm; - const matches = [...v.matchAll(regex)]; + [ + 101, + 'schedule', + { + to: (v: string, meta) => { + const regex = /((?[01][0-9]|2[0-3]):(?[0-5][0-9])\/(?[0-3][0-9](\.[0,5]|)))/gm; + const matches = [...v.matchAll(regex)]; - if (matches.length == 12) { - return matches.reduce((arr, m) => { - arr.push(parseInt(m.groups.h)); - arr.push(parseInt(m.groups.m)); - arr.push(parseFloat(m.groups.t) * 2); - return arr; - }, []); - } + if (matches.length == 12) { + return matches.reduce((arr, m) => { + arr.push(parseInt(m.groups.h)); + arr.push(parseInt(m.groups.m)); + arr.push(parseFloat(m.groups.t) * 2); + return arr; + }, []); + } - logger.warning('Ignoring invalid or incomplete schedule', NS); - }, - from: (v: number[], meta) => { - let r = ''; + logger.warning('Ignoring invalid or incomplete schedule', NS); + }, + from: (v: number[], meta) => { + let r = ''; - for (let i = 0; i < 12; i++) { - r += `${v[i*3].toString().padStart(2, '0')}:${v[i*3 + 1].toString().padStart(2, '0')}/${(v[i*3 + 2] / 2)}`; + for (let i = 0; i < 12; i++) { + r += `${v[i * 3].toString().padStart(2, '0')}:${v[i * 3 + 1].toString().padStart(2, '0')}/${v[i * 3 + 2] / 2}`; - if (i < 11) { - r += ' '; + if (i < 11) { + r += ' '; + } } - } - return r; + return r; + }, }, - }], + ], ], }, whiteLabel: [ @@ -3027,13 +3305,21 @@ const definitions: Definition[] = [ exposes: [e.battery(), e.water_leak()], }, { - fingerprint: tuya.fingerprint('TS0001', ['_TZ3000_xkap8wtb', '_TZ3000_qnejhcsu', '_TZ3000_x3ewpzyr', - '_TZ3000_mkhkxx1p', '_TZ3000_tgddllx4', '_TZ3000_kqvb5akv', '_TZ3000_g92baclx', '_TZ3000_qlai3277', '_TZ3000_qaabwu5c']), + fingerprint: tuya.fingerprint('TS0001', [ + '_TZ3000_xkap8wtb', + '_TZ3000_qnejhcsu', + '_TZ3000_x3ewpzyr', + '_TZ3000_mkhkxx1p', + '_TZ3000_tgddllx4', + '_TZ3000_kqvb5akv', + '_TZ3000_g92baclx', + '_TZ3000_qlai3277', + '_TZ3000_qaabwu5c', + ]), model: 'TS0001_power', description: 'Switch with power monitoring', vendor: 'Tuya', - fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, fz.ignore_basic_report, - tuya.fz.power_outage_memory, tuya.fz.switch_type], + fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, fz.ignore_basic_report, tuya.fz.power_outage_memory, tuya.fz.switch_type], toZigbee: [tz.on_off, tuya.tz.power_on_behavior_1, tuya.tz.switch_type], configure: async (device, coordinatorEndpoint) => { await tuya.configureMagicPacket(device, coordinatorEndpoint); @@ -3047,8 +3333,15 @@ const definitions: Definition[] = [ endpoint.saveClusterAttributeKeyValue('seMetering', {divisor: 100, multiplier: 1}); device.save(); }, - exposes: [e.switch(), e.power(), e.current(), e.voltage(), e.energy(), tuya.exposes.switchType(), - e.enum('power_outage_memory', ea.ALL, ['on', 'off', 'restore']).withDescription('Recover state after power outage')], + exposes: [ + e.switch(), + e.power(), + e.current(), + e.voltage(), + e.energy(), + tuya.exposes.switchType(), + e.enum('power_outage_memory', ea.ALL, ['on', 'off', 'restore']).withDescription('Recover state after power outage'), + ], whiteLabel: [ tuya.whitelabel('Nous', 'B2Z', '1 gang switch with power monitoring', ['_TZ3000_qlai3277']), tuya.whitelabel('Colorock', 'CR-MNZ1', '1 gang switch 30A with power monitoring', ['_TZ3000_tgddllx4']), @@ -3062,7 +3355,7 @@ const definitions: Definition[] = [ description: '2 gang switch with power monitoring', extend: [tuya.modernExtend.tuyaOnOff({switchType: true, endpoints: ['l1', 'l2'], electricalMeasurements: true})], endpoint: (device) => { - return {'l1': 1, 'l2': 2}; + return {l1: 1, l2: 2}; }, meta: {multiEndpoint: true, multiEndpointSkip: ['energy', 'current', 'voltage', 'power']}, configure: async (device, coordinatorEndpoint) => { @@ -3088,8 +3381,7 @@ const definitions: Definition[] = [ model: 'TS000F_power', description: 'Switch with power monitoring', vendor: 'Tuya', - fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, fz.ignore_basic_report, tuya.fz.power_on_behavior_1, - tuya.fz.switch_type], + fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, fz.ignore_basic_report, tuya.fz.power_on_behavior_1, tuya.fz.switch_type], toZigbee: [tz.on_off, tuya.tz.power_on_behavior_1, tuya.tz.switch_type], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -3103,8 +3395,7 @@ const definitions: Definition[] = [ device.save(); }, whiteLabel: [{vendor: 'Aubess', model: 'WHD02'}], - exposes: [e.switch(), e.power(), e.current(), e.voltage(), e.energy(), e.power_on_behavior(), - tuya.exposes.switchType()], + exposes: [e.switch(), e.power(), e.current(), e.voltage(), e.energy(), e.power_on_behavior(), tuya.exposes.switchType()], }, { zigbeeModel: ['TS0001'], @@ -3112,7 +3403,9 @@ const definitions: Definition[] = [ vendor: 'Tuya', description: '1 gang switch', extend: [tuya.modernExtend.tuyaOnOff()], - whiteLabel: [{vendor: 'CR Smart Home', model: 'TS0001', description: 'Valve control'}, {vendor: 'Lonsonho', model: 'X701'}, + whiteLabel: [ + {vendor: 'CR Smart Home', model: 'TS0001', description: 'Valve control'}, + {vendor: 'Lonsonho', model: 'X701'}, {vendor: 'Bandi', model: 'BDS03G1'}, ], configure: async (device, coordinatorEndpoint) => { @@ -3126,9 +3419,7 @@ const definitions: Definition[] = [ vendor: 'Tuya', description: '1 gang switch module', extend: [tuya.modernExtend.tuyaOnOff({indicatorMode: true, backlightModeOffOn: true, onOffCountdown: true})], - whiteLabel: [ - tuya.whitelabel('PSMART', 'T441', '1 gang switch module', ['_TZ3000_myaaknbq']), - ], + whiteLabel: [tuya.whitelabel('PSMART', 'T441', '1 gang switch module', ['_TZ3000_myaaknbq'])], configure: async (device, coordinatorEndpoint) => { await tuya.configureMagicPacket(device, coordinatorEndpoint); await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff']); @@ -3141,7 +3432,7 @@ const definitions: Definition[] = [ description: '2 gang switch with backlight', extend: [tuya.modernExtend.tuyaOnOff({powerOnBehavior2: true, indicatorMode: true, endpoints: ['l1', 'l2']})], endpoint: (device) => { - return {'l1': 1, 'l2': 2}; + return {l1: 1, l2: 2}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -3149,9 +3440,7 @@ const definitions: Definition[] = [ await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff']); await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ['genOnOff']); }, - whiteLabel: [ - tuya.whitelabel('Lonsonho', 'X702A', '2 gang switch with backlight', ['_TZ3000_54hjn4vs', '_TZ3000_aa5t61rh']), - ], + whiteLabel: [tuya.whitelabel('Lonsonho', 'X702A', '2 gang switch with backlight', ['_TZ3000_54hjn4vs', '_TZ3000_aa5t61rh'])], }, { fingerprint: tuya.fingerprint('TS0002', ['_TZ3000_mufwv0ry']), @@ -3160,12 +3449,10 @@ const definitions: Definition[] = [ description: '1 gang switch module', extend: [tuya.modernExtend.tuyaOnOff({endpoints: ['l1', 'l2'], indicatorMode: true, backlightModeOffOn: true})], endpoint: (device) => { - return {'l1': 1, 'l2': 2}; + return {l1: 1, l2: 2}; }, meta: {multiEndpoint: true}, - whiteLabel: [ - tuya.whitelabel('PSMART', 'T442', '2 gang switch module', ['_TZ3000_mufwv0ry']), - ], + whiteLabel: [tuya.whitelabel('PSMART', 'T442', '2 gang switch module', ['_TZ3000_mufwv0ry'])], configure: async (device, coordinatorEndpoint) => { await tuya.configureMagicPacket(device, coordinatorEndpoint); await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff']); @@ -3178,7 +3465,7 @@ const definitions: Definition[] = [ description: '3-Gang switch with backlight', extend: [tuya.modernExtend.tuyaOnOff({powerOnBehavior2: true, indicatorMode: true, backlightModeOffOn: true, endpoints: ['l1', 'l2', 'l3']})], endpoint: (device) => { - return {'l1': 1, 'l2': 2, 'l3': 3}; + return {l1: 1, l2: 2, l3: 3}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -3208,7 +3495,7 @@ const definitions: Definition[] = [ extend: [tuya.modernExtend.tuyaOnOff()], exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2')], endpoint: (device) => { - return {'l1': 1, 'l2': 2}; + return {l1: 1, l2: 2}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -3252,8 +3539,17 @@ const definitions: Definition[] = [ }, }, { - fingerprint: tuya.fingerprint('TS0002', ['_TZ3000_01gpyda5', '_TZ3000_bvrlqyj7', '_TZ3000_7ed9cqgi', - '_TZ3000_zmy4lslw', '_TZ3000_ruxexjfz', '_TZ3000_4xfqlgqo', '_TZ3000_eei0ubpy', '_TZ3000_qaa59zqd', '_TZ3000_lmlsduws']), + fingerprint: tuya.fingerprint('TS0002', [ + '_TZ3000_01gpyda5', + '_TZ3000_bvrlqyj7', + '_TZ3000_7ed9cqgi', + '_TZ3000_zmy4lslw', + '_TZ3000_ruxexjfz', + '_TZ3000_4xfqlgqo', + '_TZ3000_eei0ubpy', + '_TZ3000_qaa59zqd', + '_TZ3000_lmlsduws', + ]), model: 'TS0002_switch_module', vendor: 'Tuya', description: '2 gang switch module', @@ -3265,7 +3561,7 @@ const definitions: Definition[] = [ ], extend: [tuya.modernExtend.tuyaOnOff({switchType: true, endpoints: ['l1', 'l2']})], endpoint: (device) => { - return {'l1': 1, 'l2': 2}; + return {l1: 1, l2: 2}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -3281,7 +3577,7 @@ const definitions: Definition[] = [ description: '2 gang switch module', extend: [tuya.modernExtend.tuyaOnOff({switchType: true, indicatorMode: true, endpoints: ['l1', 'l2']})], endpoint: (device) => { - return {'l1': 1, 'l2': 2}; + return {l1: 1, l2: 2}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -3289,9 +3585,7 @@ const definitions: Definition[] = [ await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff']); await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ['genOnOff']); }, - whiteLabel: [ - tuya.whitelabel('AVATTO', 'ZWSM16-2-Zigbee', '2 gang switch module', ['_TZ3000_mtnpt6ws']), - ], + whiteLabel: [tuya.whitelabel('AVATTO', 'ZWSM16-2-Zigbee', '2 gang switch module', ['_TZ3000_mtnpt6ws'])], }, { fingerprint: tuya.fingerprint('TS0003', ['_TZ3000_4o16jdca', '_TZ3000_odzoiovu', '_TZ3000_hbic3ka3', '_TZ3000_lvhy15ix']), @@ -3300,7 +3594,7 @@ const definitions: Definition[] = [ description: '3 gang switch module', extend: [tuya.modernExtend.tuyaOnOff({switchType: true, indicatorMode: true, endpoints: ['l1', 'l2', 'l3']})], endpoint: (device) => { - return {'l1': 1, 'l2': 2, 'l3': 3}; + return {l1: 1, l2: 2, l3: 3}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -3309,9 +3603,7 @@ const definitions: Definition[] = [ await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ['genOnOff']); await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ['genOnOff']); }, - whiteLabel: [ - tuya.whitelabel('AVATTO', 'ZWSM16-3-Zigbee', '3 gang switch module', ['_TZ3000_hbic3ka3']), - ], + whiteLabel: [tuya.whitelabel('AVATTO', 'ZWSM16-3-Zigbee', '3 gang switch module', ['_TZ3000_hbic3ka3'])], }, { fingerprint: tuya.fingerprint('TS0003', ['_TZ3000_vsasbzkf', '_TZ3000_nnwehhst']), @@ -3321,7 +3613,7 @@ const definitions: Definition[] = [ whiteLabel: [{vendor: 'OXT', model: 'SWTZ23'}], extend: [tuya.modernExtend.tuyaOnOff({switchType: true, backlightModeOffOn: true, endpoints: ['l1', 'l2', 'l3']})], endpoint: (device) => { - return {'l1': 1, 'l2': 2, 'l3': 3}; + return {l1: 1, l2: 2, l3: 3}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -3339,7 +3631,7 @@ const definitions: Definition[] = [ whiteLabel: [{vendor: 'OXT', model: 'SWTZ27'}], extend: [tuya.modernExtend.tuyaOnOff({switchType: true, endpoints: ['l1', 'l2', 'l3', 'l4']})], endpoint: (device) => { - return {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4}; + return {l1: 1, l2: 2, l3: 3, l4: 4}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -3352,8 +3644,12 @@ const definitions: Definition[] = [ }, { zigbeeModel: [ - 'owvfni3\u0000', 'owvfni3', 'u1rkty3', 'aabybja', // Curtain motors - 'mcdj3aq', 'mcdj3aq\u0000', // Tubular motors + 'owvfni3\u0000', + 'owvfni3', + 'u1rkty3', + 'aabybja', // Curtain motors + 'mcdj3aq', + 'mcdj3aq\u0000', // Tubular motors ], fingerprint: [ // Curtain motors: @@ -3438,11 +3734,11 @@ const definitions: Definition[] = [ toZigbee: [legacy.toZigbee.tuya_cover_control, legacy.toZigbee.tuya_cover_options], exposes: [ e.cover_position().setAccess('position', ea.STATE_SET), - e.composite('options', 'options', ea.STATE_SET) - .withFeature(e.numeric('motor_speed', ea.STATE_SET) - .withValueMin(0).withValueMax(255).withDescription('Motor speed')) - .withFeature(e.binary('reverse_direction', ea.STATE_SET, true, false) - .withDescription('Reverse the motor direction'))], + e + .composite('options', 'options', ea.STATE_SET) + .withFeature(e.numeric('motor_speed', ea.STATE_SET).withValueMin(0).withValueMax(255).withDescription('Motor speed')) + .withFeature(e.binary('reverse_direction', ea.STATE_SET, true, false).withDescription('Reverse the motor direction')), + ], }, { fingerprint: [ @@ -3452,17 +3748,13 @@ const definitions: Definition[] = [ model: 'TS0601_cover_2', vendor: 'Tuya', description: 'Curtain motor fixed speed', - whiteLabel: [ - {vendor: 'Zemismart', model: 'BCM100DB'}, - ], + whiteLabel: [{vendor: 'Zemismart', model: 'BCM100DB'}], fromZigbee: [legacy.fromZigbee.tuya_cover, fz.ignore_basic_report], toZigbee: [legacy.toZigbee.tuya_cover_control], exposes: [e.cover_position().setAccess('position', ea.STATE_SET)], }, { - fingerprint: [ - {modelID: 'TS0601', manufacturerName: '_TZE200_cpbo62rn'}, - ], + fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_cpbo62rn'}], model: 'TS0601_cover_6', vendor: 'Tuya', description: 'Cover motor', @@ -3477,19 +3769,17 @@ const definitions: Definition[] = [ e.enum('set_upper_limit', ea.STATE_SET, ['start', 'stop']).withDescription('Learning'), e.enum('factory_reset', ea.STATE_SET, ['SET']).withDescription('Remove limits'), ], - whiteLabel: [ - tuya.whitelabel('Tuya', 'LY-108', 'Cover', ['_TZE200_cpbo62rn']), - ], + whiteLabel: [tuya.whitelabel('Tuya', 'LY-108', 'Cover', ['_TZE200_cpbo62rn'])], meta: { tuyaDatapoints: [ - [1, 'state', tuya.valueConverterBasic.lookup({'CLOSE': tuya.enum(2), 'STOP': tuya.enum(1), 'OPEN': tuya.enum(0)})], + [1, 'state', tuya.valueConverterBasic.lookup({CLOSE: tuya.enum(2), STOP: tuya.enum(1), OPEN: tuya.enum(0)})], [2, 'position', tuya.valueConverter.coverPositionInverted], [3, 'position', tuya.valueConverter.coverPositionInverted], - [4, 'opening_mode', tuya.valueConverterBasic.lookup({'tilt': tuya.enum(0), 'lift': tuya.enum(1)})], - [7, 'work_state', tuya.valueConverterBasic.lookup({'standby': tuya.enum(0), 'success': tuya.enum(1), 'learning': tuya.enum(2)})], + [4, 'opening_mode', tuya.valueConverterBasic.lookup({tilt: tuya.enum(0), lift: tuya.enum(1)})], + [7, 'work_state', tuya.valueConverterBasic.lookup({standby: tuya.enum(0), success: tuya.enum(1), learning: tuya.enum(2)})], [13, 'battery', tuya.valueConverter.raw], - [101, 'motor_direction', tuya.valueConverterBasic.lookup({'left': tuya.enum(0), 'right': tuya.enum(1)})], - [102, 'set_upper_limit', tuya.valueConverterBasic.lookup({'start': tuya.enum(1), 'stop': tuya.enum(0)})], + [101, 'motor_direction', tuya.valueConverterBasic.lookup({left: tuya.enum(0), right: tuya.enum(1)})], + [102, 'set_upper_limit', tuya.valueConverterBasic.lookup({start: tuya.enum(1), stop: tuya.enum(0)})], [107, 'factory_reset', tuya.valueConverter.setLimit], ], }, @@ -3506,7 +3796,7 @@ const definitions: Definition[] = [ exposes: [e.cover_position().setAccess('position', ea.STATE_SET), e.battery()], meta: { tuyaDatapoints: [ - [1, 'state', tuya.valueConverterBasic.lookup({'OPEN': tuya.enum(0), 'STOP': tuya.enum(1), 'CLOSE': tuya.enum(2)})], + [1, 'state', tuya.valueConverterBasic.lookup({OPEN: tuya.enum(0), STOP: tuya.enum(1), CLOSE: tuya.enum(2)})], [2, 'position', tuya.valueConverter.coverPosition], [3, 'position', tuya.valueConverter.raw], // motor_direction doesn't work: https://github.com/Koenkk/zigbee2mqtt/issues/18103 @@ -3526,17 +3816,15 @@ const definitions: Definition[] = [ options: [exposes.options.invert_cover()], exposes: [ e.cover_position().setAccess('position', ea.STATE_SET), - e.enum('reverse_direction', ea.STATE_SET, ['forward', 'back']) - .withDescription('Reverse the motor direction'), - e.binary('motor_fault', ea.STATE, true, false) - .withDescription('Motor Fault'), + e.enum('reverse_direction', ea.STATE_SET, ['forward', 'back']).withDescription('Reverse the motor direction'), + e.binary('motor_fault', ea.STATE, true, false).withDescription('Motor Fault'), ], meta: { tuyaDatapoints: [ - [1, 'state', tuya.valueConverterBasic.lookup({'OPEN': tuya.enum(0), 'STOP': tuya.enum(1), 'CLOSE': tuya.enum(2)})], + [1, 'state', tuya.valueConverterBasic.lookup({OPEN: tuya.enum(0), STOP: tuya.enum(1), CLOSE: tuya.enum(2)})], [2, 'position', tuya.valueConverter.coverPositionInverted], [3, 'position', tuya.valueConverter.coverPositionInverted], - [5, 'reverse_direction', tuya.valueConverterBasic.lookup({'forward': tuya.enum(0), 'back': tuya.enum(1)})], + [5, 'reverse_direction', tuya.valueConverterBasic.lookup({forward: tuya.enum(0), back: tuya.enum(1)})], [12, 'motor_fault', tuya.valueConverter.trueFalse1], ], }, @@ -3558,10 +3846,10 @@ const definitions: Definition[] = [ exposes: [e.cover_position().setAccess('position', ea.STATE_SET), e.battery()], meta: { tuyaDatapoints: [ - [1, 'state', tuya.valueConverterBasic.lookup({'OPEN': tuya.enum(0), 'STOP': tuya.enum(1), 'CLOSE': tuya.enum(2)})], + [1, 'state', tuya.valueConverterBasic.lookup({OPEN: tuya.enum(0), STOP: tuya.enum(1), CLOSE: tuya.enum(2)})], [2, 'position', tuya.valueConverter.coverPosition], [3, 'position', tuya.valueConverter.raw], - [5, 'motor_direction', tuya.valueConverterBasic.lookup({'normal': tuya.enum(0), 'reversed': tuya.enum(1)})], + [5, 'motor_direction', tuya.valueConverterBasic.lookup({normal: tuya.enum(0), reversed: tuya.enum(1)})], [101, 'battery', tuya.valueConverter.raw], ], }, @@ -3602,33 +3890,65 @@ const definitions: Definition[] = [ ota: ota.zigbeeOTA, onEvent: tuya.onEventSetLocalTime, fromZigbee: [legacy.fromZigbee.tuya_thermostat, fz.ignore_basic_report, fz.ignore_tuya_set_time], - toZigbee: [legacy.toZigbee.tuya_thermostat_child_lock, legacy.toZigbee.tuya_thermostat_window_detection, + toZigbee: [ + legacy.toZigbee.tuya_thermostat_child_lock, + legacy.toZigbee.tuya_thermostat_window_detection, legacy.toZigbee.tuya_thermostat_valve_detection, - legacy.toZigbee.tuya_thermostat_current_heating_setpoint, legacy.toZigbee.tuya_thermostat_auto_lock, - legacy.toZigbee.tuya_thermostat_calibration, legacy.toZigbee.tuya_thermostat_min_temp, legacy.toZigbee.tuya_thermostat_max_temp, - legacy.toZigbee.tuya_thermostat_boost_time, legacy.toZigbee.tuya_thermostat_comfort_temp, legacy.toZigbee.tuya_thermostat_eco_temp, - legacy.toZigbee.tuya_thermostat_force_to_mode, legacy.toZigbee.tuya_thermostat_force, legacy.toZigbee.tuya_thermostat_preset, - legacy.toZigbee.tuya_thermostat_window_detect, legacy.toZigbee.tuya_thermostat_schedule, legacy.toZigbee.tuya_thermostat_week, - legacy.toZigbee.tuya_thermostat_schedule_programming_mode, legacy.toZigbee.tuya_thermostat_away_mode, - legacy.toZigbee.tuya_thermostat_away_preset], - exposes: [ - e.child_lock(), e.window_detection(), + legacy.toZigbee.tuya_thermostat_current_heating_setpoint, + legacy.toZigbee.tuya_thermostat_auto_lock, + legacy.toZigbee.tuya_thermostat_calibration, + legacy.toZigbee.tuya_thermostat_min_temp, + legacy.toZigbee.tuya_thermostat_max_temp, + legacy.toZigbee.tuya_thermostat_boost_time, + legacy.toZigbee.tuya_thermostat_comfort_temp, + legacy.toZigbee.tuya_thermostat_eco_temp, + legacy.toZigbee.tuya_thermostat_force_to_mode, + legacy.toZigbee.tuya_thermostat_force, + legacy.toZigbee.tuya_thermostat_preset, + legacy.toZigbee.tuya_thermostat_window_detect, + legacy.toZigbee.tuya_thermostat_schedule, + legacy.toZigbee.tuya_thermostat_week, + legacy.toZigbee.tuya_thermostat_schedule_programming_mode, + legacy.toZigbee.tuya_thermostat_away_mode, + legacy.toZigbee.tuya_thermostat_away_preset, + ], + exposes: [ + e.child_lock(), + e.window_detection(), e.binary('window_open', ea.STATE, true, false).withDescription('Window open?'), - e.battery_low(), e.valve_detection(), e.position(), - e.climate().withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET) - .withLocalTemperature(ea.STATE).withSystemMode(['heat', 'auto', 'off'], ea.STATE_SET, + e.battery_low(), + e.valve_detection(), + e.position(), + e + .climate() + .withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET) + .withLocalTemperature(ea.STATE) + .withSystemMode( + ['heat', 'auto', 'off'], + ea.STATE_SET, 'Mode of this device, in the `heat` mode the TS0601 will remain continuously heating, i.e. it does not regulate ' + - 'to the desired temperature. If you want TRV to properly regulate the temperature you need to use mode `auto` ' + - 'instead setting the desired temperature.') + 'to the desired temperature. If you want TRV to properly regulate the temperature you need to use mode `auto` ' + + 'instead setting the desired temperature.', + ) .withLocalTemperatureCalibration(-9, 9, 0.5, ea.STATE_SET) .withPreset(['schedule', 'manual', 'boost', 'complex', 'comfort', 'eco', 'away']) .withRunningState(['idle', 'heat'], ea.STATE), - e.auto_lock(), e.away_mode(), e.away_preset_days(), e.boost_time(), e.comfort_temperature(), e.eco_temperature(), e.force(), - e.max_temperature().withValueMin(16).withValueMax(70), e.min_temperature(), e.away_preset_temperature(), + e.auto_lock(), + e.away_mode(), + e.away_preset_days(), + e.boost_time(), + e.comfort_temperature(), + e.eco_temperature(), + e.force(), + e.max_temperature().withValueMin(16).withValueMax(70), + e.min_temperature(), + e.away_preset_temperature(), e.week(), - e.text('workdays_schedule', ea.STATE_SET) + e + .text('workdays_schedule', ea.STATE_SET) .withDescription('Workdays schedule, 6 entries max, example: "00:20/5°C 01:20/5°C 6:59/15°C 18:00/5°C 20:00/5°C 23:30/5°C"'), - e.text('holidays_schedule', ea.STATE_SET) + e + .text('holidays_schedule', ea.STATE_SET) .withDescription('Holidays schedule, 6 entries max, example: "00:20/5°C 01:20/5°C 6:59/15°C 18:00/5°C 20:00/5°C 23:30/5°C"'), ], }, @@ -3642,7 +3962,8 @@ const definitions: Definition[] = [ onEvent: tuya.onEventSetTime, configure: tuya.configureMagicPacket, exposes: [ - e.battery(), e.child_lock(), + e.battery(), + e.child_lock(), e.max_temperature().withValueMin(15).withValueMax(45), e.min_temperature().withValueMin(5).withValueMax(15), e.window_detection(), @@ -3650,26 +3971,43 @@ const definitions: Definition[] = [ e.comfort_temperature().withValueMin(5).withValueMax(35), e.eco_temperature().withValueMin(5).withValueMax(35), e.holiday_temperature().withValueMin(5).withValueMax(35), - e.climate().withPreset(['auto', 'manual', 'holiday', 'comfort']).withLocalTemperatureCalibration(-9, 9, 0.1, ea.STATE_SET) - .withLocalTemperature(ea.STATE).withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.STATE_SET) + e + .climate() + .withPreset(['auto', 'manual', 'holiday', 'comfort']) + .withLocalTemperatureCalibration(-9, 9, 0.1, ea.STATE_SET) + .withLocalTemperature(ea.STATE) + .withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.STATE_SET) .withSystemMode(['off', 'heat'], ea.STATE_SET, 'Only for Homeassistant') .withRunningState(['idle', 'heat'], ea.STATE_SET), - tuya.exposes.frostProtection('When Anti-Freezing function is activated, the temperature in the house is kept '+ - 'at 8 °C, the device display "AF".press the pair button to cancel.'), - e.numeric('boost_timeset_countdown', ea.STATE_SET).withUnit('s').withDescription('Setting '+ - 'minimum 0 - maximum 465 seconds boost time. The boost function is activated. The remaining '+ - 'time for the function will be counted down in seconds ( 465 to 0 ).').withValueMin(0).withValueMax(465), - e.composite('schedule', 'schedule', ea.STATE_SET).withFeature(e.enum('week_day', ea.SET, ['monday', 'tuesday', - 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'])).withFeature(e.text('schedule', ea.SET)) - .withDescription('Schedule will work with "auto" preset. In this mode, the device executes ' + - 'a preset week programming temperature time and temperature. Before using these properties, check `working_day` ' + - 'property. Each day can contain up to 10 segments. At least 1 segment should be defined. Different count of segments ' + - 'can be defined for each day, e.g., 3 segments for Monday, 5 segments for Thursday, etc. It should be defined in the ' + - 'following format: `hours:minutes/temperature`. Minutes can be only tens, i.e., 00, 10, 20, 30, 40, 50. Segments should ' + - 'be divided by space symbol. Each day should end with the last segment of 24:00. Examples: `04:00/20 08:30/22 10:10/18 ' + - '18:40/24 22:50/19.5`; `06:00/21.5 17:20/26 24:00/18`. The temperature will be set from the beginning/start of one ' + - 'period and until the next period, e.g., `04:00/20 24:00/22` means that from 00:00 to 04:00 temperature will be 20 ' + - 'degrees and from 04:00 to 00:00 temperature will be 22 degrees.'), + tuya.exposes.frostProtection( + 'When Anti-Freezing function is activated, the temperature in the house is kept ' + + 'at 8 °C, the device display "AF".press the pair button to cancel.', + ), + e + .numeric('boost_timeset_countdown', ea.STATE_SET) + .withUnit('s') + .withDescription( + 'Setting ' + + 'minimum 0 - maximum 465 seconds boost time. The boost function is activated. The remaining ' + + 'time for the function will be counted down in seconds ( 465 to 0 ).', + ) + .withValueMin(0) + .withValueMax(465), + e + .composite('schedule', 'schedule', ea.STATE_SET) + .withFeature(e.enum('week_day', ea.SET, ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'])) + .withFeature(e.text('schedule', ea.SET)) + .withDescription( + 'Schedule will work with "auto" preset. In this mode, the device executes ' + + 'a preset week programming temperature time and temperature. Before using these properties, check `working_day` ' + + 'property. Each day can contain up to 10 segments. At least 1 segment should be defined. Different count of segments ' + + 'can be defined for each day, e.g., 3 segments for Monday, 5 segments for Thursday, etc. It should be defined in the ' + + 'following format: `hours:minutes/temperature`. Minutes can be only tens, i.e., 00, 10, 20, 30, 40, 50. Segments should ' + + 'be divided by space symbol. Each day should end with the last segment of 24:00. Examples: `04:00/20 08:30/22 10:10/18 ' + + '18:40/24 22:50/19.5`; `06:00/21.5 17:20/26 24:00/18`. The temperature will be set from the beginning/start of one ' + + 'period and until the next period, e.g., `04:00/20 24:00/22` means that from 00:00 to 04:00 temperature will be 20 ' + + 'degrees and from 04:00 to 00:00 temperature will be 22 degrees.', + ), ...tuya.exposes.scheduleAllDays(ea.STATE, 'HH:MM/C'), e.binary('valve', ea.STATE, 'CLOSED', 'OPEN'), e.enum('factory_reset', ea.STATE_SET, ['SET']).withDescription('Remove limits'), @@ -3677,10 +4015,13 @@ const definitions: Definition[] = [ ], meta: { tuyaDatapoints: [ - [49, 'running_state', tuya.valueConverterBasic.lookup({'heat': tuya.enum(1), 'idle': tuya.enum(0)})], - [49, 'system_mode', tuya.valueConverterBasic.lookup({'heat': tuya.enum(1), 'off': tuya.enum(0)})], - [2, 'preset', tuya.valueConverterBasic.lookup({'comfort': tuya.enum(3), 'auto': tuya.enum(0), - 'manual': tuya.enum(2), 'holiday': tuya.enum(1)})], + [49, 'running_state', tuya.valueConverterBasic.lookup({heat: tuya.enum(1), idle: tuya.enum(0)})], + [49, 'system_mode', tuya.valueConverterBasic.lookup({heat: tuya.enum(1), off: tuya.enum(0)})], + [ + 2, + 'preset', + tuya.valueConverterBasic.lookup({comfort: tuya.enum(3), auto: tuya.enum(0), manual: tuya.enum(2), holiday: tuya.enum(1)}), + ], [4, 'current_heating_setpoint', tuya.valueConverter.divideBy10], [5, 'local_temperature', tuya.valueConverter.divideBy10], [6, 'battery', tuya.valueConverter.raw], @@ -3709,13 +4050,19 @@ const definitions: Definition[] = [ [39, 'Switch Scale', tuya.valueConverter.raw], [47, 'local_temperature_calibration', tuya.valueConverter.localTempCalibration1], [48, 'valve_testing', tuya.valueConverter.raw], - [49, 'valve', tuya.valueConverterBasic.lookup({'OPEN': 1, 'CLOSE': 0})], + [49, 'valve', tuya.valueConverterBasic.lookup({OPEN: 1, CLOSE: 0})], ], }, }, { - fingerprint: tuya.fingerprint('TS0601', ['_TZE200_68nvbio9', '_TZE200_pw7mji0l', '_TZE200_cf1sl3tj', '_TZE200_nw1r9hp6', '_TZE200_9p5xmj5r', - '_TZE200_eevqq1uv']), + fingerprint: tuya.fingerprint('TS0601', [ + '_TZE200_68nvbio9', + '_TZE200_pw7mji0l', + '_TZE200_cf1sl3tj', + '_TZE200_nw1r9hp6', + '_TZE200_9p5xmj5r', + '_TZE200_eevqq1uv', + ]), model: 'TS0601_cover_3', vendor: 'Tuya', description: 'Cover motor', @@ -3725,7 +4072,8 @@ const definitions: Definition[] = [ options: [exposes.options.invert_cover()], configure: tuya.configureMagicPacket, exposes: [ - e.battery(), e.cover_position(), + e.battery(), + e.cover_position(), e.enum('reverse_direction', ea.STATE_SET, ['forward', 'back']).withDescription('Reverse the motor direction'), e.enum('border', ea.STATE_SET, ['up', 'down', 'up_delete', 'down_delete', 'remove_top_bottom']), e.enum('click_control', ea.STATE_SET, ['up', 'down']).withDescription('Single motor steps'), @@ -3740,16 +4088,24 @@ const definitions: Definition[] = [ meta: { // All datapoints go in here tuyaDatapoints: [ - [1, 'state', tuya.valueConverterBasic.lookup({'OPEN': tuya.enum(0), 'STOP': tuya.enum(1), 'CLOSE': tuya.enum(2)})], + [1, 'state', tuya.valueConverterBasic.lookup({OPEN: tuya.enum(0), STOP: tuya.enum(1), CLOSE: tuya.enum(2)})], [2, 'position', tuya.valueConverter.coverPosition], [3, 'position', tuya.valueConverter.raw], - [5, 'reverse_direction', tuya.valueConverterBasic.lookup({'forward': tuya.enum(0), 'back': tuya.enum(1)})], + [5, 'reverse_direction', tuya.valueConverterBasic.lookup({forward: tuya.enum(0), back: tuya.enum(1)})], [12, 'motor_fault', tuya.valueConverter.trueFalse1], [13, 'battery', tuya.valueConverter.raw], - [16, 'border', tuya.valueConverterBasic.lookup({ - 'up': tuya.enum(0), 'down': tuya.enum(1), 'up_delete': tuya.enum(2), 'down_delete': tuya.enum(3), - 'remove_top_bottom': tuya.enum(4)})], - [20, 'click_control', tuya.valueConverterBasic.lookup({'up': tuya.enum(0), 'down': tuya.enum(1)})], + [ + 16, + 'border', + tuya.valueConverterBasic.lookup({ + up: tuya.enum(0), + down: tuya.enum(1), + up_delete: tuya.enum(2), + down_delete: tuya.enum(3), + remove_top_bottom: tuya.enum(4), + }), + ], + [20, 'click_control', tuya.valueConverterBasic.lookup({up: tuya.enum(0), down: tuya.enum(1)})], ], }, }, @@ -3769,21 +4125,19 @@ const definitions: Definition[] = [ e.enum('set_bottom_limit', ea.SET, ['SET']).withDescription('Set the bottom limit, to reset limits use factory_reset'), e.binary('factory_reset', ea.SET, true, false).withDescription('Factory reset the device'), ], - whiteLabel: [ - tuya.whitelabel('Moes', 'AM43-0.45/40-ES-EB', 'Roller blind/shades drive motor', ['_TZE200_zah67ekd', '_TZE200_icka1clh']), - ], + whiteLabel: [tuya.whitelabel('Moes', 'AM43-0.45/40-ES-EB', 'Roller blind/shades drive motor', ['_TZE200_zah67ekd', '_TZE200_icka1clh'])], configure: async (device, coordinatorEndpoint) => { device.powerSource = 'Mains (single phase)'; device.save(); }, meta: { tuyaDatapoints: [ - [1, 'state', tuya.valueConverterBasic.lookup({'OPEN': tuya.enum(0), 'STOP': tuya.enum(1), 'CLOSE': tuya.enum(2)})], + [1, 'state', tuya.valueConverterBasic.lookup({OPEN: tuya.enum(0), STOP: tuya.enum(1), CLOSE: tuya.enum(2)})], [2, 'position', tuya.valueConverter.coverPosition], [3, 'position', tuya.valueConverter.raw], [5, 'motor_direction', tuya.valueConverter.tubularMotorDirection], [7, null, null], // work_state, not useful, ignore - [101, 'opening_mode', tuya.valueConverterBasic.lookup({'tilt': tuya.enum(0), 'lift': tuya.enum(1)})], + [101, 'opening_mode', tuya.valueConverterBasic.lookup({tilt: tuya.enum(0), lift: tuya.enum(1)})], [102, 'factory_reset', tuya.valueConverter.raw], [103, 'set_upper_limit', tuya.valueConverter.setLimit], [104, 'set_bottom_limit', tuya.valueConverter.setLimit], @@ -3793,16 +4147,16 @@ const definitions: Definition[] = [ }, { fingerprint: tuya.fingerprint('TS0601', [ - '_TZE200_sur6q7ko', /* model: '3012732', vendor: 'LSC Smart Connect' */ - '_TZE200_hue3yfsn', /* model: 'TV02-Zigbee', vendor: 'Tuya' */ - '_TZE200_e9ba97vf', /* model: 'TV01-ZB', vendor: 'Moes' */ - '_TZE200_husqqvux', /* model: 'TSL-TRV-TV01ZG', vendor: 'Tesla Smart' */ - '_TZE200_lnbfnyxd', /* model: 'TSL-TRV-TV01ZG', vendor: 'Tesla Smart' */ - '_TZE200_lllliz3p', /* model: 'TV02-Zigbee', vendor: 'Tuya' */ - '_TZE200_mudxchsu', /* model: 'TV05-ZG curve', vendor: 'Tuya' */ - '_TZE200_7yoranx2', /* model: 'TV01-ZB', vendor: 'Moes' */ - '_TZE200_kds0pmmv', /* model: 'TV01-ZB', vendor: 'Moes' */ - '_TZE200_py4cm3he', /* model: 'TV06-Zigbee', vendor: 'Tuya' */ + '_TZE200_sur6q7ko' /* model: '3012732', vendor: 'LSC Smart Connect' */, + '_TZE200_hue3yfsn' /* model: 'TV02-Zigbee', vendor: 'Tuya' */, + '_TZE200_e9ba97vf' /* model: 'TV01-ZB', vendor: 'Moes' */, + '_TZE200_husqqvux' /* model: 'TSL-TRV-TV01ZG', vendor: 'Tesla Smart' */, + '_TZE200_lnbfnyxd' /* model: 'TSL-TRV-TV01ZG', vendor: 'Tesla Smart' */, + '_TZE200_lllliz3p' /* model: 'TV02-Zigbee', vendor: 'Tuya' */, + '_TZE200_mudxchsu' /* model: 'TV05-ZG curve', vendor: 'Tuya' */, + '_TZE200_7yoranx2' /* model: 'TV01-ZB', vendor: 'Moes' */, + '_TZE200_kds0pmmv' /* model: 'TV01-ZB', vendor: 'Moes' */, + '_TZE200_py4cm3he' /* model: 'TV06-Zigbee', vendor: 'Tuya' */, ]), model: 'TV02-Zigbee', vendor: 'Tuya', @@ -3821,40 +4175,73 @@ const definitions: Definition[] = [ onEvent: tuya.onEventSetLocalTime, configure: tuya.configureMagicPacket, exposes: [ - e.battery_low(), e.child_lock(), e.open_window(), e.open_window_temperature().withValueMin(5).withValueMax(30), - e.comfort_temperature().withValueMin(5).withValueMax(30), e.eco_temperature().withValueMin(5).withValueMax(30), - e.climate().withPreset(['auto', 'manual', 'holiday']).withLocalTemperatureCalibration(-5, 5, 0.1, ea.STATE_SET) - .withLocalTemperature(ea.STATE).withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.STATE_SET) + e.battery_low(), + e.child_lock(), + e.open_window(), + e.open_window_temperature().withValueMin(5).withValueMax(30), + e.comfort_temperature().withValueMin(5).withValueMax(30), + e.eco_temperature().withValueMin(5).withValueMax(30), + e + .climate() + .withPreset(['auto', 'manual', 'holiday']) + .withLocalTemperatureCalibration(-5, 5, 0.1, ea.STATE_SET) + .withLocalTemperature(ea.STATE) + .withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.STATE_SET) .withSystemMode(['off', 'heat'], ea.STATE_SET, 'Only for Homeassistant'), - e.binary('heating_stop', ea.STATE_SET, 'ON', 'OFF').withDescription('Battery life can be prolonged'+ - ' by switching the heating off. To achieve this, the valve is closed fully. To activate the '+ - 'heating stop, the device display "HS", press the pair button to cancel.'), - tuya.exposes.frostProtection('When Anti-Freezing function is activated, the temperature in the house is kept '+ - 'at 8 °C, the device display "AF".press the pair button to cancel.'), - e.numeric('boost_timeset_countdown', ea.STATE_SET).withUnit('s').withDescription('Setting '+ - 'minimum 0 - maximum 465 seconds boost time. The boost (♨) function is activated. The remaining '+ - 'time for the function will be counted down in seconds ( 465 to 0 ).').withValueMin(0).withValueMax(465), + e + .binary('heating_stop', ea.STATE_SET, 'ON', 'OFF') + .withDescription( + 'Battery life can be prolonged' + + ' by switching the heating off. To achieve this, the valve is closed fully. To activate the ' + + 'heating stop, the device display "HS", press the pair button to cancel.', + ), + tuya.exposes.frostProtection( + 'When Anti-Freezing function is activated, the temperature in the house is kept ' + + 'at 8 °C, the device display "AF".press the pair button to cancel.', + ), + e + .numeric('boost_timeset_countdown', ea.STATE_SET) + .withUnit('s') + .withDescription( + 'Setting ' + + 'minimum 0 - maximum 465 seconds boost time. The boost (♨) function is activated. The remaining ' + + 'time for the function will be counted down in seconds ( 465 to 0 ).', + ) + .withValueMin(0) + .withValueMax(465), e.holiday_temperature().withValueMin(5).withValueMax(30), - e.text('holiday_start_stop', ea.STATE_SET).withDescription('The holiday mode will automatically start ' + - 'at the set time starting point and run the holiday temperature. Can be defined in the following format: ' + - '`startYear/startMonth/startDay startHours:startMinutes | endYear/endMonth/endDay endHours:endMinutes`. ' + - 'For example: `2022/10/01 16:30 | 2022/10/21 18:10`. After the end of holiday mode, it switches to "auto" ' + - 'mode and uses schedule.'), - e.enum('working_day', ea.STATE_SET, ['mon_sun', 'mon_fri+sat+sun', 'separate']).withDescription('`mon_sun` ' + - '- schedule for Monday used for each day (define it only for Monday). `mon_fri+sat+sun` - schedule for ' + - 'workdays used from Monday (define it only for Monday), Saturday and Sunday are defined separately. `separate` ' + - '- schedule for each day is defined separately.'), - e.composite('schedule', 'schedule', ea.SET).withFeature(e.enum('week_day', ea.SET, ['monday', 'tuesday', - 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'])).withFeature(e.text('schedule', ea.SET)) - .withDescription('Schedule will work with "auto" preset. In this mode, the device executes ' + - 'a preset week programming temperature time and temperature. Before using these properties, check `working_day` ' + - 'property. Each day can contain up to 10 segments. At least 1 segment should be defined. Different count of segments ' + - 'can be defined for each day, e.g., 3 segments for Monday, 5 segments for Thursday, etc. It should be defined in the ' + - 'following format: `hours:minutes/temperature`. Minutes can be only tens, i.e., 00, 10, 20, 30, 40, 50. Segments should ' + - 'be divided by space symbol. Each day should end with the last segment of 24:00. Examples: `04:00/20 08:30/22 10:10/18 ' + - '18:40/24 22:50/19.5`; `06:00/21.5 17:20/26 24:00/18`. The temperature will be set from the beginning/start of one ' + - 'period and until the next period, e.g., `04:00/20 24:00/22` means that from 00:00 to 04:00 temperature will be 20 ' + - 'degrees and from 04:00 to 00:00 temperature will be 22 degrees.'), + e + .text('holiday_start_stop', ea.STATE_SET) + .withDescription( + 'The holiday mode will automatically start ' + + 'at the set time starting point and run the holiday temperature. Can be defined in the following format: ' + + '`startYear/startMonth/startDay startHours:startMinutes | endYear/endMonth/endDay endHours:endMinutes`. ' + + 'For example: `2022/10/01 16:30 | 2022/10/21 18:10`. After the end of holiday mode, it switches to "auto" ' + + 'mode and uses schedule.', + ), + e + .enum('working_day', ea.STATE_SET, ['mon_sun', 'mon_fri+sat+sun', 'separate']) + .withDescription( + '`mon_sun` ' + + '- schedule for Monday used for each day (define it only for Monday). `mon_fri+sat+sun` - schedule for ' + + 'workdays used from Monday (define it only for Monday), Saturday and Sunday are defined separately. `separate` ' + + '- schedule for each day is defined separately.', + ), + e + .composite('schedule', 'schedule', ea.SET) + .withFeature(e.enum('week_day', ea.SET, ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'])) + .withFeature(e.text('schedule', ea.SET)) + .withDescription( + 'Schedule will work with "auto" preset. In this mode, the device executes ' + + 'a preset week programming temperature time and temperature. Before using these properties, check `working_day` ' + + 'property. Each day can contain up to 10 segments. At least 1 segment should be defined. Different count of segments ' + + 'can be defined for each day, e.g., 3 segments for Monday, 5 segments for Thursday, etc. It should be defined in the ' + + 'following format: `hours:minutes/temperature`. Minutes can be only tens, i.e., 00, 10, 20, 30, 40, 50. Segments should ' + + 'be divided by space symbol. Each day should end with the last segment of 24:00. Examples: `04:00/20 08:30/22 10:10/18 ' + + '18:40/24 22:50/19.5`; `06:00/21.5 17:20/26 24:00/18`. The temperature will be set from the beginning/start of one ' + + 'period and until the next period, e.g., `04:00/20 24:00/22` means that from 00:00 to 04:00 temperature will be 20 ' + + 'degrees and from 04:00 to 00:00 temperature will be 22 degrees.', + ), ...tuya.exposes.scheduleAllDays(ea.STATE, 'HH:MM/C'), e.binary('online', ea.STATE_SET, 'ON', 'OFF').withDescription('The current data request from the device.'), tuya.exposes.errorStatus(), @@ -3868,8 +4255,11 @@ const definitions: Definition[] = [ [16, 'current_heating_setpoint', tuya.valueConverter.divideBy10], [24, 'local_temperature', tuya.valueConverter.divideBy10], [27, 'local_temperature_calibration', tuya.valueConverter.localTempCalibration1], - [31, 'working_day', tuya.valueConverterBasic.lookup({'mon_sun': tuya.enum(0), 'mon_fri+sat+sun': tuya.enum(1), - 'separate': tuya.enum(2)})], + [ + 31, + 'working_day', + tuya.valueConverterBasic.lookup({mon_sun: tuya.enum(0), 'mon_fri+sat+sun': tuya.enum(1), separate: tuya.enum(2)}), + ], [32, 'holiday_temperature', tuya.valueConverter.divideBy10], [35, 'battery_low', tuya.valueConverter.trueFalse0], [40, 'child_lock', tuya.valueConverter.lockUnlock], @@ -3895,23 +4285,19 @@ const definitions: Definition[] = [ }, }, { - fingerprint: tuya.fingerprint('TS0601', [ - '_TZE200_0hg58wyk', /* model: 'S366', vendor: 'Cloud Even' */ - ]), + fingerprint: tuya.fingerprint('TS0601', ['_TZE200_0hg58wyk' /* model: 'S366', vendor: 'Cloud Even' */]), model: 'TS0601_thermostat_2', vendor: 'Tuya', description: 'Thermostat radiator valve', - whiteLabel: [ - {vendor: 'S366', model: 'Cloud Even'}, - ], + whiteLabel: [{vendor: 'S366', model: 'Cloud Even'}], fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], onEvent: tuya.onEventSetLocalTime, configure: tuya.configureMagicPacket, meta: { tuyaDatapoints: [ - [1, 'system_mode', tuya.valueConverterBasic.lookup({'heat': true, 'off': false})], - [2, 'preset', tuya.valueConverterBasic.lookup({'manual': tuya.enum(0), 'holiday': tuya.enum(1), 'program': tuya.enum(2)})], + [1, 'system_mode', tuya.valueConverterBasic.lookup({heat: true, off: false})], + [2, 'preset', tuya.valueConverterBasic.lookup({manual: tuya.enum(0), holiday: tuya.enum(1), program: tuya.enum(2)})], [3, null, null], // TODO: Unknown DP [8, 'open_window', tuya.valueConverter.onOff], [10, 'frost_protection', tuya.valueConverter.onOff], @@ -3931,8 +4317,13 @@ const definitions: Definition[] = [ ], }, exposes: [ - e.battery_low(), e.child_lock(), e.open_window(), tuya.exposes.frostProtection(), tuya.exposes.errorStatus(), - e.climate() + e.battery_low(), + e.child_lock(), + e.open_window(), + tuya.exposes.frostProtection(), + tuya.exposes.errorStatus(), + e + .climate() .withSystemMode(['off', 'heat'], ea.STATE_SET) .withPreset(['manual', 'holiday', 'program']) .withLocalTemperatureCalibration(-5, 5, 0.1, ea.STATE_SET) @@ -3943,10 +4334,10 @@ const definitions: Definition[] = [ }, { fingerprint: tuya.fingerprint('TS0601', [ - '_TZE200_bvu2wnxz', /* model: 'ME167', vendor: 'AVATTO' */ - '_TZE200_6rdj8dzm', /* model: 'ME167', vendor: 'AVATTO' */ - '_TZE200_p3dbf6qs', /* model: 'ME168', vendor: 'AVATTO' */ - '_TZE200_rxntag7i', /* model: 'ME168', vendor: 'AVATTO' */ + '_TZE200_bvu2wnxz' /* model: 'ME167', vendor: 'AVATTO' */, + '_TZE200_6rdj8dzm' /* model: 'ME167', vendor: 'AVATTO' */, + '_TZE200_p3dbf6qs' /* model: 'ME168', vendor: 'AVATTO' */, + '_TZE200_rxntag7i' /* model: 'ME168', vendor: 'AVATTO' */, ]), model: 'TS0601_thermostat_3', vendor: 'Tuya', @@ -3960,27 +4351,36 @@ const definitions: Definition[] = [ onEvent: tuya.onEventSetTime, configure: tuya.configureMagicPacket, exposes: [ - e.child_lock(), e.battery_low(), - e.climate() + e.child_lock(), + e.battery_low(), + e + .climate() .withSetpoint('current_heating_setpoint', 5, 35, 1, ea.STATE_SET) .withLocalTemperature(ea.STATE) .withSystemMode(['auto', 'heat', 'off'], ea.STATE_SET) .withRunningState(['idle', 'heat'], ea.STATE) .withLocalTemperatureCalibration(-9, 9, 1, ea.STATE_SET), ...tuya.exposes.scheduleAllDays(ea.STATE_SET, 'HH:MM/C HH:MM/C HH:MM/C HH:MM/C HH:MM/C HH:MM/C'), - e.binary('scale_protection', ea.STATE_SET, 'ON', 'OFF').withDescription('If the heat sink is not fully opened within ' + - 'two weeks or is not used for a long time, the valve will be blocked due to silting up and the heat sink will not be ' + - 'able to be used. To ensure normal use of the heat sink, the controller will automatically open the valve fully every ' + - 'two weeks. It will run for 30 seconds per time with the screen displaying "Ad", then return to its normal working state ' + - 'again.'), - e.binary('frost_protection', ea.STATE_SET, 'ON', 'OFF').withDescription('When the room temperature is lower than ' + - '5 °C, the valve opens; when the temperature rises to 8 °C, the valve closes.'), + e + .binary('scale_protection', ea.STATE_SET, 'ON', 'OFF') + .withDescription( + 'If the heat sink is not fully opened within ' + + 'two weeks or is not used for a long time, the valve will be blocked due to silting up and the heat sink will not be ' + + 'able to be used. To ensure normal use of the heat sink, the controller will automatically open the valve fully every ' + + 'two weeks. It will run for 30 seconds per time with the screen displaying "Ad", then return to its normal working state ' + + 'again.', + ), + e + .binary('frost_protection', ea.STATE_SET, 'ON', 'OFF') + .withDescription( + 'When the room temperature is lower than ' + '5 °C, the valve opens; when the temperature rises to 8 °C, the valve closes.', + ), e.numeric('error', ea.STATE).withDescription('If NTC is damaged, "Er" will be on the TRV display.'), ], meta: { tuyaDatapoints: [ - [2, 'system_mode', tuya.valueConverterBasic.lookup({'auto': tuya.enum(0), 'heat': tuya.enum(1), 'off': tuya.enum(2)})], - [3, 'running_state', tuya.valueConverterBasic.lookup({'heat': tuya.enum(0), 'idle': tuya.enum(1)})], + [2, 'system_mode', tuya.valueConverterBasic.lookup({auto: tuya.enum(0), heat: tuya.enum(1), off: tuya.enum(2)})], + [3, 'running_state', tuya.valueConverterBasic.lookup({heat: tuya.enum(0), idle: tuya.enum(1)})], [4, 'current_heating_setpoint', tuya.valueConverter.divideBy10], [5, 'local_temperature', tuya.valueConverter.divideBy10], [7, 'child_lock', tuya.valueConverter.lockUnlock], @@ -3999,9 +4399,7 @@ const definitions: Definition[] = [ }, }, { - fingerprint: tuya.fingerprint('TS0601', [ - '_TZE204_pcdmj88b', - ]), + fingerprint: tuya.fingerprint('TS0601', ['_TZE204_pcdmj88b']), model: 'TS0601_thermostat_4', vendor: 'Tuya', description: 'Thermostatic radiator valve', @@ -4013,7 +4411,8 @@ const definitions: Definition[] = [ e.child_lock(), e.battery(), e.battery_low(), - e.climate() + e + .climate() .withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET) .withLocalTemperature(ea.STATE) .withPreset(['schedule', 'holiday', 'manual', 'comfort', 'eco']) @@ -4023,21 +4422,36 @@ const definitions: Definition[] = [ e.holiday_temperature().withValueMin(5).withValueMax(30), e.comfort_temperature().withValueMin(5).withValueMax(30), e.eco_temperature().withValueMin(5).withValueMax(30), - e.binary('scale_protection', ea.STATE_SET, 'ON', 'OFF').withDescription('If the heat sink is not fully opened within ' + - 'two weeks or is not used for a long time, the valve will be blocked due to silting up and the heat sink will not be ' + - 'able to be used. To ensure normal use of the heat sink, the controller will automatically open the valve fully every ' + - 'two weeks. It will run for 30 seconds per time with the screen displaying "Ad", then return to its normal working state ' + - 'again.'), - e.binary('frost_protection', ea.STATE_SET, 'ON', 'OFF').withDescription('When the room temperature is lower than ' + - '5 °C, the valve opens; when the temperature rises to 8 °C, the valve closes.'), + e + .binary('scale_protection', ea.STATE_SET, 'ON', 'OFF') + .withDescription( + 'If the heat sink is not fully opened within ' + + 'two weeks or is not used for a long time, the valve will be blocked due to silting up and the heat sink will not be ' + + 'able to be used. To ensure normal use of the heat sink, the controller will automatically open the valve fully every ' + + 'two weeks. It will run for 30 seconds per time with the screen displaying "Ad", then return to its normal working state ' + + 'again.', + ), + e + .binary('frost_protection', ea.STATE_SET, 'ON', 'OFF') + .withDescription( + 'When the room temperature is lower than ' + '5 °C, the valve opens; when the temperature rises to 8 °C, the valve closes.', + ), e.numeric('error', ea.STATE).withDescription('If NTC is damaged, "Er" will be on the TRV display.'), - e.binary('boost_heating', ea.STATE_SET, 'ON', 'OFF') - .withDescription('Boost Heating: the device will enter the boost heating mode.'), + e.binary('boost_heating', ea.STATE_SET, 'ON', 'OFF').withDescription('Boost Heating: the device will enter the boost heating mode.'), ], meta: { tuyaDatapoints: [ - [2, 'preset', tuya.valueConverterBasic.lookup( - {'schedule': tuya.enum(0), 'holiday': tuya.enum(1), 'manual': tuya.enum(2), 'comfort': tuya.enum(3), 'eco': tuya.enum(4)})], + [ + 2, + 'preset', + tuya.valueConverterBasic.lookup({ + schedule: tuya.enum(0), + holiday: tuya.enum(1), + manual: tuya.enum(2), + comfort: tuya.enum(3), + eco: tuya.enum(4), + }), + ], [4, 'current_heating_setpoint', tuya.valueConverter.divideBy10], [5, 'local_temperature', tuya.valueConverter.divideBy10], [6, 'battery', tuya.valueConverter.raw], @@ -4057,7 +4471,7 @@ const definitions: Definition[] = [ [37, 'boost_heating', tuya.valueConverter.onOff], [39, 'scale_protection', tuya.valueConverter.onOff], [47, 'local_temperature_calibration', tuya.valueConverter.localTempCalibration2], - [49, 'system_mode', tuya.valueConverterBasic.lookup({'off': tuya.enum(0), 'heat': tuya.enum(1)})], + [49, 'system_mode', tuya.valueConverterBasic.lookup({off: tuya.enum(0), heat: tuya.enum(1)})], ], }, }, @@ -4069,10 +4483,19 @@ const definitions: Definition[] = [ model: 'HT-08', vendor: 'ETOP', description: 'Wall-mount thermostat', - fromZigbee: [legacy.fromZigbee.tuya_thermostat_weekly_schedule_1, legacy.fromZigbee.etop_thermostat, - fz.ignore_basic_report, fz.ignore_tuya_set_time], - toZigbee: [legacy.toZigbee.etop_thermostat_system_mode, legacy.toZigbee.etop_thermostat_away_mode, legacy.toZigbee.tuya_thermostat_child_lock, - legacy.toZigbee.tuya_thermostat_current_heating_setpoint, legacy.toZigbee.tuya_thermostat_weekly_schedule], + fromZigbee: [ + legacy.fromZigbee.tuya_thermostat_weekly_schedule_1, + legacy.fromZigbee.etop_thermostat, + fz.ignore_basic_report, + fz.ignore_tuya_set_time, + ], + toZigbee: [ + legacy.toZigbee.etop_thermostat_system_mode, + legacy.toZigbee.etop_thermostat_away_mode, + legacy.toZigbee.tuya_thermostat_child_lock, + legacy.toZigbee.tuya_thermostat_current_heating_setpoint, + legacy.toZigbee.tuya_thermostat_weekly_schedule, + ], onEvent: tuya.onEventSetTime, meta: { thermostat: { @@ -4081,20 +4504,38 @@ const definitions: Definition[] = [ weeklyScheduleFirstDayDpId: 101, }, }, - exposes: [e.child_lock(), e.away_mode(), e.climate().withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET) - .withLocalTemperature(ea.STATE) - .withSystemMode(['off', 'heat', 'auto'], ea.STATE_SET).withRunningState(['idle', 'heat'], ea.STATE)], + exposes: [ + e.child_lock(), + e.away_mode(), + e + .climate() + .withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET) + .withLocalTemperature(ea.STATE) + .withSystemMode(['off', 'heat', 'auto'], ea.STATE_SET) + .withRunningState(['idle', 'heat'], ea.STATE), + ], }, { - fingerprint: [{modelID: 'dpplnsn\u0000', manufacturerName: '_TYST11_2dpplnsn'}, - {modelID: 'TS0601', manufacturerName: '_TZE200_2dpplnsn'}], + fingerprint: [ + {modelID: 'dpplnsn\u0000', manufacturerName: '_TYST11_2dpplnsn'}, + {modelID: 'TS0601', manufacturerName: '_TZE200_2dpplnsn'}, + ], model: 'HT-10', vendor: 'ETOP', description: 'Radiator valve', - fromZigbee: [legacy.fromZigbee.tuya_thermostat_weekly_schedule_1, legacy.fromZigbee.etop_thermostat, - fz.ignore_basic_report, fz.ignore_tuya_set_time], - toZigbee: [legacy.toZigbee.etop_thermostat_system_mode, legacy.toZigbee.etop_thermostat_away_mode, legacy.toZigbee.tuya_thermostat_child_lock, - legacy.toZigbee.tuya_thermostat_current_heating_setpoint, legacy.toZigbee.tuya_thermostat_weekly_schedule], + fromZigbee: [ + legacy.fromZigbee.tuya_thermostat_weekly_schedule_1, + legacy.fromZigbee.etop_thermostat, + fz.ignore_basic_report, + fz.ignore_tuya_set_time, + ], + toZigbee: [ + legacy.toZigbee.etop_thermostat_system_mode, + legacy.toZigbee.etop_thermostat_away_mode, + legacy.toZigbee.tuya_thermostat_child_lock, + legacy.toZigbee.tuya_thermostat_current_heating_setpoint, + legacy.toZigbee.tuya_thermostat_weekly_schedule, + ], onEvent: tuya.onEventSetTime, meta: { timeout: 20000, // TRV wakes up every 10sec @@ -4105,10 +4546,15 @@ const definitions: Definition[] = [ }, }, exposes: [ - e.battery_low(), e.child_lock(), e.away_mode(), e.climate() + e.battery_low(), + e.child_lock(), + e.away_mode(), + e + .climate() .withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET) .withLocalTemperature(ea.STATE) - .withSystemMode(['off', 'heat', 'auto'], ea.STATE_SET).withRunningState(['idle', 'heat'], ea.STATE), + .withSystemMode(['off', 'heat', 'auto'], ea.STATE_SET) + .withRunningState(['idle', 'heat'], ea.STATE), ], }, { @@ -4120,35 +4566,43 @@ const definitions: Definition[] = [ model: 'TS0601_thermostat_1', vendor: 'Tuya', description: 'Thermostatic radiator valve', - whiteLabel: [ - tuya.whitelabel('id3', 'GTZ06', 'Thermostatic radiator valve', ['_TZE200_z1tyspqw']), - ], + whiteLabel: [tuya.whitelabel('id3', 'GTZ06', 'Thermostatic radiator valve', ['_TZE200_z1tyspqw'])], onEvent: tuya.onEventSetLocalTime, fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, exposes: [ - e.battery(), e.child_lock(), e.max_temperature(), e.min_temperature(), - e.position(), e.window_detection(), + e.battery(), + e.child_lock(), + e.max_temperature(), + e.min_temperature(), + e.position(), + e.window_detection(), e.binary('window', ea.STATE, 'CLOSED', 'OPEN').withDescription('Window status closed or open '), e.binary('alarm_switch', ea.STATE, 'ON', 'OFF').withDescription('Thermostat in error state'), - e.climate() - .withLocalTemperature(ea.STATE).withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET) + e + .climate() + .withLocalTemperature(ea.STATE) + .withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET) .withLocalTemperatureCalibration(-30, 30, 0.1, ea.STATE_SET) - .withPreset(['auto', 'manual', 'off', 'on'], + .withPreset( + ['auto', 'manual', 'off', 'on'], 'MANUAL MODE ☝ - In this mode, the device executes manual temperature setting. ' + - 'When the set temperature is lower than the "minimum temperature", the valve is closed (forced closed). ' + - 'AUTO MODE ⏱ - In this mode, the device executes a preset week programming temperature time and temperature. ' + - 'ON - In this mode, the thermostat stays open ' + - 'OFF - In this mode, the thermostat stays closed') + 'When the set temperature is lower than the "minimum temperature", the valve is closed (forced closed). ' + + 'AUTO MODE ⏱ - In this mode, the device executes a preset week programming temperature time and temperature. ' + + 'ON - In this mode, the thermostat stays open ' + + 'OFF - In this mode, the thermostat stays closed', + ) .withSystemMode(['auto', 'heat', 'off'], ea.STATE) .withRunningState(['idle', 'heat'], ea.STATE), ...tuya.exposes.scheduleAllDays(ea.STATE_SET, 'HH:MM/C HH:MM/C HH:MM/C HH:MM/C'), - e.binary('boost_heating', ea.STATE_SET, 'ON', 'OFF') - .withDescription('Boost Heating: press and hold "+" for 3 seconds, ' + - 'the device will enter the boost heating mode, and the ▷╵◁ will flash. The countdown will be displayed in the APP'), - e.numeric('boost_time', ea.STATE_SET).withUnit('min').withDescription('Countdown in minutes') - .withValueMin(0).withValueMax(1000), + e + .binary('boost_heating', ea.STATE_SET, 'ON', 'OFF') + .withDescription( + 'Boost Heating: press and hold "+" for 3 seconds, ' + + 'the device will enter the boost heating mode, and the ▷╵◁ will flash. The countdown will be displayed in the APP', + ), + e.numeric('boost_time', ea.STATE_SET).withUnit('min').withDescription('Countdown in minutes').withValueMin(0).withValueMax(1000), ], meta: { tuyaDatapoints: [ @@ -4159,8 +4613,8 @@ const definitions: Definition[] = [ [3, 'local_temperature', tuya.valueConverter.divideBy10], [4, 'boost_heating', tuya.valueConverter.onOff], [5, 'boost_time', tuya.valueConverter.countdown], - [6, 'running_state', tuya.valueConverterBasic.lookup({'heat': 1, 'idle': 0})], - [7, 'window', tuya.valueConverterBasic.lookup({'OPEN': 1, 'CLOSE': 0})], + [6, 'running_state', tuya.valueConverterBasic.lookup({heat: 1, idle: 0})], + [7, 'window', tuya.valueConverterBasic.lookup({OPEN: 1, CLOSE: 0})], [8, 'window_detection', tuya.valueConverter.onOff], [12, 'child_lock', tuya.valueConverter.lockUnlock], [13, 'battery', tuya.valueConverter.raw], @@ -4180,9 +4634,7 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [ - {modelID: 'TS0601', manufacturerName: '_TZE204_rtrmfadk'}, - ], + fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE204_rtrmfadk'}], model: 'TRV601', vendor: 'Tuya', description: 'Thermostatic radiator valve.', @@ -4191,24 +4643,36 @@ const definitions: Definition[] = [ toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, exposes: [ - e.battery(), e.child_lock(), e.max_temperature(), e.min_temperature(), - e.position(), e.window_detection(), + e.battery(), + e.child_lock(), + e.max_temperature(), + e.min_temperature(), + e.position(), + e.window_detection(), e.binary('window', ea.STATE, 'OPEN', 'CLOSE').withDescription('Window status closed or open '), e.binary('alarm_switch', ea.STATE, 'ON', 'OFF').withDescription('Thermostat in error state'), - e.climate() - .withLocalTemperature(ea.STATE).withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET) + e + .climate() + .withLocalTemperature(ea.STATE) + .withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET) .withLocalTemperatureCalibration(-30, 30, 0.1, ea.STATE_SET) - .withPreset(['auto', 'manual', 'off', 'on'], + .withPreset( + ['auto', 'manual', 'off', 'on'], 'MANUAL MODE ☝ - In this mode, the device executes manual temperature setting. ' + - 'When the set temperature is lower than the "minimum temperature", the valve is closed (forced closed). ' + - 'AUTO MODE ⏱ - In this mode, the device executes a preset week programming temperature time and temperature. ' + - 'ON - In this mode, the thermostat stays open ' + - 'OFF - In this mode, the thermostat stays closed') + 'When the set temperature is lower than the "minimum temperature", the valve is closed (forced closed). ' + + 'AUTO MODE ⏱ - In this mode, the device executes a preset week programming temperature time and temperature. ' + + 'ON - In this mode, the thermostat stays open ' + + 'OFF - In this mode, the thermostat stays closed', + ) .withSystemMode(['auto', 'heat', 'off'], ea.STATE) .withRunningState(['idle', 'heat'], ea.STATE), ...tuya.exposes.scheduleAllDays(ea.STATE_SET, 'HH:MM/C HH:MM/C HH:MM/C HH:MM/C'), - e.enum('mode', ea.STATE_SET, ['comfort', 'eco']).withDescription('Hysteresis - comfort > switches off/on exactly at reached ' + - 'temperature with valve smooth from 0 to 100%, eco > 0.5 degrees above or below, valve either 0 or 100%'), + e + .enum('mode', ea.STATE_SET, ['comfort', 'eco']) + .withDescription( + 'Hysteresis - comfort > switches off/on exactly at reached ' + + 'temperature with valve smooth from 0 to 100%, eco > 0.5 degrees above or below, valve either 0 or 100%', + ), ], meta: { tuyaDatapoints: [ @@ -4217,8 +4681,8 @@ const definitions: Definition[] = [ [1, 'preset', tuya.valueConverter.thermostatSystemModeAndPreset('preset')], [2, 'current_heating_setpoint', tuya.valueConverter.divideBy10], [3, 'local_temperature', tuya.valueConverter.divideBy10], - [6, 'running_state', tuya.valueConverterBasic.lookup({'heat': 1, 'idle': 0})], - [7, 'window', tuya.valueConverterBasic.lookup({'OPEN': 1, 'CLOSE': 0})], + [6, 'running_state', tuya.valueConverterBasic.lookup({heat: 1, idle: 0})], + [7, 'window', tuya.valueConverterBasic.lookup({OPEN: 1, CLOSE: 0})], [8, 'window_detection', tuya.valueConverter.onOff], [12, 'child_lock', tuya.valueConverter.lockUnlock], [13, 'battery', tuya.valueConverter.raw], @@ -4234,14 +4698,12 @@ const definitions: Definition[] = [ [23, 'schedule_sunday', tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(7)], [101, 'local_temperature_calibration', tuya.valueConverter.localTempCalibration1], [108, 'position', tuya.valueConverter.divideBy10], - [114, 'mode', tuya.valueConverterBasic.lookup({'comfort': tuya.enum(0), 'eco': tuya.enum(1)})], + [114, 'mode', tuya.valueConverterBasic.lookup({comfort: tuya.enum(0), eco: tuya.enum(1)})], ], }, }, { - fingerprint: [ - {modelID: 'TS0601', manufacturerName: '_TZE200_rtrmfadk'}, - ], + fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_rtrmfadk'}], model: 'TRV602', vendor: 'Tuya', description: 'Thermostatic radiator valve.', @@ -4250,26 +4712,38 @@ const definitions: Definition[] = [ toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, exposes: [ - e.battery(), e.child_lock(), e.max_temperature(), e.min_temperature(), - e.position(), e.window_detection(), + e.battery(), + e.child_lock(), + e.max_temperature(), + e.min_temperature(), + e.position(), + e.window_detection(), e.binary('window', ea.STATE, 'OPEN', 'CLOSE').withDescription('Window status closed or open '), e.binary('alarm_switch', ea.STATE, 'ON', 'OFF').withDescription('Thermostat in error state'), - e.climate() - .withLocalTemperature(ea.STATE).withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET) + e + .climate() + .withLocalTemperature(ea.STATE) + .withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET) .withLocalTemperatureCalibration(-30, 30, 0.1, ea.STATE_SET) - .withPreset(['auto', 'manual', 'off', 'on'], + .withPreset( + ['auto', 'manual', 'off', 'on'], 'MANUAL MODE ☝ - In this mode, the device executes manual temperature setting. ' + - 'When the set temperature is lower than the "minimum temperature", the valve is closed (forced closed). ' + - 'AUTO MODE ⏱ - In this mode, the device executes a preset week programming temperature time and temperature. ' + - 'ON - In this mode, the thermostat stays open ' + - 'OFF - In this mode, the thermostat stays closed') + 'When the set temperature is lower than the "minimum temperature", the valve is closed (forced closed). ' + + 'AUTO MODE ⏱ - In this mode, the device executes a preset week programming temperature time and temperature. ' + + 'ON - In this mode, the thermostat stays open ' + + 'OFF - In this mode, the thermostat stays closed', + ) .withSystemMode(['auto', 'heat', 'off'], ea.STATE) .withRunningState(['idle', 'heat'], ea.STATE), ...tuya.exposes.scheduleAllDays(ea.STATE_SET, 'HH:MM/C HH:MM/C HH:MM/C HH:MM/C'), e.enum('display_brightness', ea.STATE_SET, ['high', 'medium', 'low']).withDescription('Display brightness'), e.enum('screen_orientation', ea.STATE_SET, ['up', 'right', 'down', 'left']).withDescription('Screen orientation'), - e.enum('mode', ea.STATE_SET, ['comfort', 'eco']).withDescription('Hysteresis - comfort > switches off/on exactly at reached ' + - 'temperature with valve smooth from 0 to 100%, eco > 0.5 degrees above or below, valve either 0 or 100%'), + e + .enum('mode', ea.STATE_SET, ['comfort', 'eco']) + .withDescription( + 'Hysteresis - comfort > switches off/on exactly at reached ' + + 'temperature with valve smooth from 0 to 100%, eco > 0.5 degrees above or below, valve either 0 or 100%', + ), ], meta: { tuyaDatapoints: [ @@ -4278,8 +4752,8 @@ const definitions: Definition[] = [ [1, 'preset', tuya.valueConverter.thermostatSystemModeAndPreset('preset')], [2, 'current_heating_setpoint', tuya.valueConverter.divideBy10], [3, 'local_temperature', tuya.valueConverter.divideBy10], - [6, 'running_state', tuya.valueConverterBasic.lookup({'heat': 1, 'idle': 0})], - [7, 'window', tuya.valueConverterBasic.lookup({'OPEN': 1, 'CLOSE': 0})], + [6, 'running_state', tuya.valueConverterBasic.lookup({heat: 1, idle: 0})], + [7, 'window', tuya.valueConverterBasic.lookup({OPEN: 1, CLOSE: 0})], [8, 'window_detection', tuya.valueConverter.onOff], [12, 'child_lock', tuya.valueConverter.lockUnlock], [13, 'battery', tuya.valueConverter.raw], @@ -4295,11 +4769,18 @@ const definitions: Definition[] = [ [23, 'schedule_sunday', tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(7)], [101, 'local_temperature_calibration', tuya.valueConverter.localTempCalibration1], [108, 'position', tuya.valueConverter.divideBy10], - [111, 'display_brightness', tuya.valueConverterBasic.lookup({'high': tuya.enum(0), 'medium': tuya.enum(1), 'low': tuya.enum(2)})], - [113, 'screen_orientation', tuya.valueConverterBasic.lookup({ - 'up': tuya.enum(0), 'right': tuya.enum(1), 'down': tuya.enum(2), 'left': tuya.enum(3), - })], - [114, 'mode', tuya.valueConverterBasic.lookup({'comfort': tuya.enum(0), 'eco': tuya.enum(1)})], + [111, 'display_brightness', tuya.valueConverterBasic.lookup({high: tuya.enum(0), medium: tuya.enum(1), low: tuya.enum(2)})], + [ + 113, + 'screen_orientation', + tuya.valueConverterBasic.lookup({ + up: tuya.enum(0), + right: tuya.enum(1), + down: tuya.enum(2), + left: tuya.enum(3), + }), + ], + [114, 'mode', tuya.valueConverterBasic.lookup({comfort: tuya.enum(0), eco: tuya.enum(1)})], ], }, }, @@ -4315,15 +4796,18 @@ const definitions: Definition[] = [ {vendor: 'Connecte', model: '4500993'}, ], vendor: 'Tuya', - fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, fz.ignore_basic_report, tuya.fz.power_outage_memory, - tuya.fz.indicator_mode], + fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, fz.ignore_basic_report, tuya.fz.power_outage_memory, tuya.fz.indicator_mode], toZigbee: [tz.on_off, tuya.tz.power_on_behavior_1, tuya.tz.backlight_indicator_mode_1], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement', 'seMetering']); endpoint.saveClusterAttributeKeyValue('seMetering', {divisor: 100, multiplier: 1}); endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', { - acVoltageMultiplier: 1, acVoltageDivisor: 1, acCurrentMultiplier: 1, acCurrentDivisor: 1000, acPowerMultiplier: 1, + acVoltageMultiplier: 1, + acVoltageDivisor: 1, + acCurrentMultiplier: 1, + acCurrentDivisor: 1000, + acPowerMultiplier: 1, acPowerDivisor: 1, }); try { @@ -4331,17 +4815,24 @@ const definitions: Definition[] = [ await reporting.rmsVoltage(endpoint, {change: 5}); await reporting.rmsCurrent(endpoint, {change: 50}); await reporting.activePower(endpoint, {change: 10}); - } catch (error) {/* fails for some https://github.com/Koenkk/zigbee2mqtt/issues/11179 - and https://github.com/Koenkk/zigbee2mqtt/issues/16864 */} + } catch (error) { + /* fails for some https://github.com/Koenkk/zigbee2mqtt/issues/11179 + and https://github.com/Koenkk/zigbee2mqtt/issues/16864 */ + } await endpoint.read('genOnOff', ['onOff', 'moesStartUpOnOff', 'tuyaBacklightMode']); }, options: [exposes.options.measurement_poll_interval()], // This device doesn't support reporting correctly. // https://github.com/Koenkk/zigbee-herdsman-converters/pull/1270 - exposes: [e.switch(), e.power(), e.current(), e.voltage(), - e.energy(), e.enum('power_outage_memory', ea.ALL, ['on', 'off', 'restore']) - .withDescription('Recover state after power outage'), - e.enum('indicator_mode', ea.ALL, ['off', 'off/on', 'on/off']).withDescription('LED indicator mode')], + exposes: [ + e.switch(), + e.power(), + e.current(), + e.voltage(), + e.energy(), + e.enum('power_outage_memory', ea.ALL, ['on', 'off', 'restore']).withDescription('Recover state after power outage'), + e.enum('indicator_mode', ea.ALL, ['off', 'off/on', 'on/off']).withDescription('LED indicator mode'), + ], onEvent: (type, data, device, options) => tuya.onEventMeasurementPoll(type, data, device, options, true, false), }, { @@ -4359,10 +4850,16 @@ const definitions: Definition[] = [ model: 'TS011F_plug_1', description: 'Smart plug (with power monitoring)', vendor: 'Tuya', - whiteLabel: [{vendor: 'LELLKI', model: 'TS011F_plug'}, {vendor: 'Neo', model: 'NAS-WR01B'}, - {vendor: 'BlitzWolf', model: 'BW-SHP15'}, {vendor: 'BlitzWolf', model: 'BW-SHP13'}, - {vendor: 'MatSee Plus', model: 'PJ-ZSW01'}, {vendor: 'MODEMIX', model: 'MOD037'}, {vendor: 'MODEMIX', model: 'MOD048'}, - {vendor: 'Coswall', model: 'CS-AJ-DE2U-ZG-11'}, {vendor: 'Aubess', model: 'TS011F_plug_1'}, + whiteLabel: [ + {vendor: 'LELLKI', model: 'TS011F_plug'}, + {vendor: 'Neo', model: 'NAS-WR01B'}, + {vendor: 'BlitzWolf', model: 'BW-SHP15'}, + {vendor: 'BlitzWolf', model: 'BW-SHP13'}, + {vendor: 'MatSee Plus', model: 'PJ-ZSW01'}, + {vendor: 'MODEMIX', model: 'MOD037'}, + {vendor: 'MODEMIX', model: 'MOD048'}, + {vendor: 'Coswall', model: 'CS-AJ-DE2U-ZG-11'}, + {vendor: 'Aubess', model: 'TS011F_plug_1'}, tuya.whitelabel('Nous', 'A1Z', 'Smart plug (with power monitoring)', ['_TZ3000_2putqrmw']), tuya.whitelabel('Moes', 'MOES_plug', 'Smart plug (with power monitoring)', ['_TZ3000_yujkchbz']), tuya.whitelabel('Moes', 'ZK-EU', 'Smart wallsocket (with power monitoring)', ['_TZ3000_ss98ec5d']), @@ -4370,9 +4867,16 @@ const definitions: Definition[] = [ tuya.whitelabel('Elivco', 'LSPA9', 'Smart plug (with power monitoring)', ['_TZ3000_okaz9tjs']), ], ota: ota.zigbeeOTA, - extend: [tuya.modernExtend.tuyaOnOff({ - electricalMeasurements: true, electricalMeasurementsFzConverter: fzLocal.TS011F_electrical_measurement, - powerOutageMemory: true, indicatorMode: true, childLock: true, onOffCountdown: true})], + extend: [ + tuya.modernExtend.tuyaOnOff({ + electricalMeasurements: true, + electricalMeasurementsFzConverter: fzLocal.TS011F_electrical_measurement, + powerOutageMemory: true, + indicatorMode: true, + childLock: true, + onOffCountdown: true, + }), + ], configure: async (device, coordinatorEndpoint) => { await tuya.configureMagicPacket(device, coordinatorEndpoint); const endpoint = device.getEndpoint(1); @@ -4394,8 +4898,14 @@ const definitions: Definition[] = [ }, }, { - fingerprint: tuya.fingerprint('TS011F', - ['_TZ3000_hyfvrar3', '_TZ3000_v1pdxuqq', '_TZ3000_8a833yls', '_TZ3000_bfn1w0mm', '_TZ3000_nzkqcvvs', '_TZ3000_rtcrrvia']), + fingerprint: tuya.fingerprint('TS011F', [ + '_TZ3000_hyfvrar3', + '_TZ3000_v1pdxuqq', + '_TZ3000_8a833yls', + '_TZ3000_bfn1w0mm', + '_TZ3000_nzkqcvvs', + '_TZ3000_rtcrrvia', + ]), model: 'TS011F_plug_2', description: 'Smart plug (without power monitoring)', vendor: 'Tuya', @@ -4418,8 +4928,12 @@ const definitions: Definition[] = [ model: 'TS011F_plug_3', description: 'Smart plug (with power monitoring by polling)', vendor: 'Tuya', - whiteLabel: [{vendor: 'VIKEFON', model: 'TS011F'}, {vendor: 'BlitzWolf', model: 'BW-SHP15'}, - {vendor: 'AVATTO', model: 'MIUCOT10Z'}, {vendor: 'Neo', model: 'NAS-WR01B'}, {vendor: 'Neo', model: 'PLUG-001SPB2'}, + whiteLabel: [ + {vendor: 'VIKEFON', model: 'TS011F'}, + {vendor: 'BlitzWolf', model: 'BW-SHP15'}, + {vendor: 'AVATTO', model: 'MIUCOT10Z'}, + {vendor: 'Neo', model: 'NAS-WR01B'}, + {vendor: 'Neo', model: 'PLUG-001SPB2'}, tuya.whitelabel('Tuya', 'BSD29_1', 'Smart plug (with power monitoring by polling)', ['_TZ3000_okaz9tjs']), ], ota: ota.zigbeeOTA, @@ -4434,7 +4948,11 @@ const definitions: Definition[] = [ }, options: [exposes.options.measurement_poll_interval()], onEvent: (type, data, device, options) => - tuya.onEventMeasurementPoll(type, data, device, options, + tuya.onEventMeasurementPoll( + type, + data, + device, + options, true, // polling for voltage, current and power [100, 160].includes(device.applicationVersion) || ['1.0.5\u0000'].includes(device.softwareBuildID), // polling for energy ), @@ -4477,9 +4995,7 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [ - {modelID: 'TS0601', manufacturerName: '_TZE204_ntcy3xu1'}, - ], + fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE204_ntcy3xu1'}], model: 'TS0601_smoke_1', vendor: 'Tuya', description: 'Smoke sensor', @@ -4496,9 +5012,7 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [ - {modelID: 'TS0601', manufacturerName: '_TZE200_ntcy3xu1'}, - ], + fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_ntcy3xu1'}], model: 'TS0601_smoke_6', vendor: 'Tuya', description: 'Smoke sensor', @@ -4524,7 +5038,9 @@ const definitions: Definition[] = [ onEvent: tuya.onEventSetTime, configure: tuya.configureMagicPacket, exposes: [ - e.smoke(), e.battery(), e.test(), + e.smoke(), + e.battery(), + e.test(), e.numeric('smoke_concentration', ea.STATE).withUnit('ppm').withDescription('Parts per million of smoke detected'), e.binary('device_fault', ea.STATE, true, false).withDescription('Indicates a fault with the device'), ], @@ -4545,9 +5061,7 @@ const definitions: Definition[] = [ vendor: 'Tuya', model: 'TS0601_smoke_3', description: 'Photoelectric smoke detector', - whiteLabel: [ - {vendor: 'KnockautX', model: 'SMOAL024'}, - ], + whiteLabel: [{vendor: 'KnockautX', model: 'SMOAL024'}], configure: tuya.configureMagicPacket, fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], @@ -4593,8 +5107,14 @@ const definitions: Definition[] = [ fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, - exposes: [e.smoke(), e.tamper(), e.battery(), tuya.exposes.faultAlarm(), - tuya.exposes.silence(), e.binary('alarm', ea.STATE_SET, 'ON', 'OFF').withDescription('Enable the alarm')], + exposes: [ + e.smoke(), + e.tamper(), + e.battery(), + tuya.exposes.faultAlarm(), + tuya.exposes.silence(), + e.binary('alarm', ea.STATE_SET, 'ON', 'OFF').withDescription('Enable the alarm'), + ], meta: { tuyaDatapoints: [ [1, 'smoke', tuya.valueConverter.trueFalse0], @@ -4615,8 +5135,15 @@ const definitions: Definition[] = [ toZigbee: [tuya.tz.datapoints], onEvent: tuya.onEventSetTime, configure: tuya.configureMagicPacket, - exposes: [e.smoke(), tuya.exposes.faultAlarm(), tuya.exposes.batteryState(), e.battery(), tuya.exposes.silence(), tuya.exposes.selfTest(), - e.numeric('smoke_concentration', ea.STATE).withUnit('ppm').withDescription('Parts per million of smoke detected')], + exposes: [ + e.smoke(), + tuya.exposes.faultAlarm(), + tuya.exposes.batteryState(), + e.battery(), + tuya.exposes.silence(), + tuya.exposes.selfTest(), + e.numeric('smoke_concentration', ea.STATE).withUnit('ppm').withDescription('Parts per million of smoke detected'), + ], meta: { tuyaDatapoints: [ [1, 'smoke', tuya.valueConverter.trueFalse0], @@ -4637,11 +5164,13 @@ const definitions: Definition[] = [ meta: {timeout: 30000, disableDefaultResponse: true}, fromZigbee: [legacy.fromZigbee.SA12IZL], toZigbee: [legacy.toZigbee.SA12IZL_silence_siren, legacy.toZigbee.SA12IZL_alarm], - exposes: [e.battery(), + exposes: [ + e.battery(), e.binary('smoke', ea.STATE, true, false).withDescription('Smoke alarm status'), e.enum('battery_level', ea.STATE, ['low', 'middle', 'high']).withDescription('Battery level state'), e.binary('alarm', ea.STATE_SET, true, false).withDescription('Enable the alarm'), - e.binary('silence_siren', ea.STATE_SET, true, false).withDescription('Silence the siren')], + e.binary('silence_siren', ea.STATE_SET, true, false).withDescription('Silence the siren'), + ], onEvent: tuya.onEventSetTime, }, { @@ -4661,9 +5190,7 @@ const definitions: Definition[] = [ [101, 'energy', tuya.valueConverter.divideBy1000], ], }, - whiteLabel: [ - tuya.whitelabel('Tuya', 'PJ-1203-W', 'Electricity energy monitor', ['_TZE284_cjbofhxw']), - ], + whiteLabel: [tuya.whitelabel('Tuya', 'PJ-1203-W', 'Electricity energy monitor', ['_TZE284_cjbofhxw'])], }, { fingerprint: tuya.fingerprint('TS0601', ['_TZE200_bkkmqmyo', '_TZE200_eaac7dkw', '_TZE204_wbhaespm', '_TZE204_bkkmqmyo']), @@ -4673,10 +5200,19 @@ const definitions: Definition[] = [ fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, - exposes: [tuya.exposes.switch(), e.ac_frequency(), e.energy(), e.power(), e.power_factor().withUnit('%'), - e.voltage(), e.current(), e.produced_energy(), e.power_reactive(), + exposes: [ + tuya.exposes.switch(), + e.ac_frequency(), + e.energy(), + e.power(), + e.power_factor().withUnit('%'), + e.voltage(), + e.current(), + e.produced_energy(), + e.power_reactive(), e.numeric('energy_reactive', ea.STATE).withUnit('kVArh').withDescription('Sum of reactive energy'), - e.numeric('total_energy', ea.STATE).withUnit('kWh').withDescription('Total consumed and produced energy')], + e.numeric('total_energy', ea.STATE).withUnit('kWh').withDescription('Total consumed and produced energy'), + ], meta: { tuyaDatapoints: [ [1, 'energy', tuya.valueConverter.divideBy100], @@ -4695,10 +5231,13 @@ const definitions: Definition[] = [ [18, null, null], // 18 - Alarm set2 - value seems garbage "AAUAZAAFAB4APAAAAAAAAAA=" ], }, - whiteLabel: [{vendor: 'Tuya', model: 'RC-MCB'}, + whiteLabel: [ + {vendor: 'Tuya', model: 'RC-MCB'}, tuya.whitelabel('RTX', 'ZCR1-40EM', 'Zigbee DIN energy meter', ['_TZE204_wbhaespm']), - tuya.whitelabel('Hiking', 'DDS238-2', - 'Single phase DIN-rail energy meter with switch function', ['_TZE200_bkkmqmyo', '_TZE204_bkkmqmyo']), + tuya.whitelabel('Hiking', 'DDS238-2', 'Single phase DIN-rail energy meter with switch function', [ + '_TZE200_bkkmqmyo', + '_TZE204_bkkmqmyo', + ]), ], }, { @@ -4709,23 +5248,34 @@ const definitions: Definition[] = [ fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, - exposes: [tuya.exposes.switch(), e.energy(), e.power(), e.voltage(), e.current(), - e.enum('fault', ea.STATE, ['clear', 'over_current_threshold', 'over_power_threshold', - 'over_voltage threshold', 'wrong_frequency_threshold']).withDescription('Fault status of the device (clear = nothing)'), - e.enum('threshold_1', ea.STATE, ['not_set', 'over_current_threshold', 'over_voltage_threshold']) - .withDescription('State of threshold_1'), - e.binary('threshold_1_protection', ea.STATE, 'ON', 'OFF') + exposes: [ + tuya.exposes.switch(), + e.energy(), + e.power(), + e.voltage(), + e.current(), + e + .enum('fault', ea.STATE, [ + 'clear', + 'over_current_threshold', + 'over_power_threshold', + 'over_voltage threshold', + 'wrong_frequency_threshold', + ]) + .withDescription('Fault status of the device (clear = nothing)'), + e.enum('threshold_1', ea.STATE, ['not_set', 'over_current_threshold', 'over_voltage_threshold']).withDescription('State of threshold_1'), + e + .binary('threshold_1_protection', ea.STATE, 'ON', 'OFF') .withDescription('OFF - alarm only, ON - relay will be off when threshold reached'), - e.numeric('threshold_1_value', ea.STATE) + e + .numeric('threshold_1_value', ea.STATE) .withDescription('Can be in Volt or Ampere depending on threshold setting. Setup the value on the device'), - e.enum('threshold_2', ea.STATE, ['not_set', 'over_current_threshold', 'over_voltage_threshold']) - .withDescription('State of threshold_2'), - e.binary('threshold_2_protection', ea.STATE, 'ON', 'OFF') + e.enum('threshold_2', ea.STATE, ['not_set', 'over_current_threshold', 'over_voltage_threshold']).withDescription('State of threshold_2'), + e + .binary('threshold_2_protection', ea.STATE, 'ON', 'OFF') .withDescription('OFF - alarm only, ON - relay will be off when threshold reached'), - e.numeric('threshold_2_value', ea.STATE) - .withDescription('Setup value on the device'), - e.binary('clear_fault', ea.STATE_SET, 'ON', 'OFF') - .withDescription('Turn ON to clear last the fault'), + e.numeric('threshold_2_value', ea.STATE).withDescription('Setup value on the device'), + e.binary('clear_fault', ea.STATE_SET, 'ON', 'OFF').withDescription('Turn ON to clear last the fault'), e.text('meter_id', ea.STATE).withDescription('Meter ID (ID of device)'), ], meta: { @@ -4734,8 +5284,17 @@ const definitions: Definition[] = [ [3, null, null], // Monthly, but sends data only after request [4, null, null], // Dayly, but sends data only after request [6, null, tuya.valueConverter.phaseVariant2], // voltage and current - [10, 'fault', tuya.valueConverterBasic.lookup({'clear': 0, 'over_current_threshold': 1, - 'over_power_threshold': 2, 'over_voltage_threshold': 4, 'wrong_frequency_threshold': 8})], + [ + 10, + 'fault', + tuya.valueConverterBasic.lookup({ + clear: 0, + over_current_threshold: 1, + over_power_threshold: 2, + over_voltage_threshold: 4, + wrong_frequency_threshold: 8, + }), + ], [11, null, null], // Frozen - strange function, in native app - nothing is clear [16, 'state', tuya.valueConverter.onOff], [17, null, tuya.valueConverter.threshold], // It's settable, but can't write converter @@ -4747,9 +5306,7 @@ const definitions: Definition[] = [ [24, null, null], // Forward Energy T4 - don't know what this ], }, - whiteLabel: [ - tuya.whitelabel('MatSee Plus', 'DAC2161C', 'Smart Zigbee energy meter 80A din rail', ['_TZE200_lsanae15', '_TZE204_lsanae15']), - ], + whiteLabel: [tuya.whitelabel('MatSee Plus', 'DAC2161C', 'Smart Zigbee energy meter 80A din rail', ['_TZE200_lsanae15', '_TZE204_lsanae15'])], }, { fingerprint: tuya.fingerprint('TS0601', ['_TZE200_rhblgy0z', '_TZE204_rhblgy0z']), @@ -4760,23 +5317,35 @@ const definitions: Definition[] = [ toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, whiteLabel: [{vendor: 'XOCA', model: 'DAC2161C'}], - exposes: [tuya.exposes.switch(), e.energy(), e.produced_energy(), e.power(), e.voltage(), e.current(), - e.enum('fault', ea.STATE, ['clear', 'over_current_threshold', 'over_power_threshold', - 'over_voltage threshold', 'wrong_frequency_threshold']).withDescription('Fault status of the device (clear = nothing)'), - e.enum('threshold_1', ea.STATE, ['not_set', 'over_current_threshold', 'over_voltage_threshold']) - .withDescription('State of threshold_1'), - e.binary('threshold_1_protection', ea.STATE, 'ON', 'OFF') + exposes: [ + tuya.exposes.switch(), + e.energy(), + e.produced_energy(), + e.power(), + e.voltage(), + e.current(), + e + .enum('fault', ea.STATE, [ + 'clear', + 'over_current_threshold', + 'over_power_threshold', + 'over_voltage threshold', + 'wrong_frequency_threshold', + ]) + .withDescription('Fault status of the device (clear = nothing)'), + e.enum('threshold_1', ea.STATE, ['not_set', 'over_current_threshold', 'over_voltage_threshold']).withDescription('State of threshold_1'), + e + .binary('threshold_1_protection', ea.STATE, 'ON', 'OFF') .withDescription('OFF - alarm only, ON - relay will be off when threshold reached'), - e.numeric('threshold_1_value', ea.STATE) + e + .numeric('threshold_1_value', ea.STATE) .withDescription('Can be in Volt or Ampere depending on threshold setting. Setup the value on the device'), - e.enum('threshold_2', ea.STATE, ['not_set', 'over_current_threshold', 'over_voltage_threshold']) - .withDescription('State of threshold_2'), - e.binary('threshold_2_protection', ea.STATE, 'ON', 'OFF') + e.enum('threshold_2', ea.STATE, ['not_set', 'over_current_threshold', 'over_voltage_threshold']).withDescription('State of threshold_2'), + e + .binary('threshold_2_protection', ea.STATE, 'ON', 'OFF') .withDescription('OFF - alarm only, ON - relay will be off when threshold reached'), - e.numeric('threshold_2_value', ea.STATE) - .withDescription('Setup value on the device'), - e.binary('clear_fault', ea.STATE_SET, 'ON', 'OFF') - .withDescription('Turn ON to clear last the fault'), + e.numeric('threshold_2_value', ea.STATE).withDescription('Setup value on the device'), + e.binary('clear_fault', ea.STATE_SET, 'ON', 'OFF').withDescription('Turn ON to clear last the fault'), e.text('meter_id', ea.STATE).withDescription('Meter ID (ID of device)'), ], meta: { @@ -4786,8 +5355,17 @@ const definitions: Definition[] = [ [3, null, null], // Monthly, but sends data only after request [4, null, null], // Dayly, but sends data only after request [6, null, tuya.valueConverter.phaseVariant2], // voltage and current - [10, 'fault', tuya.valueConverterBasic.lookup({'clear': 0, 'over_current_threshold': 1, - 'over_power_threshold': 2, 'over_voltage_threshold': 4, 'wrong_frequency_threshold': 8})], + [ + 10, + 'fault', + tuya.valueConverterBasic.lookup({ + clear: 0, + over_current_threshold: 1, + over_power_threshold: 2, + over_voltage_threshold: 4, + wrong_frequency_threshold: 8, + }), + ], [11, null, null], // Frozen - strange function, in native app - nothing is clear [16, 'state', tuya.valueConverter.onOff], [17, null, tuya.valueConverter.threshold], // It's settable, but can't write converter @@ -4801,9 +5379,11 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_byzdayie'}, + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE200_byzdayie'}, {modelID: 'TS0601', manufacturerName: '_TZE200_fsb6zw01'}, - {modelID: 'TS0601', manufacturerName: '_TZE200_ewxhg6o9'}], + {modelID: 'TS0601', manufacturerName: '_TZE200_ewxhg6o9'}, + ], model: 'TS0601_din', vendor: 'Tuya', description: 'Zigbee smart energy meter DDS238-2 Zigbee', @@ -4829,7 +5409,7 @@ const definitions: Definition[] = [ description: 'Zigbee dimmer module 2 channel', whiteLabel: [{vendor: 'OXT', model: 'SWTZ25'}], extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2}}), + deviceEndpoints({endpoints: {l1: 1, l2: 2}}), tuyaLight({minBrightness: 'attribute', endpointNames: ['l1', 'l2'], configureReporting: true}), ], configure: async (device, coordinatorEndpoint) => { @@ -4838,9 +5418,14 @@ const definitions: Definition[] = [ }, { zigbeeModel: ['RH3001'], - fingerprint: [{type: 'EndDevice', manufacturerID: 4098, applicationVersion: 66, endpoints: [ - {ID: 1, profileID: 260, deviceID: 1026, inputClusters: [0, 10, 1, 1280], outputClusters: [25]}, - ]}], + fingerprint: [ + { + type: 'EndDevice', + manufacturerID: 4098, + applicationVersion: 66, + endpoints: [{ID: 1, profileID: 260, deviceID: 1026, inputClusters: [0, 10, 1, 1280], outputClusters: [25]}], + }, + ], model: 'SNTZ007', vendor: 'Tuya', description: 'Rechargeable Zigbee contact sensor', @@ -4856,7 +5441,10 @@ const definitions: Definition[] = [ description: 'PIR sensor', fromZigbee: [fz.battery, fz.ignore_basic_report, fz.ias_occupancy_alarm_1], toZigbee: [], - whiteLabel: [{vendor: 'Samotech', model: 'SM301Z'}, {vendor: 'Nedis', model: 'ZBSM10WT'}], + whiteLabel: [ + {vendor: 'Samotech', model: 'SM301Z'}, + {vendor: 'Nedis', model: 'ZBSM10WT'}, + ], exposes: [e.battery(), e.occupancy(), e.battery_low(), e.tamper()], }, { @@ -4915,14 +5503,20 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'TS0011', manufacturerName: '_TZ3000_qmi1cfuq'}, - {modelID: 'TS0011', manufacturerName: '_TZ3000_txpirhfq'}, {modelID: 'TS0011', manufacturerName: '_TZ3000_ji4araar'}], + fingerprint: [ + {modelID: 'TS0011', manufacturerName: '_TZ3000_qmi1cfuq'}, + {modelID: 'TS0011', manufacturerName: '_TZ3000_txpirhfq'}, + {modelID: 'TS0011', manufacturerName: '_TZ3000_ji4araar'}, + ], model: 'TS0011_switch_module', vendor: 'Tuya', description: '1 gang switch module - (without neutral)', extend: [tuya.modernExtend.tuyaOnOff({switchType: true})], - whiteLabel: [{vendor: 'AVATTO', model: '1gang N-ZLWSM01'}, {vendor: 'SMATRUL', model: 'TMZ02L-16A-W'}, - {vendor: 'Aubess', model: 'TMZ02L-16A-B'}], + whiteLabel: [ + {vendor: 'AVATTO', model: '1gang N-ZLWSM01'}, + {vendor: 'SMATRUL', model: 'TMZ02L-16A-W'}, + {vendor: 'Aubess', model: 'TMZ02L-16A-B'}, + ], configure: async (device, coordinatorEndpoint) => { await tuya.configureMagicPacket(device, coordinatorEndpoint); await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff']); @@ -4935,13 +5529,16 @@ const definitions: Definition[] = [ model: 'TS0012', vendor: 'Tuya', description: 'Smart light switch - 2 gang', - whiteLabel: [{vendor: 'Vrey', model: 'VR-X712U-0013'}, {vendor: 'TUYATEC', model: 'GDKES-02TZXD'}, - {vendor: 'Earda', model: 'ESW-2ZAA-EU'}, {vendor: 'Moes', model: 'ZS-US2-BK-MS'}, + whiteLabel: [ + {vendor: 'Vrey', model: 'VR-X712U-0013'}, + {vendor: 'TUYATEC', model: 'GDKES-02TZXD'}, + {vendor: 'Earda', model: 'ESW-2ZAA-EU'}, + {vendor: 'Moes', model: 'ZS-US2-BK-MS'}, tuya.whitelabel('Moes', 'ZS-EUB_2gang', 'Smart light switch - 2 gang', ['_TZ3000_18ejxno0']), ], extend: [tuya.modernExtend.tuyaOnOff({backlightModeOffNormalInverted: true, endpoints: ['left', 'right']})], endpoint: (device) => { - return {'left': 1, 'right': 2}; + return {left: 1, right: 2}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -4953,11 +5550,13 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'TS0012', manufacturerName: '_TZ3000_jl7qyupf'}, + fingerprint: [ + {modelID: 'TS0012', manufacturerName: '_TZ3000_jl7qyupf'}, {modelID: 'TS0012', manufacturerName: '_TZ3000_nPGIPl5D'}, {modelID: 'TS0012', manufacturerName: '_TZ3000_kpatq5pq'}, {modelID: 'TS0012', manufacturerName: '_TZ3000_ljhbw1c9'}, - {modelID: 'TS0012', manufacturerName: '_TZ3000_4zf0crgo'}], + {modelID: 'TS0012', manufacturerName: '_TZ3000_4zf0crgo'}, + ], model: 'TS0012_switch_module', vendor: 'Tuya', description: '2 gang switch module - (without neutral)', @@ -4967,7 +5566,7 @@ const definitions: Definition[] = [ ], extend: [tuya.modernExtend.tuyaOnOff({switchType: true, endpoints: ['left', 'right']})], endpoint: (device) => { - return {'left': 1, 'right': 2}; + return {left: 1, right: 2}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -4985,7 +5584,7 @@ const definitions: Definition[] = [ description: 'Smart light switch - 3 gang without neutral wire', extend: [tuya.modernExtend.tuyaOnOff({backlightModeOffNormalInverted: true, endpoints: ['left', 'center', 'right']})], endpoint: (device) => { - return {'left': 1, 'center': 2, 'right': 3}; + return {left: 1, center: 2, right: 3}; }, whiteLabel: [{vendor: 'TUYATEC', model: 'GDKES-03TZXD'}], meta: {multiEndpoint: true}, @@ -5017,7 +5616,7 @@ const definitions: Definition[] = [ ], extend: [tuya.modernExtend.tuyaOnOff({switchType: true, endpoints: ['left', 'center', 'right']})], endpoint: (device) => { - return {'left': 1, 'center': 2, 'right': 3}; + return {left: 1, center: 2, right: 3}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -5041,10 +5640,14 @@ const definitions: Definition[] = [ description: 'Smart light switch - 4 gang without neutral wire', extend: [tuya.modernExtend.tuyaOnOff({backlightModeLowMediumHigh: true, endpoints: ['l1', 'l2', 'l3', 'l4']})], endpoint: (device) => { - return {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4}; + return {l1: 1, l2: 2, l3: 3, l4: 4}; }, - whiteLabel: [{vendor: 'TUYATEC', model: 'GDKES-04TZXD'}, {vendor: 'Vizo', model: 'VZ-222S'}, - {vendor: 'MakeGood', model: 'MG-ZG04W/B/G'}, {vendor: 'Mercator Ikuü', model: 'SSW04'}], + whiteLabel: [ + {vendor: 'TUYATEC', model: 'GDKES-04TZXD'}, + {vendor: 'Vizo', model: 'VZ-222S'}, + {vendor: 'MakeGood', model: 'MG-ZG04W/B/G'}, + {vendor: 'Mercator Ikuü', model: 'SSW04'}, + ], meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { await tuya.configureMagicPacket(device, coordinatorEndpoint); @@ -5108,7 +5711,7 @@ const definitions: Definition[] = [ description: 'Smart light switch - 4 gang with neutral wire', extend: [tuya.modernExtend.tuyaOnOff({powerOnBehavior2: true, backlightModeOffOn: true, endpoints: ['l1', 'l2', 'l3', 'l4']})], endpoint: (device) => { - return {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4}; + return {l1: 1, l2: 2, l3: 3, l4: 4}; }, whiteLabel: [ tuya.whitelabel('Tuya', 'DS-111', 'Smart light switch - 4 gang with neutral wire', ['_TZ3000_mdj7kra9']), @@ -5138,7 +5741,7 @@ const definitions: Definition[] = [ e.action(['scene_1', 'scene_2', 'scene_3', 'scene_4']), ], endpoint: (device) => { - return {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4}; + return {l1: 1, l2: 2, l3: 3, l4: 4}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -5154,10 +5757,16 @@ const definitions: Definition[] = [ vendor: 'Tuya', description: '6 gang switch module with neutral wire', extend: [tuya.modernExtend.tuyaOnOff()], - exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), e.switch().withEndpoint('l3'), - e.switch().withEndpoint('l4'), e.switch().withEndpoint('l5'), e.switch().withEndpoint('l6')], + exposes: [ + e.switch().withEndpoint('l1'), + e.switch().withEndpoint('l2'), + e.switch().withEndpoint('l3'), + e.switch().withEndpoint('l4'), + e.switch().withEndpoint('l5'), + e.switch().withEndpoint('l6'), + ], endpoint: (device) => { - return {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4, 'l5': 5, 'l6': 6}; + return {l1: 1, l2: 2, l3: 3, l4: 4, l5: 5, l6: 6}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -5169,9 +5778,7 @@ const definitions: Definition[] = [ await reporting.bind(device.getEndpoint(5), coordinatorEndpoint, ['genOnOff']); await reporting.bind(device.getEndpoint(6), coordinatorEndpoint, ['genOnOff']); }, - whiteLabel: [ - tuya.whitelabel('AVATTO', 'TS0006_1', '4 gang switch module with neutral wire and socket', ['_TZ3000_cvis4qmw']), - ], + whiteLabel: [tuya.whitelabel('AVATTO', 'TS0006_1', '4 gang switch module with neutral wire and socket', ['_TZ3000_cvis4qmw'])], }, { zigbeeModel: ['HY0080'], @@ -5179,17 +5786,35 @@ const definitions: Definition[] = [ vendor: 'Tuya', description: 'Environment controller', fromZigbee: [legacy.fromZigbee.thermostat_att_report, fz.fan], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_local_temperature_calibration, - tz.thermostat_occupancy, tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint, - tz.thermostat_occupied_cooling_setpoint, tz.thermostat_unoccupied_cooling_setpoint, - tz.thermostat_setpoint_raise_lower, tz.thermostat_remote_sensing, - tz.thermostat_control_sequence_of_operation, tz.thermostat_system_mode, tz.thermostat_weekly_schedule, - tz.thermostat_clear_weekly_schedule, tz.thermostat_relay_status_log, - tz.thermostat_temperature_setpoint_hold, tz.thermostat_temperature_setpoint_hold_duration, tz.fan_mode], - exposes: [e.climate().withSetpoint('occupied_heating_setpoint', 5, 30, 0.5).withLocalTemperature() - .withSystemMode(['off', 'auto', 'heat'], ea.ALL) - .withRunningState(['idle', 'heat', 'cool'], ea.STATE) - .withLocalTemperatureCalibration(-30, 30, 0.1, ea.ALL).withPiHeatingDemand()], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_local_temperature_calibration, + tz.thermostat_occupancy, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_unoccupied_heating_setpoint, + tz.thermostat_occupied_cooling_setpoint, + tz.thermostat_unoccupied_cooling_setpoint, + tz.thermostat_setpoint_raise_lower, + tz.thermostat_remote_sensing, + tz.thermostat_control_sequence_of_operation, + tz.thermostat_system_mode, + tz.thermostat_weekly_schedule, + tz.thermostat_clear_weekly_schedule, + tz.thermostat_relay_status_log, + tz.thermostat_temperature_setpoint_hold, + tz.thermostat_temperature_setpoint_hold_duration, + tz.fan_mode, + ], + exposes: [ + e + .climate() + .withSetpoint('occupied_heating_setpoint', 5, 30, 0.5) + .withLocalTemperature() + .withSystemMode(['off', 'auto', 'heat'], ea.ALL) + .withRunningState(['idle', 'heat', 'cool'], ea.STATE) + .withLocalTemperatureCalibration(-30, 30, 0.1, ea.ALL) + .withPiHeatingDemand(), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(9); await reporting.bind(endpoint, coordinatorEndpoint, ['hvacThermostat', 'hvacFanCtrl']); @@ -5207,14 +5832,22 @@ const definitions: Definition[] = [ model: 'D3-DPWK-TY', vendor: 'Tuya', description: 'HVAC controller', - exposes: [e.climate().withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.STATE_SET) - .withLocalTemperature(ea.STATE) - .withSystemMode(['off', 'auto', 'heat'], ea.STATE_SET) - .withRunningState(['idle', 'heat', 'cool'], ea.STATE)], + exposes: [ + e + .climate() + .withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.STATE_SET) + .withLocalTemperature(ea.STATE) + .withSystemMode(['off', 'auto', 'heat'], ea.STATE_SET) + .withRunningState(['idle', 'heat', 'cool'], ea.STATE), + ], fromZigbee: [legacy.fromZigbee.tuya_thermostat, fz.ignore_basic_report, legacy.fromZigbee.tuya_dimmer], meta: {tuyaThermostatSystemMode: legacy.thermostatSystemModes2, tuyaThermostatPreset: legacy.thermostatPresets}, - toZigbee: [legacy.toZigbee.tuya_thermostat_current_heating_setpoint, legacy.toZigbee.tuya_thermostat_system_mode, - legacy.toZigbee.tuya_thermostat_fan_mode, legacy.toZigbee.tuya_dimmer_state], + toZigbee: [ + legacy.toZigbee.tuya_thermostat_current_heating_setpoint, + legacy.toZigbee.tuya_thermostat_system_mode, + legacy.toZigbee.tuya_thermostat_fan_mode, + legacy.toZigbee.tuya_dimmer_state, + ], }, { zigbeeModel: ['E220-KR4N0Z0-HA', 'JZ-ZB-004'], @@ -5223,8 +5856,7 @@ const definitions: Definition[] = [ description: 'Multiprise with 4 AC outlets and 2 USB super charging ports (16A)', extend: [tuya.modernExtend.tuyaOnOff()], fromZigbee: [fz.on_off_skip_duplicate_transaction], - exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), e.switch().withEndpoint('l3'), - e.switch().withEndpoint('l4')], + exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), e.switch().withEndpoint('l3'), e.switch().withEndpoint('l4')], whiteLabel: [{vendor: 'LEELKI', model: 'WP33-EU'}], meta: {multiEndpoint: true}, endpoint: (device) => { @@ -5244,8 +5876,11 @@ const definitions: Definition[] = [ vendor: 'Tuya', description: 'Sound and flash siren', fromZigbee: [fz.ts0216_siren, fz.battery], - exposes: [e.battery(), e.binary('alarm', ea.STATE_SET, true, false), - e.numeric('volume', ea.ALL).withValueMin(0).withValueMax(100).withDescription('Volume of siren')], + exposes: [ + e.battery(), + e.binary('alarm', ea.STATE_SET, true, false), + e.numeric('volume', ea.ALL).withValueMin(0).withValueMax(100).withDescription('Volume of siren'), + ], toZigbee: [tz.ts0216_alarm, tz.ts0216_duration, tz.ts0216_volume], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); @@ -5263,9 +5898,14 @@ const definitions: Definition[] = [ fromZigbee: [legacy.fromZigbee.hy_thermostat, fz.ignore_basic_report], toZigbee: [legacy.toZigbee.hy_thermostat], onEvent: tuya.onEventSetTime, - exposes: [e.climate().withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.STATE_SET) - .withLocalTemperature(ea.STATE) - .withSystemMode(['off', 'auto', 'heat'], ea.STATE_SET).withRunningState(['idle', 'heat'], ea.STATE)], + exposes: [ + e + .climate() + .withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.STATE_SET) + .withLocalTemperature(ea.STATE) + .withSystemMode(['off', 'auto', 'heat'], ea.STATE_SET) + .withRunningState(['idle', 'heat'], ea.STATE), + ], }, { fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_g9a3awaj'}], @@ -5277,16 +5917,20 @@ const definitions: Definition[] = [ onEvent: tuya.onEvent({timeStart: '1970'}), configure: tuya.configureMagicPacket, exposes: [ - e.climate().withSetpoint('current_heating_setpoint', 5, 60, 0.5, ea.STATE_SET) - .withSystemMode(['off', 'heat'], ea.STATE_SET).withRunningState(['idle', 'heat'], ea.STATE) + e + .climate() + .withSetpoint('current_heating_setpoint', 5, 60, 0.5, ea.STATE_SET) + .withSystemMode(['off', 'heat'], ea.STATE_SET) + .withRunningState(['idle', 'heat'], ea.STATE) .withPreset(['manual', 'program']) .withLocalTemperature(), - e.binary('frost', ea.STATE_SET, 'ON', 'OFF').withDescription('Antifreeze function')], + e.binary('frost', ea.STATE_SET, 'ON', 'OFF').withDescription('Antifreeze function'), + ], meta: { tuyaDatapoints: [ - [1, 'system_mode', tuya.valueConverterBasic.lookup({'heat': true, 'off': false})], - [2, 'preset', tuya.valueConverterBasic.lookup({'manual': tuya.enum(1), 'program': tuya.enum(0)})], - [36, 'running_state', tuya.valueConverterBasic.lookup({'heat': 1, 'idle': 0})], + [1, 'system_mode', tuya.valueConverterBasic.lookup({heat: true, off: false})], + [2, 'preset', tuya.valueConverterBasic.lookup({manual: tuya.enum(1), program: tuya.enum(0)})], + [36, 'running_state', tuya.valueConverterBasic.lookup({heat: 1, idle: 0})], [16, 'current_heating_setpoint', tuya.valueConverter.divideBy10], [24, 'local_temperature', tuya.valueConverter.divideBy10], [10, 'frost', tuya.valueConverter.onOff], @@ -5300,35 +5944,58 @@ const definitions: Definition[] = [ description: 'Wall-mount thermostat', fromZigbee: [fz.ignore_basic_report, fz.ignore_tuya_set_time, legacy.fromZigbee.x5h_thermostat], toZigbee: [legacy.toZigbee.x5h_thermostat], - whiteLabel: [{vendor: 'Beok', model: 'TGR85-ZB'}, {vendor: 'AVATTO', model: 'ZWT-100-16A'}], + whiteLabel: [ + {vendor: 'Beok', model: 'TGR85-ZB'}, + {vendor: 'AVATTO', model: 'ZWT-100-16A'}, + ], exposes: [ - e.climate().withSetpoint('current_heating_setpoint', 5, 60, 0.5, ea.STATE_SET) - .withLocalTemperature(ea.STATE).withLocalTemperatureCalibration(-9.9, 9.9, 0.1, ea.STATE_SET) - .withSystemMode(['off', 'heat'], ea.STATE_SET).withRunningState(['idle', 'heat'], ea.STATE) + e + .climate() + .withSetpoint('current_heating_setpoint', 5, 60, 0.5, ea.STATE_SET) + .withLocalTemperature(ea.STATE) + .withLocalTemperatureCalibration(-9.9, 9.9, 0.1, ea.STATE_SET) + .withSystemMode(['off', 'heat'], ea.STATE_SET) + .withRunningState(['idle', 'heat'], ea.STATE) .withPreset(['manual', 'program']), e.temperature_sensor_select(['internal', 'external', 'both']), - e.text('schedule', ea.STATE_SET).withDescription('There are 8 periods in the schedule in total. ' + - '6 for workdays and 2 for holidays. It should be set in the following format for each of the periods: ' + - '`hours:minutes/temperature`. All periods should be set at once and delimited by the space symbol. ' + - 'For example: `06:00/20.5 08:00/15 11:30/15 13:30/15 17:00/22 22:00/15 06:00/20 22:00/15`. ' + - 'The thermostat doesn\'t report the schedule by itself even if you change it manually from device'), - e.child_lock(), e.week(), - e.enum('brightness_state', ea.STATE_SET, ['off', 'low', 'medium', 'high']) - .withDescription('Screen brightness'), - e.binary('sound', ea.STATE_SET, 'ON', 'OFF') - .withDescription('Switches beep sound when interacting with thermostat'), - e.binary('frost_protection', ea.STATE_SET, 'ON', 'OFF') - .withDescription('Antifreeze function'), - e.binary('factory_reset', ea.STATE_SET, 'ON', 'OFF') - .withDescription('Resets all settings to default. Doesn\'t unpair device.'), - e.numeric('heating_temp_limit', ea.STATE_SET).withUnit('°C').withValueMax(60) - .withValueMin(5).withValueStep(1).withPreset('default', 35, 'Default value') + e + .text('schedule', ea.STATE_SET) + .withDescription( + 'There are 8 periods in the schedule in total. ' + + '6 for workdays and 2 for holidays. It should be set in the following format for each of the periods: ' + + '`hours:minutes/temperature`. All periods should be set at once and delimited by the space symbol. ' + + 'For example: `06:00/20.5 08:00/15 11:30/15 13:30/15 17:00/22 22:00/15 06:00/20 22:00/15`. ' + + "The thermostat doesn't report the schedule by itself even if you change it manually from device", + ), + e.child_lock(), + e.week(), + e.enum('brightness_state', ea.STATE_SET, ['off', 'low', 'medium', 'high']).withDescription('Screen brightness'), + e.binary('sound', ea.STATE_SET, 'ON', 'OFF').withDescription('Switches beep sound when interacting with thermostat'), + e.binary('frost_protection', ea.STATE_SET, 'ON', 'OFF').withDescription('Antifreeze function'), + e.binary('factory_reset', ea.STATE_SET, 'ON', 'OFF').withDescription("Resets all settings to default. Doesn't unpair device."), + e + .numeric('heating_temp_limit', ea.STATE_SET) + .withUnit('°C') + .withValueMax(60) + .withValueMin(5) + .withValueStep(1) + .withPreset('default', 35, 'Default value') .withDescription('Heating temperature limit'), - e.numeric('deadzone_temperature', ea.STATE_SET).withUnit('°C').withValueMax(9.5) - .withValueMin(0.5).withValueStep(0.5).withPreset('default', 1, 'Default value') + e + .numeric('deadzone_temperature', ea.STATE_SET) + .withUnit('°C') + .withValueMax(9.5) + .withValueMin(0.5) + .withValueStep(0.5) + .withPreset('default', 1, 'Default value') .withDescription('The delta between local_temperature and current_heating_setpoint to trigger Heat'), - e.numeric('upper_temp', ea.STATE_SET).withUnit('°C').withValueMax(95) - .withValueMin(35).withValueStep(1).withPreset('default', 60, 'Default value'), + e + .numeric('upper_temp', ea.STATE_SET) + .withUnit('°C') + .withValueMax(95) + .withValueMin(35) + .withValueStep(1) + .withPreset('default', 60, 'Default value'), ], onEvent: tuya.onEventSetTime, }, @@ -5342,37 +6009,36 @@ const definitions: Definition[] = [ toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, exposes: [ - e.binary('factory_reset', ea.STATE_SET, 'ON', 'OFF') - .withDescription('Full factory reset, use with caution!'), + e.binary('factory_reset', ea.STATE_SET, 'ON', 'OFF').withDescription('Full factory reset, use with caution!'), e.child_lock(), e.temperature_sensor_select(['internal', 'external', 'both']), - e.climate() + e + .climate() .withSystemMode(['off', 'heat'], ea.STATE_SET) .withPreset(['manual', 'auto', 'temporary_manual']) .withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET) .withRunningState(['idle', 'heat'], ea.STATE) .withLocalTemperature(ea.STATE) .withLocalTemperatureCalibration(-9.9, 9.9, 0.1, ea.STATE_SET), - e.binary('frost_protection', ea.STATE_SET, 'ON', 'OFF') - .withDescription('Antifreeze function'), - e.max_temperature_limit() + e.binary('frost_protection', ea.STATE_SET, 'ON', 'OFF').withDescription('Antifreeze function'), + e + .max_temperature_limit() .withUnit('°C') .withValueMin(15) .withValueMax(90) .withValueStep(0.5) .withPreset('default', 60, 'Default value') .withDescription('Maximum upper temperature'), - e.numeric('deadzone_temperature', ea.STATE_SET) + e + .numeric('deadzone_temperature', ea.STATE_SET) .withUnit('°C') .withValueMax(10) .withValueMin(0.5) .withValueStep(0.5) .withPreset('default', 1, 'Default value') .withDescription('The delta between local_temperature (5 { - // https://github.com/Koenkk/zigbee2mqtt/issues/21353#issuecomment-1938328429 - if (device.manufacturerName === '_TZE200_viy9ihs7') { - return {'auto': tuya.enum(1), 'manual': tuya.enum(0), 'temporary_manual': tuya.enum(2)}; - } else { - return {'manual': tuya.enum(0), 'auto': tuya.enum(1), 'temporary_manual': tuya.enum(2)}; - } - })], + [ + 4, + 'preset', + tuya.valueConverterBasic.lookup((_, device) => { + // https://github.com/Koenkk/zigbee2mqtt/issues/21353#issuecomment-1938328429 + if (device.manufacturerName === '_TZE200_viy9ihs7') { + return {auto: tuya.enum(1), manual: tuya.enum(0), temporary_manual: tuya.enum(2)}; + } else { + return {manual: tuya.enum(0), auto: tuya.enum(1), temporary_manual: tuya.enum(2)}; + } + }), + ], [9, 'child_lock', tuya.valueConverter.lockUnlock], [11, 'faultalarm', tuya.valueConverter.raw], [15, 'max_temperature_limit', tuya.valueConverter.divideBy10], [19, 'local_temperature_calibration', tuya.valueConverter.localTempCalibration3], - [101, 'running_state', tuya.valueConverterBasic.lookup({'heat': tuya.enum(1), 'idle': tuya.enum(0)})], + [101, 'running_state', tuya.valueConverterBasic.lookup({heat: tuya.enum(1), idle: tuya.enum(0)})], [102, 'frost_protection', tuya.valueConverter.onOff], [103, 'factory_reset', tuya.valueConverter.onOff], [104, 'working_day', tuya.valueConverter.workingDay], - [106, 'sensor', tuya.valueConverterBasic.lookup({'internal': tuya.enum(0), 'external': tuya.enum(1), 'both': tuya.enum(2)})], + [106, 'sensor', tuya.valueConverterBasic.lookup({internal: tuya.enum(0), external: tuya.enum(1), both: tuya.enum(2)})], [107, 'deadzone_temperature', tuya.valueConverter.divideBy10], [109, null, tuya.valueConverter.ZWT198_schedule], [109, 'schedule_weekday', tuya.valueConverter.ZWT198_schedule], @@ -5448,13 +6118,13 @@ const definitions: Definition[] = [ toZigbee: [], configure: tuya.configureMagicPacket, exposes: [e.battery(), e.temperature(), e.humidity(), e.illuminance()], - whiteLabel: [ - tuya.whitelabel('Tuya', 'QT-07S', 'Soil sensor', ['_TZ3000_kky16aay']), - ], + whiteLabel: [tuya.whitelabel('Tuya', 'QT-07S', 'Soil sensor', ['_TZ3000_kky16aay'])], }, { - fingerprint: [{modelID: 'TS0222', manufacturerName: '_TYZB01_4mdqxxnn'}, - {modelID: 'TS0222', manufacturerName: '_TYZB01_m6ec2pgj'}], + fingerprint: [ + {modelID: 'TS0222', manufacturerName: '_TYZB01_4mdqxxnn'}, + {modelID: 'TS0222', manufacturerName: '_TYZB01_m6ec2pgj'}, + ], model: 'TS0222', vendor: 'Tuya', description: 'Light intensity sensor', @@ -5468,17 +6138,22 @@ const definitions: Definition[] = [ model: 'TS0210', vendor: 'Tuya', description: 'Vibration sensor', - whiteLabel: [ - tuya.whitelabel('Niceboy', 'ORBIS Vibration Sensor', 'Vibration sensor', ['_TYZB01_821siati']), - ], + whiteLabel: [tuya.whitelabel('Niceboy', 'ORBIS Vibration Sensor', 'Vibration sensor', ['_TYZB01_821siati'])], fromZigbee: [fz.battery, fz.ias_vibration_alarm_1_with_timeout], toZigbee: [tz.TS0210_sensitivity], - exposes: [e.battery(), e.battery_voltage(), e.vibration(), - e.numeric('sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(50) + exposes: [ + e.battery(), + e.battery_voltage(), + e.vibration(), + e + .numeric('sensitivity', ea.STATE_SET) + .withValueMin(0) + .withValueMax(50) .withDescription( 'Sensitivty of the sensor (0 = highest sensitivity, 50 = lowest sensitivity). ' + - 'Press button on the device right before changing this', - )], + 'Press button on the device right before changing this', + ), + ], }, { fingerprint: tuya.fingerprint('TS0601', ['_TZE200_8ply8mjj']), @@ -5490,9 +6165,14 @@ const definitions: Definition[] = [ configure: tuya.configureMagicPacket, exposes: [ e.vibration(), - e.numeric('sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(2) - .withDescription('Sensitivity of the sensor (single press the button when muted to switch between' + - ' low (one beep), medium (two beeps) and max (three beeps))'), + e + .numeric('sensitivity', ea.STATE_SET) + .withValueMin(0) + .withValueMax(2) + .withDescription( + 'Sensitivity of the sensor (single press the button when muted to switch between' + + ' low (one beep), medium (two beeps) and max (three beeps))', + ), e.text('buzzer_mute', ea.STATE).withDescription('ON when buzzer is muted (double press the button on device to toggle)'), ], meta: { @@ -5504,13 +6184,21 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'TS011F', manufacturerName: '_TZ3000_8bxrzyxz'}, - {modelID: 'TS011F', manufacturerName: '_TZ3000_ky0fq4ho'}], + fingerprint: [ + {modelID: 'TS011F', manufacturerName: '_TZ3000_8bxrzyxz'}, + {modelID: 'TS011F', manufacturerName: '_TZ3000_ky0fq4ho'}, + ], model: 'TS011F_din_smart_relay', description: 'Din smart relay (with power monitoring)', vendor: 'Tuya', - fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, fz.ignore_basic_report, tuya.fz.power_outage_memory, - fz.tuya_relay_din_led_indicator], + fromZigbee: [ + fz.on_off, + fz.electrical_measurement, + fz.metering, + fz.ignore_basic_report, + tuya.fz.power_outage_memory, + fz.tuya_relay_din_led_indicator, + ], toZigbee: [tz.on_off, tuya.tz.power_on_behavior_1, tz.tuya_relay_din_led_indicator], whiteLabel: [{vendor: 'MatSee Plus', model: 'ATMS1602Z'}], ota: ota.zigbeeOTA, @@ -5525,19 +6213,29 @@ const definitions: Definition[] = [ endpoint.saveClusterAttributeKeyValue('seMetering', {divisor: 100, multiplier: 1}); device.save(); }, - exposes: [e.switch(), e.power(), e.current(), e.voltage(), - e.energy(), e.enum('power_outage_memory', ea.ALL, ['on', 'off', 'restore']) - .withDescription('Recover state after power outage'), - e.enum('indicator_mode', ea.STATE_SET, ['off', 'on_off', 'off_on']) - .withDescription('Relay LED indicator mode')], + exposes: [ + e.switch(), + e.power(), + e.current(), + e.voltage(), + e.energy(), + e.enum('power_outage_memory', ea.ALL, ['on', 'off', 'restore']).withDescription('Recover state after power outage'), + e.enum('indicator_mode', ea.STATE_SET, ['off', 'on_off', 'off_on']).withDescription('Relay LED indicator mode'), + ], }, { fingerprint: [{modelID: 'TS011F', manufacturerName: '_TZ3000_qeuvnohg'}], model: 'TS011F_din_smart_relay_polling', description: 'Din smart relay (with power monitoring via polling)', vendor: 'Tuya', - fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, fz.ignore_basic_report, tuya.fz.power_outage_memory, - fz.tuya_relay_din_led_indicator], + fromZigbee: [ + fz.on_off, + fz.electrical_measurement, + fz.metering, + fz.ignore_basic_report, + tuya.fz.power_outage_memory, + fz.tuya_relay_din_led_indicator, + ], toZigbee: [tz.on_off, tuya.tz.power_on_behavior_1, tz.tuya_relay_din_led_indicator], whiteLabel: [tuya.whitelabel('Tongou', 'TO-Q-SY1-JZT', 'Din smart relay (with power monitoring via polling)', ['_TZ3000_qeuvnohg'])], ota: ota.zigbeeOTA, @@ -5552,11 +6250,15 @@ const definitions: Definition[] = [ endpoint.saveClusterAttributeKeyValue('seMetering', {divisor: 100, multiplier: 1}); device.save(); }, - exposes: [e.switch(), e.power(), e.current(), e.voltage(), - e.energy(), e.enum('power_outage_memory', ea.ALL, ['on', 'off', 'restore']) - .withDescription('Recover state after power outage'), - e.enum('indicator_mode', ea.STATE_SET, ['off', 'on_off', 'off_on']) - .withDescription('Relay LED indicator mode')], + exposes: [ + e.switch(), + e.power(), + e.current(), + e.voltage(), + e.energy(), + e.enum('power_outage_memory', ea.ALL, ['on', 'off', 'restore']).withDescription('Recover state after power outage'), + e.enum('indicator_mode', ea.STATE_SET, ['off', 'on_off', 'off_on']).withDescription('Relay LED indicator mode'), + ], options: [exposes.options.measurement_poll_interval()], onEvent: (type, data, device, options) => tuya.onEventMeasurementPoll(type, data, device, options, true, false), }, @@ -5572,11 +6274,11 @@ const definitions: Definition[] = [ await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']); device.save(); }, - exposes: [e.switch(), - e.enum('power_outage_memory', ea.ALL, ['on', 'off', 'restore']) - .withDescription('Recover state after power outage'), - e.enum('indicator_mode', ea.STATE_SET, ['off', 'on_off', 'off_on']) - .withDescription('Relay LED indicator mode')], + exposes: [ + e.switch(), + e.enum('power_outage_memory', ea.ALL, ['on', 'off', 'restore']).withDescription('Recover state after power outage'), + e.enum('indicator_mode', ea.STATE_SET, ['off', 'on_off', 'off_on']).withDescription('Relay LED indicator mode'), + ], }, { fingerprint: tuya.fingerprint('TS0601', ['_TZE204_nklqjk62', '_TZE200_nklqjk62']), @@ -5591,8 +6293,10 @@ const definitions: Definition[] = [ const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genBasic']); }, - exposes: [e.binary('trigger', ea.STATE_SET, true, false).withDescription('Trigger the door movement'), - e.binary('garage_door_contact', ea.STATE, true, false)], + exposes: [ + e.binary('trigger', ea.STATE_SET, true, false).withDescription('Trigger the door movement'), + e.binary('garage_door_contact', ea.STATE, true, false), + ], }, { fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_wfxuhoea'}], @@ -5607,9 +6311,12 @@ const definitions: Definition[] = [ const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genBasic']); }, - exposes: [e.binary('trigger', ea.STATE_SET, true, false).withDescription('Trigger the door movement'), - e.binary('garage_door_contact', ea.STATE, false, true) - .withDescription('Indicates if the garage door contact is closed (= true) or open (= false)')], + exposes: [ + e.binary('trigger', ea.STATE_SET, true, false).withDescription('Trigger the door movement'), + e + .binary('garage_door_contact', ea.STATE, false, true) + .withDescription('Indicates if the garage door contact is closed (= true) or open (= false)'), + ], }, { fingerprint: [{modelID: 'TS0603', manufacturerName: '_TZE608_c75zqghm'}], @@ -5632,8 +6339,10 @@ const definitions: Definition[] = [ toZigbee: [tuya.tz.datapoints], exposes: [ e.binary('state', ea.STATE_SET, true, false).withDescription('Trigger the door movement'), - e.binary('garage_door_contact', ea.STATE, true, false) - .withDescription('Indicates if the garage door contact is closed (= true) or open (= false)')], + e + .binary('garage_door_contact', ea.STATE, true, false) + .withDescription('Indicates if the garage door contact is closed (= true) or open (= false)'), + ], }, { fingerprint: [{modelID: 'TS0201', manufacturerName: '_TZ3000_qaaysllp'}], @@ -5646,22 +6355,37 @@ const definitions: Definition[] = [ const endpoint = device.getEndpoint(1); // Enables reporting of measurement state changes await tuya.configureMagicPacket(device, coordinatorEndpoint); - await reporting.bind(endpoint, coordinatorEndpoint, ['genBasic', 'genPowerCfg', - 'msTemperatureMeasurement', 'msIlluminanceMeasurement', 'msRelativeHumidity', 'manuSpecificTuya_2']); + await reporting.bind(endpoint, coordinatorEndpoint, [ + 'genBasic', + 'genPowerCfg', + 'msTemperatureMeasurement', + 'msIlluminanceMeasurement', + 'msRelativeHumidity', + 'manuSpecificTuya_2', + ]); }, - exposes: [e.temperature(), e.humidity(), e.battery(), e.illuminance(), e.illuminance_lux(), - e.numeric('alarm_temperature_max', ea.STATE_SET).withUnit('°C').withDescription('Alarm temperature max') - .withValueMin(-20).withValueMax(80), - e.numeric('alarm_temperature_min', ea.STATE_SET).withUnit('°C').withDescription('Alarm temperature min') - .withValueMin(-20).withValueMax(80), - e.numeric('alarm_humidity_max', ea.STATE_SET).withUnit('%').withDescription('Alarm humidity max') - .withValueMin(0).withValueMax(100), - e.numeric('alarm_humidity_min', ea.STATE_SET).withUnit('%').withDescription('Alarm humidity min') - .withValueMin(0).withValueMax(100), - e.enum('alarm_humidity', ea.STATE, ['below_min_humdity', 'over_humidity', 'off']) - .withDescription('Alarm humidity status'), - e.enum('alarm_temperature', ea.STATE, ['below_min_temperature', 'over_temperature', 'off']) - .withDescription('Alarm temperature status'), + exposes: [ + e.temperature(), + e.humidity(), + e.battery(), + e.illuminance(), + e.illuminance_lux(), + e + .numeric('alarm_temperature_max', ea.STATE_SET) + .withUnit('°C') + .withDescription('Alarm temperature max') + .withValueMin(-20) + .withValueMax(80), + e + .numeric('alarm_temperature_min', ea.STATE_SET) + .withUnit('°C') + .withDescription('Alarm temperature min') + .withValueMin(-20) + .withValueMax(80), + e.numeric('alarm_humidity_max', ea.STATE_SET).withUnit('%').withDescription('Alarm humidity max').withValueMin(0).withValueMax(100), + e.numeric('alarm_humidity_min', ea.STATE_SET).withUnit('%').withDescription('Alarm humidity min').withValueMin(0).withValueMax(100), + e.enum('alarm_humidity', ea.STATE, ['below_min_humdity', 'over_humidity', 'off']).withDescription('Alarm humidity status'), + e.enum('alarm_temperature', ea.STATE, ['below_min_temperature', 'over_temperature', 'off']).withDescription('Alarm temperature status'), ], }, { @@ -5676,12 +6400,9 @@ const definitions: Definition[] = [ e.enum('o_sensitivity', ea.STATE_SET, Object.values(legacy.msLookups.OSensitivity)).withDescription('O-Sensitivity mode'), e.enum('v_sensitivity', ea.STATE_SET, Object.values(legacy.msLookups.VSensitivity)).withDescription('V-Sensitivity mode'), e.enum('led_status', ea.STATE_SET, ['ON', 'OFF']).withDescription('Led status switch'), - e.numeric('vacancy_delay', ea.STATE_SET).withUnit('sec').withDescription('Vacancy delay').withValueMin(0) - .withValueMax(1000), - e.numeric('light_on_luminance_prefer', ea.STATE_SET).withDescription('Light-On luminance prefer') - .withValueMin(0).withValueMax(10000), - e.numeric('light_off_luminance_prefer', ea.STATE_SET).withDescription('Light-Off luminance prefer') - .withValueMin(0).withValueMax(10000), + e.numeric('vacancy_delay', ea.STATE_SET).withUnit('sec').withDescription('Vacancy delay').withValueMin(0).withValueMax(1000), + e.numeric('light_on_luminance_prefer', ea.STATE_SET).withDescription('Light-On luminance prefer').withValueMin(0).withValueMax(10000), + e.numeric('light_off_luminance_prefer', ea.STATE_SET).withDescription('Light-Off luminance prefer').withValueMin(0).withValueMax(10000), e.enum('mode', ea.STATE_SET, Object.values(legacy.msLookups.Mode)).withDescription('Working mode'), e.numeric('luminance_level', ea.STATE).withDescription('Luminance level'), e.numeric('reference_luminance', ea.STATE).withDescription('Reference luminance'), @@ -5701,21 +6422,37 @@ const definitions: Definition[] = [ await tuya.configureMagicPacket(device, coordinatorEndpoint); }, exposes: [ - e.illuminance_lux(), e.presence(), e.occupancy(), + e.illuminance_lux(), + e.presence(), + e.occupancy(), e.numeric('motion_speed', ea.STATE).withDescription('Speed of movement'), - e.enum('motion_direction', ea.STATE, ['standing_still', 'moving_forward', 'moving_backward']) + e + .enum('motion_direction', ea.STATE, ['standing_still', 'moving_forward', 'moving_backward']) .withDescription('direction of movement from the point of view of the radar'), - e.numeric('radar_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(1) + e + .numeric('radar_sensitivity', ea.STATE_SET) + .withValueMin(0) + .withValueMax(10) + .withValueStep(1) .withDescription('Sensitivity of the radar'), - e.enum('radar_scene', ea.STATE_SET, ['default', 'area', 'toilet', 'bedroom', 'parlour', 'office', 'hotel']) + e + .enum('radar_scene', ea.STATE_SET, ['default', 'area', 'toilet', 'bedroom', 'parlour', 'office', 'hotel']) .withDescription('Presets for sensitivity for presence and movement'), e.enum('tumble_switch', ea.STATE_SET, ['ON', 'OFF']).withDescription('Tumble status switch'), - e.numeric('fall_sensitivity', ea.STATE_SET).withValueMin(1).withValueMax(10).withValueStep(1) + e + .numeric('fall_sensitivity', ea.STATE_SET) + .withValueMin(1) + .withValueMax(10) + .withValueStep(1) .withDescription('Fall sensitivity of the radar'), - e.numeric('tumble_alarm_time', ea.STATE_SET).withValueMin(1).withValueMax(5).withValueStep(1) - .withUnit('min').withDescription('Tumble alarm time'), - e.enum('fall_down_status', ea.STATE, ['none', 'maybe_fall', 'fall']) - .withDescription('Fall down status'), + e + .numeric('tumble_alarm_time', ea.STATE_SET) + .withValueMin(1) + .withValueMax(5) + .withValueStep(1) + .withUnit('min') + .withDescription('Tumble alarm time'), + e.enum('fall_down_status', ea.STATE, ['none', 'maybe_fall', 'fall']).withDescription('Fall down status'), e.text('static_dwell_alarm', ea.STATE).withDescription('Static dwell alarm'), ], meta: { @@ -5726,12 +6463,10 @@ const definitions: Definition[] = [ [103, 'illuminance_lux', tuya.valueConverter.raw], [105, 'tumble_switch', tuya.valueConverter.plus1], [106, 'tumble_alarm_time', tuya.valueConverter.raw], - [112, 'radar_scene', tuya.valueConverterBasic.lookup( - {'default': 0, 'area': 1, 'toilet': 2, 'bedroom': 3, 'parlour': 4, 'office': 5, 'hotel': 6})], - [114, 'motion_direction', tuya.valueConverterBasic.lookup( - {'standing_still': 0, 'moving_forward': 1, 'moving_backward': 2})], + [112, 'radar_scene', tuya.valueConverterBasic.lookup({default: 0, area: 1, toilet: 2, bedroom: 3, parlour: 4, office: 5, hotel: 6})], + [114, 'motion_direction', tuya.valueConverterBasic.lookup({standing_still: 0, moving_forward: 1, moving_backward: 2})], [115, 'motion_speed', tuya.valueConverter.raw], - [116, 'fall_down_status', tuya.valueConverterBasic.lookup({'none': 0, 'maybe_fall': 1, 'fall': 2})], + [116, 'fall_down_status', tuya.valueConverterBasic.lookup({none: 0, maybe_fall: 1, fall: 2})], [117, 'static_dwell_alarm', tuya.valueConverter.raw], [118, 'fall_sensitivity', tuya.valueConverter.raw], // Below are ignored @@ -5752,9 +6487,29 @@ const definitions: Definition[] = [ description: 'Wireless switch with 6 buttons', whiteLabel: [{vendor: 'LoraTap', model: 'SS9600ZB'}], fromZigbee: [tuya.fz.on_off_action, fz.battery], - exposes: [e.battery(), e.action(['1_single', '1_double', '1_hold', '2_single', '2_double', '2_hold', - '3_single', '3_double', '3_hold', '4_single', '4_double', '4_hold', - '5_single', '5_double', '5_hold', '6_single', '6_double', '6_hold'])], + exposes: [ + e.battery(), + e.action([ + '1_single', + '1_double', + '1_hold', + '2_single', + '2_double', + '2_hold', + '3_single', + '3_double', + '3_hold', + '4_single', + '4_double', + '4_hold', + '5_single', + '5_double', + '5_hold', + '6_single', + '6_double', + '6_hold', + ]), + ], toZigbee: [], configure: tuya.configureMagicPacket, }, @@ -5765,27 +6520,56 @@ const definitions: Definition[] = [ description: '2 in 1 dimming remote control and scene control', exposes: [ e.battery(), - e.action(['on', 'off', - 'brightness_move_up', 'brightness_step_up', 'brightness_step_down', 'brightness_move_down', 'brightness_stop', - 'color_temperature_step_down', 'color_temperature_step_up', - '1_single', '1_double', '1_hold', '2_single', '2_double', '2_hold', - '3_single', '3_double', '3_hold', '4_single', '4_double', '4_hold', + e.action([ + 'on', + 'off', + 'brightness_move_up', + 'brightness_step_up', + 'brightness_step_down', + 'brightness_move_down', + 'brightness_stop', + 'color_temperature_step_down', + 'color_temperature_step_up', + '1_single', + '1_double', + '1_hold', + '2_single', + '2_double', + '2_hold', + '3_single', + '3_double', + '3_hold', + '4_single', + '4_double', + '4_hold', ]), - e.enum('operation_mode', ea.ALL, ['command', 'event']).withDescription( - 'Operation mode: "command" - for group control, "event" - for clicks'), + e + .enum('operation_mode', ea.ALL, ['command', 'event']) + .withDescription('Operation mode: "command" - for group control, "event" - for clicks'), + ], + fromZigbee: [ + fz.battery, + fz.command_on, + fz.command_off, + fz.command_step, + fz.command_move, + fz.command_stop, + fz.command_step_color_temperature, + tuya.fz.on_off_action, + fz.tuya_operation_mode, ], - fromZigbee: [fz.battery, fz.command_on, fz.command_off, fz.command_step, fz.command_move, fz.command_stop, - fz.command_step_color_temperature, tuya.fz.on_off_action, fz.tuya_operation_mode], toZigbee: [tz.tuya_operation_mode], onEvent: tuya.onEventSetLocalTime, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await endpoint.read('genBasic', [0x0004, 0x000, 0x0001, 0x0005, 0x0007, 0xfffe]); - await endpoint.write('genOnOff', {'tuyaOperationMode': 1}); + await endpoint.write('genOnOff', {tuyaOperationMode: 1}); await endpoint.read('genOnOff', ['tuyaOperationMode']); try { - await endpoint.read(0xE001, [0xD011]); - } catch (err) {/* do nothing */} + await endpoint.read(0xe001, [0xd011]); + } catch (err) { + /* do nothing */ + } await endpoint.read('genPowerCfg', ['batteryVoltage', 'batteryPercentageRemaining']); await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']); @@ -5814,7 +6598,10 @@ const definitions: Definition[] = [ e.numeric('voltage_rms', ea.STATE).withUnit('V').withDescription('Voltage RMS'), e.numeric('current', ea.STATE).withUnit('A').withDescription('Current'), e.numeric('current_average', ea.STATE).withUnit('A').withDescription('Current average'), - e.power(), e.voltage(), e.energy(), e.temperature(), + e.power(), + e.voltage(), + e.energy(), + e.temperature(), e.numeric('power_l1', ea.STATE).withUnit('W').withDescription('Instantaneous measured power on phase 1'), e.numeric('power_l2', ea.STATE).withUnit('W').withDescription('Instantaneous measured power on phase 2'), e.numeric('power_l3', ea.STATE).withUnit('W').withDescription('Instantaneous measured power on phase 3'), @@ -5823,37 +6610,61 @@ const definitions: Definition[] = [ ], }, { - fingerprint: [{modelID: 'TS004F', manufacturerName: '_TZ3000_4fjiwweb'}, {modelID: 'TS004F', manufacturerName: '_TZ3000_uri7ongn'}, - {modelID: 'TS004F', manufacturerName: '_TZ3000_ixla93vd'}, {modelID: 'TS004F', manufacturerName: '_TZ3000_qja6nq5z'}, - {modelID: 'TS004F', manufacturerName: '_TZ3000_abrsvsou'}], + fingerprint: [ + {modelID: 'TS004F', manufacturerName: '_TZ3000_4fjiwweb'}, + {modelID: 'TS004F', manufacturerName: '_TZ3000_uri7ongn'}, + {modelID: 'TS004F', manufacturerName: '_TZ3000_ixla93vd'}, + {modelID: 'TS004F', manufacturerName: '_TZ3000_qja6nq5z'}, + {modelID: 'TS004F', manufacturerName: '_TZ3000_abrsvsou'}, + ], model: 'ERS-10TZBVK-AA', vendor: 'Tuya', description: 'Smart knob', fromZigbee: [ - fz.command_step, fz.command_toggle, fz.command_move_hue, fz.command_step_color_temperature, fz.command_stop_move_raw, - fz.tuya_multi_action, fz.tuya_operation_mode, fz.battery, + fz.command_step, + fz.command_toggle, + fz.command_move_hue, + fz.command_step_color_temperature, + fz.command_stop_move_raw, + fz.tuya_multi_action, + fz.tuya_operation_mode, + fz.battery, ], toZigbee: [tz.tuya_operation_mode], exposes: [ e.action([ - 'toggle', 'brightness_step_up', 'brightness_step_down', 'color_temperature_step_up', 'color_temperature_step_down', - 'saturation_move', 'hue_move', 'hue_stop', 'single', 'double', 'hold', 'rotate_left', 'rotate_right', + 'toggle', + 'brightness_step_up', + 'brightness_step_down', + 'color_temperature_step_up', + 'color_temperature_step_down', + 'saturation_move', + 'hue_move', + 'hue_stop', + 'single', + 'double', + 'hold', + 'rotate_left', + 'rotate_right', ]), e.numeric('action_step_size', ea.STATE).withValueMin(0).withValueMax(255), e.numeric('action_transition_time', ea.STATE).withUnit('s'), e.numeric('action_rate', ea.STATE).withValueMin(0).withValueMax(255), e.battery(), - e.enum('operation_mode', ea.ALL, ['command', 'event']).withDescription( - 'Operation mode: "command" - for group control, "event" - for clicks'), + e + .enum('operation_mode', ea.ALL, ['command', 'event']) + .withDescription('Operation mode: "command" - for group control, "event" - for clicks'), ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await endpoint.read('genBasic', [0x0004, 0x000, 0x0001, 0x0005, 0x0007, 0xfffe]); - await endpoint.write('genOnOff', {'tuyaOperationMode': 1}); + await endpoint.write('genOnOff', {tuyaOperationMode: 1}); await endpoint.read('genOnOff', ['tuyaOperationMode']); try { - await endpoint.read(0xE001, [0xD011]); - } catch (err) {/* do nothing */} + await endpoint.read(0xe001, [0xd011]); + } catch (err) { + /* do nothing */ + } await endpoint.read('genPowerCfg', ['batteryVoltage', 'batteryPercentageRemaining']); await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']); @@ -5870,7 +6681,10 @@ const definitions: Definition[] = [ exposes: [e.contact(), e.battery(), e.vibration()], }, { - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_yi4jtqq1'}, {modelID: 'TS0601', manufacturerName: '_TZE200_khx7nnka'}], + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE200_yi4jtqq1'}, + {modelID: 'TS0601', manufacturerName: '_TZE200_khx7nnka'}, + ], model: 'XFY-CGQ-ZIGB', vendor: 'Tuya', description: 'Illuminance sensor', @@ -5879,7 +6693,10 @@ const definitions: Definition[] = [ exposes: [e.illuminance_lux(), e.brightness_state()], }, { - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_kltffuzl'}, {modelID: 'TS0601', manufacturerName: '_TZE200_fwoorn8y'}], + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE200_kltffuzl'}, + {modelID: 'TS0601', manufacturerName: '_TZE200_fwoorn8y'}, + ], model: 'TM001-ZA/TM081', vendor: 'Tuya', description: 'Door and window sensor', @@ -5892,9 +6709,29 @@ const definitions: Definition[] = [ model: 'SS9600ZB', vendor: 'Tuya', description: '6 gang remote', - exposes: [e.battery(), - e.action(['1_single', '1_double', '1_hold', '2_single', '2_double', '2_hold', '3_single', '3_double', '3_hold', - '4_single', '4_double', '4_hold', '5_single', '5_double', '5_hold', '6_single', '6_double', '6_hold'])], + exposes: [ + e.battery(), + e.action([ + '1_single', + '1_double', + '1_hold', + '2_single', + '2_double', + '2_hold', + '3_single', + '3_double', + '3_hold', + '4_single', + '4_double', + '4_hold', + '5_single', + '5_double', + '5_hold', + '6_single', + '6_double', + '6_hold', + ]), + ], fromZigbee: [legacy.fromZigbee.tuya_remote], toZigbee: [], }, @@ -5911,7 +6748,7 @@ const definitions: Definition[] = [ vendor: 'Tuya', description: 'Zigbee dimmer module 2 channel', extend: [ - deviceEndpoints({endpoints: {'l1': 1, 'l2': 2}}), + deviceEndpoints({endpoints: {l1: 1, l2: 2}}), tuyaLight({powerOnBehavior: true, configureReporting: true, switchType: true, minBrightness: 'attribute', endpointNames: ['l1', 'l2']}), ], configure: async (device, coordinatorEndpoint) => { @@ -5919,7 +6756,8 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_ikvncluo'}, + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE200_ikvncluo'}, {modelID: 'TS0601', manufacturerName: '_TZE200_lyetpprm'}, {modelID: 'TS0601', manufacturerName: '_TZE200_jva8ink8'}, {modelID: 'TS0601', manufacturerName: '_TZE204_xpq2rzhq'}, @@ -5929,30 +6767,44 @@ const definitions: Definition[] = [ {modelID: 'TS0601', manufacturerName: '_TZE204_xsm7l9xa'}, {modelID: 'TS0601', manufacturerName: '_TZE204_ztc6ggyl'}, {modelID: 'TS0601', manufacturerName: '_TZE200_ztc6ggyl'}, - {modelID: 'TS0601', manufacturerName: '_TZE200_sgpeacqp'}], + {modelID: 'TS0601', manufacturerName: '_TZE200_sgpeacqp'}, + ], model: 'TS0601_smart_human_presence_sensor_1', vendor: 'Tuya', description: 'Smart Human presence sensor', fromZigbee: [legacy.fz.tuya_smart_human_presense_sensor], toZigbee: [legacy.tz.tuya_smart_human_presense_sensor], - whiteLabel: [ - tuya.whitelabel('Tuya', 'ZY-M100-L', 'Ceiling human breathe sensor', ['_TZE204_ztc6ggyl']), - ], + whiteLabel: [tuya.whitelabel('Tuya', 'ZY-M100-L', 'Ceiling human breathe sensor', ['_TZE204_ztc6ggyl'])], exposes: [ - e.illuminance_lux(), e.presence(), + e.illuminance_lux(), + e.presence(), e.numeric('target_distance', ea.STATE).withDescription('Distance to target').withUnit('m'), - e.numeric('radar_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(9).withValueStep(1) - .withDescription('sensitivity of the radar'), - e.numeric('minimum_range', ea.STATE_SET).withValueMin(0).withValueMax(9.5).withValueStep(0.15) - .withDescription('Minimum range').withUnit('m'), - e.numeric('maximum_range', ea.STATE_SET).withValueMin(0).withValueMax(9.5).withValueStep(0.15) - .withDescription('Maximum range').withUnit('m'), - e.numeric('detection_delay', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(0.1) - .withDescription('Detection delay').withUnit('s'), - e.numeric('fading_time', ea.STATE_SET).withValueMin(0).withValueMax(1500).withValueStep(1) - .withDescription('Fading time').withUnit('s'), + e.numeric('radar_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(9).withValueStep(1).withDescription('sensitivity of the radar'), + e + .numeric('minimum_range', ea.STATE_SET) + .withValueMin(0) + .withValueMax(9.5) + .withValueStep(0.15) + .withDescription('Minimum range') + .withUnit('m'), + e + .numeric('maximum_range', ea.STATE_SET) + .withValueMin(0) + .withValueMax(9.5) + .withValueStep(0.15) + .withDescription('Maximum range') + .withUnit('m'), + e + .numeric('detection_delay', ea.STATE_SET) + .withValueMin(0) + .withValueMax(10) + .withValueStep(0.1) + .withDescription('Detection delay') + .withUnit('s'), + e.numeric('fading_time', ea.STATE_SET).withValueMin(0).withValueMax(1500).withValueStep(1).withDescription('Fading time').withUnit('s'), // e.text('cli', ea.STATE).withDescription('not recognize'), - e.enum('self_test', ea.STATE, Object.values(legacy.tuyaHPSCheckingResult)) + e + .enum('self_test', ea.STATE, Object.values(legacy.tuyaHPSCheckingResult)) .withDescription('Self_test, possible results: checking, check_success, check_failure, others, comm_fault, radar_fault.'), ], }, @@ -5963,22 +6815,34 @@ const definitions: Definition[] = [ description: 'Mini human breathe sensor', fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], - whiteLabel: [ - tuya.whitelabel('Wenzhi', 'WZ-M100-W', 'Human presence sensor', ['_TZE204_e5m9c5hl']), - ], + whiteLabel: [tuya.whitelabel('Wenzhi', 'WZ-M100-W', 'Human presence sensor', ['_TZE204_e5m9c5hl'])], exposes: [ - e.illuminance_lux(), e.presence(), + e.illuminance_lux(), + e.presence(), e.numeric('target_distance', ea.STATE).withDescription('Distance to target').withUnit('m'), - e.numeric('radar_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(9).withValueStep(1) - .withDescription('sensitivity of the radar'), - e.numeric('minimum_range', ea.STATE_SET).withValueMin(0).withValueMax(9.5).withValueStep(0.15) - .withDescription('Minimum range').withUnit('m'), - e.numeric('maximum_range', ea.STATE_SET).withValueMin(0).withValueMax(9.5).withValueStep(0.15) - .withDescription('Maximum range').withUnit('m'), - e.numeric('detection_delay', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(0.1) - .withDescription('Detection delay').withUnit('s'), - e.numeric('fading_time', ea.STATE_SET).withValueMin(0.5).withValueMax(1500).withValueStep(1) - .withDescription('Fading time').withUnit('s'), + e.numeric('radar_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(9).withValueStep(1).withDescription('sensitivity of the radar'), + e + .numeric('minimum_range', ea.STATE_SET) + .withValueMin(0) + .withValueMax(9.5) + .withValueStep(0.15) + .withDescription('Minimum range') + .withUnit('m'), + e + .numeric('maximum_range', ea.STATE_SET) + .withValueMin(0) + .withValueMax(9.5) + .withValueStep(0.15) + .withDescription('Maximum range') + .withUnit('m'), + e + .numeric('detection_delay', ea.STATE_SET) + .withValueMin(0) + .withValueMax(10) + .withValueStep(0.1) + .withDescription('Detection delay') + .withUnit('s'), + e.numeric('fading_time', ea.STATE_SET).withValueMin(0.5).withValueMax(1500).withValueStep(1).withDescription('Fading time').withUnit('s'), ], meta: { tuyaDatapoints: [ @@ -6001,18 +6865,32 @@ const definitions: Definition[] = [ fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], exposes: [ - e.illuminance_lux(), e.presence(), + e.illuminance_lux(), + e.presence(), e.numeric('target_distance', ea.STATE).withDescription('Distance to target').withUnit('m'), - e.numeric('radar_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(9).withValueStep(1) - .withDescription('sensitivity of the radar'), - e.numeric('minimum_range', ea.STATE_SET).withValueMin(0).withValueMax(9.5).withValueStep(0.15) - .withDescription('Minimum range').withUnit('m'), - e.numeric('maximum_range', ea.STATE_SET).withValueMin(0).withValueMax(9.5).withValueStep(0.15) - .withDescription('Maximum range').withUnit('m'), - e.numeric('detection_delay', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(0.1) - .withDescription('Detection delay').withUnit('s'), - e.numeric('fading_time', ea.STATE_SET).withValueMin(0.5).withValueMax(1500).withValueStep(1) - .withDescription('Fading time').withUnit('s'), + e.numeric('radar_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(9).withValueStep(1).withDescription('sensitivity of the radar'), + e + .numeric('minimum_range', ea.STATE_SET) + .withValueMin(0) + .withValueMax(9.5) + .withValueStep(0.15) + .withDescription('Minimum range') + .withUnit('m'), + e + .numeric('maximum_range', ea.STATE_SET) + .withValueMin(0) + .withValueMax(9.5) + .withValueStep(0.15) + .withDescription('Maximum range') + .withUnit('m'), + e + .numeric('detection_delay', ea.STATE_SET) + .withValueMin(0) + .withValueMax(10) + .withValueStep(0.1) + .withDescription('Detection delay') + .withUnit('s'), + e.numeric('fading_time', ea.STATE_SET).withValueMin(0.5).withValueMax(1500).withValueStep(1).withDescription('Fading time').withUnit('s'), ], meta: { tuyaDatapoints: [ @@ -6026,9 +6904,7 @@ const definitions: Definition[] = [ [101, 'detection_delay', tuya.valueConverter.divideBy10], ], }, - whiteLabel: [ - tuya.whitelabel('iHenso', '_TZE204_ztqnh5cg', 'Human presence sensor', ['_TZE204_ztqnh5cg']), - ], + whiteLabel: [tuya.whitelabel('iHenso', '_TZE204_ztqnh5cg', 'Human presence sensor', ['_TZE204_ztqnh5cg'])], }, { fingerprint: tuya.fingerprint('TS0225', ['_TZE200_hl0ss9oa']), @@ -6041,19 +6917,54 @@ const definitions: Definition[] = [ e.presence(), e.enum('motion_state', ea.STATE, ['none', 'large', 'small', 'static']).withDescription('Motion state'), e.illuminance_lux(), - e.numeric('fading_time', ea.STATE_SET).withValueMin(0).withValueMax(3600).withValueStep(1).withUnit('s') + e + .numeric('fading_time', ea.STATE_SET) + .withValueMin(0) + .withValueMax(3600) + .withValueStep(1) + .withUnit('s') .withDescription('Presence keep time'), - e.numeric('large_motion_detection_distance', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(0.01).withUnit('m') + e + .numeric('large_motion_detection_distance', ea.STATE_SET) + .withValueMin(0) + .withValueMax(10) + .withValueStep(0.01) + .withUnit('m') .withDescription('Large motion detection distance'), - e.numeric('large_motion_detection_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(1).withUnit('x') + e + .numeric('large_motion_detection_sensitivity', ea.STATE_SET) + .withValueMin(0) + .withValueMax(10) + .withValueStep(1) + .withUnit('x') .withDescription('Large motion detection sensitivity'), - e.numeric('small_motion_detection_distance', ea.STATE_SET).withValueMin(0).withValueMax(6).withValueStep(0.01).withUnit('m') + e + .numeric('small_motion_detection_distance', ea.STATE_SET) + .withValueMin(0) + .withValueMax(6) + .withValueStep(0.01) + .withUnit('m') .withDescription('Small motion detection distance'), - e.numeric('small_motion_detection_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(1).withUnit('x') + e + .numeric('small_motion_detection_sensitivity', ea.STATE_SET) + .withValueMin(0) + .withValueMax(10) + .withValueStep(1) + .withUnit('x') .withDescription('Small motion detection sensitivity'), - e.numeric('static_detection_distance', ea.STATE_SET).withValueMin(0).withValueMax(6).withValueStep(0.01).withUnit('m') + e + .numeric('static_detection_distance', ea.STATE_SET) + .withValueMin(0) + .withValueMax(6) + .withValueStep(0.01) + .withUnit('m') .withDescription('Static detection distance'), - e.numeric('static_detection_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(1).withUnit('x') + e + .numeric('static_detection_sensitivity', ea.STATE_SET) + .withValueMin(0) + .withValueMax(10) + .withValueStep(1) + .withUnit('x') .withDescription('Static detection sensitivity'), e.enum('mode', ea.STATE_SET, ['off', 'arm', 'alarm', 'doorbell']).withDescription('Working mode'), e.enum('alarm_volume', ea.STATE_SET, ['mute', 'low', 'medium', 'high']).withDescription('Alarm volume'), @@ -6064,9 +6975,16 @@ const definitions: Definition[] = [ tuyaDatapoints: [ [1, 'presence', tuya.valueConverter.trueFalse1], [20, 'illuminance_lux', tuya.valueConverter.raw], - [11, 'motion_state', tuya.valueConverterBasic.lookup({ - 'none': tuya.enum(0), 'large': tuya.enum(1), 'small': tuya.enum(2), 'static': tuya.enum(3), - })], + [ + 11, + 'motion_state', + tuya.valueConverterBasic.lookup({ + none: tuya.enum(0), + large: tuya.enum(1), + small: tuya.enum(2), + static: tuya.enum(3), + }), + ], [12, 'fading_time', tuya.valueConverter.raw], [13, 'large_motion_detection_distance', tuya.valueConverter.divideBy100], [15, 'large_motion_detection_sensitivity', tuya.valueConverter.raw], @@ -6074,11 +6992,17 @@ const definitions: Definition[] = [ [16, 'small_motion_detection_sensitivity', tuya.valueConverter.raw], [103, 'static_detection_distance', tuya.valueConverter.divideBy100], [104, 'static_detection_sensitivity', tuya.valueConverter.raw], - [105, 'mode', tuya.valueConverterBasic.lookup( - {'arm': tuya.enum(0), 'off': tuya.enum(1), 'alarm': tuya.enum(2), 'doorbell': tuya.enum(3)})], - [102, 'alarm_volume', tuya.valueConverterBasic.lookup({ - 'low': tuya.enum(0), 'medium': tuya.enum(1), 'high': tuya.enum(2), 'mute': tuya.enum(3), - })], + [105, 'mode', tuya.valueConverterBasic.lookup({arm: tuya.enum(0), off: tuya.enum(1), alarm: tuya.enum(2), doorbell: tuya.enum(3)})], + [ + 102, + 'alarm_volume', + tuya.valueConverterBasic.lookup({ + low: tuya.enum(0), + medium: tuya.enum(1), + high: tuya.enum(2), + mute: tuya.enum(3), + }), + ], [101, 'alarm_time', tuya.valueConverter.raw], [24, 'light_mode', tuya.valueConverter.onOff], ], @@ -6097,22 +7021,23 @@ const definitions: Definition[] = [ await reporting.bind(endpoint, coordinatorEndpoint, ['genBasic']); }, exposes: [ - e.temperature(), e.humidity(), e.battery(), - e.numeric('temperature_report_interval', ea.STATE_SET).withUnit('min').withValueMin(5).withValueMax(60).withValueStep(5) + e.temperature(), + e.humidity(), + e.battery(), + e + .numeric('temperature_report_interval', ea.STATE_SET) + .withUnit('min') + .withValueMin(5) + .withValueMax(60) + .withValueStep(5) .withDescription('Temperature Report interval'), e.enum('temperature_unit_convert', ea.STATE_SET, ['celsius', 'fahrenheit']).withDescription('Current display unit'), - e.enum('temperature_alarm', ea.STATE, ['canceled', 'lower_alarm', 'upper_alarm']) - .withDescription('Temperature alarm status'), - e.numeric('max_temperature', ea.STATE_SET).withUnit('°C').withValueMin(-20).withValueMax(60) - .withDescription('Alarm temperature max'), - e.numeric('min_temperature', ea.STATE_SET).withUnit('°C').withValueMin(-20).withValueMax(60) - .withDescription('Alarm temperature min'), - e.enum('humidity_alarm', ea.STATE, ['canceled', 'lower_alarm', 'upper_alarm']) - .withDescription('Humidity alarm status'), - e.numeric('max_humidity', ea.STATE_SET).withUnit('%').withValueMin(0).withValueMax(100) - .withDescription('Alarm humidity max'), - e.numeric('min_humidity', ea.STATE_SET).withUnit('%').withValueMin(0).withValueMax(100) - .withDescription('Alarm humidity min'), + e.enum('temperature_alarm', ea.STATE, ['canceled', 'lower_alarm', 'upper_alarm']).withDescription('Temperature alarm status'), + e.numeric('max_temperature', ea.STATE_SET).withUnit('°C').withValueMin(-20).withValueMax(60).withDescription('Alarm temperature max'), + e.numeric('min_temperature', ea.STATE_SET).withUnit('°C').withValueMin(-20).withValueMax(60).withDescription('Alarm temperature min'), + e.enum('humidity_alarm', ea.STATE, ['canceled', 'lower_alarm', 'upper_alarm']).withDescription('Humidity alarm status'), + e.numeric('max_humidity', ea.STATE_SET).withUnit('%').withValueMin(0).withValueMax(100).withDescription('Alarm humidity max'), + e.numeric('min_humidity', ea.STATE_SET).withUnit('%').withValueMin(0).withValueMax(100).withDescription('Alarm humidity min'), ], }, { @@ -6132,22 +7057,29 @@ const definitions: Definition[] = [ e.numeric('cycle_time', ea.STATE).withDescription('Cycle time').withUnit('ms'), e.enum('top_limit', ea.STATE_SET, ['SET', 'CLEAR']).withDescription('Setup or clear top limit'), e.enum('bottom_limit', ea.STATE_SET, ['SET', 'CLEAR']).withDescription('Setup or clear bottom limit'), - e.numeric('favorite_position', ea.STATE_SET).withValueMin(0).withValueMax(100) - .withDescription('Favorite position of this cover'), + e.numeric('favorite_position', ea.STATE_SET).withValueMin(0).withValueMax(100).withDescription('Favorite position of this cover'), e.binary(`reverse_direction`, ea.STATE_SET, true, false).withDescription(`Inverts the cover direction`), e.text('motor_type', ea.STATE), e.enum('report', ea.SET, ['']), ], }, { - fingerprint: tuya.fingerprint('TS1201', ['_TZ3290_7v1k4vufotpowp9z', '_TZ3290_rlkmy85q4pzoxobl', - '_TZ3290_jxvzqatwgsaqzx1u', '_TZ3290_lypnqvlem5eq1ree']), + fingerprint: tuya.fingerprint('TS1201', [ + '_TZ3290_7v1k4vufotpowp9z', + '_TZ3290_rlkmy85q4pzoxobl', + '_TZ3290_jxvzqatwgsaqzx1u', + '_TZ3290_lypnqvlem5eq1ree', + ]), model: 'ZS06', vendor: 'Tuya', description: 'Universal smart IR remote control', fromZigbee: [ - fzZosung.zosung_send_ir_code_00, fzZosung.zosung_send_ir_code_01, fzZosung.zosung_send_ir_code_02, - fzZosung.zosung_send_ir_code_03, fzZosung.zosung_send_ir_code_04, fzZosung.zosung_send_ir_code_05, + fzZosung.zosung_send_ir_code_00, + fzZosung.zosung_send_ir_code_01, + fzZosung.zosung_send_ir_code_02, + fzZosung.zosung_send_ir_code_03, + fzZosung.zosung_send_ir_code_04, + fzZosung.zosung_send_ir_code_05, ], toZigbee: [tzZosung.zosung_ir_code_to_send, tzZosung.zosung_learn_ir_code], exposes: [ez.learn_ir_code(), ez.learned_ir_code(), ez.ir_code_to_send()], @@ -6165,7 +7097,10 @@ const definitions: Definition[] = [ toZigbee: [tzLocal.temperature_unit], onEvent: tuya.onEventSetLocalTime, exposes: [ - e.temperature(), e.humidity(), e.battery(), e.battery_voltage(), + e.temperature(), + e.humidity(), + e.battery(), + e.battery_voltage(), e.enum('temperature_unit', ea.STATE_SET, ['celsius', 'fahrenheit']).withDescription('Current display unit'), ], configure: async (device, coordinatorEndpoint) => { @@ -6176,22 +7111,22 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_0u3bj3rc'}, + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE200_0u3bj3rc'}, {modelID: 'TS0601', manufacturerName: '_TZE200_v6ossqfy'}, - {modelID: 'TS0601', manufacturerName: '_TZE200_mx6u6l4y'}], + {modelID: 'TS0601', manufacturerName: '_TZE200_mx6u6l4y'}, + ], model: 'TS0601_human_presence_sensor', vendor: 'Tuya', description: 'Human presence sensor Zigbee', fromZigbee: [legacy.fromZigbee.hpsz], toZigbee: [legacy.toZigbee.hpsz], onEvent: tuya.onEventSetLocalTime, - exposes: [e.presence(), - e.numeric('duration_of_attendance', ea.STATE).withUnit('min') - .withDescription('Shows the presence duration in minutes'), - e.numeric('duration_of_absence', ea.STATE).withUnit('min') - .withDescription('Shows the duration of the absence in minutes'), - e.binary('led_state', ea.STATE_SET, true, false) - .withDescription('Turns the onboard LED on or off'), + exposes: [ + e.presence(), + e.numeric('duration_of_attendance', ea.STATE).withUnit('min').withDescription('Shows the presence duration in minutes'), + e.numeric('duration_of_absence', ea.STATE).withUnit('min').withDescription('Shows the duration of the absence in minutes'), + e.binary('led_state', ea.STATE_SET, true, false).withDescription('Turns the onboard LED on or off'), ], }, { @@ -6202,8 +7137,14 @@ const definitions: Definition[] = [ fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, - exposes: [e.temperature(), e.humidity(), tuya.exposes.temperatureUnit(), tuya.exposes.temperatureCalibration(), - tuya.exposes.humidityCalibration(), e.battery()], + exposes: [ + e.temperature(), + e.humidity(), + tuya.exposes.temperatureUnit(), + tuya.exposes.temperatureCalibration(), + tuya.exposes.humidityCalibration(), + e.battery(), + ], whiteLabel: [ tuya.whitelabel('Tuya', 'ZG-227Z', 'Temperature and humidity sensor', ['_TZE200_a8sdabtg']), tuya.whitelabel('KOJIMA', 'KOJIMA-THS-ZG-LCD', 'Temperature and humidity sensor', ['_TZE200_dikkika5']), @@ -6229,21 +7170,34 @@ const definitions: Definition[] = [ fromZigbee: [tuya.fz.datapoints], configure: tuya.configureMagicPacket, exposes: [ - e.battery(), e.battery_low(), + e.battery(), + e.battery_low(), e.binary('vacation', ea.STATE_SET, 'ON', 'OFF').withDescription('Vacation mode'), e.enum('alarm', ea.STATE, ['ALARM', 'IDLE']).withDescription('Alarm'), e.binary('alarm_switch', ea.STATE_SET, 'ON', 'OFF').withDescription('Alarm enable'), e.binary('handlesound', ea.STATE_SET, 'ON', 'OFF').withDescription('Handle closed sound'), e.enum('opening_mode', ea.STATE, ['closed', 'tilted']).withDescription('Window tilt'), - e.temperature(), e.humidity(), + e.temperature(), + e.humidity(), e.binary('keysound', ea.STATE_SET, 'ON', 'OFF').withDescription('Key beep sound'), e.enum('sensitivity', ea.STATE_SET, ['off', 'low', 'medium', 'high', 'max']).withDescription('Sensitivity of the alarm sensor'), e.enum('position', ea.STATE, ['up', 'right', 'down', 'left']), e.enum('button_left', ea.STATE, ['released', 'pressed']), e.enum('button_right', ea.STATE, ['released', 'pressed']), - e.numeric('duration', ea.STATE_SET).withValueMin(0).withValueMax(300).withValueStep(1) - .withUnit('sec').withDescription('Duration of the alarm').withPreset('default', 180, 'Default value'), - e.numeric('update_frequency', ea.STATE_SET).withUnit('min').withDescription('Update frequency').withValueMin(0).withValueMax(700) + e + .numeric('duration', ea.STATE_SET) + .withValueMin(0) + .withValueMax(300) + .withValueStep(1) + .withUnit('sec') + .withDescription('Duration of the alarm') + .withPreset('default', 180, 'Default value'), + e + .numeric('update_frequency', ea.STATE_SET) + .withUnit('min') + .withDescription('Update frequency') + .withValueMin(0) + .withValueMax(700) .withPreset('default', 20, 'Default value'), e.enum('calibrate', ea.STATE_SET, ['clear', 'execute']), ], @@ -6252,22 +7206,30 @@ const definitions: Definition[] = [ [3, 'battery', tuya.valueConverter.raw], [8, 'temperature', tuya.valueConverter.divideBy10], [101, 'humidity', tuya.valueConverter.raw], - [102, 'alarm', tuya.valueConverterBasic.lookup({'IDLE': tuya.enum(0), 'ALARM': tuya.enum(1)})], - [103, 'opening_mode', tuya.valueConverterBasic.lookup({'closed': tuya.enum(0), 'tilted': tuya.enum(1)})], - [104, 'position', tuya.valueConverterBasic.lookup( - {'left': tuya.enum(4), 'up': tuya.enum(1), 'down': tuya.enum(2), 'right': tuya.enum(3)})], - [105, 'button_left', tuya.valueConverterBasic.lookup({'released': tuya.enum(0), 'pressed': tuya.enum(1)})], - [106, 'button_right', tuya.valueConverterBasic.lookup({'released': tuya.enum(0), 'pressed': tuya.enum(1)})], - [107, 'vacation', tuya.valueConverterBasic.lookup({'OFF': tuya.enum(0), 'ON': tuya.enum(1)})], - [108, 'sensitivity', tuya.valueConverterBasic.lookup( - {'off': tuya.enum(0), 'low': tuya.enum(1), 'medium': tuya.enum(2), 'high': tuya.enum(3), 'max': tuya.enum(4)})], - [109, 'alarm_switch', tuya.valueConverterBasic.lookup({'OFF': tuya.enum(0), 'ON': tuya.enum(1)})], + [102, 'alarm', tuya.valueConverterBasic.lookup({IDLE: tuya.enum(0), ALARM: tuya.enum(1)})], + [103, 'opening_mode', tuya.valueConverterBasic.lookup({closed: tuya.enum(0), tilted: tuya.enum(1)})], + [104, 'position', tuya.valueConverterBasic.lookup({left: tuya.enum(4), up: tuya.enum(1), down: tuya.enum(2), right: tuya.enum(3)})], + [105, 'button_left', tuya.valueConverterBasic.lookup({released: tuya.enum(0), pressed: tuya.enum(1)})], + [106, 'button_right', tuya.valueConverterBasic.lookup({released: tuya.enum(0), pressed: tuya.enum(1)})], + [107, 'vacation', tuya.valueConverterBasic.lookup({OFF: tuya.enum(0), ON: tuya.enum(1)})], + [ + 108, + 'sensitivity', + tuya.valueConverterBasic.lookup({ + off: tuya.enum(0), + low: tuya.enum(1), + medium: tuya.enum(2), + high: tuya.enum(3), + max: tuya.enum(4), + }), + ], + [109, 'alarm_switch', tuya.valueConverterBasic.lookup({OFF: tuya.enum(0), ON: tuya.enum(1)})], [110, 'update_frequency', tuya.valueConverter.raw], - [111, 'keysound', tuya.valueConverterBasic.lookup({'OFF': tuya.enum(0), 'ON': tuya.enum(1)})], - [112, 'battery_low', tuya.valueConverterBasic.lookup({'ON': tuya.enum(0), 'OFF': tuya.enum(1)})], + [111, 'keysound', tuya.valueConverterBasic.lookup({OFF: tuya.enum(0), ON: tuya.enum(1)})], + [112, 'battery_low', tuya.valueConverterBasic.lookup({ON: tuya.enum(0), OFF: tuya.enum(1)})], [113, 'duration', tuya.valueConverter.raw], - [114, 'handlesound', tuya.valueConverterBasic.lookup({'OFF': tuya.enum(0), 'ON': tuya.enum(1)})], - [120, 'calibrate', tuya.valueConverterBasic.lookup({'clear': tuya.enum(0), 'execute': tuya.enum(1)})], + [114, 'handlesound', tuya.valueConverterBasic.lookup({OFF: tuya.enum(0), ON: tuya.enum(1)})], + [120, 'calibrate', tuya.valueConverterBasic.lookup({clear: tuya.enum(0), execute: tuya.enum(1)})], ], }, }, @@ -6283,8 +7245,14 @@ const definitions: Definition[] = [ await tuya.configureMagicPacket(device, coordinatorEndpoint); await device.getEndpoint(1).command('manuSpecificTuya', 'dataQuery', {}); }, - exposes: [e.temperature(), e.humidity(), tuya.exposes.temperatureUnit(), tuya.exposes.temperatureCalibration(), - tuya.exposes.humidityCalibration(), e.battery()], + exposes: [ + e.temperature(), + e.humidity(), + tuya.exposes.temperatureUnit(), + tuya.exposes.temperatureCalibration(), + tuya.exposes.humidityCalibration(), + e.battery(), + ], meta: { tuyaDatapoints: [ [1, 'temperature', tuya.valueConverter.divideBy10], @@ -6320,9 +7288,18 @@ const definitions: Definition[] = [ fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, - exposes: [e.contact(), e.illuminance().withUnit('lx'), e.battery(), - e.numeric('illuminance_interval', ea.STATE_SET).withValueMin(1).withValueMax(720).withValueStep(1).withUnit('minutes') - .withDescription('Brightness acquisition interval (refresh and update only while active)')], + exposes: [ + e.contact(), + e.illuminance().withUnit('lx'), + e.battery(), + e + .numeric('illuminance_interval', ea.STATE_SET) + .withValueMin(1) + .withValueMax(720) + .withValueStep(1) + .withUnit('minutes') + .withDescription('Brightness acquisition interval (refresh and update only while active)'), + ], meta: { tuyaDatapoints: [ [1, 'contact', tuya.valueConverter.inverse], @@ -6351,14 +7328,19 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_8isdky6j'}, {modelID: 'TS0225', manufacturerName: '_TZE200_p6fuhvez'}], + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE200_8isdky6j'}, + {modelID: 'TS0225', manufacturerName: '_TZE200_p6fuhvez'}, + ], model: 'ZG-225Z', vendor: 'Tuya', description: 'Gas sensor', fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, - exposes: [e.gas(), tuya.exposes.gasValue().withUnit('ppm'), + exposes: [ + e.gas(), + tuya.exposes.gasValue().withUnit('ppm'), e.enum('sensitivity', ea.STATE_SET, ['low', 'medium', 'high']).withDescription('Gas sensor sensitivity'), e.enum('ring', ea.STATE_SET, ['ring1', 'ring2']).withDescription('Ring'), ], @@ -6366,8 +7348,8 @@ const definitions: Definition[] = [ tuyaDatapoints: [ [1, 'gas', tuya.valueConverter.trueFalse0], [2, 'gas_value', tuya.valueConverter.raw], - [101, 'sensitivity', tuya.valueConverterBasic.lookup({'low': tuya.enum(0), 'medium': tuya.enum(1), 'high': tuya.enum(2)})], - [6, 'ring', tuya.valueConverterBasic.lookup({'ring1': tuya.enum(0), 'ring2': tuya.enum(1)})], + [101, 'sensitivity', tuya.valueConverterBasic.lookup({low: tuya.enum(0), medium: tuya.enum(1), high: tuya.enum(2)})], + [6, 'ring', tuya.valueConverterBasic.lookup({ring1: tuya.enum(0), ring2: tuya.enum(1)})], ], }, }, @@ -6379,24 +7361,31 @@ const definitions: Definition[] = [ fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], exposes: [ - e.occupancy(), e.illuminance().withUnit('lx'), e.battery(), - e.enum('sensitivity', ea.STATE_SET, ['low', 'medium', 'high']) + e.occupancy(), + e.illuminance().withUnit('lx'), + e.battery(), + e + .enum('sensitivity', ea.STATE_SET, ['low', 'medium', 'high']) .withDescription('PIR sensor sensitivity (refresh and update only while active)'), - e.enum('keep_time', ea.STATE_SET, ['10', '30', '60', '120']) + e + .enum('keep_time', ea.STATE_SET, ['10', '30', '60', '120']) .withDescription('PIR keep time in seconds (refresh and update only while active)'), - e.numeric('illuminance_interval', ea.STATE_SET).withValueMin(1).withValueMax(720).withValueStep(1).withUnit('minutes') + e + .numeric('illuminance_interval', ea.STATE_SET) + .withValueMin(1) + .withValueMax(720) + .withValueStep(1) + .withUnit('minutes') .withDescription('Brightness acquisition interval (refresh and update only while active)'), ], meta: { tuyaDatapoints: [ [1, 'occupancy', tuya.valueConverter.trueFalse0], [4, 'battery', tuya.valueConverter.raw], - [9, 'sensitivity', tuya.valueConverterBasic.lookup({'low': tuya.enum(0), 'medium': tuya.enum(1), 'high': tuya.enum(2)})], - [10, 'keep_time', tuya.valueConverterBasic.lookup( - {'10': tuya.enum(0), '30': tuya.enum(1), '60': tuya.enum(2), '120': tuya.enum(3)})], + [9, 'sensitivity', tuya.valueConverterBasic.lookup({low: tuya.enum(0), medium: tuya.enum(1), high: tuya.enum(2)})], + [10, 'keep_time', tuya.valueConverterBasic.lookup({'10': tuya.enum(0), '30': tuya.enum(1), '60': tuya.enum(2), '120': tuya.enum(3)})], [12, 'illuminance', tuya.valueConverter.raw], [102, 'illuminance_interval', tuya.valueConverter.raw], - ], }, }, @@ -6408,22 +7397,58 @@ const definitions: Definition[] = [ fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], exposes: [ - e.presence(), e.illuminance().withUnit('lx'), - e.numeric('large_motion_detection_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(1).withUnit('x') + e.presence(), + e.illuminance().withUnit('lx'), + e + .numeric('large_motion_detection_sensitivity', ea.STATE_SET) + .withValueMin(0) + .withValueMax(10) + .withValueStep(1) + .withUnit('x') .withDescription('Motion detection sensitivity'), - e.numeric('large_motion_detection_distance', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(0.01).withUnit('m') + e + .numeric('large_motion_detection_distance', ea.STATE_SET) + .withValueMin(0) + .withValueMax(10) + .withValueStep(0.01) + .withUnit('m') .withDescription('Motion detection distance'), e.enum('motion_state', ea.STATE, ['none', 'small', 'medium', 'large']).withDescription('State of the motion'), - e.numeric('fading_time', ea.STATE_SET).withValueMin(0).withValueMax(28800).withValueStep(1).withUnit('s') + e + .numeric('fading_time', ea.STATE_SET) + .withValueMin(0) + .withValueMax(28800) + .withValueStep(1) + .withUnit('s') .withDescription('For how much time presence should stay true after detecting it'), - e.numeric('medium_motion_detection_distance', ea.STATE_SET).withValueMin(0).withValueMax(6).withValueStep(0.01).withUnit('m') + e + .numeric('medium_motion_detection_distance', ea.STATE_SET) + .withValueMin(0) + .withValueMax(6) + .withValueStep(0.01) + .withUnit('m') .withDescription('Medium motion detection distance'), - e.numeric('medium_motion_detection_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(1).withUnit('x') + e + .numeric('medium_motion_detection_sensitivity', ea.STATE_SET) + .withValueMin(0) + .withValueMax(10) + .withValueStep(1) + .withUnit('x') .withDescription('Medium motion detection sensitivity'), e.binary('indicator', ea.STATE_SET, 'ON', 'OFF').withDescription('LED Indicator'), - e.numeric('small_detection_distance', ea.STATE_SET).withValueMin(0).withValueMax(6).withValueStep(0.01).withUnit('m') + e + .numeric('small_detection_distance', ea.STATE_SET) + .withValueMin(0) + .withValueMax(6) + .withValueStep(0.01) + .withUnit('m') .withDescription('Small detection distance'), - e.numeric('small_detection_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(1).withUnit('x') + e + .numeric('small_detection_sensitivity', ea.STATE_SET) + .withValueMin(0) + .withValueMax(10) + .withValueStep(1) + .withUnit('x') .withDescription('Small detection sensitivity'), ], meta: { @@ -6431,8 +7456,11 @@ const definitions: Definition[] = [ [1, 'presence', tuya.valueConverter.trueFalse1], [2, 'large_motion_detection_sensitivity', tuya.valueConverter.raw], [4, 'large_motion_detection_distance', tuya.valueConverter.divideBy100], - [101, 'motion_state', tuya.valueConverterBasic.lookup( - {'none': tuya.enum(0), 'large': tuya.enum(1), 'medium': tuya.enum(2), 'small': tuya.enum(3)})], + [ + 101, + 'motion_state', + tuya.valueConverterBasic.lookup({none: tuya.enum(0), large: tuya.enum(1), medium: tuya.enum(2), small: tuya.enum(3)}), + ], [102, 'fading_time', tuya.valueConverter.raw], [104, 'medium_motion_detection_distance', tuya.valueConverter.divideBy100], [105, 'medium_motion_detection_sensitivity', tuya.valueConverter.raw], @@ -6469,36 +7497,70 @@ const definitions: Definition[] = [ exposes: [ e.presence(), e.enum('motion_state', ea.STATE, ['none', 'large', 'small', 'static']).withDescription('Motion state'), - e.illuminance_lux(), e.battery(), - e.numeric('fading_time', ea.STATE_SET).withValueMin(0).withValueMax(28800).withValueStep(1).withUnit('s') + e.illuminance_lux(), + e.battery(), + e + .numeric('fading_time', ea.STATE_SET) + .withValueMin(0) + .withValueMax(28800) + .withValueStep(1) + .withUnit('s') .withDescription('Presence keep time'), - e.numeric('static_detection_distance', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(0.01).withUnit('m') + e + .numeric('static_detection_distance', ea.STATE_SET) + .withValueMin(0) + .withValueMax(10) + .withValueStep(0.01) + .withUnit('m') .withDescription('Static detection distance'), - e.numeric('static_detection_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(1).withUnit('x') + e + .numeric('static_detection_sensitivity', ea.STATE_SET) + .withValueMin(0) + .withValueMax(10) + .withValueStep(1) + .withUnit('x') .withDescription('Static detection sensitivity'), e.binary('indicator', ea.STATE_SET, 'ON', 'OFF').withDescription('LED indicator mode'), - e.enum('motion_detection_mode', ea.STATE_SET, ['only_pir', 'pir_and_radar', 'only_radar']) + e + .enum('motion_detection_mode', ea.STATE_SET, ['only_pir', 'pir_and_radar', 'only_radar']) .withDescription('Motion detection mode (Firmware version>=0122052017)'), - e.numeric('motion_detection_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(1).withUnit('x') + e + .numeric('motion_detection_sensitivity', ea.STATE_SET) + .withValueMin(0) + .withValueMax(10) + .withValueStep(1) + .withUnit('x') .withDescription('Motion detection sensitivity (Firmware version>=0122052017)'), ], meta: { tuyaDatapoints: [ [1, 'presence', tuya.valueConverter.trueFalse1], [106, 'illuminance_lux', tuya.valueConverter.raw], - [101, 'motion_state', tuya.valueConverterBasic.lookup({ - 'none': tuya.enum(0), 'large': tuya.enum(1), 'small': tuya.enum(2), 'static': tuya.enum(3), - })], + [ + 101, + 'motion_state', + tuya.valueConverterBasic.lookup({ + none: tuya.enum(0), + large: tuya.enum(1), + small: tuya.enum(2), + static: tuya.enum(3), + }), + ], [102, 'fading_time', tuya.valueConverter.raw], [4, 'static_detection_distance', tuya.valueConverter.divideBy100], [2, 'static_detection_sensitivity', tuya.valueConverter.raw], [107, 'indicator', tuya.valueConverter.onOff], [121, 'battery', tuya.valueConverter.raw], - [122, 'motion_detection_mode', tuya.valueConverterBasic.lookup({ - 'only_pir': tuya.enum(0), 'pir_and_radar': tuya.enum(1), 'only_radar': tuya.enum(2), - })], + [ + 122, + 'motion_detection_mode', + tuya.valueConverterBasic.lookup({ + only_pir: tuya.enum(0), + pir_and_radar: tuya.enum(1), + only_radar: tuya.enum(2), + }), + ], [123, 'motion_detection_sensitivity', tuya.valueConverter.raw], - ], }, }, @@ -6516,7 +7578,9 @@ const definitions: Definition[] = [ toZigbee: [tz.TS110E_onoff_brightness, tz.TS110E_options, tuya.tz.power_on_behavior_1, tz.light_brightness_move], exposes: [ e.light_brightness().withMinBrightness().withMaxBrightness(), - tuya.exposes.lightType().withAccess(ea.ALL), e.power_on_behavior().withAccess(ea.ALL)], + tuya.exposes.lightType().withAccess(ea.ALL), + e.power_on_behavior().withAccess(ea.ALL), + ], configure: async (device, coordinatorEndpoint) => { await tuya.configureMagicPacket(device, coordinatorEndpoint); const endpoint = device.getEndpoint(1); @@ -6531,10 +7595,7 @@ const definitions: Definition[] = [ description: '1 channel dimmer', fromZigbee: [fz.TS110E, tuya.fz.power_on_behavior_1, fz.on_off], toZigbee: [tz.TS110E_onoff_brightness, tz.TS110E_options, tuya.tz.power_on_behavior_1, tz.light_brightness_move], - exposes: [ - e.light_brightness().withMinBrightness().withMaxBrightness(), - e.power_on_behavior().withAccess(ea.ALL), - ], + exposes: [e.light_brightness().withMinBrightness().withMaxBrightness(), e.power_on_behavior().withAccess(ea.ALL)], configure: async (device, coordinatorEndpoint) => { await tuya.configureMagicPacket(device, coordinatorEndpoint); const endpoint = device.getEndpoint(1); @@ -6543,7 +7604,10 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'TS110E', manufacturerName: '_TZ3210_wdexaypg'}, {modelID: 'TS110E', manufacturerName: '_TZ3210_3mpwqzuu'}], + fingerprint: [ + {modelID: 'TS110E', manufacturerName: '_TZ3210_wdexaypg'}, + {modelID: 'TS110E', manufacturerName: '_TZ3210_3mpwqzuu'}, + ], model: 'TS110E_2gang_1', vendor: 'Tuya', description: '2 channel dimmer', @@ -6555,8 +7619,10 @@ const definitions: Definition[] = [ toZigbee: [tz.TS110E_light_onoff_brightness, tuya.tz.power_on_behavior_1, tz.TS110E_options], configure: tuya.configureMagicPacket, exposes: [ - e.min_brightness().withEndpoint('l1'), e.max_brightness().withEndpoint('l1'), - e.min_brightness().withEndpoint('l2'), e.max_brightness().withEndpoint('l2'), + e.min_brightness().withEndpoint('l1'), + e.max_brightness().withEndpoint('l1'), + e.min_brightness().withEndpoint('l2'), + e.max_brightness().withEndpoint('l2'), e.power_on_behavior(), tuya.exposes.switchType().withEndpoint('l1'), tuya.exposes.switchType().withEndpoint('l2'), @@ -6573,7 +7639,8 @@ const definitions: Definition[] = [ exposes: [ e.light_brightness().withMinBrightness().withMaxBrightness().withEndpoint('l1'), e.light_brightness().withMinBrightness().withMaxBrightness().withEndpoint('l2'), - e.power_on_behavior().withAccess(ea.ALL)], + e.power_on_behavior().withAccess(ea.ALL), + ], configure: async (device, coordinatorEndpoint) => { await tuya.configureMagicPacket(device, coordinatorEndpoint); const endpoint = device.getEndpoint(1); @@ -6597,12 +7664,26 @@ const definitions: Definition[] = [ {vendor: 'Owon', model: 'PC321-Z-TY'}, ], exposes: [ - e.ac_frequency(), e.temperature(), e.current(), e.power(), e.energy(), - tuya.exposes.energyWithPhase('a'), tuya.exposes.energyWithPhase('b'), tuya.exposes.energyWithPhase('c'), - tuya.exposes.voltageWithPhase('a'), tuya.exposes.voltageWithPhase('b'), tuya.exposes.voltageWithPhase('c'), - tuya.exposes.powerWithPhase('a'), tuya.exposes.powerWithPhase('b'), tuya.exposes.powerWithPhase('c'), - tuya.exposes.currentWithPhase('a'), tuya.exposes.currentWithPhase('b'), tuya.exposes.currentWithPhase('c'), - tuya.exposes.powerFactorWithPhase('a'), tuya.exposes.powerFactorWithPhase('b'), tuya.exposes.powerFactorWithPhase('c'), + e.ac_frequency(), + e.temperature(), + e.current(), + e.power(), + e.energy(), + tuya.exposes.energyWithPhase('a'), + tuya.exposes.energyWithPhase('b'), + tuya.exposes.energyWithPhase('c'), + tuya.exposes.voltageWithPhase('a'), + tuya.exposes.voltageWithPhase('b'), + tuya.exposes.voltageWithPhase('c'), + tuya.exposes.powerWithPhase('a'), + tuya.exposes.powerWithPhase('b'), + tuya.exposes.powerWithPhase('c'), + tuya.exposes.currentWithPhase('a'), + tuya.exposes.currentWithPhase('b'), + tuya.exposes.currentWithPhase('c'), + tuya.exposes.powerFactorWithPhase('a'), + tuya.exposes.powerFactorWithPhase('b'), + tuya.exposes.powerFactorWithPhase('c'), ], meta: { multiEndpointSkip: ['power_factor', 'power_factor_phase_b', 'power_factor_phase_c', 'energy'], @@ -6635,10 +7716,19 @@ const definitions: Definition[] = [ configure: tuya.configureMagicPacket, whiteLabel: [{vendor: 'Wenzhou Taiye Electric', model: 'TAC7361C BI'}], exposes: [ - e.switch().setAccess('state', ea.STATE_SET), e.power(), e.energy(), e.produced_energy(), - tuya.exposes.voltageWithPhase('a'), tuya.exposes.voltageWithPhase('b'), tuya.exposes.voltageWithPhase('c'), - tuya.exposes.powerWithPhase('a'), tuya.exposes.powerWithPhase('b'), tuya.exposes.powerWithPhase('c'), - tuya.exposes.currentWithPhase('a'), tuya.exposes.currentWithPhase('b'), tuya.exposes.currentWithPhase('c'), + e.switch().setAccess('state', ea.STATE_SET), + e.power(), + e.energy(), + e.produced_energy(), + tuya.exposes.voltageWithPhase('a'), + tuya.exposes.voltageWithPhase('b'), + tuya.exposes.voltageWithPhase('c'), + tuya.exposes.powerWithPhase('a'), + tuya.exposes.powerWithPhase('b'), + tuya.exposes.powerWithPhase('c'), + tuya.exposes.currentWithPhase('a'), + tuya.exposes.currentWithPhase('b'), + tuya.exposes.currentWithPhase('c'), ], meta: { tuyaDatapoints: [ @@ -6661,9 +7751,11 @@ const definitions: Definition[] = [ toZigbee: [tuya.tz.datapoints], onEvent: tuya.onEventSetLocalTime, configure: tuya.configureMagicPacket, - exposes: [tuya.exposes.errorStatus(), tuya.exposes.switch(), tuya.exposes.batteryState(), - tuya.exposes.countdown().withValueMin(0).withValueMax(255).withUnit('minutes') - .withDescription('Max on time in minutes'), + exposes: [ + tuya.exposes.errorStatus(), + tuya.exposes.switch(), + tuya.exposes.batteryState(), + tuya.exposes.countdown().withValueMin(0).withValueMax(255).withUnit('minutes').withDescription('Max on time in minutes'), ], meta: { tuyaSendCommand: 'sendData', @@ -6684,23 +7776,24 @@ const definitions: Definition[] = [ toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, exposes: [ - tuya.exposes.switch(), e.power_on_behavior(['off', 'on']).withAccess(ea.STATE_SET), + tuya.exposes.switch(), + e.power_on_behavior(['off', 'on']).withAccess(ea.STATE_SET), tuya.exposes.countdown().withValueMin(0).withValueMax(43200).withUnit('s').withDescription('Max ON time in seconds'), - e.numeric('fan_speed', ea.STATE_SET).withValueMin(1).withValueMax(5).withValueStep(1) - .withDescription('Speed off the fan'), + e.numeric('fan_speed', ea.STATE_SET).withValueMin(1).withValueMax(5).withValueStep(1).withDescription('Speed off the fan'), ], meta: { tuyaDatapoints: [ [1, 'state', tuya.valueConverter.onOff], [2, 'countdown', tuya.valueConverter.countdown], - [3, 'fan_speed', tuya.valueConverterBasic - .lookup({'1': tuya.enum(0), '2': tuya.enum(1), '3': tuya.enum(2), '4': tuya.enum(3), '5': tuya.enum(4)})], - [11, 'power_on_behavior', tuya.valueConverterBasic.lookup({'off': tuya.enum(0), 'on': tuya.enum(1)})], + [ + 3, + 'fan_speed', + tuya.valueConverterBasic.lookup({'1': tuya.enum(0), '2': tuya.enum(1), '3': tuya.enum(2), '4': tuya.enum(3), '5': tuya.enum(4)}), + ], + [11, 'power_on_behavior', tuya.valueConverterBasic.lookup({off: tuya.enum(0), on: tuya.enum(1)})], ], }, - whiteLabel: [ - {vendor: 'Lerlink', model: 'T2-Z67/T2-W67'}, - ], + whiteLabel: [{vendor: 'Lerlink', model: 'T2-Z67/T2-W67'}], }, { fingerprint: tuya.fingerprint('TS0601', ['_TZE200_hmqzfqml']), @@ -6719,14 +7812,12 @@ const definitions: Definition[] = [ meta: { tuyaDatapoints: [ [1, 'state', tuya.valueConverter.onOff], - [101, 'fan_speed', tuya.valueConverterBasic.lookup({'minimum': tuya.enum(0), 'medium': tuya.enum(1), 'maximum': tuya.enum(2)})], - [11, 'power_on_behavior', tuya.valueConverterBasic.lookup({'OFF': tuya.enum(0), 'ON': tuya.enum(1)})], + [101, 'fan_speed', tuya.valueConverterBasic.lookup({minimum: tuya.enum(0), medium: tuya.enum(1), maximum: tuya.enum(2)})], + [11, 'power_on_behavior', tuya.valueConverterBasic.lookup({OFF: tuya.enum(0), ON: tuya.enum(1)})], [5, 'status_indication', tuya.valueConverter.onOff], ], }, - whiteLabel: [ - {vendor: 'Liwokit', model: 'Fan+Light-01'}, - ], + whiteLabel: [{vendor: 'Liwokit', model: 'Fan+Light-01'}], }, { fingerprint: tuya.fingerprint('TS0601', ['_TZE200_lawxy9e2']), @@ -6740,21 +7831,24 @@ const definitions: Definition[] = [ e.binary('status_indication', ea.STATE_SET, 'ON', 'OFF').withDescription('Light switch'), tuya.exposes.switch(), e.power_on_behavior(['OFF', 'ON']).withAccess(ea.STATE_SET).withDescription('Fan On Off'), - e.numeric('fan_speed', ea.STATE_SET).withValueMin(1).withValueMax(5).withValueStep(1) - .withDescription('Speed off the fan'), + e.numeric('fan_speed', ea.STATE_SET).withValueMin(1).withValueMax(5).withValueStep(1).withDescription('Speed off the fan'), ], meta: { tuyaDatapoints: [ [1, 'state', tuya.valueConverter.onOff], - [3, 'fan_speed', tuya.valueConverterBasic - .lookup({'1': tuya.enum(0), '2': tuya.enum(1), '3': tuya.enum(2), '4': tuya.enum(3), '5': tuya.enum(4)}, '5')], - [11, 'power_on_behavior', tuya.valueConverterBasic.lookup({'OFF': tuya.enum(0), 'ON': tuya.enum(1)})], + [ + 3, + 'fan_speed', + tuya.valueConverterBasic.lookup( + {'1': tuya.enum(0), '2': tuya.enum(1), '3': tuya.enum(2), '4': tuya.enum(3), '5': tuya.enum(4)}, + '5', + ), + ], + [11, 'power_on_behavior', tuya.valueConverterBasic.lookup({OFF: tuya.enum(0), ON: tuya.enum(1)})], [5, 'status_indication', tuya.valueConverter.onOff], ], }, - whiteLabel: [ - {vendor: 'Liwokit', model: 'Fan+Light-01'}, - ], + whiteLabel: [{vendor: 'Liwokit', model: 'Fan+Light-01'}], }, { zigbeeModel: ['TS0224'], @@ -6763,12 +7857,17 @@ const definitions: Definition[] = [ description: 'Smart light & sound siren', fromZigbee: [], toZigbee: [tz.warning, tzLocal.TS0224], - exposes: [e.warning(), + exposes: [ + e.warning(), e.binary('light', ea.STATE_SET, 'ON', 'OFF').withDescription('Turn the light of the alarm ON/OFF'), - e.numeric('duration', ea.STATE_SET).withValueMin(60).withValueMax(3600).withValueStep(1).withUnit('s') + e + .numeric('duration', ea.STATE_SET) + .withValueMin(60) + .withValueMax(3600) + .withValueStep(1) + .withUnit('s') .withDescription('Duration of the alarm'), - e.enum('volume', ea.STATE_SET, ['mute', 'low', 'medium', 'high']) - .withDescription('Volume of the alarm'), + e.enum('volume', ea.STATE_SET, ['mute', 'low', 'medium', 'high']).withDescription('Volume of the alarm'), ], }, { @@ -6787,34 +7886,58 @@ const definitions: Definition[] = [ description: 'Din rail switch with power monitoring and threshold settings', vendor: 'Tuya', ota: ota.zigbeeOTA, - extend: [tuya.modernExtend.tuyaOnOff({ - electricalMeasurements: true, electricalMeasurementsFzConverter: fzLocal.TS011F_electrical_measurement, - powerOutageMemory: true, indicatorMode: true, - })], + extend: [ + tuya.modernExtend.tuyaOnOff({ + electricalMeasurements: true, + electricalMeasurementsFzConverter: fzLocal.TS011F_electrical_measurement, + powerOutageMemory: true, + indicatorMode: true, + }), + ], fromZigbee: [fz.temperature, fzLocal.TS011F_threshold], toZigbee: [tzLocal.TS011F_threshold], exposes: [ e.temperature(), - e.numeric('temperature_threshold', ea.STATE_SET).withValueMin(40).withValueMax(100).withValueStep(1).withUnit('*C') + e + .numeric('temperature_threshold', ea.STATE_SET) + .withValueMin(40) + .withValueMax(100) + .withValueStep(1) + .withUnit('*C') .withDescription('High temperature threshold'), - e.binary('temperature_breaker', ea.STATE_SET, 'ON', 'OFF') - .withDescription('High temperature breaker'), - e.numeric('power_threshold', ea.STATE_SET).withValueMin(1).withValueMax(26).withValueStep(1).withUnit('kW') + e.binary('temperature_breaker', ea.STATE_SET, 'ON', 'OFF').withDescription('High temperature breaker'), + e + .numeric('power_threshold', ea.STATE_SET) + .withValueMin(1) + .withValueMax(26) + .withValueStep(1) + .withUnit('kW') .withDescription('High power threshold'), - e.binary('power_breaker', ea.STATE_SET, 'ON', 'OFF') - .withDescription('High power breaker'), - e.numeric('over_current_threshold', ea.STATE_SET).withValueMin(1).withValueMax(64).withValueStep(1).withUnit('A') + e.binary('power_breaker', ea.STATE_SET, 'ON', 'OFF').withDescription('High power breaker'), + e + .numeric('over_current_threshold', ea.STATE_SET) + .withValueMin(1) + .withValueMax(64) + .withValueStep(1) + .withUnit('A') .withDescription('Over-current threshold'), - e.binary('over_current_breaker', ea.STATE_SET, 'ON', 'OFF') - .withDescription('Over-current breaker'), - e.numeric('over_voltage_threshold', ea.STATE_SET).withValueMin(220).withValueMax(265).withValueStep(1).withUnit('V') + e.binary('over_current_breaker', ea.STATE_SET, 'ON', 'OFF').withDescription('Over-current breaker'), + e + .numeric('over_voltage_threshold', ea.STATE_SET) + .withValueMin(220) + .withValueMax(265) + .withValueStep(1) + .withUnit('V') .withDescription('Over-voltage threshold'), - e.binary('over_voltage_breaker', ea.STATE_SET, 'ON', 'OFF') - .withDescription('Over-voltage breaker'), - e.numeric('under_voltage_threshold', ea.STATE_SET).withValueMin(76).withValueMax(240).withValueStep(1).withUnit('V') + e.binary('over_voltage_breaker', ea.STATE_SET, 'ON', 'OFF').withDescription('Over-voltage breaker'), + e + .numeric('under_voltage_threshold', ea.STATE_SET) + .withValueMin(76) + .withValueMax(240) + .withValueStep(1) + .withUnit('V') .withDescription('Under-voltage threshold'), - e.binary('under_voltage_breaker', ea.STATE_SET, 'ON', 'OFF') - .withDescription('Under-voltage breaker'), + e.binary('under_voltage_breaker', ea.STATE_SET, 'ON', 'OFF').withDescription('Under-voltage breaker'), ], configure: async (device, coordinatorEndpoint) => { await tuya.configureMagicPacket(device, coordinatorEndpoint); @@ -6856,7 +7979,7 @@ const definitions: Definition[] = [ description: 'Zigbee 3.0 smart light switch module 2 gang', extend: [tuya.modernExtend.tuyaOnOff({switchType: true, endpoints: ['l1', 'l2']})], endpoint: (device) => { - return {'l1': 1, 'l2': 2}; + return {l1: 1, l2: 2}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -6892,7 +8015,7 @@ const definitions: Definition[] = [ description: '2-Gang switch with backlight', extend: [tuya.modernExtend.tuyaOnOff({powerOnBehavior2: true, backlightModeOffOn: true, endpoints: ['l1', 'l2']})], endpoint: (device) => { - return {'l1': 1, 'l2': 2}; + return {l1: 1, l2: 2}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -6906,15 +8029,13 @@ const definitions: Definition[] = [ ], }, { - fingerprint: [ - {modelID: 'TS0002', manufacturerName: '_TZ3000_i9w5mehz'}, - ], + fingerprint: [{modelID: 'TS0002', manufacturerName: '_TZ3000_i9w5mehz'}], model: 'TS0002_switch_module_4', vendor: 'Tuya', description: '2 gang switch with backlight', extend: [tuya.modernExtend.tuyaOnOff({powerOnBehavior2: true, backlightModeOffOn: true, indicatorMode: true, endpoints: ['l1', 'l2']})], endpoint: (device) => { - return {'l1': 1, 'l2': 2}; + return {l1: 1, l2: 2}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -6933,7 +8054,7 @@ const definitions: Definition[] = [ description: '3-Gang switch with backlight', extend: [tuya.modernExtend.tuyaOnOff({powerOnBehavior2: true, backlightModeOffOn: true, endpoints: ['left', 'center', 'right']})], endpoint: (device) => { - return {'left': 1, 'center': 2, 'right': 3}; + return {left: 1, center: 2, right: 3}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -6948,9 +8069,7 @@ const definitions: Definition[] = [ ], }, { - fingerprint: [ - {modelID: 'TS0601', manufacturerName: '_TZE200_hewlydpz'}, - ], + fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_hewlydpz'}], model: 'TS0601_switch_4_gang_2', vendor: 'Tuya', description: '4 gang switch with backlight', @@ -6965,7 +8084,7 @@ const definitions: Definition[] = [ tuya.exposes.backlightModeOffOn(), ], endpoint: (device) => { - return {'l1': 1, 'l2': 1, 'l3': 1, 'l4': 1}; + return {l1: 1, l2: 1, l3: 1, l4: 1}; }, meta: { multiEndpoint: true, @@ -6977,14 +8096,10 @@ const definitions: Definition[] = [ [7, 'backlight_mode', tuya.valueConverter.onOff], ], }, - whiteLabel: [ - tuya.whitelabel('Homeetec', '37022714', '4 Gang switch with backlight', ['_TZE200_hewlydpz']), - ], + whiteLabel: [tuya.whitelabel('Homeetec', '37022714', '4 Gang switch with backlight', ['_TZE200_hewlydpz'])], }, { - fingerprint: [ - {modelID: 'TS0601', manufacturerName: '_TZE200_p6vz3wzt'}, - ], + fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_p6vz3wzt'}], model: 'TS0601_cover_5', vendor: 'Tuya', description: 'Curtain/blind switch', @@ -7000,17 +8115,15 @@ const definitions: Definition[] = [ ], meta: { tuyaDatapoints: [ - [1, 'state', tuya.valueConverterBasic.lookup({'OPEN': tuya.enum(0), 'STOP': tuya.enum(1), 'CLOSE': tuya.enum(2)})], + [1, 'state', tuya.valueConverterBasic.lookup({OPEN: tuya.enum(0), STOP: tuya.enum(1), CLOSE: tuya.enum(2)})], [2, 'position', tuya.valueConverter.coverPosition], - [3, 'calibration', tuya.valueConverterBasic.lookup({'START': tuya.enum(0), 'END': tuya.enum(1)})], + [3, 'calibration', tuya.valueConverterBasic.lookup({START: tuya.enum(0), END: tuya.enum(1)})], [7, 'backlight_mode', tuya.valueConverter.onOff], - [8, 'motor_steering', tuya.valueConverterBasic.lookup({'FORWARD': tuya.enum(0), 'BACKWARD': tuya.enum(1)})], + [8, 'motor_steering', tuya.valueConverterBasic.lookup({FORWARD: tuya.enum(0), BACKWARD: tuya.enum(1)})], [103, 'child_lock', tuya.valueConverter.onOff], ], }, - whiteLabel: [ - tuya.whitelabel('Homeetec', '37022483', 'Curtain/blind switch', ['_TZE200_p6vz3wzt']), - ], + whiteLabel: [tuya.whitelabel('Homeetec', '37022483', 'Curtain/blind switch', ['_TZE200_p6vz3wzt'])], }, { zigbeeModel: ['TS030F'], @@ -7025,22 +8138,21 @@ const definitions: Definition[] = [ await reporting.batteryPercentageRemaining(endpoint); await reporting.currentPositionLiftPercentage(endpoint); }, - whiteLabel: [ - tuya.whitelabel('Lidl', 'HG09648', 'Livarno roller blinds', ['_TZB000_42ha4rsc']), - ], + whiteLabel: [tuya.whitelabel('Lidl', 'HG09648', 'Livarno roller blinds', ['_TZB000_42ha4rsc'])], exposes: [ e.cover_position(), e.enum('border', ea.SET, ['up', 'down', 'up_delete', 'down_delete']), e.numeric('calibration_time', ea.ALL).withValueMin(0).withValueMax(100), - e.binary('motor_reversal', ea.ALL, 'ON', 'OFF') - .withDescription('Reverse the motor, resets all endpoints! Also the upper border after hardware initialisation. Be careful!' + - 'After this you have to turn off and turn on the roller so that it can drive into the uppest position.'), + e + .binary('motor_reversal', ea.ALL, 'ON', 'OFF') + .withDescription( + 'Reverse the motor, resets all endpoints! Also the upper border after hardware initialisation. Be careful!' + + 'After this you have to turn off and turn on the roller so that it can drive into the uppest position.', + ), ], }, { - fingerprint: [ - {modelID: 'TS0601', manufacturerName: '_TZE200_jhkttplm'}, - ], + fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_jhkttplm'}], model: 'TS0601_cover_with_1_switch', vendor: 'Tuya', description: 'Curtain/blind switch with 1 Gang switch', @@ -7056,28 +8168,24 @@ const definitions: Definition[] = [ e.binary('child_lock', ea.STATE_SET, 'ON', 'OFF').withDescription('Child Lock'), ], endpoint: (device) => { - return {'l1': 1}; + return {l1: 1}; }, meta: { multiEndpoint: true, tuyaDatapoints: [ - [1, 'state', tuya.valueConverterBasic.lookup({'OPEN': tuya.enum(0), 'STOP': tuya.enum(1), 'CLOSE': tuya.enum(2)})], + [1, 'state', tuya.valueConverterBasic.lookup({OPEN: tuya.enum(0), STOP: tuya.enum(1), CLOSE: tuya.enum(2)})], [2, 'position', tuya.valueConverter.coverPosition], - [3, 'calibration', tuya.valueConverterBasic.lookup({'START': tuya.enum(0), 'END': tuya.enum(1)})], + [3, 'calibration', tuya.valueConverterBasic.lookup({START: tuya.enum(0), END: tuya.enum(1)})], [7, 'backlight_mode', tuya.valueConverter.onOff], - [8, 'motor_steering', tuya.valueConverterBasic.lookup({'FORWARD': tuya.enum(0), 'BACKWARD': tuya.enum(1)})], + [8, 'motor_steering', tuya.valueConverterBasic.lookup({FORWARD: tuya.enum(0), BACKWARD: tuya.enum(1)})], [101, 'state_l1', tuya.valueConverter.onOff], [103, 'child_lock', tuya.valueConverter.onOff], ], }, - whiteLabel: [ - tuya.whitelabel('Homeetec', '37022493', 'Curtain/blind switch with 1 Gang switch', ['_TZE200_jhkttplm']), - ], + whiteLabel: [tuya.whitelabel('Homeetec', '37022493', 'Curtain/blind switch with 1 Gang switch', ['_TZE200_jhkttplm'])], }, { - fingerprint: [ - {modelID: 'TS0601', manufacturerName: '_TZE200_5nldle7w'}, - ], + fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_5nldle7w'}], model: 'TS0601_cover_with_2_switch', vendor: 'Tuya', description: 'Curtain/blind switch with 2 Gang switch', @@ -7094,24 +8202,22 @@ const definitions: Definition[] = [ e.binary('child_lock', ea.STATE_SET, 'ON', 'OFF').withDescription('Child Lock'), ], endpoint: (device) => { - return {'l1': 1, 'l2': 1}; + return {l1: 1, l2: 1}; }, meta: { multiEndpoint: true, tuyaDatapoints: [ - [1, 'state', tuya.valueConverterBasic.lookup({'OPEN': tuya.enum(0), 'STOP': tuya.enum(1), 'CLOSE': tuya.enum(2)})], + [1, 'state', tuya.valueConverterBasic.lookup({OPEN: tuya.enum(0), STOP: tuya.enum(1), CLOSE: tuya.enum(2)})], [2, 'position', tuya.valueConverter.coverPosition], - [3, 'calibration', tuya.valueConverterBasic.lookup({'START': tuya.enum(0), 'END': tuya.enum(1)})], + [3, 'calibration', tuya.valueConverterBasic.lookup({START: tuya.enum(0), END: tuya.enum(1)})], [7, 'backlight_mode', tuya.valueConverter.onOff], - [8, 'motor_steering', tuya.valueConverterBasic.lookup({'FORWARD': tuya.enum(0), 'BACKWARD': tuya.enum(1)})], + [8, 'motor_steering', tuya.valueConverterBasic.lookup({FORWARD: tuya.enum(0), BACKWARD: tuya.enum(1)})], [101, 'state_l2', tuya.valueConverter.onOff], [102, 'state_l1', tuya.valueConverter.onOff], [103, 'child_lock', tuya.valueConverter.onOff], ], }, - whiteLabel: [ - tuya.whitelabel('Homeetec', '37022173', 'Curtain/blind switch with 2 Gang switch', ['_TZE200_5nldle7w']), - ], + whiteLabel: [tuya.whitelabel('Homeetec', '37022173', 'Curtain/blind switch with 2 Gang switch', ['_TZE200_5nldle7w'])], }, { fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_bcusnqt8'}], @@ -7121,7 +8227,10 @@ const definitions: Definition[] = [ fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, - exposes: [e.voltage(), e.power(), e.current(), + exposes: [ + e.voltage(), + e.power(), + e.current(), // Change the description according to the specifications of the device e.energy().withDescription('Total forward active energy'), e.produced_energy().withDescription('Total reverse active energy'), @@ -7130,15 +8239,19 @@ const definitions: Definition[] = [ tuyaDatapoints: [ [1, 'energy', tuya.valueConverter.divideBy100], [2, 'produced_energy', tuya.valueConverter.divideBy100], - [6, null, { - from: (v: Buffer) => { - return { - voltage: v.readUint16BE(0)/10, - current: ((v.readUint8(2)<<16)+(v.readUint8(3)<<8)+v.readUint8(4))/1000, - power: ((v.readUint8(5)<<16)+(v.readUint8(6)<<8)+v.readUint8(7)), - }; + [ + 6, + null, + { + from: (v: Buffer) => { + return { + voltage: v.readUint16BE(0) / 10, + current: ((v.readUint8(2) << 16) + (v.readUint8(3) << 8) + v.readUint8(4)) / 1000, + power: (v.readUint8(5) << 16) + (v.readUint8(6) << 8) + v.readUint8(7), + }; + }, }, - }], + ], [6, 'voltage', tuya.valueConverter.raw], [6, 'current', tuya.valueConverter.raw], [6, 'power', tuya.valueConverter.raw], @@ -7147,16 +8260,26 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE204_ves1ycwx'}, {modelID: 'TS0601', manufacturerName: '_TZE200_ves1ycwx'}], + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE204_ves1ycwx'}, + {modelID: 'TS0601', manufacturerName: '_TZE200_ves1ycwx'}, + ], model: 'SPM02', vendor: 'Tuya', description: 'Smart energy monitor for 3P+N system', fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, - exposes: [tuya.exposes.voltageWithPhase('X'), tuya.exposes.voltageWithPhase('Y'), tuya.exposes.voltageWithPhase('Z'), - tuya.exposes.powerWithPhase('X'), tuya.exposes.powerWithPhase('Y'), tuya.exposes.powerWithPhase('Z'), - tuya.exposes.currentWithPhase('X'), tuya.exposes.currentWithPhase('Y'), tuya.exposes.currentWithPhase('Z'), + exposes: [ + tuya.exposes.voltageWithPhase('X'), + tuya.exposes.voltageWithPhase('Y'), + tuya.exposes.voltageWithPhase('Z'), + tuya.exposes.powerWithPhase('X'), + tuya.exposes.powerWithPhase('Y'), + tuya.exposes.powerWithPhase('Z'), + tuya.exposes.currentWithPhase('X'), + tuya.exposes.currentWithPhase('Y'), + tuya.exposes.currentWithPhase('Z'), // Change the description according to the specifications of the device e.energy().withDescription('Total forward active energy'), e.produced_energy().withDescription('Total reverse active energy'), @@ -7173,17 +8296,24 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_qhlxve78'}, {modelID: 'TS0601', manufacturerName: '_TZE204_qhlxve78'}], + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE200_qhlxve78'}, + {modelID: 'TS0601', manufacturerName: '_TZE204_qhlxve78'}, + ], model: 'SPM01V2', vendor: 'Tuya', description: 'Smart energy monitor for 1P+N system', fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, - exposes: [e.voltage(), e.power(), e.current(), + exposes: [ + e.voltage(), + e.power(), + e.current(), e.energy().withDescription('Total forward active energy'), e.produced_energy().withDescription('Total reverse active energy'), - e.power_factor().withUnit('%'), e.ac_frequency(), + e.power_factor().withUnit('%'), + e.ac_frequency(), ], meta: { tuyaDatapoints: [ @@ -7200,19 +8330,30 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE204_v9hkz2yn'}, {modelID: 'TS0601', manufacturerName: '_TZE200_v9hkz2yn'}], + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE204_v9hkz2yn'}, + {modelID: 'TS0601', manufacturerName: '_TZE200_v9hkz2yn'}, + ], model: 'SPM02V2', vendor: 'Tuya', description: 'Smart energy monitor for 3P+N system', fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, - exposes: [tuya.exposes.voltageWithPhase('a'), tuya.exposes.voltageWithPhase('b'), tuya.exposes.voltageWithPhase('c'), - tuya.exposes.powerWithPhase('a'), tuya.exposes.powerWithPhase('b'), tuya.exposes.powerWithPhase('c'), - tuya.exposes.currentWithPhase('a'), tuya.exposes.currentWithPhase('b'), tuya.exposes.currentWithPhase('c'), + exposes: [ + tuya.exposes.voltageWithPhase('a'), + tuya.exposes.voltageWithPhase('b'), + tuya.exposes.voltageWithPhase('c'), + tuya.exposes.powerWithPhase('a'), + tuya.exposes.powerWithPhase('b'), + tuya.exposes.powerWithPhase('c'), + tuya.exposes.currentWithPhase('a'), + tuya.exposes.currentWithPhase('b'), + tuya.exposes.currentWithPhase('c'), e.energy().withDescription('Total forward active energy'), e.produced_energy().withDescription('Total reverse active energy'), - e.power_factor().withUnit('%'), e.power(), + e.power_factor().withUnit('%'), + e.power(), e.ac_frequency(), ], meta: { @@ -7238,24 +8379,42 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE204_ugekduaj'}, {modelID: 'TS0601', manufacturerName: '_TZE200_ugekduaj'}, - {modelID: 'TS0601', manufacturerName: '_TZE204_iwn0gpzz'}, {modelID: 'TS0601', manufacturerName: '_TZE200_iwn0gpzz'}], + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE204_ugekduaj'}, + {modelID: 'TS0601', manufacturerName: '_TZE200_ugekduaj'}, + {modelID: 'TS0601', manufacturerName: '_TZE204_iwn0gpzz'}, + {modelID: 'TS0601', manufacturerName: '_TZE200_iwn0gpzz'}, + ], model: 'SDM01', vendor: 'Tuya', description: 'Smart energy monitor for 3P+N system', fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, - exposes: [tuya.exposes.voltageWithPhase('a'), tuya.exposes.voltageWithPhase('b'), tuya.exposes.voltageWithPhase('c'), - tuya.exposes.powerWithPhase('a'), tuya.exposes.powerWithPhase('b'), tuya.exposes.powerWithPhase('c'), - tuya.exposes.currentWithPhase('a'), tuya.exposes.currentWithPhase('b'), tuya.exposes.currentWithPhase('c'), + exposes: [ + tuya.exposes.voltageWithPhase('a'), + tuya.exposes.voltageWithPhase('b'), + tuya.exposes.voltageWithPhase('c'), + tuya.exposes.powerWithPhase('a'), + tuya.exposes.powerWithPhase('b'), + tuya.exposes.powerWithPhase('c'), + tuya.exposes.currentWithPhase('a'), + tuya.exposes.currentWithPhase('b'), + tuya.exposes.currentWithPhase('c'), e.energy().withDescription('Total forward active energy'), e.produced_energy().withDescription('Total reverse active energy'), - e.power_factor().withUnit('%').withDescription('Total power factor'), e.power().withDescription('Total active power'), + e.power_factor().withUnit('%').withDescription('Total power factor'), + e.power().withDescription('Total active power'), e.ac_frequency(), - tuya.exposes.energyWithPhase('a'), tuya.exposes.energyWithPhase('b'), tuya.exposes.energyWithPhase('c'), - tuya.exposes.energyProducedWithPhase('a'), tuya.exposes.energyProducedWithPhase('b'), tuya.exposes.energyProducedWithPhase('c'), - tuya.exposes.powerFactorWithPhase('a'), tuya.exposes.powerFactorWithPhase('b'), tuya.exposes.powerFactorWithPhase('c'), + tuya.exposes.energyWithPhase('a'), + tuya.exposes.energyWithPhase('b'), + tuya.exposes.energyWithPhase('c'), + tuya.exposes.energyProducedWithPhase('a'), + tuya.exposes.energyProducedWithPhase('b'), + tuya.exposes.energyProducedWithPhase('c'), + tuya.exposes.powerFactorWithPhase('a'), + tuya.exposes.powerFactorWithPhase('b'), + tuya.exposes.powerFactorWithPhase('c'), ], meta: { tuyaDatapoints: [ @@ -7295,7 +8454,11 @@ const definitions: Definition[] = [ onEvent: tuya.onEventSetTime, configure: tuya.configureMagicPacket, exposes: [ - e.energy(), e.produced_energy(), e.power(), e.voltage(), e.current(), + e.energy(), + e.produced_energy(), + e.power(), + e.voltage(), + e.current(), e.enum('energy_flow', ea.STATE, ['consuming', 'producing']).withDescription('Direction of energy'), ], meta: { @@ -7303,7 +8466,7 @@ const definitions: Definition[] = [ [1, 'energy', tuya.valueConverter.divideBy100], [2, 'produced_energy', tuya.valueConverter.divideBy100], [6, null, tuya.valueConverter.phaseVariant3], - [102, 'energy_flow', tuya.valueConverterBasic.lookup({'consuming': 0, 'producing': 1})], + [102, 'energy_flow', tuya.valueConverterBasic.lookup({consuming: 0, producing: 1})], ], }, }, @@ -7329,7 +8492,7 @@ const definitions: Definition[] = [ tuya.exposes.switch().withEndpoint('l8'), ], endpoint: (device) => { - return {'l1': 1, 'l2': 1, 'l3': 1, 'l4': 1, 'l5': 1, 'l6': 1, 'l7': 1, 'l8': 1}; + return {l1: 1, l2: 1, l3: 1, l4: 1, l5: 1, l6: 1, l7: 1, l8: 1}; }, meta: { multiEndpoint: true, @@ -7346,9 +8509,7 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [ - {modelID: 'TS0601', manufacturerName: '_TZE204_adlblwab'}, - ], + fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE204_adlblwab'}], model: 'TS0601_switch_8_2', vendor: 'Tuya', description: '8 gang switch', @@ -7375,7 +8536,7 @@ const definitions: Definition[] = [ e.power_on_behavior().withAccess(ea.STATE_SET), ], endpoint: (device) => { - return {'l1': 1, 'l2': 1, 'l3': 1, 'l4': 1, 'l5': 1, 'l6': 1, 'l7': 1, 'l8': 1}; + return {l1: 1, l2: 1, l3: 1, l4: 1, l5: 1, l6: 1, l7: 1, l8: 1}; }, meta: { multiEndpoint: true, @@ -7410,7 +8571,7 @@ const definitions: Definition[] = [ configure: tuya.configureMagicPacket, exposes: [...Array.from({length: 10}, (_, i) => tuya.exposes.switch().withEndpoint(`l${i + 1}`))], endpoint: (device) => { - return {'l1': 1, 'l2': 1, 'l3': 1, 'l4': 1, 'l5': 1, 'l6': 1, 'l7': 1, 'l8': 1, 'l9': 1, 'l10': 1, 'l11': 1, 'l12': 1}; + return {l1: 1, l2: 1, l3: 1, l4: 1, l5: 1, l6: 1, l7: 1, l8: 1, l9: 1, l10: 1, l11: 1, l12: 1}; }, meta: { multiEndpoint: true, @@ -7438,7 +8599,7 @@ const definitions: Definition[] = [ configure: tuya.configureMagicPacket, exposes: [...Array.from({length: 12}, (_, i) => tuya.exposes.switch().withEndpoint(`l${i + 1}`))], endpoint: (device) => { - return {'l1': 1, 'l2': 1, 'l3': 1, 'l4': 1, 'l5': 1, 'l6': 1, 'l7': 1, 'l8': 1, 'l9': 1, 'l10': 1, 'l11': 1, 'l12': 1}; + return {l1: 1, l2: 1, l3: 1, l4: 1, l5: 1, l6: 1, l7: 1, l8: 1, l9: 1, l10: 1, l11: 1, l12: 1}; }, meta: { multiEndpoint: true, @@ -7454,7 +8615,7 @@ const definitions: Definition[] = [ [0x67, 'state_l9', tuya.valueConverter.onOff], [0x68, 'state_l10', tuya.valueConverter.onOff], [0x69, 'state_l11', tuya.valueConverter.onOff], - [0x6A, 'state_l12', tuya.valueConverter.onOff], + [0x6a, 'state_l12', tuya.valueConverter.onOff], ], }, }, @@ -7478,8 +8639,15 @@ const definitions: Definition[] = [ description: '2 gang plug', vendor: 'Tuya', ota: ota.zigbeeOTA, - extend: [tuya.modernExtend.tuyaOnOff({ - electricalMeasurements: true, powerOutageMemory: true, indicatorMode: true, childLock: true, endpoints: ['l1', 'l2']})], + extend: [ + tuya.modernExtend.tuyaOnOff({ + electricalMeasurements: true, + powerOutageMemory: true, + indicatorMode: true, + childLock: true, + endpoints: ['l1', 'l2'], + }), + ], endpoint: (device) => { return {l1: 1, l2: 2}; }, @@ -7493,9 +8661,7 @@ const definitions: Definition[] = [ }, options: [exposes.options.measurement_poll_interval()], onEvent: (type, data, device, options) => tuya.onEventMeasurementPoll(type, data, device, options, true, false), - whiteLabel: [ - tuya.whitelabel('Nous', 'A4Z', '2 gang outdoor plug', ['_TZ3000_rqbjepe8', '_TZ3000_uwkja6z1']), - ], + whiteLabel: [tuya.whitelabel('Nous', 'A4Z', '2 gang outdoor plug', ['_TZ3000_rqbjepe8', '_TZ3000_uwkja6z1'])], }, { fingerprint: tuya.fingerprint('TS011F', ['_TZ3000_cfnprab5', '_TZ3000_o005nuxx', '_TZ3000_gdyjfvgm']), @@ -7541,20 +8707,16 @@ const definitions: Definition[] = [ model: 'TS0001_fingerbot', vendor: 'Tuya', description: 'Zigbee fingerbot plus', - whiteLabel: [ - tuya.whitelabel('Adaprox', 'TS0001_fingerbot_1', 'Zigbee fingerbot plus', ['_TZ3210_dse8ogfy']), - ], + whiteLabel: [tuya.whitelabel('Adaprox', 'TS0001_fingerbot_1', 'Zigbee fingerbot plus', ['_TZ3210_dse8ogfy'])], fromZigbee: [fz.on_off, tuya.fz.datapoints], toZigbee: [tz.on_off, tuya.tz.datapoints], exposes: [ - e.switch(), e.battery(), + e.switch(), + e.battery(), e.enum('mode', ea.STATE_SET, ['click', 'switch', 'program']).withDescription('Working mode'), - e.numeric('lower', ea.STATE_SET).withValueMin(50).withValueMax(100).withValueStep(1).withUnit('%') - .withDescription('Down movement limit'), - e.numeric('upper', ea.STATE_SET).withValueMin(0).withValueMax(50).withValueStep(1).withUnit('%') - .withDescription('Up movement limit'), - e.numeric('delay', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(1).withUnit('s') - .withDescription('Sustain time'), + e.numeric('lower', ea.STATE_SET).withValueMin(50).withValueMax(100).withValueStep(1).withUnit('%').withDescription('Down movement limit'), + e.numeric('upper', ea.STATE_SET).withValueMin(0).withValueMax(50).withValueStep(1).withUnit('%').withDescription('Up movement limit'), + e.numeric('delay', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(1).withUnit('s').withDescription('Sustain time'), e.binary('reverse', ea.STATE_SET, 'ON', 'OFF').withDescription('Reverse'), e.binary('touch', ea.STATE_SET, 'ON', 'OFF').withDescription('Touch controll'), ], @@ -7565,13 +8727,13 @@ const definitions: Definition[] = [ meta: { tuyaSendCommand: 'sendData', tuyaDatapoints: [ - [0x65, 'mode', tuya.valueConverterBasic.lookup({'click': tuya.enum(0), 'switch': tuya.enum(1), 'program': tuya.enum(2)})], + [0x65, 'mode', tuya.valueConverterBasic.lookup({click: tuya.enum(0), switch: tuya.enum(1), program: tuya.enum(2)})], [0x66, 'lower', tuya.valueConverter.raw], [0x67, 'delay', tuya.valueConverter.raw], - [0x68, 'reverse', tuya.valueConverterBasic.lookup({'ON': tuya.enum(1), 'OFF': tuya.enum(0)})], + [0x68, 'reverse', tuya.valueConverterBasic.lookup({ON: tuya.enum(1), OFF: tuya.enum(0)})], [0x69, 'battery', tuya.valueConverter.raw], [0x6a, 'upper', tuya.valueConverter.raw], - [0x6b, 'touch', tuya.valueConverterBasic.lookup({'ON': true, 'OFF': false})], + [0x6b, 'touch', tuya.valueConverterBasic.lookup({ON: true, OFF: false})], // ? [0x6c, '', tuya.valueConverter.onOff], [0x6d, 'program', tuya.valueConverter.raw], // ? [0x70, '', tuya.valueConverter.raw], @@ -7593,10 +8755,12 @@ const definitions: Definition[] = [ e.switch().withEndpoint('l4'), e.switch().withEndpoint('l5'), e.switch().withEndpoint('l6'), - e.current(), e.power(), e.voltage(), + e.current(), + e.power(), + e.voltage(), ], endpoint: (device) => { - return {'l1': 1, 'l2': 1, 'l3': 1, 'l4': 1, 'l5': 1, 'l6': 1}; + return {l1: 1, l2: 1, l3: 1, l4: 1, l5: 1, l6: 1}; }, meta: { multiEndpoint: true, @@ -7640,7 +8804,7 @@ const definitions: Definition[] = [ ], onEvent: tuya.onEventSetTime, endpoint: (device) => { - return {'l1': 1, 'l2': 1, 'l3': 1, 'l4': 1, 'l5': 1, 'l6': 1, 'state': 1, 'backlight': 1}; + return {l1: 1, l2: 1, l3: 1, l4: 1, l5: 1, l6: 1, state: 1, backlight: 1}; }, meta: { multiEndpoint: true, @@ -7682,7 +8846,7 @@ const definitions: Definition[] = [ ], onEvent: tuya.onEventSetTime, endpoint: (device) => { - return {'l1': 1, 'l2': 1, 'state': 1, 'backlight': 1}; + return {l1: 1, l2: 1, state: 1, backlight: 1}; }, meta: { multiEndpoint: true, @@ -7718,7 +8882,7 @@ const definitions: Definition[] = [ ], onEvent: tuya.onEventSetTime, endpoint: (device) => { - return {'l1': 1, 'l2': 1, 'l3': 1, 'state': 1, 'backlight': 1}; + return {l1: 1, l2: 1, l3: 1, state: 1, backlight: 1}; }, meta: { multiEndpoint: true, @@ -7758,7 +8922,7 @@ const definitions: Definition[] = [ ], onEvent: tuya.onEventSetTime, endpoint: (device) => { - return {'l1': 1, 'l2': 1, 'l3': 1, 'l4': 1, 'state': 1, 'backlight': 1}; + return {l1: 1, l2: 1, l3: 1, l4: 1, state: 1, backlight: 1}; }, meta: { multiEndpoint: true, @@ -7796,7 +8960,7 @@ const definitions: Definition[] = [ new exposes.Numeric('delay', ea.STATE_SET).withUnit('sec').withDescription('light off delay').withValueMin(0).withValueMax(1000), ], endpoint: (device) => { - return {'l1': 1, 'l2': 1, 'l3': 1, 'l4': 1, 'l5': 1, 'l6': 1, 'state': 1, 'backlight': 1, 'l7': 1, 'l8': 1}; + return {l1: 1, l2: 1, l3: 1, l4: 1, l5: 1, l6: 1, state: 1, backlight: 1, l7: 1, l8: 1}; }, meta: { multiEndpoint: true, @@ -7855,19 +9019,34 @@ const definitions: Definition[] = [ fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], exposes: [ - e.illuminance_lux(), e.presence(), + e.illuminance_lux(), + e.presence(), e.numeric('target_distance', ea.STATE).withDescription('Distance to target').withUnit('m'), - e.numeric('radar_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(9).withValueStep(1) - .withDescription('Sensitivity of the radar'), - e.numeric('minimum_range', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(0.1) - .withDescription('Minimum range').withUnit('m'), - e.numeric('maximum_range', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(0.1) - .withDescription('Maximum range').withUnit('m'), - e.numeric('detection_delay', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(0.1) - .withDescription('Detection delay').withUnit('s'), - e.numeric('fading_time', ea.STATE_SET).withValueMin(0).withValueMax(1500).withValueStep(1) - .withDescription('Fading time').withUnit('s'), - e.enum('radar_scene', ea.STATE_SET, ['default', 'bathroom', 'bedroom', 'sleeping', 'unknown']) + e.numeric('radar_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(9).withValueStep(1).withDescription('Sensitivity of the radar'), + e + .numeric('minimum_range', ea.STATE_SET) + .withValueMin(0) + .withValueMax(10) + .withValueStep(0.1) + .withDescription('Minimum range') + .withUnit('m'), + e + .numeric('maximum_range', ea.STATE_SET) + .withValueMin(0) + .withValueMax(10) + .withValueStep(0.1) + .withDescription('Maximum range') + .withUnit('m'), + e + .numeric('detection_delay', ea.STATE_SET) + .withValueMin(0) + .withValueMax(10) + .withValueStep(0.1) + .withDescription('Detection delay') + .withUnit('s'), + e.numeric('fading_time', ea.STATE_SET).withValueMin(0).withValueMax(1500).withValueStep(1).withDescription('Fading time').withUnit('s'), + e + .enum('radar_scene', ea.STATE_SET, ['default', 'bathroom', 'bedroom', 'sleeping', 'unknown']) .withDescription('Presets for sensitivity for presence and movement'), ], meta: { @@ -7879,13 +9058,17 @@ const definitions: Definition[] = [ [0x65, 'illuminance_lux', tuya.valueConverter.raw], [0x66, 'detection_delay', tuya.valueConverter.divideBy10], [0x67, 'fading_time', tuya.valueConverter.divideBy10], - [0x68, 'radar_scene', tuya.valueConverterBasic.lookup({ - 'default': tuya.enum(0), - 'bathroom': tuya.enum(1), - 'bedroom': tuya.enum(2), - 'sleeping': tuya.enum(3), - 'unknown': tuya.enum(4), - })], + [ + 0x68, + 'radar_scene', + tuya.valueConverterBasic.lookup({ + default: tuya.enum(0), + bathroom: tuya.enum(1), + bedroom: tuya.enum(2), + sleeping: tuya.enum(3), + unknown: tuya.enum(4), + }), + ], [0x69, 'target_distance', tuya.valueConverter.divideBy100], ], }, @@ -7923,33 +9106,88 @@ const definitions: Definition[] = [ e.binary('alarm', ea.STATE_SET, 'ON', 'OFF').withDescription('Turn the light of the alarm ON/OFF'), e.enum('type', ea.STATE_SET, ['sound', 'light', 'sound+light', 'normal']).withDescription('Alarm type'), e.enum('volume', ea.STATE_SET, ['mute', 'low', 'middle', 'high']).withDescription('Volume of the alarm'), - e.enum('ringtone', ea.STATE_SET, [ - 'melody1', 'melody2', 'melody3', 'melody4', 'melody5', 'melody6', 'melody7', 'melody8', - 'door', 'water', 'temperature', 'entered', 'left', - ]).withDescription('Ringtone of the alarm'), + e + .enum('ringtone', ea.STATE_SET, [ + 'melody1', + 'melody2', + 'melody3', + 'melody4', + 'melody5', + 'melody6', + 'melody7', + 'melody8', + 'door', + 'water', + 'temperature', + 'entered', + 'left', + ]) + .withDescription('Ringtone of the alarm'), e.enum('power_type', ea.STATE, ['battery', 'cable']).withDescription('Power type'), - e.numeric('duration', ea.STATE_SET).withValueMin(1).withValueMax(60).withValueStep(1) - .withUnit('min').withDescription('Duration of the alarm'), + e + .numeric('duration', ea.STATE_SET) + .withValueMin(1) + .withValueMax(60) + .withValueStep(1) + .withUnit('min') + .withDescription('Duration of the alarm'), e.enum('battery_level', ea.STATE, ['low', 'middle', 'high']).withDescription('Battery level state'), e.battery(), ], meta: { tuyaDatapoints: [ - [1, 'type', tuya.valueConverterBasic.lookup({ - 'sound': tuya.enum(0), 'light': tuya.enum(1), 'sound+light': tuya.enum(2), 'normal': tuya.enum(3)})], - [5, 'volume', tuya.valueConverterBasic.lookup({ - 'low': tuya.enum(0), 'middle': tuya.enum(1), 'high': tuya.enum(2), 'mute': tuya.enum(3)})], - [6, 'power_type', tuya.valueConverterBasic.lookup({'cable': false, 'battery': true})], + [ + 1, + 'type', + tuya.valueConverterBasic.lookup({ + sound: tuya.enum(0), + light: tuya.enum(1), + 'sound+light': tuya.enum(2), + normal: tuya.enum(3), + }), + ], + [ + 5, + 'volume', + tuya.valueConverterBasic.lookup({ + low: tuya.enum(0), + middle: tuya.enum(1), + high: tuya.enum(2), + mute: tuya.enum(3), + }), + ], + [6, 'power_type', tuya.valueConverterBasic.lookup({cable: false, battery: true})], [7, 'duration', tuya.valueConverter.raw], [13, 'alarm', tuya.valueConverter.onOff], - [14, 'battery_level', tuya.valueConverterBasic.lookup({ - 'low': tuya.enum(0), 'middle': tuya.enum(1), 'high': tuya.enum(2)})], + [ + 14, + 'battery_level', + tuya.valueConverterBasic.lookup({ + low: tuya.enum(0), + middle: tuya.enum(1), + high: tuya.enum(2), + }), + ], [15, 'battery', tuya.valueConverter.raw], - [21, 'ringtone', tuya.valueConverterBasic.lookup({ - 'melody1': tuya.enum(0), 'melody2': tuya.enum(1), 'melody3': tuya.enum(2), 'melody4': tuya.enum(3), - 'melody5': tuya.enum(4), 'melody6': tuya.enum(5), 'melody7': tuya.enum(6), 'melody8': tuya.enum(7), - 'door': tuya.enum(8), 'water': tuya.enum(9), 'temperature': tuya.enum(10), 'entered': tuya.enum(11), 'left': tuya.enum(12), - })], + [ + 21, + 'ringtone', + tuya.valueConverterBasic.lookup({ + melody1: tuya.enum(0), + melody2: tuya.enum(1), + melody3: tuya.enum(2), + melody4: tuya.enum(3), + melody5: tuya.enum(4), + melody6: tuya.enum(5), + melody7: tuya.enum(6), + melody8: tuya.enum(7), + door: tuya.enum(8), + water: tuya.enum(9), + temperature: tuya.enum(10), + entered: tuya.enum(11), + left: tuya.enum(12), + }), + ], ], }, }, @@ -7964,8 +9202,30 @@ const definitions: Definition[] = [ exposes: [...Array.from(Array(24).keys()).map((ep) => tuya.exposes.switch().withEndpoint(`l${ep + 1}`))], endpoint: (device) => { return { - 'l1': 1, 'l2': 1, 'l3': 1, 'l4': 1, 'l5': 1, 'l6': 1, 'l7': 1, 'l8': 1, 'l9': 1, 'l10': 1, 'l11': 1, 'l12': 1, - 'l13': 1, 'l14': 1, 'l15': 1, 'l16': 1, 'l17': 1, 'l18': 1, 'l19': 1, 'l20': 1, 'l21': 1, 'l22': 1, 'l23': 1, 'l24': 1, + l1: 1, + l2: 1, + l3: 1, + l4: 1, + l5: 1, + l6: 1, + l7: 1, + l8: 1, + l9: 1, + l10: 1, + l11: 1, + l12: 1, + l13: 1, + l14: 1, + l15: 1, + l16: 1, + l17: 1, + l18: 1, + l19: 1, + l20: 1, + l21: 1, + l22: 1, + l23: 1, + l24: 1, }; }, meta: { @@ -7982,12 +9242,12 @@ const definitions: Definition[] = [ [0x67, 'state_l9', tuya.valueConverter.onOff], [0x68, 'state_l10', tuya.valueConverter.onOff], [0x69, 'state_l11', tuya.valueConverter.onOff], - [0x6A, 'state_l12', tuya.valueConverter.onOff], - [0x6B, 'state_l13', tuya.valueConverter.onOff], - [0x6C, 'state_l14', tuya.valueConverter.onOff], - [0x6D, 'state_l15', tuya.valueConverter.onOff], - [0x6E, 'state_l16', tuya.valueConverter.onOff], - [0x6F, 'state_l17', tuya.valueConverter.onOff], + [0x6a, 'state_l12', tuya.valueConverter.onOff], + [0x6b, 'state_l13', tuya.valueConverter.onOff], + [0x6c, 'state_l14', tuya.valueConverter.onOff], + [0x6d, 'state_l15', tuya.valueConverter.onOff], + [0x6e, 'state_l16', tuya.valueConverter.onOff], + [0x6f, 'state_l17', tuya.valueConverter.onOff], [0x70, 'state_l18', tuya.valueConverter.onOff], [0x71, 'state_l19', tuya.valueConverter.onOff], [0x72, 'state_l20', tuya.valueConverter.onOff], @@ -8007,27 +9267,26 @@ const definitions: Definition[] = [ toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, exposes: [ - e.enum('state', ea.STATE, ['none', 'presence', 'move']) - .withDescription('Presence state sensor'), + e.enum('state', ea.STATE, ['none', 'presence', 'move']).withDescription('Presence state sensor'), e.presence().withDescription('Occupancy'), e.numeric('distance', ea.STATE).withDescription('Target distance'), e.illuminance_lux().withDescription('Illuminance sensor'), - e.numeric('move_sensitivity', ea.STATE_SET).withValueMin(1) - .withValueMax(10) - .withValueStep(1) - .withDescription('Motion sensitivity'), - e.numeric('presence_sensitivity', ea.STATE_SET).withValueMin(1) - .withValueMax(10) - .withValueStep(1) - .withDescription('Presence sensitivity'), - e.numeric('radar_range', ea.STATE_SET).withValueMin(1.5) + e.numeric('move_sensitivity', ea.STATE_SET).withValueMin(1).withValueMax(10).withValueStep(1).withDescription('Motion sensitivity'), + e.numeric('presence_sensitivity', ea.STATE_SET).withValueMin(1).withValueMax(10).withValueStep(1).withDescription('Presence sensitivity'), + e + .numeric('radar_range', ea.STATE_SET) + .withValueMin(1.5) .withValueMax(5.5) .withValueStep(1) - .withUnit('m').withDescription('Maximum range'), - e.numeric('presence_timeout', ea.STATE_SET).withValueMin(1) + .withUnit('m') + .withDescription('Maximum range'), + e + .numeric('presence_timeout', ea.STATE_SET) + .withValueMin(1) .withValueMax(1500) .withValueStep(1) - .withUnit('s').withDescription('Fade time'), + .withUnit('s') + .withDescription('Fade time'), ], meta: { multiEndpoint: true, @@ -8041,7 +9300,7 @@ const definitions: Definition[] = [ [104, 'illuminance_lux', tuya.valueConverter.raw], [102, 'illuminance_treshold_max', tuya.valueConverter.raw], [103, 'illuminance_treshold_min', tuya.valueConverter.raw], - [105, 'state', tuya.valueConverterBasic.lookup({'none': 0, 'presence': 1, 'move': 2})], + [105, 'state', tuya.valueConverterBasic.lookup({none: 0, presence: 1, move: 2})], ], }, }, @@ -8054,31 +9313,33 @@ const definitions: Definition[] = [ toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, exposes: [ - e.enum('state', ea.STATE, ['none', 'presence', 'move']) - .withDescription('Presence state sensor'), + e.enum('state', ea.STATE, ['none', 'presence', 'move']).withDescription('Presence state sensor'), e.presence().withDescription('Occupancy'), e.numeric('distance', ea.STATE).withDescription('Target distance'), e.illuminance_lux().withDescription('Illuminance sensor'), - e.numeric('move_sensitivity', ea.STATE_SET).withValueMin(1) - .withValueMax(10) - .withValueStep(1) - .withDescription('Motion Sensitivity'), - e.numeric('presence_sensitivity', ea.STATE_SET).withValueMin(1) - .withValueMax(10) - .withValueStep(1) - .withDescription('Presence Sensitivity'), - e.numeric('detection_distance_min', ea.STATE_SET).withValueMin(0) + e.numeric('move_sensitivity', ea.STATE_SET).withValueMin(1).withValueMax(10).withValueStep(1).withDescription('Motion Sensitivity'), + e.numeric('presence_sensitivity', ea.STATE_SET).withValueMin(1).withValueMax(10).withValueStep(1).withDescription('Presence Sensitivity'), + e + .numeric('detection_distance_min', ea.STATE_SET) + .withValueMin(0) .withValueMax(8.25) .withValueStep(0.75) - .withUnit('m').withDescription('Minimum range'), - e.numeric('detection_distance_max', ea.STATE_SET).withValueMin(0.75) - .withValueMax(9.00) + .withUnit('m') + .withDescription('Minimum range'), + e + .numeric('detection_distance_max', ea.STATE_SET) + .withValueMin(0.75) + .withValueMax(9.0) .withValueStep(0.75) - .withUnit('m').withDescription('Maximum range'), - e.numeric('presence_timeout', ea.STATE_SET).withValueMin(1) + .withUnit('m') + .withDescription('Maximum range'), + e + .numeric('presence_timeout', ea.STATE_SET) + .withValueMin(1) .withValueMax(1500) .withValueStep(1) - .withUnit('s').withDescription('Fade time'), + .withUnit('s') + .withDescription('Fade time'), ], meta: { tuyaDatapoints: [ @@ -8090,7 +9351,7 @@ const definitions: Definition[] = [ [9, 'distance', tuya.valueConverter.divideBy100], [105, 'presence_timeout', tuya.valueConverter.raw], [103, 'illuminance_lux', tuya.valueConverter.raw], - [1, 'state', tuya.valueConverterBasic.lookup({'none': 0, 'presence': 1, 'move': 2})], + [1, 'state', tuya.valueConverterBasic.lookup({none: 0, presence: 1, move: 2})], ], }, }, @@ -8102,26 +9363,56 @@ const definitions: Definition[] = [ fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], exposes: [ - e.illuminance().withUnit('lx'), e.presence(), - e.numeric('presence_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(100).withValueStep(1).withUnit('%') + e.illuminance().withUnit('lx'), + e.presence(), + e + .numeric('presence_sensitivity', ea.STATE_SET) + .withValueMin(0) + .withValueMax(100) + .withValueStep(1) + .withUnit('%') .withDescription('Presence sensitivity'), - e.numeric('detection_range', ea.STATE_SET).withValueMin(1.5).withValueMax(4.5).withValueStep(0.1).withUnit('m') + e + .numeric('detection_range', ea.STATE_SET) + .withValueMin(1.5) + .withValueMax(4.5) + .withValueStep(0.1) + .withUnit('m') .withDescription('Detection range'), - e.numeric('detection_delay', ea.STATE_SET).withValueMin(1).withValueMax(600).withValueStep(1).withUnit('s') + e + .numeric('detection_delay', ea.STATE_SET) + .withValueMin(1) + .withValueMax(600) + .withValueStep(1) + .withUnit('s') .withDescription('Presence detection delay'), - e.numeric('illuminance_treshold_max', ea.STATE_SET).withValueMin(0).withValueMax(2000).withValueStep(1).withUnit('lx') + e + .numeric('illuminance_treshold_max', ea.STATE_SET) + .withValueMin(0) + .withValueMax(2000) + .withValueStep(1) + .withUnit('lx') .withDescription('The max illumiance threshold to turn on the light'), - e.numeric('illuminance_treshold_min', ea.STATE_SET).withValueMin(0).withValueMax(2000).withValueStep(1).withUnit('lx') + e + .numeric('illuminance_treshold_min', ea.STATE_SET) + .withValueMin(0) + .withValueMax(2000) + .withValueStep(1) + .withUnit('lx') .withDescription('The min illumiance threshold to turn on the light'), - e.binary('presence_illuminance_switch', ea.STATE_SET, true, false).withDescription( - `Whether to enable 'light_switch' illumination is between min/max threshold`), - e.binary('light_switch', ea.STATE, 'ON', 'OFF').withDescription( - 'This state will determine the light on/off based on the lighting threshold and presence sensing'), + e + .binary('presence_illuminance_switch', ea.STATE_SET, true, false) + .withDescription(`Whether to enable 'light_switch' illumination is between min/max threshold`), + e + .binary('light_switch', ea.STATE, 'ON', 'OFF') + .withDescription('This state will determine the light on/off based on the lighting threshold and presence sensing'), e.binary('light_linkage', ea.STATE_SET, true, false).withDescription('Light linkage'), - e.enum('detection_method', ea.STATE_SET, ['only_move', 'exist_move']).withDescription( - `When 'only_move' is used, presence will only be triggered when there is movement`), + e + .enum('detection_method', ea.STATE_SET, ['only_move', 'exist_move']) + .withDescription(`When 'only_move' is used, presence will only be triggered when there is movement`), e.enum('indicator_light', ea.STATE_SET, ['presence', 'off', 'on']).withDescription('Controls when the indicator light is turned on'), - e.binary('identify', ea.STATE_SET, true, false) + e + .binary('identify', ea.STATE_SET, true, false) .withDescription('After turning on, the indicator light quickly flashes, used to locate devices'), ], meta: { @@ -8136,15 +9427,23 @@ const definitions: Definition[] = [ [109, 'presence_illuminance_switch', tuya.valueConverter.trueFalseEnum1], [105, 'light_switch', tuya.valueConverter.onOff], [106, 'light_linkage', tuya.valueConverter.trueFalseEnum1], - [107, 'indicator_light', tuya.valueConverterBasic.lookup({'presence': tuya.enum(0), 'off': tuya.enum(1), 'on': tuya.enum(2)})], - [108, 'detection_method', tuya.valueConverterBasic.lookup({'only_move': tuya.enum(0), 'exist_move': tuya.enum(1)})], + [107, 'indicator_light', tuya.valueConverterBasic.lookup({presence: tuya.enum(0), off: tuya.enum(1), on: tuya.enum(2)})], + [108, 'detection_method', tuya.valueConverterBasic.lookup({only_move: tuya.enum(0), exist_move: tuya.enum(1)})], [113, 'find_switch', tuya.valueConverter.raw], ], }, }, { - fingerprint: tuya.fingerprint('TS0601', ['_TZE204_sbyx0lm6', '_TZE204_clrdrnya', '_TZE204_dtzziy1e', '_TZE204_iaeejhvf', '_TZE204_mtoaryre', - '_TZE200_mp902om5', '_TZE204_pfayrzcw', '_TZE284_4qznlkbu']), + fingerprint: tuya.fingerprint('TS0601', [ + '_TZE204_sbyx0lm6', + '_TZE204_clrdrnya', + '_TZE204_dtzziy1e', + '_TZE204_iaeejhvf', + '_TZE204_mtoaryre', + '_TZE200_mp902om5', + '_TZE204_pfayrzcw', + '_TZE284_4qznlkbu', + ]), model: 'MTG075-ZB-RL', vendor: 'Tuya', description: '2.4G/5.8G human presence sensor with relay', @@ -8157,36 +9456,82 @@ const definitions: Definition[] = [ fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], exposes: [ - e.presence(), e.illuminance_lux(), + e.presence(), + e.illuminance_lux(), e.numeric('target_distance', ea.STATE).withDescription('Distance to target').withUnit('m'), - e.numeric('radar_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(9).withValueStep(1) + e + .numeric('radar_sensitivity', ea.STATE_SET) + .withValueMin(0) + .withValueMax(9) + .withValueStep(1) .withDescription('Detection threshold for the strength of object energy'), - e.numeric('detection_range', ea.STATE_SET).withValueMin(0).withValueMax(8).withValueStep(0.1).withUnit('m') + e + .numeric('detection_range', ea.STATE_SET) + .withValueMin(0) + .withValueMax(8) + .withValueStep(0.1) + .withUnit('m') .withDescription('Maximum distance detected by the sensor'), - e.numeric('shield_range', ea.STATE_SET).withValueMin(0).withValueMax(8).withValueStep(0.1).withUnit('m') + e + .numeric('shield_range', ea.STATE_SET) + .withValueMin(0) + .withValueMax(8) + .withValueStep(0.1) + .withUnit('m') .withDescription('Nearest distance detected by the sensor'), - e.numeric('entry_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(9).withValueStep(1) + e + .numeric('entry_sensitivity', ea.STATE_SET) + .withValueMin(0) + .withValueMax(9) + .withValueStep(1) .withDescription('Sensitivity threshold triggered for the first time when the target enters the detection range'), - e.numeric('entry_distance_indentation', ea.STATE_SET).withValueMin(0).withValueMax(8).withValueStep(0.1).withUnit('m') + e + .numeric('entry_distance_indentation', ea.STATE_SET) + .withValueMin(0) + .withValueMax(8) + .withValueStep(0.1) + .withUnit('m') .withDescription('Indent the distance inward based on the dectection distance'), - e.numeric('entry_filter_time', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(0.1).withUnit('s') + e + .numeric('entry_filter_time', ea.STATE_SET) + .withValueMin(0) + .withValueMax(10) + .withValueStep(0.1) + .withUnit('s') .withDescription('Sensitivity threshold triggered for the first time when the target enters the detection range '), - e.numeric('departure_delay', ea.STATE_SET).withValueMin(0).withValueMax(600).withValueStep(1).withUnit('s'). - withDescription('Confirmation time after the target disappears'), - e.numeric('block_time', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(0.1).withUnit('s') + e + .numeric('departure_delay', ea.STATE_SET) + .withValueMin(0) + .withValueMax(600) + .withValueStep(1) + .withUnit('s') + .withDescription('Confirmation time after the target disappears'), + e + .numeric('block_time', ea.STATE_SET) + .withValueMin(0) + .withValueMax(10) + .withValueStep(0.1) + .withUnit('s') .withDescription('Time for the target to be detected again after switching from manned(occupy) to unmanned(unoccupy) mode'), - e.binary('breaker_status', ea.STATE_SET, 'ON', 'OFF') - .withDescription('Remotely control the breaker in standard mode'), - e.enum('breaker_mode', ea.STATE_SET, ['standard', 'local']) + e.binary('breaker_status', ea.STATE_SET, 'ON', 'OFF').withDescription('Remotely control the breaker in standard mode'), + e + .enum('breaker_mode', ea.STATE_SET, ['standard', 'local']) .withDescription('Breaker mode: standard is remotely controlled, local is automatic'), - e.numeric('illuminance_threshold', ea.STATE_SET).withValueMin(0).withValueMax(420).withValueStep(0.1).withUnit('lx') + e + .numeric('illuminance_threshold', ea.STATE_SET) + .withValueMin(0) + .withValueMax(420) + .withValueStep(0.1) + .withUnit('lx') .withDescription('Illumination threshold for local (automatic) switching mode operation'), - e.enum('status_indication', ea.STATE_SET, ['OFF', 'ON']) - .withDescription('Indicator light will flash when human presence is detected'), - e.enum('sensor', ea.STATE_SET, ['on', 'off', 'occupied', 'unoccupied']) - .withDescription(`The radar sensor can be set in four states: on, off, occupied and unoccupied. For example, if set to occupied, ` + - `it will continue to maintain presence regardless of whether someone is present or not. If set to unoccupied, the unoccupied ` + - `state will be maintained permanently.`), + e.enum('status_indication', ea.STATE_SET, ['OFF', 'ON']).withDescription('Indicator light will flash when human presence is detected'), + e + .enum('sensor', ea.STATE_SET, ['on', 'off', 'occupied', 'unoccupied']) + .withDescription( + `The radar sensor can be set in four states: on, off, occupied and unoccupied. For example, if set to occupied, ` + + `it will continue to maintain presence regardless of whether someone is present or not. If set to unoccupied, the unoccupied ` + + `state will be maintained permanently.`, + ), ], meta: { tuyaDatapoints: [ @@ -8202,16 +9547,24 @@ const definitions: Definition[] = [ [104, 'illuminance_lux', tuya.valueConverter.divideBy10], [105, 'entry_sensitivity', tuya.valueConverter.raw], [106, 'entry_distance_indentation', tuya.valueConverter.divideBy100], - [107, 'breaker_mode', tuya.valueConverterBasic.lookup({'standard': tuya.enum(0), 'local': tuya.enum(1)})], - [108, 'breaker_status', tuya.valueConverterBasic.lookup({'OFF': tuya.enum(0), 'ON': tuya.enum(1)})], - [109, 'status_indication', tuya.valueConverterBasic.lookup({'OFF': tuya.enum(0), 'ON': tuya.enum(1)})], + [107, 'breaker_mode', tuya.valueConverterBasic.lookup({standard: tuya.enum(0), local: tuya.enum(1)})], + [108, 'breaker_status', tuya.valueConverterBasic.lookup({OFF: tuya.enum(0), ON: tuya.enum(1)})], + [109, 'status_indication', tuya.valueConverterBasic.lookup({OFF: tuya.enum(0), ON: tuya.enum(1)})], [110, 'illuminance_threshold', tuya.valueConverter.divideBy10], - [111, 'breaker_polarity', tuya.valueConverterBasic.lookup({'NC': tuya.enum(0), 'NO': tuya.enum(1)})], + [111, 'breaker_polarity', tuya.valueConverterBasic.lookup({NC: tuya.enum(0), NO: tuya.enum(1)})], [112, 'block_time', tuya.valueConverter.divideBy10], [113, 'parameter_setting_result', tuya.valueConverter.raw], [114, 'factory_parameters', tuya.valueConverter.raw], - [115, 'sensor', tuya.valueConverterBasic.lookup({ - 'on': tuya.enum(0), 'off': tuya.enum(1), 'occupied': tuya.enum(2), 'unoccupied': tuya.enum(3)})], + [ + 115, + 'sensor', + tuya.valueConverterBasic.lookup({ + on: tuya.enum(0), + off: tuya.enum(1), + occupied: tuya.enum(2), + unoccupied: tuya.enum(3), + }), + ], ], }, }, @@ -8225,24 +9578,39 @@ const definitions: Definition[] = [ onEvent: tuya.onEventSetTime, configure: tuya.configureMagicPacket, options: [ - e.binary('late_energy_flow_a', ea.SET, true, false ) + e + .binary('late_energy_flow_a', ea.SET, true, false) .withDescription(`Delay channel A publication until the next energy flow update (default false).`), - e.binary('late_energy_flow_b', ea.SET, true, false ) + e + .binary('late_energy_flow_b', ea.SET, true, false) .withDescription(`Delay channel B publication until the next energy flow update (default false).`), - e.binary('signed_power_a', ea.SET, true, false ) + e + .binary('signed_power_a', ea.SET, true, false) .withDescription(`Report energy flow direction for channel A using signed power (default false).`), - e.binary('signed_power_b', ea.SET, true, false ) + e + .binary('signed_power_b', ea.SET, true, false) .withDescription(`Report energy flow direction for channel B using signed power (default false).`), ], exposes: [ - e.ac_frequency(), e.voltage(), - tuya.exposes.powerWithPhase('a'), tuya.exposes.powerWithPhase('b'), tuya.exposes.powerWithPhase('ab'), - tuya.exposes.currentWithPhase('a'), tuya.exposes.currentWithPhase('b'), - tuya.exposes.powerFactorWithPhase('a'), tuya.exposes.powerFactorWithPhase('b'), - tuya.exposes.energyFlowWithPhase('a', ['sign']), tuya.exposes.energyFlowWithPhase('b', ['sign']), - tuya.exposes.energyWithPhase('a'), tuya.exposes.energyWithPhase('b'), - tuya.exposes.energyProducedWithPhase('a'), tuya.exposes.energyProducedWithPhase('b'), - e.numeric('update_frequency', ea.STATE_SET).withUnit('s').withDescription('Update frequency') + e.ac_frequency(), + e.voltage(), + tuya.exposes.powerWithPhase('a'), + tuya.exposes.powerWithPhase('b'), + tuya.exposes.powerWithPhase('ab'), + tuya.exposes.currentWithPhase('a'), + tuya.exposes.currentWithPhase('b'), + tuya.exposes.powerFactorWithPhase('a'), + tuya.exposes.powerFactorWithPhase('b'), + tuya.exposes.energyFlowWithPhase('a', ['sign']), + tuya.exposes.energyFlowWithPhase('b', ['sign']), + tuya.exposes.energyWithPhase('a'), + tuya.exposes.energyWithPhase('b'), + tuya.exposes.energyProducedWithPhase('a'), + tuya.exposes.energyProducedWithPhase('b'), + e + .numeric('update_frequency', ea.STATE_SET) + .withUnit('s') + .withDescription('Update frequency') .withValueMin(3) .withValueMax(60) .withPreset('default', 10, 'Default value'), @@ -8285,12 +9653,22 @@ const definitions: Definition[] = [ onEvent: tuya.onEventSetTime, configure: tuya.configureMagicPacket, exposes: [ - e.ac_frequency(), e.voltage(), e.power(), e.current(), e.energy(), e.energy_produced(), - tuya.exposes.powerWithPhase('a'), tuya.exposes.powerWithPhase('b'), - tuya.exposes.currentWithPhase('a'), tuya.exposes.currentWithPhase('b'), - tuya.exposes.powerFactorWithPhase('a'), tuya.exposes.powerFactorWithPhase('b'), - tuya.exposes.energyWithPhase('a'), tuya.exposes.energyWithPhase('b'), - tuya.exposes.energyProducedWithPhase('a'), tuya.exposes.energyProducedWithPhase('b'), + e.ac_frequency(), + e.voltage(), + e.power(), + e.current(), + e.energy(), + e.energy_produced(), + tuya.exposes.powerWithPhase('a'), + tuya.exposes.powerWithPhase('b'), + tuya.exposes.currentWithPhase('a'), + tuya.exposes.currentWithPhase('b'), + tuya.exposes.powerFactorWithPhase('a'), + tuya.exposes.powerFactorWithPhase('b'), + tuya.exposes.energyWithPhase('a'), + tuya.exposes.energyWithPhase('b'), + tuya.exposes.energyProducedWithPhase('a'), + tuya.exposes.energyProducedWithPhase('b'), ], meta: { multiEndpointSkip: ['power_factor', 'power_factor_phase_b', 'power_factor_phase_c', 'energy'], @@ -8326,7 +9704,8 @@ const definitions: Definition[] = [ exposes: [ e.binary('state', ea.STATE_SET, 'ON', 'OFF').withDescription('Turn the thermostat ON/OFF'), e.child_lock(), - e.climate() + e + .climate() .withLocalTemperature(ea.STATE) .withSetpoint('current_heating_setpoint', 5, 35, 1, ea.STATE_SET) .withSystemMode(['cool', 'heat', 'fan_only'], ea.STATE_SET) @@ -8343,19 +9722,17 @@ const definitions: Definition[] = [ ...tuya.exposes.scheduleAllDays(ea.STATE_SET, 'HH:MM/C HH:MM/C HH:MM/C HH:MM/C HH:MM/C HH:MM/C'), ], meta: { - tuyaDatapoints: - [ + tuyaDatapoints: [ [1, 'state', tuya.valueConverter.onOff], - [2, 'system_mode', tuya.valueConverterBasic.lookup({'cool': tuya.enum(0), 'heat': tuya.enum(1), 'fan_only': tuya.enum(2)})], + [2, 'system_mode', tuya.valueConverterBasic.lookup({cool: tuya.enum(0), heat: tuya.enum(1), fan_only: tuya.enum(2)})], [4, 'eco_mode', tuya.valueConverter.onOff], [16, 'current_heating_setpoint', tuya.valueConverter.divideBy10], [19, 'max_temperature', tuya.valueConverter.divideBy10], [24, 'local_temperature', tuya.valueConverter.divideBy10], [26, 'min_temperature', tuya.valueConverter.divideBy10], [27, 'local_temperature_calibration', tuya.valueConverter.localTemperatureCalibration], - [28, 'fan_mode', tuya.valueConverterBasic.lookup( - {'low': tuya.enum(0), 'medium': tuya.enum(1), 'high': tuya.enum(2), 'auto': tuya.enum(3)})], - [36, 'valve', tuya.valueConverterBasic.lookup({'OPEN': 0, 'CLOSE': 1})], + [28, 'fan_mode', tuya.valueConverterBasic.lookup({low: tuya.enum(0), medium: tuya.enum(1), high: tuya.enum(2), auto: tuya.enum(3)})], + [36, 'valve', tuya.valueConverterBasic.lookup({OPEN: 0, CLOSE: 1})], [40, 'child_lock', tuya.valueConverter.lockUnlock], [103, 'deadzone_temperature', tuya.valueConverter.raw], [104, 'min_temperature_limit', tuya.valueConverter.divideBy10], @@ -8381,19 +9758,44 @@ const definitions: Definition[] = [ toZigbee: [tuya.tz.datapoints], onEvent: tuya.onEventSetTime, exposes: [ - e.presence(), e.illuminance().withUnit('lx'), - e.numeric('detection_delay', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(0.1) - .withDescription('Detection delay').withUnit('s'), - e.numeric('detection_distance', ea.STATE).withValueMin(0).withValueMax(1000).withValueStep(1) - .withDescription('Distance of detected person').withUnit('cm'), - e.numeric('sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(1) - .withDescription('Detection sensitivity'), - e.numeric('keep_time', ea.STATE_SET).withValueMin(5).withValueMax(3600).withValueStep(1) - .withDescription('Detection keep time').withUnit('s'), - e.numeric('minimum_range', ea.STATE_SET).withValueMin(0).withValueMax(1000).withValueStep(50) - .withDescription('Minimum detection range').withUnit('m'), - e.numeric('maximum_range', ea.STATE_SET).withValueMin(50).withValueMax(1000).withValueStep(50) - .withDescription('Maximum detection range').withUnit('m'), + e.presence(), + e.illuminance().withUnit('lx'), + e + .numeric('detection_delay', ea.STATE_SET) + .withValueMin(0) + .withValueMax(10) + .withValueStep(0.1) + .withDescription('Detection delay') + .withUnit('s'), + e + .numeric('detection_distance', ea.STATE) + .withValueMin(0) + .withValueMax(1000) + .withValueStep(1) + .withDescription('Distance of detected person') + .withUnit('cm'), + e.numeric('sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(1).withDescription('Detection sensitivity'), + e + .numeric('keep_time', ea.STATE_SET) + .withValueMin(5) + .withValueMax(3600) + .withValueStep(1) + .withDescription('Detection keep time') + .withUnit('s'), + e + .numeric('minimum_range', ea.STATE_SET) + .withValueMin(0) + .withValueMax(1000) + .withValueStep(50) + .withDescription('Minimum detection range') + .withUnit('m'), + e + .numeric('maximum_range', ea.STATE_SET) + .withValueMin(50) + .withValueMax(1000) + .withValueStep(50) + .withDescription('Maximum detection range') + .withUnit('m'), ], meta: { tuyaDatapoints: [ @@ -8418,24 +9820,46 @@ const definitions: Definition[] = [ configure: tuya.configureMagicPacket, exposes: [ e.occupancy(), - e.enum('human_motion_state', ea.STATE, ['none', 'small', 'large']) - .withDescription('Human Motion State'), - e.numeric('departure_delay', ea.STATE_SET).withUnit('s').withValueMin(3) - .withValueMax(600).withValueStep(1).withDescription('Presence Time'), - e.numeric('radar_range', ea.STATE_SET).withUnit('cm').withValueMin(150).withValueMax(600) - .withValueStep(75).withDescription('Motion Range Detection'), - e.numeric('radar_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(7) - .withValueStep(1).withDescription('Motion Detection Sensitivity'), - e.numeric('presence_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(7) - .withValueStep(1).withDescription('Motionless Detection Sensitivity'), - e.numeric('dis_current', ea.STATE).withUnit('cm').withValueMin(0).withValueMax(1000) - .withValueStep(1).withLabel('Current distance') + e.enum('human_motion_state', ea.STATE, ['none', 'small', 'large']).withDescription('Human Motion State'), + e + .numeric('departure_delay', ea.STATE_SET) + .withUnit('s') + .withValueMin(3) + .withValueMax(600) + .withValueStep(1) + .withDescription('Presence Time'), + e + .numeric('radar_range', ea.STATE_SET) + .withUnit('cm') + .withValueMin(150) + .withValueMax(600) + .withValueStep(75) + .withDescription('Motion Range Detection'), + e + .numeric('radar_sensitivity', ea.STATE_SET) + .withValueMin(0) + .withValueMax(7) + .withValueStep(1) + .withDescription('Motion Detection Sensitivity'), + e + .numeric('presence_sensitivity', ea.STATE_SET) + .withValueMin(0) + .withValueMax(7) + .withValueStep(1) + .withDescription('Motionless Detection Sensitivity'), + e + .numeric('dis_current', ea.STATE) + .withUnit('cm') + .withValueMin(0) + .withValueMax(1000) + .withValueStep(1) + .withLabel('Current distance') .withDescription('Current Distance of Detected Motion'), ], meta: { tuyaDatapoints: [ [1, 'occupancy', tuya.valueConverter.trueFalse1], - [11, 'human_motion_state', tuya.valueConverterBasic.lookup({'none': 0, 'small': 1, 'large': 2})], + [11, 'human_motion_state', tuya.valueConverterBasic.lookup({none: 0, small: 1, large: 2})], [12, 'departure_delay', tuya.valueConverter.raw], [13, 'radar_range', tuya.valueConverter.raw], [15, 'radar_sensitivity', tuya.valueConverter.raw], @@ -8460,30 +9884,70 @@ const definitions: Definition[] = [ configure: tuya.configureMagicPacket, exposes: [ e.numeric('tds', ea.STATE).withUnit('ppm').withDescription('Total Dissolved Solids'), - e.temperature(), e.battery(), - e.numeric('ph', ea.STATE).withUnit('pH').withDescription('pH value, if the pH value is lower than 6.5, it means that the water quality ' + - 'is too acidic and has impurities, and it is necessary to add disinfectant water for disinfection'), + e.temperature(), + e.battery(), + e + .numeric('ph', ea.STATE) + .withUnit('pH') + .withDescription( + 'pH value, if the pH value is lower than 6.5, it means that the water quality ' + + 'is too acidic and has impurities, and it is necessary to add disinfectant water for disinfection', + ), e.numeric('ec', ea.STATE).withUnit('µS/cm').withDescription('Electrical conductivity'), - e.numeric('orp', ea.STATE).withUnit('mV').withDescription('Oxidation Reduction Potential value. If the ORP value is above 850mv, it ' + - 'means that the disinfectant has been added too much, and it is necessary to add water or change the water for neutralization. ' + - 'If the ORP value is below 487mv, it means that too little disinfectant has been added and the pool needs to be disinfected again'), - e.numeric('free_chlorine', ea.STATE).withUnit('mg/L').withDescription('Free chlorine value. The water in the swimming pool should ' + - 'be between 6.5-8ph and ORP should be between 487-840mv, and the chlorine value will be displayed normally. Chlorine will not ' + - 'be displayed if either value is out of range'), + e + .numeric('orp', ea.STATE) + .withUnit('mV') + .withDescription( + 'Oxidation Reduction Potential value. If the ORP value is above 850mv, it ' + + 'means that the disinfectant has been added too much, and it is necessary to add water or change the water for neutralization. ' + + 'If the ORP value is below 487mv, it means that too little disinfectant has been added and the pool needs to be disinfected again', + ), + e + .numeric('free_chlorine', ea.STATE) + .withUnit('mg/L') + .withDescription( + 'Free chlorine value. The water in the swimming pool should ' + + 'be between 6.5-8ph and ORP should be between 487-840mv, and the chlorine value will be displayed normally. Chlorine will not ' + + 'be displayed if either value is out of range', + ), e.numeric('ph_max', ea.STATE_SET).withUnit('pH').withDescription('pH maximal value').withValueMin(0).withValueMax(20), e.numeric('ph_min', ea.STATE_SET).withUnit('pH').withDescription('pH minimal value').withValueMin(0).withValueMax(20), - e.numeric('ec_max', ea.STATE_SET).withUnit('µS/cm').withDescription('Electrical Conductivity maximal value') - .withValueMin(0).withValueMax(100), - e.numeric('ec_min', ea.STATE_SET).withUnit('µS/cm').withDescription('Electrical Conductivity minimal value') - .withValueMin(0).withValueMax(100), - e.numeric('orp_max', ea.STATE_SET).withUnit('mV').withDescription('Oxidation Reduction Potential maximal value') - .withValueMin(0).withValueMax(1000), - e.numeric('orp_min', ea.STATE_SET).withUnit('mV').withDescription('Oxidation Reduction Potential minimal value') - .withValueMin(0).withValueMax(1000), - e.numeric('free_chlorine_max', ea.STATE_SET).withUnit('mg/L').withDescription('Free Chlorine maximal value') - .withValueMin(0).withValueMax(15), - e.numeric('free_chlorine_min', ea.STATE_SET).withUnit('mg/L').withDescription('Free Chlorine minimal value') - .withValueMin(0).withValueMax(15), + e + .numeric('ec_max', ea.STATE_SET) + .withUnit('µS/cm') + .withDescription('Electrical Conductivity maximal value') + .withValueMin(0) + .withValueMax(100), + e + .numeric('ec_min', ea.STATE_SET) + .withUnit('µS/cm') + .withDescription('Electrical Conductivity minimal value') + .withValueMin(0) + .withValueMax(100), + e + .numeric('orp_max', ea.STATE_SET) + .withUnit('mV') + .withDescription('Oxidation Reduction Potential maximal value') + .withValueMin(0) + .withValueMax(1000), + e + .numeric('orp_min', ea.STATE_SET) + .withUnit('mV') + .withDescription('Oxidation Reduction Potential minimal value') + .withValueMin(0) + .withValueMax(1000), + e + .numeric('free_chlorine_max', ea.STATE_SET) + .withUnit('mg/L') + .withDescription('Free Chlorine maximal value') + .withValueMin(0) + .withValueMax(15), + e + .numeric('free_chlorine_min', ea.STATE_SET) + .withUnit('mg/L') + .withDescription('Free Chlorine minimal value') + .withValueMin(0) + .withValueMax(15), e.numeric('salinity', ea.STATE).withUnit('ppm').withDescription('Salt value'), // e.numeric('backlightvalue', ea.STATE).withUnit('gg').withDescription('Backlight Value'), ], @@ -8554,8 +10018,8 @@ const definitions: Definition[] = [ description: 'SOS button', extend: [ tuya.modernExtend.combineActions([ - tuya.modernExtend.dpAction({dp: 26, lookup: {'sos': 0}}), - tuya.modernExtend.dpAction({dp: 29, lookup: {'emergency': 0}}), + tuya.modernExtend.dpAction({dp: 26, lookup: {sos: 0}}), + tuya.modernExtend.dpAction({dp: 29, lookup: {emergency: 0}}), ]), iasZoneAlarm({zoneType: 'generic', zoneAttributes: ['battery_low']}), ], @@ -8571,15 +10035,28 @@ const definitions: Definition[] = [ onEvent: tuya.onEventSetTime, exposes: [ e.presence(), - e.numeric('detection_range', ea.STATE_SET).withValueMin(1.5).withValueMax(6).withValueStep(0.75).withUnit('m') + e + .numeric('detection_range', ea.STATE_SET) + .withValueMin(1.5) + .withValueMax(6) + .withValueStep(0.75) + .withUnit('m') .withDescription('Maximum range'), - e.numeric('radar_sensitivity', ea.STATE_SET).withValueMin(68).withValueMax(90).withValueStep(1) + e + .numeric('radar_sensitivity', ea.STATE_SET) + .withValueMin(68) + .withValueMax(90) + .withValueStep(1) .withDescription('Sensitivity of the radar'), - e.numeric('target_distance', ea.STATE).withValueMin(0).withValueMax(1000).withValueStep(1) - .withDescription('Distance of detected target').withUnit('cm'), + e + .numeric('target_distance', ea.STATE) + .withValueMin(0) + .withValueMax(1000) + .withValueStep(1) + .withDescription('Distance of detected target') + .withUnit('cm'), e.binary('indicator', ea.STATE_SET, 'ON', 'OFF').withDescription('LED indicator'), - e.numeric('fading_time', ea.STATE_SET).withValueMin(3).withValueMax(1799).withValueStep(1) - .withDescription('Fading time').withUnit('s'), + e.numeric('fading_time', ea.STATE_SET).withValueMin(3).withValueMax(1799).withValueStep(1).withDescription('Fading time').withUnit('s'), ], meta: { tuyaDatapoints: [ @@ -8600,7 +10077,7 @@ const definitions: Definition[] = [ description: '4 gang switch module', extend: [tuya.modernExtend.tuyaOnOff({switchType: true, indicatorMode: true, endpoints: ['l1', 'l2', 'l3', 'l4']})], endpoint: (device) => { - return {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4}; + return {l1: 1, l2: 2, l3: 3, l4: 4}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -8610,9 +10087,7 @@ const definitions: Definition[] = [ await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ['genOnOff']); await reporting.bind(device.getEndpoint(4), coordinatorEndpoint, ['genOnOff']); }, - whiteLabel: [ - tuya.whitelabel('AVATTO', 'ZWSM16-4-Zigbee', '4 gang switch module', ['_TZ3000_5ajpkyq6']), - ], + whiteLabel: [tuya.whitelabel('AVATTO', 'ZWSM16-4-Zigbee', '4 gang switch module', ['_TZ3000_5ajpkyq6'])], }, { fingerprint: [{modelID: 'TS1002', manufacturerName: '_TZ3000_etufnltx'}], @@ -8624,7 +10099,7 @@ const definitions: Definition[] = [ battery({voltage: true}), tuya.modernExtend.combineActions([ actionEnumLookup({ - actionLookup: {'scene_1': 1, 'scene_2': 2, 'scene_3': 3, 'scene_4': 4}, + actionLookup: {scene_1: 1, scene_2: 2, scene_3: 3, scene_4: 4}, cluster: 'genOnOff', commands: ['commandTuyaAction'], attribute: 'data', @@ -8654,8 +10129,17 @@ const definitions: Definition[] = [ [3, null, null], // Monthly, but sends data only after request [4, null, null], // Dayly, but sends data only after request [6, null, tuya.valueConverter.phaseVariant2], // voltage and current - [10, 'fault', tuya.valueConverterBasic.lookup({'clear': 0, 'over_current_threshold': 1, - 'over_power_threshold': 2, 'over_voltage_threshold': 4, 'wrong_frequency_threshold': 8})], + [ + 10, + 'fault', + tuya.valueConverterBasic.lookup({ + clear: 0, + over_current_threshold: 1, + over_power_threshold: 2, + over_voltage_threshold: 4, + wrong_frequency_threshold: 8, + }), + ], [11, null, null], // Frozen - strange function, in native app - nothing is clear [16, 'state', tuya.valueConverter.onOff], [17, null, tuya.valueConverter.threshold], // It's settable, but can't write converter @@ -8673,12 +10157,7 @@ const definitions: Definition[] = [ model: 'SZT06 V2.0', vendor: 'Tuya', description: 'Smart mini temperature and humidity sensor', - extend: [ - temperature(), - humidity(), - identify({isSleepy: true}), - battery({voltage: true}), - ], + extend: [temperature(), humidity(), identify({isSleepy: true}), battery({voltage: true})], }, { fingerprint: tuya.fingerprint('TS0601', ['_TZE200_pl31aqf5']), @@ -8700,9 +10179,9 @@ const definitions: Definition[] = [ ], meta: { tuyaDatapoints: [ - [1, 'air_quality', tuya.valueConverterBasic.lookup({'excellent': tuya.enum(0), 'moderate': tuya.enum(1), 'poor': tuya.enum(2)})], + [1, 'air_quality', tuya.valueConverterBasic.lookup({excellent: tuya.enum(0), moderate: tuya.enum(1), poor: tuya.enum(2)})], [2, 'co2', tuya.valueConverter.raw], - [5, 'alarm_ringtone', tuya.valueConverterBasic.lookup({'melody_1': tuya.enum(0), 'melody_2': tuya.enum(1), 'OFF': tuya.enum(2)})], + [5, 'alarm_ringtone', tuya.valueConverterBasic.lookup({melody_1: tuya.enum(0), melody_2: tuya.enum(1), OFF: tuya.enum(2)})], [14, 'battery_state', tuya.valueConverter.batteryState], [17, 'backlight_mode', tuya.valueConverter.raw], [18, 'temperature', tuya.valueConverter.raw], @@ -8717,9 +10196,7 @@ const definitions: Definition[] = [ description: '0-10v dimmer', fromZigbee: [fz.TS110E, fz.on_off], toZigbee: [tz.TS110E_onoff_brightness, tz.TS110E_options, tz.light_brightness_move], - exposes: [ - e.light_brightness().withMinBrightness().withMaxBrightness(), - ], + exposes: [e.light_brightness().withMinBrightness().withMaxBrightness()], configure: async (device, coordinatorEndpoint) => { await tuya.configureMagicPacket(device, coordinatorEndpoint); const endpoint = device.getEndpoint(1); @@ -8737,25 +10214,28 @@ const definitions: Definition[] = [ onEvent: tuya.onEventSetTime, // Add this if you are getting no converter for 'commandMcuSyncTime' configure: tuya.configureMagicPacket, exposes: [ - e.climate() + e + .climate() .withSystemMode(['off', 'heat'], ea.STATE_SET) .withPreset(['manual', 'auto']) .withRunningState(['idle', 'heat'], ea.STATE) .withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET) - .withLocalTemperature(ea.STATE).withDescription('Floor temperature') - .withLocalTemperatureCalibration(-9, 9, 0.1, ea.STATE_SET).withDescription('Calibration floor temperature sensor'), + .withLocalTemperature(ea.STATE) + .withDescription('Floor temperature') + .withLocalTemperatureCalibration(-9, 9, 0.1, ea.STATE_SET) + .withDescription('Calibration floor temperature sensor'), e.deadzone_temperature().withValueMin(0).withValueMax(5).withValueStep(1).withDescription('Floor temperature'), e.child_lock(), ...tuya.exposes.scheduleAllDays(ea.STATE_SET, 'HH:MM/C HH:MM/C HH:MM/C HH:MM/C'), ], meta: { tuyaDatapoints: [ - [1, 'system_mode', tuya.valueConverterBasic.lookup({'heat': true, 'off': false})], + [1, 'system_mode', tuya.valueConverterBasic.lookup({heat: true, off: false})], [2, 'preset', tuya.valueConverter.tv02Preset()], [16, 'current_heating_setpoint', tuya.valueConverter.divideBy10], [24, 'device_temperature', tuya.valueConverter.divideBy10], [27, 'local_temperature_calibration', tuya.valueConverter.localTempCalibration2], - [36, 'running_state', tuya.valueConverterBasic.lookup({'heat': tuya.enum(0), 'idle': tuya.enum(1)})], + [36, 'running_state', tuya.valueConverterBasic.lookup({heat: tuya.enum(0), idle: tuya.enum(1)})], [40, 'child_lock', tuya.valueConverter.lockUnlock], [102, 'local_temperature', tuya.valueConverter.divideBy10], [103, 'deadzone_temperature', tuya.valueConverter.raw], @@ -8786,7 +10266,7 @@ const definitions: Definition[] = [ meta: { tuyaDatapoints: [ [1, 'state', tuya.valueConverter.onOff], - [8, 'valve_state', tuya.valueConverterBasic.lookup({'unknown': tuya.enum(0), 'open': tuya.enum(1), 'closed': tuya.enum(2)})], + [8, 'valve_state', tuya.valueConverterBasic.lookup({unknown: tuya.enum(0), open: tuya.enum(1), closed: tuya.enum(2)})], ], }, }, @@ -8866,7 +10346,7 @@ const definitions: Definition[] = [ tuya.modernExtend.dpHumidity({dp: 103}), ], endpoint: (device) => { - return {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4}; + return {l1: 1, l2: 2, l3: 3, l4: 4}; }, exposes: [], meta: {multiEndpoint: true}, diff --git a/src/devices/ubisys.ts b/src/devices/ubisys.ts index 86814c0636df2..f63e513551afa 100644 --- a/src/devices/ubisys.ts +++ b/src/devices/ubisys.ts @@ -1,16 +1,17 @@ -import * as exposes from '../lib/exposes'; +import * as semver from 'semver'; +import {Zcl} from 'zigbee-herdsman'; + import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; +import * as constants from '../lib/constants'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; +import {logger} from '../lib/logger'; import * as ota from '../lib/ota'; -import * as utils from '../lib/utils'; import * as reporting from '../lib/reporting'; -import * as constants from '../lib/constants'; -import {Zcl} from 'zigbee-herdsman'; import {Definition, Fz, OnEventType, Tz, OnEventData, Zh, KeyValue, KeyValueAny} from '../lib/types'; import {ubisysModernExtend} from '../lib/ubisys'; -import * as semver from 'semver'; -import {logger} from '../lib/logger'; +import * as utils from '../lib/utils'; const NS = 'zhc:ubisys'; const e = exposes.presets; @@ -27,11 +28,13 @@ const manufacturerOptions = { ubisysNull: {manufacturerCode: null}, }; -const ubisysOnEventReadCurrentSummDelivered = async function(type: OnEventType, data: OnEventData, device: Zh.Device) { +const ubisysOnEventReadCurrentSummDelivered = async function (type: OnEventType, data: OnEventData, device: Zh.Device) { if (data.type === 'attributeReport' && data.cluster === 'seMetering') { try { await data.endpoint.read('seMetering', ['currentSummDelivered']); - } catch (error) {/* Do nothing*/} + } catch (error) { + /* Do nothing*/ + } } }; @@ -99,7 +102,7 @@ const ubisys = { result['input_configurations'] = msg.data['inputConfigurations']; } if (msg.data['inputActions'] != null) { - result['input_actions'] = msg.data['inputActions'].map(function(el: KeyValue) { + result['input_actions'] = msg.data['inputActions'].map(function (el: KeyValue) { return Object.values(el); }); } @@ -121,14 +124,18 @@ const ubisys = { let operationalStatus = 0; do { await sleepSeconds(2); - operationalStatus = (await entity.read('closuresWindowCovering', - // @ts-expect-error - ['operationalStatus'])).operationalStatus; + const response = await entity.read('closuresWindowCovering', ['operationalStatus']); + // @ts-expect-error + operationalStatus = response.operationalStatus; } while (operationalStatus != 0); await sleepSeconds(2); }; - const writeAttrFromJson = async (attr: string, jsonAttr?: string, converterFunc?: (v: unknown) => unknown, - delaySecondsAfter?: number) => { + const writeAttrFromJson = async ( + attr: string, + jsonAttr?: string, + converterFunc?: (v: unknown) => unknown, + delaySecondsAfter?: number, + ) => { if (!jsonAttr) jsonAttr = attr; if (jsonAttr.startsWith('ubisys')) { jsonAttr = jsonAttr.substring(6, 1).toLowerCase + jsonAttr.substring(7); @@ -171,16 +178,20 @@ const ubisys = { await waitUntilStopped(); log(' Settings some attributes...'); // reset attributes - await entity.write('closuresWindowCovering', { - installedOpenLimitLiftCm: 0, - installedClosedLimitLiftCm: 240, - installedOpenLimitTiltDdegree: 0, - installedClosedLimitTiltDdegree: 900, - ubisysLiftToTiltTransitionSteps: 0xffff, - ubisysTotalSteps: 0xffff, - ubisysLiftToTiltTransitionSteps2: 0xffff, - ubisysTotalSteps2: 0xffff, - }, manufacturerOptions.ubisys); + await entity.write( + 'closuresWindowCovering', + { + installedOpenLimitLiftCm: 0, + installedClosedLimitLiftCm: 240, + installedOpenLimitTiltDdegree: 0, + installedClosedLimitTiltDdegree: 900, + ubisysLiftToTiltTransitionSteps: 0xffff, + ubisysTotalSteps: 0xffff, + ubisysLiftToTiltTransitionSteps2: 0xffff, + ubisysTotalSteps2: 0xffff, + }, + manufacturerOptions.ubisys, + ); // enable calibration mode await sleepSeconds(2); await entity.write('closuresWindowCovering', {windowCoveringMode: mode | modeCalibrationBitMask}); @@ -217,10 +228,8 @@ const ubisys = { // some convenience functions to not have to calculate await writeAttrFromJson('ubisysTotalSteps', 'open_to_closed_s', (s: number) => s * stepsPerSecond); await writeAttrFromJson('ubisysTotalSteps2', 'closed_to_open_s', (s: number) => s * stepsPerSecond); - await writeAttrFromJson('ubisysLiftToTiltTransitionSteps', 'lift_to_tilt_transition_ms', - (s: number) => s * stepsPerSecond / 1000); - await writeAttrFromJson('ubisysLiftToTiltTransitionSteps2', 'lift_to_tilt_transition_ms', - (s: number) => s * stepsPerSecond / 1000); + await writeAttrFromJson('ubisysLiftToTiltTransitionSteps', 'lift_to_tilt_transition_ms', (s: number) => (s * stepsPerSecond) / 1000); + await writeAttrFromJson('ubisysLiftToTiltTransitionSteps2', 'lift_to_tilt_transition_ms', (s: number) => (s * stepsPerSecond) / 1000); if (hasCalibrate) { log(' Finalizing calibration...'); // disable calibration mode again @@ -236,38 +245,49 @@ const ubisys = { const log = (json: unknown) => { logger.warning(`ubisys: Cover configuration read: ${JSON.stringify(json)}`, NS); }; - log(await entity.read('closuresWindowCovering', [ - 'windowCoveringType', - 'physicalClosedLimitLiftCm', - 'physicalClosedLimitTiltDdegree', - 'installedOpenLimitLiftCm', - 'installedClosedLimitLiftCm', - 'installedOpenLimitTiltDdegree', - 'installedClosedLimitTiltDdegree', - ])); - log(await entity.read('closuresWindowCovering', [ - 'configStatus', - 'windowCoveringMode', - 'currentPositionLiftPercentage', - 'currentPositionLiftCm', - 'currentPositionTiltPercentage', - 'currentPositionTiltDdegree', - 'operationalStatus', - ])); - log(await entity.read('closuresWindowCovering', [ - 'ubisysTurnaroundGuardTime', - 'ubisysLiftToTiltTransitionSteps', - 'ubisysTotalSteps', - 'ubisysLiftToTiltTransitionSteps2', - 'ubisysTotalSteps2', - 'ubisysAdditionalSteps', - 'ubisysInactivePowerThreshold', - 'ubisysStartupSteps', - ], manufacturerOptions.ubisys)); + log( + await entity.read('closuresWindowCovering', [ + 'windowCoveringType', + 'physicalClosedLimitLiftCm', + 'physicalClosedLimitTiltDdegree', + 'installedOpenLimitLiftCm', + 'installedClosedLimitLiftCm', + 'installedOpenLimitTiltDdegree', + 'installedClosedLimitTiltDdegree', + ]), + ); + log( + await entity.read('closuresWindowCovering', [ + 'configStatus', + 'windowCoveringMode', + 'currentPositionLiftPercentage', + 'currentPositionLiftCm', + 'currentPositionTiltPercentage', + 'currentPositionTiltDdegree', + 'operationalStatus', + ]), + ); + log( + await entity.read( + 'closuresWindowCovering', + [ + 'ubisysTurnaroundGuardTime', + 'ubisysLiftToTiltTransitionSteps', + 'ubisysTotalSteps', + 'ubisysLiftToTiltTransitionSteps2', + 'ubisysTotalSteps2', + 'ubisysAdditionalSteps', + 'ubisysInactivePowerThreshold', + 'ubisysStartupSteps', + ], + manufacturerOptions.ubisys, + ), + ); }, } satisfies Tz.Converter, dimmer_setup: { - key: ['capabilities_forward_phase_control', + key: [ + 'capabilities_forward_phase_control', 'capabilities_reverse_phase_control', 'capabilities_reactance_discriminator', 'capabilities_configurable_curve', @@ -277,15 +297,19 @@ const ubisys = { 'status_overload', 'status_capacitive_load', 'status_inductive_load', - 'mode_phase_control'], + 'mode_phase_control', + ], convertSet: async (entity, key, value, meta) => { if (key === 'mode_phase_control') { utils.assertString(value, 'mode_phase_control'); const phaseControl = value.toLowerCase(); - const phaseControlValues = {'automatic': 0, 'forward': 1, 'reverse': 2}; + const phaseControlValues = {automatic: 0, forward: 1, reverse: 2}; utils.validateValue(phaseControl, Object.keys(phaseControlValues)); - await entity.write('manuSpecificUbisysDimmerSetup', - {'mode': utils.getFromLookup(phaseControl, phaseControlValues)}, manufacturerOptions.ubisysNull); + await entity.write( + 'manuSpecificUbisysDimmerSetup', + {mode: utils.getFromLookup(phaseControl, phaseControlValues)}, + manufacturerOptions.ubisysNull, + ); } await ubisys.tz.dimmer_setup.convertGet(entity, key, meta); }, @@ -299,7 +323,7 @@ const ubisys = { key: ['minimum_on_level'], convertSet: async (entity, key, value, meta) => { if (key === 'minimum_on_level') { - await entity.write('genLevelCtrl', {'ubisysMinimumOnLevel': value}, manufacturerOptions.ubisys); + await entity.write('genLevelCtrl', {ubisysMinimumOnLevel: value}, manufacturerOptions.ubisys); } await ubisys.tz.dimmer_setup_genLevelCtrl.convertGet(entity, key, meta); }, @@ -330,15 +354,17 @@ const ubisys = { if (useWriteStruct) { await devMgmtEp.writeStructured( 'manuSpecificUbisysDeviceSetup', - [{ - attrId: attributeInputConfigurations.ID, - selector: {}, - dataType: Zcl.DataType.ARRAY, - elementData: { - elementType: Zcl.DataType.DATA8, - elements: value.input_configurations, + [ + { + attrId: attributeInputConfigurations.ID, + selector: {}, + dataType: Zcl.DataType.ARRAY, + elementData: { + elementType: Zcl.DataType.DATA8, + elements: value.input_configurations, + }, }, - }], + ], manufacturerOptions.ubisysNull, ); } else { @@ -355,15 +381,17 @@ const ubisys = { if (useWriteStruct) { await devMgmtEp.writeStructured( 'manuSpecificUbisysDeviceSetup', - [{ - attrId: attributeInputActions.ID, - selector: {}, - dataType: Zcl.DataType.ARRAY, - elementData: { - elementType: Zcl.DataType.OCTET_STR, - elements: value.input_actions, + [ + { + attrId: attributeInputActions.ID, + selector: {}, + dataType: Zcl.DataType.ARRAY, + elementData: { + elementType: Zcl.DataType.OCTET_STR, + elements: value.input_actions, + }, }, - }], + ], manufacturerOptions.ubisysNull, ); } else { @@ -379,34 +407,28 @@ const ubisys = { const templateTypes = { // source: "ZigBee Device Physical Input Configurations Integrator’s Guide" // (can be obtained directly from ubisys upon request) - 'toggle': { - getInputActions: (input: unknown, endpoint: Zh.Endpoint) => [ - [input, 0x0D, endpoint, 0x06, 0x00, 0x02], - ], + toggle: { + getInputActions: (input: unknown, endpoint: Zh.Endpoint) => [[input, 0x0d, endpoint, 0x06, 0x00, 0x02]], }, - 'toggle_switch': { + toggle_switch: { getInputActions: (input: unknown, endpoint: Zh.Endpoint) => [ - [input, 0x0D, endpoint, 0x06, 0x00, 0x02], + [input, 0x0d, endpoint, 0x06, 0x00, 0x02], [input, 0x03, endpoint, 0x06, 0x00, 0x02], ], }, - 'on_off_switch': { + on_off_switch: { getInputActions: (input: unknown, endpoint: Zh.Endpoint) => [ - [input, 0x0D, endpoint, 0x06, 0x00, 0x01], + [input, 0x0d, endpoint, 0x06, 0x00, 0x01], [input, 0x03, endpoint, 0x06, 0x00, 0x00], ], }, - 'on': { - getInputActions: (input: unknown, endpoint: Zh.Endpoint) => [ - [input, 0x0D, endpoint, 0x06, 0x00, 0x01], - ], + on: { + getInputActions: (input: unknown, endpoint: Zh.Endpoint) => [[input, 0x0d, endpoint, 0x06, 0x00, 0x01]], }, - 'off': { - getInputActions: (input: unknown, endpoint: Zh.Endpoint) => [ - [input, 0x0D, endpoint, 0x06, 0x00, 0x00], - ], + off: { + getInputActions: (input: unknown, endpoint: Zh.Endpoint) => [[input, 0x0d, endpoint, 0x06, 0x00, 0x00]], }, - 'dimmer_single': { + dimmer_single: { getInputActions: (input: unknown, endpoint: Zh.Endpoint, template: KeyValue) => { const moveUpCmd = template.no_onoff || template.no_onoff_up ? 0x01 : 0x05; const moveDownCmd = template.no_onoff || template.no_onoff_down ? 0x01 : 0x05; @@ -414,12 +436,12 @@ const ubisys = { return [ [input, 0x07, endpoint, 0x06, 0x00, 0x02], [input, 0x86, endpoint, 0x08, 0x00, moveUpCmd, 0x00, moveRate], - [input, 0xC6, endpoint, 0x08, 0x00, moveDownCmd, 0x01, moveRate], - [input, 0x0B, endpoint, 0x08, 0x00, 0x03], + [input, 0xc6, endpoint, 0x08, 0x00, moveDownCmd, 0x01, moveRate], + [input, 0x0b, endpoint, 0x08, 0x00, 0x03], ]; }, }, - 'dimmer_double': { + dimmer_double: { doubleInputs: true, getInputActions: (inputs: unknown[], endpoint: Zh.Endpoint, template: KeyValue) => { const moveUpCmd = template.no_onoff || template.no_onoff_up ? 0x01 : 0x05; @@ -428,46 +450,42 @@ const ubisys = { return [ [inputs[0], 0x07, endpoint, 0x06, 0x00, 0x01], [inputs[0], 0x06, endpoint, 0x08, 0x00, moveUpCmd, 0x00, moveRate], - [inputs[0], 0x0B, endpoint, 0x08, 0x00, 0x03], + [inputs[0], 0x0b, endpoint, 0x08, 0x00, 0x03], [inputs[1], 0x07, endpoint, 0x06, 0x00, 0x00], [inputs[1], 0x06, endpoint, 0x08, 0x00, moveDownCmd, 0x01, moveRate], - [inputs[1], 0x0B, endpoint, 0x08, 0x00, 0x03], + [inputs[1], 0x0b, endpoint, 0x08, 0x00, 0x03], ]; }, }, - 'cover': { + cover: { cover: true, doubleInputs: true, getInputActions: (inputs: unknown[], endpoint: Zh.Endpoint) => [ - [inputs[0], 0x0D, endpoint, 0x02, 0x01, 0x00], + [inputs[0], 0x0d, endpoint, 0x02, 0x01, 0x00], [inputs[0], 0x07, endpoint, 0x02, 0x01, 0x02], - [inputs[1], 0x0D, endpoint, 0x02, 0x01, 0x01], + [inputs[1], 0x0d, endpoint, 0x02, 0x01, 0x01], [inputs[1], 0x07, endpoint, 0x02, 0x01, 0x02], ], }, - 'cover_switch': { + cover_switch: { cover: true, doubleInputs: true, getInputActions: (inputs: unknown[], endpoint: Zh.Endpoint) => [ - [inputs[0], 0x0D, endpoint, 0x02, 0x01, 0x00], + [inputs[0], 0x0d, endpoint, 0x02, 0x01, 0x00], [inputs[0], 0x03, endpoint, 0x02, 0x01, 0x02], - [inputs[1], 0x0D, endpoint, 0x02, 0x01, 0x01], + [inputs[1], 0x0d, endpoint, 0x02, 0x01, 0x01], [inputs[1], 0x03, endpoint, 0x02, 0x01, 0x02], ], }, - 'cover_up': { + cover_up: { cover: true, - getInputActions: (input: unknown, endpoint: Zh.Endpoint) => [ - [input, 0x0D, endpoint, 0x02, 0x01, 0x00], - ], + getInputActions: (input: unknown, endpoint: Zh.Endpoint) => [[input, 0x0d, endpoint, 0x02, 0x01, 0x00]], }, - 'cover_down': { + cover_down: { cover: true, - getInputActions: (input: unknown, endpoint: Zh.Endpoint) => [ - [input, 0x0D, endpoint, 0x02, 0x01, 0x01], - ], + getInputActions: (input: unknown, endpoint: Zh.Endpoint) => [[input, 0x0d, endpoint, 0x02, 0x01, 0x01]], }, - 'scene': { + scene: { scene: true, getInputActions: (input: unknown, endpoint: Zh.Endpoint, groupId: number, sceneId: number) => [ [input, 0x07, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId], @@ -476,10 +494,10 @@ const ubisys = { [input, 0x06, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId], ], }, - 'scene_switch': { + scene_switch: { scene: true, getInputActions: (input: unknown, endpoint: Zh.Endpoint, groupId: number, sceneId: number) => [ - [input, 0x0D, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId], + [input, 0x0d, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId], ], getInputActions2: (input: unknown, endpoint: Zh.Endpoint, groupId: number, sceneId: number) => [ [input, 0x03, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId], @@ -491,19 +509,20 @@ const ubisys = { let input = 0; // first client endpoint - depends on actual device if (Array.isArray(meta.mapped)) throw new Error(`Not supported for groups`); - let endpoint = {'S1': 2, 'S2': 3, 'D1': 2, 'J1': 2, 'C4': 1}[meta.mapped.model]; + let endpoint = {S1: 2, S2: 3, D1: 2, J1: 2, C4: 1}[meta.mapped.model]; // default group id let groupId = 0; - const templates = Array.isArray(value.input_action_templates) ? value.input_action_templates : - [value.input_action_templates]; + const templates = Array.isArray(value.input_action_templates) ? value.input_action_templates : [value.input_action_templates]; let resultingInputActions: unknown[] = []; for (const template of templates) { // @ts-expect-error const templateType = templateTypes[template.type]; if (!templateType) { - throw new Error(`input_action_templates: Template type '${template.type}' is not valid ` + - `(valid types: ${Object.keys(templateTypes)})`); + throw new Error( + `input_action_templates: Template type '${template.type}' is not valid ` + + `(valid types: ${Object.keys(templateTypes)})`, + ); } if (template.hasOwnProperty('input')) { @@ -536,8 +555,7 @@ const ubisys = { if (template.hasOwnProperty('group_id_2')) { groupId = template.group_id_2; } - inputActions = inputActions.concat(templateType.getInputActions2(input, endpoint, groupId, - template.scene_id_2)); + inputActions = inputActions.concat(templateType.getInputActions2(input, endpoint, groupId, template.scene_id_2)); } } } else { @@ -553,20 +571,21 @@ const ubisys = { endpoint += 1; } - logger.debug(`ubisys: input_actions to be sent to '${meta.options.friendly_name}': ` + - JSON.stringify(resultingInputActions), NS); + logger.debug(`ubisys: input_actions to be sent to '${meta.options.friendly_name}': ` + JSON.stringify(resultingInputActions), NS); if (useWriteStruct) { await devMgmtEp.writeStructured( 'manuSpecificUbisysDeviceSetup', - [{ - attrId: attributeInputActions.ID, - selector: {}, - dataType: Zcl.DataType.ARRAY, - elementData: { - elementType: Zcl.DataType.OCTET_STR, - elements: resultingInputActions, + [ + { + attrId: attributeInputActions.ID, + selector: {}, + dataType: Zcl.DataType.ARRAY, + elementData: { + elementType: Zcl.DataType.OCTET_STR, + elements: resultingInputActions, + }, }, - }], + ], manufacturerOptions.ubisysNull, ); } else { @@ -584,10 +603,8 @@ const ubisys = { convertGet: async (entity, key, meta) => { const devMgmtEp = meta.device.getEndpoint(232); - await devMgmtEp.read('manuSpecificUbisysDeviceSetup', ['inputConfigurations'], - manufacturerOptions.ubisysNull); - await devMgmtEp.read('manuSpecificUbisysDeviceSetup', ['inputActions'], - manufacturerOptions.ubisysNull); + await devMgmtEp.read('manuSpecificUbisysDeviceSetup', ['inputConfigurations'], manufacturerOptions.ubisysNull); + await devMgmtEp.read('manuSpecificUbisysDeviceSetup', ['inputActions'], manufacturerOptions.ubisysNull); }, } satisfies Tz.Converter, }, @@ -601,24 +618,29 @@ const definitions: Definition[] = [ description: 'Power switch S1', exposes: [ e.switch(), - e.action([ - 'toggle', 'on', 'off', 'recall_*', - 'brightness_move_up', 'brightness_move_down', 'brightness_stop', - ]), + e.action(['toggle', 'on', 'off', 'recall_*', 'brightness_move_up', 'brightness_move_down', 'brightness_stop']), e.power_on_behavior(), e.power().withAccess(ea.STATE_GET), e.energy().withAccess(ea.STATE_GET), ], - fromZigbee: [fz.on_off, fz.metering, fz.command_toggle, fz.command_on, fz.command_off, fz.command_recall, fz.command_move, - fz.command_stop, fz.power_on_behavior, ubisys.fz.configure_device_setup], + fromZigbee: [ + fz.on_off, + fz.metering, + fz.command_toggle, + fz.command_on, + fz.command_off, + fz.command_recall, + fz.command_move, + fz.command_stop, + fz.power_on_behavior, + ubisys.fz.configure_device_setup, + ], toZigbee: [tz.on_off, tz.metering_power, tz.currentsummdelivered, ubisys.tz.configure_device_setup, tz.power_on_behavior], endpoint: (device) => { - return {'l1': 1, 's1': 2, 'meter': 3}; + return {l1: 1, s1: 2, meter: 3}; }, - meta: {multiEndpointEnforce: {'power': 3, 'energy': 3}}, - extend: [ - ubisysModernExtend.addCustomClusterManuSpecificUbisysDeviceSetup(), - ], + meta: {multiEndpointEnforce: {power: 3, energy: 3}}, + extend: [ubisysModernExtend.addCustomClusterManuSpecificUbisysDeviceSetup()], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(3); await reporting.bind(endpoint, coordinatorEndpoint, ['seMetering']); @@ -653,24 +675,29 @@ const definitions: Definition[] = [ description: 'Power switch S1-R', exposes: [ e.switch(), - e.action([ - 'toggle', 'on', 'off', 'recall_*', - 'brightness_move_up', 'brightness_move_down', 'brightness_stop', - ]), + e.action(['toggle', 'on', 'off', 'recall_*', 'brightness_move_up', 'brightness_move_down', 'brightness_stop']), e.power_on_behavior(), e.power().withAccess(ea.STATE_GET), e.energy().withAccess(ea.STATE_GET), ], - fromZigbee: [fz.on_off, fz.metering, fz.command_toggle, fz.command_on, fz.command_off, fz.command_recall, fz.command_move, - fz.command_stop, fz.power_on_behavior, ubisys.fz.configure_device_setup], + fromZigbee: [ + fz.on_off, + fz.metering, + fz.command_toggle, + fz.command_on, + fz.command_off, + fz.command_recall, + fz.command_move, + fz.command_stop, + fz.power_on_behavior, + ubisys.fz.configure_device_setup, + ], toZigbee: [tz.on_off, tz.metering_power, tz.currentsummdelivered, ubisys.tz.configure_device_setup, tz.power_on_behavior], - meta: {multiEndpointEnforce: {'power': 4, 'energy': 4}}, + meta: {multiEndpointEnforce: {power: 4, energy: 4}}, endpoint: (device) => { - return {'l1': 1, 's1': 2, 'meter': 4}; + return {l1: 1, s1: 2, meter: 4}; }, - extend: [ - ubisysModernExtend.addCustomClusterManuSpecificUbisysDeviceSetup(), - ], + extend: [ubisysModernExtend.addCustomClusterManuSpecificUbisysDeviceSetup()], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(4); await reporting.bind(endpoint, coordinatorEndpoint, ['seMetering']); @@ -706,24 +733,45 @@ const definitions: Definition[] = [ exposes: [ e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), - e.action(['toggle_s1', 'toggle_s2', 'on_s1', 'on_s2', 'off_s1', 'off_s2', 'recall_*_s1', 'recal_*_s2', 'brightness_move_up_s1', - 'brightness_move_up_s2', 'brightness_move_down_s1', 'brightness_move_down_s2', 'brightness_stop_s1', - 'brightness_stop_s2']), + e.action([ + 'toggle_s1', + 'toggle_s2', + 'on_s1', + 'on_s2', + 'off_s1', + 'off_s2', + 'recall_*_s1', + 'recal_*_s2', + 'brightness_move_up_s1', + 'brightness_move_up_s2', + 'brightness_move_down_s1', + 'brightness_move_down_s2', + 'brightness_stop_s1', + 'brightness_stop_s2', + ]), e.power_on_behavior().withEndpoint('l1'), e.power_on_behavior().withEndpoint('l2'), e.power().withAccess(ea.STATE_GET), e.energy().withAccess(ea.STATE_GET), ], - fromZigbee: [fz.on_off, fz.metering, fz.command_toggle, fz.command_on, fz.command_off, fz.command_recall, fz.command_move, - fz.command_stop, fz.power_on_behavior, ubisys.fz.configure_device_setup], + fromZigbee: [ + fz.on_off, + fz.metering, + fz.command_toggle, + fz.command_on, + fz.command_off, + fz.command_recall, + fz.command_move, + fz.command_stop, + fz.power_on_behavior, + ubisys.fz.configure_device_setup, + ], toZigbee: [tz.on_off, tz.metering_power, ubisys.tz.configure_device_setup, tz.power_on_behavior, tz.currentsummdelivered], endpoint: (device) => { - return {'l1': 1, 'l2': 2, 's1': 3, 's2': 4, 'meter': 5}; + return {l1: 1, l2: 2, s1: 3, s2: 4, meter: 5}; }, - meta: {multiEndpoint: true, multiEndpointSkip: ['power', 'energy'], multiEndpointEnforce: {'power': 5, 'energy': 5}}, - extend: [ - ubisysModernExtend.addCustomClusterManuSpecificUbisysDeviceSetup(), - ], + meta: {multiEndpoint: true, multiEndpointSkip: ['power', 'energy'], multiEndpointEnforce: {power: 5, energy: 5}}, + extend: [ubisysModernExtend.addCustomClusterManuSpecificUbisysDeviceSetup()], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(5); await reporting.bind(endpoint, coordinatorEndpoint, ['seMetering']); @@ -764,70 +812,140 @@ const definitions: Definition[] = [ model: 'D1', vendor: 'Ubisys', description: 'Universal dimmer D1', - fromZigbee: [fz.on_off, fz.brightness, fz.metering, fz.command_toggle, fz.command_on, fz.command_off, fz.command_recall, - fz.command_move, fz.command_stop, fz.lighting_ballast_configuration, fz.level_config, ubisys.fz.dimmer_setup, - ubisys.fz.dimmer_setup_genLevelCtrl, ubisys.fz.configure_device_setup], - toZigbee: [tz.light_onoff_brightness, tz.ballast_config, tz.level_config, ubisys.tz.dimmer_setup, - ubisys.tz.dimmer_setup_genLevelCtrl, ubisys.tz.configure_device_setup, tz.ignore_transition, tz.light_brightness_move, - tz.light_brightness_step, tz.metering_power, tz.currentsummdelivered], + fromZigbee: [ + fz.on_off, + fz.brightness, + fz.metering, + fz.command_toggle, + fz.command_on, + fz.command_off, + fz.command_recall, + fz.command_move, + fz.command_stop, + fz.lighting_ballast_configuration, + fz.level_config, + ubisys.fz.dimmer_setup, + ubisys.fz.dimmer_setup_genLevelCtrl, + ubisys.fz.configure_device_setup, + ], + toZigbee: [ + tz.light_onoff_brightness, + tz.ballast_config, + tz.level_config, + ubisys.tz.dimmer_setup, + ubisys.tz.dimmer_setup_genLevelCtrl, + ubisys.tz.configure_device_setup, + tz.ignore_transition, + tz.light_brightness_move, + tz.light_brightness_step, + tz.metering_power, + tz.currentsummdelivered, + ], exposes: [ - e.action(['toggle_s1', 'toggle_s2', 'on_s1', 'on_s2', 'off_s1', 'off_s2', 'recall_*_s1', 'recal_*_s2', 'brightness_move_up_s1', - 'brightness_move_up_s2', 'brightness_move_down_s1', 'brightness_move_down_s2', 'brightness_stop_s1', - 'brightness_stop_s2']), + e.action([ + 'toggle_s1', + 'toggle_s2', + 'on_s1', + 'on_s2', + 'off_s1', + 'off_s2', + 'recall_*_s1', + 'recal_*_s2', + 'brightness_move_up_s1', + 'brightness_move_up_s2', + 'brightness_move_down_s1', + 'brightness_move_down_s2', + 'brightness_stop_s1', + 'brightness_stop_s2', + ]), e.light_brightness(), - e.composite('level_config', 'level_config', ea.ALL) - .withFeature(e.numeric('on_off_transition_time', ea.ALL) - .withDescription('Specifies the amount of time, in units of 0.1 seconds, which will be used during a transition to ' + - 'either the on or off state, when an on/off/toggle command of the on/off cluster is used to turn the light on or off')) - .withFeature(e.numeric('on_level', ea.ALL) - .withValueMin(1).withValueMax(254) - .withPreset('previous', 255, 'Use previous value') - .withDescription('Specifies the level that shall be applied, when an on/toggle command causes the light to turn on.')) - .withFeature(e.binary('execute_if_off', ea.ALL, true, false) - .withDescription('Defines if you can send a brightness change without to turn on the light')) - .withFeature(e.numeric('current_level_startup', ea.ALL) - .withValueMin(1).withValueMax(254) - .withPreset('previous', 255, 'Use previous value') - .withDescription('Specifies the initial level to be applied after the device is supplied with power')), + e + .composite('level_config', 'level_config', ea.ALL) + .withFeature( + e + .numeric('on_off_transition_time', ea.ALL) + .withDescription( + 'Specifies the amount of time, in units of 0.1 seconds, which will be used during a transition to ' + + 'either the on or off state, when an on/off/toggle command of the on/off cluster is used to turn the light on or off', + ), + ) + .withFeature( + e + .numeric('on_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) + .withPreset('previous', 255, 'Use previous value') + .withDescription('Specifies the level that shall be applied, when an on/toggle command causes the light to turn on.'), + ) + .withFeature( + e + .binary('execute_if_off', ea.ALL, true, false) + .withDescription('Defines if you can send a brightness change without to turn on the light'), + ) + .withFeature( + e + .numeric('current_level_startup', ea.ALL) + .withValueMin(1) + .withValueMax(254) + .withPreset('previous', 255, 'Use previous value') + .withDescription('Specifies the initial level to be applied after the device is supplied with power'), + ), e.power().withAccess(ea.STATE_GET), e.energy().withAccess(ea.STATE_GET), - e.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254) + e + .numeric('ballast_minimum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) .withDescription('Specifies the minimum light output of the ballast'), - e.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254) + e + .numeric('ballast_maximum_level', ea.ALL) + .withValueMin(1) + .withValueMax(254) .withDescription('Specifies the maximum light output of the ballast'), - e.numeric('minimum_on_level', ea.ALL).withValueMin(0).withValueMax(255) - .withDescription('Specifies the minimum level that shall be applied, when an on/toggle command causes the ' + - 'light to turn on. When this attribute is set to the invalid value (255) this feature is disabled ' + - 'and standard rules apply: The light will either return to the previously active level (before it ' + - 'was turned off) if the OnLevel attribute is set to the invalid value (255/previous); or to the specified ' + - 'value of the OnLevel attribute if this value is in the range 0…254. Otherwise, if the ' + - 'MinimumOnLevel is in the range 0…254, the light will be set to the the previously ' + - 'active level (before it was turned off), or the value specified here, whichever is the larger ' + - 'value. For example, if the previous level was 30 and the MinimumOnLevel was 40 then ' + - 'the light would turn on and move to level 40. Conversely, if the previous level was 50, ' + - 'and the MinimumOnLevel was 40, then the light would turn on and move to level 50.'), - e.binary('capabilities_forward_phase_control', ea.ALL, true, false) - .withDescription('The dimmer supports AC forward phase control.'), - e.binary('capabilities_reverse_phase_control', ea.ALL, true, false) - .withDescription('The dimmer supports AC reverse phase control.'), - e.binary('capabilities_reactance_discriminator', ea.ALL, true, false) + e + .numeric('minimum_on_level', ea.ALL) + .withValueMin(0) + .withValueMax(255) + .withDescription( + 'Specifies the minimum level that shall be applied, when an on/toggle command causes the ' + + 'light to turn on. When this attribute is set to the invalid value (255) this feature is disabled ' + + 'and standard rules apply: The light will either return to the previously active level (before it ' + + 'was turned off) if the OnLevel attribute is set to the invalid value (255/previous); or to the specified ' + + 'value of the OnLevel attribute if this value is in the range 0…254. Otherwise, if the ' + + 'MinimumOnLevel is in the range 0…254, the light will be set to the the previously ' + + 'active level (before it was turned off), or the value specified here, whichever is the larger ' + + 'value. For example, if the previous level was 30 and the MinimumOnLevel was 40 then ' + + 'the light would turn on and move to level 40. Conversely, if the previous level was 50, ' + + 'and the MinimumOnLevel was 40, then the light would turn on and move to level 50.', + ), + e.binary('capabilities_forward_phase_control', ea.ALL, true, false).withDescription('The dimmer supports AC forward phase control.'), + e.binary('capabilities_reverse_phase_control', ea.ALL, true, false).withDescription('The dimmer supports AC reverse phase control.'), + e + .binary('capabilities_reactance_discriminator', ea.ALL, true, false) .withDescription('The dimmer is capable of measuring the reactanceto distinguish inductive and capacitive loads.'), - e.binary('capabilities_configurable_curve', ea.ALL, true, false) + e + .binary('capabilities_configurable_curve', ea.ALL, true, false) .withDescription('The dimmer is capable of replacing the built-in, default dimming curve.'), - e.binary('capabilities_overload_detection', ea.ALL, true, false) + e + .binary('capabilities_overload_detection', ea.ALL, true, false) .withDescription('The dimmer is capable of detecting an output overload and shutting the output off.'), - e.binary('status_forward_phase_control', ea.ALL, true, false) + e + .binary('status_forward_phase_control', ea.ALL, true, false) .withDescription('The dimmer is currently operating in AC forward phase control mode.'), - e.binary('status_reverse_phase_control', ea.ALL, true, false) + e + .binary('status_reverse_phase_control', ea.ALL, true, false) .withDescription('The dimmer is currently operating in AC reverse phase control mode.'), - e.binary('status_overload', ea.ALL, true, false) + e + .binary('status_overload', ea.ALL, true, false) .withDescription('The output is currently turned off, because the dimmer has detected an overload.'), - e.binary('status_capacitive_load', ea.ALL, true, false) - .withDescription('The dimmer\'s reactance discriminator had detected a capacitive load.'), - e.binary('status_inductive_load', ea.ALL, true, false) - .withDescription('The dimmer\'s reactance discriminator had detected an inductive load.'), - e.enum('mode_phase_control', ea.ALL, ['automatic', 'forward', 'reverse']) - .withDescription('Configures the dimming technique.')], + e + .binary('status_capacitive_load', ea.ALL, true, false) + .withDescription("The dimmer's reactance discriminator had detected a capacitive load."), + e + .binary('status_inductive_load', ea.ALL, true, false) + .withDescription("The dimmer's reactance discriminator had detected an inductive load."), + e.enum('mode_phase_control', ea.ALL, ['automatic', 'forward', 'reverse']).withDescription('Configures the dimming technique.'), + ], extend: [ ubisysModernExtend.addCustomClusterManuSpecificUbisysDeviceSetup(), ubisysModernExtend.addCustomClusterManuSpecificUbisysDimmerSetup(), @@ -839,9 +957,9 @@ const definitions: Definition[] = [ await reporting.readMeteringMultiplierDivisor(endpoint); await reporting.instantaneousDemand(endpoint); }, - meta: {multiEndpoint: true, multiEndpointSkip: ['state', 'brightness', 'power', 'energy'], multiEndpointEnforce: {'power': 4, 'energy': 4}}, + meta: {multiEndpoint: true, multiEndpointSkip: ['state', 'brightness', 'power', 'energy'], multiEndpointEnforce: {power: 4, energy: 4}}, endpoint: (device) => { - return {'default': 1, 's1': 2, 's2': 3, 'meter': 4}; + return {default: 1, s1: 2, s2: 3, meter: 4}; }, onEvent: async (type, data, device) => { /* @@ -867,42 +985,41 @@ const definitions: Definition[] = [ vendor: 'Ubisys', description: 'Shutter control J1', fromZigbee: [fz.cover_position_tilt, fz.metering, ubisys.fz.configure_device_setup], - toZigbee: [tz.cover_state, tz.cover_position_tilt, tz.metering_power, - ubisys.tz.configure_j1, ubisys.tz.configure_device_setup, - tz.currentsummdelivered], + toZigbee: [ + tz.cover_state, + tz.cover_position_tilt, + tz.metering_power, + ubisys.tz.configure_j1, + ubisys.tz.configure_device_setup, + tz.currentsummdelivered, + ], exposes: (device, options) => { const coverExpose = e.cover(); - const coverType = (device?.getEndpoint(1).getClusterAttributeValue('closuresWindowCovering', 'windowCoveringType') ?? undefined); - switch (coverType) { // cf. Ubisys J1 Technical Reference Manual, chapter 7.2.5.1 Calibration - case 0: // Roller Shade, Lift only - case 1: // Roller Shade two motors, Lift only - case 2: // Roller Shade exterior, Lift only - case 3: // Roller Shade two motors exterior, Lift only - case 4: // Drapery, Lift only - case 5: // Awning, Lift only - case 9: // Projector Screen, Lift only - coverExpose.withPosition(); - break; - case 6: // Shutter, Tilt only - case 7: // Tilt Blind, Tilt only - coverExpose.withTilt(); - break; - case 8: // Tilt Blind, Lift & Tilt - default: - coverExpose.withPosition().withTilt(); - break; + const coverType = device?.getEndpoint(1).getClusterAttributeValue('closuresWindowCovering', 'windowCoveringType') ?? undefined; + switch ( + coverType // cf. Ubisys J1 Technical Reference Manual, chapter 7.2.5.1 Calibration + ) { + case 0: // Roller Shade, Lift only + case 1: // Roller Shade two motors, Lift only + case 2: // Roller Shade exterior, Lift only + case 3: // Roller Shade two motors exterior, Lift only + case 4: // Drapery, Lift only + case 5: // Awning, Lift only + case 9: // Projector Screen, Lift only + coverExpose.withPosition(); + break; + case 6: // Shutter, Tilt only + case 7: // Tilt Blind, Tilt only + coverExpose.withTilt(); + break; + case 8: // Tilt Blind, Lift & Tilt + default: + coverExpose.withPosition().withTilt(); + break; } - return [ - coverExpose, - e.power().withAccess(ea.STATE_GET), - e.energy().withAccess(ea.STATE_GET), - e.linkquality(), - ]; + return [coverExpose, e.power().withAccess(ea.STATE_GET), e.energy().withAccess(ea.STATE_GET), e.linkquality()]; }, - extend: [ - ubisysModernExtend.addCustomClusterManuSpecificUbisysDeviceSetup(), - ubisysModernExtend.addCustomClusterClosuresWindowCovering(), - ], + extend: [ubisysModernExtend.addCustomClusterManuSpecificUbisysDeviceSetup(), ubisysModernExtend.addCustomClusterClosuresWindowCovering()], configure: async (device, coordinatorEndpoint) => { const endpoint1 = device.getEndpoint(1); const endpoint3 = device.getEndpoint(3); @@ -913,9 +1030,9 @@ const definitions: Definition[] = [ await reporting.currentPositionLiftPercentage(endpoint1); }, endpoint: (device) => { - return {'default': 1, 'meter': 3}; + return {default: 1, meter: 3}; }, - meta: {multiEndpointEnforce: {'power': 3, 'energy': 3}}, + meta: {multiEndpointEnforce: {power: 3, energy: 3}}, onEvent: async (type, data, device) => { /* * As per technical doc page 21 section 7.3.4 @@ -938,23 +1055,53 @@ const definitions: Definition[] = [ model: 'C4', vendor: 'Ubisys', description: 'Control unit C4', - fromZigbee: [legacy.fz.ubisys_c4_scenes, legacy.fz.ubisys_c4_onoff, legacy.fz.ubisys_c4_level, legacy.fz.ubisys_c4_cover, - ubisys.fz.configure_device_setup], + fromZigbee: [ + legacy.fz.ubisys_c4_scenes, + legacy.fz.ubisys_c4_onoff, + legacy.fz.ubisys_c4_level, + legacy.fz.ubisys_c4_cover, + ubisys.fz.configure_device_setup, + ], toZigbee: [ubisys.tz.configure_device_setup], exposes: [ e.action([ - 'toggle_s1', 'toggle_s2', 'toggle_s3', 'toggle_s4', 'on_s1', 'on_s2', 'on_s3', 'on_s4', - 'off_s1', 'off_s2', 'off_s3', 'off_s4', 'recall_*_s1', 'recal_*_s2', 'recall_*_s3', 'recal_*_s4', - 'brightness_move_up_s1', 'brightness_move_up_s2', 'brightness_move_up_s3', 'brightness_move_up_s4', - 'brightness_move_down_s1', 'brightness_move_down_s2', 'brightness_move_down_s3', 'brightness_move_down_s4', - 'brightness_stop_s1', 'brightness_stop_s2', 'brightness_stop_s3', 'brightness_stop_s4', - 'cover_open_s5', 'cover_close_s5', 'cover_stop_s5', - 'cover_open_s6', 'cover_close_s6', 'cover_stop_s6', + 'toggle_s1', + 'toggle_s2', + 'toggle_s3', + 'toggle_s4', + 'on_s1', + 'on_s2', + 'on_s3', + 'on_s4', + 'off_s1', + 'off_s2', + 'off_s3', + 'off_s4', + 'recall_*_s1', + 'recal_*_s2', + 'recall_*_s3', + 'recal_*_s4', + 'brightness_move_up_s1', + 'brightness_move_up_s2', + 'brightness_move_up_s3', + 'brightness_move_up_s4', + 'brightness_move_down_s1', + 'brightness_move_down_s2', + 'brightness_move_down_s3', + 'brightness_move_down_s4', + 'brightness_stop_s1', + 'brightness_stop_s2', + 'brightness_stop_s3', + 'brightness_stop_s4', + 'cover_open_s5', + 'cover_close_s5', + 'cover_stop_s5', + 'cover_open_s6', + 'cover_close_s6', + 'cover_stop_s6', ]), ], - extend: [ - ubisysModernExtend.addCustomClusterManuSpecificUbisysDeviceSetup(), - ], + extend: [ubisysModernExtend.addCustomClusterManuSpecificUbisysDeviceSetup()], configure: async (device, coordinatorEndpoint) => { for (const ep of [1, 2, 3, 4]) { await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ['genScenes', 'genOnOff', 'genLevelCtrl']); @@ -965,7 +1112,7 @@ const definitions: Definition[] = [ }, meta: {multiEndpoint: true}, endpoint: (device) => { - return {'s1': 1, 's2': 2, 's3': 3, 's4': 4, 's5': 5, 's6': 6}; + return {s1: 1, s2: 2, s3: 3, s4: 4, s5: 5, s6: 6}; }, ota: ota.ubisys, }, @@ -977,15 +1124,20 @@ const definitions: Definition[] = [ meta: {thermostat: {dontMapPIHeatingDemand: true}}, fromZigbee: [fz.battery, fz.thermostat, fz.thermostat_weekly_schedule], toZigbee: [ - tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint, - tz.thermostat_local_temperature, tz.thermostat_system_mode, - tz.thermostat_weekly_schedule, tz.thermostat_clear_weekly_schedule, - tz.thermostat_running_mode, tz.thermostat_pi_heating_demand, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_unoccupied_heating_setpoint, + tz.thermostat_local_temperature, + tz.thermostat_system_mode, + tz.thermostat_weekly_schedule, + tz.thermostat_clear_weekly_schedule, + tz.thermostat_running_mode, + tz.thermostat_pi_heating_demand, tz.battery_percentage_remaining, ], exposes: [ e.battery().withAccess(ea.STATE_GET), - e.climate() + e + .climate() .withSystemMode(['off', 'heat'], ea.ALL) .withRunningMode(['off', 'heat']) .withSetpoint('occupied_heating_setpoint', 7, 30, 0.5) @@ -1013,15 +1165,10 @@ const definitions: Definition[] = [ // seem to be limited. await reporting.thermostatSystemMode(endpoint); await reporting.thermostatRunningMode(endpoint); - await reporting.thermostatTemperature(endpoint, - {min: 0, max: constants.repInterval.HOUR, change: 50}); - await reporting.thermostatOccupiedHeatingSetpoint(endpoint, - {min: 0, max: constants.repInterval.HOUR, change: 50}); - await reporting.thermostatPIHeatingDemand(endpoint, - {min: 15, max: constants.repInterval.HOUR, change: 1}); - await reporting.batteryPercentageRemaining(endpoint, - {min: constants.repInterval.HOUR, max: 43200, change: 1}); - + await reporting.thermostatTemperature(endpoint, {min: 0, max: constants.repInterval.HOUR, change: 50}); + await reporting.thermostatOccupiedHeatingSetpoint(endpoint, {min: 0, max: constants.repInterval.HOUR, change: 50}); + await reporting.thermostatPIHeatingDemand(endpoint, {min: 15, max: constants.repInterval.HOUR, change: 1}); + await reporting.batteryPercentageRemaining(endpoint, {min: constants.repInterval.HOUR, max: 43200, change: 1}); // read attributes // NOTE: configuring reporting on hvacThermostat seems to trigger an immediate @@ -1034,7 +1181,7 @@ const definitions: Definition[] = [ // NOTE: device checks in every 1h once the device has entered deepsleep // this might be a bit long if you want to set the temperature remotely // update this to every 15 minutes. (value is in 1/4th of a second) - await endpoint.write('genPollCtrl', {'checkinInterval': (4 * 60 * 15)}); + await endpoint.write('genPollCtrl', {checkinInterval: 4 * 60 * 15}); }, ota: ota.ubisys, }, @@ -1046,30 +1193,44 @@ const definitions: Definition[] = [ meta: {thermostat: {dontMapPIHeatingDemand: true}, multiEndpoint: true}, fromZigbee: [fz.on_off, fz.thermostat, fz.thermostat_weekly_schedule], toZigbee: [ - tz.on_off, tz.thermostat_occupied_heating_setpoint, - tz.thermostat_unoccupied_heating_setpoint, tz.thermostat_local_temperature, - tz.thermostat_system_mode, tz.thermostat_weekly_schedule, - tz.thermostat_clear_weekly_schedule, tz.thermostat_running_mode, + tz.on_off, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_unoccupied_heating_setpoint, + tz.thermostat_local_temperature, + tz.thermostat_system_mode, + tz.thermostat_weekly_schedule, + tz.thermostat_clear_weekly_schedule, + tz.thermostat_running_mode, tz.thermostat_pi_heating_demand, ], endpoint: (device) => { return { - 'l1': 11, 'l2': 12, 'l3': 13, 'l4': 14, 'l5': 15, - 'l6': 16, 'l7': 17, 'l8': 18, 'l9': 19, 'l10': 20, - 'default': 21, + l1: 11, + l2: 12, + l3: 13, + l4: 14, + l5: 15, + l6: 16, + l7: 17, + l8: 18, + l9: 19, + l10: 20, + default: 21, }; }, exposes: [ - e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), - e.switch().withEndpoint('l3'), e.switch().withEndpoint('l4'), - e.switch().withEndpoint('l5'), e.switch().withEndpoint('l6'), - e.switch().withEndpoint('l7'), e.switch().withEndpoint('l8'), - e.switch().withEndpoint('l9'), e.switch().withEndpoint('l10'), - ], - extend: [ - ubisysModernExtend.addCustomClusterHvacThermostat(), - ubisysModernExtend.addCustomClusterGenLevelCtrl(), + e.switch().withEndpoint('l1'), + e.switch().withEndpoint('l2'), + e.switch().withEndpoint('l3'), + e.switch().withEndpoint('l4'), + e.switch().withEndpoint('l5'), + e.switch().withEndpoint('l6'), + e.switch().withEndpoint('l7'), + e.switch().withEndpoint('l8'), + e.switch().withEndpoint('l9'), + e.switch().withEndpoint('l10'), ], + extend: [ubisysModernExtend.addCustomClusterHvacThermostat(), ubisysModernExtend.addCustomClusterGenLevelCtrl()], configure: async (device, coordinatorEndpoint) => { // setup ep 11-20 as on/off switches const heaterCoolerBinds = ['genOnOff']; diff --git a/src/devices/uhome.ts b/src/devices/uhome.ts index 4bfc2a38d8ed6..4c75055637f5e 100644 --- a/src/devices/uhome.ts +++ b/src/devices/uhome.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; import * as exposes from '../lib/exposes'; -import * as reporting from '../lib/reporting'; import * as ota from '../lib/ota'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/universal_electronics_inc.ts b/src/devices/universal_electronics_inc.ts index 49bc7dba78450..d58bdd6e3796e 100644 --- a/src/devices/universal_electronics_inc.ts +++ b/src/devices/universal_electronics_inc.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; import * as globalStore from '../lib/store'; @@ -46,17 +46,30 @@ const definitions: Definition[] = [ vendor: 'Universal Electronics Inc', description: 'Xfinity security keypad', meta: {battery: {voltageToPercentage: '3V_2100'}}, - fromZigbee: [fz.command_arm_with_transaction, fz.temperature, fz.battery, fz.ias_occupancy_alarm_1, fz.identify, - fz.ias_contact_alarm_1, fz.ias_ace_occupancy_with_timeout], + fromZigbee: [ + fz.command_arm_with_transaction, + fz.temperature, + fz.battery, + fz.ias_occupancy_alarm_1, + fz.identify, + fz.ias_contact_alarm_1, + fz.ias_ace_occupancy_with_timeout, + ], toZigbee: [tz.arm_mode], - exposes: [e.battery(), e.battery_voltage(), e.occupancy(), e.battery_low(), e.tamper(), e.presence(), - e.contact(), e.temperature(), + exposes: [ + e.battery(), + e.battery_voltage(), + e.occupancy(), + e.battery_low(), + e.tamper(), + e.presence(), + e.contact(), + e.temperature(), e.numeric('action_code', ea.STATE).withDescription('Pin code introduced.'), e.numeric('action_transaction', ea.STATE).withDescription('Last action transaction number.'), e.text('action_zone', ea.STATE).withDescription('Alarm zone. Default value 0'), - e.action([ - 'disarm', 'arm_day_zones', 'identify', 'arm_night_zones', 'arm_all_zones', 'exit_delay', 'emergency', - ])], + e.action(['disarm', 'arm_day_zones', 'identify', 'arm_night_zones', 'arm_all_zones', 'exit_delay', 'emergency']), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const clusters = ['msTemperatureMeasurement', 'genPowerCfg', 'ssIasZone', 'ssIasAce', 'genBasic', 'genIdentify']; @@ -65,15 +78,19 @@ const definitions: Definition[] = [ await reporting.batteryVoltage(endpoint); }, onEvent: async (type, data, device) => { - if (type === 'message' && data.type === 'commandGetPanelStatus' && data.cluster === 'ssIasAce' && - globalStore.hasValue(device.getEndpoint(1), 'panelStatus')) { + if ( + type === 'message' && + data.type === 'commandGetPanelStatus' && + data.cluster === 'ssIasAce' && + globalStore.hasValue(device.getEndpoint(1), 'panelStatus') + ) { const payload = { panelstatus: globalStore.getValue(device.getEndpoint(1), 'panelStatus'), - secondsremain: 0x00, audiblenotif: 0x00, alarmstatus: 0x00, + secondsremain: 0x00, + audiblenotif: 0x00, + alarmstatus: 0x00, }; - await device.getEndpoint(1).commandResponse( - 'ssIasAce', 'getPanelStatusRsp', payload, {}, data.meta.zclTransactionSequenceNumber, - ); + await device.getEndpoint(1).commandResponse('ssIasAce', 'getPanelStatusRsp', payload, {}, data.meta.zclTransactionSequenceNumber); } }, }, @@ -84,19 +101,29 @@ const definitions: Definition[] = [ description: 'Xfinity security keypad', meta: {battery: {voltageToPercentage: '3V_2100'}}, fromZigbee: [ - fz.command_arm_with_transaction, fz.temperature, fz.battery, fz.ias_occupancy_alarm_1, - fz.identify, fz.ias_contact_alarm_1, fz.ias_ace_occupancy_with_timeout, + fz.command_arm_with_transaction, + fz.temperature, + fz.battery, + fz.ias_occupancy_alarm_1, + fz.identify, + fz.ias_contact_alarm_1, + fz.ias_ace_occupancy_with_timeout, ], toZigbee: [tz.arm_mode], exposes: [ - e.battery(), e.battery_voltage(), e.occupancy(), e.battery_low(), - e.tamper(), e.presence(), e.contact(), e.temperature(), + e.battery(), + e.battery_voltage(), + e.occupancy(), + e.battery_low(), + e.tamper(), + e.presence(), + e.contact(), + e.temperature(), e.numeric('action_code', ea.STATE).withDescription('Pin code introduced.'), e.numeric('action_transaction', ea.STATE).withDescription('Last action transaction number.'), e.text('action_zone', ea.STATE).withDescription('Alarm zone. Default value 0'), - e.action([ - 'disarm', 'arm_day_zones', 'identify', 'arm_night_zones', 'arm_all_zones', 'exit_delay', 'emergency', - ])], + e.action(['disarm', 'arm_day_zones', 'identify', 'arm_night_zones', 'arm_all_zones', 'exit_delay', 'emergency']), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const clusters = ['msTemperatureMeasurement', 'genPowerCfg', 'ssIasZone', 'ssIasAce', 'genBasic', 'genIdentify']; @@ -105,15 +132,19 @@ const definitions: Definition[] = [ await reporting.batteryVoltage(endpoint); }, onEvent: async (type, data, device) => { - if (type === 'message' && data.type === 'commandGetPanelStatus' && data.cluster === 'ssIasAce' && - globalStore.hasValue(device.getEndpoint(1), 'panelStatus')) { + if ( + type === 'message' && + data.type === 'commandGetPanelStatus' && + data.cluster === 'ssIasAce' && + globalStore.hasValue(device.getEndpoint(1), 'panelStatus') + ) { const payload = { panelstatus: globalStore.getValue(device.getEndpoint(1), 'panelStatus'), - secondsremain: 0x00, audiblenotif: 0x00, alarmstatus: 0x00, + secondsremain: 0x00, + audiblenotif: 0x00, + alarmstatus: 0x00, }; - await device.getEndpoint(1).commandResponse( - 'ssIasAce', 'getPanelStatusRsp', payload, {}, data.meta.zclTransactionSequenceNumber, - ); + await device.getEndpoint(1).commandResponse('ssIasAce', 'getPanelStatusRsp', payload, {}, data.meta.zclTransactionSequenceNumber); } }, }, diff --git a/src/devices/vbled.ts b/src/devices/vbled.ts index a8fe810a3019a..7852d86baf70e 100644 --- a/src/devices/vbled.ts +++ b/src/devices/vbled.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/vesternet.ts b/src/devices/vesternet.ts index b16dec890d9a3..aa92883a60f19 100644 --- a/src/devices/vesternet.ts +++ b/src/devices/vesternet.ts @@ -1,14 +1,17 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; import {electricityMeter, light} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ { - fingerprint: [{modelID: 'HK-SL-DIM-A', softwareBuildID: '2.5.3_r52'}, {modelID: 'HK-SL-DIM-A', softwareBuildID: '2.9.2_r54'}], + fingerprint: [ + {modelID: 'HK-SL-DIM-A', softwareBuildID: '2.5.3_r52'}, + {modelID: 'HK-SL-DIM-A', softwareBuildID: '2.9.2_r54'}, + ], model: 'VES-ZB-DIM-004', vendor: 'Vesternet', description: 'Zigbee dimmer', @@ -56,18 +59,51 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'ZGRC-KEY-013', softwareBuildID: '2.5.3_r20'}, {modelID: 'ZGRC-KEY-013', softwareBuildID: '2.7.6_r25'}], + fingerprint: [ + {modelID: 'ZGRC-KEY-013', softwareBuildID: '2.5.3_r20'}, + {modelID: 'ZGRC-KEY-013', softwareBuildID: '2.7.6_r25'}, + ], model: 'VES-ZB-REM-013', vendor: 'Vesternet', description: 'Zigbee remote control - 12 button', fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.command_recall, fz.battery, fz.ignore_genOta], - exposes: [e.battery(), e.action([ - 'on_1', 'off_1', 'stop_1', 'brightness_move_up_1', 'brightness_move_down_1', 'brightness_stop_1', - 'on_2', 'off_2', 'stop_2', 'brightness_move_up_2', 'brightness_move_down_2', 'brightness_stop_2', - 'on_3', 'off_3', 'stop_3', 'brightness_move_up_3', 'brightness_move_down_3', 'brightness_stop_3', - 'on_4', 'off_4', 'stop_4', 'brightness_move_up_4', 'brightness_move_down_4', 'brightness_stop_4', - 'recall_1_1', 'recall_1_2', 'recall_1_3', 'recall_1_4', - 'recall_2_1', 'recall_2_2', 'recall_2_3', 'recall_2_4'])], + exposes: [ + e.battery(), + e.action([ + 'on_1', + 'off_1', + 'stop_1', + 'brightness_move_up_1', + 'brightness_move_down_1', + 'brightness_stop_1', + 'on_2', + 'off_2', + 'stop_2', + 'brightness_move_up_2', + 'brightness_move_down_2', + 'brightness_stop_2', + 'on_3', + 'off_3', + 'stop_3', + 'brightness_move_up_3', + 'brightness_move_down_3', + 'brightness_stop_3', + 'on_4', + 'off_4', + 'stop_4', + 'brightness_move_up_4', + 'brightness_move_down_4', + 'brightness_stop_4', + 'recall_1_1', + 'recall_1_2', + 'recall_1_3', + 'recall_1_4', + 'recall_2_1', + 'recall_2_2', + 'recall_2_3', + 'recall_2_4', + ]), + ], toZigbee: [], meta: {multiEndpoint: true, battery: {dontDividePercentage: true}, publishDuplicateTransaction: true}, whiteLabel: [{vendor: 'Sunricher', model: 'SR-ZG9001K12-DIM-Z4'}], @@ -80,7 +116,10 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'HK-SL-RELAY-A', softwareBuildID: '2.5.3_r47'}, {modelID: 'HK-SL-RELAY-A', softwareBuildID: '2.9.2_r54'}], + fingerprint: [ + {modelID: 'HK-SL-RELAY-A', softwareBuildID: '2.5.3_r47'}, + {modelID: 'HK-SL-RELAY-A', softwareBuildID: '2.9.2_r54'}, + ], model: 'VES-ZB-SWI-005', vendor: 'Vesternet', description: 'Zigbee switch', @@ -95,17 +134,27 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'ON/OFF(2CH)', softwareBuildID: '2.5.3_r2'}, {modelID: 'ON/OFF(2CH)', softwareBuildID: '2.9.2_r3'}], + fingerprint: [ + {modelID: 'ON/OFF(2CH)', softwareBuildID: '2.5.3_r2'}, + {modelID: 'ON/OFF(2CH)', softwareBuildID: '2.9.2_r3'}, + ], model: 'VES-ZB-SWI-015', vendor: 'Vesternet', description: 'Zigbee 2 channel switch', fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, fz.power_on_behavior, fz.ignore_genOta], toZigbee: [tz.on_off, tz.power_on_behavior], - exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), e.power(), e.current(), e.voltage(), - e.energy(), e.power_on_behavior(['off', 'on', 'previous'])], + exposes: [ + e.switch().withEndpoint('l1'), + e.switch().withEndpoint('l2'), + e.power(), + e.current(), + e.voltage(), + e.energy(), + e.power_on_behavior(['off', 'on', 'previous']), + ], whiteLabel: [{vendor: 'Sunricher', model: 'SR-ZG9101SAC-HP-SWITCH-2CH'}], endpoint: (device) => { - return {'l1': 1, 'l2': 2}; + return {l1: 1, l2: 2}; }, meta: {multiEndpoint: true, multiEndpointSkip: ['power', 'energy', 'current', 'voltage']}, configure: async (device, coordinatorEndpoint) => { @@ -137,13 +186,15 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'ZG2833K2_EU07', softwareBuildID: '2.5.3_r20'}, {modelID: 'ZG2833K2_EU07', softwareBuildID: '2.7.6_r25'}], + fingerprint: [ + {modelID: 'ZG2833K2_EU07', softwareBuildID: '2.5.3_r20'}, + {modelID: 'ZG2833K2_EU07', softwareBuildID: '2.7.6_r25'}, + ], model: 'VES-ZB-WAL-006', vendor: 'Vesternet', description: 'Zigbee wall controller - 2 button', fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.battery, fz.ignore_genOta], - exposes: [e.battery(), e.action([ - 'on_1', 'off_1', 'stop_1', 'brightness_move_up_1', 'brightness_move_down_1', 'brightness_stop_1'])], + exposes: [e.battery(), e.action(['on_1', 'off_1', 'stop_1', 'brightness_move_up_1', 'brightness_move_down_1', 'brightness_stop_1'])], toZigbee: [], meta: {multiEndpoint: true, battery: {dontDividePercentage: true}}, whiteLabel: [{vendor: 'Sunricher', model: 'SR-ZG9001K2-DIM2'}], @@ -153,14 +204,31 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'ZG2833K4_EU06', softwareBuildID: '2.5.3_r20'}, {modelID: 'ZG2833K4_EU06', softwareBuildID: '2.7.6_r25'}], + fingerprint: [ + {modelID: 'ZG2833K4_EU06', softwareBuildID: '2.5.3_r20'}, + {modelID: 'ZG2833K4_EU06', softwareBuildID: '2.7.6_r25'}, + ], model: 'VES-ZB-WAL-011', vendor: 'Vesternet', description: 'Zigbee wall controller - 4 button', fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.battery, fz.ignore_genOta], - exposes: [e.battery(), e.action([ - 'on_1', 'off_1', 'stop_1', 'brightness_move_up_1', 'brightness_move_down_1', 'brightness_stop_1', - 'on_2', 'off_2', 'stop_2', 'brightness_move_up_2', 'brightness_move_down_2', 'brightness_stop_2'])], + exposes: [ + e.battery(), + e.action([ + 'on_1', + 'off_1', + 'stop_1', + 'brightness_move_up_1', + 'brightness_move_down_1', + 'brightness_stop_1', + 'on_2', + 'off_2', + 'stop_2', + 'brightness_move_up_2', + 'brightness_move_down_2', + 'brightness_stop_2', + ]), + ], toZigbee: [], meta: {multiEndpoint: true, battery: {dontDividePercentage: true}}, whiteLabel: [{vendor: 'Sunricher', model: 'SR-ZG9001K4-DIM2'}], @@ -171,16 +239,43 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'ZG2833K8_EU05', softwareBuildID: '2.5.3_r20'}, {modelID: 'ZG2833K8_EU05', softwareBuildID: '2.7.6_r25'}], + fingerprint: [ + {modelID: 'ZG2833K8_EU05', softwareBuildID: '2.5.3_r20'}, + {modelID: 'ZG2833K8_EU05', softwareBuildID: '2.7.6_r25'}, + ], model: 'VES-ZB-WAL-012', vendor: 'Vesternet', description: 'Zigbee wall controller - 8 button', fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.battery, fz.ignore_genOta], - exposes: [e.battery(), e.action([ - 'on_1', 'off_1', 'stop_1', 'brightness_move_up_1', 'brightness_move_down_1', 'brightness_stop_1', - 'on_2', 'off_2', 'stop_2', 'brightness_move_up_2', 'brightness_move_down_2', 'brightness_stop_2', - 'on_3', 'off_3', 'stop_3', 'brightness_move_up_3', 'brightness_move_down_3', 'brightness_stop_3', - 'on_4', 'off_4', 'stop_4', 'brightness_move_up_4', 'brightness_move_down_4', 'brightness_stop_4'])], + exposes: [ + e.battery(), + e.action([ + 'on_1', + 'off_1', + 'stop_1', + 'brightness_move_up_1', + 'brightness_move_down_1', + 'brightness_stop_1', + 'on_2', + 'off_2', + 'stop_2', + 'brightness_move_up_2', + 'brightness_move_down_2', + 'brightness_stop_2', + 'on_3', + 'off_3', + 'stop_3', + 'brightness_move_up_3', + 'brightness_move_down_3', + 'brightness_stop_3', + 'on_4', + 'off_4', + 'stop_4', + 'brightness_move_up_4', + 'brightness_move_down_4', + 'brightness_stop_4', + ]), + ], toZigbee: [], meta: {multiEndpoint: true, battery: {dontDividePercentage: true}}, whiteLabel: [{vendor: 'Sunricher', model: 'SR-ZG9001K8-DIM'}], diff --git a/src/devices/viessmann.ts b/src/devices/viessmann.ts index 4a38bf1e1e3df..47f1fe4ab31f5 100644 --- a/src/devices/viessmann.ts +++ b/src/devices/viessmann.ts @@ -1,8 +1,9 @@ import {Zcl} from 'zigbee-herdsman'; -import * as exposes from '../lib/exposes'; + import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; import * as reporting from '../lib/reporting'; import {Definition} from '../lib/types'; const e = exposes.presets; @@ -33,19 +34,27 @@ const definitions: Definition[] = [ vendor: 'Viessmann', description: 'ViCare radiator thermostat valve', fromZigbee: [legacy.fz.viessmann_thermostat_att_report, fz.battery, legacy.fz.hvac_user_interface], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_occupied_heating_setpoint, tz.thermostat_control_sequence_of_operation, - tz.thermostat_system_mode, tz.thermostat_keypad_lockout, tz.viessmann_window_open, tz.viessmann_window_open_force, - tz.viessmann_assembly_mode, tz.thermostat_weekly_schedule, tz.thermostat_clear_weekly_schedule, + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_control_sequence_of_operation, + tz.thermostat_system_mode, + tz.thermostat_keypad_lockout, + tz.viessmann_window_open, + tz.viessmann_window_open_force, + tz.viessmann_assembly_mode, + tz.thermostat_weekly_schedule, + tz.thermostat_clear_weekly_schedule, ], exposes: [ - e.climate().withSetpoint('occupied_heating_setpoint', 7, 30, 1) + e + .climate() + .withSetpoint('occupied_heating_setpoint', 7, 30, 1) .withLocalTemperature() .withSystemMode(['heat', 'sleep']) .withWeeklySchedule(['heat']), - e.binary('window_open', ea.STATE_GET, true, false) - .withDescription('Detected by sudden temperature drop or set manually.'), - e.binary('window_open_force', ea.ALL, true, false) - .withDescription('Manually set window_open, ~1 minute to take affect.'), + e.binary('window_open', ea.STATE_GET, true, false).withDescription('Detected by sudden temperature drop or set manually.'), + e.binary('window_open_force', ea.ALL, true, false).withDescription('Manually set window_open, ~1 minute to take affect.'), e.keypad_lockout(), e.battery(), ], @@ -61,8 +70,11 @@ const definitions: Definition[] = [ await reporting.thermostatPIHeatingDemand(endpoint); // manufacturer attributes - await endpoint.configureReporting('hvacThermostat', [{attribute: 'viessmannWindowOpenInternal', minimumReportInterval: 60, - maximumReportInterval: 3600, reportableChange: 1}], options); + await endpoint.configureReporting( + 'hvacThermostat', + [{attribute: 'viessmannWindowOpenInternal', minimumReportInterval: 60, maximumReportInterval: 3600, reportableChange: 1}], + options, + ); // read window_open_force, we don't need reporting as it cannot be set physically on the device await endpoint.read('hvacThermostat', ['viessmannWindowOpenForce'], options); diff --git a/src/devices/villeroy_boch.ts b/src/devices/villeroy_boch.ts index e49ac8be13cc2..3e005ab621420 100644 --- a/src/devices/villeroy_boch.ts +++ b/src/devices/villeroy_boch.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/vimar.ts b/src/devices/vimar.ts index afb6389564016..41a2a5943f1fc 100644 --- a/src/devices/vimar.ts +++ b/src/devices/vimar.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; import {onOff, light} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ @@ -81,7 +81,9 @@ const definitions: Definition[] = [ tz.thermostat_system_mode, ], exposes: [ - e.climate().withSetpoint('occupied_heating_setpoint', 4, 40, 0.1) + e + .climate() + .withSetpoint('occupied_heating_setpoint', 4, 40, 0.1) .withSetpoint('occupied_cooling_setpoint', 4, 40, 0.1) .withLocalTemperature() .withSystemMode(['heat', 'cool']), diff --git a/src/devices/visonic.ts b/src/devices/visonic.ts index 17694cdfb414a..db00481870f7e 100644 --- a/src/devices/visonic.ts +++ b/src/devices/visonic.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/vrey.ts b/src/devices/vrey.ts index c7248edd67822..4e55196095a6b 100644 --- a/src/devices/vrey.ts +++ b/src/devices/vrey.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/wally.ts b/src/devices/wally.ts index ad163e84f886f..81627836dd73e 100644 --- a/src/devices/wally.ts +++ b/src/devices/wally.ts @@ -1,7 +1,7 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ @@ -10,8 +10,7 @@ const definitions: Definition[] = [ model: 'U02I007C.01', vendor: 'Wally', description: 'WallyHome multi-sensor', - fromZigbee: [fz.command_on, fz.command_off, fz.battery, fz.temperature, fz.humidity, fz.U02I007C01_contact, - fz.U02I007C01_water_leak], + fromZigbee: [fz.command_on, fz.command_off, fz.battery, fz.temperature, fz.humidity, fz.U02I007C01_contact, fz.U02I007C01_water_leak], exposes: [e.battery(), e.temperature(), e.humidity(), e.action(['on', 'off']), e.contact(), e.water_leak()], toZigbee: [], configure: async (device, coordinatorEndpoint) => { diff --git a/src/devices/waxman.ts b/src/devices/waxman.ts index 04baa3546e7c4..b4141f094d0cb 100644 --- a/src/devices/waxman.ts +++ b/src/devices/waxman.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/weiser.ts b/src/devices/weiser.ts index 1ab3e4bbd7c0c..4cdbaf5b7b086 100644 --- a/src/devices/weiser.ts +++ b/src/devices/weiser.ts @@ -1,7 +1,7 @@ -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; import * as constants from '../lib/constants'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; import {Definition} from '../lib/types'; const e = exposes.presets; @@ -24,7 +24,8 @@ const definitions: Definition[] = [ // Note - Keypad triggered deletions do not cause a zigbee event, though Adds work fine. onEvent: async (type, data, device) => { // When we receive a code updated message, lets read the new value - if (data.type === 'commandProgrammingEventNotification' && + if ( + data.type === 'commandProgrammingEventNotification' && data.cluster === 'closuresDoorLock' && data.data && data.data.userid !== undefined && @@ -53,7 +54,8 @@ const definitions: Definition[] = [ // Note - Keypad triggered deletions do not cause a zigbee event, though Adds work fine. onEvent: async (type, data, device) => { // When we receive a code updated message, lets read the new value - if (data.type === 'commandProgrammingEventNotification' && + if ( + data.type === 'commandProgrammingEventNotification' && data.cluster === 'closuresDoorLock' && data.data && data.data.userid !== undefined && diff --git a/src/devices/weten.ts b/src/devices/weten.ts index 60ea1d01d9aa4..cc193727e5590 100644 --- a/src/devices/weten.ts +++ b/src/devices/weten.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; import fz from '../converters/fromZigbee'; -import * as tuya from '../lib/tuya'; import * as exposes from '../lib/exposes'; import {onOff} from '../lib/modernExtend'; +import * as tuya from '../lib/tuya'; +import {Definition} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; @@ -32,17 +32,19 @@ const definitions: Definition[] = [ e.binary('rf_remote_control', ea.STATE_SET, 'ON', 'OFF').withDescription('Enables/disables RF 433 remote control').withCategory('config'), e.binary('buzzer_feedback', ea.STATE_SET, 'ON', 'OFF').withDescription('Enable buzzer feedback.').withCategory('config'), e.enum('power_on_behavior', ea.STATE_SET, ['on', 'off']).withDescription('Power On Behavior').withCategory('config'), - e.binary('child_lock', ea.STATE_SET, 'LOCK', 'UNLOCK').withDescription('Enables/disables physical input on the device') + e + .binary('child_lock', ea.STATE_SET, 'LOCK', 'UNLOCK') + .withDescription('Enables/disables physical input on the device') .withCategory('config'), ], meta: { tuyaDatapoints: [ [1, 'state', tuya.valueConverter.onOff], - [101, 'restart_mode', tuya.valueConverterBasic.lookup({'restart': tuya.enum(0), 'force restart': tuya.enum(1), '–': tuya.enum(2)})], - [102, 'rf_remote_control', tuya.valueConverterBasic.lookup({'ON': tuya.enum(0), 'OFF': tuya.enum(1)})], + [101, 'restart_mode', tuya.valueConverterBasic.lookup({restart: tuya.enum(0), 'force restart': tuya.enum(1), '–': tuya.enum(2)})], + [102, 'rf_remote_control', tuya.valueConverterBasic.lookup({ON: tuya.enum(0), OFF: tuya.enum(1)})], [103, 'rf_pairing', tuya.valueConverter.onOff], [104, 'buzzer_feedback', tuya.valueConverter.onOff], - [105, 'power_on_behavior', tuya.valueConverterBasic.lookup({'off': tuya.enum(0), 'on': tuya.enum(1)})], + [105, 'power_on_behavior', tuya.valueConverterBasic.lookup({off: tuya.enum(0), on: tuya.enum(1)})], [106, 'child_lock', tuya.valueConverter.lockUnlock], ], }, diff --git a/src/devices/wirenboard.ts b/src/devices/wirenboard.ts index 23c69fce05c6f..ad6bf49399319 100644 --- a/src/devices/wirenboard.ts +++ b/src/devices/wirenboard.ts @@ -1,17 +1,14 @@ -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; import * as constants from '../lib/constants'; +import * as exposes from '../lib/exposes'; +import * as reporting from '../lib/reporting'; import {Configure, Definition, Fz, KeyValueAny, ModernExtend, Tz} from '../lib/types'; const e = exposes.presets; const ea = exposes.access; -import {assertString, getFromLookup, getOptions, toNumber} from '../lib/utils'; import * as modernExtend from '../lib/modernExtend'; -const { - forcePowerSource, temperature, humidity, co2, deviceEndpoints, - onOff, illuminance, occupancy, ota, -} = modernExtend; +import {assertString, getFromLookup, getOptions, toNumber} from '../lib/utils'; +const {forcePowerSource, temperature, humidity, co2, deviceEndpoints, onOff, illuminance, occupancy, ota} = modernExtend; const sprutCode = 0x6666; const manufacturerOptions = {manufacturerCode: sprutCode}; @@ -122,41 +119,39 @@ const tzLocal = { key: ['play_store', 'learn_start', 'learn_stop', 'clear_store', 'play_ram', 'learn_ram_start', 'learn_ram_stop'], convertSet: async (entity, key, value: KeyValueAny, meta) => { const options = { - frameType: 0, manufacturerCode: sprutCode, disableDefaultResponse: true, - disableResponse: true, reservedBits: 0, direction: 0, writeUndiv: false, + frameType: 0, + manufacturerCode: sprutCode, + disableDefaultResponse: true, + disableResponse: true, + reservedBits: 0, + direction: 0, + writeUndiv: false, // @ts-expect-error transactionSequenceNumber: null, }; switch (key) { - case 'play_store': - await entity.command('sprutIrBlaster', 'playStore', - {param: value['rom']}, options); - break; - case 'learn_start': - await entity.command('sprutIrBlaster', 'learnStart', - {value: value['rom']}, options); - break; - case 'learn_stop': - await entity.command('sprutIrBlaster', 'learnStop', - {value: value['rom']}, options); - break; - case 'clear_store': - await entity.command('sprutIrBlaster', 'clearStore', - {}, options); - break; - case 'play_ram': - await entity.command('sprutIrBlaster', 'playRam', - {}, options); - break; - case 'learn_ram_start': - await entity.command('sprutIrBlaster', 'learnRamStart', - {}, options); - break; - case 'learn_ram_stop': - await entity.command('sprutIrBlaster', 'learnRamStop', - {}, options); - break; + case 'play_store': + await entity.command('sprutIrBlaster', 'playStore', {param: value['rom']}, options); + break; + case 'learn_start': + await entity.command('sprutIrBlaster', 'learnStart', {value: value['rom']}, options); + break; + case 'learn_stop': + await entity.command('sprutIrBlaster', 'learnStop', {value: value['rom']}, options); + break; + case 'clear_store': + await entity.command('sprutIrBlaster', 'clearStore', {}, options); + break; + case 'play_ram': + await entity.command('sprutIrBlaster', 'playRam', {}, options); + break; + case 'learn_ram_start': + await entity.command('sprutIrBlaster', 'learnRamStart', {}, options); + break; + case 'learn_ram_stop': + await entity.command('sprutIrBlaster', 'learnRamStop', {}, options); + break; } }, } satisfies Tz.Converter, @@ -189,7 +184,7 @@ const tzLocal = { let number = toNumber(value, 'occupancy_sensitivity'); number *= 1; const options = getOptions(meta.mapped, entity, manufacturerOptions); - await entity.write('msOccupancySensing', {'sprutOccupancySensitivity': number}, options); + await entity.write('msOccupancySensing', {sprutOccupancySensitivity: number}, options); return {state: {[key]: number}}; }, convertGet: async (entity, key, meta) => { @@ -202,7 +197,7 @@ const tzLocal = { let number = toNumber(value, 'noise_detect_level'); number *= 1; const options = getOptions(meta.mapped, entity, manufacturerOptions); - await entity.write('sprutNoise', {'noiseDetectLevel': number}, options); + await entity.write('sprutNoise', {noiseDetectLevel: number}, options); return {state: {[key]: number}}; }, convertGet: async (entity, key, meta) => { @@ -216,7 +211,7 @@ const tzLocal = { number *= 1; const newValue = number * 100.0; const options = getOptions(meta.mapped, entity, manufacturerOptions); - await entity.write('msTemperatureMeasurement', {'sprutTemperatureOffset': newValue}, options); + await entity.write('msTemperatureMeasurement', {sprutTemperatureOffset: newValue}, options); return {state: {[key]: number}}; }, } satisfies Tz.Converter, @@ -229,7 +224,6 @@ const tzLocal = { const options = getOptions(meta.mapped, entity, manufacturerOptions); await entity.write('msCO2', {[getFromLookup(key, co2Lookup)]: newValue}, options); - return {state: {[key]: value}}; }, convertGet: async (entity, key, meta) => { @@ -243,7 +237,7 @@ const tzLocal = { assertString(value, 'th_heater'); newValue = switchActionValues.indexOf(value); const options = getOptions(meta.mapped, entity, manufacturerOptions); - await entity.write('msRelativeHumidity', {'sprutHeater': newValue}, options); + await entity.write('msRelativeHumidity', {sprutHeater: newValue}, options); return {state: {[key]: value}}; }, @@ -254,191 +248,209 @@ const tzLocal = { }; const sprutModernExtend = { - sprutActivityIndicator: (args?: Partial) => modernExtend.binary({ - name: 'activity_led', - cluster: 'genBinaryOutput', - attribute: 'presentValue', - description: 'Controls green activity LED', - reporting: {attribute: 'presentValue', min: 'MIN', max: 'MAX', change: 1}, - valueOn: [true, 1], - valueOff: [false, 0], - access: 'ALL', - entityCategory: 'config', - ...args, - }), - sprutTemperatureOffset: (args?: Partial) => modernExtend.numeric({ - name: 'temperature_offset', - cluster: 'msTemperatureMeasurement', - attribute: 'sprutTemperatureOffset', - description: 'Self-heating compensation. The compensation value is subtracted from the measured temperature (default: 0)', - valueMin: -10, - valueMax: 10, - unit: '°C', - scale: 100, - access: 'ALL', - entityCategory: 'config', - zigbeeCommandOptions: manufacturerOptions, - ...args, - }), - sprutThHeater: (args?: Partial) => modernExtend.binary({ - name: 'th_heater', - cluster: 'msRelativeHumidity', - attribute: 'sprutHeater', - description: 'Turn on when working in conditions of high humidity (more than 70 %, RH) or condensation, ' + - 'if the sensor shows 0 or 100 %.', - valueOn: [true, 1], - valueOff: [false, 0], - access: 'ALL', - entityCategory: 'config', - zigbeeCommandOptions: manufacturerOptions, - ...args, - }), - sprutOccupancyLevel: (args?: Partial) => modernExtend.numeric({ - name: 'occupancy_level', - cluster: 'msOccupancySensing', - attribute: 'sprutOccupancyLevel', - reporting: {min: '10_SECONDS', max: '1_MINUTE', change: 5}, - description: 'Measured occupancy level', - access: 'STATE_GET', - entityCategory: 'diagnostic', - ...args, - }), - sprutOccupancyTimeout: (args?: Partial) => modernExtend.numeric({ - name: 'occupancy_timeout', - cluster: 'msOccupancySensing', - attribute: 'pirOToUDelay', - description: 'Time in seconds after which occupancy is cleared after detecting it (default: 60)', - valueMin: 0, - valueMax: 2000, - unit: 's', - access: 'ALL', - entityCategory: 'config', - ...args, - }), - sprutOccupancySensitivity: (args?: Partial) => modernExtend.numeric({ - name: 'occupancy_sensitivity', - cluster: 'msOccupancySensing', - attribute: 'sprutOccupancySensitivity', - description: 'If the sensor is triggered by the slightest movement, reduce the sensitivity, ' + - 'otherwise increase it (default: 50)', - valueMin: 0, - valueMax: 2000, - access: 'ALL', - entityCategory: 'config', - zigbeeCommandOptions: manufacturerOptions, - ...args, - }), - sprutNoise: (args?: Partial) => modernExtend.numeric({ - name: 'noise', - cluster: 'sprutNoise', - attribute: 'noise', - reporting: {min: '10_SECONDS', max: '1_MINUTE', change: 5}, - description: 'Measured noise level', - unit: 'dBA', - precision: 2, - access: 'STATE_GET', - entityCategory: 'diagnostic', - ...args, - }), - sprutNoiseDetectLevel: (args?: Partial) => modernExtend.numeric({ - name: 'noise_detect_level', - cluster: 'sprutNoise', - attribute: 'noiseDetectLevel', - description: 'The minimum noise level at which the detector will work (default: 50)', - valueMin: 0, - valueMax: 150, - unit: 'dBA', - access: 'ALL', - entityCategory: 'config', - zigbeeCommandOptions: manufacturerOptions, - ...args, - }), - sprutNoiseDetected: (args?: Partial) => modernExtend.binary({ - name: 'noise_detected', - cluster: 'sprutNoise', - attribute: 'noiseDetected', - valueOn: [true, 1], - valueOff: [false, 0], - description: 'Indicates whether the device detected noise', - access: 'STATE_GET', - ...args, - }), - sprutNoiseTimeout: (args?: Partial) => modernExtend.numeric({ - name: 'noise_timeout', - cluster: 'sprutNoise', - attribute: 'noiseAfterDetectDelay', - description: 'Time in seconds after which noise is cleared after detecting it (default: 60)', - valueMin: 0, - valueMax: 2000, - unit: 's', - access: 'ALL', - entityCategory: 'config', - ...args, - }), - sprutVoc: (args?: Partial) => modernExtend.numeric({ - name: 'voc', - label: 'VOC', - cluster: 'sprutVoc', - attribute: 'voc', - reporting: {min: '10_SECONDS', max: '1_MINUTE', change: 10}, - description: 'Measured VOC level', - unit: 'µg/m³', - access: 'STATE_GET', - ...args, - }), + sprutActivityIndicator: (args?: Partial) => + modernExtend.binary({ + name: 'activity_led', + cluster: 'genBinaryOutput', + attribute: 'presentValue', + description: 'Controls green activity LED', + reporting: {attribute: 'presentValue', min: 'MIN', max: 'MAX', change: 1}, + valueOn: [true, 1], + valueOff: [false, 0], + access: 'ALL', + entityCategory: 'config', + ...args, + }), + sprutTemperatureOffset: (args?: Partial) => + modernExtend.numeric({ + name: 'temperature_offset', + cluster: 'msTemperatureMeasurement', + attribute: 'sprutTemperatureOffset', + description: 'Self-heating compensation. The compensation value is subtracted from the measured temperature (default: 0)', + valueMin: -10, + valueMax: 10, + unit: '°C', + scale: 100, + access: 'ALL', + entityCategory: 'config', + zigbeeCommandOptions: manufacturerOptions, + ...args, + }), + sprutThHeater: (args?: Partial) => + modernExtend.binary({ + name: 'th_heater', + cluster: 'msRelativeHumidity', + attribute: 'sprutHeater', + description: + 'Turn on when working in conditions of high humidity (more than 70 %, RH) or condensation, ' + 'if the sensor shows 0 or 100 %.', + valueOn: [true, 1], + valueOff: [false, 0], + access: 'ALL', + entityCategory: 'config', + zigbeeCommandOptions: manufacturerOptions, + ...args, + }), + sprutOccupancyLevel: (args?: Partial) => + modernExtend.numeric({ + name: 'occupancy_level', + cluster: 'msOccupancySensing', + attribute: 'sprutOccupancyLevel', + reporting: {min: '10_SECONDS', max: '1_MINUTE', change: 5}, + description: 'Measured occupancy level', + access: 'STATE_GET', + entityCategory: 'diagnostic', + ...args, + }), + sprutOccupancyTimeout: (args?: Partial) => + modernExtend.numeric({ + name: 'occupancy_timeout', + cluster: 'msOccupancySensing', + attribute: 'pirOToUDelay', + description: 'Time in seconds after which occupancy is cleared after detecting it (default: 60)', + valueMin: 0, + valueMax: 2000, + unit: 's', + access: 'ALL', + entityCategory: 'config', + ...args, + }), + sprutOccupancySensitivity: (args?: Partial) => + modernExtend.numeric({ + name: 'occupancy_sensitivity', + cluster: 'msOccupancySensing', + attribute: 'sprutOccupancySensitivity', + description: 'If the sensor is triggered by the slightest movement, reduce the sensitivity, ' + 'otherwise increase it (default: 50)', + valueMin: 0, + valueMax: 2000, + access: 'ALL', + entityCategory: 'config', + zigbeeCommandOptions: manufacturerOptions, + ...args, + }), + sprutNoise: (args?: Partial) => + modernExtend.numeric({ + name: 'noise', + cluster: 'sprutNoise', + attribute: 'noise', + reporting: {min: '10_SECONDS', max: '1_MINUTE', change: 5}, + description: 'Measured noise level', + unit: 'dBA', + precision: 2, + access: 'STATE_GET', + entityCategory: 'diagnostic', + ...args, + }), + sprutNoiseDetectLevel: (args?: Partial) => + modernExtend.numeric({ + name: 'noise_detect_level', + cluster: 'sprutNoise', + attribute: 'noiseDetectLevel', + description: 'The minimum noise level at which the detector will work (default: 50)', + valueMin: 0, + valueMax: 150, + unit: 'dBA', + access: 'ALL', + entityCategory: 'config', + zigbeeCommandOptions: manufacturerOptions, + ...args, + }), + sprutNoiseDetected: (args?: Partial) => + modernExtend.binary({ + name: 'noise_detected', + cluster: 'sprutNoise', + attribute: 'noiseDetected', + valueOn: [true, 1], + valueOff: [false, 0], + description: 'Indicates whether the device detected noise', + access: 'STATE_GET', + ...args, + }), + sprutNoiseTimeout: (args?: Partial) => + modernExtend.numeric({ + name: 'noise_timeout', + cluster: 'sprutNoise', + attribute: 'noiseAfterDetectDelay', + description: 'Time in seconds after which noise is cleared after detecting it (default: 60)', + valueMin: 0, + valueMax: 2000, + unit: 's', + access: 'ALL', + entityCategory: 'config', + ...args, + }), + sprutVoc: (args?: Partial) => + modernExtend.numeric({ + name: 'voc', + label: 'VOC', + cluster: 'sprutVoc', + attribute: 'voc', + reporting: {min: '10_SECONDS', max: '1_MINUTE', change: 10}, + description: 'Measured VOC level', + unit: 'µg/m³', + access: 'STATE_GET', + ...args, + }), sprutIrBlaster: (): ModernExtend => { - const toZigbee: Tz.Converter[] = [{ - key: ['play_store', 'learn_start', 'learn_stop', 'clear_store', 'play_ram', 'learn_ram_start', 'learn_ram_stop'], - convertSet: async (entity, key, value: KeyValueAny, meta) => { - const options = { - frameType: 0, manufacturerCode: sprutCode, disableDefaultResponse: true, - disableResponse: true, reservedBits: 0, direction: 0, writeUndiv: false, - // @ts-expect-error - transactionSequenceNumber: null, - }; + const toZigbee: Tz.Converter[] = [ + { + key: ['play_store', 'learn_start', 'learn_stop', 'clear_store', 'play_ram', 'learn_ram_start', 'learn_ram_stop'], + convertSet: async (entity, key, value: KeyValueAny, meta) => { + const options = { + frameType: 0, + manufacturerCode: sprutCode, + disableDefaultResponse: true, + disableResponse: true, + reservedBits: 0, + direction: 0, + writeUndiv: false, + // @ts-expect-error + transactionSequenceNumber: null, + }; - switch (key) { - case 'play_store': - await entity.command('sprutIrBlaster', 'playStore', - {param: value['rom']}, options); - break; - case 'learn_start': - await entity.command('sprutIrBlaster', 'learnStart', - {value: value['rom']}, options); - break; - case 'learn_stop': - await entity.command('sprutIrBlaster', 'learnStop', - {value: value['rom']}, options); - break; - case 'clear_store': - await entity.command('sprutIrBlaster', 'clearStore', - {}, options); - break; - case 'play_ram': - await entity.command('sprutIrBlaster', 'playRam', - {}, options); - break; - case 'learn_ram_start': - await entity.command('sprutIrBlaster', 'learnRamStart', - {}, options); - break; - case 'learn_ram_stop': - await entity.command('sprutIrBlaster', 'learnRamStop', - {}, options); - break; - } + switch (key) { + case 'play_store': + await entity.command('sprutIrBlaster', 'playStore', {param: value['rom']}, options); + break; + case 'learn_start': + await entity.command('sprutIrBlaster', 'learnStart', {value: value['rom']}, options); + break; + case 'learn_stop': + await entity.command('sprutIrBlaster', 'learnStop', {value: value['rom']}, options); + break; + case 'clear_store': + await entity.command('sprutIrBlaster', 'clearStore', {}, options); + break; + case 'play_ram': + await entity.command('sprutIrBlaster', 'playRam', {}, options); + break; + case 'learn_ram_start': + await entity.command('sprutIrBlaster', 'learnRamStart', {}, options); + break; + case 'learn_ram_stop': + await entity.command('sprutIrBlaster', 'learnRamStop', {}, options); + break; + } + }, }, - }]; + ]; const configure: Configure[] = [modernExtend.setupConfigureForBinding('sprutIrBlaster', 'input')]; return {toZigbee, configure, isModernExtend: true}; }, }; const { - sprutActivityIndicator, sprutOccupancyLevel, sprutNoise, sprutVoc, - sprutNoiseDetected, sprutOccupancyTimeout, sprutNoiseTimeout, - sprutTemperatureOffset, sprutThHeater, sprutOccupancySensitivity, - sprutNoiseDetectLevel, sprutIrBlaster, + sprutActivityIndicator, + sprutOccupancyLevel, + sprutNoise, + sprutVoc, + sprutNoiseDetected, + sprutOccupancyTimeout, + sprutNoiseTimeout, + sprutTemperatureOffset, + sprutThHeater, + sprutOccupancySensitivity, + sprutNoiseDetectLevel, + sprutIrBlaster, } = sprutModernExtend; const definitions: Definition[] = [ @@ -447,39 +459,119 @@ const definitions: Definition[] = [ model: 'WB-MSW-ZIGBEE v.3', vendor: 'Wirenboard', description: 'Wall-mounted multi sensor', - fromZigbee: [fzLocal.temperature, fz.illuminance, fz.humidity, fz.occupancy, fzLocal.occupancy_level, fz.co2, fzLocal.voc, - fzLocal.noise, fzLocal.noise_detected, fz.on_off, fzLocal.occupancy_timeout, fzLocal.noise_timeout, fzLocal.co2_mh_z19b_config, - fzLocal.th_heater, fzLocal.occupancy_sensitivity, fzLocal.noise_detect_level], - toZigbee: [tz.on_off, tzLocal.sprut_ir_remote, tzLocal.occupancy_timeout, tzLocal.noise_timeout, tzLocal.co2_mh_z19b_config, - tzLocal.th_heater, tzLocal.temperature_offset, tzLocal.occupancy_sensitivity, tzLocal.noise_detect_level], - exposes: [e.temperature(), e.illuminance(), e.illuminance_lux(), e.humidity(), e.occupancy(), e.occupancy_level(), e.co2(), - e.voc(), e.noise(), e.noise_detected(), e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), + fromZigbee: [ + fzLocal.temperature, + fz.illuminance, + fz.humidity, + fz.occupancy, + fzLocal.occupancy_level, + fz.co2, + fzLocal.voc, + fzLocal.noise, + fzLocal.noise_detected, + fz.on_off, + fzLocal.occupancy_timeout, + fzLocal.noise_timeout, + fzLocal.co2_mh_z19b_config, + fzLocal.th_heater, + fzLocal.occupancy_sensitivity, + fzLocal.noise_detect_level, + ], + toZigbee: [ + tz.on_off, + tzLocal.sprut_ir_remote, + tzLocal.occupancy_timeout, + tzLocal.noise_timeout, + tzLocal.co2_mh_z19b_config, + tzLocal.th_heater, + tzLocal.temperature_offset, + tzLocal.occupancy_sensitivity, + tzLocal.noise_detect_level, + ], + exposes: [ + e.temperature(), + e.illuminance(), + e.illuminance_lux(), + e.humidity(), + e.occupancy(), + e.occupancy_level(), + e.co2(), + e.voc(), + e.noise(), + e.noise_detected(), + e.switch().withEndpoint('l1'), + e.switch().withEndpoint('l2'), e.switch().withEndpoint('l3'), - e.numeric('noise_timeout', ea.ALL).withValueMin(0).withValueMax(2000).withUnit('s').withCategory('config') + e + .numeric('noise_timeout', ea.ALL) + .withValueMin(0) + .withValueMax(2000) + .withUnit('s') + .withCategory('config') .withDescription('Time in seconds after which noise is cleared after detecting it (default: 60)'), - e.numeric('occupancy_timeout', ea.ALL).withValueMin(0).withValueMax(2000).withUnit('s').withCategory('config') + e + .numeric('occupancy_timeout', ea.ALL) + .withValueMin(0) + .withValueMax(2000) + .withUnit('s') + .withCategory('config') .withDescription('Time in seconds after which occupancy is cleared after detecting it (default: 60)'), - e.numeric('temperature_offset', ea.SET).withValueMin(-10).withValueMax(10).withUnit('°C').withCategory('config') + e + .numeric('temperature_offset', ea.SET) + .withValueMin(-10) + .withValueMax(10) + .withUnit('°C') + .withCategory('config') .withDescription('Self-heating compensation. The compensation value is subtracted from the measured temperature'), - e.numeric('occupancy_sensitivity', ea.ALL).withValueMin(0).withValueMax(2000).withCategory('config') - .withDescription('If the sensor is triggered by the slightest movement, reduce the sensitivity, '+ - 'otherwise increase it (default: 50)'), - e.numeric('noise_detect_level', ea.ALL).withValueMin(0).withValueMax(150).withUnit('dBA').withCategory('config') + e + .numeric('occupancy_sensitivity', ea.ALL) + .withValueMin(0) + .withValueMax(2000) + .withCategory('config') + .withDescription( + 'If the sensor is triggered by the slightest movement, reduce the sensitivity, ' + 'otherwise increase it (default: 50)', + ), + e + .numeric('noise_detect_level', ea.ALL) + .withValueMin(0) + .withValueMax(150) + .withUnit('dBA') + .withCategory('config') .withDescription('The minimum noise level at which the detector will work (default: 50)'), - e.enum('co2_autocalibration', ea.ALL, switchActionValues).withCategory('config') - .withDescription('Automatic calibration of the CO2 sensor. If ON, the CO2 sensor will automatically calibrate '+ - 'every 7 days. (MH-Z19B sensor)'), - e.enum('co2_manual_calibration', ea.ALL, switchActionValues).withCategory('config') - .withDescription('Ventilate the room for 20 minutes, turn on manual calibration, and turn it off after one second. '+ - 'After about 5 minutes the CO2 sensor will show 400ppm. Calibration completed. (MH-Z19B sensor)'), - e.enum('th_heater', ea.ALL, switchActionValues).withCategory('config') - .withDescription('Turn on when working in conditions of high humidity (more than 70 %, RH) or condensation, '+ - 'if the sensor shows 0 or 100 %.'), + e + .enum('co2_autocalibration', ea.ALL, switchActionValues) + .withCategory('config') + .withDescription( + 'Automatic calibration of the CO2 sensor. If ON, the CO2 sensor will automatically calibrate ' + 'every 7 days. (MH-Z19B sensor)', + ), + e + .enum('co2_manual_calibration', ea.ALL, switchActionValues) + .withCategory('config') + .withDescription( + 'Ventilate the room for 20 minutes, turn on manual calibration, and turn it off after one second. ' + + 'After about 5 minutes the CO2 sensor will show 400ppm. Calibration completed. (MH-Z19B sensor)', + ), + e + .enum('th_heater', ea.ALL, switchActionValues) + .withCategory('config') + .withDescription( + 'Turn on when working in conditions of high humidity (more than 70 %, RH) or condensation, ' + 'if the sensor shows 0 or 100 %.', + ), ], configure: async (device, coordinatorEndpoint) => { const endpoint1 = device.getEndpoint(1); - const binds = ['genBasic', 'msTemperatureMeasurement', 'msIlluminanceMeasurement', 'msRelativeHumidity', - 'msOccupancySensing', 'msCO2', 'sprutVoc', 'sprutNoise', 'sprutIrBlaster', 'genOta']; + const binds = [ + 'genBasic', + 'msTemperatureMeasurement', + 'msIlluminanceMeasurement', + 'msRelativeHumidity', + 'msOccupancySensing', + 'msCO2', + 'sprutVoc', + 'sprutNoise', + 'sprutIrBlaster', + 'genOta', + ]; await reporting.bind(endpoint1, coordinatorEndpoint, binds); // report configuration @@ -504,7 +596,7 @@ const definitions: Definition[] = [ await device.getEndpoint(4).read('genOnOff', ['onOff']); }, endpoint: (device) => { - return {'default': 1, 'l1': 2, 'l2': 3, 'l3': 4}; + return {default: 1, l1: 2, l2: 3, l3: 4}; }, meta: {multiEndpoint: true, multiEndpointSkip: ['humidity']}, extend: [ota()], @@ -517,7 +609,7 @@ const definitions: Definition[] = [ extend: [ forcePowerSource({powerSource: 'Mains (single phase)'}), deviceEndpoints({ - endpoints: {'default': 1, 'l1': 2, 'l2': 3, 'l3': 4, 'indicator': 5}, + endpoints: {default: 1, l1: 2, l2: 3, l3: 4, indicator: 5}, }), onOff({powerOnBehavior: false, endpointNames: ['l1', 'l2', 'l3']}), sprutActivityIndicator({endpointName: 'indicator'}), diff --git a/src/devices/wisdom.ts b/src/devices/wisdom.ts index 2b947058e793e..44307f89827e2 100644 --- a/src/devices/wisdom.ts +++ b/src/devices/wisdom.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/woolley.ts b/src/devices/woolley.ts index 64544de622727..9ad72bccf696b 100644 --- a/src/devices/woolley.ts +++ b/src/devices/woolley.ts @@ -1,9 +1,9 @@ -import * as exposes from '../lib/exposes'; -import * as utils from '../lib/utils'; -import * as reporting from '../lib/reporting'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; +import * as reporting from '../lib/reporting'; import {Definition, Fz, KeyValue} from '../lib/types'; +import * as utils from '../lib/utils'; const e = exposes.presets; const fzLocal = { diff --git a/src/devices/woox.ts b/src/devices/woox.ts index b47e1ea1f2bf9..be5dc0d145235 100644 --- a/src/devices/woox.ts +++ b/src/devices/woox.ts @@ -1,8 +1,8 @@ +import fz from '../converters/fromZigbee'; +import tz from '../converters/toZigbee'; import * as exposes from '../lib/exposes'; import * as legacy from '../lib/legacy'; import * as reporting from '../lib/reporting'; -import fz from '../converters/fromZigbee'; -import tz from '../converters/toZigbee'; import * as tuya from '../lib/tuya'; import {Definition} from '../lib/types'; const e = exposes.presets; @@ -55,28 +55,37 @@ const definitions: Definition[] = [ meta: {timeout: 30000, disableDefaultResponse: true}, fromZigbee: [legacy.fromZigbee.R7049_status, fz.ignore_tuya_set_time, fz.ignore_time_read], toZigbee: [legacy.toZigbee.R7049_silenceSiren, legacy.toZigbee.R7049_testAlarm, legacy.toZigbee.R7049_alarm], - exposes: [e.battery_low(), + exposes: [ + e.battery_low(), e.binary('smoke', ea.STATE, true, false).withDescription('Smoke alarm status'), e.binary('test_alarm', ea.STATE_SET, true, false).withDescription('Test alarm'), - e.enum('test_alarm_result', ea.STATE, ['checking', 'check_success', 'check_failure', 'others']) - .withDescription('Test alarm result'), + e.enum('test_alarm_result', ea.STATE, ['checking', 'check_success', 'check_failure', 'others']).withDescription('Test alarm result'), e.enum('battery_level', ea.STATE, ['low', 'middle', 'high']).withDescription('Battery level state'), e.binary('alarm', ea.STATE_SET, true, false).withDescription('Alarm enable'), e.binary('fault_alarm', ea.STATE, true, false).withDescription('Fault alarm status'), - e.binary('silence_siren', ea.STATE_SET, true, false).withDescription('Silence siren')], + e.binary('silence_siren', ea.STATE_SET, true, false).withDescription('Silence siren'), + ], }, { - fingerprint: [{modelID: 'TS0219', manufacturerName: '_TYZB01_ynsiasng'}, {modelID: 'TS0219', manufacturerName: '_TYZB01_bwsijaty'}, - {modelID: 'TS0219', manufacturerName: '_TYZB01_rs7ff6o7'}], + fingerprint: [ + {modelID: 'TS0219', manufacturerName: '_TYZB01_ynsiasng'}, + {modelID: 'TS0219', manufacturerName: '_TYZB01_bwsijaty'}, + {modelID: 'TS0219', manufacturerName: '_TYZB01_rs7ff6o7'}, + ], model: 'R7051', vendor: 'Woox', description: 'Smart siren', fromZigbee: [fz.battery, fz.ts0216_siren, fz.ias_alarm_only_alarm_1, fz.power_source], toZigbee: [tz.warning, tz.ts0216_volume, tz.ts0216_duration], - exposes: [e.battery(), e.battery_voltage(), e.warning(), e.binary('alarm', ea.STATE, true, false), + exposes: [ + e.battery(), + e.battery_voltage(), + e.warning(), + e.binary('alarm', ea.STATE, true, false), e.binary('ac_connected', ea.STATE, true, false).withDescription('Is the device plugged in'), e.numeric('volume', ea.ALL).withValueMin(0).withValueMax(100).withDescription('Volume of siren'), - e.numeric('duration', ea.ALL).withValueMin(0).withValueMax(3600).withDescription('Duration of siren')], + e.numeric('duration', ea.ALL).withValueMin(0).withValueMax(3600).withDescription('Duration of siren'), + ], meta: {disableDefaultResponse: true}, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); diff --git a/src/devices/wyze.ts b/src/devices/wyze.ts index e1abeb745ff12..a132fb3ab5383 100644 --- a/src/devices/wyze.ts +++ b/src/devices/wyze.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/xinghuoyuan.ts b/src/devices/xinghuoyuan.ts index ed9a02ba5b03f..9cfb22fb5eea6 100644 --- a/src/devices/xinghuoyuan.ts +++ b/src/devices/xinghuoyuan.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {onOff} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/xyzroe.ts b/src/devices/xyzroe.ts index 57492ebbbdf7e..0390901548844 100644 --- a/src/devices/xyzroe.ts +++ b/src/devices/xyzroe.ts @@ -1,16 +1,15 @@ - -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; import {Definition, Tz, Fz, KeyValueAny} from '../lib/types'; import * as utils from '../lib/utils'; const e = exposes.presets; const ea = exposes.access; const buttonModesList = { - 'single_click': 0x01, - 'multi_click': 0x02, + single_click: 0x01, + multi_click: 0x02, }; const inputLinkList = { @@ -20,24 +19,24 @@ const inputLinkList = { const bindCommandList = { 'on/off': 0x00, - 'toggle': 0x01, - 'change_level_up': 0x02, - 'change_level_down': 0x03, - 'change_level_up_with_off': 0x04, - 'change_level_down_with_off': 0x05, - 'recall_scene_0': 0x06, - 'recall_scene_1': 0x07, - 'recall_scene_2': 0x08, - 'recall_scene_3': 0x09, - 'recall_scene_4': 0x0A, - 'recall_scene_5': 0x0B, + toggle: 0x01, + change_level_up: 0x02, + change_level_down: 0x03, + change_level_up_with_off: 0x04, + change_level_down_with_off: 0x05, + recall_scene_0: 0x06, + recall_scene_1: 0x07, + recall_scene_2: 0x08, + recall_scene_3: 0x09, + recall_scene_4: 0x0a, + recall_scene_5: 0x0b, }; const switchTypesList = { - 'switch': 0x00, - 'single_click': 0x01, - 'multi_click': 0x02, - 'reset_to_defaults': 0xff, + switch: 0x00, + single_click: 0x01, + multi_click: 0x02, + reset_to_defaults: 0xff, }; const switchActionsList = { @@ -46,7 +45,7 @@ const switchActionsList = { toggle: 0x02, }; -function getSortedList(source: { [key: string]: number }): string[] { +function getSortedList(source: {[key: string]: number}): string[] { const keysSorted: [string, number][] = []; for (const key in source) { @@ -69,12 +68,9 @@ function getSortedList(source: { [key: string]: number }): string[] { function zigDcInputConfigExposes(epName: string, desc: string) { const features = []; - features.push(e.enum('switch_type', exposes.access.ALL, - getSortedList(switchTypesList)).withEndpoint(epName).withDescription(desc)); - features.push(e.enum('switch_actions', exposes.access.ALL, - getSortedList(switchActionsList)).withEndpoint(epName)); - features.push(e.enum('bind_command', exposes.access.ALL, - getSortedList(bindCommandList)).withEndpoint(epName)); + features.push(e.enum('switch_type', exposes.access.ALL, getSortedList(switchTypesList)).withEndpoint(epName).withDescription(desc)); + features.push(e.enum('switch_actions', exposes.access.ALL, getSortedList(switchActionsList)).withEndpoint(epName)); + features.push(e.enum('bind_command', exposes.access.ALL, getSortedList(bindCommandList)).withEndpoint(epName)); return features; } @@ -88,18 +84,18 @@ const tzLocal = { let payload; let data; switch (key) { - case 'button_mode': - data = utils.getFromLookup(value, buttonModesList); - payload = {buttonMode: data}; - break; - case 'link_to_output': - data = utils.getFromLookup(value, inputLinkList); - payload = {0x4001: {value: data, type: 32 /* uint8 */}}; - break; - case 'bind_command': - data = utils.getFromLookup(value, bindCommandList); - payload = {0x4002: {value: data, type: 32 /* uint8 */}}; - break; + case 'button_mode': + data = utils.getFromLookup(value, buttonModesList); + payload = {buttonMode: data}; + break; + case 'link_to_output': + data = utils.getFromLookup(value, inputLinkList); + payload = {0x4001: {value: data, type: 32 /* uint8 */}}; + break; + case 'bind_command': + data = utils.getFromLookup(value, bindCommandList); + payload = {0x4002: {value: data, type: 32 /* uint8 */}}; + break; } await entity.write('genOnOffSwitchCfg', payload); }, @@ -144,15 +140,17 @@ const tzLocal = { utils.assertNumber(value, key); utils.assertEndpoint(entity); if (key === 'restart') { - await entity.command('genOnOff', 'onWithTimedOff', {ctrlbits: 0, ontime: Math.round(value*10), offwaittime: 0}); + await entity.command('genOnOff', 'onWithTimedOff', {ctrlbits: 0, ontime: Math.round(value * 10), offwaittime: 0}); return {state: {[key]: value}}; } else if (key === 'interval') { - await entity.configureReporting('genOnOff', [{ - attribute: 'onOff', - minimumReportInterval: value, - maximumReportInterval: value, - reportableChange: 0, - }]); + await entity.configureReporting('genOnOff', [ + { + attribute: 'onOff', + minimumReportInterval: value, + maximumReportInterval: value, + reportableChange: 0, + }, + ]); return {state: {[key]: value}}; } }, @@ -164,12 +162,14 @@ const tzLocal = { const endpoint = meta.device.getEndpoint(epId); const value2 = parseInt(value.toString()); if (!isNaN(value2) && value2 > 0) { - await endpoint.configureReporting('genOnOff', [{ - attribute: 'onOff', - minimumReportInterval: value2, - maximumReportInterval: value2, - reportableChange: 0, - }]); + await endpoint.configureReporting('genOnOff', [ + { + attribute: 'onOff', + minimumReportInterval: value2, + maximumReportInterval: value2, + reportableChange: 0, + }, + ]); } return; }, @@ -183,18 +183,18 @@ const tzLocal = { let payload; let data; switch (key) { - case 'switch_type': - data = utils.getFromLookup(value, switchTypesList); - payload = {switchType: data}; - break; - case 'switch_actions': - data = utils.getFromLookup(value, switchActionsList); - payload = {switchActions: data}; - break; - case 'bind_command': - data = utils.getFromLookup(value, bindCommandList); - payload = {0x4002: {value: data, type: 32 /* uint8 */}}; - break; + case 'switch_type': + data = utils.getFromLookup(value, switchTypesList); + payload = {switchType: data}; + break; + case 'switch_actions': + data = utils.getFromLookup(value, switchActionsList); + payload = {switchActions: data}; + break; + case 'bind_command': + data = utils.getFromLookup(value, bindCommandList); + payload = {0x4002: {value: data, type: 32 /* uint8 */}}; + break; } await entity.write('genOnOffSwitchCfg', payload); }, @@ -242,10 +242,10 @@ const fzLocal = { let val = utils.precisionRound(valRaw, 1); const nameLookup: KeyValueAny = { - 'C': 'temperature', - 'V': 'voltage', - 'A': 'current', - 'W': 'power', + C: 'temperature', + V: 'voltage', + A: 'current', + W: 'power', }; let nameAlt = ''; @@ -260,7 +260,7 @@ const fzLocal = { if (nameAlt === undefined) { const valueIndex = parseInt(unit, 10); - if (! isNaN(valueIndex)) { + if (!isNaN(valueIndex)) { nameAlt = 'val' + unit; } } @@ -280,8 +280,7 @@ const fzLocal = { convert: (model, msg, publish, options, meta) => { if (msg.data.hasOwnProperty('onOff')) { const payload: KeyValueAny = {}; - const endpointName = model.hasOwnProperty('endpoint') ? - utils.getKey(model.endpoint(meta.device), msg.endpoint.ID) : msg.endpoint.ID; + const endpointName = model.hasOwnProperty('endpoint') ? utils.getKey(model.endpoint(meta.device), msg.endpoint.ID) : msg.endpoint.ID; const state = msg.data['onOff'] === 1 ? 'OFF' : 'ON'; payload[`state_${endpointName}`] = state; return payload; @@ -292,7 +291,7 @@ const fzLocal = { cluster: 'genAnalogInput', type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { - const payload: { [key: string]: number } = {}; + const payload: {[key: string]: number} = {}; const endpoint = msg.endpoint.ID; if (endpoint === 3 || endpoint === 5) { @@ -304,7 +303,7 @@ const fzLocal = { const isCurrent = param === 'A'; const name = isCurrent ? 'current' : 'voltage'; const alt = isCurrent ? 'voltage' : 'current'; - const baseCh = (addr === 41) ? 1 : 4; + const baseCh = addr === 41 ? 1 : 4; const suffix = `_ch${baseCh + ch - 1}`; const otherKey = alt + suffix; const otherValue = meta.state[otherKey] as number; @@ -321,7 +320,7 @@ const fzLocal = { cluster: 'genAnalogInput', type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { - const payload: { [key: string]: number } = {}; + const payload: {[key: string]: number} = {}; const channel = msg.endpoint.ID; if (channel === 1) { @@ -347,15 +346,11 @@ const fzLocal = { } satisfies Fz.Converter, }; - function zigusbBtnConfigExposes(epName: string) { const features = []; - features.push(e.enum('button_mode', exposes.access.ALL, - getSortedList(buttonModesList)).withEndpoint(epName)); - features.push(e.enum('link_to_output', exposes.access.ALL, - getSortedList(inputLinkList)).withEndpoint(epName)); - features.push(e.enum('bind_command', exposes.access.ALL, - getSortedList(bindCommandList)).withEndpoint(epName)); + features.push(e.enum('button_mode', exposes.access.ALL, getSortedList(buttonModesList)).withEndpoint(epName)); + features.push(e.enum('link_to_output', exposes.access.ALL, getSortedList(inputLinkList)).withEndpoint(epName)); + features.push(e.enum('bind_command', exposes.access.ALL, getSortedList(bindCommandList)).withEndpoint(epName)); return features; } @@ -365,22 +360,42 @@ const definitions: Definition[] = [ model: 'ZigUSB', vendor: 'xyzroe', description: 'Zigbee USB power monitor and switch', - fromZigbee: [fz.ignore_basic_report, fzLocal.zigusb_on_off_invert, fzLocal.zigusb_analog_input, fz.temperature, - fz.ptvo_multistate_action, legacy.fz.ptvo_switch_buttons, fzLocal.zigusb_button_config], + fromZigbee: [ + fz.ignore_basic_report, + fzLocal.zigusb_on_off_invert, + fzLocal.zigusb_analog_input, + fz.temperature, + fz.ptvo_multistate_action, + legacy.fz.ptvo_switch_buttons, + fzLocal.zigusb_button_config, + ], toZigbee: [tzLocal.zigusb_restart_interval, tzLocal.zigusb_on_off_invert, tz.ptvo_switch_analog_input, tzLocal.zigusb_button_config], - exposes: [e.switch().withEndpoint('l1'), - e.numeric('restart', ea.SET).withEndpoint('l1').withValueMin(1).withValueMax(30).withValueStep(1) - .withDescription('OFF time').withUnit('seconds'), + exposes: [ + e.switch().withEndpoint('l1'), + e + .numeric('restart', ea.SET) + .withEndpoint('l1') + .withValueMin(1) + .withValueMax(30) + .withValueStep(1) + .withDescription('OFF time') + .withUnit('seconds'), ...zigusbBtnConfigExposes('l1'), - e.action(['single', 'double', 'triple']) - .withDescription('Single click works only with NO link to output'), + e.action(['single', 'double', 'triple']).withDescription('Single click works only with NO link to output'), e.current().withAccess(ea.STATE).withEndpoint('l2'), e.voltage().withAccess(ea.STATE).withEndpoint('l2'), e.power().withAccess(ea.STATE).withEndpoint('l2'), - e.numeric('interval', ea.SET).withEndpoint('l2').withValueMin(1).withValueMax(3600).withValueStep(1) - .withDescription('Reporting interval').withUnit('sec'), + e + .numeric('interval', ea.SET) + .withEndpoint('l2') + .withValueMin(1) + .withValueMax(3600) + .withValueStep(1) + .withDescription('Reporting interval') + .withUnit('sec'), e.cpu_temperature().withProperty('temperature').withEndpoint('l4'), - e.numeric('uptime', ea.STATE).withEndpoint('l5').withDescription('CC2530').withUnit('seconds')], + e.numeric('uptime', ea.STATE).withEndpoint('l5').withDescription('CC2530').withUnit('seconds'), + ], meta: {multiEndpoint: true}, endpoint: (device) => { return {l1: 1, l2: 2, l4: 4, l5: 5}; @@ -424,8 +439,7 @@ const definitions: Definition[] = [ ...zigDcInputConfigExposes('l8', 'IN2'), ...zigDcInputConfigExposes('l1', 'BTN'), e.numeric('uptime', ea.STATE).withDescription('Uptime').withUnit('sec'), - e.numeric('interval', ea.SET).withValueMin(5).withValueMax(600).withValueStep(1) - .withDescription('Reporting interval').withUnit('sec'), + e.numeric('interval', ea.SET).withValueMin(5).withValueMax(600).withValueStep(1).withDescription('Reporting interval').withUnit('sec'), ], meta: {multiEndpoint: true}, endpoint: (device) => { @@ -436,7 +450,8 @@ const definitions: Definition[] = [ await endpoint1.read('genBasic', ['modelId', 'swBuildId', 'powerSource']); const endpoint2 = device.getEndpoint(2); await endpoint2.configureReporting('genOnOff', [ - {attribute: 'onOff', minimumReportInterval: 20, maximumReportInterval: 120, reportableChange: 0.1}]); + {attribute: 'onOff', minimumReportInterval: 20, maximumReportInterval: 120, reportableChange: 0.1}, + ]); }, }, ]; diff --git a/src/devices/yale.ts b/src/devices/yale.ts index 17bc68e5759cf..a905fd9a90f42 100644 --- a/src/devices/yale.ts +++ b/src/devices/yale.ts @@ -1,38 +1,56 @@ -import * as exposes from '../lib/exposes'; +import {Definition, Fz, ModernExtend, Reporting, Tz} from 'src/lib/types'; +import {KeyValue} from 'zigbee-herdsman/dist/controller/tstype'; + import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; +import {logger} from '../lib/logger'; +import {battery, lock} from '../lib/modernExtend'; import * as reporting from '../lib/reporting'; -import {Definition, Fz, ModernExtend, Reporting, Tz} from 'src/lib/types'; import {getFromLookup} from '../lib/utils'; -import {KeyValue} from 'zigbee-herdsman/dist/controller/tstype'; -import {battery, lock} from '../lib/modernExtend'; -import {logger} from '../lib/logger'; const NS = 'zhc:yale'; const e = exposes.presets; const ea = exposes.access; -const lockExtend = (meta={}, lockStateOptions: Reporting.Override|false=null, binds=['closuresDoorLock', 'genPowerCfg']): ModernExtend => { +const lockExtend = (meta = {}, lockStateOptions: Reporting.Override | false = null, binds = ['closuresDoorLock', 'genPowerCfg']): ModernExtend => { return { - fromZigbee: [fz.lock, fz.battery, fz.lock_operation_event, fz.lock_programming_event, fz.lock_pin_code_response, - fz.lock_user_status_response], + fromZigbee: [ + fz.lock, + fz.battery, + fz.lock_operation_event, + fz.lock_programming_event, + fz.lock_pin_code_response, + fz.lock_user_status_response, + ], toZigbee: [tz.lock, tz.pincode_lock, tz.lock_userstatus, tz.lock_auto_relock_time, tz.lock_sound_volume], meta: {pinCodeCount: 250, ...meta}, - exposes: [e.lock(), e.battery(), e.pincode(), e.lock_action(), e.lock_action_source_name(), e.lock_action_user(), - e.auto_relock_time().withValueMin(0).withValueMax(3600), e.sound_volume(), e.battery_low()], - configure: [async (device, coordinatorEndpoint) => { - const endpoint = device.getEndpoint(1); - await reporting.bind(endpoint, coordinatorEndpoint, binds); - if (lockStateOptions !== false) { - await reporting.lockState(endpoint, lockStateOptions); - } - await reporting.batteryPercentageRemaining(endpoint); - try { - await reporting.batteryAlarmState(endpoint); - } catch (e) { - // Fails for some: https://github.com/Koenkk/zigbee-herdsman-converters/pull/5414 - } - }], + exposes: [ + e.lock(), + e.battery(), + e.pincode(), + e.lock_action(), + e.lock_action_source_name(), + e.lock_action_user(), + e.auto_relock_time().withValueMin(0).withValueMax(3600), + e.sound_volume(), + e.battery_low(), + ], + configure: [ + async (device, coordinatorEndpoint) => { + const endpoint = device.getEndpoint(1); + await reporting.bind(endpoint, coordinatorEndpoint, binds); + if (lockStateOptions !== false) { + await reporting.lockState(endpoint, lockStateOptions); + } + await reporting.batteryPercentageRemaining(endpoint); + try { + await reporting.batteryAlarmState(endpoint); + } catch (e) { + // Fails for some: https://github.com/Koenkk/zigbee-herdsman-converters/pull/5414 + } + }, + ], isModernExtend: true, }; }; @@ -201,13 +219,13 @@ const tzLocal = { key: ['auto_lock_time'], convertSet: async (entity, key, value, meta) => { const lookup = { - 'off': 0, + off: 0, '30seconds': 30, '60seconds': 60, '2minutes': 120, '3minutes': 180, }; - await entity.write('manuSpecificAssaDoorLock', {'autoLockTime': getFromLookup(value, lookup)}, {disableDefaultResponse: true}); + await entity.write('manuSpecificAssaDoorLock', {autoLockTime: getFromLookup(value, lookup)}, {disableDefaultResponse: true}); return {state: {auto_lock_time: value}}; }, convertGet: async (entity, key, meta) => { @@ -218,11 +236,11 @@ const tzLocal = { key: ['volume'], convertSet: async (entity, key, value, meta) => { const lookup = { - 'silent': 1, - 'low': 2, - 'high': 3, + silent: 1, + low: 2, + high: 3, }; - await entity.write('manuSpecificAssaDoorLock', {'volume': getFromLookup(value, lookup)}, {disableDefaultResponse: true}); + await entity.write('manuSpecificAssaDoorLock', {volume: getFromLookup(value, lookup)}, {disableDefaultResponse: true}); return {state: {volume: value}}; }, convertGet: async (entity, key, meta) => { @@ -399,15 +417,17 @@ const definitions: Definition[] = [ extend: [lockExtend({battery: {dontDividePercentage: true}})], }, { - fingerprint: [{ - type: 'EndDevice', - manufacturerName: 'Yale', - manufacturerID: 43690, - powerSource: 'Battery', - endpoints: [ - {ID: 1, profileID: 260, deviceID: 10, inputClusters: [0, 9, 10, 257, 64512, 1], outputClusters: []}, - {ID: 196, profileID: 260, deviceID: 10, inputClusters: [1], outputClusters: []}, - ]}, + fingerprint: [ + { + type: 'EndDevice', + manufacturerName: 'Yale', + manufacturerID: 43690, + powerSource: 'Battery', + endpoints: [ + {ID: 1, profileID: 260, deviceID: 10, inputClusters: [0, 9, 10, 257, 64512, 1], outputClusters: []}, + {ID: 196, profileID: 260, deviceID: 10, inputClusters: [1], outputClusters: []}, + ], + }, ], model: 'ZYA-C4-MOD-S', vendor: 'Yale', diff --git a/src/devices/ynoa.ts b/src/devices/ynoa.ts index 66b6394a2000b..16aa3fb38a63c 100644 --- a/src/devices/ynoa.ts +++ b/src/devices/ynoa.ts @@ -1,9 +1,9 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; import {light} from '../lib/modernExtend'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; @@ -48,10 +48,8 @@ const definitions: Definition[] = [ model: 'LA-5KEY-RGBW', vendor: 'Ynoa', description: '5 key control for RGBW light', - fromZigbee: [fz.command_on, fz.command_off, fz.command_move_to_color_temp, - fz.command_move_to_color, fz.command_move_to_level, fz.battery], - exposes: [e.battery(), e.battery_low(), e.action(['on', 'off', 'brightness_move_to_level', - 'color_temperature_move', 'color_move'])], + fromZigbee: [fz.command_on, fz.command_off, fz.command_move_to_color_temp, fz.command_move_to_color, fz.command_move_to_level, fz.battery], + exposes: [e.battery(), e.battery_low(), e.action(['on', 'off', 'brightness_move_to_level', 'color_temperature_move', 'color_move'])], toZigbee: [], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); diff --git a/src/devices/yookee.ts b/src/devices/yookee.ts index a543118fd9706..2c243a64ee31c 100644 --- a/src/devices/yookee.ts +++ b/src/devices/yookee.ts @@ -1,8 +1,8 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ diff --git a/src/devices/ysrsai.ts b/src/devices/ysrsai.ts index b91ff1fc375b7..410f6bac321ab 100644 --- a/src/devices/ysrsai.ts +++ b/src/devices/ysrsai.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import * as tuya from '../lib/tuya'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/devices/zemismart.ts b/src/devices/zemismart.ts index b22414d1b3282..1f293ee40b78a 100644 --- a/src/devices/zemismart.ts +++ b/src/devices/zemismart.ts @@ -1,13 +1,12 @@ -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; import * as reporting from '../lib/reporting'; import {Definition} from '../lib/types'; const e = exposes.presets; -import * as tuya from '../lib/tuya'; import {forcePowerSource, light, onOff, identify, deviceEndpoints} from '../lib/modernExtend'; - +import * as tuya from '../lib/tuya'; const ea = exposes.access; @@ -19,13 +18,18 @@ const definitions: Definition[] = [ description: 'Tubular motor', fromZigbee: [legacy.fromZigbee.tuya_cover, tuya.fz.datapoints], toZigbee: [legacy.toZigbee.tuya_cover_control, tuya.tz.datapoints], - exposes: [e.cover_position().setAccess('position', ea.STATE_SET), + exposes: [ + e.cover_position().setAccess('position', ea.STATE_SET), e.enum('motor_direction', ea.STATE_SET, ['normal', 'reversed']).withDescription('Motor direction').withCategory('config'), - e.enum('motor_working_mode', ea.STATE_SET, ['continuous', 'intermittently']).withDescription('Motor operating mode') + e + .enum('motor_working_mode', ea.STATE_SET, ['continuous', 'intermittently']) + .withDescription('Motor operating mode') .withCategory('config'), e.enum('remote_pair', ea.STATE_SET, ['on', 'off']).withDescription('Remote control pairing mode').withCategory('config'), e.enum('upper_stroke_limit', ea.STATE_SET, ['SET', 'RESET']).withDescription('Set / Reset the upper stroke limit').withCategory('config'), - e.enum('middle_stroke_limit', ea.STATE_SET, ['SET', 'RESET']).withDescription('Set / Reset the middle stroke limit') + e + .enum('middle_stroke_limit', ea.STATE_SET, ['SET', 'RESET']) + .withDescription('Set / Reset the middle stroke limit') .withCategory('config'), e.enum('lower_stroke_limit', ea.STATE_SET, ['SET', 'RESET']).withDescription('Set / Reset the lower stroke limit').withCategory('config'), ], @@ -33,11 +37,11 @@ const definitions: Definition[] = [ // All datapoints go in here tuyaDatapoints: [ [5, 'motor_direction', tuya.valueConverter.tubularMotorDirection], - [101, 'remote_pair', tuya.valueConverterBasic.lookup({'on': true, 'off': false})], - [103, 'upper_stroke_limit', tuya.valueConverterBasic.lookup({'SET': true, 'RESET': false})], - [104, 'middle_stroke_limit', tuya.valueConverterBasic.lookup({'SET': true, 'RESET': false})], - [105, 'lower_stroke_limit', tuya.valueConverterBasic.lookup({'SET': true, 'RESET': false})], - [106, 'motor_working_mode', tuya.valueConverterBasic.lookup({'continuous': tuya.enum(0), 'intermittently': tuya.enum(1)})], + [101, 'remote_pair', tuya.valueConverterBasic.lookup({on: true, off: false})], + [103, 'upper_stroke_limit', tuya.valueConverterBasic.lookup({SET: true, RESET: false})], + [104, 'middle_stroke_limit', tuya.valueConverterBasic.lookup({SET: true, RESET: false})], + [105, 'lower_stroke_limit', tuya.valueConverterBasic.lookup({SET: true, RESET: false})], + [106, 'motor_working_mode', tuya.valueConverterBasic.lookup({continuous: tuya.enum(0), intermittently: tuya.enum(1)})], ], }, }, @@ -72,14 +76,17 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'TS0003', manufacturerName: '_TZ3000_vjhcenzo'}, {modelID: 'TS0003', manufacturerName: '_TZ3000_f09j9qjb'}], + fingerprint: [ + {modelID: 'TS0003', manufacturerName: '_TZ3000_vjhcenzo'}, + {modelID: 'TS0003', manufacturerName: '_TZ3000_f09j9qjb'}, + ], model: 'TB25', vendor: 'Zemismart', description: 'Smart light switch and socket - 2 gang with neutral wire', extend: [tuya.modernExtend.tuyaOnOff({endpoints: ['left', 'center', 'right']})], meta: {multiEndpoint: true}, endpoint: () => { - return {'left': 1, 'center': 2, 'right': 3}; + return {left: 1, center: 2, right: 3}; }, configure: async (device, coordinatorEndpoint) => { await tuya.configureMagicPacket(device, coordinatorEndpoint); @@ -105,13 +112,29 @@ const definitions: Definition[] = [ fromZigbee: [legacy.fromZigbee.ZMRM02], toZigbee: [], onEvent: tuya.onEventSetTime, - exposes: [e.battery(), e.action([ - 'button_1_hold', 'button_1_single', 'button_1_double', - 'button_2_hold', 'button_2_single', 'button_2_double', - 'button_3_hold', 'button_3_single', 'button_3_double', - 'button_4_hold', 'button_4_single', 'button_4_double', - 'button_5_hold', 'button_5_single', 'button_5_double', - 'button_6_hold', 'button_6_single', 'button_6_double'])], + exposes: [ + e.battery(), + e.action([ + 'button_1_hold', + 'button_1_single', + 'button_1_double', + 'button_2_hold', + 'button_2_single', + 'button_2_double', + 'button_3_hold', + 'button_3_single', + 'button_3_double', + 'button_4_hold', + 'button_4_single', + 'button_4_double', + 'button_5_hold', + 'button_5_single', + 'button_5_double', + 'button_6_hold', + 'button_6_single', + 'button_6_double', + ]), + ], }, { fingerprint: tuya.fingerprint('TS011F', ['_TZ3000_zigisuyh', '_TZ3000_v4mevirn', '_TZ3000_mlswgkc3']), @@ -120,7 +143,7 @@ const definitions: Definition[] = [ description: 'Zigbee smart outlet universal socket with USB port', extend: [tuya.modernExtend.tuyaOnOff({powerOutageMemory: true, endpoints: ['l1', 'l2']})], endpoint: (device) => { - return {'l1': 1, 'l2': 2}; + return {l1: 1, l2: 2}; }, meta: {multiEndpoint: true}, configure: async (device, coordinatorEndpoint) => { @@ -147,32 +170,55 @@ const definitions: Definition[] = [ e.cover_position().setAccess('position', ea.STATE_SET), e.battery(), e.enum('program', ea.SET, ['set_bottom', 'set_upper', 'reset']).withDescription('Set the upper/bottom limit'), - e.enum('click_control', ea.SET, ['upper', 'upper_micro', 'lower', 'lower_micro']) + e + .enum('click_control', ea.SET, ['upper', 'upper_micro', 'lower', 'lower_micro']) .withDescription('Control motor in steps (ignores set limits; normal/micro = 120deg/5deg movement)'), e.enum('motor_direction', ea.STATE_SET, ['normal', 'reversed']).withDescription('Motor direction'), ], meta: { tuyaDatapoints: [ - [1, 'state', tuya.valueConverterBasic.lookup( - (options) => options.invert_cover ? - {'OPEN': tuya.enum(2), 'STOP': tuya.enum(1), 'CLOSE': tuya.enum(0)} : - {'OPEN': tuya.enum(0), 'STOP': tuya.enum(1), 'CLOSE': tuya.enum(2)}, - )], + [ + 1, + 'state', + tuya.valueConverterBasic.lookup((options) => + options.invert_cover + ? {OPEN: tuya.enum(2), STOP: tuya.enum(1), CLOSE: tuya.enum(0)} + : {OPEN: tuya.enum(0), STOP: tuya.enum(1), CLOSE: tuya.enum(2)}, + ), + ], [2, 'position', tuya.valueConverter.coverPosition], [3, 'position', tuya.valueConverter.coverPosition], [5, 'motor_direction', tuya.valueConverter.tubularMotorDirection], - [7, 'work_state', tuya.valueConverterBasic.lookup( - (options) => options.invert_cover ? - {'opening': tuya.enum(1), 'closing': tuya.enum(0)} : - {'opening': tuya.enum(0), 'closing': tuya.enum(1)}, - )], + [ + 7, + 'work_state', + tuya.valueConverterBasic.lookup((options) => + options.invert_cover ? {opening: tuya.enum(1), closing: tuya.enum(0)} : {opening: tuya.enum(0), closing: tuya.enum(1)}, + ), + ], [13, 'battery', tuya.valueConverter.raw], - [101, 'program', tuya.valueConverterBasic.lookup((options) => options.invert_cover ? - {'set_bottom': tuya.enum(0), 'set_upper': tuya.enum(1), 'reset': tuya.enum(4)} : - {'set_bottom': tuya.enum(1), 'set_upper': tuya.enum(0), 'reset': tuya.enum(4)}, null)], - [101, 'click_control', tuya.valueConverterBasic.lookup((options) => options.invert_cover ? - {'lower': tuya.enum(2), 'upper': tuya.enum(3), 'lower_micro': tuya.enum(5), 'upper_micro': tuya.enum(6)} : - {'lower': tuya.enum(3), 'upper': tuya.enum(2), 'lower_micro': tuya.enum(6), 'upper_micro': tuya.enum(5)}, null)], + [ + 101, + 'program', + tuya.valueConverterBasic.lookup( + (options) => + options.invert_cover + ? {set_bottom: tuya.enum(0), set_upper: tuya.enum(1), reset: tuya.enum(4)} + : {set_bottom: tuya.enum(1), set_upper: tuya.enum(0), reset: tuya.enum(4)}, + null, + ), + ], + [ + 101, + 'click_control', + tuya.valueConverterBasic.lookup( + (options) => + options.invert_cover + ? {lower: tuya.enum(2), upper: tuya.enum(3), lower_micro: tuya.enum(5), upper_micro: tuya.enum(6)} + : {lower: tuya.enum(3), upper: tuya.enum(2), lower_micro: tuya.enum(6), upper_micro: tuya.enum(5)}, + null, + ), + ], [103, 'battery', tuya.valueConverter.raw], ], }, @@ -184,34 +230,37 @@ const definitions: Definition[] = [ description: 'Zigbee/RF curtain converter', fromZigbee: [legacy.fz.ZMAM02_cover], toZigbee: [legacy.tz.ZMAM02_cover], - exposes: [e.cover_position().setAccess('position', ea.STATE_SET), - e.composite('options', 'options', ea.STATE) - .withFeature(e.numeric('motor_speed', ea.STATE) - .withValueMin(0) - .withValueMax(255) - .withDescription('Motor speed')), + exposes: [ + e.cover_position().setAccess('position', ea.STATE_SET), + e + .composite('options', 'options', ea.STATE) + .withFeature(e.numeric('motor_speed', ea.STATE).withValueMin(0).withValueMax(255).withDescription('Motor speed')), e.enum('motor_working_mode', ea.STATE_SET, Object.values(legacy.ZMLookups.AM02MotorWorkingMode)), e.numeric('percent_state', ea.STATE).withValueMin(0).withValueMax(100).withValueStep(1).withUnit('%'), e.enum('mode', ea.STATE_SET, Object.values(legacy.ZMLookups.AM02Mode)), e.enum('motor_direction', ea.STATE_SET, Object.values(legacy.ZMLookups.AM02Direction)), e.enum('border', ea.STATE_SET, Object.values(legacy.ZMLookups.AM02Border)), - // --------------------------------------------------------------------------------- - // DP exists, but not used at the moment - // e.numeric('percent_control', ea.STATE_SET).withValueMin(0).withValueMax(100).withValueStep(1).withUnit('%'), - // exposes.enum('work_state', ea.STATE, Object.values(tuya.ZMAM02.AM02WorkState)), - // e.numeric('countdown_left', ea.STATE).withUnit('s'), - // e.numeric('time_total', ea.STATE).withUnit('ms'), - // exposes.enum('situation_set', ea.STATE, Object.values(tuya.ZMAM02.AM02Situation)), + // --------------------------------------------------------------------------------- + // DP exists, but not used at the moment + // e.numeric('percent_control', ea.STATE_SET).withValueMin(0).withValueMax(100).withValueStep(1).withUnit('%'), + // exposes.enum('work_state', ea.STATE, Object.values(tuya.ZMAM02.AM02WorkState)), + // e.numeric('countdown_left', ea.STATE).withUnit('s'), + // e.numeric('time_total', ea.STATE).withUnit('ms'), + // exposes.enum('situation_set', ea.STATE, Object.values(tuya.ZMAM02.AM02Situation)), ], }, { - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_gubdgai2'}, {modelID: 'TS0601', manufacturerName: '_TZE200_vdiuwbkq'}], + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE200_gubdgai2'}, + {modelID: 'TS0601', manufacturerName: '_TZE200_vdiuwbkq'}, + ], model: 'M515EGBZTN', vendor: 'Zemismart', description: 'Roller shade driver', fromZigbee: [legacy.fz.ZMAM02_cover], toZigbee: [legacy.tz.ZMAM02_cover], - exposes: [e.cover_position().setAccess('position', ea.STATE_SET), + exposes: [ + e.cover_position().setAccess('position', ea.STATE_SET), e.enum('motor_direction', ea.STATE_SET, Object.values(legacy.ZMLookups.AM02Direction)), e.enum('border', ea.STATE_SET, Object.values(legacy.ZMLookups.AM02Border)), ], @@ -227,20 +276,25 @@ const definitions: Definition[] = [ extend: [forcePowerSource({powerSource: 'Mains (single phase)'})], }, { - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_1n2kyphz'}, {modelID: 'TS0601', manufacturerName: '_TZE200_shkxsgis'}, - {modelID: 'TS0601', manufacturerName: '_TZE204_shkxsgis'}], + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE200_1n2kyphz'}, + {modelID: 'TS0601', manufacturerName: '_TZE200_shkxsgis'}, + {modelID: 'TS0601', manufacturerName: '_TZE204_shkxsgis'}, + ], model: 'TB26-4', vendor: 'Zemismart', description: '4-gang smart wall switch', - exposes: [e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), + exposes: [ + e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l2').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l3').setAccess('state', ea.STATE_SET), - e.switch().withEndpoint('l4').setAccess('state', ea.STATE_SET)], + e.switch().withEndpoint('l4').setAccess('state', ea.STATE_SET), + ], fromZigbee: [fz.ignore_basic_report, legacy.fz.tuya_switch], toZigbee: [legacy.tz.tuya_switch_state], meta: {multiEndpoint: true}, endpoint: (device) => { - return {'l1': 1, 'l2': 1, 'l3': 1, 'l4': 1}; + return {l1: 1, l2: 1, l3: 1, l4: 1}; }, configure: async (device, coordinatorEndpoint) => { await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff']); @@ -252,21 +306,26 @@ const definitions: Definition[] = [ }, }, { - fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_9mahtqtg'}, {modelID: 'TS0601', manufacturerName: '_TZE200_r731zlxk'}], + fingerprint: [ + {modelID: 'TS0601', manufacturerName: '_TZE200_9mahtqtg'}, + {modelID: 'TS0601', manufacturerName: '_TZE200_r731zlxk'}, + ], model: 'TB26-6', vendor: 'Zemismart', description: '6-gang smart wall switch', - exposes: [e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), + exposes: [ + e.switch().withEndpoint('l1').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l2').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l3').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l4').setAccess('state', ea.STATE_SET), e.switch().withEndpoint('l5').setAccess('state', ea.STATE_SET), - e.switch().withEndpoint('l6').setAccess('state', ea.STATE_SET)], + e.switch().withEndpoint('l6').setAccess('state', ea.STATE_SET), + ], fromZigbee: [fz.ignore_basic_report, legacy.fz.tuya_switch], toZigbee: [legacy.tz.tuya_switch_state], meta: {multiEndpoint: true}, endpoint: (device) => { - return {'l1': 1, 'l2': 1, 'l3': 1, 'l4': 1, 'l5': 1, 'l6': 1}; + return {l1: 1, l2: 1, l3: 1, l4: 1, l5: 1, l6: 1}; }, configure: async (device, coordinatorEndpoint) => { await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff']); @@ -286,7 +345,7 @@ const definitions: Definition[] = [ vendor: 'Zemismart', description: 'Smart 2 poles outlet (20A + 10A)', extend: [ - deviceEndpoints({'endpoints': {'l1': 1, 'l2': 2}}), + deviceEndpoints({endpoints: {l1: 1, l2: 2}}), identify(), tuya.modernExtend.tuyaOnOff({indicatorMode: true, onOffCountdown: true, childLock: true, endpoints: ['l1', 'l2']}), ], @@ -297,7 +356,7 @@ const definitions: Definition[] = [ vendor: 'Zemismart', description: 'Smart 2 gangs switch with outlet', extend: [ - deviceEndpoints({'endpoints': {'l1': 1, 'l2': 2, 'l3': 3}}), + deviceEndpoints({endpoints: {l1: 1, l2: 2, l3: 3}}), identify(), tuya.modernExtend.tuyaOnOff({indicatorMode: true, onOffCountdown: true, endpoints: ['l1', 'l2', 'l3']}), ], diff --git a/src/devices/zen.ts b/src/devices/zen.ts index dc9a6f1d9bcd9..18c6afa437f5c 100644 --- a/src/devices/zen.ts +++ b/src/devices/zen.ts @@ -1,10 +1,10 @@ -import {Definition} from '../lib/types'; -import * as exposes from '../lib/exposes'; import fz from '../converters/fromZigbee'; -import * as legacy from '../lib/legacy'; import tz from '../converters/toZigbee'; -import * as reporting from '../lib/reporting'; +import * as exposes from '../lib/exposes'; +import * as legacy from '../lib/legacy'; import * as ota from '../lib/ota'; +import * as reporting from '../lib/reporting'; +import {Definition} from '../lib/types'; const e = exposes.presets; const definitions: Definition[] = [ @@ -14,17 +14,36 @@ const definitions: Definition[] = [ vendor: 'Zen', description: 'Thermostat', fromZigbee: [fz.battery, legacy.fz.thermostat_att_report], - toZigbee: [tz.thermostat_local_temperature, tz.thermostat_local_temperature_calibration, - tz.thermostat_occupancy, tz.thermostat_occupied_heating_setpoint, tz.thermostat_occupied_cooling_setpoint, - tz.thermostat_unoccupied_heating_setpoint, tz.thermostat_setpoint_raise_lower, tz.thermostat_running_state, - tz.thermostat_remote_sensing, tz.thermostat_control_sequence_of_operation, tz.thermostat_system_mode, - tz.thermostat_weekly_schedule, tz.thermostat_clear_weekly_schedule, tz.thermostat_relay_status_log, - tz.thermostat_keypad_lockout, tz.fan_mode], + toZigbee: [ + tz.thermostat_local_temperature, + tz.thermostat_local_temperature_calibration, + tz.thermostat_occupancy, + tz.thermostat_occupied_heating_setpoint, + tz.thermostat_occupied_cooling_setpoint, + tz.thermostat_unoccupied_heating_setpoint, + tz.thermostat_setpoint_raise_lower, + tz.thermostat_running_state, + tz.thermostat_remote_sensing, + tz.thermostat_control_sequence_of_operation, + tz.thermostat_system_mode, + tz.thermostat_weekly_schedule, + tz.thermostat_clear_weekly_schedule, + tz.thermostat_relay_status_log, + tz.thermostat_keypad_lockout, + tz.fan_mode, + ], ota: ota.zigbeeOTA, - exposes: [e.climate().withSetpoint('occupied_heating_setpoint', 10, 30, 0.5) - .withSetpoint('occupied_cooling_setpoint', 10, 31, 0.5).withLocalTemperature() - .withSystemMode(['off', 'auto', 'heat', 'cool', 'emergency_heating']).withRunningState(['idle', 'heat', 'cool']) - .withLocalTemperatureCalibration().withFanMode(['auto', 'on'])], + exposes: [ + e + .climate() + .withSetpoint('occupied_heating_setpoint', 10, 30, 0.5) + .withSetpoint('occupied_cooling_setpoint', 10, 31, 0.5) + .withLocalTemperature() + .withSystemMode(['off', 'auto', 'heat', 'cool', 'emergency_heating']) + .withRunningState(['idle', 'heat', 'cool']) + .withLocalTemperatureCalibration() + .withFanMode(['auto', 'on']), + ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(3) || device.getEndpoint(1); const binds = ['genBasic', 'genIdentify', 'genPowerCfg', 'genTime', 'hvacThermostat', 'hvacUserInterfaceCfg', 'hvacFanCtrl']; diff --git a/src/devices/zigbeetlc.ts b/src/devices/zigbeetlc.ts index 73e333b4912b4..fc35b51719d70 100644 --- a/src/devices/zigbeetlc.ts +++ b/src/devices/zigbeetlc.ts @@ -1,15 +1,6 @@ import {Zcl} from 'zigbee-herdsman'; -import { - battery, - binary, - enumLookup, - humidity, - numeric, - ota, - quirkAddEndpointCluster, - temperature, -} from '../lib/modernExtend'; +import {battery, binary, enumLookup, humidity, numeric, ota, quirkAddEndpointCluster, temperature} from '../lib/modernExtend'; const extend = { comfortDisplay: binary({ @@ -72,17 +63,8 @@ const extend = { }), endpointQuirk: quirkAddEndpointCluster({ endpointID: 1, - outputClusters: [ - 'genOta', - ], - inputClusters: [ - 'genBasic', - 'genPowerCfg', - 'genIdentify', - 'hvacUserInterfaceCfg', - 'msTemperatureMeasurement', - 'msRelativeHumidity', - ], + outputClusters: ['genOta'], + inputClusters: ['genBasic', 'genPowerCfg', 'genIdentify', 'hvacUserInterfaceCfg', 'msTemperatureMeasurement', 'msRelativeHumidity'], }), humidityCalibration: numeric({ name: 'humidity_calibration', @@ -117,7 +99,7 @@ const extend = { }), tempDisplayMode: enumLookup({ name: 'temperature_display_mode', - lookup: {'celsius': 0, 'fahrenheit': 1}, + lookup: {celsius: 0, fahrenheit: 1}, cluster: 'hvacUserInterfaceCfg', attribute: 'tempDisplayMode', description: 'The unit of the temperature displayed on the device screen.', diff --git a/src/devices/zipato.ts b/src/devices/zipato.ts index d1f3c776727d2..69cf8a9d7ad3c 100644 --- a/src/devices/zipato.ts +++ b/src/devices/zipato.ts @@ -1,5 +1,5 @@ -import {Definition} from '../lib/types'; import {light} from '../lib/modernExtend'; +import {Definition} from '../lib/types'; const definitions: Definition[] = [ { diff --git a/src/index.ts b/src/index.ts index 4eef6afd019de..672861cd201bb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,21 @@ import assert from 'assert'; import * as ota from './lib/ota'; import allDefinitions from './devices'; import * as utils from './lib/utils'; -import {Definition, Fingerprint, Zh, OnEventData, OnEventType, Configure, Expose, Tz, OtaUpdateAvailableResult, KeyValue, OnEvent, DefinitionExposes, DefinitionExposesFunction} from './lib/types'; +import { + Definition, + Fingerprint, + Zh, + OnEventData, + OnEventType, + Configure, + Expose, + Tz, + OtaUpdateAvailableResult, + KeyValue, + OnEvent, + DefinitionExposes, + DefinitionExposesFunction, +} from './lib/types'; import {generateDefinition} from './lib/generateDefinition'; import {Zcl} from 'zigbee-herdsman'; import * as logger from './lib/logger'; @@ -41,7 +55,6 @@ export { export const getConfigureKey = configureKey.getConfigureKey; - // key: zigbeeModel, value: array of definitions (most of the times 1) const lookup = new Map(); export const definitions: Definition[] = []; @@ -102,8 +115,14 @@ function processExtensions(definition: Definition): Definition { } // Modern extend, merges properties, e.g. when both extend and definition has toZigbee, toZigbee will be combined let { - extend, toZigbee, fromZigbee, exposes: definitionExposes, meta, endpoint, ota, - configure: definitionConfigure, + extend, + toZigbee, + fromZigbee, + exposes: definitionExposes, + meta, + endpoint, + ota, + configure: definitionConfigure, onEvent: definitionOnEvent, ...definitionWithoutExtend } = definition; @@ -112,13 +131,13 @@ function processExtensions(definition: Definition): Definition { // Otherwise return a DefinitionExposesFunction. const allExposesIsExposeOnly = (allExposes: (Expose | DefinitionExposesFunction)[]): allExposes is Expose[] => { return !allExposes.find((e) => typeof e === 'function'); - } + }; let allExposes: (Expose | DefinitionExposesFunction)[] = []; if (definitionExposes) { typeof definitionExposes === 'function' ? allExposes.push(definitionExposes) : allExposes.push(...definitionExposes); } - toZigbee = [...toZigbee ?? []]; - fromZigbee = [...fromZigbee ?? []]; + toZigbee = [...(toZigbee ?? [])]; + fromZigbee = [...(fromZigbee ?? [])]; const configures: Configure[] = definitionConfigure ? [definitionConfigure] : []; const onEvents: OnEvent[] = definitionOnEvent ? [definitionOnEvent] : []; @@ -156,10 +175,10 @@ function processExtensions(definition: Definition): Definition { for (const expose of actionExposes) { if (expose instanceof EnumClass) { for (const action of expose.values) { - actions.push(action.toString()) - } + actions.push(action.toString()); + } } - } + } const uniqueActions = actions.filter((value, index, array) => array.indexOf(value) === index); allExposes.push(exposesLib.presets.action(uniqueActions)); } @@ -170,7 +189,7 @@ function processExtensions(definition: Definition): Definition { for (const func of configures) { await func(device, coordinatorEndpoint, configureDefinition); } - } + }; } let onEvent: OnEvent = null; if (onEvents.length !== 0) { @@ -178,7 +197,7 @@ function processExtensions(definition: Definition): Definition { for (const func of onEvents) { await func(type, data, device, settings, state); } - } + }; } // In case there is a function in allExposes, return a function, otherwise just an array. @@ -196,22 +215,31 @@ function processExtensions(definition: Definition): Definition { } } return result; - } + }; } definition = {toZigbee, fromZigbee, exposes, meta, configure, endpoint, onEvent, ota, ...definitionWithoutExtend}; } - return definition + return definition; } function prepareDefinition(definition: Definition): Definition { definition = processExtensions(definition); definition.toZigbee.push( - toZigbee.scene_store, toZigbee.scene_recall, toZigbee.scene_add, toZigbee.scene_remove, toZigbee.scene_remove_all, - toZigbee.scene_rename, toZigbee.read, toZigbee.write, - toZigbee.command, toZigbee.factory_reset, toZigbee.zcl_command); + toZigbee.scene_store, + toZigbee.scene_recall, + toZigbee.scene_add, + toZigbee.scene_remove, + toZigbee.scene_remove_all, + toZigbee.scene_rename, + toZigbee.read, + toZigbee.write, + toZigbee.command, + toZigbee.factory_reset, + toZigbee.zcl_command, + ); if (definition.exposes && Array.isArray(definition.exposes) && !definition.exposes.find((e) => e.name === 'linkquality')) { definition.exposes = definition.exposes.concat([exposesLib.presets.linkquality()]); @@ -225,7 +253,11 @@ function prepareDefinition(definition: Definition): Definition { // Add calibration/precision options based on expose for (const expose of Array.isArray(definition.exposes) ? definition.exposes : definition.exposes(null, null)) { - if (!optionKeys.includes(expose.name) && utils.isNumericExposeFeature(expose) && expose.name in utils.calibrateAndPrecisionRoundOptionsDefaultPrecision) { + if ( + !optionKeys.includes(expose.name) && + utils.isNumericExposeFeature(expose) && + expose.name in utils.calibrateAndPrecisionRoundOptionsDefaultPrecision + ) { // Battery voltage is not calibratable if (expose.name === 'voltage' && expose.unit === 'mV') continue; const type = utils.calibrateAndPrecisionRoundOptionsIsPercentual(expose.name) ? 'percentual' : 'absolute'; @@ -249,7 +281,7 @@ function prepareDefinition(definition: Definition): Definition { } } - return definition + return definition; } export function postProcessConvertedFromZigbeeMessage(definition: Definition, payload: KeyValue, options: KeyValue) { @@ -268,7 +300,7 @@ export function postProcessConvertedFromZigbeeMessage(definition: Definition, pa } export function addDefinition(definition: Definition) { - definition = prepareDefinition(definition) + definition = prepareDefinition(definition); definitions.splice(0, 0, definition); @@ -324,7 +356,7 @@ export async function findDefinition(device: Zh.Device, generateForUnknown: bool return candidates[0]; } else { // First try to match based on fingerprint, return the first matching one. - const fingerprintMatch: {priority: number, definition: Definition} = {priority: null, definition: null}; + const fingerprintMatch: {priority: number; definition: Definition} = {priority: null, definition: null}; for (const candidate of candidates) { if (candidate.fingerprint) { @@ -372,25 +404,27 @@ function isFingerprintMatch(fingerprint: Fingerprint, device: Zh.Device) { (!fingerprint.zclVersion || device.zclVersion === fingerprint.zclVersion) && (!fingerprint.ieeeAddr || device.ieeeAddr.match(fingerprint.ieeeAddr)) && (!fingerprint.endpoints || - arrayEquals(device.endpoints.map((e) => e.ID), fingerprint.endpoints.map((e) => e.ID))); + arrayEquals( + device.endpoints.map((e) => e.ID), + fingerprint.endpoints.map((e) => e.ID), + )); if (match && fingerprint.endpoints) { for (const fingerprintEndpoint of fingerprint.endpoints) { const deviceEndpoint = device.getEndpoint(fingerprintEndpoint.ID); - match = match && + match = + match && (!fingerprintEndpoint.deviceID || deviceEndpoint.deviceID === fingerprintEndpoint.deviceID) && (!fingerprintEndpoint.profileID || deviceEndpoint.profileID === fingerprintEndpoint.profileID) && - (!fingerprintEndpoint.inputClusters || - arrayEquals(deviceEndpoint.inputClusters, fingerprintEndpoint.inputClusters)) && - (!fingerprintEndpoint.outputClusters || - arrayEquals(deviceEndpoint.outputClusters, fingerprintEndpoint.outputClusters)); + (!fingerprintEndpoint.inputClusters || arrayEquals(deviceEndpoint.inputClusters, fingerprintEndpoint.inputClusters)) && + (!fingerprintEndpoint.outputClusters || arrayEquals(deviceEndpoint.outputClusters, fingerprintEndpoint.outputClusters)); } } return match; } -export function findByModel(model: string){ +export function findByModel(model: string) { /* Search device description by definition model name. Useful when redefining, expanding device descriptions in external converters. @@ -417,11 +451,11 @@ export async function onEvent(type: OnEventType, data: OnEventData, device: Zh.D const payload = {0xf00: {value: 23, type: 35}}; endpoint.readResponse('genBasic', frame.header.transactionSequenceNumber, payload, options).catch((e) => { logger.logger.warning(`Legrand security read response failed: ${e}`, NS); - }) + }); return true; } return false; - } + }; } // Aqara feeder C1 polls the time during the interview, need to send back the local time instead of the UTC. @@ -430,15 +464,15 @@ export async function onEvent(type: OnEventType, data: OnEventData, device: Zh.D device.customReadResponse = (frame, endpoint) => { if (frame.isCluster('genTime')) { const oneJanuary2000 = new Date('January 01, 2000 00:00:00 UTC+00:00').getTime(); - const secondsUTC = Math.round(((new Date()).getTime() - oneJanuary2000) / 1000); - const secondsLocal = secondsUTC - (new Date()).getTimezoneOffset() * 60; + const secondsUTC = Math.round((new Date().getTime() - oneJanuary2000) / 1000); + const secondsLocal = secondsUTC - new Date().getTimezoneOffset() * 60; endpoint.readResponse('genTime', frame.header.transactionSequenceNumber, {time: secondsLocal}).catch((e) => { logger.logger.warning(`ZNCWWSQ01LM custom time response failed: ${e}`, NS); - }) + }); return true; } return false; - } + }; } } diff --git a/src/lib/color.ts b/src/lib/color.ts index 1b15c1e5d0dc1..32d033b80b24a 100644 --- a/src/lib/color.ts +++ b/src/lib/color.ts @@ -1,8 +1,7 @@ import kelvinToXyLookup from './kelvinToXy'; -import {precisionRound} from './utils'; import {findColorTempRange, clampColorTemp} from './light'; import {KeyValueAny, Tz, Zh, KeyValue} from './types'; - +import {precisionRound} from './utils'; /** * Converts color temp mireds to Kelvins @@ -58,7 +57,7 @@ export class ColorRGB { * @param rgb - object with properties red, green and blue * @returns new ColoRGB object */ - static fromObject(rgb: {red: number, green: number, blue: number}) { + static fromObject(rgb: {red: number; green: number; blue: number}) { if (!rgb.hasOwnProperty('red') || !rgb.hasOwnProperty('green') || !rgb.hasOwnProperty('blue')) { throw new Error('One or more required properties missing. Required properties: "red", "green", "blue"'); } @@ -81,18 +80,14 @@ export class ColorRGB { * @param precision - decimal places to round to */ rounded(precision: number): ColorRGB { - return new ColorRGB( - precisionRound(this.red, precision), - precisionRound(this.green, precision), - precisionRound(this.blue, precision), - ); + return new ColorRGB(precisionRound(this.red, precision), precisionRound(this.green, precision), precisionRound(this.blue, precision)); } /** * Convert to Object * @returns object with properties red, green and blue */ - toObject(): {red: number, green: number, blue: number} { + toObject(): {red: number; green: number; blue: number} { return { red: this.red, green: this.green, @@ -110,17 +105,29 @@ export class ColorRGB { const g = this.green; const b = this.blue; - const max = Math.max(r, g, b); const min = Math.min(r, g, b); + const max = Math.max(r, g, b); + const min = Math.min(r, g, b); const d = max - min; let h; - const s = (max === 0 ? 0 : d / max); + const s = max === 0 ? 0 : d / max; const v = max; switch (max) { - case min: h = 0; break; - case r: h = (g - b) + d * (g < b ? 6: 0); h /= 6 * d; break; - case g: h = (b - r) + d * 2; h /= 6 * d; break; - case b: h = (r - g) + d * 4; h /= 6 * d; break; + case min: + h = 0; + break; + case r: + h = g - b + d * (g < b ? 6 : 0); + h /= 6 * d; + break; + case g: + h = b - r + d * 2; + h /= 6 * d; + break; + case b: + h = r - g + d * 4; + h /= 6 * d; + break; } return new ColorHSV(h * 360, s * 100, v * 100); @@ -136,11 +143,11 @@ export class ColorRGB { // RGB values to XYZ using the Wide RGB D65 conversion formula const X = this.red * 0.664511 + this.green * 0.154324 + this.blue * 0.162028; const Y = this.red * 0.283881 + this.green * 0.668433 + this.blue * 0.047685; - const Z = this.red * 0.000088 + this.green * 0.072310 + this.blue * 0.986039; + const Z = this.red * 0.000088 + this.green * 0.07231 + this.blue * 0.986039; const sum = X + Y + Z; - const retX = (sum == 0) ? 0 : X / sum; - const retY = (sum == 0) ? 0 : Y / sum; + const retX = sum == 0 ? 0 : X / sum; + const retY = sum == 0 ? 0 : Y / sum; return new ColorXY(retX, retY); } @@ -151,7 +158,7 @@ export class ColorRGB { */ gammaCorrected(): ColorRGB { function transform(v: number) { - return (v > 0.04045) ? Math.pow((v + 0.055) / (1.0 + 0.055), 2.4) : (v / 12.92); + return v > 0.04045 ? Math.pow((v + 0.055) / (1.0 + 0.055), 2.4) : v / 12.92; } return new ColorRGB(transform(this.red), transform(this.green), transform(this.blue)); } @@ -162,7 +169,7 @@ export class ColorRGB { */ gammaUncorrected(): ColorRGB { function transform(v: number) { - return v <= 0.0031308 ? 12.92 * v : (1.0 + 0.055) * Math.pow(v, (1.0 / 2.4)) - 0.055; + return v <= 0.0031308 ? 12.92 * v : (1.0 + 0.055) * Math.pow(v, 1.0 / 2.4) - 0.055; } return new ColorRGB(transform(this.red), transform(this.green), transform(this.blue)); } @@ -172,10 +179,18 @@ export class ColorRGB { * @returns hex hex encoded RGB color */ toHEX(): string { - return '#' + - parseInt((this.red * 255).toFixed(0)).toString(16).padStart(2, '0')+ - parseInt((this.green * 255).toFixed(0)).toString(16).padStart(2, '0')+ - parseInt((this.blue * 255).toFixed(0)).toString(16).padStart(2, '0'); + return ( + '#' + + parseInt((this.red * 255).toFixed(0)) + .toString(16) + .padStart(2, '0') + + parseInt((this.green * 255).toFixed(0)) + .toString(16) + .padStart(2, '0') + + parseInt((this.blue * 255).toFixed(0)) + .toString(16) + .padStart(2, '0') + ); } } @@ -203,7 +218,7 @@ export class ColorXY { * @param xy - object with properties x and y * @returns new ColorXY object */ - static fromObject(xy: {x: number, y: number}): ColorXY { + static fromObject(xy: {x: number; y: number}): ColorXY { if (!xy.hasOwnProperty('x') || !xy.hasOwnProperty('y')) { throw new Error('One or more required properties missing. Required properties: "x", "y"'); } @@ -225,7 +240,7 @@ export class ColorXY { * @returns color temp in mireds */ toMireds(): number { - const n = (this.x - 0.3320) / (0.1858 - this.y); + const n = (this.x - 0.332) / (0.1858 - this.y); const kelvin = Math.abs(437 * Math.pow(n, 3) + 3601 * Math.pow(n, 2) + 6861 * n + 5517); return kelvinToMireds(kelvin); } @@ -246,7 +261,7 @@ export class ColorXY { // Convert to RGB using Wide RGB D65 conversion let red = X * 1.656492 - Y * 0.354851 - Z * 0.255038; let green = -X * 0.707196 + Y * 1.655397 + Z * 0.036152; - let blue = X * 0.051713 - Y * 0.121364 + Z * 1.011530; + let blue = X * 0.051713 - Y * 0.121364 + Z * 1.01153; // If red, green or blue is larger than 1.0 set it back to the maximum of 1.0 if (red > blue && red > green && red > 1.0) { @@ -264,9 +279,9 @@ export class ColorXY { } // This fixes situation when due to computational errors value get slightly below 0, or NaN in case of zero-division. - red = (isNaN(red) || red < 0) ? 0 : red; - green = (isNaN(green) || green < 0) ? 0 : green; - blue = (isNaN(blue) || blue < 0) ? 0 : blue; + red = isNaN(red) || red < 0 ? 0 : red; + green = isNaN(green) || green < 0 ? 0 : green; + blue = isNaN(blue) || blue < 0 ? 0 : blue; return new ColorRGB(red, green, blue); } @@ -284,17 +299,14 @@ export class ColorXY { * @param precision - decimal places to round to */ rounded(precision: number): ColorXY { - return new ColorXY( - precisionRound(this.x, precision), - precisionRound(this.y, precision), - ); + return new ColorXY(precisionRound(this.x, precision), precisionRound(this.y, precision)); } /** * Convert to object * @returns object with properties x and y */ - toObject(): {x: number, y: number} { + toObject(): {x: number; y: number} { return { x: this.x, y: this.y, @@ -316,9 +328,9 @@ class ColorHSV { /** * Create color in HSV space */ - constructor(hue: number, saturation: number=null, value: number=null) { + constructor(hue: number, saturation: number = null, value: number = null) { /** hue component (0..360) */ - this.hue = (hue === null) ? null : hue % 360; + this.hue = hue === null ? null : hue % 360; /** saturation component (0..100) */ this.saturation = saturation; /** value component (0..100) */ @@ -328,11 +340,11 @@ class ColorHSV { /** * Create HSV color from object */ - static fromObject(hsv: {hue?: number, saturation?: number, value: number}): ColorHSV { + static fromObject(hsv: {hue?: number; saturation?: number; value: number}): ColorHSV { if (!hsv.hasOwnProperty('hue') && !hsv.hasOwnProperty('saturation')) { throw new Error('HSV color must specify at least hue or saturation.'); } - return new ColorHSV((hsv.hue === undefined) ? null : hsv.hue, hsv.saturation, hsv.value); + return new ColorHSV(hsv.hue === undefined ? null : hsv.hue, hsv.saturation, hsv.value); } /** @@ -340,13 +352,13 @@ class ColorHSV { * @param hsl - color in HSL space * @returns color in HSV space */ - static fromHSL(hsl: {hue: number, saturation: number, lightness: number}): ColorHSV { + static fromHSL(hsl: {hue: number; saturation: number; lightness: number}): ColorHSV { if (!hsl.hasOwnProperty('hue') || !hsl.hasOwnProperty('saturation') || !hsl.hasOwnProperty('lightness')) { throw new Error('One or more required properties missing. Required properties: "hue", "saturation", "lightness"'); } const retH = hsl.hue; - const retV = hsl.saturation * Math.min(hsl.lightness, 100 - hsl.lightness) / 100 + hsl.lightness; - const retS = retV ? (200 * (1 - hsl.lightness / retV)) : 0; + const retV = (hsl.saturation * Math.min(hsl.lightness, 100 - hsl.lightness)) / 100 + hsl.lightness; + const retS = retV ? 200 * (1 - hsl.lightness / retV) : 0; return new ColorHSV(retH, retS, retV); } @@ -368,9 +380,10 @@ class ColorHSV { * @param includeValue - omit v(alue) from return */ toObject( - short: boolean=false, includeValue: boolean=true, - ): {h?: number, hue?: number, s?: number, saturation?: number, v?:number, value?: number} { - const ret: {h?: number, hue?: number, s?: number, saturation?: number, v?:number, value?: number} = {}; + short: boolean = false, + includeValue: boolean = true, + ): {h?: number; hue?: number; s?: number; saturation?: number; v?: number; value?: number} { + const ret: {h?: number; hue?: number; s?: number; saturation?: number; v?: number; value?: number} = {}; if (this.hue !== null) { if (short) { ret.h = this.hue; @@ -385,7 +398,7 @@ class ColorHSV { ret.saturation = this.saturation; } } - if ((this.value !== null) && includeValue) { + if (this.value !== null && includeValue) { if (short) { ret.v = this.value; } else { @@ -405,19 +418,33 @@ class ColorHSV { const s = hsvComplete.saturation / 100; const v = hsvComplete.value / 100; - let r; let g; let b; + let r; + let g; + let b; const i = Math.floor(h * 6); const f = h * 6 - i; const p = v * (1 - s); const q = v * (1 - f * s); const t = v * (1 - (1 - f) * s); switch (i % 6) { - case 0: r = v, g = t, b = p; break; - case 1: r = q, g = v, b = p; break; - case 2: r = p, g = v, b = t; break; - case 3: r = p, g = q, b = v; break; - case 4: r = t, g = p, b = v; break; - case 5: r = v, g = p, b = q; break; + case 0: + (r = v), (g = t), (b = p); + break; + case 1: + (r = q), (g = v), (b = p); + break; + case 2: + (r = p), (g = v), (b = t); + break; + case 3: + (r = p), (g = q), (b = v); + break; + case 4: + (r = t), (g = p), (b = v); + break; + case 5: + (r = v), (g = p), (b = q); + break; } return new ColorRGB(r, g, b); } @@ -462,11 +489,11 @@ class ColorHSV { // reverse sort calibration map and find left edge clonedCorrectionMap.sort((a, b) => b.in - a.in); - const correctionLeft = clonedCorrectionMap.find((m) => m.in <= hue) || {'in': 0, 'out': 0}; + const correctionLeft = clonedCorrectionMap.find((m) => m.in <= hue) || {in: 0, out: 0}; // sort calibration map and find right edge clonedCorrectionMap.sort((a, b) => a.in - b.in); - const correctionRight = clonedCorrectionMap.find((m) => m.in > hue) || {'in': 359, 'out': 359}; + const correctionRight = clonedCorrectionMap.find((m) => m.in > hue) || {in: 359, out: 359}; const ratio = 1 - (correctionRight.in - hue) / (correctionRight.in - correctionLeft.in); return Math.round(correctionLeft.out + ratio * (correctionRight.out - correctionLeft.out)); @@ -529,14 +556,13 @@ export class Color { if (!(rgb instanceof ColorRGB)) { throw new Error('rgb argument must be an instance of ColorRGB class'); } - } else /* if (xy !== null) */ { + } /* if (xy !== null) */ else { if (!(xy instanceof ColorXY)) { throw new Error('xy argument must be an instance of ColorXY class'); } } this.hsv = hsv; - this.rgb = rgb, - this.xy = xy; + (this.rgb = rgb), (this.xy = xy); } /** @@ -544,7 +570,7 @@ export class Color { * @param value - converter value argument * @returns Color object */ - // eslint-disable-next-line + // eslint-disable-next-line @typescript-eslint/no-explicit-any static fromConverterArg(value: any): Color { if (value.hasOwnProperty('x') && value.hasOwnProperty('y')) { const xy = ColorXY.fromObject(value); @@ -632,11 +658,9 @@ export class Color { * @returns state with color, color_temp, and color_mode set and synchronized from newState's attributes * (other attributes are not included make sure to merge yourself) */ -export function syncColorState( - newState: KeyValueAny, oldState: KeyValueAny, endpoint: Zh.Endpoint | Zh.Group, options: KeyValue, -): KeyValueAny { +export function syncColorState(newState: KeyValueAny, oldState: KeyValueAny, endpoint: Zh.Endpoint | Zh.Group, options: KeyValue): KeyValueAny { const colorTargets = []; - const colorSync = (options && options.hasOwnProperty('color_sync')) ? options.color_sync : true; + const colorSync = options && options.hasOwnProperty('color_sync') ? options.color_sync : true; const result: KeyValueAny = {}; const [colorTempMin, colorTempMax] = findColorTempRange(endpoint); @@ -659,8 +683,11 @@ export function syncColorState( } else if (oldState.hasOwnProperty('color_mode')) { result.color_mode = oldState.color_mode; } else { - result.color_mode = newState.hasOwnProperty('color_temp') ? 'color_temp' : - (newState.hasOwnProperty('color') && newState.color.hasOwnProperty('hue') ? 'hs' : 'xy'); + result.color_mode = newState.hasOwnProperty('color_temp') + ? 'color_temp' + : newState.hasOwnProperty('color') && newState.color.hasOwnProperty('hue') + ? 'hs' + : 'xy'; } // figure out target attributes @@ -683,67 +710,67 @@ export function syncColorState( // sync color attributes result.color = {}; switch (result.color_mode) { - case 'hs': - if (newState.hasOwnProperty('color') && newState.color.hasOwnProperty('hue')) { - Object.assign(result.color, {'hue': newState.color.hue}); - } else if (oldState.hasOwnProperty('color') && oldState.color.hasOwnProperty('hue')) { - Object.assign(result.color, {'hue': oldState.color.hue}); - } - if (newState.hasOwnProperty('color') && newState.color.hasOwnProperty('saturation')) { - Object.assign(result.color, {'saturation': newState.color.saturation}); - } else if (oldState.hasOwnProperty('color') && oldState.color.hasOwnProperty('saturation')) { - Object.assign(result.color, {'saturation': oldState.color.saturation}); - } - - if (result.color.hasOwnProperty('hue') && result.color.hasOwnProperty('saturation')) { - const hsv = new ColorHSV(result.color.hue, result.color.saturation); - if (colorTargets.includes('color_temp')) { - result.color_temp = clampColorTemp(precisionRound(hsv.toMireds(), 0), colorTempMin, colorTempMax); + case 'hs': + if (newState.hasOwnProperty('color') && newState.color.hasOwnProperty('hue')) { + Object.assign(result.color, {hue: newState.color.hue}); + } else if (oldState.hasOwnProperty('color') && oldState.color.hasOwnProperty('hue')) { + Object.assign(result.color, {hue: oldState.color.hue}); } - if (colorTargets.includes('xy')) { - Object.assign(result.color, hsv.toXY().rounded(4).toObject()); + if (newState.hasOwnProperty('color') && newState.color.hasOwnProperty('saturation')) { + Object.assign(result.color, {saturation: newState.color.saturation}); + } else if (oldState.hasOwnProperty('color') && oldState.color.hasOwnProperty('saturation')) { + Object.assign(result.color, {saturation: oldState.color.saturation}); } - } - break; - case 'xy': - if (newState.hasOwnProperty('color') && newState.color.hasOwnProperty('x')) { - Object.assign(result.color, {'x': newState.color.x}); - } else if (oldState.hasOwnProperty('color') && oldState.color.hasOwnProperty('x')) { - Object.assign(result.color, {'x': oldState.color.x}); - } - if (newState.hasOwnProperty('color') && newState.color.hasOwnProperty('y')) { - Object.assign(result.color, {'y': newState.color.y}); - } else if (oldState.hasOwnProperty('color') && oldState.color.hasOwnProperty('y')) { - Object.assign(result.color, {'y': oldState.color.y}); - } - if (result.color.hasOwnProperty('x') && result.color.hasOwnProperty('y')) { - const xy = new ColorXY(result.color.x, result.color.y); - if (colorTargets.includes('color_temp')) { - result.color_temp = clampColorTemp(precisionRound(xy.toMireds(), 0), colorTempMin, colorTempMax); + if (result.color.hasOwnProperty('hue') && result.color.hasOwnProperty('saturation')) { + const hsv = new ColorHSV(result.color.hue, result.color.saturation); + if (colorTargets.includes('color_temp')) { + result.color_temp = clampColorTemp(precisionRound(hsv.toMireds(), 0), colorTempMin, colorTempMax); + } + if (colorTargets.includes('xy')) { + Object.assign(result.color, hsv.toXY().rounded(4).toObject()); + } } - if (colorTargets.includes('hs')) { - Object.assign(result.color, xy.toHSV().rounded(0).toObject(false, false)); + break; + case 'xy': + if (newState.hasOwnProperty('color') && newState.color.hasOwnProperty('x')) { + Object.assign(result.color, {x: newState.color.x}); + } else if (oldState.hasOwnProperty('color') && oldState.color.hasOwnProperty('x')) { + Object.assign(result.color, {x: oldState.color.x}); + } + if (newState.hasOwnProperty('color') && newState.color.hasOwnProperty('y')) { + Object.assign(result.color, {y: newState.color.y}); + } else if (oldState.hasOwnProperty('color') && oldState.color.hasOwnProperty('y')) { + Object.assign(result.color, {y: oldState.color.y}); } - } - break; - case 'color_temp': - if (newState.hasOwnProperty('color_temp')) { - result.color_temp = newState.color_temp; - } else if (oldState.hasOwnProperty('color_temp')) { - result.color_temp = oldState.color_temp; - } - if (result.hasOwnProperty('color_temp')) { - const xy = ColorXY.fromMireds(result.color_temp); - if (colorTargets.includes('xy')) { - Object.assign(result.color, xy.rounded(4).toObject()); + if (result.color.hasOwnProperty('x') && result.color.hasOwnProperty('y')) { + const xy = new ColorXY(result.color.x, result.color.y); + if (colorTargets.includes('color_temp')) { + result.color_temp = clampColorTemp(precisionRound(xy.toMireds(), 0), colorTempMin, colorTempMax); + } + if (colorTargets.includes('hs')) { + Object.assign(result.color, xy.toHSV().rounded(0).toObject(false, false)); + } } - if (colorTargets.includes('hs')) { - Object.assign(result.color, xy.toHSV().rounded(0).toObject(false, false)); + break; + case 'color_temp': + if (newState.hasOwnProperty('color_temp')) { + result.color_temp = newState.color_temp; + } else if (oldState.hasOwnProperty('color_temp')) { + result.color_temp = oldState.color_temp; } - } - break; + + if (result.hasOwnProperty('color_temp')) { + const xy = ColorXY.fromMireds(result.color_temp); + if (colorTargets.includes('xy')) { + Object.assign(result.color, xy.rounded(4).toObject()); + } + if (colorTargets.includes('hs')) { + Object.assign(result.color, xy.toHSV().rounded(0).toObject(false, false)); + } + } + break; } // drop empty result.color diff --git a/src/lib/configureKey.ts b/src/lib/configureKey.ts index fe1a568a7bc71..6223cd961a9ac 100644 --- a/src/lib/configureKey.ts +++ b/src/lib/configureKey.ts @@ -9,7 +9,7 @@ function hashCode(str: string) { } for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); - hash = ((hash<<5)-hash)+char; + hash = (hash << 5) - hash + char; hash = hash & hash; // Convert to 32bit integer } return hash; @@ -51,11 +51,11 @@ const legacyKeys: {[s: string]: number} = { '81825_-1560235388': 1, '81849_-1455162343': 1, '81855_-1455162343': 1, - 'BPU3_1466202208': 1, + BPU3_1466202208: 1, '4713407_-1239574396': 2, 'CTR.UBX_1721592371': 1, - 'Aj_Zigbee_Led_Strip_88764544': 2, - 'AJ_ZB_GU10_88764544': 2, + Aj_Zigbee_Led_Strip_88764544: 2, + AJ_ZB_GU10_88764544: 2, 'L122FF63H11A5.0W_88764544': 2, 'L122CB63H11A9.0W_88764544': 2, 'L122AA63H11A6.5W_88764544': 2, @@ -63,7 +63,7 @@ const legacyKeys: {[s: string]: number} = { 'C422AC11D41H140.0W_88764544': 2, 'C422AC14D41H140.0W_88764544': 2, '67200BL_639411107': 1, - 'Z6_398307118': 1, + Z6_398307118: 1, 'AU-A1GUZBCX5_88764544': 2, 'AU-A1GUZBRGBW_88764544': 2, 'AU-A1GSZ9RGBW_HV-GSCXZB269K_88764544': 2, @@ -141,19 +141,19 @@ const legacyKeys: {[s: string]: number} = { 'SMRZB-332_-570313272': 1, 'DIYRuZ_FreePad_-1164805536': 1, 'FreePad_LeTV_8_-1164805536': 1, - 'DIYRuZ_Geiger_1515339524': 1, - 'DIYRuZ_Flower_1834002472': 1, + DIYRuZ_Geiger_1515339524: 1, + DIYRuZ_Flower_1834002472: 1, 'DIYRuZ_AirSense_-1397322426': 1, - 'Mega23M12_88764544': 2, + Mega23M12_88764544: 2, 'XVV-Mega23M12_88764544': 2, 'EasyCode903G2.1_1220202591': 1, - 'RL460WHZHA69_88764544': 2, - 'SAGE206611_785189978': 1, + RL460WHZHA69_88764544: 2, + SAGE206611_785189978: 1, '4655BC0-R_1257941778': 1, - 'A9A19A60WESDZ02_88764544': 2, - 'A9BR3065WESDZ02_88764544': 2, - 'D1821_88764544': 2, - 'D1542_88764544': 2, + A9A19A60WESDZ02_88764544: 2, + A9BR3065WESDZ02_88764544: 2, + D1821_88764544: 2, + D1542_88764544: 2, '1TST-EU_1215199571': 1, 'PLUG EDP RE:DY_-1416421624': 3, 'SWITCH EDP RE:DY_-975288168': 2, @@ -171,18 +171,18 @@ const legacyKeys: {[s: string]: number} = { 'ZB-SW03_-1997814597': 1, 'ECW-100-A03_-1997814597': 1, 'TZSW22FW-L4_-1140672598': 1, - 'SBM01ZB_1553276182': 1, - 'SSA01ZB_1553276182': 1, - 'SCA01ZB_1553276182': 1, + SBM01ZB_1553276182: 1, + SSA01ZB_1553276182: 1, + SCA01ZB_1553276182: 1, 'SLS301ZB_2_-1140672598': 1, - 'SLS301ZB_3_934212491': 1, + SLS301ZB_3_934212491: 1, '45852GE_1466202208': 1, '45853GE_1783627105': 4, '45856GE_1466202208': 1, '45857GE_1466202208': 1, 'PTAPT-WH02_513000575': 1, - 'GWA1521_1466202208': 1, - 'GWA1531_734529022': 1, + GWA1521_1466202208: 1, + GWA1531_734529022: 1, 'GL-H-001_88764544': 2, 'GL-C-006_88764544': 2, 'GL-C-006S_88764544': 2, @@ -237,18 +237,18 @@ const legacyKeys: {[s: string]: number} = { 'GL-G-001ZS_88764544': 2, 'GL-G-001P_88764544': 2, 'GL-G-007Z_88764544': 2, - 'B07KG5KF5R_88764544': 2, + B07KG5KF5R_88764544: 2, 'SSHM-I1_491743244': 1, 'BRHM8E27W70-I1_1676534437': 2, '99432_-1806066778': 1, '54668161_88764544': 2, 'HS1CA-M_1553276182': 1, 'HS2SK_-1105234664': 5, - 'HS2SK_nxp_1881458503': 5, - 'HS1SA_491743244': 1, - 'HS3SA_491743244': 1, - 'HS3DS_1036280848': 1, - 'HS1DS_1036280848': 1, + HS2SK_nxp_1881458503: 5, + HS1SA_491743244: 1, + HS3SA_491743244: 1, + HS3DS_1036280848: 1, + HS1DS_1036280848: 1, 'HS1WL/HS3WL_1036280848': 1, 'HS1RC-N_1036280848': 1, 'HM1RC-2-E_1036280848': 1, @@ -265,7 +265,7 @@ const legacyKeys: {[s: string]: number} = { 'HS1VS-N_1036280848': 1, 'HS1VS-EF_1036280848': 1, 'HS2AQ-EM_-492954587': 1, - 'HS2IRC_1630495734': 1, + HS2IRC_1630495734: 1, 'BDHM8E27W70-I1_88764544': 2, 'HS2SW1A/HS2SW1A-N_-1621764089': 1, 'HS2SW2A/HS2SW2A-N_-1610006317': 1, @@ -276,18 +276,18 @@ const legacyKeys: {[s: string]: number} = { 'GLSK6ZB-1714_-652790423': 1, 'GLSK6ZB-1715_-1379451466': 1, 'GLSK6ZB-1716_-1915097310': 1, - 'MOT003_752847752': 1, - 'DWS003_752847752': 1, + MOT003_752847752: 1, + DWS003_752847752: 1, 'HV-GSCXZB229B_88764544': 2, '1613V_694625864': 3, 'HV-GSCXZB269_88764544': 2, 'HV-GSCXZB279_HV-GSCXZB229_HV-GSCXZB229K_88764544': 2, 'HV-GUCXZB5_88764544': 2, 'UK7004240_-1419162246': 1, - 'SLR1b_1715456678': 1, + SLR1b_1715456678: 1, 'SLR2_-1078819997': 3, 'SLR2b_-1078819997': 3, - 'U86K31ND6_1351642653': 1, + U86K31ND6_1351642653: 1, '10011725_88764544': 2, '10297667_88764544': 2, '10011723_88764544': 2, @@ -301,41 +301,41 @@ const legacyKeys: {[s: string]: number} = { 'ICZB-B1FC60/B3FC64/B2FC95/B2FC125_88764544': 2, 'ICZB-R11D_-1674843135': 1, 'ICZB-R12D_-1674843135': 1, - 'LED1545G12_88764544': 2, - 'T2011_88764544': 2, - 'LED1546G12_88764544': 2, + LED1545G12_88764544: 2, + T2011_88764544: 2, + LED1546G12_88764544: 2, 'LED1537R6/LED1739R5_88764544': 2, - 'LED1536G5_88764544': 2, + LED1536G5_88764544: 2, 'LED1903C5/LED1835C6_88764544': 2, - 'LED1733G7_88764544': 2, - 'LED1624G9_1676534437': 2, - 'LED1924G9_1676534437': 2, - 'LED1732G11_88764544': 2, - 'LED1736G9_88764544': 2, - 'T1820_88764544': 2, + LED1733G7_88764544: 2, + LED1624G9_1676534437: 2, + LED1924G9_1676534437: 2, + LED1732G11_88764544: 2, + LED1736G9_88764544: 2, + T1820_88764544: 2, 'ICTC-G-1_958235471': 1, - 'L1527_88764544': 2, - 'L1529_88764544': 2, - 'L1530_88764544': 2, - 'L1528_88764544': 2, - 'L1531_88764544': 2, + L1527_88764544: 2, + L1529_88764544: 2, + L1530_88764544: 2, + L1528_88764544: 2, + L1531_88764544: 2, 'E1603/E1702_1466202208': 1, 'E1524/E1810_996882109': 1, - 'W2049_369126006': 1, + W2049_369126006: 1, 'E1743_-1158733963': 1, 'E1841_-1158733963': 1, - 'E1842_1466202208': 1, - 'E1812_1426909482': 1, - 'E1744_958235471': 1, + E1842_1466202208: 1, + E1812_1426909482: 1, + E1744_958235471: 1, 'E1525/E1745_-98329414': 1, - 'E1746_39848625': 2, + E1746_39848625: 2, 'E1757_-881591257': 2, 'E1926_-881591257': 2, 'E1766_-1158733963': 1, - 'T1828_88764544': 2, - 'T1829_88764544': 2, - 'LED1738G7_88764544': 2, - 'LED1923R5_88764544': 2, + T1828_88764544: 2, + T1829_88764544: 2, + LED1738G7_88764544: 2, + LED1923R5_88764544: 2, '511.201_-1674843135': 1, '5120.1100_-1674843135': 1, '511.202_-1755039265': 1, @@ -377,7 +377,7 @@ const legacyKeys: {[s: string]: number} = { 'OFL 140 C_88764544': 2, 'OSL 130 C_88764544': 2, '57008000_-1277534575': 1, - 'IL06_1_574458647': 1, + IL06_1_574458647: 1, '3210-L_1034369459': 5, '3326-L_574458647': 2, '3320-L_574458647': 1, @@ -385,7 +385,7 @@ const legacyKeys: {[s: string]: number} = { '3460-L_-374819602': 1, 'iL07_1_-1045491241': 1, '27087-03_323577616': 1, - 'K2RGBW01_88764544': 2, + K2RGBW01_88764544: 2, 'SV01_-94213072': 1, 'SV02_-94213072': 1, 'ZCC-3500_1466202208': 1, @@ -399,17 +399,17 @@ const legacyKeys: {[s: string]: number} = { '99100-006_1226555785': 4, 'HK-LN-DIM-A_-1674843135': 2, '4058075181472_88764544': 2, - 'GPDRPLOP401100CE_88764544': 2, - 'AC25697_88764544': 2, - 'AC08560_88764544': 2, + GPDRPLOP401100CE_88764544: 2, + AC25697_88764544: 2, + AC08560_88764544: 2, '4058075208414_88764544': 2, '4058075208339_88764544': 2, '4058075485174_88764544': 2, '4058075173989_88764544': 2, '4058075208353_88764544': 2, - 'ZM350STW1TCF_88764544': 2, + ZM350STW1TCF_88764544: 2, 'A806S-Q1G_88764544': 2, - 'ZA806SQ1TCF_88764544': 2, + ZA806SQ1TCF_88764544: 2, '6ARCZABZH_491743244': 1, '6xy-M350ST-W1Z_88764544': 2, 'FC80CC_-1451534777': 1, @@ -429,17 +429,17 @@ const legacyKeys: {[s: string]: number} = { 'DG6HD-1BW_1466202208': 1, 'RC-2000WH_2080074453': 1, 'HG06337_-771124779': 1, - 'HG06668_491743244': 1, - 'HG06335_172811876': 1, + HG06668_491743244: 1, + HG06335_172811876: 1, 'HG06336_-994008938': 1, - 'HG06338_1421721297': 1, - 'HG06104A_1842547789': 2, - 'HG06106B_1842547789': 2, - 'HG06106A_1842547789': 2, - 'HG06106C_1842547789': 2, - 'HG06492A_1811148075': 1, - 'HG06492B_1811148075': 1, - 'HG06492C_1811148075': 1, + HG06338_1421721297: 1, + HG06104A_1842547789: 2, + HG06106B_1842547789: 2, + HG06106A_1842547789: 2, + HG06106C_1842547789: 2, + HG06492A_1811148075: 1, + HG06492B_1811148075: 1, + HG06492C_1811148075: 1, '14147206L_1811148075': 1, '14148906L_1842547789': 2, '14149505L/14149506L_1842547789': 2, @@ -449,10 +449,10 @@ const legacyKeys: {[s: string]: number} = { '200106V3_1466202208': 1, 'ZL1000100-CCT-US-V1A02_88764544': 2, 'ZL1000400-CCT-EU-2-V1A02_88764544': 2, - 'ZL100050004_88764544': 2, - 'ZS110050078_491743244': 1, - 'ZS232000178_491743244': 1, - 'ZS190000118_1466202208': 1, + ZL100050004_88764544: 2, + ZS110050078_491743244: 1, + ZS232000178_491743244: 1, + ZS190000118_1466202208: 1, 'TI0001_-1584537915': 1, 'TI0001-switch_-1584537915': 1, 'TI0001-switch-2gang_-1584537915': 1, @@ -470,8 +470,8 @@ const legacyKeys: {[s: string]: number} = { '12126_-353372667': 1, '12127_-2057794164': 2, 'Z3-1BRL_-1603959706': 1, - 'MEAZON_BIZY_PLUG_399769828': 2, - 'MEAZON_DINRAIL_1014780245': 2, + MEAZON_BIZY_PLUG_399769828: 2, + MEAZON_DINRAIL_1014780245: 2, 'MS-104Z_240216015': 1, 'MS-104BZ_1620506091': 1, 'ZK-EU-2U_-1884513748': 1, @@ -499,7 +499,7 @@ const legacyKeys: {[s: string]: number} = { 'Z809A_-98119581': 2, '170-33505_-1950902545': 5, '552-80699_1018624792': 1, - 'Z809AF_175167552': 3, + Z809AF_175167552: 3, '98425031_-1674843135': 1, '98423051_-1755039265': 1, 'HGZB-01_585186286': 2, @@ -517,7 +517,7 @@ const legacyKeys: {[s: string]: number} = { 'NCZ-3011-HA_491743244': 1, 'GWRJN5169_-1794950721': 1, 'T18W3Z_-60678371': 2, - 'RL804CZB_88764544': 2, + RL804CZB_88764544: 2, 'RL804QZB_-597493282': 1, 'ST20_-1546832024': 2, 'ST21_-1546832024': 2, @@ -531,36 +531,36 @@ const legacyKeys: {[s: string]: number} = { '73699_1676534437': 2, '4058075816718_88764544': 2, '4058075816732_88764544': 2, - 'AA69697_88764544': 2, - 'AC25704_88764544': 2, - 'AC10787_88764544': 2, - 'AC25702_88764544': 2, - 'AC03645_88764544': 2, - 'AC03642_88764544': 2, - 'AC03647_88764544': 2, - 'AC16381_88764544': 2, - 'AA70155_88764544': 2, - 'AA68199_88764544': 2, + AA69697_88764544: 2, + AC25704_88764544: 2, + AC10787_88764544: 2, + AC25702_88764544: 2, + AC03645_88764544: 2, + AC03642_88764544: 2, + AC03647_88764544: 2, + AC16381_88764544: 2, + AA70155_88764544: 2, + AA68199_88764544: 2, '4058075148338_88764544': 2, - 'AB32840_88764544': 2, + AB32840_88764544: 2, '4058075816794_88764544': 2, - 'AB401130055_88764544': 2, + AB401130055_88764544: 2, 'AB3257001NJ_-440201058': 1, 'AC10691_-1780925762': 1, '4052899926110_88764544': 2, '4058075036185_88764544': 2, '4058075036147_88764544': 2, '4058075047853_88764544': 2, - 'AC0363900NJ_88764544': 2, - 'AB35996_88764544': 2, - 'AC08559_88764544': 2, - 'AC01353010G_574458647': 1, - 'AC03648_88764544': 2, + AC0363900NJ_88764544: 2, + AB35996_88764544: 2, + AC08559_88764544: 2, + AC01353010G_574458647: 1, + AC03648_88764544: 2, 'AC0251100NJ/AC0251700NJ_1910007521': 1, '4058075816459_836988949': 1, '595UGR22_88764544': 2, 'WSP404_-229912473': 1, - 'CB432_1885354737': 1, + CB432_1885354737: 1, 'PIR313-E_-980837332': 1, 'NLG-CCT light_88764544': 2, 'NLG-TW light_88764544': 2, @@ -733,19 +733,19 @@ const legacyKeys: {[s: string]: number} = { 'SPE600_-1186389220': 4, 'SP600_-1186389220': 4, 'SR600_-1864443560': 4, - 'SM308_1801730331': 1, + SM308_1801730331: 1, 'SM309_-1674843135': 1, 'BE468_-1996534945': 3, - 'WV704R0A0902_866257616': 1, + WV704R0A0902_866257616: 1, 'U202DST600ZB_-1633395186': 2, 'CCT5010-0001_-1069299941': 1, - 'U201DST600ZB_2115037955': 1, - 'U201SRY2KWZB_182076854': 1, + U201DST600ZB_2115037955: 1, + U201SRY2KWZB_182076854: 1, 'U202SRY2KWZB_-619579899': 1, 'MEG5113-0300/MEG5165-0000_-321561689': 1, '545D6514_-1882755890': 1, 'ZHS-15_-1105234664': 3, - 'HAL300_88764544': 2, + HAL300_88764544: 2, 'PP-WHT-US_135599241': 4, 'E12-N1E_88764544': 2, 'E1G-G8E_88764544': 2, @@ -768,10 +768,10 @@ const legacyKeys: {[s: string]: number} = { 'TH1300ZB_-161211044': 3, 'TH1400ZB_-1088069291': 1, 'TH1500ZB_-1088069291': 1, - 'SW2500ZB_1466202208': 1, - 'SP2600ZB_1466202208': 1, + SW2500ZB_1466202208: 1, + SP2600ZB_1466202208: 1, 'DM2500ZB_-1455162343': 1, - 'RM3250ZB_1677026278': 1, + RM3250ZB_1677026278: 1, 'S9ZGBRC01_-994008938': 1, '5p1vj8r_821693351': 1, 'HGZB-07A_88764544': 2, @@ -805,10 +805,10 @@ const legacyKeys: {[s: string]: number} = { 'SNZB-01_323577616': 1, 'SNZB-02_-1105870073': 3, 'SNZB-03_563431407': 1, - 'ST218_2110948859': 2, - 'STZB402_2110948859': 1, - 'SMT402_185351366': 2, - 'SMT402AD_185351366': 2, + ST218_2110948859: 2, + STZB402_2110948859: 1, + SMT402_185351366: 2, + SMT402AD_185351366: 2, 'ZG192910-4_88764544': 2, 'ZG9101SAC-HP_-1674843135': 1, 'ZG9101SAC-HP-Switch_-1755039265': 1, @@ -827,7 +827,7 @@ const legacyKeys: {[s: string]: number} = { '72922-A_1466202208': 1, '71831_88764544': 2, '74282_88764544': 2, - 'LTFY004_1676534437': 2, + LTFY004_1676534437: 2, '72569_88764544': 2, '72567_88764544': 2, '75541_88764544': 2, @@ -841,45 +841,45 @@ const legacyKeys: {[s: string]: number} = { 'ZPIR-8000_491743244': 1, 'ZCTS-808_491743244': 1, 'WHD02_-1851317188': 1, - 'TS0505B_88764544': 2, - 'TS0503B_1676534437': 2, - 'TS0504B_1676534437': 2, - 'TS0108_241577581': 1, + TS0505B_88764544: 2, + TS0503B_1676534437: 2, + TS0504B_1676534437: 2, + TS0108_241577581: 1, 'TS0601_dimmer_-1560235388': 1, - 'TS0601_switch_72637060': 1, - 'TS0601_switch_2_gang_1261674374': 1, + TS0601_switch_72637060: 1, + TS0601_switch_2_gang_1261674374: 1, 'TS0601_switch_3_gang_-1884513748': 1, - 'TS0215A_remote_1750255244': 1, - 'TS0503A_1676534437': 2, - 'TS0502A_88764544': 2, - 'TS0504A_88764544': 2, - 'TS0505A_88764544': 2, - 'TS0041_346438280': 1, - 'TS0042_491743244': 1, + TS0215A_remote_1750255244: 1, + TS0503A_1676534437: 2, + TS0502A_88764544: 2, + TS0504A_88764544: 2, + TS0505A_88764544: 2, + TS0041_346438280: 1, + TS0042_491743244: 1, 'TS0001_-1851317188': 1, 'TS0002_-1884513748': 3, 'TS0121_plug_-917712964': 1, 'TS0601_din_-353372667': 1, - 'TS0115_1886156532': 1, - 'TS0011_1681093717': 2, - 'TS0012_918350781': 2, + TS0115_1886156532: 1, + TS0011_1681093717: 2, + TS0012_918350781: 2, 'TS0013_-885858470': 2, - 'TS0014_891847970': 2, + TS0014_891847970: 2, 'gq8b1uv_-1560235388': 1, 'TS0004_-652790423': 1, 'TS0006_-1915097310': 1, 'U86KWF-ZPSJ_2080074453': 1, 'E220-KR4N0Z0-HA_-652790423': 1, 'TS0216_-994008938': 1, - 'S1_1057123855': 3, + S1_1057123855: 3, 'S1-R_-1045329648': 3, - 'S2_1147184145': 3, + S2_1147184145: 3, 'D1_-1045329648': 3, - 'J1_34944581': 1, + J1_34944581: 1, 'C4_-1860800622': 1, 'XHS2-UE_574458647': 1, 'SM-SO306EZ-10_2125574690': 1, - 'ZK03840_803369124': 3, + ZK03840_803369124: 3, '14592.0_1681128303': 3, 'MCT-340 E_574458647': 1, 'MCT-340 SMA_574458647': 1, @@ -888,10 +888,10 @@ const legacyKeys: {[s: string]: number} = { '9GED18000-009_1226555785': 4, '9GED21500-005_1226555785': 3, 'URC4450BC0-X-R_1764952399': 1, - 'XDD12LM_88764544': 2, - 'XDD13LM_88764544': 2, - 'JWSP001A_88764544': 2, - 'JWDL001A_88764544': 2, + XDD12LM_88764544: 2, + XDD13LM_88764544: 2, + JWSP001A_88764544: 2, + JWDL001A_88764544: 2, 'WXKG06LM_-1392309340': 1, 'WS-USC01_1466202208': 1, 'WS-USC02_1620506091': 1, @@ -905,28 +905,28 @@ const legacyKeys: {[s: string]: number} = { 'QBKG26LM_-581830709': 1, 'WSDCGQ11LM_-150999557': 1, 'WSDCGQ12LM_-1602939088': 1, - 'RTCGQ12LM_1043360472': 1, + RTCGQ12LM_1043360472: 1, 'RTCGQ13LM_-189603484': 1, 'SP-EUC01_877088476': 3, - 'ZNCLDJ12LM_185298305': 1, + ZNCLDJ12LM_185298305: 1, 'ZNMS12LM_-265702992': 1, 'ZNMS13LM_-265702992': 1, 'WXCJKG11LM_-934383155': 1, 'WXCJKG12LM_-304697434': 1, 'WXCJKG13LM_-304697434': 1, 'GZCGQ01LM_-944874515': 2, - 'ZNTGMK11LM_88764544': 2, + ZNTGMK11LM_88764544: 2, 'SSM-U01_-1008457051': 1, 'SSM-U02_1466202208': 1, - 'YRD426NRSC_923668394': 3, - 'YRD226HA2619_923668394': 2, - 'YRD256HA20BP_923668394': 2, + YRD426NRSC_923668394: 3, + YRD226HA2619_923668394: 2, + YRD256HA20BP_923668394: 2, 'YMF40/YDM4109+_923668394': 2, 'YRD210-HA-605_923668394': 1, 'YRL-220L_923668394': 1, 'YRD226/246 TSDB_923668394': 2, 'YRD220/YRD221_923668394': 1, - 'YRD246HA20BP_923668394': 1, + YRD246HA20BP_923668394: 1, 'YRD216-HA2-619_923668394': 3, 'YRL226L TS_923668394': 2, 'D10110_-881591257': 1, diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 557054698715b..2d95b7fb57518 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -51,8 +51,7 @@ export const acovaThermostatSystemModes: KeyValueNumberString = { 4: 'away_or_vacation', }; - -export const thermostatRunningMode: KeyValueNumberString= { +export const thermostatRunningMode: KeyValueNumberString = { 0: 'off', 3: 'cool', 4: 'heat', @@ -102,13 +101,13 @@ export const thermostatScheduleMode: KeyValueNumberString = { }; export const fanMode = { - 'off': 0, - 'low': 1, - 'medium': 2, - 'high': 3, - 'on': 4, - 'auto': 5, - 'smart': 6, + off: 0, + low: 1, + medium: 2, + high: 3, + on: 4, + auto: 5, + smart: 6, }; export const temperatureDisplayMode: KeyValueNumberString = { @@ -116,7 +115,7 @@ export const temperatureDisplayMode: KeyValueNumberString = { 1: 'fahrenheit', }; -export const danfossAdaptionRunStatus: KeyValueNumberString= { +export const danfossAdaptionRunStatus: KeyValueNumberString = { 0: 'none', 1: 'in_progress', 2: 'found', @@ -288,8 +287,8 @@ export const easyCodeTouchActions: KeyValueNumberString = { 0x0300: 'rfid_lock', 0x0301: 'rfid_unlock', - 0xFF0D: 'lock', - 0xFF0E: 'zigbee_unlock', + 0xff0d: 'lock', + 0xff0e: 'zigbee_unlock', }; export const wiserDimmerControlMode: KeyValueNumberString = { diff --git a/src/lib/ewelink.ts b/src/lib/ewelink.ts index f7f65c90dc32e..1073e57780794 100644 --- a/src/lib/ewelink.ts +++ b/src/lib/ewelink.ts @@ -1,6 +1,6 @@ -import {Expose, Fz, ModernExtend, KeyValueAny, Configure} from './types'; import {presets} from './exposes'; import {setupConfigureForBinding} from './modernExtend'; +import {Expose, Fz, ModernExtend, KeyValueAny, Configure} from './types'; export const ewelinkModernExtend = { ewelinkAction: (): ModernExtend => { @@ -11,7 +11,7 @@ export const ewelinkModernExtend = { cluster: 'genOnOff', type: ['commandOn', 'commandOff', 'commandToggle'], convert: (model, msg, publish, options, meta) => { - const lookup: KeyValueAny = {'commandToggle': 'single', 'commandOn': 'double', 'commandOff': 'long'}; + const lookup: KeyValueAny = {commandToggle: 'single', commandOn: 'double', commandOff: 'long'}; return {action: lookup[msg.type]}; }, }, diff --git a/src/lib/exposes.ts b/src/lib/exposes.ts index 50dc78ed6a655..9905346a67ebc 100644 --- a/src/lib/exposes.ts +++ b/src/lib/exposes.ts @@ -1,7 +1,5 @@ -/* eslint max-len: 0 */ -/* eslint camelcase: 0 */ - import assert from 'assert'; + import {Access, Range} from './types'; import {getLabelFromName} from './utils'; @@ -67,12 +65,12 @@ export class Base { validateCategory() { switch (this.category) { - case 'config': - assert(this.access & a.SET, 'Config expose must be settable'); - break; - case 'diagnostic': - assert(!(this.access & a.SET), 'Diagnostic expose must not be settable'); - break; + case 'config': + assert(this.access & a.SET, 'Config expose must be settable'); + break; + case 'diagnostic': + assert(!(this.access & a.SET), 'Diagnostic expose must not be settable'); + break; } } @@ -107,7 +105,7 @@ export class Switch extends Base { this.features = []; } - withState(property: string, toggle: string | boolean, description: string, access=a.ALL, value_on='ON', value_off='OFF') { + withState(property: string, toggle: string | boolean, description: string, access = a.ALL, value_on = 'ON', value_off = 'OFF') { const feature = new Binary('state', access, value_on, value_off).withProperty(property).withDescription(description); if (toggle) { feature.withValueToggle('TOGGLE'); @@ -125,23 +123,25 @@ export class Lock extends Base { this.features = []; } - withState(property: string, valueOn: string, valueOff: string, description: string, access=a.ALL) { + withState(property: string, valueOn: string, valueOff: string, description: string, access = a.ALL) { this.addFeature(new Binary('state', access, valueOn, valueOff).withProperty(property).withDescription(description)); return this; } withLockState(property: string, description: string) { - this.addFeature(new Enum('lock_state', access.STATE, ['not_fully_locked', 'locked', 'unlocked']).withProperty(property).withDescription(description)); + this.addFeature( + new Enum('lock_state', access.STATE, ['not_fully_locked', 'locked', 'unlocked']).withProperty(property).withDescription(description), + ); return this; } } export class Binary extends Base { - value_on: string|boolean; - value_off: string|boolean; + value_on: string | boolean; + value_off: string | boolean; value_toggle?: string; - constructor(name: string, access: number, valueOn: string|boolean, valueOff: string|boolean) { + constructor(name: string, access: number, valueOn: string | boolean, valueOff: string | boolean) { super(); this.type = 'binary'; this.name = name; @@ -190,7 +190,7 @@ export class Numeric extends Base { value_max?: number; value_min?: number; value_step?: number; - presets?:{name: string, value: number | string, description: string}[]; + presets?: {name: string; value: number | string; description: string}[]; constructor(name: string, access: number) { super(); @@ -229,9 +229,9 @@ export class Numeric extends Base { } export class Enum extends Base { - values: (string|number)[]; + values: (string | number)[]; - constructor(name: string, access: number, values: (string|number)[]) { + constructor(name: string, access: number, values: (string | number)[]) { super(); this.type = 'enum'; this.name = name; @@ -296,38 +296,59 @@ export class Light extends Base { withLevelConfig(disableFeatures: string[] = []) { let levelConfig = new Composite('level_config', 'level_config', access.ALL); if (!disableFeatures.includes('on_off_transition_time')) { - levelConfig = levelConfig.withFeature(new Numeric('on_off_transition_time', access.ALL) - .withLabel('ON/OFF transition time') - .withDescription('Represents the time taken to move to or from the target level when On of Off commands are received by an On/Off cluster')); + levelConfig = levelConfig.withFeature( + new Numeric('on_off_transition_time', access.ALL) + .withLabel('ON/OFF transition time') + .withDescription( + 'Represents the time taken to move to or from the target level when On of Off commands are received by an On/Off cluster', + ), + ); } if (!disableFeatures.includes('on_transition_time')) { - levelConfig = levelConfig.withFeature(new Numeric('on_transition_time', access.ALL) - .withLabel('ON transition time') - .withPreset('disabled', 65535, 'Use on_off_transition_time value') - .withDescription('Represents the time taken to move the current level from the minimum level to the maximum level when an On command is received')); + levelConfig = levelConfig.withFeature( + new Numeric('on_transition_time', access.ALL) + .withLabel('ON transition time') + .withPreset('disabled', 65535, 'Use on_off_transition_time value') + .withDescription( + 'Represents the time taken to move the current level from the minimum level to the maximum level when an On command is received', + ), + ); } if (!disableFeatures.includes('off_transition_time')) { - levelConfig = levelConfig.withFeature(new Numeric('off_transition_time', access.ALL) - .withLabel('OFF transition time') - .withPreset('disabled', 65535, 'Use on_off_transition_time value') - .withDescription('Represents the time taken to move the current level from the maximum level to the minimum level when an Off command is received')); + levelConfig = levelConfig.withFeature( + new Numeric('off_transition_time', access.ALL) + .withLabel('OFF transition time') + .withPreset('disabled', 65535, 'Use on_off_transition_time value') + .withDescription( + 'Represents the time taken to move the current level from the maximum level to the minimum level when an Off command is received', + ), + ); } if (!disableFeatures.includes('execute_if_off')) { - levelConfig = levelConfig.withFeature(new Binary('execute_if_off', access.ALL, true, false) - .withDescription('this setting can affect the "on_level", "current_level_startup" or "brightness" setting')); + levelConfig = levelConfig.withFeature( + new Binary('execute_if_off', access.ALL, true, false).withDescription( + 'this setting can affect the "on_level", "current_level_startup" or "brightness" setting', + ), + ); } if (!disableFeatures.includes('on_level')) { - levelConfig = levelConfig.withFeature(new Numeric('on_level', access.ALL) - .withValueMin(1).withValueMax(254) - .withPreset('previous', 255, 'Use previous value') - .withDescription('Specifies the level that shall be applied, when an on/toggle command causes the light to turn on.')); + levelConfig = levelConfig.withFeature( + new Numeric('on_level', access.ALL) + .withValueMin(1) + .withValueMax(254) + .withPreset('previous', 255, 'Use previous value') + .withDescription('Specifies the level that shall be applied, when an on/toggle command causes the light to turn on.'), + ); } if (!disableFeatures.includes('current_level_startup')) { - levelConfig = levelConfig.withFeature(new Numeric('current_level_startup', access.ALL) - .withValueMin(1).withValueMax(254) - .withPreset('minimum', 0, 'Use minimum permitted value') - .withPreset('previous', 255, 'Use previous value') - .withDescription('Defines the desired startup level for a device when it is supplied with power')); + levelConfig = levelConfig.withFeature( + new Numeric('current_level_startup', access.ALL) + .withValueMin(1) + .withValueMax(254) + .withPreset('minimum', 0, 'Use minimum permitted value') + .withPreset('previous', 255, 'Use previous value') + .withDescription('Defines the desired startup level for a device when it is supplied with power'), + ); } levelConfig = levelConfig.withDescription('Configure genLevelCtrl'); this.addFeature(levelConfig); @@ -340,7 +361,10 @@ export class Light extends Base { range = [150, 500]; } - const feature = new Numeric('color_temp', access.ALL).withUnit('mired').withValueMin(range[0]).withValueMax(range[1]) + const feature = new Numeric('color_temp', access.ALL) + .withUnit('mired') + .withValueMin(range[0]) + .withValueMax(range[1]) .withDescription('Color temperature of this light'); if (process.env.ZHC_TEST) { @@ -354,7 +378,9 @@ export class Light extends Base { {name: 'neutral', value: 370, description: 'Neutral temperature (370 mireds / 2700 Kelvin)'}, {name: 'warm', value: 454, description: 'Warm temperature (454 mireds / 2200 Kelvin)'}, {name: 'warmest', value: range[1], description: 'Warmest temperature supported'}, - ].filter((p) => p.value >= range[0] && p.value <= range[1]).forEach((p) => feature.withPreset(p.name, p.value, p.description)); + ] + .filter((p) => p.value >= range[0] && p.value <= range[1]) + .forEach((p) => feature.withPreset(p.name, p.value, p.description)); this.addFeature(feature); return this; @@ -365,7 +391,10 @@ export class Light extends Base { range = [150, 500]; } - const feature = new Numeric('color_temp_startup', access.ALL).withUnit('mired').withValueMin(range[0]).withValueMax(range[1]) + const feature = new Numeric('color_temp_startup', access.ALL) + .withUnit('mired') + .withValueMin(range[0]) + .withValueMax(range[1]) .withDescription('Color temperature after cold power on of this light'); [ @@ -374,7 +403,9 @@ export class Light extends Base { {name: 'neutral', value: 370, description: 'Neutral temperature (370 mireds / 2700 Kelvin)'}, {name: 'warm', value: 454, description: 'Warm temperature (454 mireds / 2200 Kelvin)'}, {name: 'warmest', value: range[1], description: 'Warmest temperature supported'}, - ].filter((p) => p.value >= range[0] && p.value <= range[1]).forEach((p) => feature.withPreset(p.name, p.value, p.description)); + ] + .filter((p) => p.value >= range[0] && p.value <= range[1]) + .forEach((p) => feature.withPreset(p.name, p.value, p.description)); feature.withPreset('previous', 65535, 'Restore previous color_temp on cold power on'); this.addFeature(feature); @@ -415,7 +446,9 @@ export class Cover extends Base { } withPosition() { - this.addFeature(new Numeric('position', access.ALL).withValueMin(0).withValueMax(100).withDescription('Position of this cover').withUnit('%')); + this.addFeature( + new Numeric('position', access.ALL).withValueMin(0).withValueMax(100).withDescription('Position of this cover').withUnit('%'), + ); return this; } @@ -433,7 +466,7 @@ export class Fan extends Base { this.addFeature(new Binary('state', access.ALL, 'ON', 'OFF').withDescription('On/off state of this fan').withProperty('fan_state')); } - withModes(modes: string[], access=a.ALL) { + withModes(modes: string[], access = a.ALL) { this.addFeature(new Enum('mode', access, modes).withProperty('fan_mode').withDescription('Mode of this fan')); return this; } @@ -446,110 +479,146 @@ export class Climate extends Base { this.features = []; } - withSetpoint(property: string, min: number, max: number, step: number, access=a.ALL) { - assert(['occupied_heating_setpoint', 'current_heating_setpoint', 'occupied_cooling_setpoint', 'unoccupied_heating_setpoint', 'unoccupied_cooling_setpoint'].includes(property)); - this.addFeature(new Numeric(property, access) - .withValueMin(min).withValueMax(max).withValueStep(step).withUnit('°C').withDescription('Temperature setpoint')); + withSetpoint(property: string, min: number, max: number, step: number, access = a.ALL) { + assert( + [ + 'occupied_heating_setpoint', + 'current_heating_setpoint', + 'occupied_cooling_setpoint', + 'unoccupied_heating_setpoint', + 'unoccupied_cooling_setpoint', + ].includes(property), + ); + this.addFeature( + new Numeric(property, access) + .withValueMin(min) + .withValueMax(max) + .withValueStep(step) + .withUnit('°C') + .withDescription('Temperature setpoint'), + ); return this; } - withLocalTemperature(access=a.STATE_GET, description='Current temperature measured on the device') { + withLocalTemperature(access = a.STATE_GET, description = 'Current temperature measured on the device') { this.addFeature(new Numeric('local_temperature', access).withUnit('°C').withDescription(description)); return this; } - withLocalTemperatureCalibration(min=-12.8, max=12.7, step=0.1, access=a.ALL) { + withLocalTemperatureCalibration(min = -12.8, max = 12.7, step = 0.1, access = a.ALL) { // For devices following the ZCL local_temperature_calibration is an int8, so min = -12.8 and max 12.7 - this.addFeature(new Numeric('local_temperature_calibration', access) - .withValueMin(min).withValueMax(max).withValueStep(step).withUnit('°C').withDescription('Offset to add/subtract to the local temperature')); + this.addFeature( + new Numeric('local_temperature_calibration', access) + .withValueMin(min) + .withValueMax(max) + .withValueStep(step) + .withUnit('°C') + .withDescription('Offset to add/subtract to the local temperature'), + ); return this; } - withSystemMode(modes: string[], access=a.ALL, description='Mode of this device') { + withSystemMode(modes: string[], access = a.ALL, description = 'Mode of this device') { const allowed = ['off', 'heat', 'cool', 'auto', 'dry', 'fan_only', 'sleep', 'emergency_heating']; modes.forEach((m) => assert(allowed.includes(m))); this.addFeature(new Enum('system_mode', access, modes).withDescription(description)); return this; } - withRunningState(modes: string[], access=a.STATE_GET) { + withRunningState(modes: string[], access = a.STATE_GET) { const allowed = ['idle', 'heat', 'cool', 'fan_only']; modes.forEach((m) => assert(allowed.includes(m))); this.addFeature(new Enum('running_state', access, modes).withDescription('The current running state')); return this; } - withRunningMode(modes: string[], access=a.STATE_GET) { + withRunningMode(modes: string[], access = a.STATE_GET) { const allowed = ['off', 'cool', 'heat']; modes.forEach((m) => assert(allowed.includes(m))); this.addFeature(new Enum('running_mode', access, modes).withDescription('The current running mode')); return this; } - withFanMode(modes: string[], access=a.ALL) { + withFanMode(modes: string[], access = a.ALL) { const allowed = ['off', 'low', 'medium', 'high', 'on', 'auto', 'smart']; modes.forEach((m) => assert(allowed.includes(m))); this.addFeature(new Enum('fan_mode', access, modes).withDescription('Mode of the fan')); return this; } - withSwingMode(modes: string[], access=a.ALL) { + withSwingMode(modes: string[], access = a.ALL) { this.addFeature(new Enum('swing_mode', access, modes).withDescription('Swing mode')); return this; } - withPreset(modes: string[], description='Mode of this device (similar to system_mode)') { + withPreset(modes: string[], description = 'Mode of this device (similar to system_mode)') { this.addFeature(new Enum('preset', access.STATE_SET, modes).withDescription(description)); return this; } - withPiHeatingDemand(access=a.STATE) { - this.addFeature(new Numeric('pi_heating_demand', access).withLabel('PI heating demand').withValueMin(0).withValueMax(100).withUnit('%').withDescription('Position of the valve (= demanded heat) where 0% is fully closed and 100% is fully open')); + withPiHeatingDemand(access = a.STATE) { + this.addFeature( + new Numeric('pi_heating_demand', access) + .withLabel('PI heating demand') + .withValueMin(0) + .withValueMax(100) + .withUnit('%') + .withDescription('Position of the valve (= demanded heat) where 0% is fully closed and 100% is fully open'), + ); return this; } - withControlSequenceOfOperation(modes: string[], access=a.STATE) { - const allowed = ['cooling_only', 'cooling_with_reheat', 'heating_only', 'heating_with_reheat', 'cooling_and_heating_4-pipes', 'cooling_and_heating_4-pipes_with_reheat']; + withControlSequenceOfOperation(modes: string[], access = a.STATE) { + const allowed = [ + 'cooling_only', + 'cooling_with_reheat', + 'heating_only', + 'heating_with_reheat', + 'cooling_and_heating_4-pipes', + 'cooling_and_heating_4-pipes_with_reheat', + ]; modes.forEach((m) => assert(allowed.includes(m))); this.addFeature(new Enum('control_sequence_of_operation', access, modes).withDescription('Operating environment of the thermostat')); return this; } - withAcLouverPosition(positions: string[], access=a.ALL) { + withAcLouverPosition(positions: string[], access = a.ALL) { const allowed = ['fully_open', 'fully_closed', 'half_open', 'quarter_open', 'three_quarters_open']; positions.forEach((m) => assert(allowed.includes(m))); - this.addFeature(new Enum('ac_louver_position', access, positions).withLabel('AC louver position').withDescription('AC louver position of this device')); + this.addFeature( + new Enum('ac_louver_position', access, positions).withLabel('AC louver position').withDescription('AC louver position of this device'), + ); return this; } - withWeeklySchedule(modes: string[], access=a.ALL) { + withWeeklySchedule(modes: string[], access = a.ALL) { const allowed = ['heat', 'cool']; modes.forEach((m) => assert(allowed.includes(m))); - const featureDayOfWeek = new List('dayofweek', a.SET, new Composite('day', 'dayofweek', a.SET).withFeature(new Enum('day', a.SET, [ - 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', - 'saturday', 'sunday', 'away_or_vacation', - ]))).withLabel('Day of week').withLengthMin(1).withLengthMax(8).withDescription('Days on which the schedule will be active.'); + const featureDayOfWeek = new List( + 'dayofweek', + a.SET, + new Composite('day', 'dayofweek', a.SET).withFeature( + new Enum('day', a.SET, ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday', 'away_or_vacation']), + ), + ) + .withLabel('Day of week') + .withLengthMin(1) + .withLengthMax(8) + .withDescription('Days on which the schedule will be active.'); const featureTransitionTime = new Composite('time', 'transitionTime', a.SET) .withFeature(new Numeric('hour', a.SET)) .withFeature(new Numeric('minute', a.SET)) .withDescription('Trigger transition X minutes after 00:00.'); - const featureTransitionHeatSetPoint = new Numeric('heatSetpoint', a.SET) - .withLabel('Heat setpoint') - .withDescription('Target heat setpoint'); - const featureTransitionCoolSetPoint = new Numeric('coolSetpoint', a.SET) - .withLabel('Cool setpoint') - .withDescription('Target cool setpoint'); + const featureTransitionHeatSetPoint = new Numeric('heatSetpoint', a.SET).withLabel('Heat setpoint').withDescription('Target heat setpoint'); + const featureTransitionCoolSetPoint = new Numeric('coolSetpoint', a.SET).withLabel('Cool setpoint').withDescription('Target cool setpoint'); let featureTransition = new Composite('transition', 'transition', a.SET).withFeature(featureTransitionTime); if (modes.includes('heat')) featureTransition = featureTransition.withFeature(featureTransitionHeatSetPoint); if (modes.includes('cool')) featureTransition = featureTransition.withFeature(featureTransitionCoolSetPoint); - const featureTransitions = new List('transitions', a.SET, featureTransition) - .withLengthMin(1).withLengthMax(10); + const featureTransitions = new List('transitions', a.SET, featureTransition).withLengthMin(1).withLengthMax(10); - const schedule = new Composite('schedule', 'weekly_schedule', access) - .withFeature(featureDayOfWeek) - .withFeature(featureTransitions); + const schedule = new Composite('schedule', 'weekly_schedule', access).withFeature(featureDayOfWeek).withFeature(featureTransitions); this.addFeature(schedule); return this; @@ -559,12 +628,12 @@ export class Climate extends Base { * The access property is a 3-bit bitmask. */ export const access: { - STATE: Access, - SET: Access, - GET: Access, - STATE_SET: Access, - STATE_GET: Access, - ALL: Access, + STATE: Access; + SET: Access; + GET: Access; + STATE_SET: Access; + STATE_GET: Access; + ALL: Access; } = { /** * Bit 0: The property can be found in the published state of this device @@ -595,30 +664,107 @@ export const access: { const a = access; export const options = { - calibration: (name: string, type='absolute') => new Numeric(`${name}_calibration`, access.SET).withDescription(`Calibrates the ${name} value (${type} offset), takes into effect on next report of device.`), - precision: (name: string) => new Numeric(`${name}_precision`, access.SET).withValueMin(0).withValueMax(3).withDescription(`Number of digits after decimal point for ${name}, takes into effect on next report of device. This option can only decrease the precision, not increase it.`), - invert_cover: () => new Binary(`invert_cover`, access.SET, true, false).withDescription(`Inverts the cover position, false: open=100,close=0, true: open=0,close=100 (default false).`), - color_sync: () => new Binary(`color_sync`, access.SET, true, false).withDescription(`When enabled colors will be synced, e.g. if the light supports both color x/y and color temperature a conversion from color x/y to color temperature will be done when setting the x/y color (default true).`), - thermostat_unit: () => new Enum('thermostat_unit', access.SET, ['celsius', 'fahrenheit']).withDescription('Controls the temperature unit of the thermostat (default celsius).'), - expose_pin: () => new Binary(`expose_pin`, access.SET, true, false).withLabel('Expose PIN').withDescription(`Expose pin of this lock in the published payload (default false).`), - occupancy_timeout: () => new Numeric(`occupancy_timeout`, access.SET).withValueMin(0).withDescription('Time in seconds after which occupancy is cleared after detecting it (default 90 seconds).'), - occupancy_timeout_2: () => new Numeric(`occupancy_timeout`, access.SET).withValueMin(0).withValueStep(0.1).withUnit('s').withDescription('Time in seconds after which occupancy is cleared after detecting it (default is "detection_interval" + 2 seconds). The value must be equal to or greater than "detection_interval", and it can also be a fraction.'), - vibration_timeout: () => new Numeric(`vibration_timeout`, access.SET).withValueMin(0).withDescription('Time in seconds after which vibration is cleared after detecting it (default 90 seconds).'), - simulated_brightness: (extraNote='') => new Composite('simulated_brightness', 'simulated_brightness', access.SET) - .withDescription(`Simulate a brightness value. If this device provides a brightness_move_up or brightness_move_down action it is possible to specify the update interval and delta. The action_brightness_delta indicates the delta for each interval. ${extraNote}`) - .withFeature(new Numeric('delta', access.SET).withValueMin(0).withDescription('Delta per interval, 20 by default')) - .withFeature(new Numeric('interval', access.SET).withValueMin(0).withUnit('ms').withDescription('Interval duration')), - no_occupancy_since_true: () => new List(`no_occupancy_since`, access.SET, new Numeric('time', access.STATE_SET)).withDescription('Sends a message the last time occupancy (occupancy: true) was detected. When setting this for example to [10, 60] a `{"no_occupancy_since": 10}` will be send after 10 seconds and a `{"no_occupancy_since": 60}` after 60 seconds.'), - no_occupancy_since_false: () => new List(`no_occupancy_since`, access.SET, new Numeric('time', access.STATE_SET)).withDescription('Sends a message after the last time no occupancy (occupancy: false) was detected. When setting this for example to [10, 60] a `{"no_occupancy_since": 10}` will be send after 10 seconds and a `{"no_occupancy_since": 60}` after 60 seconds.'), - presence_timeout: () => new Numeric(`presence_timeout`, access.SET).withValueMin(0).withDescription('Time in seconds after which presence is cleared after detecting it (default 100 seconds).'), - no_position_support: () => new Binary('no_position_support', access.SET, true, false).withDescription('Set to true when your device only reports position 0, 100 and 50 (in this case your device has an older firmware) (default false).'), - transition: () => new Numeric(`transition`, access.SET).withValueMin(0).withDescription('Controls the transition time (in seconds) of on/off, brightness, color temperature (if applicable) and color (if applicable) changes. Defaults to `0` (no transition).'), - legacy: () => new Binary(`legacy`, access.SET, true, false).withDescription(`Set to false to disable the legacy integration (highly recommended), will change structure of the published payload (default true).`), - measurement_poll_interval: (extraNote='') => new Numeric(`measurement_poll_interval`, access.SET).withValueMin(-1).withDescription(`This device does not support reporting electric measurements so it is polled instead. The default poll interval is 60 seconds, set to -1 to disable.${extraNote}`), - illuminance_below_threshold_check: () => new Binary(`illuminance_below_threshold_check`, access.SET, true, false).withDescription(`Set to false to also send messages when illuminance is above threshold in night mode (default true).`), - state_action: () => new Binary(`state_action`, access.SET, true, false).withDescription(`State actions will also be published as 'action' when true (default false).`), - identify_timeout: () => new Numeric('identify_timeout', access.SET).withDescription('Sets duration of identification procedure in seconds (i.e., how long device would flash). Value ranges from 1 to 30 seconds (default 3).').withValueMin(1).withValueMax(30), - cover_position_tilt_disable_report: () => new Binary(`cover_position_tilt_disable_report`, access.SET, true, false).withDescription(`Do not publish set cover target position as a normal 'position' value (default false).`), + calibration: (name: string, type = 'absolute') => + new Numeric(`${name}_calibration`, access.SET).withDescription( + `Calibrates the ${name} value (${type} offset), takes into effect on next report of device.`, + ), + precision: (name: string) => + new Numeric(`${name}_precision`, access.SET) + .withValueMin(0) + .withValueMax(3) + .withDescription( + `Number of digits after decimal point for ${name}, takes into effect on next report of device. This option can only decrease the precision, not increase it.`, + ), + invert_cover: () => + new Binary(`invert_cover`, access.SET, true, false).withDescription( + `Inverts the cover position, false: open=100,close=0, true: open=0,close=100 (default false).`, + ), + color_sync: () => + new Binary(`color_sync`, access.SET, true, false).withDescription( + `When enabled colors will be synced, e.g. if the light supports both color x/y and color temperature a conversion from color x/y to color temperature will be done when setting the x/y color (default true).`, + ), + thermostat_unit: () => + new Enum('thermostat_unit', access.SET, ['celsius', 'fahrenheit']).withDescription( + 'Controls the temperature unit of the thermostat (default celsius).', + ), + expose_pin: () => + new Binary(`expose_pin`, access.SET, true, false) + .withLabel('Expose PIN') + .withDescription(`Expose pin of this lock in the published payload (default false).`), + occupancy_timeout: () => + new Numeric(`occupancy_timeout`, access.SET) + .withValueMin(0) + .withDescription('Time in seconds after which occupancy is cleared after detecting it (default 90 seconds).'), + occupancy_timeout_2: () => + new Numeric(`occupancy_timeout`, access.SET) + .withValueMin(0) + .withValueStep(0.1) + .withUnit('s') + .withDescription( + 'Time in seconds after which occupancy is cleared after detecting it (default is "detection_interval" + 2 seconds). The value must be equal to or greater than "detection_interval", and it can also be a fraction.', + ), + vibration_timeout: () => + new Numeric(`vibration_timeout`, access.SET) + .withValueMin(0) + .withDescription('Time in seconds after which vibration is cleared after detecting it (default 90 seconds).'), + simulated_brightness: (extraNote = '') => + new Composite('simulated_brightness', 'simulated_brightness', access.SET) + .withDescription( + `Simulate a brightness value. If this device provides a brightness_move_up or brightness_move_down action it is possible to specify the update interval and delta. The action_brightness_delta indicates the delta for each interval. ${extraNote}`, + ) + .withFeature(new Numeric('delta', access.SET).withValueMin(0).withDescription('Delta per interval, 20 by default')) + .withFeature(new Numeric('interval', access.SET).withValueMin(0).withUnit('ms').withDescription('Interval duration')), + no_occupancy_since_true: () => + new List(`no_occupancy_since`, access.SET, new Numeric('time', access.STATE_SET)).withDescription( + 'Sends a message the last time occupancy (occupancy: true) was detected. When setting this for example to [10, 60] a `{"no_occupancy_since": 10}` will be send after 10 seconds and a `{"no_occupancy_since": 60}` after 60 seconds.', + ), + no_occupancy_since_false: () => + new List(`no_occupancy_since`, access.SET, new Numeric('time', access.STATE_SET)).withDescription( + 'Sends a message after the last time no occupancy (occupancy: false) was detected. When setting this for example to [10, 60] a `{"no_occupancy_since": 10}` will be send after 10 seconds and a `{"no_occupancy_since": 60}` after 60 seconds.', + ), + presence_timeout: () => + new Numeric(`presence_timeout`, access.SET) + .withValueMin(0) + .withDescription('Time in seconds after which presence is cleared after detecting it (default 100 seconds).'), + no_position_support: () => + new Binary('no_position_support', access.SET, true, false).withDescription( + 'Set to true when your device only reports position 0, 100 and 50 (in this case your device has an older firmware) (default false).', + ), + transition: () => + new Numeric(`transition`, access.SET) + .withValueMin(0) + .withDescription( + 'Controls the transition time (in seconds) of on/off, brightness, color temperature (if applicable) and color (if applicable) changes. Defaults to `0` (no transition).', + ), + legacy: () => + new Binary(`legacy`, access.SET, true, false).withDescription( + `Set to false to disable the legacy integration (highly recommended), will change structure of the published payload (default true).`, + ), + measurement_poll_interval: (extraNote = '') => + new Numeric(`measurement_poll_interval`, access.SET) + .withValueMin(-1) + .withDescription( + `This device does not support reporting electric measurements so it is polled instead. The default poll interval is 60 seconds, set to -1 to disable.${extraNote}`, + ), + illuminance_below_threshold_check: () => + new Binary(`illuminance_below_threshold_check`, access.SET, true, false).withDescription( + `Set to false to also send messages when illuminance is above threshold in night mode (default true).`, + ), + state_action: () => + new Binary(`state_action`, access.SET, true, false).withDescription( + `State actions will also be published as 'action' when true (default false).`, + ), + identify_timeout: () => + new Numeric('identify_timeout', access.SET) + .withDescription( + 'Sets duration of identification procedure in seconds (i.e., how long device would flash). Value ranges from 1 to 30 seconds (default 3).', + ) + .withValueMin(1) + .withValueMax(30), + cover_position_tilt_disable_report: () => + new Binary(`cover_position_tilt_disable_report`, access.SET, true, false).withDescription( + `Do not publish set cover target position as a normal 'position' value (default false).`, + ), }; export const presets = { @@ -627,195 +773,469 @@ export const presets = { climate: () => new Climate(), composite: (name: string, property: string, access: number) => new Composite(name, property, access), cover: () => new Cover(), - enum: (name: string, access: number, values: (string|number)[]) => new Enum(name, access, values), + enum: (name: string, access: number, values: (string | number)[]) => new Enum(name, access, values), light: () => new Light(), numeric: (name: string, access: number) => new Numeric(name, access), text: (name: string, access: number) => new Text(name, access), list: (name: string, access: number, itemType: Feature) => new List(name, access, itemType), switch_: () => new Switch(), // Specific - ac_frequency: () => new Numeric('ac_frequency', access.STATE).withLabel('AC frequency').withUnit('Hz').withDescription('Measured electrical AC frequency'), - action: (values: string[]) => new Enum('action', access.STATE, values).withDescription('Triggered action (e.g. a button click)').withCategory('diagnostic'), - action_duration: () => new Numeric('action_duration', access.STATE).withUnit('s').withDescription('Triggered action duration in seconds').withCategory('diagnostic'), + ac_frequency: () => + new Numeric('ac_frequency', access.STATE).withLabel('AC frequency').withUnit('Hz').withDescription('Measured electrical AC frequency'), + action: (values: string[]) => + new Enum('action', access.STATE, values).withDescription('Triggered action (e.g. a button click)').withCategory('diagnostic'), + action_duration: () => + new Numeric('action_duration', access.STATE).withUnit('s').withDescription('Triggered action duration in seconds').withCategory('diagnostic'), action_group: () => new Numeric('action_group', access.STATE).withDescription('Group where the action was triggered on'), angle: (name: string) => new Numeric(name, access.STATE).withValueMin(-360).withValueMax(360).withUnit('°'), angle_axis: (name: string) => new Numeric(name, access.STATE).withValueMin(-90).withValueMax(90).withUnit('°'), aqi: () => new Numeric('aqi', access.STATE).withDescription('Air quality index'), - auto_lock: () => new Switch().withLabel('Auto lock').withState('auto_lock', false, 'Enable/disable auto lock', access.STATE_SET, 'AUTO', 'MANUAL'), - auto_off: (offTime: number) => new Binary('auto_off', access.ALL, true, false).withLabel('Auto OFF').withDescription(`Turn the device automatically off when attached device consumes less than 2W for ${offTime} minutes`).withCategory('config'), - auto_relock_time: () => new Numeric('auto_relock_time', access.ALL).withValueMin(0).withUnit('s').withDescription('The number of seconds to wait after unlocking a lock before it automatically locks again. 0=disabled'), + auto_lock: () => + new Switch().withLabel('Auto lock').withState('auto_lock', false, 'Enable/disable auto lock', access.STATE_SET, 'AUTO', 'MANUAL'), + auto_off: (offTime: number) => + new Binary('auto_off', access.ALL, true, false) + .withLabel('Auto OFF') + .withDescription(`Turn the device automatically off when attached device consumes less than 2W for ${offTime} minutes`) + .withCategory('config'), + auto_relock_time: () => + new Numeric('auto_relock_time', access.ALL) + .withValueMin(0) + .withUnit('s') + .withDescription('The number of seconds to wait after unlocking a lock before it automatically locks again. 0=disabled'), away_mode: () => new Switch().withLabel('Away mode').withState('away_mode', false, 'Enable/disable away mode', access.STATE_SET), away_preset_days: () => new Numeric('away_preset_days', access.STATE_SET).withDescription('Away preset days').withValueMin(0).withValueMax(100), - away_preset_temperature: () => new Numeric('away_preset_temperature', access.STATE_SET).withUnit('°C').withDescription('Away preset temperature').withValueMin(-10).withValueMax(35).withCategory('config'), - battery: () => new Numeric('battery', access.STATE).withUnit('%').withDescription('Remaining battery in %, can take up to 24 hours before reported').withValueMin(0).withValueMax(100).withCategory('diagnostic'), - battery_low: () => new Binary('battery_low', access.STATE, true, false).withDescription('Indicates if the battery of this device is almost empty').withCategory('diagnostic'), - battery_voltage: () => new Numeric('voltage', access.STATE).withUnit('mV').withDescription('Voltage of the battery in millivolts').withCategory('diagnostic'), + away_preset_temperature: () => + new Numeric('away_preset_temperature', access.STATE_SET) + .withUnit('°C') + .withDescription('Away preset temperature') + .withValueMin(-10) + .withValueMax(35) + .withCategory('config'), + battery: () => + new Numeric('battery', access.STATE) + .withUnit('%') + .withDescription('Remaining battery in %, can take up to 24 hours before reported') + .withValueMin(0) + .withValueMax(100) + .withCategory('diagnostic'), + battery_low: () => + new Binary('battery_low', access.STATE, true, false) + .withDescription('Indicates if the battery of this device is almost empty') + .withCategory('diagnostic'), + battery_voltage: () => + new Numeric('voltage', access.STATE).withUnit('mV').withDescription('Voltage of the battery in millivolts').withCategory('diagnostic'), boost_time: () => new Numeric('boost_time', access.STATE_SET).withUnit('s').withDescription('Boost time').withValueMin(0).withValueMax(900), - button_lock: () => new Binary('button_lock', access.ALL, 'ON', 'OFF').withDescription('Disables the physical switch button').withCategory('config'), - calibrated: () => new Binary('calibrated', access.STATE, true, false).withDescription('Indicates if this device is calibrated').withCategory('diagnostic'), + button_lock: () => + new Binary('button_lock', access.ALL, 'ON', 'OFF').withDescription('Disables the physical switch button').withCategory('config'), + calibrated: () => + new Binary('calibrated', access.STATE, true, false).withDescription('Indicates if this device is calibrated').withCategory('diagnostic'), carbon_monoxide: () => new Binary('carbon_monoxide', access.STATE, true, false).withDescription('Indicates if CO (carbon monoxide) is detected'), - child_lock: () => new Lock().withLabel('Child lock').withState('child_lock', 'LOCK', 'UNLOCK', 'Enables/disables physical input on the device', access.STATE_SET), - child_lock_bool: () => new Binary('child_lock', access.ALL, true, false).withDescription('Unlocks/locks physical input on the device').withCategory('config'), + child_lock: () => + new Lock() + .withLabel('Child lock') + .withState('child_lock', 'LOCK', 'UNLOCK', 'Enables/disables physical input on the device', access.STATE_SET), + child_lock_bool: () => + new Binary('child_lock', access.ALL, true, false).withDescription('Unlocks/locks physical input on the device').withCategory('config'), co2: () => new Numeric('co2', access.STATE).withLabel('CO2').withUnit('ppm').withDescription('The measured CO2 (carbon dioxide) value'), co: () => new Numeric('co', access.STATE).withLabel('CO').withUnit('ppm').withDescription('The measured CO (carbon monoxide) value'), - comfort_temperature: () => new Numeric('comfort_temperature', access.STATE_SET).withUnit('°C').withDescription('Comfort temperature').withValueMin(0).withValueMax(30), - consumer_connected: () => new Binary('consumer_connected', access.STATE, true, false).withDescription('Indicates whether a plug is physically attached. Device does not have to pull power or even be connected electrically (state of this binary switch can be ON even if main power switch is OFF)').withCategory('diagnostic'), + comfort_temperature: () => + new Numeric('comfort_temperature', access.STATE_SET).withUnit('°C').withDescription('Comfort temperature').withValueMin(0).withValueMax(30), + consumer_connected: () => + new Binary('consumer_connected', access.STATE, true, false) + .withDescription( + 'Indicates whether a plug is physically attached. Device does not have to pull power or even be connected electrically (state of this binary switch can be ON even if main power switch is OFF)', + ) + .withCategory('diagnostic'), contact: () => new Binary('contact', access.STATE, false, true).withDescription('Indicates if the contact is closed (= true) or open (= false)'), cover_position: () => new Cover().withPosition(), cover_position_tilt: () => new Cover().withPosition().withTilt(), cover_tilt: () => new Cover().withTilt(), - cover_mode: () => new Composite('cover_mode', 'cover_mode', access.ALL) - .withFeature(new Binary('reversed', access.ALL, true, false).withDescription('Reversal of the motor rotating direction')) - .withFeature(new Binary('calibration', access.ALL, true, false).withDescription('Set the cover calibration mode')) - .withFeature(new Binary('maintenance', access.ALL, true, false).withDescription('Set the cover maintenance mode, enabling will disable the cover motor')) - .withFeature(new Binary('led', access.ALL, true, false).withDescription('Set the LED')), - cpu_temperature: () => new Numeric('cpu_temperature', access.STATE).withLabel('CPU temperature').withUnit('°C').withDescription('Temperature of the CPU'), + cover_mode: () => + new Composite('cover_mode', 'cover_mode', access.ALL) + .withFeature(new Binary('reversed', access.ALL, true, false).withDescription('Reversal of the motor rotating direction')) + .withFeature(new Binary('calibration', access.ALL, true, false).withDescription('Set the cover calibration mode')) + .withFeature( + new Binary('maintenance', access.ALL, true, false).withDescription( + 'Set the cover maintenance mode, enabling will disable the cover motor', + ), + ) + .withFeature(new Binary('led', access.ALL, true, false).withDescription('Set the LED')), + cpu_temperature: () => + new Numeric('cpu_temperature', access.STATE).withLabel('CPU temperature').withUnit('°C').withDescription('Temperature of the CPU'), cube_side: (name: string) => new Numeric(name, access.STATE).withDescription('Side of the cube').withValueMin(0).withValueMax(6).withValueStep(1), - current: () => new Numeric('current', access.STATE).withUnit('A').withDescription('Instantaneous measured electrical current').withCategory('diagnostic'), - current_phase_b: () => new Numeric('current_phase_b', access.STATE).withLabel('Current phase B').withUnit('A').withDescription('Instantaneous measured electrical current on phase B'), - current_phase_c: () => new Numeric('current_phase_c', access.STATE).withLabel('Current phase C').withUnit('A').withDescription('Instantaneous measured electrical current on phase C'), - deadzone_temperature: () => new Numeric('deadzone_temperature', access.STATE_SET).withUnit('°C').withDescription('The delta between local_temperature and current_heating_setpoint to trigger Heat').withValueMin(0).withValueMax(5).withValueStep(1), - detection_interval: () => new Numeric('detection_interval', access.ALL).withValueMin(2).withValueMax(65535).withUnit('s').withDescription('Time interval between action detection.').withCategory('config'), - device_temperature: () => new Numeric('device_temperature', access.STATE).withUnit('°C').withDescription('Temperature of the device').withCategory('diagnostic'), + current: () => + new Numeric('current', access.STATE).withUnit('A').withDescription('Instantaneous measured electrical current').withCategory('diagnostic'), + current_phase_b: () => + new Numeric('current_phase_b', access.STATE) + .withLabel('Current phase B') + .withUnit('A') + .withDescription('Instantaneous measured electrical current on phase B'), + current_phase_c: () => + new Numeric('current_phase_c', access.STATE) + .withLabel('Current phase C') + .withUnit('A') + .withDescription('Instantaneous measured electrical current on phase C'), + deadzone_temperature: () => + new Numeric('deadzone_temperature', access.STATE_SET) + .withUnit('°C') + .withDescription('The delta between local_temperature and current_heating_setpoint to trigger Heat') + .withValueMin(0) + .withValueMax(5) + .withValueStep(1), + detection_interval: () => + new Numeric('detection_interval', access.ALL) + .withValueMin(2) + .withValueMax(65535) + .withUnit('s') + .withDescription('Time interval between action detection.') + .withCategory('config'), + device_temperature: () => + new Numeric('device_temperature', access.STATE).withUnit('°C').withDescription('Temperature of the device').withCategory('diagnostic'), eco2: () => new Numeric('eco2', access.STATE).withLabel('eCO2').withLabel('PPM').withUnit('ppm').withDescription('Measured eCO2 value'), eco_mode: () => new Binary('eco_mode', access.STATE_SET, 'ON', 'OFF').withDescription('ECO mode (energy saving mode)'), - eco_temperature: () => new Numeric('eco_temperature', access.STATE_SET).withUnit('°C').withDescription('Eco temperature').withValueMin(0).withValueMax(35), - effect: () => new Enum('effect', access.SET, ['blink', 'breathe', 'okay', 'channel_change', 'finish_effect', 'stop_effect']).withDescription('Triggers an effect on the light (e.g. make light blink for a few seconds)'), + eco_temperature: () => + new Numeric('eco_temperature', access.STATE_SET).withUnit('°C').withDescription('Eco temperature').withValueMin(0).withValueMax(35), + effect: () => + new Enum('effect', access.SET, ['blink', 'breathe', 'okay', 'channel_change', 'finish_effect', 'stop_effect']).withDescription( + 'Triggers an effect on the light (e.g. make light blink for a few seconds)', + ), energy: () => new Numeric('energy', access.STATE).withUnit('kWh').withDescription('Sum of consumed energy'), produced_energy: () => new Numeric('produced_energy', access.STATE).withUnit('kWh').withDescription('Sum of produced energy'), energy_produced: () => new Numeric('energy_produced', access.STATE).withUnit('kWh').withDescription('Sum of produced energy'), fan: () => new Fan(), - flip_indicator_light: () => new Binary('flip_indicator_light', access.ALL, 'ON', 'OFF').withDescription('After turn on, the indicator light turns on while switch is off, and vice versa').withCategory('config'), + flip_indicator_light: () => + new Binary('flip_indicator_light', access.ALL, 'ON', 'OFF') + .withDescription('After turn on, the indicator light turns on while switch is off, and vice versa') + .withCategory('config'), force: () => new Enum('force', access.STATE_SET, ['normal', 'open', 'close']).withDescription('Force the valve position'), formaldehyd: () => new Numeric('formaldehyd', access.STATE).withDescription('The measured formaldehyd value').withUnit('mg/m³'), gas: () => new Binary('gas', access.STATE, true, false).withDescription('Indicates whether the device detected gas'), hcho: () => new Numeric('hcho', access.STATE).withLabel('HCHO').withUnit('mg/m³').withDescription('Measured HCHO value'), - holiday_temperature: () => new Numeric('holiday_temperature', access.STATE_SET).withUnit('°C').withDescription('Holiday temperature').withValueMin(0).withValueMax(30), + holiday_temperature: () => + new Numeric('holiday_temperature', access.STATE_SET).withUnit('°C').withDescription('Holiday temperature').withValueMin(0).withValueMax(30), humidity: () => new Numeric('humidity', access.STATE).withUnit('%').withDescription('Measured relative humidity'), illuminance: () => new Numeric('illuminance', access.STATE).withDescription('Raw measured illuminance'), - illuminance_lux: () => new Numeric('illuminance_lux', access.STATE).withLabel('Illuminance (lux)').withUnit('lx').withDescription('Measured illuminance in lux'), + illuminance_lux: () => + new Numeric('illuminance_lux', access.STATE).withLabel('Illuminance (lux)').withUnit('lx').withDescription('Measured illuminance in lux'), brightness_state: () => new Enum('brightness_state', access.STATE, ['low', 'middle', 'high', 'strong']).withDescription('Brightness state'), - keypad_lockout: () => new Enum('keypad_lockout', access.ALL, ['unlock', 'lock1', 'lock2']).withDescription('Enables/disables physical input on the device'), - led_disabled_night: () => new Binary('led_disabled_night', access.ALL, true, false).withLabel('LED disabled night').withDescription('Enable/disable the LED at night').withCategory('config'), + keypad_lockout: () => + new Enum('keypad_lockout', access.ALL, ['unlock', 'lock1', 'lock2']).withDescription('Enables/disables physical input on the device'), + led_disabled_night: () => + new Binary('led_disabled_night', access.ALL, true, false) + .withLabel('LED disabled night') + .withDescription('Enable/disable the LED at night') + .withCategory('config'), light_brightness: () => new Light().withBrightness(), - light_brightness_color: (preferHueAndSaturation: boolean) => new Light().withBrightness().withColor((preferHueAndSaturation ? ['hs', 'xy'] : ['xy', 'hs'])), + light_brightness_color: (preferHueAndSaturation: boolean) => + new Light().withBrightness().withColor(preferHueAndSaturation ? ['hs', 'xy'] : ['xy', 'hs']), light_brightness_colorhs: () => new Light().withBrightness().withColor(['hs']), - light_brightness_colortemp: (colorTempRange: Range) => new Light().withBrightness().withColorTemp(colorTempRange).withColorTempStartup(colorTempRange), - light_brightness_colortemp_color: (colorTempRange?: Range, preferHueAndSaturation?: boolean) => new Light().withBrightness().withColorTemp(colorTempRange).withColorTempStartup(colorTempRange).withColor(preferHueAndSaturation ? ['hs', 'xy'] : ['xy', 'hs']), - light_brightness_colortemp_colorhs: (colorTempRange: Range) => new Light().withBrightness().withColorTemp(colorTempRange).withColorTempStartup(colorTempRange).withColor(['hs']), - light_brightness_colortemp_colorxy: (colorTempRange?: Range) => new Light().withBrightness().withColorTemp(colorTempRange).withColorTempStartup(colorTempRange).withColor(['xy']), - light_brightness_colorxy: () => new Light().withBrightness().withColor((['xy'])), + light_brightness_colortemp: (colorTempRange: Range) => + new Light().withBrightness().withColorTemp(colorTempRange).withColorTempStartup(colorTempRange), + light_brightness_colortemp_color: (colorTempRange?: Range, preferHueAndSaturation?: boolean) => + new Light() + .withBrightness() + .withColorTemp(colorTempRange) + .withColorTempStartup(colorTempRange) + .withColor(preferHueAndSaturation ? ['hs', 'xy'] : ['xy', 'hs']), + light_brightness_colortemp_colorhs: (colorTempRange: Range) => + new Light().withBrightness().withColorTemp(colorTempRange).withColorTempStartup(colorTempRange).withColor(['hs']), + light_brightness_colortemp_colorxy: (colorTempRange?: Range) => + new Light().withBrightness().withColorTemp(colorTempRange).withColorTempStartup(colorTempRange).withColor(['xy']), + light_brightness_colorxy: () => new Light().withBrightness().withColor(['xy']), light_colorhs: () => new Light().withColor(['hs']), - light_color_options: () => new Composite('color_options', 'color_options', access.ALL).withDescription('Advanced color behavior') - .withFeature(new Binary('execute_if_off', access.SET, true, false).withDescription('Controls whether color and color temperature can be set while light is off')) - .withCategory('config'), - linkquality: () => new Numeric('linkquality', access.STATE).withUnit('lqi').withDescription('Link quality (signal strength)').withValueMin(0).withValueMax(255).withCategory('diagnostic'), - local_temperature: () => new Numeric('local_temperature', access.STATE_GET).withUnit('°C').withDescription('Current temperature measured on the device'), + light_color_options: () => + new Composite('color_options', 'color_options', access.ALL) + .withDescription('Advanced color behavior') + .withFeature( + new Binary('execute_if_off', access.SET, true, false).withDescription( + 'Controls whether color and color temperature can be set while light is off', + ), + ) + .withCategory('config'), + linkquality: () => + new Numeric('linkquality', access.STATE) + .withUnit('lqi') + .withDescription('Link quality (signal strength)') + .withValueMin(0) + .withValueMax(255) + .withCategory('diagnostic'), + local_temperature: () => + new Numeric('local_temperature', access.STATE_GET).withUnit('°C').withDescription('Current temperature measured on the device'), lock: () => new Lock().withState('state', 'LOCK', 'UNLOCK', 'State of the lock').withLockState('lock_state', 'Actual state of the lock'), - lock_action: () => new Enum('action', access.STATE, ['unknown', 'lock', 'unlock', 'lock_failure_invalid_pin_or_id', 'lock_failure_invalid_schedule', 'unlock_failure_invalid_pin_or_id', 'unlock_failure_invalid_schedule', 'one_touch_lock', 'key_lock', 'key_unlock', 'auto_lock', 'schedule_lock', 'schedule_unlock', 'manual_lock', 'manual_unlock', 'non_access_user_operational_event']).withDescription('Triggered action on the lock'), - lock_action_source_name: () => new Enum('action_source_name', access.STATE, ['keypad', 'rfid', 'manual', 'rf']).withDescription('Source of the triggered action on the lock'), + lock_action: () => + new Enum('action', access.STATE, [ + 'unknown', + 'lock', + 'unlock', + 'lock_failure_invalid_pin_or_id', + 'lock_failure_invalid_schedule', + 'unlock_failure_invalid_pin_or_id', + 'unlock_failure_invalid_schedule', + 'one_touch_lock', + 'key_lock', + 'key_unlock', + 'auto_lock', + 'schedule_lock', + 'schedule_unlock', + 'manual_lock', + 'manual_unlock', + 'non_access_user_operational_event', + ]).withDescription('Triggered action on the lock'), + lock_action_source_name: () => + new Enum('action_source_name', access.STATE, ['keypad', 'rfid', 'manual', 'rf']).withDescription( + 'Source of the triggered action on the lock', + ), lock_action_user: () => new Numeric('action_user', access.STATE).withDescription('ID of user that triggered the action on the lock'), - max_cool_setpoint_limit: (min: number, max: number, step: number) => new Numeric('max_cool_setpoint_limit', access.ALL).withUnit('°C').withDescription('Maximum Cooling set point limit').withValueMin(min).withValueMax(max).withValueStep(step), - min_cool_setpoint_limit: (min: number, max: number, step: number) => new Numeric('min_cool_setpoint_limit', access.ALL).withUnit('°C').withDescription('Minimum Cooling point limit').withValueMin(min).withValueMax(max).withValueStep(step), - max_heat_setpoint_limit: (min: number, max: number, step: number) => new Numeric('max_heat_setpoint_limit', access.ALL).withUnit('°C').withDescription('Maximum Heating set point limit').withValueMin(min).withValueMax(max).withValueStep(step), - min_heat_setpoint_limit: (min: number, max: number, step: number) => new Numeric('min_heat_setpoint_limit', access.ALL).withUnit('°C').withDescription('Minimum Heating set point limit').withValueMin(min).withValueMax(max).withValueStep(step), - max_temperature: () => new Numeric('max_temperature', access.STATE_SET).withUnit('°C').withDescription('Maximum temperature').withValueMin(15).withValueMax(35), - max_temperature_limit: () => new Numeric('max_temperature_limit', access.STATE_SET).withUnit('°C').withDescription('Maximum temperature limit. Cuts the thermostat out regardless of air temperature if the external floor sensor exceeds this temperature. Only used by the thermostat when in AL sensor mode.').withValueMin(0).withValueMax(35), - min_temperature_limit: () => new Numeric('min_temperature_limit', access.STATE_SET).withUnit('°C').withDescription('Minimum temperature limit for frost protection. Turns the thermostat on regardless of setpoint if the temperature drops below this.').withValueMin(1).withValueMax(5), - min_temperature: () => new Numeric('min_temperature', access.STATE_SET).withUnit('°C').withDescription('Minimum temperature').withValueMin(1).withValueMax(15), - mode_switch_select: (mode_switch_names: string[]) => new Enum('mode_switch', access.ALL, mode_switch_names).withDescription('Select mode switch to use').withCategory('config'), - motion_sensitivity_select: (motion_sensitivity_names: string[]) => new Enum('motion_sensitivity', access.ALL, motion_sensitivity_names).withDescription('Select motion sensitivity to use').withCategory('config'), + max_cool_setpoint_limit: (min: number, max: number, step: number) => + new Numeric('max_cool_setpoint_limit', access.ALL) + .withUnit('°C') + .withDescription('Maximum Cooling set point limit') + .withValueMin(min) + .withValueMax(max) + .withValueStep(step), + min_cool_setpoint_limit: (min: number, max: number, step: number) => + new Numeric('min_cool_setpoint_limit', access.ALL) + .withUnit('°C') + .withDescription('Minimum Cooling point limit') + .withValueMin(min) + .withValueMax(max) + .withValueStep(step), + max_heat_setpoint_limit: (min: number, max: number, step: number) => + new Numeric('max_heat_setpoint_limit', access.ALL) + .withUnit('°C') + .withDescription('Maximum Heating set point limit') + .withValueMin(min) + .withValueMax(max) + .withValueStep(step), + min_heat_setpoint_limit: (min: number, max: number, step: number) => + new Numeric('min_heat_setpoint_limit', access.ALL) + .withUnit('°C') + .withDescription('Minimum Heating set point limit') + .withValueMin(min) + .withValueMax(max) + .withValueStep(step), + max_temperature: () => + new Numeric('max_temperature', access.STATE_SET).withUnit('°C').withDescription('Maximum temperature').withValueMin(15).withValueMax(35), + max_temperature_limit: () => + new Numeric('max_temperature_limit', access.STATE_SET) + .withUnit('°C') + .withDescription( + 'Maximum temperature limit. Cuts the thermostat out regardless of air temperature if the external floor sensor exceeds this temperature. Only used by the thermostat when in AL sensor mode.', + ) + .withValueMin(0) + .withValueMax(35), + min_temperature_limit: () => + new Numeric('min_temperature_limit', access.STATE_SET) + .withUnit('°C') + .withDescription( + 'Minimum temperature limit for frost protection. Turns the thermostat on regardless of setpoint if the temperature drops below this.', + ) + .withValueMin(1) + .withValueMax(5), + min_temperature: () => + new Numeric('min_temperature', access.STATE_SET).withUnit('°C').withDescription('Minimum temperature').withValueMin(1).withValueMax(15), + mode_switch_select: (mode_switch_names: string[]) => + new Enum('mode_switch', access.ALL, mode_switch_names).withDescription('Select mode switch to use').withCategory('config'), + motion_sensitivity_select: (motion_sensitivity_names: string[]) => + new Enum('motion_sensitivity', access.ALL, motion_sensitivity_names) + .withDescription('Select motion sensitivity to use') + .withCategory('config'), noise: () => new Numeric('noise', access.STATE).withUnit('dBA').withDescription('The measured noise value'), noise_detected: () => new Binary('noise_detected', access.STATE, true, false).withDescription('Indicates whether the device detected noise'), occupancy: () => new Binary('occupancy', access.STATE, true, false).withDescription('Indicates whether the device detected occupancy'), occupancy_level: () => new Numeric('occupancy_level', access.STATE).withDescription('The measured occupancy value'), open_window: () => new Binary('open_window', access.STATE_SET, 'ON', 'OFF').withDescription('Enables/disables the status on the device'), - open_window_temperature: () => new Numeric('open_window_temperature', access.STATE_SET).withUnit('°C').withDescription('Open window temperature').withValueMin(0).withValueMax(35), - operation_mode_select: (operation_mode_names: string[]) => new Enum('operation_mode', access.ALL, operation_mode_names).withDescription('Select operation mode to use').withCategory('config'), - overload_protection: (min: number, max: number) => new Numeric('overload_protection', access.ALL).withUnit('W').withValueMin(min).withValueMax(max).withDescription('Maximum allowed load, turns off if exceeded').withCategory('config'), - pm10: () => new Numeric('pm10', access.STATE).withLabel('PM10').withUnit('µg/m³').withDescription('Measured PM10 (particulate matter) concentration'), - pm25: () => new Numeric('pm25', access.STATE).withLabel('PM25').withUnit('µg/m³').withDescription('Measured PM2.5 (particulate matter) concentration'), + open_window_temperature: () => + new Numeric('open_window_temperature', access.STATE_SET) + .withUnit('°C') + .withDescription('Open window temperature') + .withValueMin(0) + .withValueMax(35), + operation_mode_select: (operation_mode_names: string[]) => + new Enum('operation_mode', access.ALL, operation_mode_names).withDescription('Select operation mode to use').withCategory('config'), + overload_protection: (min: number, max: number) => + new Numeric('overload_protection', access.ALL) + .withUnit('W') + .withValueMin(min) + .withValueMax(max) + .withDescription('Maximum allowed load, turns off if exceeded') + .withCategory('config'), + pm10: () => + new Numeric('pm10', access.STATE).withLabel('PM10').withUnit('µg/m³').withDescription('Measured PM10 (particulate matter) concentration'), + pm25: () => + new Numeric('pm25', access.STATE).withLabel('PM25').withUnit('µg/m³').withDescription('Measured PM2.5 (particulate matter) concentration'), position: () => new Numeric('position', access.STATE).withUnit('%').withDescription('Position'), power: () => new Numeric('power', access.STATE).withUnit('W').withDescription('Instantaneous measured power').withCategory('diagnostic'), - power_phase_b: () => new Numeric('power_phase_b', access.STATE).withUnit('W').withDescription('Instantaneous measured power on phase B').withCategory('diagnostic'), - power_phase_c: () => new Numeric('power_phase_c', access.STATE).withUnit('W').withDescription('Instantaneous measured power on phase C').withCategory('diagnostic'), + power_phase_b: () => + new Numeric('power_phase_b', access.STATE) + .withUnit('W') + .withDescription('Instantaneous measured power on phase B') + .withCategory('diagnostic'), + power_phase_c: () => + new Numeric('power_phase_c', access.STATE) + .withUnit('W') + .withDescription('Instantaneous measured power on phase C') + .withCategory('diagnostic'), power_factor: () => new Numeric('power_factor', access.STATE).withDescription('Instantaneous measured power factor'), power_factor_phase_b: () => new Numeric('power_factor_phase_b', access.STATE).withDescription('Instantaneous measured power factor on phase B'), power_factor_phase_c: () => new Numeric('power_factor_phase_c', access.STATE).withDescription('Instantaneous measured power factor on phase C'), power_apparent: () => new Numeric('power_apparent', access.STATE).withUnit('VA').withDescription('Instantaneous measured apparent power'), - power_apparent_phase_b: () => new Numeric('power_apparent_phase_b', access.STATE).withUnit('VA').withDescription('Instantaneous measured apparent power on phase B'), - power_apparent_phase_c: () => new Numeric('power_apparent_phase_c', access.STATE).withUnit('VA').withDescription('Instantaneous measured apparent power on phase C'), - power_on_behavior: (values=['off', 'previous', 'on']) => new Enum('power_on_behavior', access.ALL, values).withLabel('Power-on behavior').withDescription('Controls the behavior when the device is powered on after power loss. If you get an `UNSUPPORTED_ATTRIBUTE` error, the device does not support it.').withCategory('config'), - power_outage_count: (resetsWhenPairing = true) => new Numeric('power_outage_count', access.STATE).withDescription('Number of power outages' + (resetsWhenPairing ? ' (since last pairing)' : '')).withCategory('diagnostic'), - power_outage_memory: () => new Binary('power_outage_memory', access.ALL, true, false).withDescription('Enable/disable the power outage memory, this recovers the on/off mode after power failure').withCategory('config'), + power_apparent_phase_b: () => + new Numeric('power_apparent_phase_b', access.STATE).withUnit('VA').withDescription('Instantaneous measured apparent power on phase B'), + power_apparent_phase_c: () => + new Numeric('power_apparent_phase_c', access.STATE).withUnit('VA').withDescription('Instantaneous measured apparent power on phase C'), + power_on_behavior: (values = ['off', 'previous', 'on']) => + new Enum('power_on_behavior', access.ALL, values) + .withLabel('Power-on behavior') + .withDescription( + 'Controls the behavior when the device is powered on after power loss. If you get an `UNSUPPORTED_ATTRIBUTE` error, the device does not support it.', + ) + .withCategory('config'), + power_outage_count: (resetsWhenPairing = true) => + new Numeric('power_outage_count', access.STATE) + .withDescription('Number of power outages' + (resetsWhenPairing ? ' (since last pairing)' : '')) + .withCategory('diagnostic'), + power_outage_memory: () => + new Binary('power_outage_memory', access.ALL, true, false) + .withDescription('Enable/disable the power outage memory, this recovers the on/off mode after power failure') + .withCategory('config'), power_reactive: () => new Numeric('power_reactive', access.STATE).withUnit('VAR').withDescription('Instantaneous measured reactive power'), - power_reactive_phase_b: () => new Numeric('power_reactive_phase_b', access.STATE).withUnit('VAR').withDescription('Instantaneous measured reactive power on phase B'), - power_reactive_phase_c: () => new Numeric('power_reactive_phase_c', access.STATE).withUnit('VAR').withDescription('Instantaneous measured reactive power on phase C'), + power_reactive_phase_b: () => + new Numeric('power_reactive_phase_b', access.STATE).withUnit('VAR').withDescription('Instantaneous measured reactive power on phase B'), + power_reactive_phase_c: () => + new Numeric('power_reactive_phase_c', access.STATE).withUnit('VAR').withDescription('Instantaneous measured reactive power on phase C'), presence: () => new Binary('presence', access.STATE, true, false).withDescription('Indicates whether the device detected presence'), pressure: () => new Numeric('pressure', access.STATE).withUnit('hPa').withDescription('The measured atmospheric pressure'), - programming_operation_mode: (values=['setpoint', 'schedule', 'schedule_with_preheat', 'eco']) => new Enum('programming_operation_mode', access.ALL, ['setpoint', 'schedule', 'schedule_with_preheat', 'eco']).withDescription('Controls how programming affects the thermostat. Possible values: setpoint (only use specified setpoint), schedule (follow programmed setpoint schedule), schedule_with_preheat (follow programmed setpoint schedule with pre-heating). Changing this value does not clear programmed schedules.'), - setup: () => new Binary('setup', access.STATE, true, false).withDescription('Indicates if the device is in setup mode').withCategory('diagnostic'), - schedule: () => new Binary('schedule', access.ALL, true, false).withDescription('When enabled, the device will change its state based on your schedule settings').withCategory('config'), + programming_operation_mode: (values = ['setpoint', 'schedule', 'schedule_with_preheat', 'eco']) => + new Enum('programming_operation_mode', access.ALL, ['setpoint', 'schedule', 'schedule_with_preheat', 'eco']).withDescription( + 'Controls how programming affects the thermostat. Possible values: setpoint (only use specified setpoint), schedule (follow programmed setpoint schedule), schedule_with_preheat (follow programmed setpoint schedule with pre-heating). Changing this value does not clear programmed schedules.', + ), + setup: () => + new Binary('setup', access.STATE, true, false).withDescription('Indicates if the device is in setup mode').withCategory('diagnostic'), + schedule: () => + new Binary('schedule', access.ALL, true, false) + .withDescription('When enabled, the device will change its state based on your schedule settings') + .withCategory('config'), schedule_settings: () => new Text('schedule_settings', access.ALL).withDescription('Allows schedule configuration').withCategory('config'), - external_temperature_input: () => new Numeric('external_temperature_input', access.ALL) - .withUnit('°C') - .withValueMin(0) - .withValueMax(55) - .withDescription('Input for remote temperature sensor') - .withCategory('config'), + external_temperature_input: () => + new Numeric('external_temperature_input', access.ALL) + .withUnit('°C') + .withValueMin(0) + .withValueMax(55) + .withDescription('Input for remote temperature sensor') + .withCategory('config'), smoke: () => new Binary('smoke', access.STATE, true, false).withDescription('Indicates whether the device detected smoke'), soil_moisture: () => new Numeric('soil_moisture', access.STATE).withUnit('%').withDescription('Measured soil moisture value'), sos: () => new Binary('sos', access.STATE, true, false).withLabel('SOS').withDescription('SOS alarm'), - sound_volume: () => new Enum('sound_volume', access.ALL, ['silent_mode', 'low_volume', 'high_volume']).withDescription('Sound volume of the lock'), + sound_volume: () => + new Enum('sound_volume', access.ALL, ['silent_mode', 'low_volume', 'high_volume']).withDescription('Sound volume of the lock'), switch: () => new Switch().withState('state', true, 'On/off state of the switch'), switch_type: () => new Enum('switch_type', access.ALL, ['toggle', 'momentary']).withDescription('Wall switch type'), - door_state: () => new Enum('door_state', access.STATE, ['open', 'closed', 'error_jammed', 'error_forced_open', 'error_unspecified', 'undefined']).withDescription('State of the door'), + door_state: () => + new Enum('door_state', access.STATE, [ + 'open', + 'closed', + 'error_jammed', + 'error_forced_open', + 'error_unspecified', + 'undefined', + ]).withDescription('State of the door'), tamper: () => new Binary('tamper', access.STATE, true, false).withDescription('Indicates whether the device is tampered'), temperature: () => new Numeric('temperature', access.STATE).withUnit('°C').withDescription('Measured temperature value'), - temperature_sensor_select: (sensor_names: string[]) => new Enum('sensor', access.STATE_SET, sensor_names).withDescription('Select temperature sensor to use').withCategory('config'), + temperature_sensor_select: (sensor_names: string[]) => + new Enum('sensor', access.STATE_SET, sensor_names).withDescription('Select temperature sensor to use').withCategory('config'), test: () => new Binary('test', access.STATE, true, false).withDescription('Indicates whether the device is being tested'), - trigger_count: (sinceScheduledReport = true) => new Numeric('trigger_count', exports.access.STATE).withDescription('Indicates how many times the sensor was triggered' + (sinceScheduledReport ? ' (since last scheduled report)' : '')).withCategory('diagnostic'), - trigger_indicator: () => new Binary('trigger_indicator', access.ALL, true, false).withDescription('Enables trigger indication').withCategory('config'), + trigger_count: (sinceScheduledReport = true) => + new Numeric('trigger_count', exports.access.STATE) + .withDescription('Indicates how many times the sensor was triggered' + (sinceScheduledReport ? ' (since last scheduled report)' : '')) + .withCategory('diagnostic'), + trigger_indicator: () => + new Binary('trigger_indicator', access.ALL, true, false).withDescription('Enables trigger indication').withCategory('config'), valve_alarm: () => new Binary('valve_alarm', access.STATE, true, false).withCategory('diagnostic'), valve_position: () => new Numeric('position', access.ALL).withValueMin(0).withValueMax(100).withDescription('Position of the valve'), valve_switch: () => new Binary('state', access.ALL, 'OPEN', 'CLOSE').withDescription('Valve state if open or closed'), valve_state: () => new Binary('valve_state', access.STATE, 'OPEN', 'CLOSED').withDescription('Valve state if open or closed'), - valve_detection: () => new Switch().withLabel('Valve detection').withState('valve_detection', true, 'Valve detection').setAccess('state', access.STATE_SET), // left for compatability, do not use - valve_detection_bool: () => new Binary('valve_detection', access.ALL, true, false).withDescription('Determines if temperature control abnormalities should be detected').withCategory('config'), + valve_detection: () => + new Switch().withLabel('Valve detection').withState('valve_detection', true, 'Valve detection').setAccess('state', access.STATE_SET), // left for compatability, do not use + valve_detection_bool: () => + new Binary('valve_detection', access.ALL, true, false) + .withDescription('Determines if temperature control abnormalities should be detected') + .withCategory('config'), vibration: () => new Binary('vibration', access.STATE, true, false).withDescription('Indicates whether the device detected vibration'), voc: () => new Numeric('voc', access.STATE).withLabel('VOC').withUnit('µg/m³').withDescription('Measured VOC value'), voc_index: () => new Numeric('voc_index', access.STATE).withLabel('VOC index').withDescription('VOC index'), - voltage: () => new Numeric('voltage', access.STATE).withUnit('V').withDescription('Measured electrical potential value').withCategory('diagnostic'), - voltage_phase_b: () => new Numeric('voltage_phase_b', access.STATE).withLabel('Voltage phase B').withUnit('V').withDescription('Measured electrical potential value on phase B'), - voltage_phase_c: () => new Numeric('voltage_phase_c', access.STATE).withLabel('Voltage phase C').withUnit('V').withDescription('Measured electrical potential value on phase C'), + voltage: () => + new Numeric('voltage', access.STATE).withUnit('V').withDescription('Measured electrical potential value').withCategory('diagnostic'), + voltage_phase_b: () => + new Numeric('voltage_phase_b', access.STATE) + .withLabel('Voltage phase B') + .withUnit('V') + .withDescription('Measured electrical potential value on phase B'), + voltage_phase_c: () => + new Numeric('voltage_phase_c', access.STATE) + .withLabel('Voltage phase C') + .withUnit('V') + .withDescription('Measured electrical potential value on phase C'), water_leak: () => new Binary('water_leak', access.STATE, true, false).withDescription('Indicates whether the device detected a water leak'), - pilot_wire_mode: (values=['comfort', 'eco', 'frost_protection', 'off', 'comfort_-1', 'comfort_-2']) => new Enum('pilot_wire_mode', access.ALL, ['comfort', 'eco', 'frost_protection', 'off', 'comfort_-1', 'comfort_-2']).withDescription('Controls the target temperature of the heater, with respect to the temperature set on that heater. Possible values: comfort (target temperature = heater set temperature) eco (target temperature = heater set temperature - 3.5°C), frost_protection (target temperature = 7 to 8°C), off (heater stops heating), and the less commonly used comfort_-1 (target temperature = heater set temperature - 1°C), comfort_-2 (target temperature = heater set temperature - 2°C),.'), + pilot_wire_mode: (values = ['comfort', 'eco', 'frost_protection', 'off', 'comfort_-1', 'comfort_-2']) => + new Enum('pilot_wire_mode', access.ALL, ['comfort', 'eco', 'frost_protection', 'off', 'comfort_-1', 'comfort_-2']).withDescription( + 'Controls the target temperature of the heater, with respect to the temperature set on that heater. Possible values: comfort (target temperature = heater set temperature) eco (target temperature = heater set temperature - 3.5°C), frost_protection (target temperature = 7 to 8°C), off (heater stops heating), and the less commonly used comfort_-1 (target temperature = heater set temperature - 1°C), comfort_-2 (target temperature = heater set temperature - 2°C),.', + ), rain: () => new Binary('rain', access.STATE, true, false).withDescription('Indicates whether the device detected rainfall'), - warning: () => new Composite('warning', 'warning', access.SET) - .withFeature(new Enum('mode', access.SET, ['stop', 'burglar', 'fire', 'emergency', 'police_panic', 'fire_panic', 'emergency_panic']).withDescription('Mode of the warning (sound effect)')) - .withFeature(new Enum('level', access.SET, ['low', 'medium', 'high', 'very_high']).withDescription('Sound level')) - .withFeature(new Enum('strobe_level', access.SET, ['low', 'medium', 'high', 'very_high']).withDescription('Intensity of the strobe')) - .withFeature(new Binary('strobe', access.SET, true, false).withDescription('Turn on/off the strobe (light) during warning')) - .withFeature(new Numeric('strobe_duty_cycle', access.SET).withValueMax(10).withValueMin(0).withDescription('Length of the flash cycle')) - .withFeature(new Numeric('duration', access.SET).withUnit('s').withDescription('Duration in seconds of the alarm')), + warning: () => + new Composite('warning', 'warning', access.SET) + .withFeature( + new Enum('mode', access.SET, [ + 'stop', + 'burglar', + 'fire', + 'emergency', + 'police_panic', + 'fire_panic', + 'emergency_panic', + ]).withDescription('Mode of the warning (sound effect)'), + ) + .withFeature(new Enum('level', access.SET, ['low', 'medium', 'high', 'very_high']).withDescription('Sound level')) + .withFeature(new Enum('strobe_level', access.SET, ['low', 'medium', 'high', 'very_high']).withDescription('Intensity of the strobe')) + .withFeature(new Binary('strobe', access.SET, true, false).withDescription('Turn on/off the strobe (light) during warning')) + .withFeature(new Numeric('strobe_duty_cycle', access.SET).withValueMax(10).withValueMin(0).withDescription('Length of the flash cycle')) + .withFeature(new Numeric('duration', access.SET).withUnit('s').withDescription('Duration in seconds of the alarm')), week: () => new Enum('week', access.STATE_SET, ['5+2', '6+1', '7']).withDescription('Week format user for schedule'), - window_detection: () => new Switch().withLabel('Window detection').withState('window_detection', true, 'Enables/disables window detection on the device', access.STATE_SET), // left for compatability, do not use - window_detection_bool: () => new Binary('window_detection', access.ALL, true, false).withDescription('Enables/disables window detection on the device').withCategory('config'), + window_detection: () => + new Switch() + .withLabel('Window detection') + .withState('window_detection', true, 'Enables/disables window detection on the device', access.STATE_SET), // left for compatability, do not use + window_detection_bool: () => + new Binary('window_detection', access.ALL, true, false) + .withDescription('Enables/disables window detection on the device') + .withCategory('config'), window_open: () => new Binary('window_open', access.STATE, true, false).withDescription('Indicates if window is open').withCategory('diagnostic'), moving: () => new Binary('moving', access.STATE, true, false).withDescription('Indicates if the device is moving'), x_axis: () => new Numeric('x_axis', access.STATE).withDescription('Accelerometer X value'), y_axis: () => new Numeric('y_axis', access.STATE).withDescription('Accelerometer Y value'), z_axis: () => new Numeric('z_axis', access.STATE).withDescription('Accelerometer Z value'), - pincode: () => new Composite('pin_code', 'pin_code', access.ALL) - .withFeature(new Numeric('user', access.SET).withDescription('User ID to set or clear the pincode for')) - .withFeature(new Enum('user_type', access.SET, ['unrestricted', 'year_day_schedule', 'week_day_schedule', 'master', 'non_access']).withDescription('Type of user, unrestricted: owner (default), (year|week)_day_schedule: user has ability to open lock based on specific time period, master: user has ability to both program and operate the door lock, non_access: user is recognized by the lock but does not have the ability to open the lock')) - .withFeature(new Binary('user_enabled', access.SET, true, false).withDescription('Whether the user is enabled/disabled')) - .withFeature(new Numeric('pin_code', access.SET).withLabel('PIN code').withDescription('Pincode to set, set pincode to null to clear')), - squawk: () => new Composite('squawk', 'squawk', access.SET) - .withFeature(new Enum('state', access.SET, ['system_is_armed', 'system_is_disarmed']).withDescription('Set Squawk state')) - .withFeature(new Enum('level', access.SET, ['low', 'medium', 'high', 'very_high']).withDescription('Sound level')) - .withFeature(new Binary('strobe', access.SET, true, false).withDescription('Turn on/off the strobe (light) for Squawk')), - identify_duration: () => new Numeric('identify', access.SET).withValueMin(0).withValueMax(30).withUnit('seconds').withDescription('Duration of flashing').withCategory('config'), + pincode: () => + new Composite('pin_code', 'pin_code', access.ALL) + .withFeature(new Numeric('user', access.SET).withDescription('User ID to set or clear the pincode for')) + .withFeature( + new Enum('user_type', access.SET, ['unrestricted', 'year_day_schedule', 'week_day_schedule', 'master', 'non_access']).withDescription( + 'Type of user, unrestricted: owner (default), (year|week)_day_schedule: user has ability to open lock based on specific time period, master: user has ability to both program and operate the door lock, non_access: user is recognized by the lock but does not have the ability to open the lock', + ), + ) + .withFeature(new Binary('user_enabled', access.SET, true, false).withDescription('Whether the user is enabled/disabled')) + .withFeature(new Numeric('pin_code', access.SET).withLabel('PIN code').withDescription('Pincode to set, set pincode to null to clear')), + squawk: () => + new Composite('squawk', 'squawk', access.SET) + .withFeature(new Enum('state', access.SET, ['system_is_armed', 'system_is_disarmed']).withDescription('Set Squawk state')) + .withFeature(new Enum('level', access.SET, ['low', 'medium', 'high', 'very_high']).withDescription('Sound level')) + .withFeature(new Binary('strobe', access.SET, true, false).withDescription('Turn on/off the strobe (light) for Squawk')), + identify_duration: () => + new Numeric('identify', access.SET) + .withValueMin(0) + .withValueMax(30) + .withUnit('seconds') + .withDescription('Duration of flashing') + .withCategory('config'), identify: () => new Enum('identify', access.SET, ['identify']).withDescription('Ititiate device identification').withCategory('config'), min_brightness: () => new Numeric('min_brightness', access.ALL).withValueMin(1).withValueMax(255).withDescription('Minimum light brightness'), max_brightness: () => new Numeric('max_brightness', access.ALL).withValueMin(1).withValueMax(255).withDescription('Maximum light brightness'), diff --git a/src/lib/generateDefinition.ts b/src/lib/generateDefinition.ts index e1154e8661344..0ddae7d49446f 100644 --- a/src/lib/generateDefinition.ts +++ b/src/lib/generateDefinition.ts @@ -1,18 +1,19 @@ -import {Cluster} from 'zigbee-herdsman/dist/zspec/zcl/definition/tstype'; -import {Definition, ModernExtend, Zh} from './types'; -import {getClusterAttributeValue} from './utils'; -import * as m from './modernExtend'; import * as zh from 'zigbee-herdsman/dist'; -import {philipsLight} from './philips'; import {Endpoint} from 'zigbee-herdsman/dist/controller/model'; +import {Cluster} from 'zigbee-herdsman/dist/zspec/zcl/definition/tstype'; + import {logger} from './logger'; +import * as m from './modernExtend'; +import {philipsLight} from './philips'; +import {Definition, ModernExtend, Zh} from './types'; +import {getClusterAttributeValue} from './utils'; const NS = 'zhc:gendef'; interface GeneratedExtend { - getExtend(): ModernExtend, - getSource(): string, - lib?: string + getExtend(): ModernExtend; + getSource(): string; + lib?: string; } // Generator allows to define instances of GeneratedExtend that have typed arguments to extender. @@ -22,7 +23,7 @@ class Generator implements GeneratedExtend { source: string; lib?: string; - constructor(args: {extend: (a: T) => ModernExtend, args?: T, source: string, lib?: string}) { + constructor(args: {extend: (a: T) => ModernExtend; args?: T; source: string; lib?: string}) { this.extend = args.extend; this.args = args.args; this.source = args.source; @@ -65,7 +66,8 @@ function generateSource(definition: DefinitionWithZigbeeModel, generatedExtend: }); const importsStr = Object.entries(imports) - .map((e) => `const {${e[1].join(', ')}} = require('zigbee-herdsman-converters/lib/${e[0]}');`).join('\n'); + .map((e) => `const {${e[1].join(', ')}} = require('zigbee-herdsman-converters/lib/${e[0]}');`) + .join('\n'); return `${importsStr} @@ -81,7 +83,7 @@ const definition = { module.exports = definition;`; } -export async function generateDefinition(device: Zh.Device): Promise<{externalDefinitionSource: string, definition: Definition}> { +export async function generateDefinition(device: Zh.Device): Promise<{externalDefinitionSource: string; definition: Definition}> { // Map cluster to all endpoints that have this cluster. const mapClusters = (endpoint: Endpoint, clusters: Cluster[], clusterMap: Map) => { for (const cluster of clusters) { @@ -194,9 +196,10 @@ function maybeEndpointArgs(device: Zh.Device, endpoints: Zh.Endpoint[], toExt // If multiple endpoints provided(maybe including the first device endpoint) - // they all should be passed as an argument, where possible, to be explicit. const inputExtenders: Extender[] = [ - [['msTemperatureMeasurement'], async (d, eps) => [ - new Generator({extend: m.temperature, args: maybeEndpointArgs(d, eps), source: 'temperature'}), - ]], + [ + ['msTemperatureMeasurement'], + async (d, eps) => [new Generator({extend: m.temperature, args: maybeEndpointArgs(d, eps), source: 'temperature'})], + ], [['msPressureMeasurement'], async (d, eps) => [new Generator({extend: m.pressure, args: maybeEndpointArgs(d, eps), source: 'pressure'})]], [['msRelativeHumidity'], async (d, eps) => [new Generator({extend: m.humidity, args: maybeEndpointArgs(d, eps), source: 'humidity'})]], [['msCO2'], async (d, eps) => [new Generator({extend: m.co2, args: maybeEndpointArgs(d, eps), source: 'co2'})]], @@ -204,46 +207,53 @@ const inputExtenders: Extender[] = [ [['genOnOff', 'genLevelCtrl', 'lightingColorCtrl'], extenderOnOffLight], [['seMetering', 'haElectricalMeasurement'], extenderElectricityMeter], [['closuresDoorLock'], extenderLock], - [['msIlluminanceMeasurement'], async (d, eps) => [ - new Generator({extend: m.illuminance, args: maybeEndpointArgs(d, eps), source: 'illuminance'}), - ]], - [['msOccupancySensing'], async (d, eps) => [ - new Generator({extend: m.occupancy, source: 'occupancy'}), - ]], - [['ssIasZone'], async (d, eps) => [ - new Generator({extend: m.iasZoneAlarm, args: { - zoneType: 'generic', - zoneAttributes: ['alarm_1', 'alarm_2', 'tamper', 'battery_low'], - }, source: 'iasZoneAlarm'}), - ]], - [['ssIasWd'], async (d, eps) => [ - new Generator({extend: m.iasWarning, source: 'iasWarning'}), - ]], - [['genDeviceTempCfg'], async (d, eps) => [ - new Generator({extend: m.deviceTemperature, args: maybeEndpointArgs(d, eps), source: 'deviceTemperature'}), - ]], + [ + ['msIlluminanceMeasurement'], + async (d, eps) => [new Generator({extend: m.illuminance, args: maybeEndpointArgs(d, eps), source: 'illuminance'})], + ], + [['msOccupancySensing'], async (d, eps) => [new Generator({extend: m.occupancy, source: 'occupancy'})]], + [ + ['ssIasZone'], + async (d, eps) => [ + new Generator({ + extend: m.iasZoneAlarm, + args: { + zoneType: 'generic', + zoneAttributes: ['alarm_1', 'alarm_2', 'tamper', 'battery_low'], + }, + source: 'iasZoneAlarm', + }), + ], + ], + [['ssIasWd'], async (d, eps) => [new Generator({extend: m.iasWarning, source: 'iasWarning'})]], + [ + ['genDeviceTempCfg'], + async (d, eps) => [new Generator({extend: m.deviceTemperature, args: maybeEndpointArgs(d, eps), source: 'deviceTemperature'})], + ], [['pm25Measurement'], async (d, eps) => [new Generator({extend: m.pm25, args: maybeEndpointArgs(d, eps), source: 'pm25'})]], [['msFlowMeasurement'], async (d, eps) => [new Generator({extend: m.flow, args: maybeEndpointArgs(d, eps), source: 'flow'})]], [['msSoilMoisture'], async (d, eps) => [new Generator({extend: m.soilMoisture, args: maybeEndpointArgs(d, eps), source: 'soilMoisture'})]], - [['closuresWindowCovering'], async (d, eps) => [ - new Generator({extend: m.windowCovering, args: {controls: ['lift', 'tilt']}, source: 'windowCovering'}), - ]], + [ + ['closuresWindowCovering'], + async (d, eps) => [new Generator({extend: m.windowCovering, args: {controls: ['lift', 'tilt']}, source: 'windowCovering'})], + ], [['genIdentify'], async (d, eps) => [new Generator({extend: m.identify, source: 'identify'})]], ]; const outputExtenders: Extender[] = [ - [['genOnOff'], async (d, eps) => [ - new Generator({extend: m.commandsOnOff, args: maybeEndpointArgs(d, eps), source: 'commandsOnOff'}), - ]], - [['genLevelCtrl'], async (d, eps) => [ - new Generator({extend: m.commandsLevelCtrl, args: maybeEndpointArgs(d, eps), source: 'commandsLevelCtrl'}), - ]], - [['lightingColorCtrl'], async (d, eps) => [ - new Generator({extend: m.commandsColorCtrl, args: maybeEndpointArgs(d, eps), source: 'commandsColorCtrl'}), - ]], - [['closuresWindowCovering'], async (d, eps) => [ - new Generator({extend: m.commandsWindowCovering, args: maybeEndpointArgs(d, eps), source: 'commandsWindowCovering'}), - ]], + [['genOnOff'], async (d, eps) => [new Generator({extend: m.commandsOnOff, args: maybeEndpointArgs(d, eps), source: 'commandsOnOff'})]], + [ + ['genLevelCtrl'], + async (d, eps) => [new Generator({extend: m.commandsLevelCtrl, args: maybeEndpointArgs(d, eps), source: 'commandsLevelCtrl'})], + ], + [ + ['lightingColorCtrl'], + async (d, eps) => [new Generator({extend: m.commandsColorCtrl, args: maybeEndpointArgs(d, eps), source: 'commandsColorCtrl'})], + ], + [ + ['closuresWindowCovering'], + async (d, eps) => [new Generator({extend: m.commandsWindowCovering, args: maybeEndpointArgs(d, eps), source: 'commandsWindowCovering'})], + ], ]; async function extenderLock(device: Zh.Device, endpoints: Zh.Endpoint[]): Promise { @@ -278,10 +288,10 @@ async function extenderOnOffLight(device: Zh.Device, endpoints: Zh.Endpoint[]): if (endpoint.supportsInputCluster('lightingColorCtrl')) { colorCapabilities = await getClusterAttributeValue(endpoint, 'lightingColorCtrl', 'colorCapabilities', 31); } - const supportsHueSaturation = (colorCapabilities & 1<<0) > 0; - const supportsEnhancedHueSaturation = (colorCapabilities & 1<<1) > 0; - const supportsColorXY = (colorCapabilities & 1<<3) > 0; - const supportsColorTemperature = (colorCapabilities & 1<<4) > 0; + const supportsHueSaturation = (colorCapabilities & (1 << 0)) > 0; + const supportsEnhancedHueSaturation = (colorCapabilities & (1 << 1)) > 0; + const supportsColorXY = (colorCapabilities & (1 << 3)) > 0; + const supportsColorTemperature = (colorCapabilities & (1 << 4)) > 0; const args: m.LightArgs = {}; if (supportsColorTemperature) { diff --git a/src/lib/ikea.ts b/src/lib/ikea.ts index a5253300211ec..08825743b253b 100644 --- a/src/lib/ikea.ts +++ b/src/lib/ikea.ts @@ -1,23 +1,37 @@ -import {Fz, Tz, OnEvent, Configure, KeyValue, Range, ModernExtend, Expose, KeyValueAny} from '../lib/types'; +import * as semver from 'semver'; +import {Zcl} from 'zigbee-herdsman'; + +import tz from '../converters/toZigbee'; +import * as constants from '../lib/constants'; import {presets, access, options} from '../lib/exposes'; import { - postfixWithEndpointName, precisionRound, isObject, replaceInArray, isLegacyEnabled, hasAlreadyProcessedMessage, - assertString, getFromLookup, mapNumberRange, getEndpointName, -} from '../lib/utils'; -import { - LightArgs, light as lightDontUse, ota, ReportingConfigWithoutAttribute, - timeLookup, numeric, NumericArgs, setupConfigureForBinding, - setupConfigureForReporting, deviceAddCustomCluster, + LightArgs, + light as lightDontUse, + ota, + ReportingConfigWithoutAttribute, + timeLookup, + numeric, + NumericArgs, + setupConfigureForBinding, + setupConfigureForReporting, + deviceAddCustomCluster, } from '../lib/modernExtend'; - import {tradfri as ikea} from '../lib/ota'; - -import tz from '../converters/toZigbee'; -import * as constants from '../lib/constants'; import * as reporting from '../lib/reporting'; import * as globalStore from '../lib/store'; -import {Zcl} from 'zigbee-herdsman'; -import * as semver from 'semver'; +import {Fz, Tz, OnEvent, Configure, KeyValue, Range, ModernExtend, Expose, KeyValueAny} from '../lib/types'; +import { + postfixWithEndpointName, + precisionRound, + isObject, + replaceInArray, + isLegacyEnabled, + hasAlreadyProcessedMessage, + assertString, + getFromLookup, + mapNumberRange, + getEndpointName, +} from '../lib/utils'; export const manufacturerOptions = {manufacturerCode: Zcl.ManufacturerCode.IKEA_OF_SWEDEN}; @@ -35,10 +49,14 @@ const bulbOnEvent: OnEvent = async (type, data, device, options, state: KeyValue if (type === 'deviceAnnounce') { for (const endpoint of device.endpoints) { for (const c of endpoint.configuredReportings) { - await endpoint.configureReporting(c.cluster.name, [{ - attribute: c.attribute.name, minimumReportInterval: c.minimumReportInterval, - maximumReportInterval: c.maximumReportInterval, reportableChange: c.reportableChange, - }]); + await endpoint.configureReporting(c.cluster.name, [ + { + attribute: c.attribute.name, + minimumReportInterval: c.minimumReportInterval, + maximumReportInterval: c.maximumReportInterval, + reportableChange: c.reportableChange, + }, + ]); } } @@ -46,11 +64,11 @@ const bulbOnEvent: OnEvent = async (type, data, device, options, state: KeyValue // we only restore if true, to save unneeded network writes const colorOptions = state.color_options as KeyValue; if (colorOptions?.execute_if_off === true) { - await device.endpoints[0].write('lightingColorCtrl', {'options': 1}); + await device.endpoints[0].write('lightingColorCtrl', {options: 1}); } const levelConfig = state.level_config as KeyValue; if (levelConfig?.execute_if_off === true) { - await device.endpoints[0].write('genLevelCtrl', {'options': 1}); + await device.endpoints[0].write('genLevelCtrl', {options: 1}); } if (levelConfig?.on_level !== undefined) { const onLevelRaw = levelConfig.on_level; @@ -68,7 +86,7 @@ const bulbOnEvent: OnEvent = async (type, data, device, options, state: KeyValue } }; -export function ikeaLight(args?: Omit & {colorTemp?: true | {range: Range, viaColor: true}}) { +export function ikeaLight(args?: Omit & {colorTemp?: true | {range: Range; viaColor: true}}) { const colorTemp: {range: Range} = args?.colorTemp ? (args.colorTemp === true ? {range: [250, 454]} : args.colorTemp) : undefined; const result = lightDontUse({...args, colorTemp}); result.ota = ikea; @@ -88,55 +106,61 @@ export function ikeaOta(): ModernExtend { export function ikeaBattery(): ModernExtend { const exposes: Expose[] = [ - presets.numeric('battery', access.STATE_GET).withUnit('%') + presets + .numeric('battery', access.STATE_GET) + .withUnit('%') .withDescription('Remaining battery in %') - .withValueMin(0).withValueMax(100).withCategory('diagnostic'), + .withValueMin(0) + .withValueMax(100) + .withCategory('diagnostic'), ]; - const fromZigbee: Fz.Converter[] = [{ - cluster: 'genPowerCfg', - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - const payload: KeyValue = {}; - if (msg.data.hasOwnProperty('batteryPercentageRemaining') && (msg.data['batteryPercentageRemaining'] < 255)) { - // Some devices do not comply to the ZCL and report a - // batteryPercentageRemaining of 100 when the battery is full (should be 200). - let dividePercentage = true; - if (model.model === 'E2103') { - if (semver.lt(meta.device.softwareBuildID, '24.4.13', true)) { - dividePercentage = false; - } - } else { - // IKEA corrected this on newer remote fw version, but many people are still - // 2.2.010 which is the last version supporting group bindings. We try to be - // smart and pick the correct one for IKEA remotes. - // If softwareBuildID is below 2.4.0 it should not be divided - if (semver.lt(meta.device.softwareBuildID, '2.4.0', true)) { - dividePercentage = false; + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'genPowerCfg', + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + const payload: KeyValue = {}; + if (msg.data.hasOwnProperty('batteryPercentageRemaining') && msg.data['batteryPercentageRemaining'] < 255) { + // Some devices do not comply to the ZCL and report a + // batteryPercentageRemaining of 100 when the battery is full (should be 200). + let dividePercentage = true; + if (model.model === 'E2103') { + if (semver.lt(meta.device.softwareBuildID, '24.4.13', true)) { + dividePercentage = false; + } + } else { + // IKEA corrected this on newer remote fw version, but many people are still + // 2.2.010 which is the last version supporting group bindings. We try to be + // smart and pick the correct one for IKEA remotes. + // If softwareBuildID is below 2.4.0 it should not be divided + if (semver.lt(meta.device.softwareBuildID, '2.4.0', true)) { + dividePercentage = false; + } } - } - let percentage = msg.data['batteryPercentageRemaining']; - percentage = dividePercentage ? percentage / 2 : percentage; - payload.battery = precisionRound(percentage, 2); - } + let percentage = msg.data['batteryPercentageRemaining']; + percentage = dividePercentage ? percentage / 2 : percentage; + payload.battery = precisionRound(percentage, 2); + } - return payload; + return payload; + }, }, - }]; + ]; - const toZigbee: Tz.Converter[] = [{ - key: ['battery'], - convertGet: async (entity, key, meta) => { - await entity.read('genPowerCfg', ['batteryPercentageRemaining']); + const toZigbee: Tz.Converter[] = [ + { + key: ['battery'], + convertGet: async (entity, key, meta) => { + await entity.read('genPowerCfg', ['batteryPercentageRemaining']); + }, }, - }]; + ]; const defaultReporting: ReportingConfigWithoutAttribute = {min: '1_HOUR', max: 'MAX', change: 10}; - const configure: Configure[] = [ - setupConfigureForReporting('genPowerCfg', 'batteryPercentageRemaining', defaultReporting, access.STATE_GET), - ]; + const configure: Configure[] = [setupConfigureForReporting('genPowerCfg', 'batteryPercentageRemaining', defaultReporting, access.STATE_GET)]; return {exposes, fromZigbee, toZigbee, configure, isModernExtend: true}; } @@ -163,8 +187,10 @@ export function ikeaConfigureRemote(): ModernExtend { // - https://github.com/Koenkk/zigbee2mqtt/issues/7716 const endpoint = device.getEndpoint(1); const version = device.softwareBuildID.split('.').map((n) => Number(n)); - const bindTarget = version[0] > 2 || (version[0] == 2 && version[1] > 3) || (version[0] == 2 && version[1] == 3 && version[2] >= 75) ? - coordinatorEndpoint : constants.defaultBindGroup; + const bindTarget = + version[0] > 2 || (version[0] == 2 && version[1] > 3) || (version[0] == 2 && version[1] == 3 && version[2] >= 75) + ? coordinatorEndpoint + : constants.defaultBindGroup; await endpoint.bind('genOnOff', bindTarget); }, ]; @@ -175,18 +201,19 @@ export function ikeaConfigureRemote(): ModernExtend { export function ikeaAirPurifier(): ModernExtend { const exposes: Expose[] = [ presets.fan().withModes(['off', 'auto', '1', '2', '3', '4', '5', '6', '7', '8', '9']), - presets.numeric('fan_speed', access.STATE_GET).withValueMin(0).withValueMax(9) - .withDescription('Current fan speed'), - presets.numeric('pm25', access.STATE_GET).withLabel('PM25').withUnit('µg/m³') + presets.numeric('fan_speed', access.STATE_GET).withValueMin(0).withValueMax(9).withDescription('Current fan speed'), + presets + .numeric('pm25', access.STATE_GET) + .withLabel('PM25') + .withUnit('µg/m³') .withDescription('Measured PM2.5 (particulate matter) concentration'), - presets.enum('air_quality', access.STATE_GET, [ - 'excellent', 'good', 'moderate', 'poor', - 'unhealthy', 'hazardous', 'out_of_range', - 'unknown', - ]).withDescription('Calculated air quality'), + presets + .enum('air_quality', access.STATE_GET, ['excellent', 'good', 'moderate', 'poor', 'unhealthy', 'hazardous', 'out_of_range', 'unknown']) + .withDescription('Calculated air quality'), presets.binary('led_enable', access.ALL, true, false).withDescription('Controls the LED').withCategory('config'), presets.binary('child_lock', access.ALL, 'LOCK', 'UNLOCK').withDescription('Controls physical input on the device').withCategory('config'), - presets.binary('replace_filter', access.STATE_GET, true, false) + presets + .binary('replace_filter', access.STATE_GET, true, false) .withDescription('Indicates if the filter is older than 6 months and needs replacing') .withCategory('diagnostic'), presets.numeric('filter_age', access.STATE_GET).withDescription('Time the filter has been used in minutes').withCategory('diagnostic'), @@ -226,7 +253,7 @@ export function ikeaAirPurifier(): ModernExtend { airQuality = 'unknown'; } - pm25 = (pm25 == 65535) ? -1 : pm25; + pm25 = pm25 == 65535 ? -1 : pm25; state[pm25Property] = pm25; state[airQualityProperty] = airQuality; @@ -234,22 +261,22 @@ export function ikeaAirPurifier(): ModernExtend { if (msg.data.hasOwnProperty('filterRunTime')) { // Filter needs to be replaced after 6 months - state['replace_filter'] = (parseInt(msg.data['filterRunTime']) >= 259200); + state['replace_filter'] = parseInt(msg.data['filterRunTime']) >= 259200; state['filter_age'] = parseInt(msg.data['filterRunTime']); } if (msg.data.hasOwnProperty('controlPanelLight')) { - state['led_enable'] = (msg.data['controlPanelLight'] == 0); + state['led_enable'] = msg.data['controlPanelLight'] == 0; } if (msg.data.hasOwnProperty('childLock')) { - state['child_lock'] = ((msg.data['childLock'] == 0) ? 'UNLOCK' : 'LOCK'); + state['child_lock'] = msg.data['childLock'] == 0 ? 'UNLOCK' : 'LOCK'; } if (msg.data.hasOwnProperty('fanSpeed')) { let fanSpeed = msg.data['fanSpeed']; if (fanSpeed >= 10) { - fanSpeed = (((fanSpeed - 5) * 2) / 10); + fanSpeed = ((fanSpeed - 5) * 2) / 10; } else { fanSpeed = 0; } @@ -268,7 +295,7 @@ export function ikeaAirPurifier(): ModernExtend { } state['fan_mode'] = fanMode; - state['fan_state'] = (fanMode === 'off' ? 'OFF' : 'ON'); + state['fan_state'] = fanMode === 'off' ? 'OFF' : 'ON'; } return state; @@ -288,17 +315,17 @@ export function ikeaAirPurifier(): ModernExtend { let fanMode; switch (value) { - case 'off': - fanMode = 0; - break; - case 'auto': - fanMode = 1; - break; - default: - fanMode = ((Number(value) / 2.0) * 10) + 5; + case 'off': + fanMode = 0; + break; + case 'auto': + fanMode = 1; + break; + default: + fanMode = (Number(value) / 2.0) * 10 + 5; } - await entity.write('manuSpecificIkeaAirPurifier', {'fanMode': fanMode}, manufacturerOptions); + await entity.write('manuSpecificIkeaAirPurifier', {fanMode: fanMode}, manufacturerOptions); return {state: {fan_mode: value, fan_state: value === 'off' ? 'OFF' : 'ON'}}; }, convertGet: async (entity, key, meta) => { @@ -327,8 +354,8 @@ export function ikeaAirPurifier(): ModernExtend { key: ['child_lock'], convertSet: async (entity, key, value, meta) => { assertString(value); - await entity.write('manuSpecificIkeaAirPurifier', {'childLock': ((value.toLowerCase() === 'unlock') ? 0 : 1)}, manufacturerOptions); - return {state: {child_lock: ((value.toLowerCase() === 'lock') ? 'LOCK' : 'UNLOCK')}}; + await entity.write('manuSpecificIkeaAirPurifier', {childLock: value.toLowerCase() === 'unlock' ? 0 : 1}, manufacturerOptions); + return {state: {child_lock: value.toLowerCase() === 'lock' ? 'LOCK' : 'UNLOCK'}}; }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificIkeaAirPurifier', ['childLock']); @@ -337,8 +364,8 @@ export function ikeaAirPurifier(): ModernExtend { { key: ['led_enable'], convertSet: async (entity, key, value, meta) => { - await entity.write('manuSpecificIkeaAirPurifier', {'controlPanelLight': ((value) ? 0 : 1)}, manufacturerOptions); - return {state: {led_enable: ((value) ? true : false)}}; + await entity.write('manuSpecificIkeaAirPurifier', {controlPanelLight: value ? 0 : 1}, manufacturerOptions); + return {state: {led_enable: value ? true : false}}; }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificIkeaAirPurifier', ['controlPanelLight']); @@ -351,18 +378,40 @@ export function ikeaAirPurifier(): ModernExtend { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['manuSpecificIkeaAirPurifier']); - await endpoint.configureReporting('manuSpecificIkeaAirPurifier', [{attribute: 'particulateMatter25Measurement', - minimumReportInterval: timeLookup['1_MINUTE'], maximumReportInterval: timeLookup['1_HOUR'], reportableChange: 1}], - manufacturerOptions); - await endpoint.configureReporting('manuSpecificIkeaAirPurifier', [{attribute: 'filterRunTime', - minimumReportInterval: timeLookup['1_HOUR'], maximumReportInterval: timeLookup['1_HOUR'], reportableChange: 0}], - manufacturerOptions); - await endpoint.configureReporting('manuSpecificIkeaAirPurifier', [{attribute: 'fanMode', - minimumReportInterval: 0, maximumReportInterval: timeLookup['1_HOUR'], reportableChange: 1}], - manufacturerOptions); - await endpoint.configureReporting('manuSpecificIkeaAirPurifier', [{attribute: 'fanSpeed', - minimumReportInterval: 0, maximumReportInterval: timeLookup['1_HOUR'], reportableChange: 1}], - manufacturerOptions); + await endpoint.configureReporting( + 'manuSpecificIkeaAirPurifier', + [ + { + attribute: 'particulateMatter25Measurement', + minimumReportInterval: timeLookup['1_MINUTE'], + maximumReportInterval: timeLookup['1_HOUR'], + reportableChange: 1, + }, + ], + manufacturerOptions, + ); + await endpoint.configureReporting( + 'manuSpecificIkeaAirPurifier', + [ + { + attribute: 'filterRunTime', + minimumReportInterval: timeLookup['1_HOUR'], + maximumReportInterval: timeLookup['1_HOUR'], + reportableChange: 0, + }, + ], + manufacturerOptions, + ); + await endpoint.configureReporting( + 'manuSpecificIkeaAirPurifier', + [{attribute: 'fanMode', minimumReportInterval: 0, maximumReportInterval: timeLookup['1_HOUR'], reportableChange: 1}], + manufacturerOptions, + ); + await endpoint.configureReporting( + 'manuSpecificIkeaAirPurifier', + [{attribute: 'fanSpeed', minimumReportInterval: 0, maximumReportInterval: timeLookup['1_HOUR'], reportableChange: 1}], + manufacturerOptions, + ); await endpoint.read('manuSpecificIkeaAirPurifier', ['controlPanelLight', 'childLock', 'filterRunTime']); }, @@ -390,7 +439,7 @@ export function ikeaConfigureGenPollCtrl(args?: {endpointId: number}): ModernExt async (device, coordinatorEndpoint, definition) => { const endpoint = device.getEndpoint(args.endpointId); if (Number(device?.softwareBuildID?.split('.')[0]) >= 24) { - await endpoint.write('genPollCtrl', {'checkinInterval': 172800}); + await endpoint.write('genPollCtrl', {checkinInterval: 172800}); } }, ]; @@ -401,40 +450,44 @@ export function ikeaConfigureGenPollCtrl(args?: {endpointId: number}): ModernExt export function tradfriOccupancy(): ModernExtend { const exposes: Expose[] = [ presets.binary('occupancy', access.STATE, true, false).withDescription('Indicates whether the device detected occupancy'), - presets.binary('illuminance_above_threshold', access.STATE, true, false) + presets + .binary('illuminance_above_threshold', access.STATE, true, false) .withDescription('Indicates whether the device detected bright light (works only in night mode)') .withCategory('diagnostic'), ]; - const fromZigbee: Fz.Converter[] = [{ - cluster: 'genOnOff', - type: 'commandOnWithTimedOff', - options: [options.occupancy_timeout(), options.illuminance_below_threshold_check()], - convert: (model, msg, publish, options, meta) => { - const onlyWhenOnFlag = (msg.data.ctrlbits & 1) != 0; - if (onlyWhenOnFlag && - (!options || !options.hasOwnProperty('illuminance_below_threshold_check') || - options.illuminance_below_threshold_check) && - !globalStore.hasValue(msg.endpoint, 'timer')) return; - - const timeout = options && options.hasOwnProperty('occupancy_timeout') ? - Number(options.occupancy_timeout) : msg.data.ontime / 10; - - // Stop existing timer because motion is detected and set a new one. - clearTimeout(globalStore.getValue(msg.endpoint, 'timer')); - globalStore.clearValue(msg.endpoint, 'timer'); - - if (timeout !== 0) { - const timer = setTimeout(() => { - publish({occupancy: false}); - globalStore.clearValue(msg.endpoint, 'timer'); - }, timeout * 1000); - globalStore.putValue(msg.endpoint, 'timer', timer); - } + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'genOnOff', + type: 'commandOnWithTimedOff', + options: [options.occupancy_timeout(), options.illuminance_below_threshold_check()], + convert: (model, msg, publish, options, meta) => { + const onlyWhenOnFlag = (msg.data.ctrlbits & 1) != 0; + if ( + onlyWhenOnFlag && + (!options || !options.hasOwnProperty('illuminance_below_threshold_check') || options.illuminance_below_threshold_check) && + !globalStore.hasValue(msg.endpoint, 'timer') + ) + return; + + const timeout = options && options.hasOwnProperty('occupancy_timeout') ? Number(options.occupancy_timeout) : msg.data.ontime / 10; + + // Stop existing timer because motion is detected and set a new one. + clearTimeout(globalStore.getValue(msg.endpoint, 'timer')); + globalStore.clearValue(msg.endpoint, 'timer'); + + if (timeout !== 0) { + const timer = setTimeout(() => { + publish({occupancy: false}); + globalStore.clearValue(msg.endpoint, 'timer'); + }, timeout * 1000); + globalStore.putValue(msg.endpoint, 'timer', timer); + } - return {occupancy: true, illuminance_above_threshold: onlyWhenOnFlag}; + return {occupancy: true, illuminance_above_threshold: onlyWhenOnFlag}; + }, }, - }]; + ]; return {exposes, fromZigbee, isModernExtend: true}; } @@ -445,17 +498,19 @@ export function tradfriRequestedBrightness(): ModernExtend { presets.numeric('requested_brightness_percent', access.STATE).withValueMin(30).withValueMax(100).withCategory('diagnostic'), ]; - const fromZigbee: Fz.Converter[] = [{ - // Possible values are 76 (30%) or 254 (100%) - cluster: 'genLevelCtrl', - type: 'commandMoveToLevelWithOnOff', - convert: (model, msg, publish, options, meta) => { - return { - requested_brightness_level: msg.data.level, - requested_brightness_percent: mapNumberRange(msg.data.level, 0, 254, 0, 100), - }; + const fromZigbee: Fz.Converter[] = [ + { + // Possible values are 76 (30%) or 254 (100%) + cluster: 'genLevelCtrl', + type: 'commandMoveToLevelWithOnOff', + convert: (model, msg, publish, options, meta) => { + return { + requested_brightness_level: msg.data.level, + requested_brightness_percent: mapNumberRange(msg.data.level, 0, 254, 0, 100), + }; + }, }, - }]; + ]; return {exposes, fromZigbee, isModernExtend: true}; } @@ -463,13 +518,15 @@ export function tradfriRequestedBrightness(): ModernExtend { export function tradfriCommandsOnOff(): ModernExtend { const exposes: Expose[] = [presets.action(['toggle'])]; - const fromZigbee: Fz.Converter[] = [{ - cluster: 'genOnOff', - type: 'commandToggle', - convert: (model, msg, publish, options, meta) => { - return {action: postfixWithEndpointName('toggle', msg, model, meta)}; + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'genOnOff', + type: 'commandToggle', + convert: (model, msg, publish, options, meta) => { + return {action: postfixWithEndpointName('toggle', msg, model, meta)}; + }, }, - }]; + ]; return {exposes, fromZigbee, isModernExtend: true}; } @@ -487,16 +544,23 @@ export function tradfriCommandsLevelCtrl(): ModernExtend { const exposes: Expose[] = [presets.action(Object.values(actionLookup))]; - const fromZigbee: Fz.Converter[] = [{ - cluster: 'genLevelCtrl', - type: [ - 'commandStepWithOnOff', 'commandStep', 'commandMoveWithOnOff', 'commandStopWithOnOff', 'commandMove', 'commandStop', - 'commandMoveToLevelWithOnOff', - ], - convert: (model, msg, publish, options, meta) => { - return {action: actionLookup[msg.type]}; + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'genLevelCtrl', + type: [ + 'commandStepWithOnOff', + 'commandStep', + 'commandMoveWithOnOff', + 'commandStopWithOnOff', + 'commandMove', + 'commandStop', + 'commandMoveToLevelWithOnOff', + ], + convert: (model, msg, publish, options, meta) => { + return {action: actionLookup[msg.type]}; + }, }, - }]; + ]; return {exposes, fromZigbee, isModernExtend: true}; } @@ -506,36 +570,38 @@ export function styrbarCommandOn(): ModernExtend { // https://github.com/Koenkk/zigbee2mqtt/issues/13335 const exposes: Expose[] = [presets.action(['on'])]; - const fromZigbee: Fz.Converter[] = [{ - cluster: 'genOnOff', - type: 'commandOn', - convert: (model, msg, publish, options, meta) => { - if (hasAlreadyProcessedMessage(msg, model)) return; - const arrowReleaseAgo = Date.now() - globalStore.getValue(msg.endpoint, 'arrow_release', 0); - if (arrowReleaseAgo > 700) { - return {action: 'on'}; - } + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'genOnOff', + type: 'commandOn', + convert: (model, msg, publish, options, meta) => { + if (hasAlreadyProcessedMessage(msg, model)) return; + const arrowReleaseAgo = Date.now() - globalStore.getValue(msg.endpoint, 'arrow_release', 0); + if (arrowReleaseAgo > 700) { + return {action: 'on'}; + } + }, }, - }]; + ]; return {exposes, fromZigbee, isModernExtend: true}; } -export function ikeaDotsClick(args: {actionLookup?: KeyValue, dotsPrefix?: boolean, endpointNames: string[]}): ModernExtend { +export function ikeaDotsClick(args: {actionLookup?: KeyValue; dotsPrefix?: boolean; endpointNames: string[]}): ModernExtend { args = { actionLookup: { - 'commandAction1': 'initial_press', - 'commandAction2': 'long_press', - 'commandAction3': 'short_release', - 'commandAction4': 'long_release', - 'commandAction6': 'double_press', + commandAction1: 'initial_press', + commandAction2: 'long_press', + commandAction3: 'short_release', + commandAction4: 'long_release', + commandAction6: 'double_press', }, dotsPrefix: false, ...args, }; - const actions = args.endpointNames.map( - (b) => Object.values(args.actionLookup).map((a) => args.dotsPrefix ? `dots_${b}_${a}` : `${b}_${a}`), - ).flat(); + const actions = args.endpointNames + .map((b) => Object.values(args.actionLookup).map((a) => (args.dotsPrefix ? `dots_${b}_${a}` : `${b}_${a}`))) + .flat(); const exposes: Expose[] = [presets.action(actions)]; const fromZigbee: Fz.Converter[] = [ @@ -548,9 +614,15 @@ export function ikeaDotsClick(args: {actionLookup?: KeyValue, dotsPrefix?: boole let action; const button = msg.data[5]; switch (msg.data[6]) { - case 1: action = 'initial_press'; break; - case 2: action = 'double_press'; break; - case 3: action = 'long_press'; break; + case 1: + action = 'initial_press'; + break; + case 2: + action = 'double_press'; + break; + case 3: + action = 'long_press'; + break; } return {action: args.dotsPrefix ? `dots_${button}_${action}` : `${button}_${action}`}; @@ -573,10 +645,9 @@ export function ikeaDotsClick(args: {actionLookup?: KeyValue, dotsPrefix?: boole return {exposes, fromZigbee, configure, isModernExtend: true}; } -export function ikeaArrowClick(args?: {styrbar?: boolean, bind?: boolean}): ModernExtend { +export function ikeaArrowClick(args?: {styrbar?: boolean; bind?: boolean}): ModernExtend { args = {styrbar: false, bind: true, ...args}; - const actions = ['arrow_left_click', 'arrow_left_hold', 'arrow_left_release', - 'arrow_right_click', 'arrow_right_hold', 'arrow_right_release']; + const actions = ['arrow_left_click', 'arrow_left_hold', 'arrow_left_release', 'arrow_right_click', 'arrow_right_hold', 'arrow_right_release']; const exposes: Expose[] = [presets.action(actions)]; const fromZigbee: Fz.Converter[] = [ @@ -628,8 +699,7 @@ export function ikeaArrowClick(args?: {styrbar?: boolean, bind?: boolean}): Mode } export function ikeaMediaCommands(): ModernExtend { - const actions = ['track_previous', 'track_next', 'volume_up', - 'volume_down', 'volume_up_hold', 'volume_down_hold']; + const actions = ['track_previous', 'track_next', 'volume_up', 'volume_down', 'volume_up_hold', 'volume_down_hold']; const exposes: Expose[] = [presets.action(actions)]; const fromZigbee: Fz.Converter[] = [ @@ -668,59 +738,50 @@ export function ikeaMediaCommands(): ModernExtend { } export function addCustomClusterManuSpecificIkeaAirPurifier(): ModernExtend { - return deviceAddCustomCluster( - 'manuSpecificIkeaAirPurifier', - { - ID: 0xfc7d, - manufacturerCode: Zcl.ManufacturerCode.IKEA_OF_SWEDEN, - attributes: { - filterRunTime: {ID: 0x0000, type: Zcl.DataType.UINT32}, - replaceFilter: {ID: 0x0001, type: Zcl.DataType.UINT8}, - filterLifeTime: {ID: 0x0002, type: Zcl.DataType.UINT32}, - controlPanelLight: {ID: 0x0003, type: Zcl.DataType.BOOLEAN}, - particulateMatter25Measurement: {ID: 0x0004, type: Zcl.DataType.UINT16}, - childLock: {ID: 0x0005, type: Zcl.DataType.BOOLEAN}, - fanMode: {ID: 0x0006, type: Zcl.DataType.UINT8}, - fanSpeed: {ID: 0x0007, type: Zcl.DataType.UINT8}, - deviceRunTime: {ID: 0x0008, type: Zcl.DataType.UINT32}, - }, - commands: {}, - commandsResponse: {}, + return deviceAddCustomCluster('manuSpecificIkeaAirPurifier', { + ID: 0xfc7d, + manufacturerCode: Zcl.ManufacturerCode.IKEA_OF_SWEDEN, + attributes: { + filterRunTime: {ID: 0x0000, type: Zcl.DataType.UINT32}, + replaceFilter: {ID: 0x0001, type: Zcl.DataType.UINT8}, + filterLifeTime: {ID: 0x0002, type: Zcl.DataType.UINT32}, + controlPanelLight: {ID: 0x0003, type: Zcl.DataType.BOOLEAN}, + particulateMatter25Measurement: {ID: 0x0004, type: Zcl.DataType.UINT16}, + childLock: {ID: 0x0005, type: Zcl.DataType.BOOLEAN}, + fanMode: {ID: 0x0006, type: Zcl.DataType.UINT8}, + fanSpeed: {ID: 0x0007, type: Zcl.DataType.UINT8}, + deviceRunTime: {ID: 0x0008, type: Zcl.DataType.UINT32}, }, - ); + commands: {}, + commandsResponse: {}, + }); } export function addCustomClusterManuSpecificIkeaVocIndexMeasurement(): ModernExtend { - return deviceAddCustomCluster( - 'manuSpecificIkeaVocIndexMeasurement', - { - ID: 0xfc7e, - manufacturerCode: Zcl.ManufacturerCode.IKEA_OF_SWEDEN, - attributes: { - measuredValue: {ID: 0x0000, type: Zcl.DataType.SINGLE_PREC}, - measuredMinValue: {ID: 0x0001, type: Zcl.DataType.SINGLE_PREC}, - measuredMaxValue: {ID: 0x0002, type: Zcl.DataType.SINGLE_PREC}, - }, - commands: {}, - commandsResponse: {}, + return deviceAddCustomCluster('manuSpecificIkeaVocIndexMeasurement', { + ID: 0xfc7e, + manufacturerCode: Zcl.ManufacturerCode.IKEA_OF_SWEDEN, + attributes: { + measuredValue: {ID: 0x0000, type: Zcl.DataType.SINGLE_PREC}, + measuredMinValue: {ID: 0x0001, type: Zcl.DataType.SINGLE_PREC}, + measuredMaxValue: {ID: 0x0002, type: Zcl.DataType.SINGLE_PREC}, }, - ); + commands: {}, + commandsResponse: {}, + }); } // Seems to be present on newer IKEA devices like: VINDSTYRKA, RODRET, and BADRING // Also observed on some older devices that had a post DIRIGERA release fw update. // No attributes known. export function addCustomClusterManuSpecificIkeaUnknown(): ModernExtend { - return deviceAddCustomCluster( - 'manuSpecificIkeaUnknown', - { - ID: 0xfc7c, - manufacturerCode: Zcl.ManufacturerCode.IKEA_OF_SWEDEN, - attributes: {}, - commands: {}, - commandsResponse: {}, - }, - ); + return deviceAddCustomCluster('manuSpecificIkeaUnknown', { + ID: 0xfc7c, + manufacturerCode: Zcl.ManufacturerCode.IKEA_OF_SWEDEN, + attributes: {}, + commands: {}, + commandsResponse: {}, + }); } export const legacy = { @@ -781,7 +842,5 @@ export const legacy = { }, } satisfies Fz.Converter, }, - toZigbee: { - - }, + toZigbee: {}, }; diff --git a/src/lib/kelvinToXy.ts b/src/lib/kelvinToXy.ts index 27686724a46fa..20e6af465432e 100644 --- a/src/lib/kelvinToXy.ts +++ b/src/lib/kelvinToXy.ts @@ -1,22 +1,22 @@ -const lookup: {[k: number]: {x: number, y: number}} = { - 1000: {x: 0.652750055750174, y: 0.344462227197370}, +const lookup: {[k: number]: {x: number; y: number}} = { + 1000: {x: 0.652750055750174, y: 0.34446222719737}, 1010: {x: 0.651338820005714, y: 0.345710187476526}, - 1020: {x: 0.649930067872860, y: 0.346948777634801}, + 1020: {x: 0.64993006787286, y: 0.346948777634801}, 1030: {x: 0.648523859077325, y: 0.348177799899612}, 1040: {x: 0.647120247807274, y: 0.349397065326079}, 1050: {x: 0.645719283115238, y: 0.350606393540134}, 1060: {x: 0.644321009299518, y: 0.351805612493099}, 1070: {x: 0.642925466265835, y: 0.352994558226964}, - 1080: {x: 0.641532689869940, y: 0.354173074649700}, + 1080: {x: 0.64153268986994, y: 0.3541730746497}, 1090: {x: 0.640142712241891, y: 0.355341013319936}, 1100: {x: 0.638755562092687, y: 0.356498233240403}, 1110: {x: 0.637371265003916, y: 0.357644600659585}, - 1120: {x: 0.635989843701090, y: 0.358779988881019}, + 1120: {x: 0.63598984370109, y: 0.358779988881019}, 1130: {x: 0.634611318311292, y: 0.359904278079781}, 1140: {x: 0.633235706605755, y: 0.361017355125666}, - 1150: {x: 0.631863024228000, y: 0.362119113412632}, + 1150: {x: 0.631863024228, y: 0.362119113412632}, 1160: {x: 0.630493284908101, y: 0.363209452694114}, - 1170: {x: 0.629126500663671, y: 0.364288278923810}, + 1170: {x: 0.629126500663671, y: 0.36428827892381}, 1180: {x: 0.627762681988133, y: 0.365355504101598}, 1190: {x: 0.626401838026807, y: 0.366411046124246}, 1200: {x: 0.625043976741368, y: 0.367454828640607}, @@ -25,8 +25,8 @@ const lookup: {[k: number]: {x: number, y: number}} = { 1230: {x: 0.620988353948665, y: 0.370514938996308}, 1240: {x: 0.619642484457822, y: 0.371511030177355}, 1250: {x: 0.618299624701903, y: 0.372495061588915}, - 1260: {x: 0.616959778406011, y: 0.373466988568860}, - 1270: {x: 0.615622948978640, y: 0.374426771297343}, + 1260: {x: 0.616959778406011, y: 0.37346698856886}, + 1270: {x: 0.61562294897864, y: 0.374426771297343}, 1280: {x: 0.614289139600468, y: 0.375374374679072}, 1290: {x: 0.612958353305667, y: 0.376309768228112}, 1300: {x: 0.611630593056105, y: 0.377232925955066}, @@ -35,15 +35,15 @@ const lookup: {[k: number]: {x: number, y: number}} = { 1330: {x: 0.607665498485884, y: 0.379928789450492}, 1340: {x: 0.606349872820529, y: 0.380802830100132}, 1350: {x: 0.605037289071589, y: 0.381664568631302}, - 1360: {x: 0.603727750973495, y: 0.382514003782810}, - 1370: {x: 0.602421262538920, y: 0.383351138057187}, + 1360: {x: 0.603727750973495, y: 0.38251400378281}, + 1370: {x: 0.60242126253892, y: 0.383351138057187}, 1380: {x: 0.601117828088629, y: 0.384175977622988}, 1390: {x: 0.599817452277141, y: 0.384988532218637}, 1400: {x: 0.598520140114478, y: 0.385788815057729}, 1410: {x: 0.597225896984257, y: 0.386576842735756}, 1420: {x: 0.595934728658356, y: 0.387352635138207}, - 1430: {x: 0.594646641308391, y: 0.388116215350000}, - 1440: {x: 0.593361641514230, y: 0.388867609566210}, + 1430: {x: 0.594646641308391, y: 0.38811621535}, + 1440: {x: 0.59336164151423, y: 0.38886760956621}, 1450: {x: 0.592079736269747, y: 0.389606847004059}, 1460: {x: 0.590800932986013, y: 0.390333959816145}, 1470: {x: 0.589525239492135, y: 0.391048983004883}, @@ -59,7 +59,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 1570: {x: 0.576941356101364, y: 0.397543858313098}, 1580: {x: 0.575700501670519, y: 0.398128938699214}, 1590: {x: 0.574462881390147, y: 0.398702542211806}, - 1600: {x: 0.573228507819347, y: 0.399264733637420}, + 1600: {x: 0.573228507819347, y: 0.39926473363742}, 1610: {x: 0.571997393785926, y: 0.399815579533917}, 1620: {x: 0.570769552369208, y: 0.400355148161688}, 1630: {x: 0.569544996882493, y: 0.400883509415978}, @@ -69,19 +69,19 @@ const lookup: {[k: number]: {x: number, y: number}} = { 1670: {x: 0.564679907687394, y: 0.402886332124401}, 1680: {x: 0.563471988481735, y: 0.403359757559409}, 1690: {x: 0.562267438989995, y: 0.403822425671539}, - 1700: {x: 0.561066273656675, y: 0.404274415996570}, + 1700: {x: 0.561066273656675, y: 0.40427441599657}, 1710: {x: 0.559868507015037, y: 0.404715809202991}, 1720: {x: 0.558674153669138, y: 0.405146687034153}, 1730: {x: 0.557483228276071, y: 0.405567132251513}, 1740: {x: 0.556295745528408, y: 0.405977228578975}, 1750: {x: 0.555111720136911, y: 0.406377060648321}, - 1760: {x: 0.553931166813512, y: 0.406766713945730}, + 1760: {x: 0.553931166813512, y: 0.40676671394573}, 1770: {x: 0.552754100254617, y: 0.407146274759384}, - 1780: {x: 0.551580535124720, y: 0.407515830128156}, + 1780: {x: 0.55158053512472, y: 0.407515830128156}, 1790: {x: 0.550410486040386, y: 0.407875467791389}, - 1800: {x: 0.549243967554590, y: 0.408225276139745}, - 1810: {x: 0.548080994141461, y: 0.408565344167120}, - 1820: {x: 0.546921580181400, y: 0.408895761423654}, + 1800: {x: 0.54924396755459, y: 0.408225276139745}, + 1810: {x: 0.548080994141461, y: 0.40856534416712}, + 1820: {x: 0.5469215801814, y: 0.408895761423654}, 1830: {x: 0.545765739946639, y: 0.409216617969775}, 1840: {x: 0.544613487587208, y: 0.409528004331327}, 1850: {x: 0.543464837117342, y: 0.409830011455734}, @@ -90,17 +90,17 @@ const lookup: {[k: number]: {x: number, y: number}} = { 1880: {x: 0.540040634877467, y: 0.410680672312926}, 1890: {x: 0.538906528941369, y: 0.410946078919076}, 1900: {x: 0.537776092484472, y: 0.411202565887823}, - 1910: {x: 0.536649338445830, y: 0.411450225833805}, + 1910: {x: 0.53664933844583, y: 0.411450225833805}, 1920: {x: 0.535526279546149, y: 0.411689151515339}, 1930: {x: 0.534406928277829, y: 0.411919435798755}, 1940: {x: 0.533291296895457, y: 0.412141171623705}, 1950: {x: 0.532179397406758, y: 0.412354451969443}, - 1960: {x: 0.531071241563990, y: 0.412559369822065}, + 1960: {x: 0.53107124156399, y: 0.412559369822065}, 1970: {x: 0.529966840855793, y: 0.412756018142703}, - 1980: {x: 0.528866206499480, y: 0.412944489836645}, + 1980: {x: 0.52886620649948, y: 0.412944489836645}, 1990: {x: 0.527769349433755, y: 0.413124877723394}, - 2000: {x: 0.526676280311873, y: 0.413297274507630}, - 2010: {x: 0.525587009495220, y: 0.413461772751086}, + 2000: {x: 0.526676280311873, y: 0.41329727450763}, + 2010: {x: 0.52558700949522, y: 0.413461772751086}, 2020: {x: 0.524501547047308, y: 0.413618464845305}, 2030: {x: 0.523419902728187, y: 0.413767442985273}, 2040: {x: 0.522342085989252, y: 0.413908799143933}, @@ -108,7 +108,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 2060: {x: 0.520197971485871, y: 0.414169012151805}, 2070: {x: 0.519131691039726, y: 0.414288051619026}, 2080: {x: 0.518069272802678, y: 0.414399834295808}, - 2090: {x: 0.517010724618550, y: 0.414504450691750}, + 2090: {x: 0.51701072461855, y: 0.41450445069175}, 2100: {x: 0.515956053999373, y: 0.414601990958848}, 2110: {x: 0.514905268122789, y: 0.414692544871688}, 2120: {x: 0.513858373829776, y: 0.414776201808388}, @@ -121,9 +121,9 @@ const lookup: {[k: number]: {x: number, y: number}} = { 2190: {x: 0.506639581304996, y: 0.415176101410431}, 2200: {x: 0.505624025055233, y: 0.415207746862562}, 2210: {x: 0.504612406586543, y: 0.415233279113804}, - 2220: {x: 0.503604729315130, y: 0.415252782686356}, + 2220: {x: 0.50360472931513, y: 0.415252782686356}, 2230: {x: 0.502600996314909, y: 0.415266341542373}, - 2240: {x: 0.501601210318512, y: 0.415274039072920}, + 2240: {x: 0.501601210318512, y: 0.41527403907292}, 2250: {x: 0.500605373718514, y: 0.415275958087495}, 2260: {x: 0.499613488568853, y: 0.415272180804119}, 2270: {x: 0.498625556586457, y: 0.415262788839981}, @@ -131,19 +131,19 @@ const lookup: {[k: number]: {x: number, y: number}} = { 2290: {x: 0.496661557317139, y: 0.415227484281571}, 2300: {x: 0.495685491796189, y: 0.415201731840687}, 2310: {x: 0.494713382978936, y: 0.415170685010734}, - 2320: {x: 0.493745230927870, y: 0.415134422282633}, + 2320: {x: 0.49374523092787, y: 0.415134422282633}, 2330: {x: 0.492781035381868, y: 0.415093021501115}, 2340: {x: 0.491820795758963, y: 0.415046559858839}, 2350: {x: 0.490864511159246, y: 0.414995113890958}, 2360: {x: 0.489912180367896, y: 0.414938759470118}, 2370: {x: 0.488963801858331, y: 0.414877571801882}, 2380: {x: 0.488019373795462, y: 0.414811625420557}, - 2390: {x: 0.487078894039070, y: 0.414740994185430}, + 2390: {x: 0.48707889403907, y: 0.41474099418543}, 2400: {x: 0.486142360147258, y: 0.414665751277374}, 2410: {x: 0.485209769380024, y: 0.414585969195845}, - 2420: {x: 0.484281118702893, y: 0.414501719756230}, + 2420: {x: 0.484281118702893, y: 0.41450171975623}, 2430: {x: 0.483356404790651, y: 0.414413074087558}, - 2440: {x: 0.482435624031141, y: 0.414320102630540}, + 2440: {x: 0.482435624031141, y: 0.41432010263054}, 2450: {x: 0.481518772529133, y: 0.414222875135951}, 2460: {x: 0.480605846110259, y: 0.414121460663327}, 2470: {x: 0.479696840325004, y: 0.414015927579967}, @@ -159,15 +159,15 @@ const lookup: {[k: number]: {x: number, y: number}} = { 2570: {x: 0.470821249924792, y: 0.412748618462133}, 2580: {x: 0.469955002881062, y: 0.412602094779799}, 2590: {x: 0.469092603126886, y: 0.412452218918178}, - 2600: {x: 0.468234043017033, y: 0.412299050278290}, - 2610: {x: 0.467379314690560, y: 0.412142647582159}, + 2600: {x: 0.468234043017033, y: 0.41229905027829}, + 2610: {x: 0.46737931469056, y: 0.412142647582159}, 2620: {x: 0.466528410075157, y: 0.411983068875347}, 2630: {x: 0.465681320891482, y: 0.411820371529656}, 2640: {x: 0.464838038657489, y: 0.411654612245991}, 2650: {x: 0.463998554692746, y: 0.411485847057383}, 2660: {x: 0.463162860122743, y: 0.411314131332149}, 2670: {x: 0.462330945883173, y: 0.411139519777208}, - 2680: {x: 0.461502802724214, y: 0.410962066441520}, + 2680: {x: 0.461502802724214, y: 0.41096206644152}, 2690: {x: 0.460678421214773, y: 0.410781824719658}, 2700: {x: 0.459857791746717, y: 0.410598847355503}, 2710: {x: 0.459040904539081, y: 0.410413186446054}, @@ -176,7 +176,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 2740: {x: 0.456612596164132, y: 0.409840613795755}, 2750: {x: 0.455810576877562, y: 0.409644726876854}, 2760: {x: 0.455012248499377, y: 0.409446407335191}, - 2770: {x: 0.454217600298360, y: 0.409245703472280}, + 2770: {x: 0.45421760029836, y: 0.40924570347228}, 2780: {x: 0.453426621399085, y: 0.409042662972206}, 2790: {x: 0.452639300785869, y: 0.408837332906172}, 2800: {x: 0.451855627306685, y: 0.408629759737106}, @@ -206,9 +206,9 @@ const lookup: {[k: number]: {x: number, y: number}} = { 3040: {x: 0.434110581272061, y: 0.403085678890438}, 3050: {x: 0.433414138171168, y: 0.402835449626724}, 3060: {x: 0.432721016221012, y: 0.402583987976887}, - 3070: {x: 0.432031201911340, y: 0.402331325870893}, + 3070: {x: 0.43203120191134, y: 0.402331325870893}, 3080: {x: 0.431344681687312, y: 0.402077494772728}, - 3090: {x: 0.430661441952100, y: 0.401822525685539}, + 3090: {x: 0.4306614419521, y: 0.401822525685539}, 3100: {x: 0.429981469069442, y: 0.401566449156763}, 3110: {x: 0.429304749366147, y: 0.401309295283229}, 3120: {x: 0.428631269134558, y: 0.401051093716253}, @@ -216,35 +216,35 @@ const lookup: {[k: number]: {x: number, y: number}} = { 3140: {x: 0.427293972097967, y: 0.400531663910017}, 3150: {x: 0.426630127726814, y: 0.400270492791288}, 3160: {x: 0.425969467699671, y: 0.400008388230194}, - 3170: {x: 0.425311978171859, y: 0.399745377726000}, + 3170: {x: 0.425311978171859, y: 0.399745377726}, 3180: {x: 0.424657645278049, y: 0.399481488362498}, 3190: {x: 0.424006455134406, y: 0.399216746812922}, 3200: {x: 0.423358393840696, y: 0.398951179344833}, - 3210: {x: 0.422713447482352, y: 0.398684811824980}, + 3210: {x: 0.422713447482352, y: 0.39868481182498}, 3220: {x: 0.422071602132489, y: 0.398417669724122}, - 3230: {x: 0.421432843853882, y: 0.398149778121830}, + 3230: {x: 0.421432843853882, y: 0.39814977812183}, 3240: {x: 0.420797158700909, y: 0.397881161711245}, - 3250: {x: 0.420164532721440, y: 0.397611844803810}, + 3250: {x: 0.42016453272144, y: 0.39761184480381}, 3260: {x: 0.419534951958697, y: 0.397341851333969}, 3270: {x: 0.418908402453069, y: 0.397071204863828}, 3280: {x: 0.418284870243884, y: 0.396799928587787}, - 3290: {x: 0.417664341371150, y: 0.396528045337128}, + 3290: {x: 0.41766434137115, y: 0.396528045337128}, 3300: {x: 0.417046801877253, y: 0.396255577584578}, 3310: {x: 0.416432237808611, y: 0.395982547448827}, 3320: {x: 0.415820635217303, y: 0.395708976699012}, 3330: {x: 0.415211980162654, y: 0.395434886759171}, 3340: {x: 0.414606258712775, y: 0.395160298712644}, 3350: {x: 0.414003456946084, y: 0.394885233306455}, - 3360: {x: 0.413403560952780, y: 0.394609710955639}, - 3370: {x: 0.412806556836280, y: 0.394333751747546}, - 3380: {x: 0.412212430714629, y: 0.394057375446090}, + 3360: {x: 0.41340356095278, y: 0.394609710955639}, + 3370: {x: 0.41280655683628, y: 0.394333751747546}, + 3380: {x: 0.412212430714629, y: 0.39405737544609}, 3390: {x: 0.411621168721873, y: 0.393780601495975}, 3400: {x: 0.411032757009395, y: 0.393503449026874}, 3410: {x: 0.410447181747219, y: 0.393225936857565}, 3420: {x: 0.409864429125286, y: 0.392948083500039}, 3430: {x: 0.409284485354692, y: 0.392669907163558}, - 3440: {x: 0.408707336668894, y: 0.392391425758680}, - 3450: {x: 0.408132969324891, y: 0.392112656901240}, + 3440: {x: 0.408707336668894, y: 0.39239142575868}, + 3450: {x: 0.408132969324891, y: 0.39211265690124}, 3460: {x: 0.407561369604368, y: 0.391833617916295}, 3470: {x: 0.406992523814813, y: 0.391554325842027}, 3480: {x: 0.406426418290599, y: 0.391274797433607}, @@ -269,7 +269,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 3670: {x: 0.396173117909235, y: 0.385939193028774}, 3680: {x: 0.395658997751428, y: 0.385658053221589}, 3690: {x: 0.395147338723516, y: 0.385376969987172}, - 3700: {x: 0.394638127971748, y: 0.385095954752460}, + 3700: {x: 0.394638127971748, y: 0.38509595475246}, 3710: {x: 0.394131352687072, y: 0.384815018739475}, 3720: {x: 0.393627000105649, y: 0.384534172968266}, 3730: {x: 0.393125057509339, y: 0.384253428259816}, @@ -281,38 +281,38 @@ const lookup: {[k: number]: {x: number, y: number}} = { 3790: {x: 0.390163305275465, y: 0.382571663935814}, 3800: {x: 0.389677880399752, y: 0.382291913526683}, 3810: {x: 0.389194765461792, y: 0.382012343918955}, - 3820: {x: 0.388713948173757, y: 0.381732964267030}, + 3820: {x: 0.388713948173757, y: 0.38173296426703}, 3830: {x: 0.388235416297444, y: 0.381453783553394}, - 3840: {x: 0.387759157644574, y: 0.381174810591150}, + 3840: {x: 0.387759157644574, y: 0.38117481059115}, 3850: {x: 0.387285160077084, y: 0.380896054026513}, 3860: {x: 0.386813411507402, y: 0.380617522341278}, 3870: {x: 0.386343899898705, y: 0.380339223855255}, 3880: {x: 0.385876613265173, y: 0.380061166728665}, 3890: {x: 0.385411539672215, y: 0.379783358964516}, - 3900: {x: 0.384948667236696, y: 0.379505808410940}, - 3910: {x: 0.384487984127144, y: 0.379228522763500}, + 3900: {x: 0.384948667236696, y: 0.37950580841094}, + 3910: {x: 0.384487984127144, y: 0.3792285227635}, 3920: {x: 0.384029478563943, y: 0.378951509567474}, 3930: {x: 0.383573138819519, y: 0.378674776220096}, - 3940: {x: 0.383118953218507, y: 0.378398329972780}, + 3940: {x: 0.383118953218507, y: 0.37839832997278}, 3950: {x: 0.382666910137919, y: 0.378122177933306}, 3960: {x: 0.382216998007281, y: 0.377846327067982}, - 3970: {x: 0.381769205308780, y: 0.377570784203772}, + 3970: {x: 0.38176920530878, y: 0.377570784203772}, 3980: {x: 0.381323520577382, y: 0.377295556030402}, 3990: {x: 0.380879932400953, y: 0.377020649102431}, 4000: {x: 0.380438429420364, y: 0.376746069841299}, 4010: {x: 0.379999000329579, y: 0.376471824537343}, 4020: {x: 0.379561633875749, y: 0.376197919351794}, - 4030: {x: 0.379126318859280, y: 0.375924360318735}, - 4040: {x: 0.378693044133903, y: 0.375651153347040}, + 4030: {x: 0.37912631885928, y: 0.375924360318735}, + 4040: {x: 0.378693044133903, y: 0.37565115334704}, 4050: {x: 0.378261798606731, y: 0.375378304222286}, - 4060: {x: 0.377832571238301, y: 0.375105818608640}, + 4060: {x: 0.377832571238301, y: 0.37510581860864}, 4070: {x: 0.377405351042622, y: 0.374833702050712}, 4080: {x: 0.376980127087195, y: 0.374561959975396}, - 4090: {x: 0.376556888493043, y: 0.374290597693670}, + 4090: {x: 0.376556888493043, y: 0.37429059769367}, 4100: {x: 0.376135624434723, y: 0.374019620402386}, - 4110: {x: 0.375716324140330, y: 0.373749033186026}, + 4110: {x: 0.37571632414033, y: 0.373749033186026}, 4120: {x: 0.375298976891495, y: 0.373478841018437}, - 4130: {x: 0.374883572023380, y: 0.373209048764539}, + 4130: {x: 0.37488357202338, y: 0.373209048764539}, 4140: {x: 0.374470098924658, y: 0.372939661182012}, 4150: {x: 0.374058547037489, y: 0.372670682922962}, 4160: {x: 0.373648905857491, y: 0.372402118535558}, @@ -322,7 +322,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 4200: {x: 0.372029239990332, y: 0.371332087117875}, 4210: {x: 0.371628996648516, y: 0.371065656785341}, 4220: {x: 0.371230602107658, y: 0.370799665519753}, - 4230: {x: 0.370834046236180, y: 0.370534117185738}, + 4230: {x: 0.37083404623618, y: 0.370534117185738}, 4240: {x: 0.370439318955492, y: 0.370269015556301}, 4250: {x: 0.370046410239904, y: 0.370004364314267}, 4260: {x: 0.369655310116535, y: 0.369740167053704}, @@ -330,12 +330,12 @@ const lookup: {[k: number]: {x: number, y: number}} = { 4280: {x: 0.368878496018416, y: 0.369213148417869}, 4290: {x: 0.368492762361075, y: 0.368950333799466}, 4300: {x: 0.368108797930561, y: 0.368687986678976}, - 4310: {x: 0.367726593016520, y: 0.368426110227320}, + 4310: {x: 0.36772659301652, y: 0.36842611022732}, 4320: {x: 0.367346137960767, y: 0.368164707534776}, - 4330: {x: 0.366967423157160, y: 0.367903781612270}, + 4330: {x: 0.36696742315716, y: 0.36790378161227}, 4340: {x: 0.366590439051477, y: 0.367643335392644}, 4350: {x: 0.366215176141282, y: 0.367383371731904}, - 4360: {x: 0.365841624975790, y: 0.367123893410450}, + 4360: {x: 0.36584162497579, y: 0.36712389341045}, 4370: {x: 0.365469776155733, y: 0.366864903134292}, 4380: {x: 0.365099620333213, y: 0.366606403536243}, 4390: {x: 0.364731148211558, y: 0.366348397177103}, @@ -344,19 +344,19 @@ const lookup: {[k: number]: {x: number, y: number}} = { 4420: {x: 0.363635741850315, y: 0.365577362085137}, 4430: {x: 0.363273912584652, y: 0.365321352889581}, 4440: {x: 0.362913721299567, y: 0.365065848696745}, - 4450: {x: 0.362555159002508, y: 0.364810851659140}, + 4450: {x: 0.362555159002508, y: 0.36481085165914}, 4460: {x: 0.362198216751042, y: 0.364556363865043}, - 4470: {x: 0.361842885652686, y: 0.364302387339550}, + 4470: {x: 0.361842885652686, y: 0.36430238733955}, 4480: {x: 0.361489156864733, y: 0.364048924045607}, - 4490: {x: 0.361137021594080, y: 0.363795975885023}, + 4490: {x: 0.36113702159408, y: 0.363795975885023}, 4500: {x: 0.360786471097048, y: 0.363543544699483}, 4510: {x: 0.360437496679207, y: 0.363291632271526}, - 4520: {x: 0.360090089695190, y: 0.363040240325526}, + 4520: {x: 0.36009008969519, y: 0.363040240325526}, 4530: {x: 0.359744241548512, y: 0.362789370528647}, 4540: {x: 0.359399943691386, y: 0.362539024491787}, - 4550: {x: 0.359057187624530, y: 0.362289203770517}, - 4560: {x: 0.358715964896985, y: 0.362039909865990}, - 4570: {x: 0.358376267105920, y: 0.361791144225849}, + 4550: {x: 0.35905718762453, y: 0.362289203770517}, + 4560: {x: 0.358715964896985, y: 0.36203990986599}, + 4570: {x: 0.35837626710592, y: 0.361791144225849}, 4580: {x: 0.358038085896437, y: 0.361542908245116}, 4590: {x: 0.357701412961382, y: 0.361295203267073}, 4600: {x: 0.357366240041146, y: 0.361048030584121}, @@ -365,7 +365,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 4630: {x: 0.356369639482281, y: 0.360309718484469}, 4640: {x: 0.356040384969197, y: 0.360064686917893}, 4650: {x: 0.355712589879109, y: 0.359820193374621}, - 4660: {x: 0.355386246233490, y: 0.359576238859237}, + 4660: {x: 0.35538624623349, y: 0.359576238859237}, 4670: {x: 0.355061346099949, y: 0.359332824331149}, 4680: {x: 0.354737881592023, y: 0.359089950705356}, 4690: {x: 0.354415844868975, y: 0.358847618853204}, @@ -375,7 +375,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 4730: {x: 0.353141820599519, y: 0.357883725121459}, 4740: {x: 0.352826806775565, y: 0.357644113731333}, 4750: {x: 0.352513174640586, y: 0.357405048467119}, - 4760: {x: 0.352200916668050, y: 0.357166529914816}, + 4760: {x: 0.35220091666805, y: 0.357166529914816}, 4770: {x: 0.351890025375474, y: 0.356928558622422}, 4780: {x: 0.351580493324198, y: 0.356691135100592}, 4790: {x: 0.351272313119179, y: 0.356454259823288}, @@ -385,13 +385,13 @@ const lookup: {[k: number]: {x: number, y: number}} = { 4830: {x: 0.350052964375366, y: 0.355512249389855}, 4840: {x: 0.349751433987532, y: 0.355278121204588}, 4850: {x: 0.349451211979616, y: 0.355044543372218}, - 4860: {x: 0.349152291255876, y: 0.354811516127240}, + 4860: {x: 0.349152291255876, y: 0.35481151612724}, 4870: {x: 0.348854664762465, y: 0.354579039672309}, - 4880: {x: 0.348558325487217, y: 0.354347114178810}, + 4880: {x: 0.348558325487217, y: 0.35434711417881}, 4890: {x: 0.348263266459433, y: 0.354115739787415}, 4900: {x: 0.347969480749661, y: 0.353884916608634}, 4910: {x: 0.347676961469483, y: 0.353654644723354}, - 4920: {x: 0.347385701771300, y: 0.353424924183371}, + 4920: {x: 0.3473857017713, y: 0.353424924183371}, 4930: {x: 0.347095694848114, y: 0.353195755011921}, 4940: {x: 0.346806933933317, y: 0.352967137204187}, 4950: {x: 0.346519412300469, y: 0.352739070727817}, @@ -399,20 +399,20 @@ const lookup: {[k: number]: {x: number, y: number}} = { 4970: {x: 0.345948060174438, y: 0.352284591505058}, 4980: {x: 0.345664216427301, y: 0.352058178560741}, 4990: {x: 0.345381585453779, y: 0.351832316552895}, - 5000: {x: 0.345100160725069, y: 0.351607005318840}, - 5010: {x: 0.344819935751253, y: 0.351382244671250}, + 5000: {x: 0.345100160725069, y: 0.35160700531884}, + 5010: {x: 0.344819935751253, y: 0.35138224467125}, 5020: {x: 0.344540904081086, y: 0.351158034398612}, 5030: {x: 0.344263059301778, y: 0.350934374265679}, 5040: {x: 0.343986395038783, y: 0.350711264013907}, 5050: {x: 0.343710904955591, y: 0.350488703361895}, - 5060: {x: 0.343436582753510, y: 0.350266692005812}, + 5060: {x: 0.34343658275351, y: 0.350266692005812}, 5070: {x: 0.343163422171456, y: 0.350045229619827}, 5080: {x: 0.342891416985746, y: 0.349824315856515}, - 5090: {x: 0.342620561009880, y: 0.349603950347275}, + 5090: {x: 0.34262056100988, y: 0.349603950347275}, 5100: {x: 0.342350848094336, y: 0.349384132702732}, 5110: {x: 0.342082272126361, y: 0.349164862513132}, 5120: {x: 0.341814827029758, y: 0.348946139348737}, - 5130: {x: 0.341548506764680, y: 0.348727962760208}, + 5130: {x: 0.34154850676468, y: 0.348727962760208}, 5140: {x: 0.341283305327422, y: 0.348510332278986}, 5150: {x: 0.341019216750211, y: 0.348293247417664}, 5160: {x: 0.340756235101003, y: 0.348076707670355}, @@ -422,38 +422,38 @@ const lookup: {[k: number]: {x: number, y: number}} = { 5200: {x: 0.339715260382228, y: 0.347215989076895}, 5210: {x: 0.339457725628414, y: 0.347002166689633}, 5220: {x: 0.339201262949099, y: 0.346788886012898}, - 5230: {x: 0.338945866656590, y: 0.346576146421281}, + 5230: {x: 0.33894586665659, y: 0.346576146421281}, 5240: {x: 0.338691531097289, y: 0.346363947273638}, 5250: {x: 0.338438250651491, y: 0.346152287913411}, 5260: {x: 0.338186019733187, y: 0.345941167668941}, - 5270: {x: 0.337934832789862, y: 0.345730585853780}, + 5270: {x: 0.337934832789862, y: 0.34573058585378}, 5280: {x: 0.337684684302299, y: 0.345520541766991}, 5290: {x: 0.337435568784377, y: 0.345311034693453}, 5300: {x: 0.337187480782876, y: 0.345102063904155}, 5310: {x: 0.336940414877284, y: 0.344893628656486}, 5320: {x: 0.336694365679595, y: 0.344685728194521}, 5330: {x: 0.336449327834117, y: 0.344478361749302}, - 5340: {x: 0.336205296017281, y: 0.344271528539120}, + 5340: {x: 0.336205296017281, y: 0.34427152853912}, 5350: {x: 0.335962264937442, y: 0.344065227769781}, - 5360: {x: 0.335720229334691, y: 0.343859458634880}, + 5360: {x: 0.335720229334691, y: 0.34385945863488}, 5370: {x: 0.335479183980662, y: 0.343654220316063}, 5380: {x: 0.335239123678341, y: 0.343449511983289}, 5390: {x: 0.335000043261875, y: 0.343245332795082}, 5400: {x: 0.334761937596386, y: 0.343041681898789}, 5410: {x: 0.334524801577778, y: 0.342838558430825}, 5420: {x: 0.334288630132555, y: 0.342635961516917}, - 5430: {x: 0.334053418217630, y: 0.342433890272344}, - 5440: {x: 0.333819160820140, y: 0.342232343802175}, + 5430: {x: 0.33405341821763, y: 0.342433890272344}, + 5440: {x: 0.33381916082014, y: 0.342232343802175}, 5450: {x: 0.333585852957265, y: 0.342031321201502}, 5460: {x: 0.333353489676037, y: 0.341830821555668}, 5470: {x: 0.333122066053165, y: 0.341630843940493}, 5480: {x: 0.332891577194843, y: 0.341431387422495}, - 5490: {x: 0.332662018236580, y: 0.341232451059109}, + 5490: {x: 0.33266201823658, y: 0.341232451059109}, 5500: {x: 0.332433384343009, y: 0.341034033898904}, - 5510: {x: 0.332205670707715, y: 0.340836134981790}, - 5520: {x: 0.331978872553048, y: 0.340638753339230}, + 5510: {x: 0.332205670707715, y: 0.34083613498179}, + 5520: {x: 0.331978872553048, y: 0.34063875333923}, 5530: {x: 0.331752985129957, y: 0.340441887994442}, - 5540: {x: 0.331528003717800, y: 0.340245537962605}, + 5540: {x: 0.3315280037178, y: 0.340245537962605}, 5550: {x: 0.331303923624177, y: 0.340049702251049}, 5560: {x: 0.331080740184751, y: 0.339854379859459}, 5570: {x: 0.330858448763073, y: 0.339659569780063}, @@ -466,19 +466,19 @@ const lookup: {[k: number]: {x: number, y: number}} = { 5640: {x: 0.329327001623714, y: 0.338310157832671}, 5650: {x: 0.329111682721794, y: 0.338119409137101}, 5660: {x: 0.328897215336285, y: 0.337929163378386}, - 5670: {x: 0.328683595100240, y: 0.337739419484084}, + 5670: {x: 0.32868359510024, y: 0.337739419484084}, 5680: {x: 0.328470817672741, y: 0.337550176376214}, 5690: {x: 0.328258878738739, y: 0.337361432971415}, 5700: {x: 0.328047774008889, y: 0.337173188181098}, - 5710: {x: 0.327837499219387, y: 0.336985440911600}, - 5720: {x: 0.327628050131810, y: 0.336798190064338}, - 5730: {x: 0.327419422532960, y: 0.336611434535950}, + 5710: {x: 0.327837499219387, y: 0.3369854409116}, + 5720: {x: 0.32762805013181, y: 0.336798190064338}, + 5730: {x: 0.32741942253296, y: 0.33661143453595}, 5740: {x: 0.327211612234699, y: 0.336425173218445}, 5750: {x: 0.327004615073793, y: 0.336239404999346}, 5760: {x: 0.326798426911756, y: 0.336054128761828}, 5770: {x: 0.326593043634692, y: 0.335869343384858}, - 5780: {x: 0.326388461153141, y: 0.335685047743330}, - 5790: {x: 0.326184675401921, y: 0.335501240708200}, + 5780: {x: 0.326388461153141, y: 0.33568504774333}, + 5790: {x: 0.326184675401921, y: 0.3355012407082}, 5800: {x: 0.325981682339978, y: 0.335317921146619}, 5810: {x: 0.325779477950234, y: 0.335135087922056}, 5820: {x: 0.325578058239428, y: 0.334952739894432}, @@ -486,15 +486,15 @@ const lookup: {[k: number]: {x: number, y: number}} = { 5840: {x: 0.325177556999802, y: 0.334589494852681}, 5850: {x: 0.324978467602219, y: 0.334408595541759}, 5860: {x: 0.324780147145748, y: 0.334228176834424}, - 5870: {x: 0.324582591753991, y: 0.334048237574680}, + 5870: {x: 0.324582591753991, y: 0.33404823757468}, 5880: {x: 0.324385797573479, y: 0.333868776603699}, 5890: {x: 0.324189760773522, y: 0.333689792759937}, 5900: {x: 0.323994477546069, y: 0.333511284879243}, 5910: {x: 0.323799944105563, y: 0.333333251794967}, - 5920: {x: 0.323606156688797, y: 0.333155692338070}, - 5930: {x: 0.323413111554770, y: 0.332978605337232}, + 5920: {x: 0.323606156688797, y: 0.33315569233807}, + 5930: {x: 0.32341311155477, y: 0.332978605337232}, 5940: {x: 0.323220804984549, y: 0.332801989618949}, - 5950: {x: 0.323029233281125, y: 0.332625844007640}, + 5950: {x: 0.323029233281125, y: 0.33262584400764}, 5960: {x: 0.322838392769274, y: 0.332450167325745}, 5970: {x: 0.322648279795422, y: 0.332274958393826}, 5980: {x: 0.322458890727498, y: 0.332100216030663}, @@ -503,29 +503,29 @@ const lookup: {[k: number]: {x: number, y: number}} = { 6010: {x: 0.321895030958374, y: 0.331578776516748}, 6020: {x: 0.321708501618868, y: 0.331405888584047}, 6030: {x: 0.321522678342801, y: 0.331233461290538}, - 6040: {x: 0.321337557624300, y: 0.331061493446248}, - 6050: {x: 0.321153135978060, y: 0.330889983860056}, + 6040: {x: 0.3213375576243, y: 0.331061493446248}, + 6050: {x: 0.32115313597806, y: 0.330889983860056}, 6060: {x: 0.320969409939206, y: 0.330718931339777}, 6070: {x: 0.320786376063166, y: 0.330548334692243}, 6080: {x: 0.320604030925542, y: 0.330378192723386}, - 6090: {x: 0.320422371121980, y: 0.330208504238316}, - 6100: {x: 0.320241393268040, y: 0.330039268041404}, + 6090: {x: 0.32042237112198, y: 0.330208504238316}, + 6100: {x: 0.32024139326804, y: 0.330039268041404}, 6110: {x: 0.320061093999071, y: 0.329870482936352}, 6120: {x: 0.319881469970083, y: 0.329702147726276}, 6130: {x: 0.319702517855622, y: 0.329534261213777}, 6140: {x: 0.319524234349642, y: 0.329366822201013}, 6150: {x: 0.319346616165388, y: 0.329199829489774}, - 6160: {x: 0.319169660035263, y: 0.329033281881550}, + 6160: {x: 0.319169660035263, y: 0.32903328188155}, 6170: {x: 0.318993362710712, y: 0.328867178177603}, - 6180: {x: 0.318817720962100, y: 0.328701517179031}, - 6190: {x: 0.318642731578585, y: 0.328536297686840}, + 6180: {x: 0.3188177209621, y: 0.328701517179031}, + 6190: {x: 0.318642731578585, y: 0.32853629768684}, 6200: {x: 0.318468391368004, y: 0.328371518502004}, 6210: {x: 0.318294697156752, y: 0.328207178425535}, - 6220: {x: 0.318121645789660, y: 0.328043276258542}, - 6230: {x: 0.317949234129880, y: 0.327879810802294}, + 6220: {x: 0.31812164578966, y: 0.328043276258542}, + 6230: {x: 0.31794923412988, y: 0.327879810802294}, 6240: {x: 0.317777459058767, y: 0.327716780858286}, - 6250: {x: 0.317606317475763, y: 0.327554185228290}, - 6260: {x: 0.317435806298278, y: 0.327392022714420}, + 6250: {x: 0.317606317475763, y: 0.32755418522829}, + 6260: {x: 0.317435806298278, y: 0.32739202271442}, 6270: {x: 0.317265922461579, y: 0.327230292119189}, 6280: {x: 0.317096662918675, y: 0.327068992245565}, 6290: {x: 0.316928024640201, y: 0.326908121897025}, @@ -546,7 +546,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 6440: {x: 0.314470994874025, y: 0.324545798213387}, 6450: {x: 0.314311898787797, y: 0.324391638155726}, 6460: {x: 0.314153375057231, y: 0.324237887409465}, - 6470: {x: 0.313995420951650, y: 0.324084544795669}, + 6470: {x: 0.31399542095165, y: 0.324084544795669}, 6480: {x: 0.313838033755984, y: 0.323931609136792}, 6490: {x: 0.313681210770667, y: 0.323779079256713}, 6500: {x: 0.313524949311538, y: 0.323626953980771}, @@ -554,7 +554,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 6520: {x: 0.313214100311644, y: 0.323323912550187}, 6530: {x: 0.313059507478705, y: 0.323172994053855}, 6540: {x: 0.312905465587415, y: 0.323022475478352}, - 6550: {x: 0.312751972029180, y: 0.322872355656855}, + 6550: {x: 0.31275197202918, y: 0.322872355656855}, 6560: {x: 0.312599024210234, y: 0.322722633424208}, 6570: {x: 0.312446619551539, y: 0.322573307616957}, 6580: {x: 0.312294755488698, y: 0.322424377073377}, @@ -562,12 +562,12 @@ const lookup: {[k: number]: {x: number, y: number}} = { 6600: {x: 0.311992638965609, y: 0.322127697139172}, 6610: {x: 0.311842381448913, y: 0.321979945434026}, 6620: {x: 0.311692654414993, y: 0.321832584363568}, - 6630: {x: 0.311543455371250, y: 0.321685612775174}, + 6630: {x: 0.31154345537125, y: 0.321685612775174}, 6640: {x: 0.311394781839175, y: 0.321539029518129}, 6650: {x: 0.311246631354252, y: 0.321392833443645}, 6660: {x: 0.311099001465878, y: 0.321247023404895}, 6670: {x: 0.310951889737268, y: 0.321101598257035}, - 6680: {x: 0.310805293745370, y: 0.320956556857231}, + 6680: {x: 0.31080529374537, y: 0.320956556857231}, 6690: {x: 0.310659211080778, y: 0.320811898064682}, 6700: {x: 0.310513639347644, y: 0.320667620740643}, 6710: {x: 0.310368576163594, y: 0.320523723748453}, @@ -578,7 +578,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 6760: {x: 0.309650806028835, y: 0.319809904130516}, 6770: {x: 0.309508744853186, y: 0.319668265378839}, 6780: {x: 0.309367175920125, y: 0.319526999062492}, - 6790: {x: 0.309226096952031, y: 0.319386104062240}, + 6790: {x: 0.309226096952031, y: 0.31938610406224}, 6800: {x: 0.309085505684005, y: 0.319245579261116}, 6810: {x: 0.308945399863794, y: 0.319105423544441}, 6820: {x: 0.308805777251706, y: 0.318965635799842}, @@ -587,14 +587,14 @@ const lookup: {[k: number]: {x: number, y: number}} = { 6850: {x: 0.308389786454069, y: 0.318548469309687}, 6860: {x: 0.308252074526075, y: 0.318410142376346}, 6870: {x: 0.308114834793446, y: 0.318272177888381}, - 6880: {x: 0.307978065090227, y: 0.318134574747600}, + 6880: {x: 0.307978065090227, y: 0.3181345747476}, 6890: {x: 0.307841763262478, y: 0.317997331858229}, 6900: {x: 0.307705927168207, y: 0.317860448126929}, 6910: {x: 0.307570554677287, y: 0.317723922462805}, 6920: {x: 0.307435643671386, y: 0.317587753777427}, 6930: {x: 0.307301192043892, y: 0.317451940984837}, 6940: {x: 0.307167197699838, y: 0.317316483001564}, - 6950: {x: 0.307033658555830, y: 0.317181378746639}, + 6950: {x: 0.30703365855583, y: 0.317181378746639}, 6960: {x: 0.306900572539974, y: 0.317046627141604}, 6970: {x: 0.306767937591803, y: 0.316912227110525}, 6980: {x: 0.306635751662207, y: 0.316778177580004}, @@ -613,7 +613,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 7110: {x: 0.304957283155673, y: 0.315066945518451}, 7120: {x: 0.304831174522328, y: 0.314937691733654}, 7130: {x: 0.304705485486314, y: 0.314808772659583}, - 7140: {x: 0.304580214170851, y: 0.314680187264720}, + 7140: {x: 0.304580214170851, y: 0.31468018726472}, 7150: {x: 0.304455358709365, y: 0.314551934520226}, 7160: {x: 0.304330917245426, y: 0.314424013399953}, 7170: {x: 0.304206887932689, y: 0.314296422880449}, @@ -625,29 +625,29 @@ const lookup: {[k: number]: {x: number, y: number}} = { 7230: {x: 0.303471265090658, y: 0.313537765407179}, 7240: {x: 0.303350069972432, y: 0.313412460663896}, 7250: {x: 0.303229272590146, y: 0.313287478431408}, - 7260: {x: 0.303108871185420, y: 0.313162817710744}, + 7260: {x: 0.30310887118542, y: 0.313162817710744}, 7270: {x: 0.302988864009348, y: 0.313038477505671}, - 7280: {x: 0.302869249322440, y: 0.312914456822699}, + 7280: {x: 0.30286924932244, y: 0.312914456822699}, 7290: {x: 0.302750025394565, y: 0.312790754671087}, - 7300: {x: 0.302631190504886, y: 0.312667370062840}, - 7310: {x: 0.302512742941815, y: 0.312544302012720}, + 7300: {x: 0.302631190504886, y: 0.31266737006284}, + 7310: {x: 0.302512742941815, y: 0.31254430201272}, 7320: {x: 0.302394681002945, y: 0.312421549538238}, 7330: {x: 0.302277002994998, y: 0.312299111659668}, 7340: {x: 0.302159707233771, y: 0.312176987400037}, 7350: {x: 0.302042792044074, y: 0.312055175785137}, - 7360: {x: 0.301926255759680, y: 0.311933675843520}, + 7360: {x: 0.30192625575968, y: 0.31193367584352}, 7370: {x: 0.301810096723269, y: 0.311812486606503}, - 7380: {x: 0.301694313286370, y: 0.311691607108169}, + 7380: {x: 0.30169431328637, y: 0.311691607108169}, 7390: {x: 0.301578903809311, y: 0.311571036385366}, - 7400: {x: 0.301463866661161, y: 0.311450773477710}, + 7400: {x: 0.301463866661161, y: 0.31145077347771}, 7410: {x: 0.301349200219681, y: 0.311330817427585}, 7420: {x: 0.301234902871264, y: 0.311211167280144}, - 7430: {x: 0.301120973010891, y: 0.311091822083310}, - 7440: {x: 0.301007409042070, y: 0.310972780887773}, + 7430: {x: 0.301120973010891, y: 0.31109182208331}, + 7440: {x: 0.30100740904207, y: 0.310972780887773}, 7450: {x: 0.300894209376788, y: 0.310854042746996}, 7460: {x: 0.300781372435461, y: 0.310735606717209}, 7470: {x: 0.300668896646876, y: 0.310617471857414}, - 7480: {x: 0.300556780448148, y: 0.310499637229380}, + 7480: {x: 0.300556780448148, y: 0.31049963722938}, 7490: {x: 0.300445022284661, y: 0.310382101897646}, 7500: {x: 0.300333620610026, y: 0.310264864929518}, 7510: {x: 0.300222573886023, y: 0.310147925395073}, @@ -656,28 +656,28 @@ const lookup: {[k: number]: {x: number, y: number}} = { 7540: {x: 0.299891548157161, y: 0.309798882136056}, 7550: {x: 0.299781906015211, y: 0.309683123092391}, 7560: {x: 0.299672611253656, y: 0.309567656874254}, - 7570: {x: 0.299563662382277, y: 0.309452482568300}, + 7570: {x: 0.299563662382277, y: 0.3094524825683}, 7580: {x: 0.299455057918686, y: 0.309337599263943}, 7590: {x: 0.299346796388279, y: 0.309223006053353}, 7600: {x: 0.299238876324188, y: 0.309108702031455}, 7610: {x: 0.299131296267236, y: 0.308994686295927}, 7620: {x: 0.299024054765886, y: 0.308880957947196}, - 7630: {x: 0.298917150376198, y: 0.308767516088440}, + 7630: {x: 0.298917150376198, y: 0.30876751608844}, 7640: {x: 0.298810581661782, y: 0.308654359825579}, 7650: {x: 0.298704347193756, y: 0.308541488267281}, - 7660: {x: 0.298598445550693, y: 0.308428900524950}, + 7660: {x: 0.298598445550693, y: 0.30842890052495}, 7670: {x: 0.298492875318583, y: 0.308316595712732}, 7680: {x: 0.298387635090787, y: 0.308204572947506}, 7690: {x: 0.298282723467988, y: 0.308092831348884}, 7700: {x: 0.298178139058152, y: 0.307981370039207}, 7710: {x: 0.298073880476482, y: 0.307870188143544}, 7720: {x: 0.297969946345376, y: 0.307759284789684}, - 7730: {x: 0.297866335294380, y: 0.307648659108138}, + 7730: {x: 0.29786633529438, y: 0.307648659108138}, 7740: {x: 0.297763045960149, y: 0.307538310232133}, 7750: {x: 0.297660076986403, y: 0.307428237297609}, 7760: {x: 0.297557427023885, y: 0.307318439443216}, 7770: {x: 0.297455094730317, y: 0.307208915810307}, - 7780: {x: 0.297353078770360, y: 0.307099665542941}, + 7780: {x: 0.29735307877036, y: 0.307099665542941}, 7790: {x: 0.297251377815571, y: 0.306990687787871}, 7800: {x: 0.297149990544364, y: 0.306881981694548}, 7810: {x: 0.297048915641968, y: 0.306773546415111}, @@ -690,8 +690,8 @@ const lookup: {[k: number]: {x: number, y: number}} = { 7880: {x: 0.296350028623417, y: 0.306022011590684}, 7890: {x: 0.296251406144149, y: 0.305915712620842}, 7900: {x: 0.296153084500768, y: 0.305809676954518}, - 7910: {x: 0.296055062444450, y: 0.305703903770521}, - 7920: {x: 0.295957338732757, y: 0.305598392250300}, + 7910: {x: 0.29605506244445, y: 0.305703903770521}, + 7920: {x: 0.295957338732757, y: 0.3055983922503}, 7930: {x: 0.295859912129586, y: 0.305493141577943}, 7940: {x: 0.295762781405147, y: 0.305388150940171}, 7950: {x: 0.295665945335915, y: 0.305283419526329}, @@ -700,9 +700,9 @@ const lookup: {[k: number]: {x: number, y: number}} = { 7980: {x: 0.295377192917454, y: 0.304970772561169}, 7990: {x: 0.295281523357868, y: 0.304867069988894}, 8000: {x: 0.295186142428596, y: 0.304763622626521}, - 8010: {x: 0.295091048942950, y: 0.304660429679051}, + 8010: {x: 0.29509104894295, y: 0.304660429679051}, 8020: {x: 0.294996241720257, y: 0.304557490354083}, - 8030: {x: 0.294901719585820, y: 0.304454803861798}, + 8030: {x: 0.29490171958582, y: 0.304454803861798}, 8040: {x: 0.294807481370887, y: 0.304352369414959}, 8050: {x: 0.294713525912612, y: 0.304250186228902}, 8060: {x: 0.294619852054023, y: 0.304148253521536}, @@ -715,7 +715,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 8130: {x: 0.293971923866841, y: 0.303441672814619}, 8140: {x: 0.293880461848177, y: 0.303341716087973}, 8150: {x: 0.293789271275103, y: 0.303242002915899}, - 8160: {x: 0.293698351047513, y: 0.303142532541690}, + 8160: {x: 0.293698351047513, y: 0.30314253254169}, 8170: {x: 0.293607700070806, y: 0.303043304211147}, 8180: {x: 0.293517317255857, y: 0.302944317172572}, 8190: {x: 0.293427201518978, y: 0.302845570676761}, @@ -724,15 +724,15 @@ const lookup: {[k: number]: {x: number, y: number}} = { 8220: {x: 0.293158446020866, y: 0.302550766991182}, 8230: {x: 0.293069387867134, y: 0.302452975224084}, 8240: {x: 0.292980591453561, y: 0.302355420290948}, - 8250: {x: 0.292892055728450, y: 0.302258101457411}, + 8250: {x: 0.29289205572845, y: 0.302258101457411}, 8260: {x: 0.292803779645329, y: 0.302161017991565}, 8270: {x: 0.292715762162918, y: 0.302064169163948}, - 8280: {x: 0.292628002245100, y: 0.301967554247537}, + 8280: {x: 0.2926280022451, y: 0.301967554247537}, 8290: {x: 0.292540498860893, y: 0.301871172517742}, 8300: {x: 0.292453250984416, y: 0.301775023252403}, 8310: {x: 0.292366257594866, y: 0.301679105731779}, 8320: {x: 0.292279517676482, y: 0.301583419238544}, - 8330: {x: 0.292193030218519, y: 0.301487963057780}, + 8330: {x: 0.292193030218519, y: 0.30148796305778}, 8340: {x: 0.292106794215219, y: 0.301392736476973}, 8350: {x: 0.292020808665784, y: 0.301297738786003}, 8360: {x: 0.291935072574342, y: 0.301202969277141}, @@ -755,46 +755,46 @@ const lookup: {[k: number]: {x: number, y: number}} = { 8530: {x: 0.290514784410223, y: 0.299626125143983}, 8540: {x: 0.290433373722268, y: 0.299535344576503}, 8550: {x: 0.290352194470698, y: 0.299444779189643}, - 8560: {x: 0.290271245752510, y: 0.299354428322098}, + 8560: {x: 0.29027124575251, y: 0.299354428322098}, 8570: {x: 0.290190526669069, y: 0.299264291314815}, 8580: {x: 0.290110036326088, y: 0.299174367510987}, 8590: {x: 0.290029773833599, y: 0.299084656256047}, 8600: {x: 0.289949738305931, y: 0.298995156897657}, - 8610: {x: 0.289869928861685, y: 0.298905868785710}, - 8620: {x: 0.289790344623710, y: 0.298816791272313}, - 8630: {x: 0.289710984719080, y: 0.298727923711788}, + 8610: {x: 0.289869928861685, y: 0.29890586878571}, + 8620: {x: 0.28979034462371, y: 0.298816791272313}, + 8630: {x: 0.28971098471908, y: 0.298727923711788}, 8640: {x: 0.289631848279069, y: 0.298639265460663}, 8650: {x: 0.289552934439125, y: 0.298550815877663}, 8660: {x: 0.289474242338851, y: 0.298462574323709}, 8670: {x: 0.289395771121979, y: 0.298374540161904}, 8680: {x: 0.289317519936349, y: 0.298286712757532}, - 8690: {x: 0.289239487933881, y: 0.298199091478050}, + 8690: {x: 0.289239487933881, y: 0.29819909147805}, 8700: {x: 0.289161674270557, y: 0.298111675693081}, 8710: {x: 0.289084078106396, y: 0.298024464774405}, 8720: {x: 0.289006698605431, y: 0.297937458095957}, 8730: {x: 0.288929534935688, y: 0.297850655033815}, - 8740: {x: 0.288852586269164, y: 0.297764054966200}, - 8750: {x: 0.288775851781800, y: 0.297677657273462}, - 8760: {x: 0.288699330653466, y: 0.297591461338080}, - 8770: {x: 0.288623022067933, y: 0.297505466544650}, - 8780: {x: 0.288546925212856, y: 0.297419672279880}, + 8740: {x: 0.288852586269164, y: 0.2977640549662}, + 8750: {x: 0.2887758517818, y: 0.297677657273462}, + 8760: {x: 0.288699330653466, y: 0.29759146133808}, + 8770: {x: 0.288623022067933, y: 0.29750546654465}, + 8780: {x: 0.288546925212856, y: 0.29741967227988}, 8790: {x: 0.288471039279747, y: 0.297334077932587}, - 8800: {x: 0.288395363463960, y: 0.297248682893685}, + 8800: {x: 0.28839536346396, y: 0.297248682893685}, 8810: {x: 0.288319896964663, y: 0.297163486556181}, 8820: {x: 0.288244638984821, y: 0.297078488315167}, 8830: {x: 0.288169588731173, y: 0.296993687567818}, 8840: {x: 0.288094745414214, y: 0.296909083713378}, 8850: {x: 0.288020108248168, y: 0.296824676153158}, - 8860: {x: 0.287945676450972, y: 0.296740464290530}, + 8860: {x: 0.287945676450972, y: 0.29674046429053}, 8870: {x: 0.287871449244258, y: 0.296656447530918}, 8880: {x: 0.287797425853324, y: 0.296572625281792}, 8890: {x: 0.287723605507121, y: 0.296488996952662}, - 8900: {x: 0.287649987438230, y: 0.296405561955071}, + 8900: {x: 0.28764998743823, y: 0.296405561955071}, 8910: {x: 0.287576570882842, y: 0.296322319702588}, 8920: {x: 0.287503355080738, y: 0.296239269610805}, 8930: {x: 0.287430339275271, y: 0.296156411097322}, - 8940: {x: 0.287357522713342, y: 0.296073743581750}, - 8950: {x: 0.287284904645384, y: 0.295991266485700}, + 8940: {x: 0.287357522713342, y: 0.29607374358175}, + 8950: {x: 0.287284904645384, y: 0.2959912664857}, 8960: {x: 0.287212484325343, y: 0.295908979232774}, 8970: {x: 0.287140261010655, y: 0.295826881248565}, 8980: {x: 0.287068233962231, y: 0.295744971960642}, @@ -802,15 +802,15 @@ const lookup: {[k: number]: {x: number, y: number}} = { 9000: {x: 0.286924765725065, y: 0.295581717193809}, 9010: {x: 0.286853323075337, y: 0.295500370579886}, 9020: {x: 0.286782073769861, y: 0.295419210392211}, - 9030: {x: 0.286711017086630, y: 0.295338236068163}, + 9030: {x: 0.28671101708663, y: 0.295338236068163}, 9040: {x: 0.286640152306992, y: 0.295257447047059}, - 9050: {x: 0.286569478715640, y: 0.295176842770153}, + 9050: {x: 0.28656947871564, y: 0.295176842770153}, 9060: {x: 0.286498995600591, y: 0.295096422680627}, 9070: {x: 0.286428702253164, y: 0.295016186223586}, 9080: {x: 0.286358597967967, y: 0.294936132846049}, 9090: {x: 0.286288682042877, y: 0.294856261996946}, - 9100: {x: 0.286218953779022, y: 0.294776573127110}, - 9110: {x: 0.286149412480764, y: 0.294697065689270}, + 9100: {x: 0.286218953779022, y: 0.29477657312711}, + 9110: {x: 0.286149412480764, y: 0.29469706568927}, 9120: {x: 0.286080057455679, y: 0.294617739138044}, 9130: {x: 0.286010888014543, y: 0.294538592929935}, 9140: {x: 0.285941903471314, y: 0.294459626523322}, @@ -824,7 +824,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 9220: {x: 0.285396602161005, y: 0.293834303744401}, 9230: {x: 0.285329251343354, y: 0.293756931484977}, 9240: {x: 0.285262078734374, y: 0.293679733712708}, - 9250: {x: 0.285195083681890, y: 0.293602709906207}, + 9250: {x: 0.28519508368189, y: 0.293602709906207}, 9260: {x: 0.285128265536713, y: 0.293525859545888}, 9270: {x: 0.285061623652614, y: 0.293449182113962}, 9280: {x: 0.284995157386314, y: 0.293372677094427}, @@ -832,17 +832,17 @@ const lookup: {[k: number]: {x: number, y: number}} = { 9300: {x: 0.284862749148637, y: 0.293220182237442}, 9310: {x: 0.284796805905298, y: 0.293144191376881}, 9320: {x: 0.284731035735806, y: 0.293068370882479}, - 9330: {x: 0.284665438011384, y: 0.292992720247090}, + 9330: {x: 0.284665438011384, y: 0.29299272024709}, 9340: {x: 0.284600012106111, y: 0.292917238965319}, 9350: {x: 0.284534757396907, y: 0.292841926533518}, - 9360: {x: 0.284469673263512, y: 0.292766782449780}, - 9370: {x: 0.284404759088479, y: 0.292691806213930}, - 9380: {x: 0.284340014257150, y: 0.292616997327523}, - 9390: {x: 0.284275438157650, y: 0.292542355293834}, + 9360: {x: 0.284469673263512, y: 0.29276678244978}, + 9370: {x: 0.284404759088479, y: 0.29269180621393}, + 9380: {x: 0.28434001425715, y: 0.292616997327523}, + 9390: {x: 0.28427543815765, y: 0.292542355293834}, 9400: {x: 0.284211030180865, y: 0.292467879617856}, 9410: {x: 0.284146789720432, y: 0.292393569806291}, 9420: {x: 0.284082716172722, y: 0.292319425367545}, - 9430: {x: 0.284018808936825, y: 0.292245445811720}, + 9430: {x: 0.284018808936825, y: 0.29224544581172}, 9440: {x: 0.283955067414537, y: 0.292171630650613}, 9450: {x: 0.283891491010347, y: 0.292097979397706}, 9460: {x: 0.283828079131417, y: 0.292024491568159}, @@ -858,14 +858,14 @@ const lookup: {[k: number]: {x: number, y: number}} = { 9560: {x: 0.283202880693181, y: 0.291298496082403}, 9570: {x: 0.283141240216422, y: 0.291226774385047}, 9580: {x: 0.283079757354586, y: 0.291155210421883}, - 9590: {x: 0.283018431548370, y: 0.291083803729378}, + 9590: {x: 0.28301843154837, y: 0.291083803729378}, 9600: {x: 0.282957262240958, y: 0.291012553845598}, 9610: {x: 0.282896248878006, y: 0.290941460310199}, 9620: {x: 0.282835390907627, y: 0.290870522664427}, 9630: {x: 0.282774687780384, y: 0.290799740451108}, 9640: {x: 0.282714138949271, y: 0.290729113214642}, 9650: {x: 0.282653743869704, y: 0.290658640501001}, - 9660: {x: 0.282593501999504, y: 0.290588321857720}, + 9660: {x: 0.282593501999504, y: 0.29058832185772}, 9670: {x: 0.282533412798892, y: 0.290518156833893}, 9680: {x: 0.282473475730468, y: 0.290448144980167}, 9690: {x: 0.282413690259205, y: 0.290378285848737}, @@ -881,18 +881,18 @@ const lookup: {[k: number]: {x: number, y: number}} = { 9790: {x: 0.281824057354519, y: 0.289687996838722}, 9800: {x: 0.281765904805865, y: 0.289619788534379}, 9810: {x: 0.281707897614492, y: 0.289551727697349}, - 9820: {x: 0.281650035275090, y: 0.289483813899440}, + 9820: {x: 0.28165003527509, y: 0.28948381389944}, 9830: {x: 0.281592317284549, y: 0.289416046713933}, 9840: {x: 0.281534743141951, y: 0.289348425715574}, - 9850: {x: 0.281477312348560, y: 0.289280950480568}, + 9850: {x: 0.28147731234856, y: 0.289280950480568}, 9860: {x: 0.281420024407811, y: 0.289213620586576}, 9870: {x: 0.281362878825294, y: 0.289146435612708}, 9880: {x: 0.281305875108749, y: 0.289079395139519}, 9890: {x: 0.281249012768051, y: 0.289012498749002}, 9900: {x: 0.281192291315202, y: 0.288945746024584}, - 9910: {x: 0.281135710264316, y: 0.288879136551120}, + 9910: {x: 0.281135710264316, y: 0.28887913655112}, 9920: {x: 0.281079269131611, y: 0.288812669914892}, - 9930: {x: 0.281022967435400, y: 0.288746345703594}, + 9930: {x: 0.2810229674354, y: 0.288746345703594}, 9940: {x: 0.280966804696075, y: 0.288680163506339}, 9950: {x: 0.280910780436102, y: 0.288614122913642}, 9960: {x: 0.280854894180004, y: 0.288548223517426}, @@ -903,24 +903,24 @@ const lookup: {[k: number]: {x: number, y: number}} = { 10010: {x: 0.280577516458952, y: 0.288220830298306}, 10020: {x: 0.280522448355212, y: 0.288155769589205}, 10030: {x: 0.280467514983842, y: 0.288090847258873}, - 10040: {x: 0.280412715885480, y: 0.288026062910267}, + 10040: {x: 0.28041271588548, y: 0.288026062910267}, 10050: {x: 0.280358050602727, y: 0.287961416147704}, - 10060: {x: 0.280303518680147, y: 0.287896906576850}, - 10070: {x: 0.280249119664247, y: 0.287832533804720}, + 10060: {x: 0.280303518680147, y: 0.28789690657685}, + 10070: {x: 0.280249119664247, y: 0.28783253380472}, 10080: {x: 0.280194853103475, y: 0.287768297439671}, 10090: {x: 0.280140718548207, y: 0.287704197091395}, - 10100: {x: 0.280086715550736, y: 0.287640232370920}, + 10100: {x: 0.280086715550736, y: 0.28764023237092}, 10110: {x: 0.280032843665265, y: 0.287576402890597}, 10120: {x: 0.279979102447896, y: 0.287512708264105}, - 10130: {x: 0.279925491456620, y: 0.287449148106435}, + 10130: {x: 0.27992549145662, y: 0.287449148106435}, 10140: {x: 0.279872010251307, y: 0.287385722033893}, 10150: {x: 0.279818658393699, y: 0.287322429664096}, 10160: {x: 0.279765435447398, y: 0.287259270615958}, 10170: {x: 0.279712340977858, y: 0.287196244509697}, - 10180: {x: 0.279659374552374, y: 0.287133350966820}, + 10180: {x: 0.279659374552374, y: 0.28713335096682}, 10190: {x: 0.279606535740076, y: 0.287070589610126}, 10200: {x: 0.279553824111915, y: 0.287007960063697}, - 10210: {x: 0.279501239240660, y: 0.286945461952894}, + 10210: {x: 0.27950123924066, y: 0.286945461952894}, 10220: {x: 0.279448780700881, y: 0.286883094904353}, 10230: {x: 0.279396448068949, y: 0.286820858545978}, 10240: {x: 0.279344240923019, y: 0.286758752506941}, @@ -934,7 +934,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 10320: {x: 0.278931051213145, y: 0.286266551605066}, 10330: {x: 0.278879954748767, y: 0.286205601959187}, 10340: {x: 0.278828979648306, y: 0.286144778992913}, - 10350: {x: 0.278778125509000, y: 0.286084082349025}, + 10350: {x: 0.278778125509, y: 0.286084082349025}, 10360: {x: 0.278727391929767, y: 0.286023511671516}, 10370: {x: 0.278676778511201, y: 0.285963066605585}, 10380: {x: 0.278626284855561, y: 0.285902746797628}, @@ -948,27 +948,27 @@ const lookup: {[k: number]: {x: number, y: number}} = { 10460: {x: 0.278226599890438, y: 0.285424655581824}, 10470: {x: 0.278177166473744, y: 0.285365447379593}, 10480: {x: 0.278127848932433, y: 0.285306360964418}, - 10490: {x: 0.278078646886560, y: 0.285247395995619}, + 10490: {x: 0.27807864688656, y: 0.285247395995619}, 10500: {x: 0.278029559957749, y: 0.285188552133665}, 10510: {x: 0.277980587769188, y: 0.285129829040174}, 10520: {x: 0.277931729945617, y: 0.285071226377896}, 10530: {x: 0.277882986113327, y: 0.285012743810724}, 10540: {x: 0.277834355900146, y: 0.284954381003681}, 10550: {x: 0.277785838935434, y: 0.284896137622915}, - 10560: {x: 0.277737434850077, y: 0.284838013335700}, - 10570: {x: 0.277689143276476, y: 0.284780007810430}, - 10580: {x: 0.277640963848543, y: 0.284722120716610}, + 10560: {x: 0.277737434850077, y: 0.2848380133357}, + 10570: {x: 0.277689143276476, y: 0.28478000781043}, + 10580: {x: 0.277640963848543, y: 0.28472212071661}, 10590: {x: 0.277592896201691, y: 0.284664351724861}, 10600: {x: 0.277544939972829, y: 0.284606700506907}, 10610: {x: 0.277497094800353, y: 0.284549166735576}, - 10620: {x: 0.277449360324140, y: 0.284491750084795}, + 10620: {x: 0.27744936032414, y: 0.284491750084795}, 10630: {x: 0.277401736185538, y: 0.284434450229585}, 10640: {x: 0.277354222027362, y: 0.284377266846058}, 10650: {x: 0.277306817493887, y: 0.284320199611411}, 10660: {x: 0.277259522230838, y: 0.284263248203924}, 10670: {x: 0.277212335885385, y: 0.284206412302958}, 10680: {x: 0.277165258106137, y: 0.284149691588943}, - 10690: {x: 0.277118288543130, y: 0.284093085743384}, + 10690: {x: 0.27711828854313, y: 0.284093085743384}, 10700: {x: 0.277071426847826, y: 0.284036594448849}, 10710: {x: 0.277024672673105, y: 0.283980217388971}, 10720: {x: 0.276978025673255, y: 0.283923954248441}, @@ -977,7 +977,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 10750: {x: 0.276838724286814, y: 0.283755845205629}, 10760: {x: 0.276792502557287, y: 0.283700034610422}, 10770: {x: 0.276746386294983, y: 0.283644336373752}, - 10780: {x: 0.276700375162506, y: 0.283588750186580}, + 10780: {x: 0.276700375162506, y: 0.28358875018658}, 10790: {x: 0.276654468823826, y: 0.283533275740893}, 10800: {x: 0.276608666944266, y: 0.283477912729711}, 10810: {x: 0.276562969190499, y: 0.283422660847074}, @@ -987,7 +987,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 10850: {x: 0.276381212813745, y: 0.283202758518398}, 10860: {x: 0.276336030735854, y: 0.283148057724654}, 10870: {x: 0.276290950811787, y: 0.283093466244981}, - 10880: {x: 0.276245972717486, y: 0.283038983780480}, + 10880: {x: 0.276245972717486, y: 0.28303898378048}, 10890: {x: 0.276201096130193, y: 0.282984610033243}, 10900: {x: 0.276156320728442, y: 0.282930344706354}, 10910: {x: 0.276111646192053, y: 0.282876187503881}, @@ -1006,46 +1006,46 @@ const lookup: {[k: number]: {x: number, y: number}} = { 11040: {x: 0.275539912588917, y: 0.282181850317292}, 11050: {x: 0.275496617254717, y: 0.282129176327489}, 11060: {x: 0.275453418133457, y: 0.282076606139012}, - 11070: {x: 0.275410314924724, y: 0.282024139471220}, + 11070: {x: 0.275410314924724, y: 0.28202413947122}, 11080: {x: 0.275367307329296, y: 0.281971776044394}, - 11090: {x: 0.275324395049130, y: 0.281919515579741}, + 11090: {x: 0.27532439504913, y: 0.281919515579741}, 11100: {x: 0.275281577787359, y: 0.281867357799386}, 11110: {x: 0.275238855248283, y: 0.281815302426367}, - 11120: {x: 0.275196227137370, y: 0.281763349184636}, + 11120: {x: 0.27519622713737, y: 0.281763349184636}, 11130: {x: 0.275153693161244, y: 0.281711497799054}, 11140: {x: 0.275111253027683, y: 0.281659747995386}, 11150: {x: 0.275068906445614, y: 0.281608099500299}, 11160: {x: 0.275026653125104, y: 0.281556552041359}, 11170: {x: 0.274984492777361, y: 0.281505105347028}, - 11180: {x: 0.274942425114720, y: 0.281453759146658}, - 11190: {x: 0.274900449850647, y: 0.281402513170490}, + 11180: {x: 0.27494242511472, y: 0.281453759146658}, + 11190: {x: 0.274900449850647, y: 0.28140251317049}, 11200: {x: 0.274858566699725, y: 0.281351367149652}, 11210: {x: 0.274816775377656, y: 0.281300320816152}, 11220: {x: 0.274775075601253, y: 0.281249373902877}, 11230: {x: 0.274733467088432, y: 0.281198526143592}, - 11240: {x: 0.274691949558210, y: 0.281147777272930}, + 11240: {x: 0.27469194955821, y: 0.28114777727293}, 11250: {x: 0.274650522730702, y: 0.281097127026396}, - 11260: {x: 0.274609186327110, y: 0.281046575140359}, + 11260: {x: 0.27460918632711, y: 0.281046575140359}, 11270: {x: 0.274567940069723, y: 0.280996121352052}, 11280: {x: 0.274526783681909, y: 0.280945765399568}, 11290: {x: 0.274485716888111, y: 0.280895507021853}, - 11300: {x: 0.274444739413842, y: 0.280845345958710}, + 11300: {x: 0.274444739413842, y: 0.28084534595871}, 11310: {x: 0.274403850985682, y: 0.280795281950789}, 11320: {x: 0.274363051331268, y: 0.280745314739587}, 11330: {x: 0.274322340179295, y: 0.280695444067445}, 11340: {x: 0.274281717259505, y: 0.280645669677546}, - 11350: {x: 0.274241182302690, y: 0.280595991313908}, + 11350: {x: 0.27424118230269, y: 0.280595991313908}, 11360: {x: 0.274200735040677, y: 0.280546408721385}, 11370: {x: 0.274160375206335, y: 0.280496921645661}, 11380: {x: 0.274120102533558, y: 0.280447529833248}, 11390: {x: 0.274079916757271, y: 0.280398233031483}, 11400: {x: 0.274039817613418, y: 0.280349030988528}, - 11410: {x: 0.273999804838960, y: 0.280299923453359}, - 11420: {x: 0.273959878171870, y: 0.280250910175772}, - 11430: {x: 0.273920037351130, y: 0.280201990906373}, + 11410: {x: 0.27399980483896, y: 0.280299923453359}, + 11420: {x: 0.27395987817187, y: 0.280250910175772}, + 11430: {x: 0.27392003735113, y: 0.280201990906373}, 11440: {x: 0.273880282116723, y: 0.280153165396579}, 11450: {x: 0.273840612209631, y: 0.280104433398616}, - 11460: {x: 0.273801027371830, y: 0.280055794665510}, + 11460: {x: 0.27380102737183, y: 0.28005579466551}, 11470: {x: 0.273761527346283, y: 0.280007248951091}, 11480: {x: 0.273722111876941, y: 0.279958796009987}, 11490: {x: 0.273682780708732, y: 0.279910435597618}, @@ -1056,7 +1056,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 11540: {x: 0.273487380525239, y: 0.279670012961871}, 11550: {x: 0.273448549861657, y: 0.279622202629937}, 11560: {x: 0.273409801740777, y: 0.279574483136711}, - 11570: {x: 0.273371135915210, y: 0.279526854243870}, + 11570: {x: 0.27337113591521, y: 0.27952685424387}, 11580: {x: 0.273332552138504, y: 0.279479315713862}, 11590: {x: 0.273294050165145, y: 0.279431867309899}, 11600: {x: 0.273255629750553, y: 0.279384508795959}, @@ -1078,14 +1078,14 @@ const lookup: {[k: number]: {x: number, y: number}} = { 11760: {x: 0.272651799077334, y: 0.278638807936891}, 11770: {x: 0.272614728623504, y: 0.278592942168089}, 11780: {x: 0.272577735480592, y: 0.278547162168285}, - 11790: {x: 0.272540819420939, y: 0.278501467715440}, + 11790: {x: 0.272540819420939, y: 0.27850146771544}, 11800: {x: 0.272503980217742, y: 0.278455858588224}, 11810: {x: 0.272467217645041, y: 0.278410334566013}, 11820: {x: 0.272430531477724, y: 0.278364895428887}, 11830: {x: 0.272393921491516, y: 0.278319540957628}, 11840: {x: 0.272357387462979, y: 0.278274270933717}, 11850: {x: 0.272320929169506, y: 0.278229085139331}, - 11860: {x: 0.272284546389322, y: 0.278183983357340}, + 11860: {x: 0.272284546389322, y: 0.27818398335734}, 11870: {x: 0.272248238901473, y: 0.278138965371308}, 11880: {x: 0.272212006485828, y: 0.278094030965484}, 11890: {x: 0.272175848923075, y: 0.278049179924807}, @@ -1101,25 +1101,25 @@ const lookup: {[k: number]: {x: number, y: number}} = { 11990: {x: 0.271818342532974, y: 0.277605207772966}, 12000: {x: 0.271782994107569, y: 0.277561259748537}, 12010: {x: 0.271747717965761, y: 0.277517392561125}, - 12020: {x: 0.271712513898540, y: 0.277473606004366}, + 12020: {x: 0.27171251389854, y: 0.277473606004366}, 12030: {x: 0.271677381697667, y: 0.277429899872545}, 12040: {x: 0.271642321155663, y: 0.277386273960597}, 12050: {x: 0.271607332065816, y: 0.277342728064107}, - 12060: {x: 0.271572414222168, y: 0.277299261979300}, + 12060: {x: 0.271572414222168, y: 0.2772992619793}, 12070: {x: 0.271537567419519, y: 0.277255875503048}, 12080: {x: 0.271502791453418, y: 0.277212568432862}, - 12090: {x: 0.271468086120163, y: 0.277169340566890}, + 12090: {x: 0.271468086120163, y: 0.27716934056689}, 12100: {x: 0.271433451216798, y: 0.277126191703915}, 12110: {x: 0.271398886541106, y: 0.277083121643356}, 12120: {x: 0.271364391891612, y: 0.277040130185262}, 12130: {x: 0.271329967067575, y: 0.276997217130308}, - 12140: {x: 0.271295611868982, y: 0.276954382279800}, + 12140: {x: 0.271295611868982, y: 0.2769543822798}, 12150: {x: 0.271261326096555, y: 0.276911625435664}, - 12160: {x: 0.271227109551738, y: 0.276868946400450}, + 12160: {x: 0.271227109551738, y: 0.27686894640045}, 12170: {x: 0.271192962036696, y: 0.276826344977328}, 12180: {x: 0.271158883354318, y: 0.276783820970085}, 12190: {x: 0.271124873308204, y: 0.276741374183121}, - 12200: {x: 0.271090931702670, y: 0.276699004421453}, + 12200: {x: 0.27109093170267, y: 0.276699004421453}, 12210: {x: 0.271057058342741, y: 0.276656711490705}, 12220: {x: 0.271023253034148, y: 0.276614495197111}, 12230: {x: 0.270989515583328, y: 0.276572355347512}, @@ -1133,38 +1133,38 @@ const lookup: {[k: number]: {x: number, y: number}} = { 12310: {x: 0.270722035858623, y: 0.276237965553711}, 12320: {x: 0.270688900531952, y: 0.276196505106837}, 12330: {x: 0.270655831165444, y: 0.276155119205228}, - 12340: {x: 0.270622827573076, y: 0.276113807662250}, + 12340: {x: 0.270622827573076, y: 0.27611380766225}, 12350: {x: 0.270589889569494, y: 0.276072570291852}, 12360: {x: 0.270557016970008, y: 0.276031406908559}, - 12370: {x: 0.270524209590590, y: 0.275990317327473}, + 12370: {x: 0.27052420959059, y: 0.275990317327473}, 12380: {x: 0.270491467247872, y: 0.275949301364269}, 12390: {x: 0.270458789759142, y: 0.275908358835196}, 12400: {x: 0.270426176942343, y: 0.275867489557074}, 12410: {x: 0.270393628616067, y: 0.275826693347289}, 12420: {x: 0.270361144599554, y: 0.275785970023793}, 12430: {x: 0.270328724712692, y: 0.275745319405104}, - 12440: {x: 0.270296368776009, y: 0.275704741310300}, - 12450: {x: 0.270264076610672, y: 0.275664235559020}, - 12460: {x: 0.270231848038489, y: 0.275623801971460}, + 12440: {x: 0.270296368776009, y: 0.2757047413103}, + 12450: {x: 0.270264076610672, y: 0.27566423555902}, + 12460: {x: 0.270231848038489, y: 0.27562380197146}, 12470: {x: 0.270199682881898, y: 0.275583440368375}, - 12480: {x: 0.270167580963971, y: 0.275543150571070}, + 12480: {x: 0.270167580963971, y: 0.27554315057107}, 12490: {x: 0.270135542108408, y: 0.275502932401403}, 12500: {x: 0.270103566139535, y: 0.275462785681784}, - 12510: {x: 0.270071652882303, y: 0.275422710235170}, + 12510: {x: 0.270071652882303, y: 0.27542271023517}, 12520: {x: 0.270039802162282, y: 0.275382705885063}, - 12530: {x: 0.270008013805662, y: 0.275342772455510}, - 12540: {x: 0.269976287639245, y: 0.275302909771100}, + 12530: {x: 0.270008013805662, y: 0.27534277245551}, + 12540: {x: 0.269976287639245, y: 0.2753029097711}, 12550: {x: 0.269944623490451, y: 0.275263117656963}, 12560: {x: 0.269913021187308, y: 0.275223395938766}, - 12570: {x: 0.269881480558450, y: 0.275183744442712}, + 12570: {x: 0.26988148055845, y: 0.275183744442712}, 12580: {x: 0.269850001433119, y: 0.275144162995541}, 12590: {x: 0.269818583641157, y: 0.275104651424524}, 12600: {x: 0.269787227013009, y: 0.275065209557461}, 12610: {x: 0.269755931379714, y: 0.275025837222683}, - 12620: {x: 0.269724696572910, y: 0.274986534249048}, + 12620: {x: 0.26972469657291, y: 0.274986534249048}, 12630: {x: 0.269693522424824, y: 0.274947300465937}, 12640: {x: 0.269662408768274, y: 0.274908135703255}, - 12650: {x: 0.269631355436667, y: 0.274869039791430}, + 12650: {x: 0.269631355436667, y: 0.27486903979143}, 12660: {x: 0.269600362263992, y: 0.274830012561405}, 12670: {x: 0.269569429084823, y: 0.274791053844646}, 12680: {x: 0.269538555734313, y: 0.274752163473131}, @@ -1174,22 +1174,22 @@ const lookup: {[k: number]: {x: number, y: number}} = { 12720: {x: 0.269415657342087, y: 0.274597282097033}, 12730: {x: 0.269385080682298, y: 0.274558730949341}, 12740: {x: 0.269354562874131, y: 0.274520247149494}, - 12750: {x: 0.269324103756730, y: 0.274481830533028}, + 12750: {x: 0.26932410375673, y: 0.274481830533028}, 12760: {x: 0.269293703169804, y: 0.274443480935982}, 12770: {x: 0.269263360953615, y: 0.274405198194895}, 12780: {x: 0.269233076948985, y: 0.274366982146803}, 12790: {x: 0.269202850997288, y: 0.274328832629237}, - 12800: {x: 0.269172682940450, y: 0.274290749480221}, + 12800: {x: 0.26917268294045, y: 0.274290749480221}, 12810: {x: 0.269142572620946, y: 0.274252732538276}, 12820: {x: 0.269112519881797, y: 0.274214781642409}, 12830: {x: 0.269082524566571, y: 0.274176896632118}, 12840: {x: 0.269052586519376, y: 0.274139077347387}, - 12850: {x: 0.269022705584860, y: 0.274101323628687}, - 12860: {x: 0.268992881608211, y: 0.274063635316970}, + 12850: {x: 0.26902270558486, y: 0.274101323628687}, + 12860: {x: 0.268992881608211, y: 0.27406363531697}, 12870: {x: 0.268963114435149, y: 0.274026012253673}, 12880: {x: 0.268933403911931, y: 0.273988454280711}, - 12890: {x: 0.268903749885341, y: 0.273950961240480}, - 12900: {x: 0.268874152202697, y: 0.273913532975850}, + 12890: {x: 0.268903749885341, y: 0.27395096124048}, + 12900: {x: 0.268874152202697, y: 0.27391353297585}, 12910: {x: 0.268844610711839, y: 0.273876169330168}, 12920: {x: 0.268815125261133, y: 0.273838870147253}, 12930: {x: 0.268785695699469, y: 0.273801635271398}, @@ -1198,30 +1198,30 @@ const lookup: {[k: number]: {x: number, y: number}} = { 12960: {x: 0.268697740845399, y: 0.273690314936154}, 12970: {x: 0.268668533339156, y: 0.273653335740835}, 12980: {x: 0.268639380974156, y: 0.273616420081054}, - 12990: {x: 0.268610283602376, y: 0.273579567803900}, + 12990: {x: 0.268610283602376, y: 0.2735795678039}, 13000: {x: 0.268581241076301, y: 0.273542778756921}, 13010: {x: 0.268552253248921, y: 0.273506052788124}, 13020: {x: 0.268523319973729, y: 0.273469389745973}, 13030: {x: 0.268494441104718, y: 0.273432789479388}, 13040: {x: 0.268465616496381, y: 0.273396251837743}, - 13050: {x: 0.268436846003710, y: 0.273359776670862}, + 13050: {x: 0.26843684600371, y: 0.273359776670862}, 13060: {x: 0.268408129482189, y: 0.273323363829024}, 13070: {x: 0.268379466787795, y: 0.273287013162952}, 13080: {x: 0.268350857776998, y: 0.273250724523822}, - 13090: {x: 0.268322302306755, y: 0.273214497763250}, - 13100: {x: 0.268293800234510, y: 0.273178332733301}, + 13090: {x: 0.268322302306755, y: 0.27321449776325}, + 13100: {x: 0.26829380023451, y: 0.273178332733301}, 13110: {x: 0.268265351418193, y: 0.273142229286482}, 13120: {x: 0.268236955716216, y: 0.273106187275739}, - 13130: {x: 0.268208612987472, y: 0.273070206554460}, - 13140: {x: 0.268180323091333, y: 0.273034286976470}, + 13130: {x: 0.268208612987472, y: 0.27307020655446}, + 13140: {x: 0.268180323091333, y: 0.27303428697647}, 13150: {x: 0.268152085887648, y: 0.272998428396032}, - 13160: {x: 0.268123901236740, y: 0.272962630667843}, + 13160: {x: 0.26812390123674, y: 0.272962630667843}, 13170: {x: 0.268095768999407, y: 0.272926893647034}, 13180: {x: 0.268067689036918, y: 0.272891217189167}, 13190: {x: 0.268039661211008, y: 0.272855601150237}, 13200: {x: 0.268011685383883, y: 0.272820045386665}, 13210: {x: 0.267983761418212, y: 0.272784549755302}, - 13220: {x: 0.267955889177130, y: 0.272749114113424}, + 13220: {x: 0.26795588917713, y: 0.272749114113424}, 13230: {x: 0.267928068524232, y: 0.272713738318733}, 13240: {x: 0.267900299323571, y: 0.272678422229351}, 13250: {x: 0.267872581439662, y: 0.272643165703826}, @@ -1239,7 +1239,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 13370: {x: 0.267543920838911, y: 0.272224682458029}, 13380: {x: 0.267516857948409, y: 0.272190187579474}, 13390: {x: 0.267489844524888, y: 0.272155750327873}, - 13400: {x: 0.267462880439510, y: 0.272121370567922}, + 13400: {x: 0.26746288043951, y: 0.272121370567922}, 13410: {x: 0.267435965563866, y: 0.272087048164718}, 13420: {x: 0.267409099769975, y: 0.272052782983753}, 13430: {x: 0.267382282930284, y: 0.272018574890918}, @@ -1250,12 +1250,12 @@ const lookup: {[k: number]: {x: number, y: number}} = { 13480: {x: 0.267248928609951, y: 0.271848386082345}, 13490: {x: 0.267222402840415, y: 0.271814517723962}, 13500: {x: 0.267195925143968, y: 0.271780705525908}, - 13510: {x: 0.267169495396400, y: 0.271746949357190}, + 13510: {x: 0.2671694953964, y: 0.27174694935719}, 13520: {x: 0.267143113473911, y: 0.271713249087201}, 13530: {x: 0.267116779253111, y: 0.271679604585716}, 13540: {x: 0.267090492611015, y: 0.271646015722892}, 13550: {x: 0.267064253425046, y: 0.271612482369265}, - 13560: {x: 0.267038061573030, y: 0.271579004395750}, + 13560: {x: 0.26703806157303, y: 0.27157900439575}, 13570: {x: 0.267011916933197, y: 0.271545581673638}, 13580: {x: 0.266985819384176, y: 0.271512214074599}, 13590: {x: 0.266959768804997, y: 0.271478901470675}, @@ -1288,10 +1288,10 @@ const lookup: {[k: number]: {x: number, y: number}} = { 13860: {x: 0.266273726418439, y: 0.270599791817639}, 13870: {x: 0.266248943453149, y: 0.270567968608104}, 13880: {x: 0.266224204116342, y: 0.270536196840302}, - 13890: {x: 0.266199508298318, y: 0.270504476396940}, + 13890: {x: 0.266199508298318, y: 0.27050447639694}, 13900: {x: 0.266174855889734, y: 0.270472807161063}, 13910: {x: 0.266150246781598, y: 0.270441189016052}, - 13920: {x: 0.266125680865270, y: 0.270409621845622}, + 13920: {x: 0.26612568086527, y: 0.270409621845622}, 13930: {x: 0.266101158032457, y: 0.270378105533821}, 13940: {x: 0.266076678175215, y: 0.270346639965031}, 13950: {x: 0.266052241185947, y: 0.270315225023965}, @@ -1299,35 +1299,35 @@ const lookup: {[k: number]: {x: number, y: number}} = { 13970: {x: 0.266003495382668, y: 0.270252546565504}, 13980: {x: 0.265979186355184, y: 0.270221282819181}, 13990: {x: 0.265954919768722, y: 0.270190069242723}, - 14000: {x: 0.265930695517400, y: 0.270158905722483}, + 14000: {x: 0.2659306955174, y: 0.270158905722483}, 14010: {x: 0.265906513495671, y: 0.270127792145138}, - 14020: {x: 0.265882373598326, y: 0.270096728397690}, + 14020: {x: 0.265882373598326, y: 0.27009672839769}, 14030: {x: 0.265858275720492, y: 0.270065714367461}, 14040: {x: 0.265834219757632, y: 0.270034749942096}, - 14050: {x: 0.265810205605542, y: 0.270003835009560}, + 14050: {x: 0.265810205605542, y: 0.27000383500956}, 14060: {x: 0.265786233160348, y: 0.269972969458138}, 14070: {x: 0.265762302318509, y: 0.269942153176432}, 14080: {x: 0.265738412976813, y: 0.269911386053361}, 14090: {x: 0.265714565032377, y: 0.269880667978163}, 14100: {x: 0.265690758382642, y: 0.269849998840385}, - 14110: {x: 0.265666992925380, y: 0.269819378529894}, + 14110: {x: 0.26566699292538, y: 0.269819378529894}, 14120: {x: 0.265643268558683, y: 0.269788806936865}, - 14130: {x: 0.265619585180968, y: 0.269758283951790}, + 14130: {x: 0.265619585180968, y: 0.26975828395179}, 14140: {x: 0.265595942690974, y: 0.269727809465467}, - 14150: {x: 0.265572340987760, y: 0.269697383369006}, + 14150: {x: 0.26557234098776, y: 0.269697383369006}, 14160: {x: 0.265548779970706, y: 0.269667005553824}, - 14170: {x: 0.265525259539510, y: 0.269636675911649}, + 14170: {x: 0.26552525953951, y: 0.269636675911649}, 14180: {x: 0.265501779594186, y: 0.269606394334511}, - 14190: {x: 0.265478340035065, y: 0.269576160714750}, + 14190: {x: 0.265478340035065, y: 0.26957616071475}, 14200: {x: 0.265454940762793, y: 0.269545974945007}, - 14210: {x: 0.265431581678328, y: 0.269515836918230}, + 14210: {x: 0.265431581678328, y: 0.26951583691823}, 14220: {x: 0.265408262682941, y: 0.269485746527665}, 14230: {x: 0.265384983678215, y: 0.269455703666863}, 14240: {x: 0.265361744566042, y: 0.269425708229675}, - 14250: {x: 0.265338545248624, y: 0.269395760110250}, + 14250: {x: 0.265338545248624, y: 0.26939576011025}, 14260: {x: 0.265315385628468, y: 0.269365859203038}, - 14270: {x: 0.265292265608390, y: 0.269336005402783}, - 14280: {x: 0.265269185091510, y: 0.269306198604529}, + 14270: {x: 0.26529226560839, y: 0.269336005402783}, + 14280: {x: 0.26526918509151, y: 0.269306198604529}, 14290: {x: 0.265246143981253, y: 0.269276438703614}, 14300: {x: 0.265223142181345, y: 0.269246725595669}, 14310: {x: 0.265200179595817, y: 0.269217059176621}, @@ -1338,12 +1338,12 @@ const lookup: {[k: number]: {x: number, y: number}} = { 14360: {x: 0.265085951545858, y: 0.269069423792665}, 14370: {x: 0.265063222247971, y: 0.269040035337759}, 14380: {x: 0.265040531501021, y: 0.269010692851367}, - 14390: {x: 0.265017879211410, y: 0.268981396231731}, + 14390: {x: 0.26501787921141, y: 0.268981396231731}, 14400: {x: 0.264995265285834, y: 0.268952145377374}, 14410: {x: 0.264972689631277, y: 0.268922940187105}, 14420: {x: 0.264950152155012, y: 0.268893780560014}, 14430: {x: 0.264927652764604, y: 0.268864666395474}, - 14440: {x: 0.264905191367902, y: 0.268835597593140}, + 14440: {x: 0.264905191367902, y: 0.26883559759314}, 14450: {x: 0.264882767873041, y: 0.268806574052946}, 14460: {x: 0.264860382188444, y: 0.268777595675106}, 14470: {x: 0.264838034222816, y: 0.268748662360111}, @@ -1352,14 +1352,14 @@ const lookup: {[k: number]: {x: number, y: number}} = { 14500: {x: 0.264771215731037, y: 0.268662131801274}, 14510: {x: 0.264749017733983, y: 0.268633377748114}, 14520: {x: 0.264726857003648, y: 0.268604668264403}, - 14530: {x: 0.264704733450420, y: 0.268576003252281}, + 14530: {x: 0.26470473345042, y: 0.268576003252281}, 14540: {x: 0.264682646984965, y: 0.268547382614163}, - 14550: {x: 0.264660597518220, y: 0.268518806252737}, + 14550: {x: 0.26466059751822, y: 0.268518806252737}, 14560: {x: 0.264638584961403, y: 0.268490274070956}, 14570: {x: 0.264616609225999, y: 0.268461785972046}, - 14580: {x: 0.264594670223771, y: 0.268433341859500}, - 14590: {x: 0.264572767866750, y: 0.268404941637079}, - 14600: {x: 0.264550902067240, y: 0.268376585208810}, + 14580: {x: 0.264594670223771, y: 0.2684333418595}, + 14590: {x: 0.26457276786675, y: 0.268404941637079}, + 14600: {x: 0.26455090206724, y: 0.26837658520881}, 14610: {x: 0.264529072737813, y: 0.268348272478987}, 14620: {x: 0.264507279791309, y: 0.268320003352168}, 14630: {x: 0.264485523140839, y: 0.268291777733174}, @@ -1368,34 +1368,34 @@ const lookup: {[k: number]: {x: number, y: number}} = { 14660: {x: 0.264420470100702, y: 0.268207360975306}, 14670: {x: 0.264398857770764, y: 0.268179308441084}, 14680: {x: 0.264377281306381, y: 0.268151298942726}, - 14690: {x: 0.264355740622245, y: 0.268123332386620}, - 14700: {x: 0.264334235633310, y: 0.268095408679413}, + 14690: {x: 0.264355740622245, y: 0.26812333238662}, + 14700: {x: 0.26433423563331, y: 0.268095408679413}, 14710: {x: 0.264312766254791, y: 0.268067527728008}, 14720: {x: 0.264291332402158, y: 0.268039689439562}, 14730: {x: 0.264269933991143, y: 0.268011893721492}, 14740: {x: 0.264248570937733, y: 0.267984140481466}, - 14750: {x: 0.264227243158170, y: 0.267956429627408}, + 14750: {x: 0.26422724315817, y: 0.267956429627408}, 14760: {x: 0.264205950568951, y: 0.267928761067493}, - 14770: {x: 0.264184693086830, y: 0.267901134710149}, + 14770: {x: 0.26418469308683, y: 0.267901134710149}, 14780: {x: 0.264163470628811, y: 0.267873550464056}, 14790: {x: 0.264142283112151, y: 0.267846008238144}, 14800: {x: 0.264121130454358, y: 0.267818507941593}, - 14810: {x: 0.264100012573191, y: 0.267791049483830}, + 14810: {x: 0.264100012573191, y: 0.26779104948383}, 14820: {x: 0.264078929386659, y: 0.267763632774532}, 14830: {x: 0.264057880813016, y: 0.267736257723624}, 14840: {x: 0.264036866770768, y: 0.267708924241275}, - 14850: {x: 0.264015887178665, y: 0.267681632237900}, + 14850: {x: 0.264015887178665, y: 0.2676816322379}, 14860: {x: 0.263994941955703, y: 0.267654381624161}, 14870: {x: 0.263974031021124, y: 0.267627172310963}, 14880: {x: 0.263953154294413, y: 0.267600004209452}, 14890: {x: 0.263932311695297, y: 0.267572877231021}, 14900: {x: 0.263911503143748, y: 0.267545791287299}, - 14910: {x: 0.263890728559977, y: 0.267518746290160}, + 14910: {x: 0.263890728559977, y: 0.26751874629016}, 14920: {x: 0.263869987864436, y: 0.267491742151718}, 14930: {x: 0.263849280977816, y: 0.267464778784324}, - 14940: {x: 0.263828607821048, y: 0.267437856100570}, + 14940: {x: 0.263828607821048, y: 0.26743785610057}, 14950: {x: 0.263807968315299, y: 0.267410974013283}, - 14960: {x: 0.263787362381975, y: 0.267384132435530}, + 14960: {x: 0.263787362381975, y: 0.26738413243553}, 14970: {x: 0.263766789942716, y: 0.267357331280613}, 14980: {x: 0.263746250919399, y: 0.267330570462068}, 14990: {x: 0.263725745234133, y: 0.267303849893667}, @@ -1405,26 +1405,26 @@ const lookup: {[k: number]: {x: number, y: number}} = { 15030: {x: 0.263644054323948, y: 0.267197368405124}, 15040: {x: 0.263623714168738, y: 0.267170847802188}, 15050: {x: 0.263603406889116, y: 0.267144366936916}, - 15060: {x: 0.263583132408809, y: 0.267117925724700}, - 15070: {x: 0.263562890651770, y: 0.267091524081164}, + 15060: {x: 0.263583132408809, y: 0.2671179257247}, + 15070: {x: 0.26356289065177, y: 0.267091524081164}, 15080: {x: 0.263542681542182, y: 0.267065161922158}, - 15090: {x: 0.263522505004451, y: 0.267038839163760}, + 15090: {x: 0.263522505004451, y: 0.26703883916376}, 15100: {x: 0.263502360963211, y: 0.267012555722274}, - 15110: {x: 0.263482249343318, y: 0.266986311514230}, + 15110: {x: 0.263482249343318, y: 0.26698631151423}, 15120: {x: 0.263462170069853, y: 0.266960106456383}, 15130: {x: 0.263442123068119, y: 0.266933940465714}, - 15140: {x: 0.263422108263640, y: 0.266907813459424}, + 15140: {x: 0.26342210826364, y: 0.266907813459424}, 15150: {x: 0.263402125582164, y: 0.266881725354939}, 15160: {x: 0.263382174949655, y: 0.266855676069909}, 15170: {x: 0.263362256292302, y: 0.266829665522202}, 15180: {x: 0.263342369536507, y: 0.266803693629909}, - 15190: {x: 0.263322514608895, y: 0.266777760311340}, + 15190: {x: 0.263322514608895, y: 0.26677776031134}, 15200: {x: 0.263302691436304, y: 0.266751865485026}, 15210: {x: 0.263282899945792, y: 0.266726009069714}, 15220: {x: 0.263263140064629, y: 0.266700190984371}, - 15230: {x: 0.263243411720303, y: 0.266674411148180}, + 15230: {x: 0.263243411720303, y: 0.26667441114818}, 15240: {x: 0.263223714840515, y: 0.266648669480541}, - 15250: {x: 0.263204049353180, y: 0.266622965901072}, + 15250: {x: 0.26320404935318, y: 0.266622965901072}, 15260: {x: 0.263184415186422, y: 0.266597300329601}, 15270: {x: 0.263164812268581, y: 0.266571672686177}, 15280: {x: 0.263145240528207, y: 0.266546082891056}, @@ -1433,7 +1433,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 15310: {x: 0.263086711660527, y: 0.266469539801311}, 15320: {x: 0.263067263919708, y: 0.266444100606256}, 15330: {x: 0.263047847002242, y: 0.266418698863987}, - 15340: {x: 0.263028460837929, y: 0.266393334496030}, + 15340: {x: 0.263028460837929, y: 0.26639333449603}, 15350: {x: 0.263009105356775, y: 0.266368007424123}, 15360: {x: 0.262989780488991, y: 0.266342717570212}, 15370: {x: 0.262970486164991, y: 0.266317464856449}, @@ -1442,21 +1442,21 @@ const lookup: {[k: number]: {x: number, y: number}} = { 15400: {x: 0.262912785762906, y: 0.266241928780688}, 15410: {x: 0.262893612922261, y: 0.266216823853184}, 15420: {x: 0.262874470280517, y: 0.266191755679688}, - 15430: {x: 0.262855357769300, y: 0.266166724183585}, + 15430: {x: 0.2628553577693, y: 0.266166724183585}, 15440: {x: 0.262836275320436, y: 0.266141729288465}, - 15450: {x: 0.262817222865950, y: 0.266116770918119}, + 15450: {x: 0.26281722286595, y: 0.266116770918119}, 15460: {x: 0.262798200338063, y: 0.266091848996542}, 15470: {x: 0.262779207669195, y: 0.266066963447927}, 15480: {x: 0.262760244791961, y: 0.266042114196669}, 15490: {x: 0.262741311639174, y: 0.266017301167365}, - 15500: {x: 0.262722408143840, y: 0.265992524284809}, - 15510: {x: 0.262703534239160, y: 0.265967783473994}, + 15500: {x: 0.26272240814384, y: 0.265992524284809}, + 15510: {x: 0.26270353423916, y: 0.265967783473994}, 15520: {x: 0.262684689858531, y: 0.265943078660112}, 15530: {x: 0.262665874935539, y: 0.265918409768552}, 15540: {x: 0.262647089403967, y: 0.265893776724899}, 15550: {x: 0.262628333197785, y: 0.265869179454936}, - 15560: {x: 0.262609606251158, y: 0.265844617884640}, - 15570: {x: 0.262590908498440, y: 0.265820091940183}, + 15560: {x: 0.262609606251158, y: 0.26584461788464}, + 15570: {x: 0.26259090849844, y: 0.265820091940183}, 15580: {x: 0.262572239874173, y: 0.265795601547934}, 15590: {x: 0.262553600313091, y: 0.265771146634452}, 15600: {x: 0.262534989750115, y: 0.265746727126491}, @@ -1472,10 +1472,10 @@ const lookup: {[k: number]: {x: number, y: number}} = { 15700: {x: 0.262350464832198, y: 0.265504463373313}, 15710: {x: 0.262332169006403, y: 0.265480428546484}, 15720: {x: 0.262313901412223, y: 0.265456428261069}, - 15730: {x: 0.262295661986971, y: 0.265432462446280}, + 15730: {x: 0.262295661986971, y: 0.26543246244628}, 15740: {x: 0.262277450668136, y: 0.265408531031512}, 15750: {x: 0.262259267393388, y: 0.265384633946344}, - 15760: {x: 0.262241112100575, y: 0.265360771120540}, + 15760: {x: 0.262241112100575, y: 0.26536077112054}, 15770: {x: 0.262222984727721, y: 0.265336942484046}, 15780: {x: 0.262204885213029, y: 0.265313147966991}, 15790: {x: 0.262186813494877, y: 0.265289387499685}, @@ -1487,7 +1487,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 15850: {x: 0.262078963484542, y: 0.265147535860792}, 15860: {x: 0.262061084631441, y: 0.265124011805607}, 15870: {x: 0.262043233088181, y: 0.265100521249142}, - 15880: {x: 0.262025408794698, y: 0.265077064123320}, + 15880: {x: 0.262025408794698, y: 0.26507706412332}, 15890: {x: 0.262007611691102, y: 0.265053640360243}, 15900: {x: 0.261989841717669, y: 0.265030249892184}, 15910: {x: 0.261972098814847, y: 0.265006892651596}, @@ -1499,31 +1499,31 @@ const lookup: {[k: number]: {x: number, y: number}} = { 15970: {x: 0.261866206566945, y: 0.264867443224144}, 15980: {x: 0.261848651505002, y: 0.264844316699086}, 15990: {x: 0.261831123043266, y: 0.264821222867351}, - 16000: {x: 0.261813621123680, y: 0.264798161662946}, - 16010: {x: 0.261796145688350, y: 0.264775133020042}, + 16000: {x: 0.26181362112368, y: 0.264798161662946}, + 16010: {x: 0.26179614568835, y: 0.264775133020042}, 16020: {x: 0.261778696679547, y: 0.264752136872987}, - 16030: {x: 0.261761274039702, y: 0.264729173156290}, - 16040: {x: 0.261743877711410, y: 0.264706241804634}, + 16030: {x: 0.261761274039702, y: 0.26472917315629}, + 16040: {x: 0.26174387771141, y: 0.264706241804634}, 16050: {x: 0.261726507637424, y: 0.264683342752869}, 16060: {x: 0.261709163760662, y: 0.264660475936009}, - 16070: {x: 0.261691846024196, y: 0.264637641289240}, + 16070: {x: 0.261691846024196, y: 0.26463764128924}, 16080: {x: 0.261674554371265, y: 0.264614838747909}, 16090: {x: 0.261657288745261, y: 0.264592068247532}, 16100: {x: 0.261640049089738, y: 0.264569329723791}, 16110: {x: 0.261622835348406, y: 0.264546623112531}, - 16120: {x: 0.261605647465133, y: 0.264523948349760}, + 16120: {x: 0.261605647465133, y: 0.26452394834976}, 16130: {x: 0.261588485383944, y: 0.264501305371654}, - 16140: {x: 0.261571349049020, y: 0.264478694114549}, + 16140: {x: 0.26157134904902, y: 0.264478694114549}, 16150: {x: 0.261554238404698, y: 0.264456114514944}, 16160: {x: 0.261537153395472, y: 0.264433566509502}, 16170: {x: 0.261520093965986, y: 0.264411050035046}, 16180: {x: 0.261503060061044, y: 0.264388565028561}, 16190: {x: 0.261486051625599, y: 0.264366111427192}, 16200: {x: 0.261469068604761, y: 0.264343689168247}, - 16210: {x: 0.261452110943790, y: 0.264321298189190}, + 16210: {x: 0.26145211094379, y: 0.26432129818919}, 16220: {x: 0.261435178588098, y: 0.264298938427648}, 16230: {x: 0.261418271483251, y: 0.264276609821404}, - 16240: {x: 0.261401389574964, y: 0.264254312308400}, + 16240: {x: 0.261401389574964, y: 0.2642543123084}, 16250: {x: 0.261384532809103, y: 0.264232045826737}, 16260: {x: 0.261367701131686, y: 0.264209810314673}, 16270: {x: 0.261350894488877, y: 0.264187605710622}, @@ -1532,7 +1532,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 16300: {x: 0.261300624231997, y: 0.264121176733038}, 16310: {x: 0.261283917192258, y: 0.264099095148307}, 16320: {x: 0.261267234920184, y: 0.264077044165999}, - 16330: {x: 0.261250577362829, y: 0.264055023725460}, + 16330: {x: 0.261250577362829, y: 0.26405502372546}, 16340: {x: 0.261233944467391, y: 0.264033033766191}, 16350: {x: 0.261217336181216, y: 0.264011074227843}, 16360: {x: 0.261200752451793, y: 0.263989145050222}, @@ -1551,7 +1551,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 16490: {x: 0.260987375002802, y: 0.263706801453789}, 16500: {x: 0.260971129634477, y: 0.263685291090317}, 16510: {x: 0.260954908052343, y: 0.263663810202662}, - 16520: {x: 0.260938710206146, y: 0.263642358733010}, + 16520: {x: 0.260938710206146, y: 0.26364235873301}, 16530: {x: 0.260922536045771, y: 0.263620936623693}, 16540: {x: 0.260906385521238, y: 0.263599543817186}, 16550: {x: 0.260890258582705, y: 0.263578180256108}, @@ -1559,7 +1559,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 16570: {x: 0.260858075264945, y: 0.263535540641439}, 16580: {x: 0.260842018786708, y: 0.263514264473804}, 16590: {x: 0.260825985696453, y: 0.263493017323511}, - 16600: {x: 0.260809975945010, y: 0.263471799133894}, + 16600: {x: 0.26080997594501, y: 0.263471799133894}, 16610: {x: 0.260793989483345, y: 0.263450609848427}, 16620: {x: 0.260778026262555, y: 0.263429449410728}, 16630: {x: 0.260762086233871, y: 0.263408317764554}, @@ -1568,25 +1568,25 @@ const lookup: {[k: number]: {x: number, y: number}} = { 16660: {x: 0.260714404814743, y: 0.263345095014858}, 16670: {x: 0.260698557069427, y: 0.263324077975157}, 16680: {x: 0.260682732274345, y: 0.263303089447863}, - 16690: {x: 0.260666930381514, y: 0.263282129377570}, + 16690: {x: 0.260666930381514, y: 0.26328212937757}, 16700: {x: 0.260651151343082, y: 0.263261197709007}, 16710: {x: 0.260635395111323, y: 0.263240294387043}, 16720: {x: 0.260619661638644, y: 0.263219419356682}, 16730: {x: 0.260603950877577, y: 0.263198572563066}, 16740: {x: 0.260588262780783, y: 0.263177753951473}, 16750: {x: 0.260572597301053, y: 0.263156963467314}, - 16760: {x: 0.260556954391300, y: 0.263136201056140}, + 16760: {x: 0.2605569543913, y: 0.26313620105614}, 16770: {x: 0.260541334004568, y: 0.263115466663633}, 16780: {x: 0.260525736094025, y: 0.263094760235613}, 16790: {x: 0.260510160612966, y: 0.263074081718029}, 16800: {x: 0.260494607514811, y: 0.263053431056969}, 16810: {x: 0.260479076753105, y: 0.263032808198652}, - 16820: {x: 0.260463568281517, y: 0.263012213089430}, + 16820: {x: 0.260463568281517, y: 0.26301221308943}, 16830: {x: 0.260448082053841, y: 0.262991645675787}, - 16840: {x: 0.260432618023995, y: 0.262971105904340}, - 16850: {x: 0.260417176146020, y: 0.262950593721837}, - 16860: {x: 0.260401756374079, y: 0.262930109075160}, - 16870: {x: 0.260386358662460, y: 0.262909651911318}, + 16840: {x: 0.260432618023995, y: 0.26297110590434}, + 16850: {x: 0.26041717614602, y: 0.262950593721837}, + 16860: {x: 0.260401756374079, y: 0.26293010907516}, + 16870: {x: 0.26038635866246, y: 0.262909651911318}, 16880: {x: 0.260370982965572, y: 0.262889222177453}, 16890: {x: 0.260355629237944, y: 0.262868819820835}, 16900: {x: 0.260340297434228, y: 0.262848444788868}, @@ -1599,7 +1599,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 16970: {x: 0.260233584893849, y: 0.262706580264093}, 16980: {x: 0.260218426863343, y: 0.262686421951225}, 16990: {x: 0.260203290354015, y: 0.262666290495397}, - 17000: {x: 0.260188175321710, y: 0.262646185845289}, + 17000: {x: 0.26018817532171, y: 0.262646185845289}, 17010: {x: 0.260173081722391, y: 0.262626107949704}, 17020: {x: 0.260158009512134, y: 0.262606056757572}, 17030: {x: 0.260142958647135, y: 0.262586032217947}, @@ -1615,12 +1615,12 @@ const lookup: {[k: number]: {x: number, y: number}} = { 17130: {x: 0.259993614431634, y: 0.262387241590116}, 17140: {x: 0.259978795506238, y: 0.262367506901013}, 17150: {x: 0.259963997409378, y: 0.262347798262679}, - 17160: {x: 0.259949220098731, y: 0.262328115625760}, - 17170: {x: 0.259934463532080, y: 0.262308458941021}, + 17160: {x: 0.259949220098731, y: 0.26232811562576}, + 17170: {x: 0.25993446353208, y: 0.262308458941021}, 17180: {x: 0.259919727667323, y: 0.262288828159345}, 17190: {x: 0.259905012462466, y: 0.262269223231737}, 17200: {x: 0.259890317875626, y: 0.262249644109319}, - 17210: {x: 0.259875643865030, y: 0.262230090743332}, + 17210: {x: 0.25987564386503, y: 0.262230090743332}, 17220: {x: 0.259860990389014, y: 0.262210563085136}, 17230: {x: 0.259846357406024, y: 0.262191061086207}, 17240: {x: 0.259831744874614, y: 0.262171584698139}, @@ -1628,7 +1628,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 17260: {x: 0.259802581001291, y: 0.262132708561551}, 17270: {x: 0.259788029577029, y: 0.262113308716803}, 17280: {x: 0.259773498439643, y: 0.262093934290461}, - 17290: {x: 0.259758987548230, y: 0.262074585234703}, + 17290: {x: 0.25975898754823, y: 0.262074585234703}, 17300: {x: 0.259744496861986, y: 0.262055261501819}, 17310: {x: 0.259730026340219, y: 0.262035963044217}, 17320: {x: 0.259715575942341, y: 0.262016689814418}, @@ -1636,22 +1636,22 @@ const lookup: {[k: number]: {x: number, y: number}} = { 17340: {x: 0.259686735356427, y: 0.261978218848889}, 17350: {x: 0.259672345087744, y: 0.261959021018772}, 17360: {x: 0.259657974781652, y: 0.261939848227686}, - 17370: {x: 0.259643624398088, y: 0.261920700428720}, + 17370: {x: 0.259643624398088, y: 0.26192070042872}, 17380: {x: 0.259629293897095, y: 0.261901577575079}, 17390: {x: 0.259614983238816, y: 0.261882479620077}, 17400: {x: 0.259600692383502, y: 0.261863406517142}, 17410: {x: 0.259586421291502, y: 0.261844358219814}, 17420: {x: 0.259572169923273, y: 0.261825334681743}, - 17430: {x: 0.259557938239370, y: 0.261806335856691}, + 17430: {x: 0.25955793823937, y: 0.261806335856691}, 17440: {x: 0.259543726200452, y: 0.261787361698531}, - 17450: {x: 0.259529533767280, y: 0.261768412161246}, - 17460: {x: 0.259515360900716, y: 0.261749487198930}, + 17450: {x: 0.25952953376728, y: 0.261768412161246}, + 17460: {x: 0.259515360900716, y: 0.26174948719893}, 17470: {x: 0.259501207561723, y: 0.261730586765787}, 17480: {x: 0.259487073711365, y: 0.261711710816127}, 17490: {x: 0.259472959310808, y: 0.261692859304375}, - 17500: {x: 0.259458864321314, y: 0.261674032185060}, - 17510: {x: 0.259444788704250, y: 0.261655229412822}, - 17520: {x: 0.259430732421078, y: 0.261636450942410}, + 17500: {x: 0.259458864321314, y: 0.26167403218506}, + 17510: {x: 0.25944478870425, y: 0.261655229412822}, + 17520: {x: 0.259430732421078, y: 0.26163645094241}, 17530: {x: 0.259416695433363, y: 0.261617696728679}, 17540: {x: 0.259402677702767, y: 0.261598966726591}, 17550: {x: 0.259388679191049, y: 0.261580260891218}, @@ -1660,7 +1660,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 17580: {x: 0.259346798588256, y: 0.261524287937701}, 17590: {x: 0.259332876571626, y: 0.261505678322031}, 17600: {x: 0.259318973584149, y: 0.261487092650029}, - 17610: {x: 0.259305089588170, y: 0.261468530877405}, + 17610: {x: 0.25930508958817, y: 0.261468530877405}, 17620: {x: 0.259291224546132, y: 0.261449992959969}, 17630: {x: 0.259277378420574, y: 0.261431478853643}, 17640: {x: 0.259263551174129, y: 0.261412988514447}, @@ -1670,35 +1670,35 @@ const lookup: {[k: number]: {x: number, y: number}} = { 17680: {x: 0.259208430235524, y: 0.261339263953088}, 17690: {x: 0.259194696827504, y: 0.261320891793536}, 17700: {x: 0.259180982076402, y: 0.261302543139435}, - 17710: {x: 0.259167285945517, y: 0.261284217947530}, + 17710: {x: 0.259167285945517, y: 0.26128421794753}, 17720: {x: 0.259153608398238, y: 0.261265916174672}, - 17730: {x: 0.259139949398050, y: 0.261247637777811}, - 17740: {x: 0.259126308908530, y: 0.261229382713999}, + 17730: {x: 0.25913994939805, y: 0.261247637777811}, + 17740: {x: 0.25912630890853, y: 0.261229382713999}, 17750: {x: 0.259112686893347, y: 0.261211150940392}, 17760: {x: 0.259099083316264, y: 0.261192942414244}, 17770: {x: 0.259085498141135, y: 0.261174757092912}, 17780: {x: 0.259071931331905, y: 0.261156594933852}, - 17790: {x: 0.259058382852613, y: 0.261138455894620}, + 17790: {x: 0.259058382852613, y: 0.26113845589462}, 17800: {x: 0.259044852667386, y: 0.261120339932874}, 17810: {x: 0.259031340740445, y: 0.261102247006369}, 17820: {x: 0.259017847036098, y: 0.261084177072962}, 17830: {x: 0.259004371518748, y: 0.261066130090606}, 17840: {x: 0.258990914152885, y: 0.261048106017357}, 17850: {x: 0.258977474903089, y: 0.261030104811364}, - 17860: {x: 0.258964053734029, y: 0.261012126430880}, + 17860: {x: 0.258964053734029, y: 0.26101212643088}, 17870: {x: 0.258950650610467, y: 0.260994170834252}, 17880: {x: 0.258937265497248, y: 0.260976237979926}, 17890: {x: 0.258923898359312, y: 0.260958327826446}, 17900: {x: 0.258910549161683, y: 0.260940440332451}, - 17910: {x: 0.258897217869474, y: 0.260922575456680}, + 17910: {x: 0.258897217869474, y: 0.26092257545668}, 17920: {x: 0.258883904447887, y: 0.260904733157965}, 17930: {x: 0.258870608862212, y: 0.260886913395239}, 17940: {x: 0.258857331077825, y: 0.260869116127525}, 17950: {x: 0.258844071060188, y: 0.260851341313948}, 17960: {x: 0.258830828774854, y: 0.260833588913723}, 17970: {x: 0.258817604187457, y: 0.260815858886165}, - 17980: {x: 0.258804397263723, y: 0.260798151190680}, - 17990: {x: 0.258791207969460, y: 0.260780465786771}, + 17980: {x: 0.258804397263723, y: 0.26079815119068}, + 17990: {x: 0.25879120796946, y: 0.260780465786771}, 18000: {x: 0.258778036270563, y: 0.260762802634035}, 18010: {x: 0.258764882133012, y: 0.260745161692162}, 18020: {x: 0.258751745522874, y: 0.260727542920938}, @@ -1711,14 +1711,14 @@ const lookup: {[k: number]: {x: number, y: number}} = { 18090: {x: 0.258660277186754, y: 0.260604828942052}, 18100: {x: 0.258647279584424, y: 0.260587386099451}, 18110: {x: 0.258634299207892, y: 0.260569965069744}, - 18120: {x: 0.258621336024060, y: 0.260552565813640}, - 18130: {x: 0.258608389999916, y: 0.260535188291940}, + 18120: {x: 0.25862133602406, y: 0.26055256581364}, + 18130: {x: 0.258608389999916, y: 0.26053518829194}, 18140: {x: 0.258595461102528, y: 0.260517832465535}, 18150: {x: 0.258582549299047, y: 0.260500498295406}, 18160: {x: 0.258569654556704, y: 0.260483185742626}, 18170: {x: 0.258556776842814, y: 0.260465894768356}, - 18180: {x: 0.258543916124770, y: 0.260448625333848}, - 18190: {x: 0.258531072370050, y: 0.260431377400444}, + 18180: {x: 0.25854391612477, y: 0.260448625333848}, + 18190: {x: 0.25853107237005, y: 0.260431377400444}, 18200: {x: 0.258518245546209, y: 0.260414150929574}, 18210: {x: 0.258505435620883, y: 0.260396945882758}, 18220: {x: 0.258492642561791, y: 0.260379762221603}, @@ -1726,8 +1726,8 @@ const lookup: {[k: number]: {x: number, y: number}} = { 18240: {x: 0.258467106913574, y: 0.260345458903155}, 18250: {x: 0.258454364260281, y: 0.260328339169519}, 18260: {x: 0.258441638344887, y: 0.260311240668861}, - 18270: {x: 0.258428929135505, y: 0.260294163363230}, - 18280: {x: 0.258416236600330, y: 0.260277107214760}, + 18270: {x: 0.258428929135505, y: 0.26029416336323}, + 18280: {x: 0.25841623660033, y: 0.26027710721476}, 18290: {x: 0.258403560707632, y: 0.260260072185675}, 18300: {x: 0.258390901425763, y: 0.260243058238284}, 18310: {x: 0.258378258723148, y: 0.260226065334984}, @@ -1736,7 +1736,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 18340: {x: 0.258340429776286, y: 0.260175212514881}, 18350: {x: 0.258327853076529, y: 0.260158303413627}, 18360: {x: 0.258315292799329, y: 0.260141415169734}, - 18370: {x: 0.258302748913580, y: 0.260124547746113}, + 18370: {x: 0.25830274891358, y: 0.260124547746113}, 18380: {x: 0.258290221388248, y: 0.260107701105759}, 18390: {x: 0.258277710192379, y: 0.260090875211753}, 18400: {x: 0.258265215295091, y: 0.260074070027258}, @@ -1744,18 +1744,18 @@ const lookup: {[k: number]: {x: number, y: number}} = { 18420: {x: 0.258240274273119, y: 0.260040521639884}, 18430: {x: 0.258227828087054, y: 0.260023778363754}, 18440: {x: 0.258215398076806, y: 0.260007055650633}, - 18450: {x: 0.258202984211870, y: 0.259990353464105}, - 18460: {x: 0.258190586461820, y: 0.259973671767836}, - 18470: {x: 0.258178204796300, y: 0.259957010525575}, - 18480: {x: 0.258165839185030, y: 0.259940369701154}, + 18450: {x: 0.25820298421187, y: 0.259990353464105}, + 18460: {x: 0.25819058646182, y: 0.259973671767836}, + 18470: {x: 0.2581782047963, y: 0.259957010525575}, + 18480: {x: 0.25816583918503, y: 0.259940369701154}, 18490: {x: 0.258153489597802, y: 0.259923749258486}, 18500: {x: 0.258141156004486, y: 0.259907149161569}, - 18510: {x: 0.258128838375020, y: 0.259890569374479}, - 18520: {x: 0.258116536679420, y: 0.259874009861378}, + 18510: {x: 0.25812883837502, y: 0.259890569374479}, + 18520: {x: 0.25811653667942, y: 0.259874009861378}, 18530: {x: 0.258104250887773, y: 0.259857470586504}, 18540: {x: 0.258091980970238, y: 0.259840951514182}, 18550: {x: 0.258079726897049, y: 0.259824452608815}, - 18560: {x: 0.258067488638510, y: 0.259807973834886}, + 18560: {x: 0.25806748863851, y: 0.259807973834886}, 18570: {x: 0.258055266164998, y: 0.259791515156961}, 18580: {x: 0.258043059446963, y: 0.259775076539684}, 18590: {x: 0.258030868454926, y: 0.259758657947781}, @@ -1763,22 +1763,22 @@ const lookup: {[k: number]: {x: number, y: number}} = { 18610: {x: 0.258006533531287, y: 0.259725880699395}, 18620: {x: 0.257994389541084, y: 0.259709521972761}, 18630: {x: 0.257982261159677, y: 0.259693183131199}, - 18640: {x: 0.257970148357942, y: 0.259676864139830}, + 18640: {x: 0.257970148357942, y: 0.25967686413983}, 18650: {x: 0.257958051106826, y: 0.259660564963857}, 18660: {x: 0.257945969377347, y: 0.259644285568558}, 18670: {x: 0.257933903140592, y: 0.259628025919292}, - 18680: {x: 0.257921852367720, y: 0.259611785981496}, + 18680: {x: 0.25792185236772, y: 0.259611785981496}, 18690: {x: 0.257909817029957, y: 0.259595565720684}, - 18700: {x: 0.257897797098600, y: 0.259579365102448}, + 18700: {x: 0.2578977970986, y: 0.259579365102448}, 18710: {x: 0.257885792545014, y: 0.259563184092458}, 18720: {x: 0.257873803340636, y: 0.259547022656461}, - 18730: {x: 0.257861829456969, y: 0.259530880760280}, + 18730: {x: 0.257861829456969, y: 0.25953088076028}, 18740: {x: 0.257849870865584, y: 0.259514758369818}, 18750: {x: 0.257837927538124, y: 0.259498655451051}, 18760: {x: 0.257825999446298, y: 0.259482571970033}, 18770: {x: 0.257814086561882, y: 0.259466507892896}, 18780: {x: 0.257802188856721, y: 0.259450463185845}, - 18790: {x: 0.257790306302730, y: 0.259434437815163}, + 18790: {x: 0.25779030630273, y: 0.259434437815163}, 18800: {x: 0.257778438871888, y: 0.259418431747208}, 18810: {x: 0.257766586536243, y: 0.259402444948413}, 18820: {x: 0.257754749267908, y: 0.259386477385287}, @@ -1791,22 +1791,22 @@ const lookup: {[k: number]: {x: number, y: number}} = { 18890: {x: 0.257672307945896, y: 0.259275240244568}, 18900: {x: 0.257660590220158, y: 0.259259425370066}, 18910: {x: 0.257648887313749, y: 0.259243629433164}, - 18920: {x: 0.257637199199442, y: 0.259227852401110}, + 18920: {x: 0.257637199199442, y: 0.25922785240111}, 18930: {x: 0.257625525850076, y: 0.259212094241229}, 18940: {x: 0.257613867238557, y: 0.259196354920914}, 18950: {x: 0.257602223337853, y: 0.259180634407636}, 18960: {x: 0.257590594120997, y: 0.259164932668935}, 18970: {x: 0.257578979561086, y: 0.259149249672424}, 18980: {x: 0.257567379631282, y: 0.259133585385787}, - 18990: {x: 0.257555794304810, y: 0.259117939776782}, + 18990: {x: 0.25755579430481, y: 0.259117939776782}, 19000: {x: 0.257544223554958, y: 0.259102312813239}, 19010: {x: 0.257532667355079, y: 0.259086704463055}, 19020: {x: 0.257521125678588, y: 0.259071114694204}, 19030: {x: 0.257509598498963, y: 0.259055543474727}, 19040: {x: 0.257498085789747, y: 0.259039990772738}, 19050: {x: 0.257486587524543, y: 0.259024456556421}, - 19060: {x: 0.257475103677018, y: 0.259008940794030}, - 19070: {x: 0.257463634220900, y: 0.258993443453890}, + 19060: {x: 0.257475103677018, y: 0.25900894079403}, + 19070: {x: 0.2574636342209, y: 0.25899344345389}, 19080: {x: 0.257452179129982, y: 0.258977964504397}, 19090: {x: 0.257440738378116, y: 0.258962503914014}, 19100: {x: 0.257429311939216, y: 0.258947061651277}, @@ -1815,7 +1815,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 19130: {x: 0.257395118240394, y: 0.258900844515331}, 19140: {x: 0.257383748793742, y: 0.258885475249912}, 19150: {x: 0.257372393530554, y: 0.258870124155853}, - 19160: {x: 0.257361052425110, y: 0.258854791202102}, + 19160: {x: 0.25736105242511, y: 0.258854791202102}, 19170: {x: 0.257349725451753, y: 0.258839476357677}, 19180: {x: 0.257338412584887, y: 0.258824179591664}, 19190: {x: 0.257327113798975, y: 0.258808900873217}, @@ -1830,7 +1830,7 @@ const lookup: {[k: number]: {x: number, y: number}} = { 19280: {x: 0.257226054184705, y: 0.258672199491578}, 19290: {x: 0.257214894816934, y: 0.258657099567121}, 19300: {x: 0.257203749253138, y: 0.258642017355368}, - 19310: {x: 0.257192617468490, y: 0.258626952826277}, + 19310: {x: 0.25719261746849, y: 0.258626952826277}, 19320: {x: 0.257181499438221, y: 0.258611905949869}, 19330: {x: 0.257170395137617, y: 0.258596876696235}, 19340: {x: 0.257159304542027, y: 0.258581865035527}, @@ -1838,20 +1838,20 @@ const lookup: {[k: number]: {x: number, y: number}} = { 19360: {x: 0.257137164367556, y: 0.258551894373835}, 19370: {x: 0.257126114739655, y: 0.258536935313483}, 19380: {x: 0.257115078718724, y: 0.258521993727325}, - 19390: {x: 0.257104056280397, y: 0.258507069585840}, + 19390: {x: 0.257104056280397, y: 0.25850706958584}, 19400: {x: 0.257093047400361, y: 0.258492162859569}, 19410: {x: 0.257082052054362, y: 0.258477273519121}, 19420: {x: 0.257071070218202, y: 0.258462401535166}, 19430: {x: 0.257060101867739, y: 0.258447546878439}, - 19440: {x: 0.257049146978886, y: 0.258432709519740}, - 19450: {x: 0.257038205527614, y: 0.258417889429930}, + 19440: {x: 0.257049146978886, y: 0.25843270951974}, + 19450: {x: 0.257038205527614, y: 0.25841788942993}, 19460: {x: 0.257027277489949, y: 0.258403086579935}, - 19470: {x: 0.257016362841970, y: 0.258388300940744}, - 19480: {x: 0.257005461559816, y: 0.258373532483410}, + 19470: {x: 0.25701636284197, y: 0.258388300940744}, + 19480: {x: 0.257005461559816, y: 0.25837353248341}, 19490: {x: 0.256994573619678, y: 0.258358781179047}, 19500: {x: 0.256983698997804, y: 0.258344046998832}, 19510: {x: 0.256972837670494, y: 0.258329329914005}, - 19520: {x: 0.256961989614107, y: 0.258314629895870}, + 19520: {x: 0.256961989614107, y: 0.25831462989587}, 19530: {x: 0.256951154805053, y: 0.258299946915789}, 19540: {x: 0.256940333219798, y: 0.258285280945192}, 19550: {x: 0.256929524834864, y: 0.258270631955565}, @@ -1866,14 +1866,14 @@ const lookup: {[k: number]: {x: number, y: number}} = { 19640: {x: 0.256832839537398, y: 0.258139550518662}, 19650: {x: 0.256822161876125, y: 0.258125069784118}, 19660: {x: 0.256811497160438, y: 0.258110605720958}, - 19670: {x: 0.256800845367498, y: 0.258096158301400}, + 19670: {x: 0.256800845367498, y: 0.2580961583014}, 19680: {x: 0.256790206474518, y: 0.258081727497724}, 19690: {x: 0.256779580458763, y: 0.258067313282268}, 19700: {x: 0.256768967297551, y: 0.258052915627428}, 19710: {x: 0.256758366968249, y: 0.258038534505663}, 19720: {x: 0.256747779448281, y: 0.258024169889488}, 19730: {x: 0.256737204715118, y: 0.258009821751479}, - 19740: {x: 0.256726642746284, y: 0.257995490064270}, + 19740: {x: 0.256726642746284, y: 0.25799549006427}, 19750: {x: 0.256716093519356, y: 0.257981174800553}, 19760: {x: 0.256705557011959, y: 0.257966875933081}, 19770: {x: 0.256695033201772, y: 0.257952593434662}, @@ -1882,12 +1882,12 @@ const lookup: {[k: number]: {x: number, y: number}} = { 19800: {x: 0.256663537732014, y: 0.257909843882703}, 19810: {x: 0.256653064488462, y: 0.257895626589762}, 19820: {x: 0.256642603831271, y: 0.257881425530795}, - 19830: {x: 0.256632155738421, y: 0.257867240678960}, + 19830: {x: 0.256632155738421, y: 0.25786724067896}, 19840: {x: 0.256621720187945, y: 0.257853072007472}, 19850: {x: 0.256611297157924, y: 0.257838919489601}, 19860: {x: 0.256600886626489, y: 0.257824783098676}, - 19870: {x: 0.256590488571820, y: 0.257810662808085}, - 19880: {x: 0.256580102972150, y: 0.257796558591268}, + 19870: {x: 0.25659048857182, y: 0.257810662808085}, + 19880: {x: 0.25658010297215, y: 0.257796558591268}, 19890: {x: 0.256569729805756, y: 0.257782470421726}, 19900: {x: 0.256559369050969, y: 0.257768398273014}, 19910: {x: 0.256549020686167, y: 0.257754342118744}, diff --git a/src/lib/ledvance.ts b/src/lib/ledvance.ts index cc1ded08f965f..60067a082a0ab 100644 --- a/src/lib/ledvance.ts +++ b/src/lib/ledvance.ts @@ -1,9 +1,10 @@ -import * as modernExtend from './modernExtend'; -import {isObject} from './utils'; -import {Tz, Fz, KeyValue} from '../lib/types'; -import * as utils from '../lib/utils'; import {Zcl} from 'zigbee-herdsman'; + import * as ota from '../lib/ota'; +import {Tz, Fz, KeyValue} from '../lib/types'; +import * as utils from '../lib/utils'; +import * as modernExtend from './modernExtend'; +import {isObject} from './utils'; const manufacturerOptions = {manufacturerCode: Zcl.ManufacturerCode.OSRAM_SYLVANIA}; @@ -14,8 +15,11 @@ export const ledvanceFz = { convert: (model, msg, publish, options, meta) => { if (utils.hasAlreadyProcessedMessage(msg, model)) return; const lookup: KeyValue = { - commandMoveWithOnOff: 'hold', commandMove: 'hold', commandStopWithOnOff: 'release', - commandStop: 'release', commandMoveToLevelWithOnOff: 'toggle', + commandMoveWithOnOff: 'hold', + commandMove: 'hold', + commandStopWithOnOff: 'release', + commandStop: 'release', + commandMoveToLevelWithOnOff: 'toggle', }; return {[utils.postfixWithEndpointName('action', msg, model, meta)]: lookup[msg.type]}; }, @@ -30,7 +34,7 @@ export const ledvanceTz = { if (key === 'osram_set_transition' || key === 'set_transition') { if (value) { utils.assertNumber(value, key); - const transition = (value > 1) ? Number((Math.round(Number((value * 2).toFixed(1))) / 2).toFixed(1)) * 10 : 1; + const transition = value > 1 ? Number((Math.round(Number((value * 2).toFixed(1))) / 2).toFixed(1)) * 10 : 1; const payload = {0x0012: {value: transition, type: 0x21}, 0x0013: {value: transition, type: 0x21}}; await entity.write('genLevelCtrl', payload); } diff --git a/src/lib/legacy.ts b/src/lib/legacy.ts index 04fbb49e48913..2ebc41a613a83 100644 --- a/src/lib/legacy.ts +++ b/src/lib/legacy.ts @@ -1,21 +1,24 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import fs from 'fs'; + +import fromZigbeeConverters from '../converters/fromZigbee'; import * as globalStore from './store'; import * as utils from './utils'; -import fromZigbeeConverters from '../converters/fromZigbee'; const fromZigbeeStore: KeyValueAny = {}; -import * as exposes from './exposes'; import * as constants from './constants'; +import * as exposes from './exposes'; import * as light from './light'; -import {Zh, KeyValueNumberString, Definition, Fz, Publish, Tz} from './types'; import {logger} from './logger'; +import {Zh, KeyValueNumberString, Definition, Fz, Publish, Tz} from './types'; -interface KeyValueAny {[s: string]: any} +interface KeyValueAny { + [s: string]: any; +} // get object property name (key) by it's value const getKey = (object: KeyValueAny, value: any) => { for (const key in object) { - if (object[key]==value) return key; + if (object[key] == value) return key; } }; @@ -40,43 +43,48 @@ const convertMultiByteNumberPayloadToSingleDecimalNumber = (chunks: any) => { }; function getDataValue(dpValue: any) { + let dataString = ''; switch (dpValue.datatype) { - case dataTypes.raw: - return dpValue.data; - case dataTypes.bool: - return dpValue.data[0] === 1; - case dataTypes.value: - return convertMultiByteNumberPayloadToSingleDecimalNumber(dpValue.data); - case dataTypes.string: - // eslint-disable-next-line - let dataString = ''; - // Don't use .map here, doesn't work: https://github.com/Koenkk/zigbee-herdsman-converters/pull/1799/files#r530377091 - for (let i = 0; i < dpValue.data.length; ++i) { - dataString += String.fromCharCode(dpValue.data[i]); - } - return dataString; - case dataTypes.enum: - return dpValue.data[0]; - case dataTypes.bitmap: - return convertMultiByteNumberPayloadToSingleDecimalNumber(dpValue.data); + case dataTypes.raw: + return dpValue.data; + case dataTypes.bool: + return dpValue.data[0] === 1; + case dataTypes.value: + return convertMultiByteNumberPayloadToSingleDecimalNumber(dpValue.data); + case dataTypes.string: + // Don't use .map here, doesn't work: https://github.com/Koenkk/zigbee-herdsman-converters/pull/1799/files#r530377091 + for (let i = 0; i < dpValue.data.length; ++i) { + dataString += String.fromCharCode(dpValue.data[i]); + } + return dataString; + case dataTypes.enum: + return dpValue.data[0]; + case dataTypes.bitmap: + return convertMultiByteNumberPayloadToSingleDecimalNumber(dpValue.data); } } function getTypeName(dpValue: any) { const entry = Object.entries(dataTypes).find(([typeName, typeId]) => typeId === dpValue.datatype); - return (entry ? entry[0] : 'unknown'); + return entry ? entry[0] : 'unknown'; } function logUnexpectedDataPoint(where: string, msg: KeyValueAny, dpValue: any, meta: Fz.Meta) { - logger.debug(`Received unexpected Tuya DataPoint #${dpValue.dp} from ${meta.device.ieeeAddr} with raw data '${JSON.stringify(dpValue)}': \ + logger.debug( + `Received unexpected Tuya DataPoint #${dpValue.dp} from ${meta.device.ieeeAddr} with raw data '${JSON.stringify(dpValue)}': \ type='${msg.type}', datatype='${getTypeName(dpValue)}', value='${getDataValue(dpValue)}', known DP# usage: \ - ${JSON.stringify(getDataPointNames(dpValue))}`, `zhc:${where}`); + ${JSON.stringify(getDataPointNames(dpValue))}`, + `zhc:${where}`, + ); } function logUnexpectedDataType(where: any, msg: any, dpValue: any, meta: Fz.Meta, expectedDataType?: any) { - logger.debug(`Received Tuya DataPoint #${dpValue.dp} with unexpected datatype from ${meta.device.ieeeAddr} with raw data \ + logger.debug( + `Received Tuya DataPoint #${dpValue.dp} with unexpected datatype from ${meta.device.ieeeAddr} with raw data \ '${JSON.stringify(dpValue)}': type='${msg.type}', datatype='${getTypeName(dpValue)}' (instead of '${expectedDataType}'), \ - value='${getDataValue(dpValue)}', known DP# usage: ${JSON.stringify(getDataPointNames(dpValue))}`, `zhc:${where}`); + value='${getDataValue(dpValue)}', known DP# usage: ${JSON.stringify(getDataPointNames(dpValue))}`, + `zhc:${where}`, + ); } function getDataPointNames(dpValue: any) { @@ -88,10 +96,10 @@ const coverStateOverride: KeyValueAny = { // Contains all covers which differentiate from the default enum states // Use manufacturerName to identify device! // https://github.com/Koenkk/zigbee2mqtt/issues/5596#issuecomment-759408189 - '_TZE200_rddyvrci': {close: 1, open: 2, stop: 0}, - '_TZE200_wmcdj3aq': {close: 0, open: 2, stop: 1}, - '_TZE200_cowvfni3': {close: 0, open: 2, stop: 1}, - '_TYST11_cowvfni3': {close: 0, open: 2, stop: 1}, + _TZE200_rddyvrci: {close: 1, open: 2, stop: 0}, + _TZE200_wmcdj3aq: {close: 0, open: 2, stop: 1}, + _TZE200_cowvfni3: {close: 0, open: 2, stop: 1}, + _TYST11_cowvfni3: {close: 0, open: 2, stop: 1}, }; // Gets an array containing which enums have to be used in order for the correct close/open/stop commands to be sent @@ -113,13 +121,13 @@ function convertDecimalValueTo4ByteHexArray(value: number) { } let gSec: number = undefined; -async function sendDataPoints(entity: Zh.Endpoint | Zh.Group, dpValues: any, cmd='dataRequest', seq:number=undefined) { +async function sendDataPoints(entity: Zh.Endpoint | Zh.Group, dpValues: any, cmd = 'dataRequest', seq: number = undefined) { if (seq === undefined) { if (gSec === undefined) { gSec = 0; } else { gSec++; - gSec %= 0xFFFF; + gSec %= 0xffff; } seq = gSec; } @@ -138,7 +146,7 @@ async function sendDataPoints(entity: Zh.Endpoint | Zh.Group, dpValues: any, cmd function convertStringToHexArray(value: string) { const asciiKeys = []; - for (let i = 0; i < value.length; i ++) { + for (let i = 0; i < value.length; i++) { asciiKeys.push(value[i].charCodeAt(0)); } return asciiKeys; @@ -148,7 +156,7 @@ function dpValueFromIntValue(dp: number, value: number) { return {dp, datatype: dataTypes.value, data: convertDecimalValueTo4ByteHexArray(value)}; } -function dpValueFromBool(dp: number, value: boolean|number) { +function dpValueFromBool(dp: number, value: boolean | number) { return {dp, datatype: dataTypes.bool, data: [value ? 1 : 0]}; } @@ -169,31 +177,31 @@ function dpValueFromBitmap(dp: number, bitmapBuffer: any) { } // Return `seq` - transaction ID for handling concrete response -async function sendDataPoint(entity: Zh.Endpoint | Zh.Group, dpValue: any, cmd?:string, seq:number=undefined) { +async function sendDataPoint(entity: Zh.Endpoint | Zh.Group, dpValue: any, cmd?: string, seq: number = undefined) { return await sendDataPoints(entity, [dpValue], cmd, seq); } -async function sendDataPointValue(entity: Zh.Endpoint | Zh.Group, dp:number, value:any, cmd?:string, seq:number=undefined) { +async function sendDataPointValue(entity: Zh.Endpoint | Zh.Group, dp: number, value: any, cmd?: string, seq: number = undefined) { return await sendDataPoints(entity, [dpValueFromIntValue(dp, value)], cmd, seq); } -async function sendDataPointBool(entity: Zh.Endpoint | Zh.Group, dp:number, value:boolean|number, cmd?:string, seq:number=undefined) { +async function sendDataPointBool(entity: Zh.Endpoint | Zh.Group, dp: number, value: boolean | number, cmd?: string, seq: number = undefined) { return await sendDataPoints(entity, [dpValueFromBool(dp, value)], cmd, seq); } -async function sendDataPointEnum(entity: Zh.Endpoint | Zh.Group, dp:number, value:number, cmd?:string, seq:number=undefined) { +async function sendDataPointEnum(entity: Zh.Endpoint | Zh.Group, dp: number, value: number, cmd?: string, seq: number = undefined) { return await sendDataPoints(entity, [dpValueFromEnum(dp, value)], cmd, seq); } -async function sendDataPointRaw(entity: Zh.Endpoint | Zh.Group, dp:number, value:any, cmd?:string, seq:number=undefined) { +async function sendDataPointRaw(entity: Zh.Endpoint | Zh.Group, dp: number, value: any, cmd?: string, seq: number = undefined) { return await sendDataPoints(entity, [dpValueFromRaw(dp, value)], cmd, seq); } -async function sendDataPointBitmap(entity: Zh.Endpoint | Zh.Group, dp:number, value:any, cmd?:string, seq:number=undefined) { +async function sendDataPointBitmap(entity: Zh.Endpoint | Zh.Group, dp: number, value: any, cmd?: string, seq: number = undefined) { return await sendDataPoints(entity, [dpValueFromBitmap(dp, value)], cmd, seq); } -async function sendDataPointStringBuffer(entity: Zh.Endpoint | Zh.Group, dp:number, value:any, cmd?:string, seq:number=undefined) { +async function sendDataPointStringBuffer(entity: Zh.Endpoint | Zh.Group, dp: number, value: any, cmd?: string, seq: number = undefined) { return await sendDataPoints(entity, [dpValueFromStringBuffer(dp, value)], cmd, seq); } @@ -209,13 +217,14 @@ function convertRawToCycleTimer(value: any) { timernr = value[1]; timeractive = value[2]; if (value[3] > 0) { - weekdays = (value[3] & 0x01 ? 'Su' : '') + - (value[3] & 0x02 ? 'Mo' : '') + - (value[3] & 0x04 ? 'Tu' : '') + - (value[3] & 0x08 ? 'We' : '') + - (value[3] & 0x10 ? 'Th' : '') + - (value[3] & 0x20 ? 'Fr' : '') + - (value[3] & 0x40 ? 'Sa' : ''); + weekdays = + (value[3] & 0x01 ? 'Su' : '') + + (value[3] & 0x02 ? 'Mo' : '') + + (value[3] & 0x04 ? 'Tu' : '') + + (value[3] & 0x08 ? 'We' : '') + + (value[3] & 0x10 ? 'Th' : '') + + (value[3] & 0x20 ? 'Fr' : '') + + (value[3] & 0x40 ? 'Sa' : ''); } else { weekdays = 'once'; } @@ -239,11 +248,13 @@ function convertRawToCycleTimer(value: any) { }; } - function logDataPoint(where: string, msg: KeyValueAny, dpValue: any, meta: any) { - logger.info(`Received Tuya DataPoint #${dpValue.dp} from ${meta.device.ieeeAddr} with raw data '${JSON.stringify(dpValue)}': \ + logger.info( + `Received Tuya DataPoint #${dpValue.dp} from ${meta.device.ieeeAddr} with raw data '${JSON.stringify(dpValue)}': \ type='${msg.type}', datatype='${getTypeName(dpValue)}', value='${getDataValue(dpValue)}', known DP# usage: \ - ${JSON.stringify(getDataPointNames(dpValue))}`, `zhc:${where}`); + ${JSON.stringify(getDataPointNames(dpValue))}`, + `zhc:${where}`, + ); } const thermostatSystemModes2: KeyValueAny = { @@ -353,7 +364,6 @@ const tvThermostatMode: KeyValueAny = { 2: 'auto', }; - const tvThermostatPreset: KeyValueAny = { 0: 'auto', 1: 'manual', @@ -459,13 +469,14 @@ function convertRawToTimer(value: any) { starttime = String(parseInt(minsincemidnight / 60)).padStart(2, '0') + ':' + String(minsincemidnight % 60).padStart(2, '0'); duration = value[4] * 256 + value[5]; if (value[6] > 0) { - weekdays = (value[6] & 0x01 ? 'Su' : '') + - (value[6] & 0x02 ? 'Mo' : '') + - (value[6] & 0x04 ? 'Tu' : '') + - (value[6] & 0x08 ? 'We' : '') + - (value[6] & 0x10 ? 'Th' : '') + - (value[6] & 0x20 ? 'Fr' : '') + - (value[6] & 0x40 ? 'Sa' : ''); + weekdays = + (value[6] & 0x01 ? 'Su' : '') + + (value[6] & 0x02 ? 'Mo' : '') + + (value[6] & 0x04 ? 'Tu' : '') + + (value[6] & 0x08 ? 'We' : '') + + (value[6] & 0x10 ? 'Th' : '') + + (value[6] & 0x20 ? 'Fr' : '') + + (value[6] & 0x40 ? 'Sa' : ''); } else { weekdays = 'once'; } @@ -474,23 +485,42 @@ function convertRawToTimer(value: any) { return {timernr: timernr, time: starttime, duration: duration, weekdays: weekdays, active: timeractive}; } -function logUnexpectedDataValue(where: string, msg: KeyValueAny, dpValue: any, meta: Fz.Meta, valueKind: any, - expectedMinValue:any=null, expectedMaxValue:any=null) { +function logUnexpectedDataValue( + where: string, + msg: KeyValueAny, + dpValue: any, + meta: Fz.Meta, + valueKind: any, + expectedMinValue: any = null, + expectedMaxValue: any = null, +) { if (expectedMinValue === null) { if (expectedMaxValue === null) { - logger.debug(`Received Tuya DataPoint #${dpValue.dp} with invalid value ${getDataValue(dpValue)} for ${valueKind} \ - from ${meta.device.ieeeAddr}`, `zhc:${where}`); + logger.debug( + `Received Tuya DataPoint #${dpValue.dp} with invalid value ${getDataValue(dpValue)} for ${valueKind} \ + from ${meta.device.ieeeAddr}`, + `zhc:${where}`, + ); } else { - logger.debug(`Received Tuya DataPoint #${dpValue.dp} with invalid value ${getDataValue(dpValue)} for ${valueKind} \ - from ${meta.device.ieeeAddr} which is higher than the expected maximum of ${expectedMaxValue}`, `zhc:${where}`); + logger.debug( + `Received Tuya DataPoint #${dpValue.dp} with invalid value ${getDataValue(dpValue)} for ${valueKind} \ + from ${meta.device.ieeeAddr} which is higher than the expected maximum of ${expectedMaxValue}`, + `zhc:${where}`, + ); } } else { if (expectedMaxValue === null) { - logger.debug(`Received Tuya DataPoint #${dpValue.dp} with invalid value ${getDataValue(dpValue)} for ${valueKind} \ - from ${meta.device.ieeeAddr} which is lower than the expected minimum of ${expectedMinValue}`, `zhc:${where}`); + logger.debug( + `Received Tuya DataPoint #${dpValue.dp} with invalid value ${getDataValue(dpValue)} for ${valueKind} \ + from ${meta.device.ieeeAddr} which is lower than the expected minimum of ${expectedMinValue}`, + `zhc:${where}`, + ); } else { - logger.debug(`Received Tuya DataPoint #${dpValue.dp} with invalid value ${getDataValue(dpValue)} for ${valueKind} \ - from ${meta.device.ieeeAddr} which is outside the expected range from ${expectedMinValue} to ${expectedMaxValue}`, `zhc:${where}`); + logger.debug( + `Received Tuya DataPoint #${dpValue.dp} with invalid value ${getDataValue(dpValue)} for ${valueKind} \ + from ${meta.device.ieeeAddr} which is outside the expected range from ${expectedMinValue} to ${expectedMaxValue}`, + `zhc:${where}`, + ); } } } @@ -499,8 +529,15 @@ function logUnexpectedDataValue(where: string, msg: KeyValueAny, dpValue: any, m // Default is 100 = open, 0 = closed; Devices listed here will use 0 = open, 100 = closed instead // Use manufacturerName to identify device! // Don't invert _TZE200_cowvfni3: https://github.com/Koenkk/zigbee2mqtt/issues/6043 -const coverPositionInvert = ['_TZE200_wmcdj3aq', '_TZE200_nogaemzt', '_TZE200_xuzcvlku', '_TZE200_xaabybja', '_TZE200_rmymn92d', - '_TZE200_gubdgai2', '_TZE200_r0jdjrvi']; +const coverPositionInvert = [ + '_TZE200_wmcdj3aq', + '_TZE200_nogaemzt', + '_TZE200_xuzcvlku', + '_TZE200_xaabybja', + '_TZE200_rmymn92d', + '_TZE200_gubdgai2', + '_TZE200_r0jdjrvi', +]; // Gets a boolean indicating whether the cover by this manufacturerName needs reversed positions function isCoverInverted(manufacturerName: string) { @@ -516,7 +553,6 @@ function convertDecimalValueTo2ByteHexArray(value: any) { return [chunk1, chunk2].map((hexVal) => parseInt(hexVal, 16)); } - function convertTimeTo2ByteHexArray(time: string) { const timeArray = time.split(':'); if (timeArray.length != 2) { @@ -1048,7 +1084,7 @@ const holdUpdateBrightness324131092621 = (deviceID: any) => { } }; -function getMetaValue(entity: any, definition: any, key: string, groupStrategy='first') { +function getMetaValue(entity: any, definition: any, key: string, groupStrategy = 'first') { if (entity.constructor.name === 'Group' && entity.members.length > 0) { const values = []; for (const memberMeta of definition) { @@ -1063,7 +1099,7 @@ function getMetaValue(entity: any, definition: any, key: string, groupStrategy=' } } - if (groupStrategy === 'allEqual' && (new Set(values)).size === 1) { + if (groupStrategy === 'allEqual' && new Set(values).size === 1) { return values[0]; } } else if (definition && definition.meta && definition.meta.hasOwnProperty(key)) { @@ -1074,25 +1110,24 @@ function getMetaValue(entity: any, definition: any, key: string, groupStrategy=' } const tuyaGetDataValue = (dataType: any, data: any) => { + let dataString = ''; switch (dataType) { - case dataTypes.raw: - return data; - case dataTypes.bool: - return data[0] === 1; - case dataTypes.value: - return convertMultiByteNumberPayloadToSingleDecimalNumber(data); - case dataTypes.string: - // eslint-disable-next-line - let dataString = ''; - // Don't use .map here, doesn't work: https://github.com/Koenkk/zigbee-herdsman-converters/pull/1799/files#r530377091 - for (let i = 0; i < data.length; ++i) { - dataString += String.fromCharCode(data[i]); - } - return dataString; - case dataTypes.enum: - return data[0]; - case dataTypes.bitmap: - return convertMultiByteNumberPayloadToSingleDecimalNumber(data); + case dataTypes.raw: + return data; + case dataTypes.bool: + return data[0] === 1; + case dataTypes.value: + return convertMultiByteNumberPayloadToSingleDecimalNumber(data); + case dataTypes.string: + // Don't use .map here, doesn't work: https://github.com/Koenkk/zigbee-herdsman-converters/pull/1799/files#r530377091 + for (let i = 0; i < data.length; ++i) { + dataString += String.fromCharCode(data[i]); + } + return dataString; + case dataTypes.enum: + return data[0]; + case dataTypes.bitmap: + return convertMultiByteNumberPayloadToSingleDecimalNumber(data); } }; @@ -1109,8 +1144,7 @@ const toPercentage = (value: number, min: number, max: number) => { const postfixWithEndpointName = (name: string, msg: KeyValueAny, definition: Definition) => { if (definition.meta && definition.meta.multiEndpoint) { - const endpointName = definition.hasOwnProperty('endpoint') ? - getKey(definition.endpoint(msg.device), msg.endpoint.ID) : msg.endpoint.ID; + const endpointName = definition.hasOwnProperty('endpoint') ? getKey(definition.endpoint(msg.device), msg.endpoint.ID) : msg.endpoint.ID; return `${name}_${endpointName}`; } else { return name; @@ -1118,7 +1152,7 @@ const postfixWithEndpointName = (name: string, msg: KeyValueAny, definition: Def }; const transactionStore: KeyValueAny = {}; -const hasAlreadyProcessedMessage = (msg: KeyValueAny, model: Definition, transaction:number=null, key:string=null) => { +const hasAlreadyProcessedMessage = (msg: KeyValueAny, model: Definition, transaction: number = null, key: string = null) => { if (model.meta && model.meta.publishDuplicateTransaction) return false; const current = transaction !== null ? transaction : msg.meta.zclTransactionSequenceNumber; key = key || msg.device.ieeeAddr; @@ -1168,7 +1202,7 @@ const ictcg1 = (model: any, msg: any, publish: any, options: any, action: any) = const s = fromZigbeeStore[deviceID]; // if rate == 70 so we rotate slowly - const rate = (msg.data.rate == 70) ? 0.3 : 1; + const rate = msg.data.rate == 70 ? 0.3 : 1; if (action === 'move') { s.since = Date.now(); @@ -1228,7 +1262,6 @@ const toLocalTime = (time: any, timezone: any) => { return local.toTimeString().split(' ').shift(); }; - const giexFzModelConverters = { QT06_1: { // _TZE200_sh1btabb timezone is GMT+8 @@ -1239,7 +1272,7 @@ const giexFzModelConverters = { const giexTzModelConverters: KeyValueAny = { QT06_2: { // _TZE200_a7sghmms irrigation time should not be less than 10 secs as per GiEX advice - irrigationTarget: (value: any, mode: any) => value > 0 && value < SAFETY_MIN_SECS && mode === DURATION ? SAFETY_MIN_SECS : value, + irrigationTarget: (value: any, mode: any) => (value > 0 && value < SAFETY_MIN_SECS && mode === DURATION ? SAFETY_MIN_SECS : value), }, }; @@ -1267,15 +1300,15 @@ const fromZigbee1 = { const dp = dpValue.dp; const value = getDataValue(dpValue); switch (dp) { - case 2: - result.illuminance = value; - result.illuminance_lux = value; - break; - case 4: - result.battery = value; - break; - default: - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:ts0222'); + case 2: + result.illuminance = value; + result.illuminance_lux = value; + break; + case 4: + result.battery = value; + break; + default: + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:ts0222'); } } return result; @@ -1290,41 +1323,41 @@ const fromZigbee1 = { const dp = dpValue.dp; // First we get the data point ID const value = getDataValue(dpValue); // This function will take care of converting the data to proper JS type switch (dp) { - case dataPoints.wateringTimer.water_flow: { - result.water_flow = value; - break; - } - case dataPoints.wateringTimer.remaining_watering_time: { - result.remaining_watering_time = value; - break; - } - case dataPoints.wateringTimer.last_watering_duration: { - result.last_watering_duration = value; - break; - } + case dataPoints.wateringTimer.water_flow: { + result.water_flow = value; + break; + } + case dataPoints.wateringTimer.remaining_watering_time: { + result.remaining_watering_time = value; + break; + } + case dataPoints.wateringTimer.last_watering_duration: { + result.last_watering_duration = value; + break; + } - case dataPoints.wateringTimer.valve_state: { - result.valve_state = value; - break; - } + case dataPoints.wateringTimer.valve_state: { + result.valve_state = value; + break; + } - case dataPoints.wateringTimer.shutdown_timer: { - result.shutdown_timer = value; - break; - } - case dataPoints.wateringTimer.valve_state_auto_shutdown: { - result.valve_state_auto_shutdown = value; - result.valve_state = value; - break; - } + case dataPoints.wateringTimer.shutdown_timer: { + result.shutdown_timer = value; + break; + } + case dataPoints.wateringTimer.valve_state_auto_shutdown: { + result.valve_state_auto_shutdown = value; + result.valve_state = value; + break; + } - case dataPoints.wateringTimer.battery: { - result.battery = value; - break; - } - default: { - logger.debug(`>>> UNKNOWN DP #${dp} with data "${JSON.stringify(dpValue)}"`, 'zhc:legacy:fz:watering_timer'); - } + case dataPoints.wateringTimer.battery: { + result.battery = value; + break; + } + default: { + logger.debug(`>>> UNKNOWN DP #${dp} with data "${JSON.stringify(dpValue)}"`, 'zhc:legacy:fz:watering_timer'); + } } } return result; @@ -1368,22 +1401,22 @@ const fromZigbee1 = { const dp = dpValue.dp; const value = getDataValue(dpValue); switch (dp) { - case dataPoints.state: - result.smoke = value === 0; - break; - case 15: - result.battery = value; - break; - case 16: - result.silence_siren = value; - break; - case 20: { - const alarm: KeyValueAny = {0: true, 1: false}; - result.alarm = alarm[value]; - break; - } - default: - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:sa12izl'); + case dataPoints.state: + result.smoke = value === 0; + break; + case 15: + result.battery = value; + break; + case 16: + result.silence_siren = value; + break; + case 20: { + const alarm: KeyValueAny = {0: true, 1: false}; + result.alarm = alarm[value]; + break; + } + default: + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:sa12izl'); } } return result; @@ -1398,36 +1431,36 @@ const fromZigbee1 = { const dp = dpValue.dp; // First we get the data point ID const value = getDataValue(dpValue); // This function will take care of converting the data to proper JS type switch (dp) { - case 1: - result.smoke = Boolean(!value); - break; - case 8: - result.test_alarm = value; - break; - case 9: { - const testAlarmResult: KeyValueAny = {0: 'checking', 1: 'check_success', 2: 'check_failure', 3: 'others'}; - result.test_alarm_result = testAlarmResult[value]; - break; - } - case 11: - result.fault_alarm = Boolean(value); - break; - case 14: { - const batteryLevels: KeyValueAny = {0: 'low', 1: 'middle', 2: 'high'}; - result.battery_level = batteryLevels[value]; - result.battery_low = value === 0; - break; - } - case 16: - result.silence_siren = value; - break; - case 20: { - const alarm: KeyValueAny = {0: true, 1: false}; - result.alarm = alarm[value]; - break; - } - default: - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:r7049_status'); + case 1: + result.smoke = Boolean(!value); + break; + case 8: + result.test_alarm = value; + break; + case 9: { + const testAlarmResult: KeyValueAny = {0: 'checking', 1: 'check_success', 2: 'check_failure', 3: 'others'}; + result.test_alarm_result = testAlarmResult[value]; + break; + } + case 11: + result.fault_alarm = Boolean(value); + break; + case 14: { + const batteryLevels: KeyValueAny = {0: 'low', 1: 'middle', 2: 'high'}; + result.battery_level = batteryLevels[value]; + result.battery_low = value === 0; + break; + } + case 16: + result.silence_siren = value; + break; + case 20: { + const alarm: KeyValueAny = {0: true, 1: false}; + result.alarm = alarm[value]; + break; + } + default: + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:r7049_status'); } } return result; @@ -1442,12 +1475,12 @@ const fromZigbee1 = { const value = getDataValue(dpValue); switch (dp) { - case dataPoints.wooxSwitch: - return {state: value === 2 ? 'OFF' : 'ON'}; - case 101: - return {battery: value}; - default: - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:woox_r7060'); + case dataPoints.wooxSwitch: + return {state: value === 2 ? 'OFF' : 'ON'}; + case 101: + return {battery: value}; + default: + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:woox_r7060'); } }, } satisfies Fz.Converter, @@ -1460,20 +1493,20 @@ const fromZigbee1 = { const value = getDataValue(dpValue); let result = null; switch (dp) { - case dataPoints.HPSZInductionState: - result = {presence: value === 1}; - break; - case dataPoints.HPSZPresenceTime: - result = {duration_of_attendance: value}; - break; - case dataPoints.HPSZLeavingTime: - result = {duration_of_absence: value}; - break; - case dataPoints.HPSZLEDState: - result = {led_state: value}; - break; - default: - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:hpsz'); + case dataPoints.HPSZInductionState: + result = {presence: value === 1}; + break; + case dataPoints.HPSZPresenceTime: + result = {duration_of_attendance: value}; + break; + case dataPoints.HPSZLeavingTime: + result = {duration_of_absence: value}; + break; + case dataPoints.HPSZLEDState: + result = {led_state: value}; + break; + default: + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:hpsz'); } return result; }, @@ -1488,63 +1521,71 @@ const fromZigbee1 = { const value = getDataValue(dpValue); switch (dp) { - case dataPoints.coverPosition: // Started moving to position (triggered from Zigbee) - case dataPoints.coverArrived: { // Arrived at position - const invert = (meta.state) ? !meta.state.invert_cover : false; - const position = invert ? 100 - (value & 0xFF) : (value & 0xFF); - if (position > 0 && position <= 100) { - result.position = position; - result.state = 'OPEN'; - } else if (position == 0) { // Report fully closed - result.position = position; - result.state = 'CLOSE'; + case dataPoints.coverPosition: // Started moving to position (triggered from Zigbee) + case dataPoints.coverArrived: { + // Arrived at position + const invert = meta.state ? !meta.state.invert_cover : false; + const position = invert ? 100 - (value & 0xff) : value & 0xff; + if (position > 0 && position <= 100) { + result.position = position; + result.state = 'OPEN'; + } else if (position == 0) { + // Report fully closed + result.position = position; + result.state = 'CLOSE'; + } + break; } - break; - } - case 1: // report state - // @ts-ignore - result.state = {0: 'OPEN', 1: 'STOP', 2: 'CLOSE'}[value]; - break; - case dataPoints.motorDirection: // reverse direction - result.reverse_direction = (value == 1); - break; - case 10: // cycle time - result.cycle_time = value; - break; - case 101: // model - // @ts-ignore - result.motor_type = {0: '', 1: 'AM0/6-28R-Sm', 2: 'AM0/10-19R-Sm', - 3: 'AM1/10-13R-Sm', 4: 'AM1/20-13R-Sm', 5: 'AM1/30-13R-Sm'}[value]; - break; - case 102: // cycles - result.cycle_count = value; - break; - case 103: // set or clear bottom limit - // @ts-ignore - result.bottom_limit = {0: 'SET', 1: 'CLEAR'}[value]; - break; - case 104: // set or clear top limit - // @ts-ignore - result.top_limit = {0: 'SET', 1: 'CLEAR'}[value]; - break; - case 109: // active power - result.active_power = value; - break; - case 115: // favorite_position - result.favorite_position = (value != 101) ? value : null; - break; - case 116: // report confirmation - break; - case 121: // running state - // @ts-ignore - result.motor_state = {0: 'OPENING', 1: 'STOPPED', 2: 'CLOSING'}[value]; - result.running = (value !== 1) ? true : false; - break; - default: // Unknown code - logger.debug( - `Unhandled DP #${dp} for ${meta.device.manufacturerName}: ${JSON.stringify(dpValue)}`, - 'zhc:legacy:fz:zb_sm_cover', - ); + case 1: // report state + // @ts-ignore + result.state = {0: 'OPEN', 1: 'STOP', 2: 'CLOSE'}[value]; + break; + case dataPoints.motorDirection: // reverse direction + result.reverse_direction = value == 1; + break; + case 10: // cycle time + result.cycle_time = value; + break; + case 101: // model + // @ts-ignore + result.motor_type = { + 0: '', + 1: 'AM0/6-28R-Sm', + 2: 'AM0/10-19R-Sm', + 3: 'AM1/10-13R-Sm', + 4: 'AM1/20-13R-Sm', + 5: 'AM1/30-13R-Sm', + }[value]; + break; + case 102: // cycles + result.cycle_count = value; + break; + case 103: // set or clear bottom limit + // @ts-ignore + result.bottom_limit = {0: 'SET', 1: 'CLEAR'}[value]; + break; + case 104: // set or clear top limit + // @ts-ignore + result.top_limit = {0: 'SET', 1: 'CLEAR'}[value]; + break; + case 109: // active power + result.active_power = value; + break; + case 115: // favorite_position + result.favorite_position = value != 101 ? value : null; + break; + case 116: // report confirmation + break; + case 121: // running state + // @ts-ignore + result.motor_state = {0: 'OPENING', 1: 'STOPPED', 2: 'CLOSING'}[value]; + result.running = value !== 1 ? true : false; + break; + default: // Unknown code + logger.debug( + `Unhandled DP #${dp} for ${meta.device.manufacturerName}: ${JSON.stringify(dpValue)}`, + 'zhc:legacy:fz:zb_sm_cover', + ); } } return result; @@ -1559,102 +1600,102 @@ const fromZigbee1 = { const value = getDataValue(dpValue); switch (dp) { - case dataPoints.x5hState: { - return {system_mode: value ? 'heat' : 'off'}; - } - case dataPoints.x5hWorkingStatus: { - return {running_state: value ? 'heat' : 'idle'}; - } - case dataPoints.x5hSound: { - return {sound: value ? 'ON' : 'OFF'}; - } - case dataPoints.x5hFrostProtection: { - return {frost_protection: value ? 'ON' : 'OFF'}; - } - case dataPoints.x5hWorkingDaySetting: { - return {week: thermostatWeekFormat[value]}; - } - case dataPoints.x5hFactoryReset: { - if (value) { - clearTimeout(globalStore.getValue(msg.endpoint, 'factoryResetTimer')); - const timer = setTimeout(() => publish({factory_reset: 'OFF'}), 60 * 1000); - globalStore.putValue(msg.endpoint, 'factoryResetTimer', timer); - logger.info('The thermostat is resetting now. It will be available in 1 minute.', 'zhc:legacy:fz:x5h_thermostat'); + case dataPoints.x5hState: { + return {system_mode: value ? 'heat' : 'off'}; } + case dataPoints.x5hWorkingStatus: { + return {running_state: value ? 'heat' : 'idle'}; + } + case dataPoints.x5hSound: { + return {sound: value ? 'ON' : 'OFF'}; + } + case dataPoints.x5hFrostProtection: { + return {frost_protection: value ? 'ON' : 'OFF'}; + } + case dataPoints.x5hWorkingDaySetting: { + return {week: thermostatWeekFormat[value]}; + } + case dataPoints.x5hFactoryReset: { + if (value) { + clearTimeout(globalStore.getValue(msg.endpoint, 'factoryResetTimer')); + const timer = setTimeout(() => publish({factory_reset: 'OFF'}), 60 * 1000); + globalStore.putValue(msg.endpoint, 'factoryResetTimer', timer); + logger.info('The thermostat is resetting now. It will be available in 1 minute.', 'zhc:legacy:fz:x5h_thermostat'); + } - return {factory_reset: value ? 'ON' : 'OFF'}; - } - case dataPoints.x5hTempDiff: { - return {deadzone_temperature: parseFloat((value / 10).toFixed(1))}; - } - case dataPoints.x5hProtectionTempLimit: { - return {heating_temp_limit: value}; - } - case dataPoints.x5hBackplaneBrightness: { - const lookup: KeyValueAny = {0: 'off', 1: 'low', 2: 'medium', 3: 'high'}; - - if (value >= 0 && value <= 3) { - globalStore.putValue(msg.endpoint, 'brightnessState', value); - return {brightness_state: lookup[value]}; + return {factory_reset: value ? 'ON' : 'OFF'}; + } + case dataPoints.x5hTempDiff: { + return {deadzone_temperature: parseFloat((value / 10).toFixed(1))}; + } + case dataPoints.x5hProtectionTempLimit: { + return {heating_temp_limit: value}; } + case dataPoints.x5hBackplaneBrightness: { + const lookup: KeyValueAny = {0: 'off', 1: 'low', 2: 'medium', 3: 'high'}; - // Sometimes, for example on thermostat restart, it sends message like: - // {"dpValues":[{"data":{"data":[90],"type":"Buffer"},"datatype":4,"dp":104} - // It doesn't represent any brightness value and brightness remains the previous value - const lastValue = globalStore.getValue(msg.endpoint, 'brightnessState') || 1; - return {brightness_state: lookup[lastValue]}; - } - case dataPoints.x5hWeeklyProcedure: { - const periods = []; - const periodSize = 4; - const periodsNumber = 8; + if (value >= 0 && value <= 3) { + globalStore.putValue(msg.endpoint, 'brightnessState', value); + return {brightness_state: lookup[value]}; + } - for (let i = 0; i < periodsNumber; i++) { - const hours = value[i * periodSize]; - const minutes = value[i * periodSize + 1]; - const tempHexArray = [value[i * periodSize + 2], value[i * periodSize + 3]]; - const tempRaw = Buffer.from(tempHexArray).readUIntBE(0, tempHexArray.length); - const strHours = hours.toString().padStart(2, '0'); - const strMinutes = minutes.toString().padStart(2, '0'); - const temp = parseFloat((tempRaw / 10).toFixed(1)); - periods.push(`${strHours}:${strMinutes}/${temp}`); - } + // Sometimes, for example on thermostat restart, it sends message like: + // {"dpValues":[{"data":{"data":[90],"type":"Buffer"},"datatype":4,"dp":104} + // It doesn't represent any brightness value and brightness remains the previous value + const lastValue = globalStore.getValue(msg.endpoint, 'brightnessState') || 1; + return {brightness_state: lookup[lastValue]}; + } + case dataPoints.x5hWeeklyProcedure: { + const periods = []; + const periodSize = 4; + const periodsNumber = 8; + + for (let i = 0; i < periodsNumber; i++) { + const hours = value[i * periodSize]; + const minutes = value[i * periodSize + 1]; + const tempHexArray = [value[i * periodSize + 2], value[i * periodSize + 3]]; + const tempRaw = Buffer.from(tempHexArray).readUIntBE(0, tempHexArray.length); + const strHours = hours.toString().padStart(2, '0'); + const strMinutes = minutes.toString().padStart(2, '0'); + const temp = parseFloat((tempRaw / 10).toFixed(1)); + periods.push(`${strHours}:${strMinutes}/${temp}`); + } - const schedule = periods.join(' '); - return {schedule}; - } - case dataPoints.x5hChildLock: { - return {child_lock: value ? 'LOCK' : 'UNLOCK'}; - } - case dataPoints.x5hSetTemp: { - const setpoint = parseFloat((value / 10).toFixed(1)); - globalStore.putValue(msg.endpoint, 'currentHeatingSetpoint', setpoint); - return {current_heating_setpoint: setpoint}; - } - case dataPoints.x5hSetTempCeiling: { - return {upper_temp: value}; - } - case dataPoints.x5hCurrentTemp: { - const temperature = value & (1 << 15) ? value - (1 << 16) + 1 : value; - return {local_temperature: parseFloat((temperature / 10).toFixed(1))}; - } - case dataPoints.x5hTempCorrection: { - return {local_temperature_calibration: parseFloat((value / 10).toFixed(1))}; - } - case dataPoints.x5hMode: { - const lookup: KeyValueAny = {0: 'manual', 1: 'program'}; - return {preset: lookup[value]}; - } - case dataPoints.x5hSensorSelection: { - const lookup: KeyValueAny = {0: 'internal', 1: 'external', 2: 'both'}; - return {sensor: lookup[value]}; - } - case dataPoints.x5hOutputReverse: { - return {output_reverse: value}; - } - default: { - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:x5h_thermostat'); - } + const schedule = periods.join(' '); + return {schedule}; + } + case dataPoints.x5hChildLock: { + return {child_lock: value ? 'LOCK' : 'UNLOCK'}; + } + case dataPoints.x5hSetTemp: { + const setpoint = parseFloat((value / 10).toFixed(1)); + globalStore.putValue(msg.endpoint, 'currentHeatingSetpoint', setpoint); + return {current_heating_setpoint: setpoint}; + } + case dataPoints.x5hSetTempCeiling: { + return {upper_temp: value}; + } + case dataPoints.x5hCurrentTemp: { + const temperature = value & (1 << 15) ? value - (1 << 16) + 1 : value; + return {local_temperature: parseFloat((temperature / 10).toFixed(1))}; + } + case dataPoints.x5hTempCorrection: { + return {local_temperature_calibration: parseFloat((value / 10).toFixed(1))}; + } + case dataPoints.x5hMode: { + const lookup: KeyValueAny = {0: 'manual', 1: 'program'}; + return {preset: lookup[value]}; + } + case dataPoints.x5hSensorSelection: { + const lookup: KeyValueAny = {0: 'internal', 1: 'external', 2: 'both'}; + return {sensor: lookup[value]}; + } + case dataPoints.x5hOutputReverse: { + return {output_reverse: value}; + } + default: { + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:x5h_thermostat'); + } } }, } satisfies Fz.Converter, @@ -1670,94 +1711,95 @@ const fromZigbee1 = { const day = daysMap[value[0]]; switch (dp) { - case dataPoints.zsChildLock: - return {child_lock: value ? 'LOCK' : 'UNLOCK'}; - - case dataPoints.zsHeatingSetpoint: - if (value==0) ret.system_mode='off'; - if (value==60) { - ret.system_mode='heat'; - ret.preset = 'boost'; - } + case dataPoints.zsChildLock: + return {child_lock: value ? 'LOCK' : 'UNLOCK'}; + + case dataPoints.zsHeatingSetpoint: + if (value == 0) ret.system_mode = 'off'; + if (value == 60) { + ret.system_mode = 'heat'; + ret.preset = 'boost'; + } - ret.current_heating_setpoint= (value / 2).toFixed(1); - if (value>0 && value<60) globalStore.putValue(msg.endpoint, 'current_heating_setpoint', ret.current_heating_setpoint); - return ret; - case dataPoints.zsHeatingSetpointAuto: - return {current_heating_setpoint_auto: (value / 2).toFixed(1)}; + ret.current_heating_setpoint = (value / 2).toFixed(1); + if (value > 0 && value < 60) globalStore.putValue(msg.endpoint, 'current_heating_setpoint', ret.current_heating_setpoint); + return ret; + case dataPoints.zsHeatingSetpointAuto: + return {current_heating_setpoint_auto: (value / 2).toFixed(1)}; - case dataPoints.zsOpenwindowTemp: - return {detectwindow_temperature: (value / 2).toFixed(1)}; + case dataPoints.zsOpenwindowTemp: + return {detectwindow_temperature: (value / 2).toFixed(1)}; - case dataPoints.zsOpenwindowTime: - return {detectwindow_timeminute: value}; + case dataPoints.zsOpenwindowTime: + return {detectwindow_timeminute: value}; - case dataPoints.zsLocalTemp: - return {local_temperature: (value / 10).toFixed(1)}; + case dataPoints.zsLocalTemp: + return {local_temperature: (value / 10).toFixed(1)}; - case dataPoints.zsBatteryVoltage: - return {voltage: Math.round(value * 10)}; + case dataPoints.zsBatteryVoltage: + return {voltage: Math.round(value * 10)}; - case dataPoints.zsTempCalibration: - return {local_temperature_calibration: value > 55 ? - ((value - 0x100000000)/10).toFixed(1): (value/ 10).toFixed(1)}; + case dataPoints.zsTempCalibration: + return {local_temperature_calibration: value > 55 ? ((value - 0x100000000) / 10).toFixed(1) : (value / 10).toFixed(1)}; - case dataPoints.zsBinaryOne: - return {binary_one: value ? 'ON' : 'OFF'}; + case dataPoints.zsBinaryOne: + return {binary_one: value ? 'ON' : 'OFF'}; - case dataPoints.zsBinaryTwo: - return {binary_two: value ? 'ON' : 'OFF'}; + case dataPoints.zsBinaryTwo: + return {binary_two: value ? 'ON' : 'OFF'}; - case dataPoints.zsComfortTemp: - return {comfort_temperature: (value / 2).toFixed(1)}; + case dataPoints.zsComfortTemp: + return {comfort_temperature: (value / 2).toFixed(1)}; - case dataPoints.zsEcoTemp: - return {eco_temperature: (value / 2).toFixed(1)}; + case dataPoints.zsEcoTemp: + return {eco_temperature: (value / 2).toFixed(1)}; // case dataPoints.zsAwayTemp: // return {away_preset_temperature: (value / 2).toFixed(1)}; - case dataPoints.zsMode: - switch (value) { - case 1: // manual - return {system_mode: 'heat', away_mode: 'OFF', preset: 'manual'}; - case 2: // away - return {system_mode: 'auto', away_mode: 'ON', preset: 'holiday'}; - case 0: // auto - return {system_mode: 'auto', away_mode: 'OFF', preset: 'schedule'}; - default: - logger.warning(`Preset ${value} is not recognized.`, 'zhc:legacy:fz:zs_thermostat'); - break; - } - break; - case dataPoints.zsScheduleMonday: - case dataPoints.zsScheduleTuesday: - case dataPoints.zsScheduleWednesday: - case dataPoints.zsScheduleThursday: - case dataPoints.zsScheduleFriday: - case dataPoints.zsScheduleSaturday: - case dataPoints.zsScheduleSunday: - for (let i = 1; i <= 9; i++) { - const tempId = ((i-1) * 2) +1; - const timeId = ((i-1) * 2) +2; - ret[`${day}_temp_${i}`] = (value[tempId] / 2).toFixed(1); - if (i!=9) { - ret[`${day}_hour_${i}`] = Math.floor(value[timeId] / 4).toString().padStart(2, '0'); - ret[`${day}_minute_${i}`] = ((value[timeId] % 4) *15).toString().padStart(2, '0'); + case dataPoints.zsMode: + switch (value) { + case 1: // manual + return {system_mode: 'heat', away_mode: 'OFF', preset: 'manual'}; + case 2: // away + return {system_mode: 'auto', away_mode: 'ON', preset: 'holiday'}; + case 0: // auto + return {system_mode: 'auto', away_mode: 'OFF', preset: 'schedule'}; + default: + logger.warning(`Preset ${value} is not recognized.`, 'zhc:legacy:fz:zs_thermostat'); + break; } - } - return ret; - case dataPoints.zsAwaySetting: - ret.away_preset_year = value[0]; - ret.away_preset_month = value[1]; - ret.away_preset_day = value[2]; - ret.away_preset_hour = value[3]; - ret.away_preset_minute = value[4]; - ret.away_preset_temperature = (value[5] / 2).toFixed(1); - ret.away_preset_days = (value[6]<<8)+value[7]; - return ret; - default: - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:zs_thermostat'); + break; + case dataPoints.zsScheduleMonday: + case dataPoints.zsScheduleTuesday: + case dataPoints.zsScheduleWednesday: + case dataPoints.zsScheduleThursday: + case dataPoints.zsScheduleFriday: + case dataPoints.zsScheduleSaturday: + case dataPoints.zsScheduleSunday: + for (let i = 1; i <= 9; i++) { + const tempId = (i - 1) * 2 + 1; + const timeId = (i - 1) * 2 + 2; + ret[`${day}_temp_${i}`] = (value[tempId] / 2).toFixed(1); + if (i != 9) { + ret[`${day}_hour_${i}`] = Math.floor(value[timeId] / 4) + .toString() + .padStart(2, '0'); + ret[`${day}_minute_${i}`] = ((value[timeId] % 4) * 15).toString().padStart(2, '0'); + } + } + return ret; + case dataPoints.zsAwaySetting: + ret.away_preset_year = value[0]; + ret.away_preset_month = value[1]; + ret.away_preset_day = value[2]; + ret.away_preset_hour = value[3]; + ret.away_preset_minute = value[4]; + ret.away_preset_temperature = (value[5] / 2).toFixed(1); + ret.away_preset_days = (value[6] << 8) + value[7]; + return ret; + default: + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:zs_thermostat'); } }, } satisfies Fz.Converter, @@ -1771,30 +1813,30 @@ const fromZigbee1 = { const value = getDataValue(dpValue); const {dp} = dpValue; switch (dp) { - case dataPoints.giexWaterValve.state: - return {[giexWaterValve.state]: value ? ON: OFF}; - case dataPoints.giexWaterValve.mode: - return {[giexWaterValve.mode]: value ? CAPACITY: DURATION}; - case dataPoints.giexWaterValve.irrigationTarget: - return {[giexWaterValve.irrigationTarget]: value}; - case dataPoints.giexWaterValve.cycleIrrigationNumTimes: - return {[giexWaterValve.cycleIrrigationNumTimes]: value}; - case dataPoints.giexWaterValve.cycleIrrigationInterval: - return {[giexWaterValve.cycleIrrigationInterval]: value}; - case dataPoints.giexWaterValve.waterConsumed: - return {[giexWaterValve.waterConsumed]: value}; - case dataPoints.giexWaterValve.irrigationStartTime: - return {[giexWaterValve.irrigationStartTime]: modelConverters.time?.(value) || value}; - case dataPoints.giexWaterValve.irrigationEndTime: - return {[giexWaterValve.irrigationEndTime]: modelConverters.time?.(value) || value}; - case dataPoints.giexWaterValve.lastIrrigationDuration: - return {[giexWaterValve.lastIrrigationDuration]: value.split(',').shift()}; // Remove meaningless ,0 suffix - case dataPoints.giexWaterValve.battery: - return {[giexWaterValve.battery]: value}; - case dataPoints.giexWaterValve.currentTemperature: - return; // Do Nothing - value ignored because it isn't a valid temperature reading (misdocumented and usage unclear) - default: // Unknown data point warning - logger.debug(`Unrecognized DP #${dp} with VALUE = ${value}`, 'legacy:fz:giex_water_valve'); + case dataPoints.giexWaterValve.state: + return {[giexWaterValve.state]: value ? ON : OFF}; + case dataPoints.giexWaterValve.mode: + return {[giexWaterValve.mode]: value ? CAPACITY : DURATION}; + case dataPoints.giexWaterValve.irrigationTarget: + return {[giexWaterValve.irrigationTarget]: value}; + case dataPoints.giexWaterValve.cycleIrrigationNumTimes: + return {[giexWaterValve.cycleIrrigationNumTimes]: value}; + case dataPoints.giexWaterValve.cycleIrrigationInterval: + return {[giexWaterValve.cycleIrrigationInterval]: value}; + case dataPoints.giexWaterValve.waterConsumed: + return {[giexWaterValve.waterConsumed]: value}; + case dataPoints.giexWaterValve.irrigationStartTime: + return {[giexWaterValve.irrigationStartTime]: modelConverters.time?.(value) || value}; + case dataPoints.giexWaterValve.irrigationEndTime: + return {[giexWaterValve.irrigationEndTime]: modelConverters.time?.(value) || value}; + case dataPoints.giexWaterValve.lastIrrigationDuration: + return {[giexWaterValve.lastIrrigationDuration]: value.split(',').shift()}; // Remove meaningless ,0 suffix + case dataPoints.giexWaterValve.battery: + return {[giexWaterValve.battery]: value}; + case dataPoints.giexWaterValve.currentTemperature: + return; // Do Nothing - value ignored because it isn't a valid temperature reading (misdocumented and usage unclear) + default: // Unknown data point warning + logger.debug(`Unrecognized DP #${dp} with VALUE = ${value}`, 'legacy:fz:giex_water_valve'); } } }, @@ -1807,29 +1849,29 @@ const fromZigbee1 = { const dp = dpValue.dp; const value = getDataValue(dpValue); switch (dp) { - case dataPoints.alectoSmokeState: - // @ts-ignore - return {smoke_state: {0: 'alarm', 1: 'normal'}[value]}; - case dataPoints.alectoSmokeValue: - return {smoke_value: value}; - case dataPoints.alectoSelfChecking: - return {self_checking: value}; - case dataPoints.alectoCheckingResult: - // @ts-ignore - return {checking_result: {0: 'checking', 1: 'check_success', 2: 'check_failure', 3: 'others'}[value]}; - case dataPoints.alectoSmokeTest: - return {smoke_test: value}; - case dataPoints.alectoLifecycle: - return {lifecycle: value}; - case dataPoints.alectoBatteryPercentage: - return {battery: value}; - case dataPoints.alectoBatteryState: - // @ts-ignore - return {battery_state: {0: 'low', 1: 'middle', 2: 'high'}[value]}; - case dataPoints.alectoSilence: - return {silence: value}; - default: - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(msg.data)}`, 'zhc:legacy:fz:tuya_alecto_smoke'); + case dataPoints.alectoSmokeState: + // @ts-ignore + return {smoke_state: {0: 'alarm', 1: 'normal'}[value]}; + case dataPoints.alectoSmokeValue: + return {smoke_value: value}; + case dataPoints.alectoSelfChecking: + return {self_checking: value}; + case dataPoints.alectoCheckingResult: + // @ts-ignore + return {checking_result: {0: 'checking', 1: 'check_success', 2: 'check_failure', 3: 'others'}[value]}; + case dataPoints.alectoSmokeTest: + return {smoke_test: value}; + case dataPoints.alectoLifecycle: + return {lifecycle: value}; + case dataPoints.alectoBatteryPercentage: + return {battery: value}; + case dataPoints.alectoBatteryState: + // @ts-ignore + return {battery_state: {0: 'low', 1: 'middle', 2: 'high'}[value]}; + case dataPoints.alectoSilence: + return {silence: value}; + default: + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(msg.data)}`, 'zhc:legacy:fz:tuya_alecto_smoke'); } }, } satisfies Fz.Converter, @@ -2557,7 +2599,7 @@ const fromZigbee1 = { options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (utils.isLegacyEnabled(options)) { - const pos = (msg.data.stepmode === 1) ? 'top' : 'bottom'; + const pos = msg.data.stepmode === 1 ? 'top' : 'bottom'; return {action: `right_${pos}_click`}; } else { return fromZigbeeConverters.command_step_color_temperature.convert(model, msg, publish, options, meta); @@ -2594,7 +2636,7 @@ const fromZigbee1 = { options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (utils.isLegacyEnabled(options)) { - const pos = (msg.endpoint.ID === 1) ? 'top' : 'bottom'; + const pos = msg.endpoint.ID === 1 ? 'top' : 'bottom'; return {action: `left_${pos}_release`}; } else { return fromZigbeeConverters.command_stop.convert(model, msg, publish, options, meta); @@ -2607,8 +2649,8 @@ const fromZigbee1 = { options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (utils.isLegacyEnabled(options)) { - const pos = (msg.endpoint.ID === 2) ? 'top' : 'bottom'; - const action = (msg.data.movemode === 0) ? 'release' : 'hold'; + const pos = msg.endpoint.ID === 2 ? 'top' : 'bottom'; + const action = msg.data.movemode === 0 ? 'release' : 'hold'; return {action: `right_${pos}_${action}`}; } else { return fromZigbeeConverters.command_move_hue.convert(model, msg, publish, options, meta); @@ -2621,7 +2663,7 @@ const fromZigbee1 = { options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { if (utils.isLegacyEnabled(options)) { - const pos = (msg.endpoint.ID === 2) ? 'top' : 'bottom'; + const pos = msg.endpoint.ID === 2 ? 'top' : 'bottom'; return {action: `right_${pos}_hold`}; } else { return fromZigbeeConverters.command_move_to_saturation.convert(model, msg, publish, options, meta); @@ -2863,8 +2905,8 @@ const fromZigbee1 = { if (utils.isLegacyEnabled(options)) { const value = msg.data['movemode']; let action = null; - if (value === 0) action = {'action': 'up-press', 'action_group': msg.groupID}; - else if (value === 1) action = {'action': 'down-press', 'action_group': msg.groupID}; + if (value === 0) action = {action: 'up-press', action_group: msg.groupID}; + else if (value === 1) action = {action: 'down-press', action_group: msg.groupID}; return action ? action : null; } else { return fromZigbeeConverters.command_move.convert(model, msg, publish, options, meta); @@ -2909,12 +2951,10 @@ const fromZigbee1 = { result.local_temperature = utils.precisionRound(msg.data['localTemp'], 2) / 100; } if (typeof msg.data['localTemperatureCalibration'] == 'number') { - result.local_temperature_calibration = - utils.precisionRound(msg.data['localTemperatureCalibration'], 2) / 10; + result.local_temperature_calibration = utils.precisionRound(msg.data['localTemperatureCalibration'], 2) / 10; } if (typeof msg.data['occupiedHeatingSetpoint'] == 'number') { - result.occupied_heating_setpoint = - utils.precisionRound(msg.data['occupiedHeatingSetpoint'], 2) / 100; + result.occupied_heating_setpoint = utils.precisionRound(msg.data['occupiedHeatingSetpoint'], 2) / 100; } if (typeof msg.data['runningState'] == 'number') { result.running_state = msg.data['runningState']; @@ -2948,7 +2988,7 @@ const fromZigbee1 = { if (typeof msg.data['occupiedHeatingSetpoint'] == 'number') { let ohs = utils.precisionRound(msg.data['occupiedHeatingSetpoint'], 2) / 100; // Stelpro will return -325.65 when set to off - ohs = ohs < - 250 ? 0 : ohs; + ohs = ohs < -250 ? 0 : ohs; result[postfixWithEndpointName('occupied_heating_setpoint', msg, model)] = ohs; } if (typeof msg.data['unoccupiedHeatingSetpoint'] == 'number') { @@ -2973,8 +3013,7 @@ const fromZigbee1 = { result[postfixWithEndpointName('setpoint_change_source', msg, model)] = msg.data['setpointChangeSource']; } if (typeof msg.data['setpointChangeSourceTimeStamp'] == 'number') { - result[postfixWithEndpointName('setpoint_change_source_timestamp', msg, model)] = - msg.data['setpointChangeSourceTimeStamp']; + result[postfixWithEndpointName('setpoint_change_source_timestamp', msg, model)] = msg.data['setpointChangeSourceTimeStamp']; } if (typeof msg.data['remoteSensing'] == 'number') { result[postfixWithEndpointName('remote_sensing', msg, model)] = msg.data['remoteSensing']; @@ -3001,15 +3040,16 @@ const fromZigbee1 = { result[postfixWithEndpointName('running_state', msg, model)] = constants.thermostatRunningStates[state]; } if (typeof msg.data['pIHeatingDemand'] == 'number') { - result[postfixWithEndpointName('pi_heating_demand', msg, model)] = - utils.precisionRound(msg.data['pIHeatingDemand'] / 255.0 * 100.0, 0); + result[postfixWithEndpointName('pi_heating_demand', msg, model)] = utils.precisionRound( + (msg.data['pIHeatingDemand'] / 255.0) * 100.0, + 0, + ); } if (typeof msg.data['tempSetpointHold'] == 'number') { result[postfixWithEndpointName('temperature_setpoint_hold', msg, model)] = msg.data['tempSetpointHold']; } if (typeof msg.data['tempSetpointHoldDuration'] == 'number') { - result[postfixWithEndpointName('temperature_setpoint_hold_duration', msg, model)] = - msg.data['tempSetpointHoldDuration']; + result[postfixWithEndpointName('temperature_setpoint_hold_duration', msg, model)] = msg.data['tempSetpointHoldDuration']; } return result; }, @@ -3028,10 +3068,10 @@ const fromZigbee1 = { if (mode == 'number') { result.stelpro_mode = mode; switch (mode) { - case 5: - // 'Eco' mode is translated into 'auto' here - result.system_mode = thermostatSystemModes[1]; - break; + case 5: + // 'Eco' mode is translated into 'auto' here + result.system_mode = thermostatSystemModes[1]; + break; } } const piHeatingDemand = msg.data['pIHeatingDemand']; @@ -3079,8 +3119,7 @@ const fromZigbee1 = { result.local_temperature = utils.precisionRound(msg.data['localTemp'], 2) / 100; } if (typeof msg.data['occupiedHeatingSetpoint'] == 'number') { - result.occupied_heating_setpoint = - utils.precisionRound(msg.data['occupiedHeatingSetpoint'], 2) / 100; + result.occupied_heating_setpoint = utils.precisionRound(msg.data['occupiedHeatingSetpoint'], 2) / 100; } if (typeof msg.data['pIHeatingDemand'] == 'number') { result.pi_heating_demand = utils.precisionRound(msg.data['pIHeatingDemand'], 2); @@ -3142,8 +3181,8 @@ const fromZigbee1 = { if (typeof msg.data['27'] === 'number') { return { action: 'rotate', - direction: (msg.data['27'] > 0 ? 'clockwise' : 'counterclockwise'), - number: (Math.abs(msg.data['27']) / 12), + direction: msg.data['27'] > 0 ? 'clockwise' : 'counterclockwise', + number: Math.abs(msg.data['27']) / 12, }; } }, @@ -3217,10 +3256,10 @@ const fromZigbee1 = { } switch (msg.type) { - case 'commandMoveWithOnOff': - return {action: `${msg.endpoint.ID}_level_move_${msg.data.movemode ? 'down' : 'up'}`}; - case 'commandStopWithOnOff': - return {action: `${msg.endpoint.ID}_level_stop`}; + case 'commandMoveWithOnOff': + return {action: `${msg.endpoint.ID}_level_move_${msg.data.movemode ? 'down' : 'up'}`}; + case 'commandStopWithOnOff': + return {action: `${msg.endpoint.ID}_level_stop`}; } }, } satisfies Fz.Converter, @@ -3240,9 +3279,9 @@ const fromZigbee1 = { } const lookup: KeyValueAny = { - 'commandUpOpen': 'open', - 'commandDownClose': 'close', - 'commandStop': 'stop', + commandUpOpen: 'open', + commandDownClose: 'close', + commandStop: 'stop', }; return {action: `${msg.endpoint.ID}_cover_${lookup[msg.type]}`}; }, @@ -3256,11 +3295,16 @@ const fromZigbee1 = { return fromZigbeeConverters.hue_dimmer_switch.convert(model, msg, publish, options, meta); } - const multiplePressTimeout = options && options.hasOwnProperty('multiple_press_timeout') ? - options.multiple_press_timeout : 0.25; + const multiplePressTimeout = options && options.hasOwnProperty('multiple_press_timeout') ? options.multiple_press_timeout : 0.25; - const getPayload = function(button: any, pressType: any, pressDuration: any, pressCounter: any, - brightnessSend: any, brightnessValue: any) { + const getPayload = function ( + button: any, + pressType: any, + pressDuration: any, + pressCounter: any, + brightnessSend: any, + brightnessValue: any, + ) { const payLoad: KeyValueAny = {}; payLoad['action'] = `${button}-${pressType}`; payLoad['duration'] = pressDuration / 1000; @@ -3280,15 +3324,21 @@ const fromZigbee1 = { const typeLookup: KeyValueAny = {0: 'press', 1: 'hold', 2: 'release', 3: 'release'}; const type = typeLookup[msg.data['type']]; - const brightnessEnabled = options && options.hasOwnProperty('send_brightess') ? - options.send_brightess : true; + const brightnessEnabled = options && options.hasOwnProperty('send_brightess') ? options.send_brightess : true; const brightnessSend = brightnessEnabled && button && (button == 'up' || button == 'down'); // Initialize store if (!fromZigbeeStore[deviceID]) { - fromZigbeeStore[deviceID] = {pressStart: null, pressType: null, - delayedButton: null, delayedBrightnessSend: null, delayedType: null, - delayedCounter: 0, delayedTimerStart: null, delayedTimer: null}; + fromZigbeeStore[deviceID] = { + pressStart: null, + pressType: null, + delayedButton: null, + delayedBrightnessSend: null, + delayedType: null, + delayedCounter: 0, + delayedTimerStart: null, + delayedTimer: null, + }; if (brightnessEnabled) { fromZigbeeStore[deviceID].brightnessValue = 255; fromZigbeeStore[deviceID].brightnessSince = null; @@ -3322,13 +3372,19 @@ const fromZigbee1 = { } if (type == 'press') { // pressed different button - if (fromZigbeeStore[deviceID].delayedTimer && (fromZigbeeStore[deviceID].delayedButton != button)) { + if (fromZigbeeStore[deviceID].delayedTimer && fromZigbeeStore[deviceID].delayedButton != button) { clearTimeout(fromZigbeeStore[deviceID].delayedTimer); fromZigbeeStore[deviceID].delayedTimer = null; - publish(getPayload(fromZigbeeStore[deviceID].delayedButton, - fromZigbeeStore[deviceID].delayedType, 0, fromZigbeeStore[deviceID].delayedCounter, - fromZigbeeStore[deviceID].delayedBrightnessSend, - fromZigbeeStore[deviceID].brightnessValue)); + publish( + getPayload( + fromZigbeeStore[deviceID].delayedButton, + fromZigbeeStore[deviceID].delayedType, + 0, + fromZigbeeStore[deviceID].delayedCounter, + fromZigbeeStore[deviceID].delayedBrightnessSend, + fromZigbeeStore[deviceID].brightnessValue, + ), + ); } } else { // released after press: start timer @@ -3345,20 +3401,32 @@ const fromZigbee1 = { fromZigbeeStore[deviceID].delayedCounter++; fromZigbeeStore[deviceID].delayedTimerStart = Date.now(); fromZigbeeStore[deviceID].delayedTimer = setTimeout(() => { - publish(getPayload(fromZigbeeStore[deviceID].delayedButton, - fromZigbeeStore[deviceID].delayedType, 0, fromZigbeeStore[deviceID].delayedCounter, - fromZigbeeStore[deviceID].delayedBrightnessSend, - fromZigbeeStore[deviceID].brightnessValue)); + publish( + getPayload( + fromZigbeeStore[deviceID].delayedButton, + fromZigbeeStore[deviceID].delayedType, + 0, + fromZigbeeStore[deviceID].delayedCounter, + fromZigbeeStore[deviceID].delayedBrightnessSend, + fromZigbeeStore[deviceID].brightnessValue, + ), + ); fromZigbeeStore[deviceID].delayedTimer = null; // @ts-expect-error }, multiplePressTimeout * 1000); } else { const pressDuration = - (fromZigbeeStore[deviceID].pressType == 'hold' || fromZigbeeStore[deviceID].pressType == 'hold-release') ? - Date.now() - fromZigbeeStore[deviceID].pressStart : 0; - return getPayload(button, - fromZigbeeStore[deviceID].pressType, pressDuration, null, brightnessSend, - fromZigbeeStore[deviceID].brightnessValue); + fromZigbeeStore[deviceID].pressType == 'hold' || fromZigbeeStore[deviceID].pressType == 'hold-release' + ? Date.now() - fromZigbeeStore[deviceID].pressStart + : 0; + return getPayload( + button, + fromZigbeeStore[deviceID].pressType, + pressDuration, + null, + brightnessSend, + fromZigbeeStore[deviceID].brightnessValue, + ); } } } @@ -3386,201 +3454,199 @@ const fromZigbee1 = { let temperature; /* See tuyaThermostat above for message structure comment */ switch (dp) { - case dataPoints.moesSchedule: - return { - program: { - weekdays_p1_hour: value[0], - weekdays_p1_minute: value[1], - weekdays_p1_temperature: value[2] / 2, - weekdays_p2_hour: value[3], - weekdays_p2_minute: value[4], - weekdays_p2_temperature: value[5] / 2, - weekdays_p3_hour: value[6], - weekdays_p3_minute: value[7], - weekdays_p3_temperature: value[8] / 2, - weekdays_p4_hour: value[9], - weekdays_p4_minute: value[10], - weekdays_p4_temperature: value[11] / 2, - saturday_p1_hour: value[12], - saturday_p1_minute: value[13], - saturday_p1_temperature: value[14] / 2, - saturday_p2_hour: value[15], - saturday_p2_minute: value[16], - saturday_p2_temperature: value[17] / 2, - saturday_p3_hour: value[18], - saturday_p3_minute: value[19], - saturday_p3_temperature: value[20] / 2, - saturday_p4_hour: value[21], - saturday_p4_minute: value[22], - saturday_p4_temperature: value[23] / 2, - sunday_p1_hour: value[24], - sunday_p1_minute: value[25], - sunday_p1_temperature: value[26] / 2, - sunday_p2_hour: value[27], - sunday_p2_minute: value[28], - sunday_p2_temperature: value[29] / 2, - sunday_p3_hour: value[30], - sunday_p3_minute: value[31], - sunday_p3_temperature: value[32] / 2, - sunday_p4_hour: value[33], - sunday_p4_minute: value[34], - sunday_p4_temperature: value[35] / 2, - }, - }; - case dataPoints.state: // Thermostat on standby = OFF, running = ON - if (model.model === 'BAC-002-ALZB') { - if (!value) { - return {system_mode: 'off'}; + case dataPoints.moesSchedule: + return { + program: { + weekdays_p1_hour: value[0], + weekdays_p1_minute: value[1], + weekdays_p1_temperature: value[2] / 2, + weekdays_p2_hour: value[3], + weekdays_p2_minute: value[4], + weekdays_p2_temperature: value[5] / 2, + weekdays_p3_hour: value[6], + weekdays_p3_minute: value[7], + weekdays_p3_temperature: value[8] / 2, + weekdays_p4_hour: value[9], + weekdays_p4_minute: value[10], + weekdays_p4_temperature: value[11] / 2, + saturday_p1_hour: value[12], + saturday_p1_minute: value[13], + saturday_p1_temperature: value[14] / 2, + saturday_p2_hour: value[15], + saturday_p2_minute: value[16], + saturday_p2_temperature: value[17] / 2, + saturday_p3_hour: value[18], + saturday_p3_minute: value[19], + saturday_p3_temperature: value[20] / 2, + saturday_p4_hour: value[21], + saturday_p4_minute: value[22], + saturday_p4_temperature: value[23] / 2, + sunday_p1_hour: value[24], + sunday_p1_minute: value[25], + sunday_p1_temperature: value[26] / 2, + sunday_p2_hour: value[27], + sunday_p2_minute: value[28], + sunday_p2_temperature: value[29] / 2, + sunday_p3_hour: value[30], + sunday_p3_minute: value[31], + sunday_p3_temperature: value[32] / 2, + sunday_p4_hour: value[33], + sunday_p4_minute: value[34], + sunday_p4_temperature: value[35] / 2, + }, + }; + case dataPoints.state: // Thermostat on standby = OFF, running = ON + if (model.model === 'BAC-002-ALZB') { + if (!value) { + return {system_mode: 'off'}; + } + return; + } else { + return {system_mode: value ? 'heat' : 'off'}; } - return; - } else { - return {system_mode: value ? 'heat' : 'off'}; - } - case dataPoints.tvMode: - if (model.model === 'BAC-002-ALZB') { - return {system_mode: stateLookup[value]}; - } - return {preset_mode: value ? 'program' : 'hold', preset: value ? 'program' : 'hold'}; - case dataPoints.moesChildLock: - return {child_lock: value ? 'LOCK' : 'UNLOCK'}; - case dataPoints.moesHeatingSetpoint: - if (['_TZE200_5toc8efa', '_TZE204_5toc8efa'].includes(meta.device.manufacturerName)) { - return {current_heating_setpoint: value / 10}; - } else { - return {current_heating_setpoint: value}; - } - case dataPoints.moesMinTempLimit: - if (['_TZE200_5toc8efa', '_TZE204_5toc8efa'].includes(meta.device.manufacturerName)) { - return {min_temperature_limit: value / 10}; - } else { - return {min_temperature_limit: value}; - } - case dataPoints.moesMaxTempLimit: - if (['_TZE200_5toc8efa', '_TZE204_5toc8efa'].includes(meta.device.manufacturerName)) { - return {max_temperature_limit: value / 10}; - } else { - return {max_temperature_limit: value}; - } - case dataPoints.moesMaxTemp: - if (['_TZE200_5toc8efa', '_TZE204_5toc8efa'].includes(meta.device.manufacturerName)) { - return {max_temperature: value / 10}; - } else { - return {max_temperature: value}; - } - case dataPoints.moesDeadZoneTemp: - if (['_TZE200_5toc8efa', '_TZE204_5toc8efa'].includes(meta.device.manufacturerName)) { - return {deadzone_temperature: value / 10}; - } else { - return {deadzone_temperature: value}; - } - case dataPoints.moesLocalTemp: - if (['_TZE200_5toc8efa', '_TZE204_5toc8efa'].includes(meta.device.manufacturerName)) { - temperature = value / 10; - } else { - temperature = value & 1<<15 ? value - (1<<16) + 1 : value; - if (!['_TZE200_ztvwu4nk', '_TZE200_ye5jkfsb'].includes(meta.device.manufacturerName)) { - // https://github.com/Koenkk/zigbee2mqtt/issues/11980 - temperature = temperature / 10; + case dataPoints.tvMode: + if (model.model === 'BAC-002-ALZB') { + return {system_mode: stateLookup[value]}; } - } - temperature = parseFloat(temperature.toFixed(1)); - if (temperature < 100) { - return {local_temperature: parseFloat(temperature.toFixed(1))}; - } - break; - case dataPoints.moesTempCalibration: - temperature = value; - // for negative values produce complimentary hex (equivalent to negative values) - if (temperature > 4000) temperature = temperature - 4096; - return {local_temperature_calibration: temperature}; - case dataPoints.moesScheduleEnable: // state is inverted, preset_mode is deprecated - return {preset_mode: value ? 'hold' : 'program', preset: value ? 'hold' : 'program'}; - case dataPoints.moesValve: - return {heat: value ? 'OFF' : 'ON', running_state: (value ? 'idle' : (model.model === 'BAC-002-ALZB' ? 'cool' : 'heat'))}; - case dataPoints.moesSensor: - switch (value) { - case 0: - return {sensor: 'IN'}; - case 1: - return {sensor: 'AL'}; - case 2: - return {sensor: 'OU'}; - default: - return {sensor: 'not_supported'}; - } - case dataPoints.bacFanMode: - return {fan_mode: fanModes[value]}; - default: // DataPoint 17 is unknown - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:moes_bht_022'); - } - }, - } satisfies Fz.Converter, - moesS_thermostat: { - cluster: 'manuSpecificTuya', - type: ['commandDataResponse', 'commandDataReport'], - convert: (model, msg, publish, options, meta) => { + return {preset_mode: value ? 'program' : 'hold', preset: value ? 'program' : 'hold'}; + case dataPoints.moesChildLock: + return {child_lock: value ? 'LOCK' : 'UNLOCK'}; + case dataPoints.moesHeatingSetpoint: + if (['_TZE200_5toc8efa', '_TZE204_5toc8efa'].includes(meta.device.manufacturerName)) { + return {current_heating_setpoint: value / 10}; + } else { + return {current_heating_setpoint: value}; + } + case dataPoints.moesMinTempLimit: + if (['_TZE200_5toc8efa', '_TZE204_5toc8efa'].includes(meta.device.manufacturerName)) { + return {min_temperature_limit: value / 10}; + } else { + return {min_temperature_limit: value}; + } + case dataPoints.moesMaxTempLimit: + if (['_TZE200_5toc8efa', '_TZE204_5toc8efa'].includes(meta.device.manufacturerName)) { + return {max_temperature_limit: value / 10}; + } else { + return {max_temperature_limit: value}; + } + case dataPoints.moesMaxTemp: + if (['_TZE200_5toc8efa', '_TZE204_5toc8efa'].includes(meta.device.manufacturerName)) { + return {max_temperature: value / 10}; + } else { + return {max_temperature: value}; + } + case dataPoints.moesDeadZoneTemp: + if (['_TZE200_5toc8efa', '_TZE204_5toc8efa'].includes(meta.device.manufacturerName)) { + return {deadzone_temperature: value / 10}; + } else { + return {deadzone_temperature: value}; + } + case dataPoints.moesLocalTemp: + if (['_TZE200_5toc8efa', '_TZE204_5toc8efa'].includes(meta.device.manufacturerName)) { + temperature = value / 10; + } else { + temperature = value & (1 << 15) ? value - (1 << 16) + 1 : value; + if (!['_TZE200_ztvwu4nk', '_TZE200_ye5jkfsb'].includes(meta.device.manufacturerName)) { + // https://github.com/Koenkk/zigbee2mqtt/issues/11980 + temperature = temperature / 10; + } + } + temperature = parseFloat(temperature.toFixed(1)); + if (temperature < 100) { + return {local_temperature: parseFloat(temperature.toFixed(1))}; + } + break; + case dataPoints.moesTempCalibration: + temperature = value; + // for negative values produce complimentary hex (equivalent to negative values) + if (temperature > 4000) temperature = temperature - 4096; + return {local_temperature_calibration: temperature}; + case dataPoints.moesScheduleEnable: // state is inverted, preset_mode is deprecated + return {preset_mode: value ? 'hold' : 'program', preset: value ? 'hold' : 'program'}; + case dataPoints.moesValve: + return {heat: value ? 'OFF' : 'ON', running_state: value ? 'idle' : model.model === 'BAC-002-ALZB' ? 'cool' : 'heat'}; + case dataPoints.moesSensor: + switch (value) { + case 0: + return {sensor: 'IN'}; + case 1: + return {sensor: 'AL'}; + case 2: + return {sensor: 'OU'}; + default: + return {sensor: 'not_supported'}; + } + case dataPoints.bacFanMode: + return {fan_mode: fanModes[value]}; + default: // DataPoint 17 is unknown + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:moes_bht_022'); + } + }, + } satisfies Fz.Converter, + moesS_thermostat: { + cluster: 'manuSpecificTuya', + type: ['commandDataResponse', 'commandDataReport'], + convert: (model, msg, publish, options, meta) => { const dpValue = firstDpValue(msg, meta, 'moesS_thermostat'); const dp = dpValue.dp; // First we get the data point ID const value = getDataValue(dpValue); const presetLookup = {0: 'programming', 1: 'manual', 2: 'temporary_manual', 3: 'holiday'}; switch (dp) { - case dataPoints.moesSsystemMode: - // @ts-ignore - return {preset: presetLookup[value], system_mode: 'heat'}; - case dataPoints.moesSheatingSetpoint: - return {current_heating_setpoint: value}; - case dataPoints.moesSlocalTemp: - return {local_temperature: (value / 10)}; - case dataPoints.moesSboostHeating: - return {boost_heating: value ? 'ON' : 'OFF'}; - case dataPoints.moesSboostHeatingCountdown: - return {boost_heating_countdown: value}; - case dataPoints.moesSreset: - return {running_state: value ? 'idle' : 'heat', valve_state: value ? 'CLOSED' : 'OPEN'}; - case dataPoints.moesSwindowDetectionFunktion_A2: - return {window_detection: value ? 'ON' : 'OFF'}; - case dataPoints.moesSwindowDetection: - return {window: value ? 'CLOSED' : 'OPEN'}; - case dataPoints.moesSchildLock: - return {child_lock: value ? 'LOCK' : 'UNLOCK'}; - case dataPoints.moesSbattery: - return {battery: value}; - case dataPoints.moesSboostHeatingCountdownTimeSet: - return {boost_heating_countdown_time_set: (value)}; - case dataPoints.moesSvalvePosition: - return {position: value}; - case dataPoints.moesScompensationTempSet: - return { - local_temperature_calibration: value, - // local_temperature is now stale: the valve does not report the re-calibrated value until an actual temperature change - // so update local_temperature by subtracting the old calibration and adding the new one - ...(meta && meta.state && meta.state.local_temperature != null && meta.state.local_temperature_calibration != null) ? - // @ts-expect-error - {local_temperature: meta.state.local_temperature + (value - meta.state.local_temperature_calibration)} : - {}, - }; - case dataPoints.moesSecoMode: - return {eco_mode: value ? 'ON' : 'OFF'}; - case dataPoints.moesSecoModeTempSet: - return {eco_temperature: value}; - case dataPoints.moesSmaxTempSet: - return {max_temperature: value}; - case dataPoints.moesSminTempSet: - return {min_temperature: value}; - case dataPoints.moesSschedule: { - const items = []; - const pMode = []; - for (let i = 0; i < 12; i++) { - const item = {h: value[i*3], m: value[i*3+1], temp: value[i*3+2] / 2}; - items[i] = item; - pMode[i] = item['h'].toString().padStart(2, '0') + ':' + - item['m'].toString().padStart(2, '0') + '/' + - item['temp'] + '°C'; - } - return {programming_mode: pMode.join(' ')}; - } - default: - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:moes_s_thermostat'); + case dataPoints.moesSsystemMode: + // @ts-ignore + return {preset: presetLookup[value], system_mode: 'heat'}; + case dataPoints.moesSheatingSetpoint: + return {current_heating_setpoint: value}; + case dataPoints.moesSlocalTemp: + return {local_temperature: value / 10}; + case dataPoints.moesSboostHeating: + return {boost_heating: value ? 'ON' : 'OFF'}; + case dataPoints.moesSboostHeatingCountdown: + return {boost_heating_countdown: value}; + case dataPoints.moesSreset: + return {running_state: value ? 'idle' : 'heat', valve_state: value ? 'CLOSED' : 'OPEN'}; + case dataPoints.moesSwindowDetectionFunktion_A2: + return {window_detection: value ? 'ON' : 'OFF'}; + case dataPoints.moesSwindowDetection: + return {window: value ? 'CLOSED' : 'OPEN'}; + case dataPoints.moesSchildLock: + return {child_lock: value ? 'LOCK' : 'UNLOCK'}; + case dataPoints.moesSbattery: + return {battery: value}; + case dataPoints.moesSboostHeatingCountdownTimeSet: + return {boost_heating_countdown_time_set: value}; + case dataPoints.moesSvalvePosition: + return {position: value}; + case dataPoints.moesScompensationTempSet: + return { + local_temperature_calibration: value, + // local_temperature is now stale: the valve does not report the re-calibrated value until an actual temperature change + // so update local_temperature by subtracting the old calibration and adding the new one + ...(meta && meta.state && meta.state.local_temperature != null && meta.state.local_temperature_calibration != null + ? // @ts-expect-error + {local_temperature: meta.state.local_temperature + (value - meta.state.local_temperature_calibration)} + : {}), + }; + case dataPoints.moesSecoMode: + return {eco_mode: value ? 'ON' : 'OFF'}; + case dataPoints.moesSecoModeTempSet: + return {eco_temperature: value}; + case dataPoints.moesSmaxTempSet: + return {max_temperature: value}; + case dataPoints.moesSminTempSet: + return {min_temperature: value}; + case dataPoints.moesSschedule: { + const items = []; + const pMode = []; + for (let i = 0; i < 12; i++) { + const item = {h: value[i * 3], m: value[i * 3 + 1], temp: value[i * 3 + 2] / 2}; + items[i] = item; + pMode[i] = item['h'].toString().padStart(2, '0') + ':' + item['m'].toString().padStart(2, '0') + '/' + item['temp'] + '°C'; + } + return {programming_mode: pMode.join(' ')}; + } + default: + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:moes_s_thermostat'); } }, } satisfies Fz.Converter, @@ -3592,40 +3658,40 @@ const fromZigbee1 = { const dp = dpValue.dp; const value = getDataValue(dpValue); switch (dp) { - case dataPoints.tuyaSabTemp: - return {temperature: (value > 0x2000 ? value - 0xFFFF : value) / 10}; - case dataPoints.tuyaSabHumidity: - return {humidity: value / 10}; + case dataPoints.tuyaSabTemp: + return {temperature: (value > 0x2000 ? value - 0xffff : value) / 10}; + case dataPoints.tuyaSabHumidity: + return {humidity: value / 10}; // DP22: Smart Air Box: Formaldehyd, Smart Air Housekeeper: co2 - case dataPoints.tuyaSabFormaldehyd: - if (['_TZE200_dwcarsat', '_TZE200_ryfmq5rl', '_TZE200_mja3fuja'].includes(meta.device.manufacturerName)) { - return {co2: value}; - } else { - return {formaldehyd: value}; - } + case dataPoints.tuyaSabFormaldehyd: + if (['_TZE200_dwcarsat', '_TZE200_ryfmq5rl', '_TZE200_mja3fuja'].includes(meta.device.manufacturerName)) { + return {co2: value}; + } else { + return {formaldehyd: value}; + } // DP2: Smart Air Box: co2, Smart Air Housekeeper: MP25 - case dataPoints.tuyaSabCO2: - if (['_TZE200_dwcarsat'].includes(meta.device.manufacturerName)) { - // Ignore: https://github.com/Koenkk/zigbee2mqtt/issues/11033#issuecomment-1109808552 - if (value === 0xaaac || value === 0xaaab) return; - return {pm25: value}; - } else if (meta.device.manufacturerName === '_TZE200_ryfmq5rl') { - return {formaldehyd: value / 100}; - } else if (meta.device.manufacturerName === '_TZE200_mja3fuja') { + case dataPoints.tuyaSabCO2: + if (['_TZE200_dwcarsat'].includes(meta.device.manufacturerName)) { + // Ignore: https://github.com/Koenkk/zigbee2mqtt/issues/11033#issuecomment-1109808552 + if (value === 0xaaac || value === 0xaaab) return; + return {pm25: value}; + } else if (meta.device.manufacturerName === '_TZE200_ryfmq5rl') { + return {formaldehyd: value / 100}; + } else if (meta.device.manufacturerName === '_TZE200_mja3fuja') { + return {formaldehyd: value}; + } else { + return {co2: value}; + } + case dataPoints.tuyaSabVOC: + if (meta.device.manufacturerName === '_TZE200_ryfmq5rl') { + return {voc: value / 10}; + } else { + return {voc: value}; + } + case dataPoints.tuyaSahkFormaldehyd: return {formaldehyd: value}; - } else { - return {co2: value}; - } - case dataPoints.tuyaSabVOC: - if (meta.device.manufacturerName === '_TZE200_ryfmq5rl') { - return {voc: value / 10}; - } else { - return {voc: value}; - } - case dataPoints.tuyaSahkFormaldehyd: - return {formaldehyd: value}; - default: - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:tuya_air_quality'); + default: + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:tuya_air_quality'); } }, } satisfies Fz.Converter, @@ -3637,12 +3703,12 @@ const fromZigbee1 = { const dp = dpValue.dp; const value = getDataValue(dpValue); switch (dp) { - case dataPoints.tuyaSabCO: - return {co: value / 100}; - case dataPoints.tuyaSabCOalarm: - return {carbon_monoxide: value ? 'OFF' : 'ON'}; - default: - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:tuya_co'); + case dataPoints.tuyaSabCO: + return {co: value / 100}; + case dataPoints.tuyaSabCOalarm: + return {carbon_monoxide: value ? 'OFF' : 'ON'}; + default: + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:tuya_co'); } }, } satisfies Fz.Converter, @@ -3655,43 +3721,43 @@ const fromZigbee1 = { const value = getDataValue(dpValue); switch (dp) { - case dataPoints.connecteState: - return {state: value ? 'ON' : 'OFF'}; - case dataPoints.connecteMode: - switch (value) { - case 0: // manual - return {system_mode: 'heat', away_mode: 'OFF'}; - case 1: // home (auto) - return {system_mode: 'auto', away_mode: 'OFF'}; - case 2: // away (auto) - return {system_mode: 'auto', away_mode: 'ON'}; - } - break; - case dataPoints.connecteHeatingSetpoint: - return {current_heating_setpoint: value}; - case dataPoints.connecteLocalTemp: - return {local_temperature: value}; - case dataPoints.connecteTempCalibration: - return {local_temperature_calibration: value}; - case dataPoints.connecteChildLock: - return {child_lock: value ? 'LOCK' : 'UNLOCK'}; - case dataPoints.connecteTempFloor: - return {external_temperature: value}; - case dataPoints.connecteSensorType: - // @ts-ignore - return {sensor: {0: 'internal', 1: 'external', 2: 'both'}[value]}; - case dataPoints.connecteHysteresis: - return {hysteresis: value}; - case dataPoints.connecteRunningState: - return {running_state: value ? 'heat' : 'idle'}; - case dataPoints.connecteTempProgram: - break; - case dataPoints.connecteOpenWindow: - return {window_detection: value ? 'ON' : 'OFF'}; - case dataPoints.connecteMaxProtectTemp: - return {max_temperature_protection: value}; - default: - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:connecte_thermostat'); + case dataPoints.connecteState: + return {state: value ? 'ON' : 'OFF'}; + case dataPoints.connecteMode: + switch (value) { + case 0: // manual + return {system_mode: 'heat', away_mode: 'OFF'}; + case 1: // home (auto) + return {system_mode: 'auto', away_mode: 'OFF'}; + case 2: // away (auto) + return {system_mode: 'auto', away_mode: 'ON'}; + } + break; + case dataPoints.connecteHeatingSetpoint: + return {current_heating_setpoint: value}; + case dataPoints.connecteLocalTemp: + return {local_temperature: value}; + case dataPoints.connecteTempCalibration: + return {local_temperature_calibration: value}; + case dataPoints.connecteChildLock: + return {child_lock: value ? 'LOCK' : 'UNLOCK'}; + case dataPoints.connecteTempFloor: + return {external_temperature: value}; + case dataPoints.connecteSensorType: + // @ts-ignore + return {sensor: {0: 'internal', 1: 'external', 2: 'both'}[value]}; + case dataPoints.connecteHysteresis: + return {hysteresis: value}; + case dataPoints.connecteRunningState: + return {running_state: value ? 'heat' : 'idle'}; + case dataPoints.connecteTempProgram: + break; + case dataPoints.connecteOpenWindow: + return {window_detection: value ? 'ON' : 'OFF'}; + case dataPoints.connecteMaxProtectTemp: + return {max_temperature_protection: value}; + default: + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:connecte_thermostat'); } }, } satisfies Fz.Converter, @@ -3704,117 +3770,117 @@ const fromZigbee1 = { const value = getDataValue(dpValue); switch (dp) { - case dataPoints.saswellHeating: - // heating status 1 - heating - return {'heating': value ? 'ON' : 'OFF'}; - case dataPoints.saswellWindowDetection: - return {window_detection: value ? 'ON' : 'OFF'}; - case dataPoints.saswellFrostDetection: - return {frost_detection: value ? 'ON' : 'OFF'}; - case dataPoints.saswellTempCalibration: - return {local_temperature_calibration: value > 6 ? 0xFFFFFFFF - value : value}; - case dataPoints.saswellChildLock: - return {child_lock: value ? 'LOCK' : 'UNLOCK'}; - case dataPoints.saswellState: - return {system_mode: value ? 'heat' : 'off'}; - case dataPoints.saswellLocalTemp: - return {local_temperature: parseFloat((value / 10).toFixed(1))}; - case dataPoints.saswellHeatingSetpoint: - return {current_heating_setpoint: parseFloat((value / 10).toFixed(1))}; - case dataPoints.saswellValvePos: - // single value 1-100% - break; - case dataPoints.saswellBatteryLow: - return {battery_low: value ? true : false}; - case dataPoints.saswellAwayMode: - if (value) { - return {away_mode: 'ON', preset_mode: 'away'}; - } else { - return {away_mode: 'OFF', preset_mode: 'none'}; - } - case dataPoints.saswellScheduleMode: - if (thermostatScheduleMode.hasOwnProperty(value)) { - return {schedule_mode: thermostatScheduleMode[value]}; - } else { - logger.warning(`Unknown schedule mode ${value}`, 'zhc:legacy:fz:saswell_thermostat'); - } - break; - case dataPoints.saswellScheduleEnable: - if ( value ) { - return {system_mode: 'auto'}; - } - break; - case dataPoints.saswellScheduleSet: - // Never seen being reported, but put here to prevent warnings - break; - case dataPoints.saswellSetpointHistoryDay: - // 24 values - 1 value for each hour - break; - case dataPoints.saswellTimeSync: - // uint8: year - 2000 - // uint8: month (1-12) - // uint8: day (1-21) - // uint8: hour (0-23) - // uint8: minute (0-59) - break; - case dataPoints.saswellSetpointHistoryWeek: - // 7 values - 1 value for each day - break; - case dataPoints.saswellSetpointHistoryMonth: - // 31 values - 1 value for each day - break; - case dataPoints.saswellSetpointHistoryYear: - // 12 values - 1 value for each month - break; - case dataPoints.saswellLocalHistoryDay: - // 24 values - 1 value for each hour - break; - case dataPoints.saswellLocalHistoryWeek: - // 7 values - 1 value for each day - break; - case dataPoints.saswellLocalHistoryMonth: - // 31 values - 1 value for each day - break; - case dataPoints.saswellLocalHistoryYear: - // 12 values - 1 value for each month - break; - case dataPoints.saswellMotorHistoryDay: - // 24 values - 1 value for each hour - break; - case dataPoints.saswellMotorHistoryWeek: - // 7 values - 1 value for each day - break; - case dataPoints.saswellMotorHistoryMonth: - // 31 values - 1 value for each day - break; - case dataPoints.saswellMotorHistoryYear: - // 12 values - 1 value for each month - break; - case dataPoints.saswellScheduleSunday: - case dataPoints.saswellScheduleMonday: - case dataPoints.saswellScheduleTuesday: - case dataPoints.saswellScheduleWednesday: - case dataPoints.saswellScheduleThursday: - case dataPoints.saswellScheduleFriday: - case dataPoints.saswellScheduleSaturday: - // Handled by tuya_thermostat_weekly_schedule - // Schedule for each day - // [ - // uint8: schedule mode - see above, - // uint16: time (60 * hour + minute) - // uint16: temperature * 10 - // uint16: time (60 * hour + minute) - // uint16: temperature * 10 - // uint16: time (60 * hour + minute) - // uint16: temperature * 10 - // uint16: time (60 * hour + minute) - // uint16: temperature * 10 - // ] - break; - case dataPoints.saswellAntiScaling: - return {anti_scaling: value ? 'ON' : 'OFF'}; - default: - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:saswell_thermostat'); + case dataPoints.saswellHeating: + // heating status 1 - heating + return {heating: value ? 'ON' : 'OFF'}; + case dataPoints.saswellWindowDetection: + return {window_detection: value ? 'ON' : 'OFF'}; + case dataPoints.saswellFrostDetection: + return {frost_detection: value ? 'ON' : 'OFF'}; + case dataPoints.saswellTempCalibration: + return {local_temperature_calibration: value > 6 ? 0xffffffff - value : value}; + case dataPoints.saswellChildLock: + return {child_lock: value ? 'LOCK' : 'UNLOCK'}; + case dataPoints.saswellState: + return {system_mode: value ? 'heat' : 'off'}; + case dataPoints.saswellLocalTemp: + return {local_temperature: parseFloat((value / 10).toFixed(1))}; + case dataPoints.saswellHeatingSetpoint: + return {current_heating_setpoint: parseFloat((value / 10).toFixed(1))}; + case dataPoints.saswellValvePos: + // single value 1-100% + break; + case dataPoints.saswellBatteryLow: + return {battery_low: value ? true : false}; + case dataPoints.saswellAwayMode: + if (value) { + return {away_mode: 'ON', preset_mode: 'away'}; + } else { + return {away_mode: 'OFF', preset_mode: 'none'}; + } + case dataPoints.saswellScheduleMode: + if (thermostatScheduleMode.hasOwnProperty(value)) { + return {schedule_mode: thermostatScheduleMode[value]}; + } else { + logger.warning(`Unknown schedule mode ${value}`, 'zhc:legacy:fz:saswell_thermostat'); + } + break; + case dataPoints.saswellScheduleEnable: + if (value) { + return {system_mode: 'auto'}; + } + break; + case dataPoints.saswellScheduleSet: + // Never seen being reported, but put here to prevent warnings + break; + case dataPoints.saswellSetpointHistoryDay: + // 24 values - 1 value for each hour + break; + case dataPoints.saswellTimeSync: + // uint8: year - 2000 + // uint8: month (1-12) + // uint8: day (1-21) + // uint8: hour (0-23) + // uint8: minute (0-59) + break; + case dataPoints.saswellSetpointHistoryWeek: + // 7 values - 1 value for each day + break; + case dataPoints.saswellSetpointHistoryMonth: + // 31 values - 1 value for each day + break; + case dataPoints.saswellSetpointHistoryYear: + // 12 values - 1 value for each month + break; + case dataPoints.saswellLocalHistoryDay: + // 24 values - 1 value for each hour + break; + case dataPoints.saswellLocalHistoryWeek: + // 7 values - 1 value for each day + break; + case dataPoints.saswellLocalHistoryMonth: + // 31 values - 1 value for each day + break; + case dataPoints.saswellLocalHistoryYear: + // 12 values - 1 value for each month + break; + case dataPoints.saswellMotorHistoryDay: + // 24 values - 1 value for each hour + break; + case dataPoints.saswellMotorHistoryWeek: + // 7 values - 1 value for each day + break; + case dataPoints.saswellMotorHistoryMonth: + // 31 values - 1 value for each day + break; + case dataPoints.saswellMotorHistoryYear: + // 12 values - 1 value for each month + break; + case dataPoints.saswellScheduleSunday: + case dataPoints.saswellScheduleMonday: + case dataPoints.saswellScheduleTuesday: + case dataPoints.saswellScheduleWednesday: + case dataPoints.saswellScheduleThursday: + case dataPoints.saswellScheduleFriday: + case dataPoints.saswellScheduleSaturday: + // Handled by tuya_thermostat_weekly_schedule + // Schedule for each day + // [ + // uint8: schedule mode - see above, + // uint16: time (60 * hour + minute) + // uint16: temperature * 10 + // uint16: time (60 * hour + minute) + // uint16: temperature * 10 + // uint16: time (60 * hour + minute) + // uint16: temperature * 10 + // uint16: time (60 * hour + minute) + // uint16: temperature * 10 + // ] + break; + case dataPoints.saswellAntiScaling: + return {anti_scaling: value ? 'ON' : 'OFF'}; + default: + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:saswell_thermostat'); } }, } satisfies Fz.Converter, @@ -3826,36 +3892,36 @@ const fromZigbee1 = { for (const dpValue of msg.data.dpValues) { const value = getDataValue(dpValue); switch (dpValue.dp) { - case dataPoints.evanellChildLock: - result.child_lock = value ? 'LOCK' : 'UNLOCK'; - break; - case dataPoints.evanellBattery: - result.battery = value; - break; - case dataPoints.evanellHeatingSetpoint: - result.current_heating_setpoint = value/10; - break; - case dataPoints.evanellLocalTemp: - result.local_temperature = value/10; - break; - case dataPoints.evanellMode: - switch (value) { - case 0: // manual - result.system_mode = 'auto'; + case dataPoints.evanellChildLock: + result.child_lock = value ? 'LOCK' : 'UNLOCK'; break; - case 2: // away - result.system_mode = 'heat'; + case dataPoints.evanellBattery: + result.battery = value; break; - case 3: // auto - result.system_mode = 'off'; + case dataPoints.evanellHeatingSetpoint: + result.current_heating_setpoint = value / 10; break; - default: - logger.warning(`Mode ${value} is not recognized.`, 'zhc:legacy:fz:evanell_thermostat'); + case dataPoints.evanellLocalTemp: + result.local_temperature = value / 10; break; - } - break; - default: - logger.debug(`Unrecognized DP #${dpValue.dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:evanell_thermostat'); + case dataPoints.evanellMode: + switch (value) { + case 0: // manual + result.system_mode = 'auto'; + break; + case 2: // away + result.system_mode = 'heat'; + break; + case 3: // auto + result.system_mode = 'off'; + break; + default: + logger.warning(`Mode ${value} is not recognized.`, 'zhc:legacy:fz:evanell_thermostat'); + break; + } + break; + default: + logger.debug(`Unrecognized DP #${dpValue.dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:evanell_thermostat'); } } return result; @@ -3869,43 +3935,43 @@ const fromZigbee1 = { const dp = dpValue.dp; const value = getDataValue(dpValue); - if (dp >= 101 && dp <=107) return; // handled by tuya_thermostat_weekly_schedule + if (dp >= 101 && dp <= 107) return; // handled by tuya_thermostat_weekly_schedule switch (dp) { - case dataPoints.state: // on/off - return !value ? {system_mode: 'off'} : {}; - case dataPoints.etopErrorStatus: - return { - high_temperature: (value & 1<<0) > 0 ? 'ON' : 'OFF', - low_temperature: (value & 1<<1) > 0 ? 'ON' : 'OFF', - internal_sensor_error: (value & 1<<2) > 0 ? 'ON' : 'OFF', - external_sensor_error: (value & 1<<3) > 0 ? 'ON' : 'OFF', - battery_low: (value & 1<<4) > 0, - device_offline: (value & 1<<5) > 0 ? 'ON' : 'OFF', - }; - case dataPoints.childLock: - return {child_lock: value ? 'LOCK' : 'UNLOCK'}; - case dataPoints.heatingSetpoint: - return {current_heating_setpoint: (value / 10).toFixed(1)}; - case dataPoints.localTemp: - return {local_temperature: (value / 10).toFixed(1)}; - case dataPoints.mode: - switch (value) { - case 0: // manual - return {system_mode: 'heat', away_mode: 'OFF', preset: 'none'}; - case 1: // away - return {system_mode: 'heat', away_mode: 'ON', preset: 'away'}; - case 2: // auto - return {system_mode: 'auto', away_mode: 'OFF', preset: 'none'}; - default: - logger.warning(`Preset ${value} is not recognized.`, 'zhc:legacy:fz:etop_thermostat'); + case dataPoints.state: // on/off + return !value ? {system_mode: 'off'} : {}; + case dataPoints.etopErrorStatus: + return { + high_temperature: (value & (1 << 0)) > 0 ? 'ON' : 'OFF', + low_temperature: (value & (1 << 1)) > 0 ? 'ON' : 'OFF', + internal_sensor_error: (value & (1 << 2)) > 0 ? 'ON' : 'OFF', + external_sensor_error: (value & (1 << 3)) > 0 ? 'ON' : 'OFF', + battery_low: (value & (1 << 4)) > 0, + device_offline: (value & (1 << 5)) > 0 ? 'ON' : 'OFF', + }; + case dataPoints.childLock: + return {child_lock: value ? 'LOCK' : 'UNLOCK'}; + case dataPoints.heatingSetpoint: + return {current_heating_setpoint: (value / 10).toFixed(1)}; + case dataPoints.localTemp: + return {local_temperature: (value / 10).toFixed(1)}; + case dataPoints.mode: + switch (value) { + case 0: // manual + return {system_mode: 'heat', away_mode: 'OFF', preset: 'none'}; + case 1: // away + return {system_mode: 'heat', away_mode: 'ON', preset: 'away'}; + case 2: // auto + return {system_mode: 'auto', away_mode: 'OFF', preset: 'none'}; + default: + logger.warning(`Preset ${value} is not recognized.`, 'zhc:legacy:fz:etop_thermostat'); + break; + } break; - } - break; - case dataPoints.runningState: - return {running_state: value ? 'heat' : 'idle'}; - default: - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:etop_thermostat'); + case dataPoints.runningState: + return {running_state: value ? 'heat' : 'idle'}; + default: + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:etop_thermostat'); } }, } satisfies Fz.Converter, @@ -3917,103 +3983,106 @@ const fromZigbee1 = { const dp = dpValue.dp; const value = getDataValue(dpValue); switch (dp) { - case dataPoints.windowOpen: - return {window_open: value}; - case dataPoints.windowDetection: - return { - window_detection: value[0] ? 'ON' : 'OFF', - window_detection_params: { - temperature: value[1], - minutes: value[2], - }, - }; - case dataPoints.scheduleWorkday: // set schedule for workdays/holidays [6,0,20,8,0,15,11,30,15,12,30,15,17,30,20,22,0,15] - case dataPoints.scheduleHoliday: { - // 6:00 - 20*, 8:00 - 15*, 11:30 - 15*, 12:30 - 15*, 17:30 - 20*, 22:00 - 15* - // Top bits in hours have special meaning - // 6: Current schedule indicator - const items = []; - const programmingMode = []; + case dataPoints.windowOpen: + return {window_open: value}; + case dataPoints.windowDetection: + return { + window_detection: value[0] ? 'ON' : 'OFF', + window_detection_params: { + temperature: value[1], + minutes: value[2], + }, + }; + case dataPoints.scheduleWorkday: // set schedule for workdays/holidays [6,0,20,8,0,15,11,30,15,12,30,15,17,30,20,22,0,15] + case dataPoints.scheduleHoliday: { + // 6:00 - 20*, 8:00 - 15*, 11:30 - 15*, 12:30 - 15*, 17:30 - 20*, 22:00 - 15* + // Top bits in hours have special meaning + // 6: Current schedule indicator + const items = []; + const programmingMode = []; + + for (let i = 0; i < 6; i++) { + const item: KeyValueAny = {hour: value[i * 3] & 0x3f, minute: value[i * 3 + 1], temperature: value[i * 3 + 2]}; + if (value[i * 3] & 0x40) { + item['current'] = true; + } - for (let i = 0; i < 6; i++) { - const item: KeyValueAny = {hour: value[i*3] & 0x3F, minute: value[i*3+1], temperature: value[i*3+2]}; - if (value[i*3] & 0x40) { - item['current'] = true; + items[i] = item; + programmingMode[i] = + item['hour'].toString().padStart(2, '0') + + ':' + + item['minute'].toString().padStart(2, '0') + + '/' + + item['temperature'] + + '°C'; } - items[i] = item; - programmingMode[i] = - item['hour'].toString().padStart(2, '0') + ':' + - item['minute'].toString().padStart(2, '0') + '/' + - item['temperature'] + '°C'; + if (dp == dataPoints.scheduleWorkday) { + return {workdays: items, workdays_schedule: programmingMode.join(' ')}; + } else { + return {holidays: items, holidays_schedule: programmingMode.join(' ')}; + } } - - if (dp == dataPoints.scheduleWorkday) { - return {workdays: items, workdays_schedule: programmingMode.join(' ')}; - } else { - return {holidays: items, holidays_schedule: programmingMode.join(' ')}; - } - } - case dataPoints.childLock: - return {child_lock: value ? 'LOCK' : 'UNLOCK'}; - case dataPoints.siterwellWindowDetection: - return {window_detection: value ? 'ON' : 'OFF'}; - case dataPoints.valveDetection: - return {valve_detection: value ? 'ON' : 'OFF'}; - case dataPoints.autoLock: // 0x7401 auto lock mode - return {auto_lock: value ? 'AUTO' : 'MANUAL'}; - case dataPoints.heatingSetpoint: - return {current_heating_setpoint: parseFloat((value / 10).toFixed(1))}; - case dataPoints.localTemp: - return {local_temperature: parseFloat((value / 10).toFixed(1))}; - case dataPoints.tempCalibration: - return {local_temperature_calibration: parseFloat((value / 10).toFixed(1))}; - case dataPoints.battery: // 0x1502 MCU reporting battery status - return {battery: value}; - case dataPoints.batteryLow: - return {battery_low: value}; - case dataPoints.minTemp: - return {min_temperature: value}; - case dataPoints.maxTemp: - return {max_temperature: value}; - case dataPoints.boostTime: // 0x6902 boost time - return {boost_time: value}; - case dataPoints.comfortTemp: - return {comfort_temperature: value}; - case dataPoints.ecoTemp: - return {eco_temperature: value}; - case dataPoints.valvePos: - return {position: value, running_state: value ? 'heat' : 'idle'}; - case dataPoints.awayTemp: - return {away_preset_temperature: value}; - case dataPoints.awayDays: - return {away_preset_days: value}; - case dataPoints.mode: { - const ret: KeyValueAny = {}; - const presetOk = getMetaValue(msg.endpoint, model, 'tuyaThermostatPreset').hasOwnProperty(value); - if (presetOk) { - ret.preset = getMetaValue(msg.endpoint, model, 'tuyaThermostatPreset')[value]; - ret.away_mode = ret.preset == 'away' ? 'ON' : 'OFF'; // Away is special HA mode - const presetToSystemMode = utils.getMetaValue(msg.endpoint, model, 'tuyaThermostatPresetToSystemMode', null, {}); - if (value in presetToSystemMode) { - // @ts-expect-error - ret.system_mode = presetToSystemMode[value]; + case dataPoints.childLock: + return {child_lock: value ? 'LOCK' : 'UNLOCK'}; + case dataPoints.siterwellWindowDetection: + return {window_detection: value ? 'ON' : 'OFF'}; + case dataPoints.valveDetection: + return {valve_detection: value ? 'ON' : 'OFF'}; + case dataPoints.autoLock: // 0x7401 auto lock mode + return {auto_lock: value ? 'AUTO' : 'MANUAL'}; + case dataPoints.heatingSetpoint: + return {current_heating_setpoint: parseFloat((value / 10).toFixed(1))}; + case dataPoints.localTemp: + return {local_temperature: parseFloat((value / 10).toFixed(1))}; + case dataPoints.tempCalibration: + return {local_temperature_calibration: parseFloat((value / 10).toFixed(1))}; + case dataPoints.battery: // 0x1502 MCU reporting battery status + return {battery: value}; + case dataPoints.batteryLow: + return {battery_low: value}; + case dataPoints.minTemp: + return {min_temperature: value}; + case dataPoints.maxTemp: + return {max_temperature: value}; + case dataPoints.boostTime: // 0x6902 boost time + return {boost_time: value}; + case dataPoints.comfortTemp: + return {comfort_temperature: value}; + case dataPoints.ecoTemp: + return {eco_temperature: value}; + case dataPoints.valvePos: + return {position: value, running_state: value ? 'heat' : 'idle'}; + case dataPoints.awayTemp: + return {away_preset_temperature: value}; + case dataPoints.awayDays: + return {away_preset_days: value}; + case dataPoints.mode: { + const ret: KeyValueAny = {}; + const presetOk = getMetaValue(msg.endpoint, model, 'tuyaThermostatPreset').hasOwnProperty(value); + if (presetOk) { + ret.preset = getMetaValue(msg.endpoint, model, 'tuyaThermostatPreset')[value]; + ret.away_mode = ret.preset == 'away' ? 'ON' : 'OFF'; // Away is special HA mode + const presetToSystemMode = utils.getMetaValue(msg.endpoint, model, 'tuyaThermostatPresetToSystemMode', null, {}); + if (value in presetToSystemMode) { + // @ts-expect-error + ret.system_mode = presetToSystemMode[value]; + } + } else { + logger.warning(`TRV preset ${value} is not recognized.`, 'zhc:legacy:fz:tuya_thermostat'); + return; } - } else { - logger.warning(`TRV preset ${value} is not recognized.`, 'zhc:legacy:fz:tuya_thermostat'); - return; + return ret; } - return ret; - } - // fan mode 0 - low , 1 - medium , 2 - high , 3 - auto ( tested on 6dfgetq TUYA zigbee module ) - case dataPoints.fanMode: - return {fan_mode: fanModes[value]}; - case dataPoints.forceMode: // force mode 0 - normal, 1 - open, 2 - close - return {system_mode: thermostatSystemModes3[value], force: thermostatForceMode[value]}; - case dataPoints.weekFormat: // Week select 0 - 5 days, 1 - 6 days, 2 - 7 days - return {week: thermostatWeekFormat[value]}; - default: // The purpose of the dps 17 & 19 is still unknown - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:tuya_thermostat'); + // fan mode 0 - low , 1 - medium , 2 - high , 3 - auto ( tested on 6dfgetq TUYA zigbee module ) + case dataPoints.fanMode: + return {fan_mode: fanModes[value]}; + case dataPoints.forceMode: // force mode 0 - normal, 1 - open, 2 - close + return {system_mode: thermostatSystemModes3[value], force: thermostatForceMode[value]}; + case dataPoints.weekFormat: // Week select 0 - 5 days, 1 - 6 days, 2 - 7 days + return {week: thermostatWeekFormat[value]}; + default: // The purpose of the dps 17 & 19 is still unknown + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:tuya_thermostat'); } }, } satisfies Fz.Converter, @@ -4024,7 +4093,7 @@ const fromZigbee1 = { const dpValue = firstDpValue(msg, meta, 'tuya_dimmer'); const value = getDataValue(dpValue); if (dpValue.dp === dataPoints.state) { - return {state: value ? 'ON': 'OFF'}; + return {state: value ? 'ON' : 'OFF'}; } else if (meta.device.manufacturerName === '_TZE200_swaamsoy') { // https://github.com/Koenkk/zigbee-herdsman-converters/pull/3004 if (dpValue.dp === 2) { @@ -4033,8 +4102,7 @@ const fromZigbee1 = { } return {brightness: utils.mapNumberRange(value, 10, 1000, 0, 254)}; } - } else if (['_TZE200_3p5ydos3', '_TZE200_9i9dt8is', '_TZE200_dfxkcots', '_TZE200_w4cryh2i'] - .includes(meta.device.manufacturerName)) { + } else if (['_TZE200_3p5ydos3', '_TZE200_9i9dt8is', '_TZE200_dfxkcots', '_TZE200_w4cryh2i'].includes(meta.device.manufacturerName)) { if (dpValue.dp === dataPoints.eardaDimmerLevel) { return {brightness: utils.mapNumberRange(value, 0, 1000, 0, 254)}; } else if (dpValue.dp === dataPoints.dimmerMinLevel) { @@ -4068,43 +4136,43 @@ const fromZigbee1 = { const value = getDataValue(dpValue); let result = null; switch (dp) { - case dataPoints.state: - // @ts-ignore - result = {occupancy: {1: true, 0: false}[value]}; - break; - case dataPoints.msReferenceLuminance: - result = {reference_luminance: value}; - break; - case dataPoints.msOSensitivity: - result = {o_sensitivity: msLookups.OSensitivity[value]}; - break; - case dataPoints.msVSensitivity: - result = {v_sensitivity: msLookups.VSensitivity[value]}; - break; - case dataPoints.msLedStatus: - // @ts-ignore - result = {led_status: {1: 'OFF', 0: 'ON'}[value]}; - break; - case dataPoints.msVacancyDelay: - result = {vacancy_delay: value}; - break; - case dataPoints.msLightOnLuminancePrefer: - result = {light_on_luminance_prefer: value}; - break; - case dataPoints.msLightOffLuminancePrefer: - result = {light_off_luminance_prefer: value}; - break; - case dataPoints.msMode: - result = {mode: msLookups.Mode[value]}; - break; - case dataPoints.msVacantConfirmTime: - result = {vacant_confirm_time: value}; - break; - case dataPoints.msLuminanceLevel: - result = {luminance_level: value}; - break; - default: - logger.debug(`Unrecognized DP ${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:tuya_motion_sensor'); + case dataPoints.state: + // @ts-ignore + result = {occupancy: {1: true, 0: false}[value]}; + break; + case dataPoints.msReferenceLuminance: + result = {reference_luminance: value}; + break; + case dataPoints.msOSensitivity: + result = {o_sensitivity: msLookups.OSensitivity[value]}; + break; + case dataPoints.msVSensitivity: + result = {v_sensitivity: msLookups.VSensitivity[value]}; + break; + case dataPoints.msLedStatus: + // @ts-ignore + result = {led_status: {1: 'OFF', 0: 'ON'}[value]}; + break; + case dataPoints.msVacancyDelay: + result = {vacancy_delay: value}; + break; + case dataPoints.msLightOnLuminancePrefer: + result = {light_on_luminance_prefer: value}; + break; + case dataPoints.msLightOffLuminancePrefer: + result = {light_off_luminance_prefer: value}; + break; + case dataPoints.msMode: + result = {mode: msLookups.Mode[value]}; + break; + case dataPoints.msVacantConfirmTime: + result = {vacant_confirm_time: value}; + break; + case dataPoints.msLuminanceLevel: + result = {luminance_level: value}; + break; + default: + logger.debug(`Unrecognized DP ${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:tuya_motion_sensor'); } return result; @@ -4118,20 +4186,20 @@ const fromZigbee1 = { for (const dpValue of msg.data.dpValues) { const value = getDataValue(dpValue); switch (dpValue.dp) { - case dataPoints.state: - result.contact = !value; - break; - case dataPoints.thitBatteryPercentage: - result.battery = value; - break; - case dataPoints.tuyaVibration: - result.vibration = Boolean(value); - break; - default: - logger.debug( - `Unrecognized DP #${dpValue.dp} with data ${JSON.stringify(dpValue)}`, - 'zhc:legacy:fz:tuya_smart_vibration_sensor', - ); + case dataPoints.state: + result.contact = !value; + break; + case dataPoints.thitBatteryPercentage: + result.battery = value; + break; + case dataPoints.tuyaVibration: + result.vibration = Boolean(value); + break; + default: + logger.debug( + `Unrecognized DP #${dpValue.dp} with data ${JSON.stringify(dpValue)}`, + 'zhc:legacy:fz:tuya_smart_vibration_sensor', + ); } } return result; @@ -4145,20 +4213,20 @@ const fromZigbee1 = { for (const dpValue of msg.data.dpValues) { const value = getDataValue(dpValue); switch (dpValue.dp) { - case dataPoints.garageDoorTrigger: - result.action = 'trigger'; - break; - case dataPoints.garageDoorContact: - result.garage_door_contact = Boolean(!value); - break; - case dataPoints.garageDoorStatus: - // This reports a garage door status (open, closed), but it is very naive and misleading - break; - default: - logger.debug( - `Unrecognized DP #${dpValue.dp} with data ${JSON.stringify(dpValue)}`, - 'zhc:legacy:fz:matsee_garage_door_opener', - ); + case dataPoints.garageDoorTrigger: + result.action = 'trigger'; + break; + case dataPoints.garageDoorContact: + result.garage_door_contact = Boolean(!value); + break; + case dataPoints.garageDoorStatus: + // This reports a garage door status (open, closed), but it is very naive and misleading + break; + default: + logger.debug( + `Unrecognized DP #${dpValue.dp} with data ${JSON.stringify(dpValue)}`, + 'zhc:legacy:fz:matsee_garage_door_opener', + ); } } return result; @@ -4173,80 +4241,80 @@ const fromZigbee1 = { let value = getDataValue(dpValue); let result = null; switch (dp) { - case dataPoints.tvMode: - switch (value) { - case 1: // manual - result = {system_mode: 'heat', preset: 'manual'}; + case dataPoints.tvMode: + switch (value) { + case 1: // manual + result = {system_mode: 'heat', preset: 'manual'}; + break; + case 2: // holiday + result = {system_mode: 'heat', preset: 'holiday'}; + break; + case 0: // auto + result = {system_mode: 'auto', preset: 'schedule'}; + break; + default: + logger.warning(`Preset ${value} is not recognized.`, 'zhc:legacy:fz:moes_thermostat_tv'); + break; + } break; - case 2: // holiday - result = {system_mode: 'heat', preset: 'holiday'}; + case dataPoints.tvWindowDetection: + // @ts-ignore + result = {window_detection: {1: true, 0: false}[value]}; break; - case 0: // auto - result = {system_mode: 'auto', preset: 'schedule'}; + case dataPoints.tvFrostDetection: + // @ts-ignore + result = {frost_detection: {1: true, 0: false}[value]}; break; - default: - logger.warning(`Preset ${value} is not recognized.`, 'zhc:legacy:fz:moes_thermostat_tv'); + case dataPoints.tvHeatingSetpoint: + result = {current_heating_setpoint: (value / 10).toFixed(1)}; break; - } - break; - case dataPoints.tvWindowDetection: - // @ts-ignore - result = {window_detection: {1: true, 0: false}[value]}; - break; - case dataPoints.tvFrostDetection: - // @ts-ignore - result = {frost_detection: {1: true, 0: false}[value]}; - break; - case dataPoints.tvHeatingSetpoint: - result = {current_heating_setpoint: (value / 10).toFixed(1)}; - break; - case dataPoints.tvLocalTemp: - result = {local_temperature: (value / 10).toFixed(1)}; - break; - case dataPoints.tvTempCalibration: - value = value > 0x7FFFFFFF ? 0xFFFFFFFF - value : value; - result = {local_temperature_calibration: (value / 10).toFixed(1)}; - break; - case dataPoints.tvHolidayTemp: - result = {holiday_temperature: (value / 10).toFixed(1)}; - break; - case dataPoints.tvBattery: - result = {battery: value}; - break; - case dataPoints.tvChildLock: - // @ts-ignore - result = {child_lock: {1: 'LOCK', 0: 'UNLOCK'}[value]}; - break; - case dataPoints.tvErrorStatus: - result = {error: value}; - break; - case dataPoints.tvHolidayMode: - result = {holiday_mode: value}; - break; - // case dataPoints.tvBoostMode: - // result = {boost_mode: {1: false, 0: true}[value]}; - // break; - case dataPoints.tvBoostTime: - result = {boost_heating_countdown: value}; - break; - case dataPoints.tvOpenWindowTemp: - result = {open_window_temperature: (value / 10).toFixed(1)}; - break; - case dataPoints.tvComfortTemp: - result = {comfort_temperature: (value / 10).toFixed(1)}; - break; - case dataPoints.tvEcoTemp: - result = {eco_temperature: (value / 10).toFixed(1)}; - break; - case dataPoints.tvHeatingStop: - if (value == 1) { - result = {system_mode: 'off', heating_stop: true}; - } else { - result = {heating_stop: false}; - } - break; - default: - logger.debug(`Unrecognized DP ${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:moes_thermostat_tv'); + case dataPoints.tvLocalTemp: + result = {local_temperature: (value / 10).toFixed(1)}; + break; + case dataPoints.tvTempCalibration: + value = value > 0x7fffffff ? 0xffffffff - value : value; + result = {local_temperature_calibration: (value / 10).toFixed(1)}; + break; + case dataPoints.tvHolidayTemp: + result = {holiday_temperature: (value / 10).toFixed(1)}; + break; + case dataPoints.tvBattery: + result = {battery: value}; + break; + case dataPoints.tvChildLock: + // @ts-ignore + result = {child_lock: {1: 'LOCK', 0: 'UNLOCK'}[value]}; + break; + case dataPoints.tvErrorStatus: + result = {error: value}; + break; + case dataPoints.tvHolidayMode: + result = {holiday_mode: value}; + break; + // case dataPoints.tvBoostMode: + // result = {boost_mode: {1: false, 0: true}[value]}; + // break; + case dataPoints.tvBoostTime: + result = {boost_heating_countdown: value}; + break; + case dataPoints.tvOpenWindowTemp: + result = {open_window_temperature: (value / 10).toFixed(1)}; + break; + case dataPoints.tvComfortTemp: + result = {comfort_temperature: (value / 10).toFixed(1)}; + break; + case dataPoints.tvEcoTemp: + result = {eco_temperature: (value / 10).toFixed(1)}; + break; + case dataPoints.tvHeatingStop: + if (value == 1) { + result = {system_mode: 'off', heating_stop: true}; + } else { + result = {heating_stop: false}; + } + break; + default: + logger.debug(`Unrecognized DP ${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:moes_thermostat_tv'); } return result; @@ -4272,27 +4340,27 @@ const fromZigbee1 = { result.child_lock = value ? 'ON' : 'OFF'; } if (dp === dataPoints.hochVoltage) { - result.voltage = (value[1] | value[0] << 8) / 10; + result.voltage = (value[1] | (value[0] << 8)) / 10; } if (dp === dataPoints.hochHistoricalVoltage) { - result.voltage_rms = (value[1] | value[0] << 8) / 10; + result.voltage_rms = (value[1] | (value[0] << 8)) / 10; } if (dp === dataPoints.hochCurrent) { - result.current = (value[2] | value[1] << 8) / 1000; + result.current = (value[2] | (value[1] << 8)) / 1000; } if (dp === dataPoints.hochHistoricalCurrent) { - result.current_average = (value[2] | value[1] << 8) / 1000; + result.current_average = (value[2] | (value[1] << 8)) / 1000; } if (dp === dataPoints.hochActivePower) { - result.power = (value[2] | value[1] << 8) / 10; + result.power = (value[2] | (value[1] << 8)) / 10; if (value.length > 3) { - result.power_l1 = (value[5] | value[4] << 8) / 10; + result.power_l1 = (value[5] | (value[4] << 8)) / 10; } if (value.length > 6) { - result.power_l2 = (value[8] | value[7] << 8) / 10; + result.power_l2 = (value[8] | (value[7] << 8)) / 10; } if (value.length > 9) { - result.power_l3 = (value[11] | value[10] << 8) / 10; + result.power_l3 = (value[11] | (value[10] << 8)) / 10; } } if (dp === dataPoints.hochTotalActivePower) { @@ -4333,10 +4401,10 @@ const fromZigbee1 = { result.meter_number = value.trim(); } if (dp === dataPoints.hochVoltageThreshold) { - result.over_voltage_threshold = (value[1] | value[0] << 8) / 10; + result.over_voltage_threshold = (value[1] | (value[0] << 8)) / 10; result.over_voltage_trip = value[2] ? 'ON' : 'OFF'; result.over_voltage_alarm = value[3] ? 'ON' : 'OFF'; - result.under_voltage_threshold = (value[5] | value[4] << 8) / 10; + result.under_voltage_threshold = (value[5] | (value[4] << 8)) / 10; result.under_voltage_trip = value[6] ? 'ON' : 'OFF'; result.under_voltage_alarm = value[7] ? 'ON' : 'OFF'; } @@ -4359,7 +4427,7 @@ const fromZigbee1 = { result.self_test_auto_days = value[0]; result.self_test_auto_hours = value[1]; result.self_test_auto = value[2] ? 'ON' : 'OFF'; - result.over_leakage_current_threshold = value[4] | value[3] << 8; + result.over_leakage_current_threshold = value[4] | (value[3] << 8); result.over_leakage_current_trip = value[5] ? 'ON' : 'OFF'; result.over_leakage_current_alarm = value[6] ? 'ON' : 'OFF'; result.self_test = value[7] ? 'test' : 'clear'; @@ -4371,14 +4439,13 @@ const fromZigbee1 = { cluster: 'manuSpecificTuya', type: ['commandDataResponse', 'commandDataReport'], convert: (model, msg, publish, options, meta) => { - const separateWhite = (model.meta && model.meta.separateWhite); + const separateWhite = model.meta && model.meta.separateWhite; const result: KeyValueAny = {}; - // eslint-disable-next-line no-unused-vars for (const [i, dpValue] of msg.data.dpValues.entries()) { const dp = dpValue.dp; const value = getDataValue(dpValue); if (dp === dataPoints.state) { - result.state = value ? 'ON': 'OFF'; + result.state = value ? 'ON' : 'OFF'; } else if (dp === dataPoints.silvercrestSetBrightness) { const brightness = utils.mapNumberRange(value, 0, 1000, 0, 255); if (separateWhite) { @@ -4410,77 +4477,79 @@ const fromZigbee1 = { const dp = dpValue.dp; const value = getDataValue(dpValue); switch (dp) { - case dataPoints.coverPosition: // Started moving to position (triggered from Zigbee) - case dataPoints.coverArrived: { // Arrived at position - const running = dp === dataPoints.coverArrived ? false : true; - const invert = isCoverInverted(meta.device.manufacturerName) ? !options.invert_cover : options.invert_cover; - const position = invert ? 100 - (value & 0xFF) : (value & 0xFF); - if (position > 0 && position <= 100) { - return {running, position, state: 'OPEN'}; - } else if (position == 0) { // Report fully closed - return {running, position, state: 'CLOSE'}; - } else { - return {running}; // Not calibrated yet, no position is available + case dataPoints.coverPosition: // Started moving to position (triggered from Zigbee) + case dataPoints.coverArrived: { + // Arrived at position + const running = dp === dataPoints.coverArrived ? false : true; + const invert = isCoverInverted(meta.device.manufacturerName) ? !options.invert_cover : options.invert_cover; + const position = invert ? 100 - (value & 0xff) : value & 0xff; + if (position > 0 && position <= 100) { + return {running, position, state: 'OPEN'}; + } else if (position == 0) { + // Report fully closed + return {running, position, state: 'CLOSE'}; + } else { + return {running}; // Not calibrated yet, no position is available + } } - } - case dataPoints.coverSpeed: // Cover is reporting its current speed setting - return {motor_speed: value}; - case dataPoints.state: // Ignore the cover state, it's not reliable between different covers! - case dataPoints.coverChange: // Ignore manual cover change, it's not reliable between different covers! - break; - case dataPoints.config: // Returned by configuration set; ignore - break; - case dataPoints.AM02MotorWorkingMode: - switch (value) { - case 0: // continuous 1 - return {motor_working_mode: 'continuous'}; - case 1: // intermittently - return {motor_working_mode: 'intermittently'}; - default: - logger.warning(`Mode ${value} is not recognized.`, 'zhc:legacy:fz:zmam02_cover'); + case dataPoints.coverSpeed: // Cover is reporting its current speed setting + return {motor_speed: value}; + case dataPoints.state: // Ignore the cover state, it's not reliable between different covers! + case dataPoints.coverChange: // Ignore manual cover change, it's not reliable between different covers! break; - } - break; - case dataPoints.AM02Border: - switch (value) { - case 0: // up - return {border: 'up'}; - case 1: // down - return {border: 'down'}; - case 2: // down_delete - return {border: 'down_delete'}; - default: - logger.warning(`Mode ${value} is not recognized.`, 'zhc:legacy:fz:zmam02_cover'); + case dataPoints.config: // Returned by configuration set; ignore break; - } - break; - case dataPoints.AM02Direction: - switch (value) { - case 0: - return {motor_direction: 'forward'}; - case 1: - return {motor_direction: 'back'}; - default: - logger.warning(`Mode ${value} is not recognized.`, 'zhc:legacy:fz:zmam02_cover'); + case dataPoints.AM02MotorWorkingMode: + switch (value) { + case 0: // continuous 1 + return {motor_working_mode: 'continuous'}; + case 1: // intermittently + return {motor_working_mode: 'intermittently'}; + default: + logger.warning(`Mode ${value} is not recognized.`, 'zhc:legacy:fz:zmam02_cover'); + break; + } break; - } - break; - case dataPoints.AM02Mode: - switch (value) { - case 0: // morning - return {mode: 'morning'}; - case 1: // night - return {mode: 'night'}; - default: - logger.warning(`Mode ${value} is not recognized.`, 'zhc:legacy:fz:zmam02_cover'); + case dataPoints.AM02Border: + switch (value) { + case 0: // up + return {border: 'up'}; + case 1: // down + return {border: 'down'}; + case 2: // down_delete + return {border: 'down_delete'}; + default: + logger.warning(`Mode ${value} is not recognized.`, 'zhc:legacy:fz:zmam02_cover'); + break; + } break; - } - break; - case dataPoints.AM02AddRemoter: // DP 101: Ignore until need is defined - case dataPoints.AM02TimeTotal: // DP 10: Ignore until need is defined - break; - default: // Unknown code - logger.debug(`Unrecognized DP #${dp} ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:zmam02_cover'); + case dataPoints.AM02Direction: + switch (value) { + case 0: + return {motor_direction: 'forward'}; + case 1: + return {motor_direction: 'back'}; + default: + logger.warning(`Mode ${value} is not recognized.`, 'zhc:legacy:fz:zmam02_cover'); + break; + } + break; + case dataPoints.AM02Mode: + switch (value) { + case 0: // morning + return {mode: 'morning'}; + case 1: // night + return {mode: 'night'}; + default: + logger.warning(`Mode ${value} is not recognized.`, 'zhc:legacy:fz:zmam02_cover'); + break; + } + break; + case dataPoints.AM02AddRemoter: // DP 101: Ignore until need is defined + case dataPoints.AM02TimeTotal: // DP 10: Ignore until need is defined + break; + default: // Unknown code + logger.debug(`Unrecognized DP #${dp} ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:zmam02_cover'); } }, } satisfies Fz.Converter, @@ -4526,39 +4595,39 @@ const fromZigbee1 = { const value = getDataValue(dpValue); let result = null; switch (dp) { - case dataPoints.tshpsPresenceState: - // @ts-ignore - result = {presence: {0: false, 1: true}[value]}; - break; - case dataPoints.tshpscSensitivity: - result = {radar_sensitivity: value}; - break; - case dataPoints.tshpsMinimumRange: - result = {minimum_range: value/100}; - break; - case dataPoints.tshpsMaximumRange: - result = {maximum_range: value/100}; - break; - case dataPoints.tshpsTargetDistance: - result = {target_distance: value/100}; - break; - case dataPoints.tshpsDetectionDelay: - result = {detection_delay: value/10}; - break; - case dataPoints.tshpsFadingTime: - result = {fading_time: value/10}; - break; - case dataPoints.tshpsIlluminanceLux: - result = {illuminance_lux: value}; - break; - case dataPoints.tshpsCLI: // not recognize - result = {cli: value}; - break; - case dataPoints.tshpsSelfTest: - result = {self_test: tuyaHPSCheckingResult [value]}; - break; - default: - logger.debug(`Unrecognized DP ${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:tuya_smart_human_presense_sensor'); + case dataPoints.tshpsPresenceState: + // @ts-ignore + result = {presence: {0: false, 1: true}[value]}; + break; + case dataPoints.tshpscSensitivity: + result = {radar_sensitivity: value}; + break; + case dataPoints.tshpsMinimumRange: + result = {minimum_range: value / 100}; + break; + case dataPoints.tshpsMaximumRange: + result = {maximum_range: value / 100}; + break; + case dataPoints.tshpsTargetDistance: + result = {target_distance: value / 100}; + break; + case dataPoints.tshpsDetectionDelay: + result = {detection_delay: value / 10}; + break; + case dataPoints.tshpsFadingTime: + result = {fading_time: value / 10}; + break; + case dataPoints.tshpsIlluminanceLux: + result = {illuminance_lux: value}; + break; + case dataPoints.tshpsCLI: // not recognize + result = {cli: value}; + break; + case dataPoints.tshpsSelfTest: + result = {self_test: tuyaHPSCheckingResult[value]}; + break; + default: + logger.debug(`Unrecognized DP ${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:tuya_smart_human_presense_sensor'); } return result; }, @@ -4572,25 +4641,25 @@ const fromZigbee1 = { const dp = dpValue.dp; const value = getDataValue(dpValue); switch (dp) { - case dataPoints.lmsState: - result.occupancy = (value === 0); - break; - case dataPoints.lmsBattery: - result.battery = value; - break; - case dataPoints.lmsSensitivity: - // @ts-ignore - result.sensitivity = {'0': 'low', '1': 'medium', '2': 'high'}[value]; - break; - case dataPoints.lmsKeepTime: - // @ts-ignore - result.keep_time = {'0': '10', '1': '30', '2': '60', '3': '120'}[value]; - break; - case dataPoints.lmsIlluminance: - result.illuminance = value; - break; - default: - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:zg204zl_lms'); + case dataPoints.lmsState: + result.occupancy = value === 0; + break; + case dataPoints.lmsBattery: + result.battery = value; + break; + case dataPoints.lmsSensitivity: + // @ts-ignore + result.sensitivity = {'0': 'low', '1': 'medium', '2': 'high'}[value]; + break; + case dataPoints.lmsKeepTime: + // @ts-ignore + result.keep_time = {'0': '10', '1': '30', '2': '60', '3': '120'}[value]; + break; + case dataPoints.lmsIlluminance: + result.illuminance = value; + break; + default: + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:zg204zl_lms'); } } return result; @@ -4606,31 +4675,30 @@ const fromZigbee1 = { const value = getDataValue(dpValue); let result = null; switch (dp) { - case dataPoints.coverPosition: { - const invert = !isCoverInverted(meta.device.manufacturerName) ? - !options.invert_cover : options.invert_cover; - const position = invert ? 100 - value : value; - result = {position: position}; - break; - } - case dataPoints.state: - // @ts-ignore - result = {state: {0: 'OPEN', 1: 'STOP', 2: 'CLOSE'}[value], running: {0: true, 1: false, 2: true}[value]}; - break; - case dataPoints.moesCoverBacklight: - // @ts-ignore - result = {backlight: value ? 'ON' : 'OFF'}; - break; - case dataPoints.moesCoverCalibration: - // @ts-ignore - result = {calibration: {0: 'ON', 1: 'OFF'}[value]}; - break; - case dataPoints.moesCoverMotorReversal: - // @ts-ignore - result = {motor_reversal: {0: 'OFF', 1: 'ON'}[value]}; - break; - default: - logger.debug(`Unrecognized DP ${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:moes_cover'); + case dataPoints.coverPosition: { + const invert = !isCoverInverted(meta.device.manufacturerName) ? !options.invert_cover : options.invert_cover; + const position = invert ? 100 - value : value; + result = {position: position}; + break; + } + case dataPoints.state: + // @ts-ignore + result = {state: {0: 'OPEN', 1: 'STOP', 2: 'CLOSE'}[value], running: {0: true, 1: false, 2: true}[value]}; + break; + case dataPoints.moesCoverBacklight: + // @ts-ignore + result = {backlight: value ? 'ON' : 'OFF'}; + break; + case dataPoints.moesCoverCalibration: + // @ts-ignore + result = {calibration: {0: 'ON', 1: 'OFF'}[value]}; + break; + case dataPoints.moesCoverMotorReversal: + // @ts-ignore + result = {motor_reversal: {0: 'OFF', 1: 'ON'}[value]}; + break; + default: + logger.debug(`Unrecognized DP ${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:moes_cover'); } return result; }, @@ -4643,20 +4711,20 @@ const fromZigbee1 = { const dp = dpValue.dp; const value = getDataValue(dpValue); switch (dp) { - case dataPoints.tthTemperature: - return {temperature: (value > 0x2000 ? value - 0xFFFF : value) / 10}; - case dataPoints.tthHumidity: - return {humidity: (value / (['_TZE200_bjawzodf', '_TZE200_zl1kmjqx'].includes(meta.device.manufacturerName) ? 10 : 1))}; - case dataPoints.tthBatteryLevel: - return { - // @ts-ignore - battery_level: {0: 'low', 1: 'middle', 2: 'high'}[value], - battery_low: value === 0 ? true : false, - }; - case dataPoints.tthBattery: - return {battery: value}; - default: - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:tuya_temperature_humidity_sensor'); + case dataPoints.tthTemperature: + return {temperature: (value > 0x2000 ? value - 0xffff : value) / 10}; + case dataPoints.tthHumidity: + return {humidity: value / (['_TZE200_bjawzodf', '_TZE200_zl1kmjqx'].includes(meta.device.manufacturerName) ? 10 : 1)}; + case dataPoints.tthBatteryLevel: + return { + // @ts-ignore + battery_level: {0: 'low', 1: 'middle', 2: 'high'}[value], + battery_low: value === 0 ? true : false, + }; + case dataPoints.tthBattery: + return {battery: value}; + default: + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:tuya_temperature_humidity_sensor'); } }, } satisfies Fz.Converter, @@ -4669,56 +4737,56 @@ const fromZigbee1 = { const dp = dpValue.dp; const value = getDataValue(dpValue); switch (dp) { - case dataPoints.nousTemperature: - result.temperature = value / 10; - break; - case dataPoints.nousHumidity: - result.humidity = value; - break; - case dataPoints.nousBattery: - result.battery = value; - break; - case dataPoints.nousTempUnitConvert: - // @ts-ignore - result.temperature_unit_convert = {0x00: 'celsius', 0x01: 'fahrenheit'}[value]; - break; - case dataPoints.nousMaxTemp: - result.max_temperature = value / 10; - break; - case dataPoints.nousMinTemp: - result.min_temperature = value / 10; - break; - case dataPoints.nousMaxHumi: - result.max_humidity = value; - break; - case dataPoints.nousMinHumi: - result.min_humidity = value; - break; - case dataPoints.nousTempAlarm: - // @ts-ignore - result.temperature_alarm = {0x00: 'lower_alarm', 0x01: 'upper_alarm', 0x02: 'canceled'}[value]; - break; - case dataPoints.nousHumiAlarm: - // @ts-ignore - result.humidity_alarm = {0x00: 'lower_alarm', 0x01: 'upper_alarm', 0x02: 'canceled'}[value]; - break; - case dataPoints.nousTempSensitivity: - result.temperature_sensitivity = value / 10; - break; - case dataPoints.nousHumiSensitivity: - result.humidity_sensitivity = value; - break; - case dataPoints.nousTempReportInterval: - result.temperature_report_interval = value; - break; - case dataPoints.nousHumiReportInterval: - result.humidity_report_interval = value; - break; - default: - logger.debug( - `Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, - 'zhc:legacy:fz:nous_lcd_temperature_humidity_sensor', - ); + case dataPoints.nousTemperature: + result.temperature = value / 10; + break; + case dataPoints.nousHumidity: + result.humidity = value; + break; + case dataPoints.nousBattery: + result.battery = value; + break; + case dataPoints.nousTempUnitConvert: + // @ts-ignore + result.temperature_unit_convert = {0x00: 'celsius', 0x01: 'fahrenheit'}[value]; + break; + case dataPoints.nousMaxTemp: + result.max_temperature = value / 10; + break; + case dataPoints.nousMinTemp: + result.min_temperature = value / 10; + break; + case dataPoints.nousMaxHumi: + result.max_humidity = value; + break; + case dataPoints.nousMinHumi: + result.min_humidity = value; + break; + case dataPoints.nousTempAlarm: + // @ts-ignore + result.temperature_alarm = {0x00: 'lower_alarm', 0x01: 'upper_alarm', 0x02: 'canceled'}[value]; + break; + case dataPoints.nousHumiAlarm: + // @ts-ignore + result.humidity_alarm = {0x00: 'lower_alarm', 0x01: 'upper_alarm', 0x02: 'canceled'}[value]; + break; + case dataPoints.nousTempSensitivity: + result.temperature_sensitivity = value / 10; + break; + case dataPoints.nousHumiSensitivity: + result.humidity_sensitivity = value; + break; + case dataPoints.nousTempReportInterval: + result.temperature_report_interval = value; + break; + case dataPoints.nousHumiReportInterval: + result.humidity_report_interval = value; + break; + default: + logger.debug( + `Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, + 'zhc:legacy:fz:nous_lcd_temperature_humidity_sensor', + ); } } return result; @@ -4732,19 +4800,19 @@ const fromZigbee1 = { const dp = dpValue.dp; const value = getDataValue(dpValue); switch (dp) { - case dataPoints.thitTemperature: - return {temperature: value / 10}; - case dataPoints.thitHumidity: - return {humidity: value}; - case dataPoints.thitBatteryPercentage: - return {battery: value}; - case dataPoints.thitIlluminanceLux: - return {illuminance_lux: value}; - default: - logger.debug( - `Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, - 'zhc:legacy:fz:tuya_illuminance_temperature_humidity_sensor', - ); + case dataPoints.thitTemperature: + return {temperature: value / 10}; + case dataPoints.thitHumidity: + return {humidity: value}; + case dataPoints.thitBatteryPercentage: + return {battery: value}; + case dataPoints.thitIlluminanceLux: + return {illuminance_lux: value}; + default: + logger.debug( + `Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, + 'zhc:legacy:fz:tuya_illuminance_temperature_humidity_sensor', + ); } }, } satisfies Fz.Converter, @@ -4763,12 +4831,12 @@ const fromZigbee1 = { const dp = dpValue.dp; const value = getDataValue(dpValue); switch (dp) { - case dataPoints.state: - return {brightness_state: brightnessState[value]}; - case dataPoints.tIlluminanceLux: - return {illuminance_lux: value}; - default: - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:tuya_illuminance_sensor'); + case dataPoints.state: + return {brightness_state: brightnessState[value]}; + case dataPoints.tIlluminanceLux: + return {illuminance_lux: value}; + default: + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:tuya_illuminance_sensor'); } }; })(), @@ -4782,79 +4850,91 @@ const fromZigbee1 = { const value = getDataValue(dpValue); switch (dp) { - case dataPoints.hyWorkdaySchedule1: // schedule for workdays [5,9,12,8,0,15,10,0,15] - return {workdays: [ - {hour: value[0], minute: value[1], temperature: value[2]}, - {hour: value[3], minute: value[4], temperature: value[5]}, - {hour: value[6], minute: value[7], temperature: value[8]}, - ], range: 'am'}; - case dataPoints.hyWorkdaySchedule2: // schedule for workdays [15,0,25,145,2,17,22,50,14] - return {workdays: [ - {hour: value[0], minute: value[1], temperature: value[2]}, - {hour: value[3], minute: value[4], temperature: value[5]}, - {hour: value[6], minute: value[7], temperature: value[8]}, - ], range: 'pm'}; - case dataPoints.hyHolidaySchedule1: // schedule for holidays [5,5,20,8,4,13,11,30,15] - return {holidays: [ - {hour: value[0], minute: value[1], temperature: value[2]}, - {hour: value[3], minute: value[4], temperature: value[5]}, - {hour: value[6], minute: value[7], temperature: value[8]}, - ], range: 'am'}; - case dataPoints.hyHolidaySchedule2: // schedule for holidays [13,30,15,17,0,15,22,0,15] - return {holidays: [ - {hour: value[0], minute: value[1], temperature: value[2]}, - {hour: value[3], minute: value[4], temperature: value[5]}, - {hour: value[6], minute: value[7], temperature: value[8]}, - ], range: 'pm'}; - case dataPoints.hyHeating: // heating - return {heating: value ? 'ON' : 'OFF'}; - case dataPoints.hyMaxTempProtection: // max temperature protection - return {max_temperature_protection: value ? 'ON' : 'OFF'}; - case dataPoints.hyMinTempProtection: // min temperature protection - return {min_temperature_protection: value ? 'ON' : 'OFF'}; - case dataPoints.hyState: // 0x017D work state - return {state: value ? 'ON' : 'OFF'}; - case dataPoints.hyChildLock: // 0x0181 Changed child lock status - return {child_lock: value ? 'LOCK' : 'UNLOCK'}; - case dataPoints.hyExternalTemp: // external sensor temperature - return {external_temperature: (value / 10).toFixed(1)}; - case dataPoints.hyAwayDays: // away preset days - return {away_preset_days: value}; - case dataPoints.hyAwayTemp: // away preset temperature - return {away_preset_temperature: value}; - case dataPoints.hyTempCalibration: // 0x026D Temperature correction - return {local_temperature_calibration: (value / 10).toFixed(1)}; - case dataPoints.hyHysteresis: // 0x026E Temperature hysteresis - return {hysteresis: (value / 10).toFixed(1)}; - case dataPoints.hyProtectionHysteresis: // 0x026F Temperature protection hysteresis - return {hysteresis_for_protection: value}; - case dataPoints.hyProtectionMaxTemp: // 0x027A max temperature for protection - return {max_temperature_for_protection: value}; - case dataPoints.hyProtectionMinTemp: // 0x027B min temperature for protection - return {min_temperature_for_protection: value}; - case dataPoints.hyMaxTemp: // 0x027C max temperature limit - return {max_temperature: value}; - case dataPoints.hyMinTemp: // 0x027D min temperature limit - return {min_temperature: value}; - case dataPoints.hyHeatingSetpoint: // 0x027E Changed target temperature - return {current_heating_setpoint: (value / 10).toFixed(1)}; - case dataPoints.hyLocalTemp: // 0x027F MCU reporting room temperature - return {local_temperature: (value / 10).toFixed(1)}; - case dataPoints.hySensor: // Sensor type - // @ts-ignore - return {sensor_type: {0: 'internal', 1: 'external', 2: 'both'}[value]}; - case dataPoints.hyPowerOnBehavior: // 0x0475 State after power on - // @ts-ignore - return {power_on_behavior: {0: 'restore', 1: 'off', 2: 'on'}[value]}; - case dataPoints.hyWeekFormat: // 0x0476 Week select 0 - 5 days, 1 - 6 days, 2 - 7 days - return {week: thermostatWeekFormat[value]}; - case dataPoints.hyMode: // 0x0480 mode - // @ts-ignore - return {system_mode: {0: 'manual', 1: 'auto', 2: 'away'}[value]}; - case dataPoints.hyAlarm: // [16] [0] - return {alarm: (value > 0) ? true : false}; - default: // The purpose of the codes 17 & 19 are still unknown - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:hy_thermostat'); + case dataPoints.hyWorkdaySchedule1: // schedule for workdays [5,9,12,8,0,15,10,0,15] + return { + workdays: [ + {hour: value[0], minute: value[1], temperature: value[2]}, + {hour: value[3], minute: value[4], temperature: value[5]}, + {hour: value[6], minute: value[7], temperature: value[8]}, + ], + range: 'am', + }; + case dataPoints.hyWorkdaySchedule2: // schedule for workdays [15,0,25,145,2,17,22,50,14] + return { + workdays: [ + {hour: value[0], minute: value[1], temperature: value[2]}, + {hour: value[3], minute: value[4], temperature: value[5]}, + {hour: value[6], minute: value[7], temperature: value[8]}, + ], + range: 'pm', + }; + case dataPoints.hyHolidaySchedule1: // schedule for holidays [5,5,20,8,4,13,11,30,15] + return { + holidays: [ + {hour: value[0], minute: value[1], temperature: value[2]}, + {hour: value[3], minute: value[4], temperature: value[5]}, + {hour: value[6], minute: value[7], temperature: value[8]}, + ], + range: 'am', + }; + case dataPoints.hyHolidaySchedule2: // schedule for holidays [13,30,15,17,0,15,22,0,15] + return { + holidays: [ + {hour: value[0], minute: value[1], temperature: value[2]}, + {hour: value[3], minute: value[4], temperature: value[5]}, + {hour: value[6], minute: value[7], temperature: value[8]}, + ], + range: 'pm', + }; + case dataPoints.hyHeating: // heating + return {heating: value ? 'ON' : 'OFF'}; + case dataPoints.hyMaxTempProtection: // max temperature protection + return {max_temperature_protection: value ? 'ON' : 'OFF'}; + case dataPoints.hyMinTempProtection: // min temperature protection + return {min_temperature_protection: value ? 'ON' : 'OFF'}; + case dataPoints.hyState: // 0x017D work state + return {state: value ? 'ON' : 'OFF'}; + case dataPoints.hyChildLock: // 0x0181 Changed child lock status + return {child_lock: value ? 'LOCK' : 'UNLOCK'}; + case dataPoints.hyExternalTemp: // external sensor temperature + return {external_temperature: (value / 10).toFixed(1)}; + case dataPoints.hyAwayDays: // away preset days + return {away_preset_days: value}; + case dataPoints.hyAwayTemp: // away preset temperature + return {away_preset_temperature: value}; + case dataPoints.hyTempCalibration: // 0x026D Temperature correction + return {local_temperature_calibration: (value / 10).toFixed(1)}; + case dataPoints.hyHysteresis: // 0x026E Temperature hysteresis + return {hysteresis: (value / 10).toFixed(1)}; + case dataPoints.hyProtectionHysteresis: // 0x026F Temperature protection hysteresis + return {hysteresis_for_protection: value}; + case dataPoints.hyProtectionMaxTemp: // 0x027A max temperature for protection + return {max_temperature_for_protection: value}; + case dataPoints.hyProtectionMinTemp: // 0x027B min temperature for protection + return {min_temperature_for_protection: value}; + case dataPoints.hyMaxTemp: // 0x027C max temperature limit + return {max_temperature: value}; + case dataPoints.hyMinTemp: // 0x027D min temperature limit + return {min_temperature: value}; + case dataPoints.hyHeatingSetpoint: // 0x027E Changed target temperature + return {current_heating_setpoint: (value / 10).toFixed(1)}; + case dataPoints.hyLocalTemp: // 0x027F MCU reporting room temperature + return {local_temperature: (value / 10).toFixed(1)}; + case dataPoints.hySensor: // Sensor type + // @ts-ignore + return {sensor_type: {0: 'internal', 1: 'external', 2: 'both'}[value]}; + case dataPoints.hyPowerOnBehavior: // 0x0475 State after power on + // @ts-ignore + return {power_on_behavior: {0: 'restore', 1: 'off', 2: 'on'}[value]}; + case dataPoints.hyWeekFormat: // 0x0476 Week select 0 - 5 days, 1 - 6 days, 2 - 7 days + return {week: thermostatWeekFormat[value]}; + case dataPoints.hyMode: // 0x0480 mode + // @ts-ignore + return {system_mode: {0: 'manual', 1: 'auto', 2: 'away'}[value]}; + case dataPoints.hyAlarm: // [16] [0] + return {alarm: value > 0 ? true : false}; + default: // The purpose of the codes 17 & 19 are still unknown + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:hy_thermostat'); } }, } satisfies Fz.Converter, @@ -4866,40 +4946,39 @@ const fromZigbee1 = { const dp = dpValue.dp; const value = getDataValue(dpValue); switch (dp) { - case dataPoints.neoOccupancy: - return {occupancy: value > 0 ? true : false}; - case 102: - return { + case dataPoints.neoOccupancy: + return {occupancy: value > 0 ? true : false}; + case 102: + return { + // @ts-ignore + power_type: {0: 'battery_full', 1: 'battery_high', 2: 'battery_medium', 3: 'battery_low', 4: 'usb'}[value], + battery_low: value === 3, + }; + case dataPoints.neoTamper: + return {tamper: value > 0 ? true : false}; + case 104: + return {temperature: value / 10}; + case 105: + return {humidity: value}; + case dataPoints.neoMinTemp: + return {temperature_min: value}; + case dataPoints.neoMaxTemp: + return {temperature_max: value}; + case dataPoints.neoMinHumidity: + return {humidity_min: value}; + case dataPoints.neoMaxHumidity: + return {humidity_max: value}; + case dataPoints.neoTempScale: + return {temperature_scale: value ? '°C' : '°F'}; + case 111: + return {unknown_111: value ? 'ON' : 'OFF'}; + case 112: + return {unknown_112: value ? 'ON' : 'OFF'}; + case dataPoints.neoTempHumidityAlarm: // @ts-ignore - power_type: {0: 'battery_full', 1: 'battery_high', 2: 'battery_medium', 3: 'battery_low', 4: 'usb'}[value], - battery_low: value === 3, - }; - case dataPoints.neoTamper: - return {tamper: value > 0 ? true : false}; - case 104: - return {temperature: value / 10}; - case 105: - return {humidity: value}; - case dataPoints.neoMinTemp: - return {temperature_min: value}; - case dataPoints.neoMaxTemp: - return {temperature_max: value}; - case dataPoints.neoMinHumidity: - return {humidity_min: value}; - case dataPoints.neoMaxHumidity: - return {humidity_max: value}; - case dataPoints.neoTempScale: - return {temperature_scale: value ? '°C' : '°F'}; - case 111: - return {unknown_111: value ? 'ON' : 'OFF'}; - case 112: - return {unknown_112: value ? 'ON' : 'OFF'}; - case dataPoints.neoTempHumidityAlarm: - // @ts-ignore - return {alarm: {0: 'over_temperature', 1: 'over_humidity', - 2: 'below_min_temperature', 3: 'below_min_humdity', 4: 'off'}[value]}; - default: // Unknown code - logger.debug(`Unrecognized DP #${dp}: ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:neo_nas_pd07'); + return {alarm: {0: 'over_temperature', 1: 'over_humidity', 2: 'below_min_temperature', 3: 'below_min_humdity', 4: 'off'}[value]}; + default: // Unknown code + logger.debug(`Unrecognized DP #${dp}: ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:neo_nas_pd07'); } }, } satisfies Fz.Converter, @@ -4911,43 +4990,43 @@ const fromZigbee1 = { const dp = dpValue.dp; const value = getDataValue(dpValue); switch (dp) { - case dataPoints.neoAlarm: - return {alarm: value}; - case dataPoints.neoUnknown2: // 0x0170 [0] - break; - case dataPoints.neoTempAlarm: - return {temperature_alarm: value}; - case dataPoints.neoHumidityAlarm: // 0x0172 [0]/[1] Disable/Enable alarm by humidity - return {humidity_alarm: value}; - case dataPoints.neoDuration: // 0x0267 [0,0,0,10] duration alarm in second - return {duration: value}; - case dataPoints.neoTemp: // 0x0269 [0,0,0,240] temperature - return {temperature: (value / 10)}; - case dataPoints.neoHumidity: // 0x026A [0,0,0,36] humidity - return {humidity: value}; - case dataPoints.neoMinTemp: // 0x026B [0,0,0,18] min alarm temperature - return {temperature_min: value}; - case dataPoints.neoMaxTemp: // 0x026C [0,0,0,27] max alarm temperature - return {temperature_max: value}; - case dataPoints.neoMinHumidity: // 0x026D [0,0,0,45] min alarm humidity - return {humidity_min: value}; - case dataPoints.neoMaxHumidity: // 0x026E [0,0,0,80] max alarm humidity - return {humidity_max: value}; - case dataPoints.neoPowerType: // 0x0465 [4] - return { + case dataPoints.neoAlarm: + return {alarm: value}; + case dataPoints.neoUnknown2: // 0x0170 [0] + break; + case dataPoints.neoTempAlarm: + return {temperature_alarm: value}; + case dataPoints.neoHumidityAlarm: // 0x0172 [0]/[1] Disable/Enable alarm by humidity + return {humidity_alarm: value}; + case dataPoints.neoDuration: // 0x0267 [0,0,0,10] duration alarm in second + return {duration: value}; + case dataPoints.neoTemp: // 0x0269 [0,0,0,240] temperature + return {temperature: value / 10}; + case dataPoints.neoHumidity: // 0x026A [0,0,0,36] humidity + return {humidity: value}; + case dataPoints.neoMinTemp: // 0x026B [0,0,0,18] min alarm temperature + return {temperature_min: value}; + case dataPoints.neoMaxTemp: // 0x026C [0,0,0,27] max alarm temperature + return {temperature_max: value}; + case dataPoints.neoMinHumidity: // 0x026D [0,0,0,45] min alarm humidity + return {humidity_min: value}; + case dataPoints.neoMaxHumidity: // 0x026E [0,0,0,80] max alarm humidity + return {humidity_max: value}; + case dataPoints.neoPowerType: // 0x0465 [4] + return { + // @ts-ignore + power_type: {0: 'battery_full', 1: 'battery_high', 2: 'battery_medium', 3: 'battery_low', 4: 'usb'}[value], + battery_low: value === 3, + }; + case dataPoints.neoMelody: // 0x0466 [5] Melody + return {melody: value}; + case dataPoints.neoUnknown3: // 0x0473 [0] + break; + case dataPoints.neoVolume: // 0x0474 [0]/[1]/[2] Volume 0-max, 2-low // @ts-ignore - power_type: {0: 'battery_full', 1: 'battery_high', 2: 'battery_medium', 3: 'battery_low', 4: 'usb'}[value], - battery_low: value === 3, - }; - case dataPoints.neoMelody: // 0x0466 [5] Melody - return {melody: value}; - case dataPoints.neoUnknown3: // 0x0473 [0] - break; - case dataPoints.neoVolume: // 0x0474 [0]/[1]/[2] Volume 0-max, 2-low - // @ts-ignore - return {volume: {2: 'low', 1: 'medium', 0: 'high'}[value]}; - default: // Unknown code - logger.debug(`Unrecognized DP #${dp}: ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:neo_t_h_alarm'); + return {volume: {2: 'low', 1: 'medium', 0: 'high'}[value]}; + default: // Unknown code + logger.debug(`Unrecognized DP #${dp}: ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:neo_t_h_alarm'); } }, } satisfies Fz.Converter, @@ -4960,19 +5039,19 @@ const fromZigbee1 = { const value = getDataValue(dpValue); switch (dp) { - case dataPoints.neoAOAlarm: // 0x13 [TRUE,FALSE] - return {alarm: value}; - case dataPoints.neoAODuration: // 0x7 [0,0,0,10] duration alarm in second - return {duration: value}; - case dataPoints.neoAOBattPerc: // 0x15 [0,0,0,100] battery percentage - return {battpercentage: value}; - case dataPoints.neoAOMelody: // 0x21 [5] Melody - return {melody: value}; - case dataPoints.neoAOVolume: // 0x5 [0]/[1]/[2] Volume 0-low, 2-max - // @ts-ignore - return {volume: {0: 'low', 1: 'medium', 2: 'high'}[value]}; - default: // Unknown code - logger.debug(`Unrecognized DP #${dp}: ${JSON.stringify(msg.data)}`, 'zhc:legacy:fz:neo_alarm'); + case dataPoints.neoAOAlarm: // 0x13 [TRUE,FALSE] + return {alarm: value}; + case dataPoints.neoAODuration: // 0x7 [0,0,0,10] duration alarm in second + return {duration: value}; + case dataPoints.neoAOBattPerc: // 0x15 [0,0,0,100] battery percentage + return {battpercentage: value}; + case dataPoints.neoAOMelody: // 0x21 [5] Melody + return {melody: value}; + case dataPoints.neoAOVolume: // 0x5 [0]/[1]/[2] Volume 0-low, 2-max + // @ts-ignore + return {volume: {0: 'low', 1: 'medium', 2: 'high'}[value]}; + default: // Unknown code + logger.debug(`Unrecognized DP #${dp}: ${JSON.stringify(msg.data)}`, 'zhc:legacy:fz:neo_alarm'); } }, } satisfies Fz.Converter, @@ -4984,33 +5063,33 @@ const fromZigbee1 = { const dp = dpValue.dp; const value = getDataValue(dpValue); switch (dp) { - case dataPoints.fantemPowerSupplyMode: - // @ts-ignore - return {power_supply_mode: {0: 'unknown', 1: 'no_neutral', 2: 'with_neutral'}[value]}; - case dataPoints.fantemExtSwitchType: - // @ts-ignore - return {switch_type: {0: 'unknown', 1: 'toggle', 2: 'momentary', 3: 'rotary', 4: 'auto_config'}[value]}; - case dataPoints.fantemLoadDetectionMode: - // @ts-ignore - return {load_detection_mode: {0: 'none', 1: 'first_power_on', 2: 'every_power_on'}[value]}; - case dataPoints.fantemExtSwitchStatus: - return {switch_status: value}; - case dataPoints.fantemControlMode: - // @ts-ignore - return {control_mode: {0: 'ext_switch', 1: 'remote', 2: 'both'}[value]}; - case 111: - // Value 0 is received after each device power-on. No idea what it means. - return; - case dataPoints.fantemLoadType: - // Not sure if 0 is 'resistive' and 2 is 'resistive_inductive'. - // If you see 'unknown', pls. check with Tuya gateway and app and update with label shown in Tuya app. - // @ts-ignore - return {load_type: {0: 'unknown', 1: 'resistive_capacitive', 2: 'unknown', 3: 'detecting'}[value]}; - case dataPoints.fantemLoadDimmable: - // @ts-ignore - return {load_dimmable: {0: 'unknown', 1: 'dimmable', 2: 'not_dimmable'}[value]}; - default: - logger.debug(`Unrecognized DP|Value [${dp}|${value}][${JSON.stringify(dpValue)}]`, 'zhc:legacy:fz:zb006x_settings'); + case dataPoints.fantemPowerSupplyMode: + // @ts-ignore + return {power_supply_mode: {0: 'unknown', 1: 'no_neutral', 2: 'with_neutral'}[value]}; + case dataPoints.fantemExtSwitchType: + // @ts-ignore + return {switch_type: {0: 'unknown', 1: 'toggle', 2: 'momentary', 3: 'rotary', 4: 'auto_config'}[value]}; + case dataPoints.fantemLoadDetectionMode: + // @ts-ignore + return {load_detection_mode: {0: 'none', 1: 'first_power_on', 2: 'every_power_on'}[value]}; + case dataPoints.fantemExtSwitchStatus: + return {switch_status: value}; + case dataPoints.fantemControlMode: + // @ts-ignore + return {control_mode: {0: 'ext_switch', 1: 'remote', 2: 'both'}[value]}; + case 111: + // Value 0 is received after each device power-on. No idea what it means. + return; + case dataPoints.fantemLoadType: + // Not sure if 0 is 'resistive' and 2 is 'resistive_inductive'. + // If you see 'unknown', pls. check with Tuya gateway and app and update with label shown in Tuya app. + // @ts-ignore + return {load_type: {0: 'unknown', 1: 'resistive_capacitive', 2: 'unknown', 3: 'detecting'}[value]}; + case dataPoints.fantemLoadDimmable: + // @ts-ignore + return {load_dimmable: {0: 'unknown', 1: 'dimmable', 2: 'not_dimmable'}[value]}; + default: + logger.debug(`Unrecognized DP|Value [${dp}|${value}][${JSON.stringify(dpValue)}]`, 'zhc:legacy:fz:zb006x_settings'); } }, } satisfies Fz.Converter, @@ -5031,45 +5110,47 @@ const fromZigbee1 = { const value = getDataValue(dpValue); switch (dp) { - case dataPoints.coverPosition: // Started moving to position (triggered from Zigbee) - case dataPoints.coverArrived: { // Arrived at position - const invert = isCoverInverted(meta.device.manufacturerName) ? !options.invert_cover : options.invert_cover; - const position = invert ? 100 - (value & 0xff) : value & 0xff; - const running = dp !== dataPoints.coverArrived; - - // Not all covers report coverArrived, so set running to false if device doesn't report position - // for a few seconds - clearTimeout(globalStore.getValue(msg.endpoint, 'running_timer')); - if (running) { - const timer = setTimeout(() => publish({running: false}), 3 * 1000); - globalStore.putValue(msg.endpoint, 'running_timer', timer); - } - - if (position > 0 && position <= 100) { - result.running = running; - result.position = position; - result.state = 'OPEN'; - } else if (position == 0) { - // Report fully closed - result.running = running; - result.position = position; - result.state = 'CLOSE'; - } else { - result.running = running; // Not calibrated yet, no position is available - } - } - break; - case dataPoints.coverSpeed: // Cover is reporting its current speed setting - result.motor_speed = value; - break; - case dataPoints.state: // Ignore the cover state, it's not reliable between different covers! - break; - case dataPoints.coverChange: // Ignore manual cover change, it's not reliable between different covers! - break; - case dataPoints.config: // Returned by configuration set; ignore - break; - default: // Unknown code - logger.debug(`Unrecognized DP #${dp}: ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:tuya_cover'); + case dataPoints.coverPosition: // Started moving to position (triggered from Zigbee) + case dataPoints.coverArrived: + { + // Arrived at position + const invert = isCoverInverted(meta.device.manufacturerName) ? !options.invert_cover : options.invert_cover; + const position = invert ? 100 - (value & 0xff) : value & 0xff; + const running = dp !== dataPoints.coverArrived; + + // Not all covers report coverArrived, so set running to false if device doesn't report position + // for a few seconds + clearTimeout(globalStore.getValue(msg.endpoint, 'running_timer')); + if (running) { + const timer = setTimeout(() => publish({running: false}), 3 * 1000); + globalStore.putValue(msg.endpoint, 'running_timer', timer); + } + + if (position > 0 && position <= 100) { + result.running = running; + result.position = position; + result.state = 'OPEN'; + } else if (position == 0) { + // Report fully closed + result.running = running; + result.position = position; + result.state = 'CLOSE'; + } else { + result.running = running; // Not calibrated yet, no position is available + } + } + break; + case dataPoints.coverSpeed: // Cover is reporting its current speed setting + result.motor_speed = value; + break; + case dataPoints.state: // Ignore the cover state, it's not reliable between different covers! + break; + case dataPoints.coverChange: // Ignore manual cover change, it's not reliable between different covers! + break; + case dataPoints.config: // Returned by configuration set; ignore + break; + default: // Unknown code + logger.debug(`Unrecognized DP #${dp}: ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:tuya_cover'); } } @@ -5091,13 +5172,13 @@ const fromZigbee1 = { const value = getDataValue(dpValue); switch (dp) { - case dataPoints.moesSwitchPowerOnBehavior: - return {power_on_behavior: moesSwitch.powerOnBehavior[value]}; - case dataPoints.moesSwitchIndicateLight: - return {indicate_light: moesSwitch.indicateLight[value]}; - default: - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:moes_switch'); - break; + case dataPoints.moesSwitchPowerOnBehavior: + return {power_on_behavior: moesSwitch.powerOnBehavior[value]}; + case dataPoints.moesSwitchIndicateLight: + return {indicate_light: moesSwitch.indicateLight[value]}; + default: + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:moes_switch'); + break; } }, } satisfies Fz.Converter, @@ -5119,14 +5200,14 @@ const fromZigbee1 = { for (const dpValue of msg.data.dpValues) { const value = getDataValue(dpValue); switch (dpValue.dp) { - case dataPoints.wlsWaterLeak: - result.water_leak = value < 1; - break; - case dataPoints.wlsBatteryPercentage: - result.battery = value; - break; - default: - logger.debug(`Unrecognized DP #${dpValue.dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:wls100z_water_leak'); + case dataPoints.wlsWaterLeak: + result.water_leak = value < 1; + break; + case dataPoints.wlsBatteryPercentage: + result.battery = value; + break; + default: + logger.debug(`Unrecognized DP #${dpValue.dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:wls100z_water_leak'); } } return result; @@ -5145,7 +5226,8 @@ const fromZigbee1 = { if (value !== silvercrestModes.effect) { result.effect = null; } - } if (dp === dataPoints.silvercrestSetBrightness) { + } + if (dp === dataPoints.silvercrestSetBrightness) { result.brightness = utils.mapNumberRange(value, 0, 1000, 0, 255); } else if (dp === dataPoints.silvercrestSetColor) { const h = parseInt(value.substring(0, 4), 16); @@ -5170,7 +5252,9 @@ const fromZigbee1 = { // string gives us. for (let i = 0; i < n; ++i) { const part = colorsString.substring(i * 6, (i + 1) * 6); - const r = part[0]+part[1]; const g = part[2]+part[3]; const b = part[4]+part[5]; + const r = part[0] + part[1]; + const g = part[2] + part[3]; + const b = part[4] + part[5]; result.effect.colors.push({ r: parseInt(r, 16), g: parseInt(g, 16), @@ -5190,18 +5274,18 @@ const fromZigbee1 = { const value = getDataValue(dpValue); const dp = dpValue.dp; switch (dp) { - case dataPoints.state: { - return {state: value ? 'ON': 'OFF'}; - } - case dataPoints.frankEverTreshold: { - return {threshold: value}; - } - case dataPoints.frankEverTimer: { - return {timer: value / 60}; - } - default: { - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:frankever_valve'); - } + case dataPoints.state: { + return {state: value ? 'ON' : 'OFF'}; + } + case dataPoints.frankEverTreshold: { + return {threshold: value}; + } + case dataPoints.frankEverTimer: { + return {timer: value / 60}; + } + default: { + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:frankever_valve'); + } } }, } satisfies Fz.Converter, @@ -5213,14 +5297,14 @@ const fromZigbee1 = { const dp = dpValue.dp; const value = getDataValue(dpValue); switch (dp) { - case dataPoints.wooxBattery: - return {battery_low: value === 0}; - case dataPoints.state: - return {smoke: value === 0}; - case dataPoints.wooxSmokeTest: - return {smoke: value}; - default: - logger.debug(`Unrecognized DP #${ dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:tuya_smoke'); + case dataPoints.wooxBattery: + return {battery_low: value === 0}; + case dataPoints.state: + return {smoke: value === 0}; + case dataPoints.wooxSmokeTest: + return {smoke: value}; + default: + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:tuya_smoke'); } }, } satisfies Fz.Converter, @@ -5255,18 +5339,18 @@ const fromZigbee1 = { const state = value ? 'ON' : 'OFF'; switch (dp) { - case dataPoints.state: // DPID that we added to common - return {state: state}; - case dataPoints.dinrailPowerMeterTotalEnergy: - return {energy: value/100}; - case dataPoints.dinrailPowerMeterCurrent: - return {current: value/1000}; - case dataPoints.dinrailPowerMeterPower: - return {power: value/10}; - case dataPoints.dinrailPowerMeterVoltage: - return {voltage: value/10}; - default: - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:tuya_dinrail_switch'); + case dataPoints.state: // DPID that we added to common + return {state: state}; + case dataPoints.dinrailPowerMeterTotalEnergy: + return {energy: value / 100}; + case dataPoints.dinrailPowerMeterCurrent: + return {current: value / 1000}; + case dataPoints.dinrailPowerMeterPower: + return {power: value / 10}; + case dataPoints.dinrailPowerMeterVoltage: + return {voltage: value / 10}; + default: + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:tuya_dinrail_switch'); } return null; @@ -5280,80 +5364,85 @@ const fromZigbee1 = { const value = getDataValue(dpValue); const dp = dpValue.dp; switch (dp) { - case dataPoints.state: { - return {state: value ? 'ON': 'OFF'}; - } - case 5: { - // Assume value is reported in fl. oz., converter to litres - return {water_consumed: (value / 33.8140226).toFixed(2)}; - } - case 7: { - return {battery: value}; - } - case 10: { - let data = 'disabled'; - if (value == 1) { - data = '24h'; - } else if (value == 2) { - data = '48h'; - } else if (value == 3) { - data = '72h'; - } - return {weather_delay: data}; - } - case 11: { - // value reported in seconds - return {timer_time_left: value / 60}; - } - case 12: { - if (value === 0) return {timer_state: 'disabled'}; - else if (value === 1) return {timer_state: 'active'}; - else return {timer_state: 'enabled'}; - } - case 15: { - // value reported in seconds - return {last_valve_open_duration: value / 60}; - } - case 16: { - const tresult: KeyValueAny = { - cycle_timer_1: '', - cycle_timer_2: '', - cycle_timer_3: '', - cycle_timer_4: '', - }; - for (let index = 0; index < 40; index += 12) { - const timer = convertRawToCycleTimer(value.slice(index)); - if (timer.irrigationDuration > 0) { - tresult['cycle_timer_' + (index / 13 + 1)] = timer.starttime + - ' / ' + timer.endtime + ' / ' + - timer.irrigationDuration + ' / ' + - timer.pauseDuration + ' / ' + - timer.weekdays + ' / ' + timer.active; + case dataPoints.state: { + return {state: value ? 'ON' : 'OFF'}; + } + case 5: { + // Assume value is reported in fl. oz., converter to litres + return {water_consumed: (value / 33.8140226).toFixed(2)}; + } + case 7: { + return {battery: value}; + } + case 10: { + let data = 'disabled'; + if (value == 1) { + data = '24h'; + } else if (value == 2) { + data = '48h'; + } else if (value == 3) { + data = '72h'; } - } - return tresult; - } - case 17: { - const tresult: KeyValueAny = { - normal_schedule_timer_1: '', - normal_schedule_timer_2: '', - normal_schedule_timer_3: '', - normal_schedule_timer_4: '', - }; - for (let index = 0; index < 40; index += 13) { - const timer = convertRawToTimer(value.slice(index)); - if (timer.duration > 0) { - tresult['normal_schedule_timer_' + (index / 13 + 1)] = timer.time + - ' / ' + timer.duration + - ' / ' + timer.weekdays + - ' / ' + timer.active; + return {weather_delay: data}; + } + case 11: { + // value reported in seconds + return {timer_time_left: value / 60}; + } + case 12: { + if (value === 0) return {timer_state: 'disabled'}; + else if (value === 1) return {timer_state: 'active'}; + else return {timer_state: 'enabled'}; + } + case 15: { + // value reported in seconds + return {last_valve_open_duration: value / 60}; + } + case 16: { + const tresult: KeyValueAny = { + cycle_timer_1: '', + cycle_timer_2: '', + cycle_timer_3: '', + cycle_timer_4: '', + }; + for (let index = 0; index < 40; index += 12) { + const timer = convertRawToCycleTimer(value.slice(index)); + if (timer.irrigationDuration > 0) { + tresult['cycle_timer_' + (index / 13 + 1)] = + timer.starttime + + ' / ' + + timer.endtime + + ' / ' + + timer.irrigationDuration + + ' / ' + + timer.pauseDuration + + ' / ' + + timer.weekdays + + ' / ' + + timer.active; + } } + return tresult; + } + case 17: { + const tresult: KeyValueAny = { + normal_schedule_timer_1: '', + normal_schedule_timer_2: '', + normal_schedule_timer_3: '', + normal_schedule_timer_4: '', + }; + for (let index = 0; index < 40; index += 13) { + const timer = convertRawToTimer(value.slice(index)); + if (timer.duration > 0) { + tresult['normal_schedule_timer_' + (index / 13 + 1)] = + timer.time + ' / ' + timer.duration + ' / ' + timer.weekdays + ' / ' + timer.active; + } + } + return tresult; + } + default: { + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:rtx_zvg1_valve'); } - return tresult; - } - default: { - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:rtx_zvg1_valve'); - } } }, } satisfies Fz.Converter, @@ -5365,33 +5454,31 @@ const fromZigbee1 = { const dp = dpValue.dp; const value = getDataValue(dpValue); switch (dp) { - case dataPoints.fantemTemp: - return {temperature: (value / 10)}; - case dataPoints.fantemHumidity: - return {humidity: value}; - case dataPoints.fantemBattery: - // second battery level, first battery is reported by fz.battery - return {battery2: value}; - case dataPoints.fantemReportingTime: - return {reporting_time: value}; - case dataPoints.fantemTempCalibration: - return { - temperature_calibration: ( - (value > 0x7FFFFFFF ? 0xFFFFFFFF - value : value) / 10 - ).toFixed(1), - }; - case dataPoints.fantemHumidityCalibration: - return {humidity_calibration: value > 0x7FFFFFFF ? 0xFFFFFFFF - value : value}; - case dataPoints.fantemLuxCalibration: - return {illuminance_calibration: value > 0x7FFFFFFF ? 0xFFFFFFFF - value : value}; - case dataPoints.fantemMotionEnable: - return {pir_enable: value}; - case dataPoints.fantemLedEnable: - return {led_enable: value ? false : true}; - case dataPoints.fantemReportingEnable: - return {reporting_enable: value}; - default: - logger.debug(`Unrecognized DP #${dp}: ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:zb003x'); + case dataPoints.fantemTemp: + return {temperature: value / 10}; + case dataPoints.fantemHumidity: + return {humidity: value}; + case dataPoints.fantemBattery: + // second battery level, first battery is reported by fz.battery + return {battery2: value}; + case dataPoints.fantemReportingTime: + return {reporting_time: value}; + case dataPoints.fantemTempCalibration: + return { + temperature_calibration: ((value > 0x7fffffff ? 0xffffffff - value : value) / 10).toFixed(1), + }; + case dataPoints.fantemHumidityCalibration: + return {humidity_calibration: value > 0x7fffffff ? 0xffffffff - value : value}; + case dataPoints.fantemLuxCalibration: + return {illuminance_calibration: value > 0x7fffffff ? 0xffffffff - value : value}; + case dataPoints.fantemMotionEnable: + return {pir_enable: value}; + case dataPoints.fantemLedEnable: + return {led_enable: value ? false : true}; + case dataPoints.fantemReportingEnable: + return {reporting_enable: value}; + default: + logger.debug(`Unrecognized DP #${dp}: ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:zb003x'); } }, } satisfies Fz.Converter, @@ -5415,8 +5502,8 @@ const fromZigbee1 = { // see also toZigbee.tuya_thermostat_weekly_schedule() function dataToTransition(data: any, index: number) { return { - time: (data[index+0] << 8) + data [index+1], - heating_setpoint: (parseFloat((data[index+2] << 8) + data [index+3]) / 10.0).toFixed(1), + time: (data[index + 0] << 8) + data[index + 1], + heating_setpoint: (parseFloat((data[index + 2] << 8) + data[index + 3]) / 10.0).toFixed(1), }; } const result = []; @@ -5433,7 +5520,7 @@ const fromZigbee1 = { // Saswell has scheduling mode in the first byte dataOffset = 1; } - if (dp >= firstDayDpId && dp < firstDayDpId+7) { + if (dp >= firstDayDpId && dp < firstDayDpId + 7) { const dayOfWeek = dp - firstDayDpId + 1; return { // Same as in hvacThermostat:getWeeklyScheduleRsp hvacThermostat:setWeeklySchedule cluster format @@ -5464,11 +5551,16 @@ const fromZigbee1 = { for (const [i, dpValue] of msg.data.dpValues.entries()) { logDataPoint('tuya_data_point_dump', msg, dpValue, meta); dataStr += - now + ' ' + - meta.device.ieeeAddr + ' ' + - getHex(msg.data.seq) + ' ' + - getHex(i) + ' ' + - getHex(dpValue.dp) + ' ' + + now + + ' ' + + meta.device.ieeeAddr + + ' ' + + getHex(msg.data.seq) + + ' ' + + getHex(i) + + ' ' + + getHex(dpValue.dp) + + ' ' + getHex(dpValue.datatype); dpValue.data.forEach((elem: any) => { @@ -5498,57 +5590,57 @@ const fromZigbee1 = { 6: 'initialization_completed', }; switch (dp) { - case 1: - return { - states: lookup[value], - occupancy: (0 < value && value < 5) ? true: false, - }; - case 2: - return { - sensitivity: value, - }; - case 101: - return { - illuminance_lux: value, - }; - case 102: - if (meta.device.manufacturerName === '_TZE200_kagkgk0i') { + case 1: return { - illuminance_calibration: value, + states: lookup[value], + occupancy: 0 < value && value < 5 ? true : false, }; - } else { + case 2: return { - keep_time: value, + sensitivity: value, }; - } - case 103: - return { - led_enable: value == 1 ? true : false, - }; - case 104: - return {illuminance_lux: value}; - case 105: - return { - illuminance_calibration: value, - }; - case 106: - if (meta.device.manufacturerName === '_TZE200_kagkgk0i') { + case 101: return { - keep_time: value, + illuminance_lux: value, }; - } else { - break; - } - case 107: - if (meta.device.manufacturerName === '_TZE200_kagkgk0i') { + case 102: + if (meta.device.manufacturerName === '_TZE200_kagkgk0i') { + return { + illuminance_calibration: value, + }; + } else { + return { + keep_time: value, + }; + } + case 103: return { led_enable: value == 1 ? true : false, }; - } else { - break; - } - default: - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:javis_microwave_sensor'); + case 104: + return {illuminance_lux: value}; + case 105: + return { + illuminance_calibration: value, + }; + case 106: + if (meta.device.manufacturerName === '_TZE200_kagkgk0i') { + return { + keep_time: value, + }; + } else { + break; + } + case 107: + if (meta.device.manufacturerName === '_TZE200_kagkgk0i') { + return { + led_enable: value == 1 ? true : false, + }; + } else { + break; + } + default: + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:javis_microwave_sensor'); } }, } satisfies Fz.Converter, @@ -5561,14 +5653,14 @@ const fromZigbee1 = { const value = getDataValue(dpValue); const brightnesStateLookup: KeyValueAny = {'0': 'low', '1': 'middle', '2': 'high'}; switch (dp) { - case 2: - return {illuminance_lux: value}; - case 4: - return {battery: value}; - case 1: - return {brightness_level: brightnesStateLookup[value]}; - default: - logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:s_lux_zb'); + case 2: + return {illuminance_lux: value}; + case 4: + return {battery: value}; + case 1: + return {brightness_level: brightnesStateLookup[value]}; + default: + logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, 'zhc:legacy:fz:s_lux_zb'); } }, } satisfies Fz.Converter, @@ -5601,8 +5693,8 @@ const fromZigbee2 = { // see also toZigbee.tuya_thermostat_weekly_schedule() function dataToTransition(data: any, index: any) { return { - transitionTime: (data[index+0] << 8) + data [index+1], - heatSetpoint: (parseFloat((data[index+2] << 8) + data [index+3]) / 10.0).toFixed(1), + transitionTime: (data[index + 0] << 8) + data[index + 1], + heatSetpoint: (parseFloat((data[index + 2] << 8) + data[index + 3]) / 10.0).toFixed(1), }; } const result = []; @@ -5619,7 +5711,7 @@ const fromZigbee2 = { // Saswell has scheduling mode in the first byte dataOffset = 1; } - if (dp >= firstDayDpId && dp < firstDayDpId+7) { + if (dp >= firstDayDpId && dp < firstDayDpId + 7) { const dayOfWeek = dp - firstDayDpId + 1; return { // Same as in hvacThermostat:getWeeklyScheduleRsp hvacThermostat:setWeeklySchedule cluster format @@ -5702,8 +5794,7 @@ const toZigbee1 = { if (key === 'position') { if (value >= 0 && value <= 100) { - const invert = isCoverInverted(meta.device.manufacturerName) ? - !meta.options.invert_cover : meta.options.invert_cover; + const invert = isCoverInverted(meta.device.manufacturerName) ? !meta.options.invert_cover : meta.options.invert_cover; value = invert ? 100 - value : value; await sendDataPointValue(entity, dataPoints.coverPosition, value); @@ -5719,17 +5810,17 @@ const toZigbee1 = { value = value.toLowerCase(); switch (value) { - case 'close': - await sendDataPointEnum(entity, dataPoints.state, stateEnums.close); - break; - case 'open': - await sendDataPointEnum(entity, dataPoints.state, stateEnums.open); - break; - case 'stop': - await sendDataPointEnum(entity, dataPoints.state, stateEnums.stop); - break; - default: - throw new Error('Tuya_cover_control: Invalid command received'); + case 'close': + await sendDataPointEnum(entity, dataPoints.state, stateEnums.close); + break; + case 'open': + await sendDataPointEnum(entity, dataPoints.state, stateEnums.open); + break; + case 'stop': + await sendDataPointEnum(entity, dataPoints.state, stateEnums.stop); + break; + default: + throw new Error('Tuya_cover_control: Invalid command received'); } } }, @@ -5741,195 +5832,205 @@ const toZigbee2 = { key: ['state', 'position', 'reverse_direction', 'top_limit', 'bottom_limit', 'favorite_position', 'goto_positon', 'report'], convertSet: async (entity, key, value: any, meta) => { switch (key) { - case 'position': { - const invert = (meta.state) ? !meta.state.invert_cover : false; - value = invert ? 100 - value : value; - if (value >= 0 && value <= 100) { - await sendDataPointValue(entity, dataPoints.coverPosition, value); - } else { - throw new Error('Tuya_cover_control: Curtain motor position is out of range'); + case 'position': { + const invert = meta.state ? !meta.state.invert_cover : false; + value = invert ? 100 - value : value; + if (value >= 0 && value <= 100) { + await sendDataPointValue(entity, dataPoints.coverPosition, value); + } else { + throw new Error('Tuya_cover_control: Curtain motor position is out of range'); + } + break; } - break; - } - case 'state': { - const stateEnums = getCoverStateEnums(meta.device.manufacturerName); - logger.debug( - `Using state enums for ${meta.device.manufacturerName}: ${JSON.stringify(stateEnums)}`, - 'zhc:legacy:tz:zb_sm_cover', - ); + case 'state': { + const stateEnums = getCoverStateEnums(meta.device.manufacturerName); + logger.debug(`Using state enums for ${meta.device.manufacturerName}: ${JSON.stringify(stateEnums)}`, 'zhc:legacy:tz:zb_sm_cover'); - value = value.toLowerCase(); - switch (value) { - case 'close': - await sendDataPointEnum(entity, dataPoints.state, stateEnums.close); + value = value.toLowerCase(); + switch (value) { + case 'close': + await sendDataPointEnum(entity, dataPoints.state, stateEnums.close); + break; + case 'open': + await sendDataPointEnum(entity, dataPoints.state, stateEnums.open); + break; + case 'stop': + await sendDataPointEnum(entity, dataPoints.state, stateEnums.stop); + break; + default: + throw new Error('Tuya_cover_control: Invalid command received'); + } break; - case 'open': - await sendDataPointEnum(entity, dataPoints.state, stateEnums.open); + } + case 'reverse_direction': { + logger.info(`Motor direction ${value ? 'reverse' : 'forward'}`, 'zhc:legacy:tz:zb_sm_cover'); + await sendDataPointEnum(entity, dataPoints.motorDirection, value ? 1 : 0); break; - case 'stop': - await sendDataPointEnum(entity, dataPoints.state, stateEnums.stop); + } + case 'top_limit': { + // @ts-ignore + await sendDataPointEnum(entity, 104, {SET: 0, CLEAR: 1}[value]); break; - default: - throw new Error('Tuya_cover_control: Invalid command received'); } - break; - } - case 'reverse_direction': { - logger.info(`Motor direction ${(value) ? 'reverse' : 'forward'}`, 'zhc:legacy:tz:zb_sm_cover'); - await sendDataPointEnum(entity, dataPoints.motorDirection, (value) ? 1 : 0); - break; - } - case 'top_limit': { - // @ts-ignore - await sendDataPointEnum(entity, 104, {'SET': 0, 'CLEAR': 1}[value]); - break; - } - case 'bottom_limit': { - // @ts-ignore - await sendDataPointEnum(entity, 103, {'SET': 0, 'CLEAR': 1}[value]); - break; - } - case 'favorite_position': { - await sendDataPointValue(entity, 115, value); - break; - } - case 'goto_positon': { - if (value == 'FAVORITE') { - value = (meta.state) ? meta.state.favorite_position : null; - } else { - value = parseInt(value); + case 'bottom_limit': { + // @ts-ignore + await sendDataPointEnum(entity, 103, {SET: 0, CLEAR: 1}[value]); + break; + } + case 'favorite_position': { + await sendDataPointValue(entity, 115, value); + break; + } + case 'goto_positon': { + if (value == 'FAVORITE') { + value = meta.state ? meta.state.favorite_position : null; + } else { + value = parseInt(value); + } + return toZigbee1.tuya_cover_control.convertSet(entity, 'position', value, meta); + } + case 'report': { + await sendDataPointBool(entity, 116, 0); + break; } - return toZigbee1.tuya_cover_control.convertSet(entity, 'position', value, meta); - } - case 'report': { - await sendDataPointBool(entity, 116, 0); - break; - } } }, } satisfies Tz.Converter, x5h_thermostat: { - key: ['system_mode', 'current_heating_setpoint', 'sensor', 'brightness_state', 'sound', 'frost_protection', 'week', 'factory_reset', - 'local_temperature_calibration', 'heating_temp_limit', 'deadzone_temperature', 'upper_temp', 'preset', 'child_lock', - 'schedule'], + key: [ + 'system_mode', + 'current_heating_setpoint', + 'sensor', + 'brightness_state', + 'sound', + 'frost_protection', + 'week', + 'factory_reset', + 'local_temperature_calibration', + 'heating_temp_limit', + 'deadzone_temperature', + 'upper_temp', + 'preset', + 'child_lock', + 'schedule', + ], convertSet: async (entity, key, value: any, meta) => { switch (key) { - case 'system_mode': - await sendDataPointBool(entity, dataPoints.x5hState, value === 'heat'); - break; - case 'preset': { - value = value.toLowerCase(); - const lookup: KeyValueAny = {manual: 0, program: 1}; - utils.validateValue(value, Object.keys(lookup)); - value = lookup[value]; - await sendDataPointEnum(entity, dataPoints.x5hMode, value); - break; - } - case 'upper_temp': - if (value >= 35 && value <= 95) { - await sendDataPointValue(entity, dataPoints.x5hSetTempCeiling, value); - const setpoint = globalStore.getValue(entity, 'currentHeatingSetpoint', 20); - const setpointRaw = Math.round(setpoint * 10); - await new Promise((r) => setTimeout(r, 500)); - await sendDataPointValue(entity, dataPoints.x5hSetTemp, setpointRaw); - } else { - throw new Error('Supported values are in range [35, 95]'); + case 'system_mode': + await sendDataPointBool(entity, dataPoints.x5hState, value === 'heat'); + break; + case 'preset': { + value = value.toLowerCase(); + const lookup: KeyValueAny = {manual: 0, program: 1}; + utils.validateValue(value, Object.keys(lookup)); + value = lookup[value]; + await sendDataPointEnum(entity, dataPoints.x5hMode, value); + break; } - break; - case 'deadzone_temperature': - if (value >= 0.5 && value <= 9.5) { - value = Math.round(value * 10); - await sendDataPointValue(entity, dataPoints.x5hTempDiff, value); - } else { - throw new Error('Supported values are in range [0.5, 9.5]'); + case 'upper_temp': + if (value >= 35 && value <= 95) { + await sendDataPointValue(entity, dataPoints.x5hSetTempCeiling, value); + const setpoint = globalStore.getValue(entity, 'currentHeatingSetpoint', 20); + const setpointRaw = Math.round(setpoint * 10); + await new Promise((r) => setTimeout(r, 500)); + await sendDataPointValue(entity, dataPoints.x5hSetTemp, setpointRaw); + } else { + throw new Error('Supported values are in range [35, 95]'); + } + break; + case 'deadzone_temperature': + if (value >= 0.5 && value <= 9.5) { + value = Math.round(value * 10); + await sendDataPointValue(entity, dataPoints.x5hTempDiff, value); + } else { + throw new Error('Supported values are in range [0.5, 9.5]'); + } + break; + case 'heating_temp_limit': + if (value >= 5 && value <= 60) { + await sendDataPointValue(entity, dataPoints.x5hProtectionTempLimit, value); + } else { + throw new Error('Supported values are in range [5, 60]'); + } + break; + case 'local_temperature_calibration': + if (value >= -9.9 && value <= 9.9) { + value = Math.round(value * 10); + + if (value < 0) { + value = 0xffffffff + value + 1; + } + + await sendDataPointValue(entity, dataPoints.x5hTempCorrection, value); + } else { + throw new Error('Supported values are in range [-9.9, 9.9]'); + } + break; + case 'factory_reset': + await sendDataPointBool(entity, dataPoints.x5hFactoryReset, value === 'ON'); + break; + case 'week': + await sendDataPointEnum(entity, dataPoints.x5hWorkingDaySetting, utils.getKey(thermostatWeekFormat, value, value, Number)); + break; + case 'frost_protection': + await sendDataPointBool(entity, dataPoints.x5hFrostProtection, value === 'ON'); + break; + case 'sound': + await sendDataPointBool(entity, dataPoints.x5hSound, value === 'ON'); + break; + case 'brightness_state': { + value = value.toLowerCase(); + const lookup: KeyValueAny = {off: 0, low: 1, medium: 2, high: 3}; + utils.validateValue(value, Object.keys(lookup)); + value = lookup[value]; + await sendDataPointEnum(entity, dataPoints.x5hBackplaneBrightness, value); + break; } - break; - case 'heating_temp_limit': - if (value >= 5 && value <= 60) { - await sendDataPointValue(entity, dataPoints.x5hProtectionTempLimit, value); - } else { - throw new Error('Supported values are in range [5, 60]'); + case 'sensor': { + value = value.toLowerCase(); + const lookup: KeyValueAny = {internal: 0, external: 1, both: 2}; + utils.validateValue(value, Object.keys(lookup)); + value = lookup[value]; + await sendDataPointEnum(entity, dataPoints.x5hSensorSelection, value); + break; } - break; - case 'local_temperature_calibration': - if (value >= -9.9 && value <= 9.9) { - value = Math.round(value * 10); - - if (value < 0) { - value = 0xFFFFFFFF + value + 1; + case 'current_heating_setpoint': + if (value >= 5 && value <= 60) { + value = Math.round(value * 10); + await sendDataPointValue(entity, dataPoints.x5hSetTemp, value); + } else { + throw new Error(`Unsupported value: ${value}`); } + break; + case 'child_lock': + await sendDataPointBool(entity, dataPoints.x5hChildLock, value === 'LOCK'); + break; + case 'schedule': { + const periods = value.split(' '); + const periodsNumber = 8; + const payload = []; - await sendDataPointValue(entity, dataPoints.x5hTempCorrection, value); - } else { - throw new Error('Supported values are in range [-9.9, 9.9]'); - } - break; - case 'factory_reset': - await sendDataPointBool(entity, dataPoints.x5hFactoryReset, value === 'ON'); - break; - case 'week': - await sendDataPointEnum(entity, dataPoints.x5hWorkingDaySetting, - utils.getKey(thermostatWeekFormat, value, value, Number)); - break; - case 'frost_protection': - await sendDataPointBool(entity, dataPoints.x5hFrostProtection, value === 'ON'); - break; - case 'sound': - await sendDataPointBool(entity, dataPoints.x5hSound, value === 'ON'); - break; - case 'brightness_state': { - value = value.toLowerCase(); - const lookup: KeyValueAny = {off: 0, low: 1, medium: 2, high: 3}; - utils.validateValue(value, Object.keys(lookup)); - value = lookup[value]; - await sendDataPointEnum(entity, dataPoints.x5hBackplaneBrightness, value); - break; - } - case 'sensor': { - value = value.toLowerCase(); - const lookup: KeyValueAny = {'internal': 0, 'external': 1, 'both': 2}; - utils.validateValue(value, Object.keys(lookup)); - value = lookup[value]; - await sendDataPointEnum(entity, dataPoints.x5hSensorSelection, value); - break; - } - case 'current_heating_setpoint': - if (value >= 5 && value <= 60) { - value = Math.round(value * 10); - await sendDataPointValue(entity, dataPoints.x5hSetTemp, value); - } else { - throw new Error(`Unsupported value: ${value}`); - } - break; - case 'child_lock': - await sendDataPointBool(entity, dataPoints.x5hChildLock, value === 'LOCK'); - break; - case 'schedule': { - const periods = value.split(' '); - const periodsNumber = 8; - const payload = []; + for (let i = 0; i < periodsNumber; i++) { + const timeTemp = periods[i].split('/'); + const hm = timeTemp[0].split(':', 2); + const h = parseInt(hm[0]); + const m = parseInt(hm[1]); + const temp = parseFloat(timeTemp[1]); - for (let i = 0; i < periodsNumber; i++) { - const timeTemp = periods[i].split('/'); - const hm = timeTemp[0].split(':', 2); - const h = parseInt(hm[0]); - const m = parseInt(hm[1]); - const temp = parseFloat(timeTemp[1]); + if (h < 0 || h >= 24 || m < 0 || m >= 60 || temp < 5 || temp > 60) { + throw new Error('Invalid hour, minute or temperature of: ' + periods[i]); + } - if (h < 0 || h >= 24 || m < 0 || m >= 60 || temp < 5 || temp > 60) { - throw new Error('Invalid hour, minute or temperature of: ' + periods[i]); + const tempHexArray = convertDecimalValueTo2ByteHexArray(Math.round(temp * 10)); + // 1 byte for hour, 1 byte for minutes, 2 bytes for temperature + payload.push(h, m, ...tempHexArray); } - const tempHexArray = convertDecimalValueTo2ByteHexArray(Math.round(temp * 10)); - // 1 byte for hour, 1 byte for minutes, 2 bytes for temperature - payload.push(h, m, ...tempHexArray); + await sendDataPointRaw(entity, dataPoints.x5hWeeklyProcedure, payload); + break; } - - await sendDataPointRaw(entity, dataPoints.x5hWeeklyProcedure, payload); - break; - } - default: - break; + default: + break; } }, } satisfies Tz.Converter, @@ -5955,8 +6056,8 @@ const toZigbee2 = { key: ['current_heating_setpoint'], convertSet: async (entity, key, value: number, meta) => { let temp = Math.round(value * 2); - if (temp<=0) temp = 1; - if (temp>=60) temp = 59; + if (temp <= 0) temp = 1; + if (temp >= 60) temp = 59; await sendDataPointValue(entity, dataPoints.zsHeatingSetpoint, temp); }, } satisfies Tz.Converter, @@ -5964,8 +6065,8 @@ const toZigbee2 = { key: ['current_heating_setpoint_auto'], convertSet: async (entity, key, value: number, meta) => { let temp = Math.round(value * 2); - if (temp<=0) temp = 1; - if (temp>=60) temp = 59; + if (temp <= 0) temp = 1; + if (temp >= 60) temp = 59; await sendDataPointValue(entity, dataPoints.zsHeatingSetpointAuto, temp); }, } satisfies Tz.Converter, @@ -5981,8 +6082,8 @@ const toZigbee2 = { key: ['detectwindow_temperature'], convertSet: async (entity, key, value: number, meta) => { let temp = Math.round(value * 2); - if (temp<=0) temp = 1; - if (temp>=60) temp = 59; + if (temp <= 0) temp = 1; + if (temp >= 60) temp = 59; await sendDataPointValue(entity, dataPoints.zsOpenwindowTemp, temp); }, } satisfies Tz.Converter, @@ -6002,7 +6103,7 @@ const toZigbee2 = { zs_thermostat_preset_mode: { key: ['preset'], convertSet: async (entity, key, value: any, meta) => { - const lookup: KeyValueAny = {'schedule': 0, 'manual': 1, 'holiday': 2}; + const lookup: KeyValueAny = {schedule: 0, manual: 1, holiday: 2}; if (value == 'boost') { await sendDataPointEnum(entity, dataPoints.zsMode, lookup['manual']); await sendDataPointValue(entity, dataPoints.zsHeatingSetpoint, 60); @@ -6010,7 +6111,7 @@ const toZigbee2 = { await sendDataPointEnum(entity, dataPoints.zsMode, lookup[value]); if (value == 'manual') { const temp = globalStore.getValue(entity, 'current_heating_setpoint'); - await sendDataPointValue(entity, dataPoints.zsHeatingSetpoint, temp ? Math.round(temp * 2) : 43 ); + await sendDataPointValue(entity, dataPoints.zsHeatingSetpoint, temp ? Math.round(temp * 2) : 43); } } }, @@ -6027,15 +6128,15 @@ const toZigbee2 = { // manual const temp = globalStore.getValue(entity, 'current_heating_setpoint'); await sendDataPointEnum(entity, dataPoints.zsMode, 1); - await sendDataPointValue(entity, dataPoints.zsHeatingSetpoint, temp ? Math.round(temp * 2) : 43 ); + await sendDataPointValue(entity, dataPoints.zsHeatingSetpoint, temp ? Math.round(temp * 2) : 43); } }, } satisfies Tz.Converter, zs_thermostat_local_temperature_calibration: { key: ['local_temperature_calibration'], convertSet: async (entity, key, value: number, meta) => { - if (value > 0) value = value*10; - if (value < 0) value = value*10 + 0x100000000; + if (value > 0) value = value * 10; + if (value < 0) value = value * 10 + 0x100000000; await sendDataPointValue(entity, dataPoints.zsTempCalibration, value); }, } satisfies Tz.Converter, @@ -6043,15 +6144,17 @@ const toZigbee2 = { key: ['away_setting'], convertSet: async (entity, key, value: KeyValueAny, meta) => { const result: any = []; - const daysInMonth = new Date(2000+result[0], result[1], 0).getDate(); + const daysInMonth = new Date(2000 + result[0], result[1], 0).getDate(); - for (const attrName of ['away_preset_year', + for (const attrName of [ + 'away_preset_year', 'away_preset_month', 'away_preset_day', 'away_preset_hour', 'away_preset_minute', 'away_preset_temperature', - 'away_preset_days']) { + 'away_preset_days', + ]) { let v = 0; if (value.hasOwnProperty(attrName)) { v = value[attrName]; @@ -6060,39 +6163,39 @@ const toZigbee2 = { v = meta.state[attrName]; } switch (attrName) { - case 'away_preset_year': - if (v<17 || v>99) v = 17; - result.push(Math.round(v)); - break; - case 'away_preset_month': - if (v<1 || v>12) v = 1; - result.push(Math.round(v)); - break; - case 'away_preset_day': - if (v<1) { - v = 1; - } else if (v>daysInMonth) { - v = daysInMonth; - } - result.push(Math.round(v)); - break; - case 'away_preset_hour': - if (v<0 || v>23) v = 0; - result.push(Math.round(v)); - break; - case 'away_preset_minute': - if (v<0 || v>59) v = 0; - result.push(Math.round(v)); - break; - case 'away_preset_temperature': - if (v<0.5 || v>29.5) v = 17; - result.push(Math.round(v * 2)); - break; - case 'away_preset_days': - if (v<1 || v>9999) v = 1; - result.push((v & 0xff00)>>8); - result.push((v & 0x00ff)); - break; + case 'away_preset_year': + if (v < 17 || v > 99) v = 17; + result.push(Math.round(v)); + break; + case 'away_preset_month': + if (v < 1 || v > 12) v = 1; + result.push(Math.round(v)); + break; + case 'away_preset_day': + if (v < 1) { + v = 1; + } else if (v > daysInMonth) { + v = daysInMonth; + } + result.push(Math.round(v)); + break; + case 'away_preset_hour': + if (v < 0 || v > 23) v = 0; + result.push(Math.round(v)); + break; + case 'away_preset_minute': + if (v < 0 || v > 59) v = 0; + result.push(Math.round(v)); + break; + case 'away_preset_temperature': + if (v < 0.5 || v > 29.5) v = 17; + result.push(Math.round(v * 2)); + break; + case 'away_preset_days': + if (v < 1 || v > 9999) v = 1; + result.push((v & 0xff00) >> 8); + result.push(v & 0x00ff); + break; } } @@ -6102,7 +6205,7 @@ const toZigbee2 = { zs_thermostat_local_schedule: { key: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'], convertSet: async (entity, key, value: any, meta) => { - const daysMap: KeyValueAny = {'monday': 1, 'tuesday': 2, 'wednesday': 3, 'thursday': 4, 'friday': 5, 'saturday': 6, 'sunday': 7}; + const daysMap: KeyValueAny = {monday: 1, tuesday: 2, wednesday: 3, thursday: 4, friday: 5, saturday: 6, sunday: 7}; const day = daysMap[key]; const results = []; results.push(day); @@ -6116,9 +6219,9 @@ const toZigbee2 = { // @ts-expect-error v = meta.state[attrName]; } - if (v<0.5 || v>29.5) v = 17; + if (v < 0.5 || v > 29.5) v = 17; results.push(Math.round(v * 2)); - if (i!=9) { + if (i != 9) { // hour let attrName = `${key}_hour_${i}`; let h = 0; @@ -6137,22 +6240,21 @@ const toZigbee2 = { // @ts-expect-error m = meta.state[attrName]; } - let rt = h*4 + m/15; - if (rt<1) { - rt =1; - } else if (rt>96) { + let rt = h * 4 + m / 15; + if (rt < 1) { + rt = 1; + } else if (rt > 96) { rt = 96; } results.push(Math.round(rt)); } } - if (value > 0) value = value*10; - if (value < 0) value = value*10 + 0x100000000; - await sendDataPointRaw(entity, (109+day-1), results); + if (value > 0) value = value * 10; + if (value < 0) value = value * 10 + 0x100000000; + await sendDataPointRaw(entity, 109 + day - 1, results); }, } satisfies Tz.Converter, - giexWaterValve: - { + giexWaterValve: { key: [ giexWaterValve.mode, giexWaterValve.irrigationTarget, @@ -6164,26 +6266,26 @@ const toZigbee2 = { if (Array.isArray(meta.mapped)) throw new Error(`Not supported for groups`); const modelConverters = giexTzModelConverters[meta.mapped?.model] || {}; switch (key) { - case giexWaterValve.state: - await sendDataPointBool(entity, dataPoints.giexWaterValve.state, value === ON); - break; - case giexWaterValve.mode: - await sendDataPointBool(entity, dataPoints.giexWaterValve.mode, value === CAPACITY); - return {state: {[giexWaterValve.mode]: value}}; - case giexWaterValve.irrigationTarget: { - const mode = meta.state?.[giexWaterValve.mode]; - const sanitizedValue = modelConverters.irrigationTarget?.(value, mode) || value; - await sendDataPointValue(entity, dataPoints.giexWaterValve.irrigationTarget, sanitizedValue); - return {state: {[giexWaterValve.irrigationTarget]: sanitizedValue}}; - } - case giexWaterValve.cycleIrrigationNumTimes: - await sendDataPointValue(entity, dataPoints.giexWaterValve.cycleIrrigationNumTimes, value); - return {state: {[giexWaterValve.cycleIrrigationNumTimes]: value}}; - case giexWaterValve.cycleIrrigationInterval: - await sendDataPointValue(entity, dataPoints.giexWaterValve.cycleIrrigationInterval, value); - return {state: {[giexWaterValve.cycleIrrigationInterval]: value}}; - default: // Unknown key warning - logger.warning(`Unhandled KEY ${key}`, 'zhc:legacy:tz:giex_water_Valve'); + case giexWaterValve.state: + await sendDataPointBool(entity, dataPoints.giexWaterValve.state, value === ON); + break; + case giexWaterValve.mode: + await sendDataPointBool(entity, dataPoints.giexWaterValve.mode, value === CAPACITY); + return {state: {[giexWaterValve.mode]: value}}; + case giexWaterValve.irrigationTarget: { + const mode = meta.state?.[giexWaterValve.mode]; + const sanitizedValue = modelConverters.irrigationTarget?.(value, mode) || value; + await sendDataPointValue(entity, dataPoints.giexWaterValve.irrigationTarget, sanitizedValue); + return {state: {[giexWaterValve.irrigationTarget]: sanitizedValue}}; + } + case giexWaterValve.cycleIrrigationNumTimes: + await sendDataPointValue(entity, dataPoints.giexWaterValve.cycleIrrigationNumTimes, value); + return {state: {[giexWaterValve.cycleIrrigationNumTimes]: value}}; + case giexWaterValve.cycleIrrigationInterval: + await sendDataPointValue(entity, dataPoints.giexWaterValve.cycleIrrigationInterval, value); + return {state: {[giexWaterValve.cycleIrrigationInterval]: value}}; + default: // Unknown key warning + logger.warning(`Unhandled KEY ${key}`, 'zhc:legacy:tz:giex_water_Valve'); } }, } satisfies Tz.Converter, @@ -6191,14 +6293,14 @@ const toZigbee2 = { key: ['self_checking', 'silence'], convertSet: async (entity, key, value: any, meta) => { switch (key) { - case 'self_checking': - await sendDataPointBool(entity, dataPoints.alectoSelfChecking, value); - break; - case 'silence': - await sendDataPointBool(entity, dataPoints.alectoSilence, value); - break; - default: // Unknown key - throw new Error(`zigbee-herdsman-converters:tuya_alecto_smoke: Unhandled key ${key}`); + case 'self_checking': + await sendDataPointBool(entity, dataPoints.alectoSelfChecking, value); + break; + case 'silence': + await sendDataPointBool(entity, dataPoints.alectoSilence, value); + break; + default: // Unknown key + throw new Error(`zigbee-herdsman-converters:tuya_alecto_smoke: Unhandled key ${key}`); } }, } satisfies Tz.Converter, @@ -6213,65 +6315,74 @@ const toZigbee2 = { } satisfies Tz.Converter, connecte_thermostat: { key: [ - 'child_lock', 'current_heating_setpoint', 'local_temperature_calibration', 'max_temperature_protection', 'window_detection', - 'hysteresis', 'state', 'away_mode', 'sensor', 'system_mode', + 'child_lock', + 'current_heating_setpoint', + 'local_temperature_calibration', + 'max_temperature_protection', + 'window_detection', + 'hysteresis', + 'state', + 'away_mode', + 'sensor', + 'system_mode', ], convertSet: async (entity, key, value, meta) => { switch (key) { - case 'state': - await sendDataPointBool(entity, dataPoints.connecteState, value === 'ON'); - break; - case 'child_lock': - await sendDataPointBool(entity, dataPoints.connecteChildLock, value === 'LOCK'); - break; - case 'local_temperature_calibration': - // @ts-ignore - if (value < 0) value = 0xFFFFFFFF + value + 1; - await sendDataPointValue(entity, dataPoints.connecteTempCalibration, value); - break; - case 'hysteresis': - // value = Math.round(value * 10); - await sendDataPointValue(entity, dataPoints.connecteHysteresis, value); - break; - case 'max_temperature_protection': - // @ts-ignore - await sendDataPointValue(entity, dataPoints.connecteMaxProtectTemp, Math.round(value)); - break; - case 'current_heating_setpoint': - await sendDataPointValue(entity, dataPoints.connecteHeatingSetpoint, value); - break; - case 'sensor': - await sendDataPointEnum( - entity, - dataPoints.connecteSensorType, + case 'state': + await sendDataPointBool(entity, dataPoints.connecteState, value === 'ON'); + break; + case 'child_lock': + await sendDataPointBool(entity, dataPoints.connecteChildLock, value === 'LOCK'); + break; + case 'local_temperature_calibration': // @ts-ignore - {'internal': 0, 'external': 1, 'both': 2}[value]); - break; - case 'system_mode': - switch (value) { - case 'heat': - await sendDataPointEnum(entity, dataPoints.connecteMode, 0 /* manual */); + if (value < 0) value = 0xffffffff + value + 1; + await sendDataPointValue(entity, dataPoints.connecteTempCalibration, value); break; - case 'auto': - await sendDataPointEnum(entity, dataPoints.connecteMode, 1 /* auto */); + case 'hysteresis': + // value = Math.round(value * 10); + await sendDataPointValue(entity, dataPoints.connecteHysteresis, value); break; - } - break; - case 'away_mode': - switch (value) { - case 'ON': - await sendDataPointEnum(entity, dataPoints.connecteMode, 2 /* auto */); + case 'max_temperature_protection': + // @ts-ignore + await sendDataPointValue(entity, dataPoints.connecteMaxProtectTemp, Math.round(value)); break; - case 'OFF': - await sendDataPointEnum(entity, dataPoints.connecteMode, 0 /* manual */); + case 'current_heating_setpoint': + await sendDataPointValue(entity, dataPoints.connecteHeatingSetpoint, value); break; - } - break; - case 'window_detection': - await sendDataPointBool(entity, dataPoints.connecteOpenWindow, value === 'ON'); - break; - default: // Unknown key - throw new Error(`Unhandled key toZigbee.connecte_thermostat ${key}`); + case 'sensor': + await sendDataPointEnum( + entity, + dataPoints.connecteSensorType, + // @ts-ignore + {internal: 0, external: 1, both: 2}[value], + ); + break; + case 'system_mode': + switch (value) { + case 'heat': + await sendDataPointEnum(entity, dataPoints.connecteMode, 0 /* manual */); + break; + case 'auto': + await sendDataPointEnum(entity, dataPoints.connecteMode, 1 /* auto */); + break; + } + break; + case 'away_mode': + switch (value) { + case 'ON': + await sendDataPointEnum(entity, dataPoints.connecteMode, 2 /* auto */); + break; + case 'OFF': + await sendDataPointEnum(entity, dataPoints.connecteMode, 0 /* manual */); + break; + } + break; + case 'window_detection': + await sendDataPointBool(entity, dataPoints.connecteOpenWindow, value === 'ON'); + break; + default: // Unknown key + throw new Error(`Unhandled key toZigbee.connecte_thermostat ${key}`); } }, } satisfies Tz.Converter, @@ -6343,25 +6454,25 @@ const toZigbee2 = { convertSet: async (entity, key, value, meta) => { // const stateLookup: KeyValueAny = {'0': 'cool', '1': 'heat', '2': 'fan_only'}; switch (value) { - case 'off': - await sendDataPointBool(entity, dataPoints.moesSsystemMode, 0); - break; - case 'cool': - // turn on - await sendDataPointBool(entity, dataPoints.moesSsystemMode, 1); - await sendDataPointEnum(entity, dataPoints.tvMode, 0); - break; - case 'heat': - // turn on - await sendDataPointBool(entity, dataPoints.moesSsystemMode, 1); - await sendDataPointEnum(entity, dataPoints.tvMode, 1); - break; - case 'fan_only': - // turn on - await sendDataPointBool(entity, dataPoints.moesSsystemMode, 1); - await sendDataPointEnum(entity, dataPoints.tvMode, 2); - // await sendDataPointEnum(entity, dataPoints.moesScheduleEnable, 0); - break; + case 'off': + await sendDataPointBool(entity, dataPoints.moesSsystemMode, 0); + break; + case 'cool': + // turn on + await sendDataPointBool(entity, dataPoints.moesSsystemMode, 1); + await sendDataPointEnum(entity, dataPoints.tvMode, 0); + break; + case 'heat': + // turn on + await sendDataPointBool(entity, dataPoints.moesSsystemMode, 1); + await sendDataPointEnum(entity, dataPoints.tvMode, 1); + break; + case 'fan_only': + // turn on + await sendDataPointBool(entity, dataPoints.moesSsystemMode, 1); + await sendDataPointEnum(entity, dataPoints.tvMode, 2); + // await sendDataPointEnum(entity, dataPoints.moesScheduleEnable, 0); + break; } }, } satisfies Tz.Converter, @@ -6436,7 +6547,7 @@ const toZigbee2 = { moesS_thermostat_preset: { key: ['preset'], convertSet: async (entity, key, value: any, meta) => { - const lookup: KeyValueAny = {'programming': 0, 'manual': 1, 'temporary_manual': 2, 'holiday': 3}; + const lookup: KeyValueAny = {programming: 0, manual: 1, temporary_manual: 2, holiday: 3}; await sendDataPointEnum(entity, dataPoints.moesSsystemMode, lookup[value]); }, } satisfies Tz.Converter, @@ -6476,7 +6587,7 @@ const toZigbee2 = { convertSet: async (entity, key, value: number, meta) => { let temp = Math.round(value * 1); if (temp < 0) { - temp = 0xFFFFFFFF + temp + 1; + temp = 0xffffffff + temp + 1; } await sendDataPointValue(entity, dataPoints.moesScompensationTempSet, temp); }, @@ -6522,7 +6633,9 @@ const toZigbee2 = { if (h < 0 || h >= 24 || m < 0 || m >= 60 || temp < 5 || temp >= 35) { throw new Error('Invalid hour, minute or temperature of:' + items[i]); } - payload[i*3] = h; payload[i*3+1] = m; payload[i*3+2] = temp * 2; + payload[i * 3] = h; + payload[i * 3 + 1] = m; + payload[i * 3 + 2] = temp * 2; } await sendDataPointRaw(entity, dataPoints.moesSschedule, payload); }, @@ -6537,25 +6650,25 @@ const toZigbee2 = { key: ['power_on_behavior', 'indicate_light'], convertSet: async (entity, key, value, meta) => { switch (key) { - case 'power_on_behavior': - await sendDataPointEnum( - entity, - dataPoints.moesSwitchPowerOnBehavior, - // @ts-expect-error - utils.getKey(moesSwitch.powerOnBehavior, value), - ); - break; - case 'indicate_light': - await sendDataPointEnum( - entity, - dataPoints.moesSwitchIndicateLight, - // @ts-expect-error - utils.getKey(moesSwitch.indicateLight, value), - ); - break; - default: - logger.warning(`Unhandled Key ${key}`, 'zhc:legacy:tz:moes_switch'); - break; + case 'power_on_behavior': + await sendDataPointEnum( + entity, + dataPoints.moesSwitchPowerOnBehavior, + // @ts-expect-error + utils.getKey(moesSwitch.powerOnBehavior, value), + ); + break; + case 'indicate_light': + await sendDataPointEnum( + entity, + dataPoints.moesSwitchIndicateLight, + // @ts-expect-error + utils.getKey(moesSwitch.indicateLight, value), + ); + break; + default: + logger.warning(`Unhandled Key ${key}`, 'zhc:legacy:tz:moes_switch'); + break; } }, } satisfies Tz.Converter, @@ -6564,11 +6677,11 @@ const toZigbee2 = { convertSet: async (entity, key, value: any, meta) => { if (typeof value === 'string') { value = value.toLowerCase(); - const lookup: KeyValueAny = {'in': 0, 'al': 1, 'ou': 2}; + const lookup: KeyValueAny = {in: 0, al: 1, ou: 2}; utils.validateValue(value, Object.keys(lookup)); value = lookup[value]; } - if ((typeof value === 'number') && (value >= 0) && (value <= 2)) { + if (typeof value === 'number' && value >= 0 && value <= 2) { await sendDataPointEnum(entity, dataPoints.moesSensor, value); } else { throw new Error(`Unsupported value: ${value}`); @@ -6624,7 +6737,8 @@ const toZigbee2 = { } else { throw new Error('Dimmer brightness_percent is out of range 0..100'); } - } else { // brightness + } else { + // brightness if (value >= 0 && value <= 254) { newValue = utils.mapNumberRange(value, 0, 254, 0, 1000); } else { @@ -6649,7 +6763,7 @@ const toZigbee2 = { key: ['threshold'], convertSet: async (entity, key, value: number, meta) => { // input to multiple of 10 with max value of 100 - const thresh = Math.abs(Math.min(10 * (Math.floor(value / 10)), 100)); + const thresh = Math.abs(Math.min(10 * Math.floor(value / 10), 100)); await sendDataPointValue(entity, dataPoints.frankEverTreshold, thresh, 'dataRequest', 1); return {state: {threshold: value}}; }, @@ -6677,7 +6791,7 @@ const toZigbee2 = { ZVG1_weather_delay: { key: ['weather_delay'], convertSet: async (entity, key, value: string, meta) => { - const lookup: KeyValueAny = {'disabled': 0, '24h': 1, '48h': 2, '72h': 3}; + const lookup: KeyValueAny = {disabled: 0, '24h': 1, '48h': 2, '72h': 3}; await sendDataPointEnum(entity, 10, lookup[value]); }, } satisfies Tz.Converter, @@ -6695,8 +6809,7 @@ const toZigbee2 = { ret['state'][key] = value; return ret; } else { - if ((meta.state.hasOwnProperty(key) && meta.state[key] == '') || - !meta.state.hasOwnProperty(key)) { + if ((meta.state.hasOwnProperty(key) && meta.state[key] == '') || !meta.state.hasOwnProperty(key)) { data.push(0x03); } else { data.push(0x02); @@ -6806,19 +6919,19 @@ const toZigbee2 = { key: ['system_mode'], convertSet: async (entity, key, value, meta) => { switch (value) { - case 'off': - await sendDataPointBool(entity, dataPoints.state, false); - break; - case 'heat': - await sendDataPointBool(entity, dataPoints.state, true); - await utils.sleep(500); - await sendDataPointEnum(entity, dataPoints.mode, 0 /* manual */); - break; - case 'auto': - await sendDataPointBool(entity, dataPoints.state, true); - await utils.sleep(500); - await sendDataPointEnum(entity, dataPoints.mode, 2 /* auto */); - break; + case 'off': + await sendDataPointBool(entity, dataPoints.state, false); + break; + case 'heat': + await sendDataPointBool(entity, dataPoints.state, true); + await utils.sleep(500); + await sendDataPointEnum(entity, dataPoints.mode, 0 /* manual */); + break; + case 'auto': + await sendDataPointBool(entity, dataPoints.state, true); + await utils.sleep(500); + await sendDataPointEnum(entity, dataPoints.mode, 2 /* auto */); + break; } }, } satisfies Tz.Converter, @@ -6826,14 +6939,14 @@ const toZigbee2 = { key: ['away_mode'], convertSet: async (entity, key, value, meta) => { switch (value) { - case 'ON': - await sendDataPointBool(entity, dataPoints.state, true); - await utils.sleep(500); - await sendDataPointEnum(entity, dataPoints.mode, 1 /* away */); - break; - case 'OFF': - await sendDataPointEnum(entity, dataPoints.mode, 0 /* manual */); - break; + case 'ON': + await sendDataPointBool(entity, dataPoints.state, true); + await utils.sleep(500); + await sendDataPointEnum(entity, dataPoints.mode, 1 /* away */); + break; + case 'OFF': + await sendDataPointEnum(entity, dataPoints.mode, 0 /* manual */); + break; } }, } satisfies Tz.Converter, @@ -6859,12 +6972,7 @@ const toZigbee2 = { // see also fromZigbee.tuya_thermostat_weekly_schedule() const minutesSinceMidnight = transition.transitionTime; const heatSetpoint = Math.floor(transition.heatSetpoint * 10); - return [ - (minutesSinceMidnight & 0xff00) >> 8, - minutesSinceMidnight & 0xff, - (heatSetpoint & 0xff00) >> 8, - heatSetpoint & 0xff, - ]; + return [(minutesSinceMidnight & 0xff00) >> 8, minutesSinceMidnight & 0xff, (heatSetpoint & 0xff00) >> 8, heatSetpoint & 0xff]; } for (const [, daySchedule] of Object.entries(value)) { @@ -6876,12 +6984,16 @@ const toZigbee2 = { throw new Error(`Invalid mode: ${mode} for device ${meta.options.friendly_name}`); } if (numoftrans != transitions.length) { - throw new Error(`Invalid numoftrans provided. Real: ${transitions.length} ` + - `provided ${numoftrans} for device ${meta.options.friendly_name}`); + throw new Error( + `Invalid numoftrans provided. Real: ${transitions.length} ` + + `provided ${numoftrans} for device ${meta.options.friendly_name}`, + ); } if (transitions.length > maxTransitions) { - throw new Error(`Too more transitions provided. Provided: ${transitions.length} ` + - `but supports only ${numoftrans} for device ${meta.options.friendly_name}`); + throw new Error( + `Too more transitions provided. Provided: ${transitions.length} ` + + `but supports only ${numoftrans} for device ${meta.options.friendly_name}`, + ); } if (transitions.length < maxTransitions) { logger.warning( @@ -6913,15 +7025,9 @@ const toZigbee2 = { payload.push(...transitionToData(transition)); }); if (conversion == 'saswell') { - await sendDataPointRaw( - entity, - dataPoints.saswellScheduleSet, - payload); + await sendDataPointRaw(entity, dataPoints.saswellScheduleSet, payload); } else { - await sendDataPointRaw( - entity, - firstDayDpId - 1 + dayofweek, - payload); + await sendDataPointRaw(entity, firstDayDpId - 1 + dayofweek, payload); } } }, @@ -6935,19 +7041,13 @@ const toZigbee2 = { tuya_thermostat_window_detection: { key: ['window_detection'], convertSet: async (entity, key, value, meta) => { - await sendDataPointRaw( - entity, - dataPoints.windowDetection, - [value === 'ON' ? 1 : 0]); + await sendDataPointRaw(entity, dataPoints.windowDetection, [value === 'ON' ? 1 : 0]); }, } satisfies Tz.Converter, siterwell_thermostat_window_detection: { key: ['window_detection'], convertSet: async (entity, key, value, meta) => { - await sendDataPointBool( - entity, - dataPoints.siterwellWindowDetection, - value === 'ON'); + await sendDataPointBool(entity, dataPoints.siterwellWindowDetection, value === 'ON'); }, } satisfies Tz.Converter, tuya_thermostat_valve_detection: { @@ -6994,8 +7094,11 @@ const toZigbee2 = { // @ts-expect-error const awayPresetId = utils.getKey(utils.getMetaValue(entity, meta.mapped, 'tuyaThermostatPreset'), 'away', null, Number); const schedulePresetId = utils.getKey( + utils.getMetaValue(entity, meta.mapped, 'tuyaThermostatPreset'), + 'schedule', + null, // @ts-expect-error - utils.getMetaValue(entity, meta.mapped, 'tuyaThermostatPreset'), 'schedule', null, Number, + Number, ); if (awayPresetId !== null) { if (value == 'ON') { @@ -7044,7 +7147,7 @@ const toZigbee2 = { convertSet: async (entity, key, value: number, meta) => { let temp = Math.round(value * 10); if (temp < 0) { - temp = 0xFFFFFFFF + temp + 1; + temp = 0xffffffff + temp + 1; } await sendDataPointValue(entity, dataPoints.tempCalibration, temp); }, @@ -7107,40 +7210,39 @@ const toZigbee2 = { key: ['away_preset_temperature', 'away_preset_days'], convertSet: async (entity, key, value, meta) => { switch (key) { - case 'away_preset_days': - await sendDataPointValue(entity, dataPoints.awayDays, value); - break; - case 'away_preset_temperature': - await sendDataPointValue(entity, dataPoints.awayTemp, value); - break; + case 'away_preset_days': + await sendDataPointValue(entity, dataPoints.awayDays, value); + break; + case 'away_preset_temperature': + await sendDataPointValue(entity, dataPoints.awayTemp, value); + break; } }, } satisfies Tz.Converter, - tuya_thermostat_window_detect: { // payload example { "detect":"OFF", "temperature":5, "minutes":8} + tuya_thermostat_window_detect: { + // payload example { "detect":"OFF", "temperature":5, "minutes":8} key: ['window_detect'], convertSet: async (entity, key, value: KeyValueAny, meta) => { const detect = value.detect.toUpperCase() === 'ON' ? 1 : 0; await sendDataPointRaw(entity, dataPoints.windowDetection, [detect, value.temperature, value.minutes]); }, } satisfies Tz.Converter, - tuya_thermostat_schedule: { // payload example {"holidays":[{"hour":6,"minute":0,"temperature":20},{"hour":8,"minute":0,.... 6x + tuya_thermostat_schedule: { + // payload example {"holidays":[{"hour":6,"minute":0,"temperature":20},{"hour":8,"minute":0,.... 6x key: ['schedule'], convertSet: async (entity, key, value: any, meta) => { const prob = Object.keys(value)[0]; // "workdays" or "holidays" - if ((prob === 'workdays') || (prob === 'holidays')) { - const dpId = - (prob === 'workdays') ? - dataPoints.scheduleWorkday : - dataPoints.scheduleHoliday; + if (prob === 'workdays' || prob === 'holidays') { + const dpId = prob === 'workdays' ? dataPoints.scheduleWorkday : dataPoints.scheduleHoliday; const payload = []; for (let i = 0; i < 6; i++) { - if ((value[prob][i].hour >= 0) && (value[prob][i].hour < 24)) { + if (value[prob][i].hour >= 0 && value[prob][i].hour < 24) { payload[i * 3] = value[prob][i].hour; } - if ((value[prob][i].minute >= 0) && (value[prob][i].minute < 60)) { + if (value[prob][i].minute >= 0 && value[prob][i].minute < 60) { payload[i * 3 + 1] = value[prob][i].minute; } - if ((value[prob][i].temperature >= 5) && (value[prob][i].temperature < 35)) { + if (value[prob][i].temperature >= 5 && value[prob][i].temperature < 35) { payload[i * 3 + 2] = value[prob][i].temperature; } } @@ -7148,13 +7250,11 @@ const toZigbee2 = { } }, } satisfies Tz.Converter, - tuya_thermostat_schedule_programming_mode: { // payload example "00:20/5°C 01:20/5°C 6:59/15°C 18:00/5°C 20:00/5°C 23:30/5°C" + tuya_thermostat_schedule_programming_mode: { + // payload example "00:20/5°C 01:20/5°C 6:59/15°C 18:00/5°C 20:00/5°C 23:30/5°C" key: ['workdays_schedule', 'holidays_schedule'], convertSet: async (entity, key, value: any, meta) => { - const dpId = - (key === 'workdays_schedule') ? - dataPoints.scheduleWorkday : - dataPoints.scheduleHoliday; + const dpId = key === 'workdays_schedule' ? dataPoints.scheduleWorkday : dataPoints.scheduleHoliday; const payload = []; const items = value.split(' '); @@ -7169,9 +7269,9 @@ const toZigbee2 = { throw new Error('Invalid hour, minute or temperature of:' + items[i]); } - payload[i*3] = hour; - payload[i*3+1] = minute; - payload[i*3+2] = temperature; + payload[i * 3] = hour; + payload[i * 3 + 1] = minute; + payload[i * 3 + 2] = temperature; } await sendDataPointRaw(entity, dpId, payload); }, @@ -7212,142 +7312,156 @@ const toZigbee2 = { key: ['temperature_max', 'temperature_min', 'humidity_max', 'humidity_min', 'temperature_scale', 'unknown_111', 'unknown_112'], convertSet: async (entity, key, value, meta) => { switch (key) { - case 'temperature_max': - await sendDataPointValue(entity, dataPoints.neoMaxTemp, value); - break; - case 'temperature_min': - await sendDataPointValue(entity, dataPoints.neoMinTemp, value); - break; - case 'humidity_max': - await sendDataPointValue(entity, dataPoints.neoMaxHumidity, value); - break; - case 'humidity_min': - await sendDataPointValue(entity, dataPoints.neoMinHumidity, value); - break; - case 'temperature_scale': - await sendDataPointBool(entity, dataPoints.neoTempScale, value === '°C'); - break; - case 'unknown_111': - await sendDataPointBool(entity, 111, value === 'ON'); - break; - case 'unknown_112': - await sendDataPointBool(entity, 112, value === 'ON'); - break; - default: // Unknown key - throw new Error(`tz.neo_nas_pd07: Unhandled key ${key}`); + case 'temperature_max': + await sendDataPointValue(entity, dataPoints.neoMaxTemp, value); + break; + case 'temperature_min': + await sendDataPointValue(entity, dataPoints.neoMinTemp, value); + break; + case 'humidity_max': + await sendDataPointValue(entity, dataPoints.neoMaxHumidity, value); + break; + case 'humidity_min': + await sendDataPointValue(entity, dataPoints.neoMinHumidity, value); + break; + case 'temperature_scale': + await sendDataPointBool(entity, dataPoints.neoTempScale, value === '°C'); + break; + case 'unknown_111': + await sendDataPointBool(entity, 111, value === 'ON'); + break; + case 'unknown_112': + await sendDataPointBool(entity, 112, value === 'ON'); + break; + default: // Unknown key + throw new Error(`tz.neo_nas_pd07: Unhandled key ${key}`); } }, } satisfies Tz.Converter, neo_t_h_alarm: { key: [ - 'alarm', 'melody', 'volume', 'duration', - 'temperature_max', 'temperature_min', 'humidity_min', 'humidity_max', - 'temperature_alarm', 'humidity_alarm', + 'alarm', + 'melody', + 'volume', + 'duration', + 'temperature_max', + 'temperature_min', + 'humidity_min', + 'humidity_max', + 'temperature_alarm', + 'humidity_alarm', ], convertSet: async (entity, key, value: any, meta) => { switch (key) { - case 'alarm': - await sendDataPointBool(entity, dataPoints.neoAlarm, value); - break; - case 'melody': - await sendDataPointEnum(entity, dataPoints.neoMelody, parseInt(value, 10)); - break; - case 'volume': - await sendDataPointEnum( - entity, - dataPoints.neoVolume, - // @ts-ignore - {'low': 2, 'medium': 1, 'high': 0}[value]); - break; - case 'duration': - await sendDataPointValue(entity, dataPoints.neoDuration, value); - break; - case 'temperature_max': - await sendDataPointValue(entity, dataPoints.neoMaxTemp, value); - break; - case 'temperature_min': - await sendDataPointValue(entity, dataPoints.neoMinTemp, value); - break; - case 'humidity_max': - await sendDataPointValue(entity, dataPoints.neoMaxHumidity, value); - break; - case 'humidity_min': - await sendDataPointValue(entity, dataPoints.neoMinHumidity, value); - break; - case 'temperature_alarm': - await sendDataPointBool(entity, dataPoints.neoTempAlarm, value); - break; - case 'humidity_alarm': - await sendDataPointBool(entity, dataPoints.neoHumidityAlarm, value); - break; - default: // Unknown key - throw new Error(`tz.neo_t_h_alarm: Unhandled key ${key}`); + case 'alarm': + await sendDataPointBool(entity, dataPoints.neoAlarm, value); + break; + case 'melody': + await sendDataPointEnum(entity, dataPoints.neoMelody, parseInt(value, 10)); + break; + case 'volume': + await sendDataPointEnum( + entity, + dataPoints.neoVolume, + // @ts-ignore + {low: 2, medium: 1, high: 0}[value], + ); + break; + case 'duration': + await sendDataPointValue(entity, dataPoints.neoDuration, value); + break; + case 'temperature_max': + await sendDataPointValue(entity, dataPoints.neoMaxTemp, value); + break; + case 'temperature_min': + await sendDataPointValue(entity, dataPoints.neoMinTemp, value); + break; + case 'humidity_max': + await sendDataPointValue(entity, dataPoints.neoMaxHumidity, value); + break; + case 'humidity_min': + await sendDataPointValue(entity, dataPoints.neoMinHumidity, value); + break; + case 'temperature_alarm': + await sendDataPointBool(entity, dataPoints.neoTempAlarm, value); + break; + case 'humidity_alarm': + await sendDataPointBool(entity, dataPoints.neoHumidityAlarm, value); + break; + default: // Unknown key + throw new Error(`tz.neo_t_h_alarm: Unhandled key ${key}`); } }, } satisfies Tz.Converter, neo_alarm: { - key: [ - 'alarm', 'melody', 'volume', 'duration', - ], + key: ['alarm', 'melody', 'volume', 'duration'], convertSet: async (entity, key, value: any, meta) => { switch (key) { - case 'alarm': - await sendDataPointBool(entity, dataPoints.neoAOAlarm, value); - break; - case 'melody': - await sendDataPointEnum(entity, dataPoints.neoAOMelody, parseInt(value, 10)); - break; - case 'volume': - await sendDataPointEnum( - entity, - dataPoints.neoAOVolume, - // @ts-ignore - {'low': 0, 'medium': 1, 'high': 2}[value]); - break; - case 'duration': - await sendDataPointValue(entity, dataPoints.neoAODuration, value); - break; - default: // Unknown key - throw new Error(`Unhandled key ${key}`); + case 'alarm': + await sendDataPointBool(entity, dataPoints.neoAOAlarm, value); + break; + case 'melody': + await sendDataPointEnum(entity, dataPoints.neoAOMelody, parseInt(value, 10)); + break; + case 'volume': + await sendDataPointEnum( + entity, + dataPoints.neoAOVolume, + // @ts-ignore + {low: 0, medium: 1, high: 2}[value], + ); + break; + case 'duration': + await sendDataPointValue(entity, dataPoints.neoAODuration, value); + break; + default: // Unknown key + throw new Error(`Unhandled key ${key}`); } }, } satisfies Tz.Converter, nous_lcd_temperature_humidity_sensor: { key: [ - 'min_temperature', 'max_temperature', 'temperature_sensitivity', 'temperature_unit_convert', 'temperature_report_interval', - 'min_humidity', 'max_humidity', 'humidity_sensitivity', 'humidity_report_interval', + 'min_temperature', + 'max_temperature', + 'temperature_sensitivity', + 'temperature_unit_convert', + 'temperature_report_interval', + 'min_humidity', + 'max_humidity', + 'humidity_sensitivity', + 'humidity_report_interval', ], convertSet: async (entity, key, value: any, meta) => { switch (key) { - case 'temperature_unit_convert': - await sendDataPointEnum(entity, dataPoints.nousTempUnitConvert, ['celsius', 'fahrenheit'].indexOf(value)); - break; - case 'min_temperature': - await sendDataPointValue(entity, dataPoints.nousMinTemp, Math.round(value * 10)); - break; - case 'max_temperature': - await sendDataPointValue(entity, dataPoints.nousMaxTemp, Math.round(value * 10)); - break; - case 'temperature_sensitivity': - await sendDataPointValue(entity, dataPoints.nousTempSensitivity, Math.round(value * 10)); - break; - case 'humidity_sensitivity': - await sendDataPointValue(entity, dataPoints.nousHumiSensitivity, value); - break; - case 'min_humidity': - await sendDataPointValue(entity, dataPoints.nousMinHumi, Math.round(value)); - break; - case 'max_humidity': - await sendDataPointValue(entity, dataPoints.nousMaxHumi, Math.round(value)); - break; - case 'temperature_report_interval': - await sendDataPointValue(entity, dataPoints.nousTempReportInterval, value); - break; - case 'humidity_report_interval': - await sendDataPointValue(entity, dataPoints.nousHumiReportInterval, value); - break; - default: // Unknown key - logger.warning(`Unhandled key ${key}`, 'zhc:legacy:tz:nous_lcd_temperature_humidity_sensor'); + case 'temperature_unit_convert': + await sendDataPointEnum(entity, dataPoints.nousTempUnitConvert, ['celsius', 'fahrenheit'].indexOf(value)); + break; + case 'min_temperature': + await sendDataPointValue(entity, dataPoints.nousMinTemp, Math.round(value * 10)); + break; + case 'max_temperature': + await sendDataPointValue(entity, dataPoints.nousMaxTemp, Math.round(value * 10)); + break; + case 'temperature_sensitivity': + await sendDataPointValue(entity, dataPoints.nousTempSensitivity, Math.round(value * 10)); + break; + case 'humidity_sensitivity': + await sendDataPointValue(entity, dataPoints.nousHumiSensitivity, value); + break; + case 'min_humidity': + await sendDataPointValue(entity, dataPoints.nousMinHumi, Math.round(value)); + break; + case 'max_humidity': + await sendDataPointValue(entity, dataPoints.nousMaxHumi, Math.round(value)); + break; + case 'temperature_report_interval': + await sendDataPointValue(entity, dataPoints.nousTempReportInterval, value); + break; + case 'humidity_report_interval': + await sendDataPointValue(entity, dataPoints.nousHumiReportInterval, value); + break; + default: // Unknown key + logger.warning(`Unhandled key ${key}`, 'zhc:legacy:tz:nous_lcd_temperature_humidity_sensor'); } }, } satisfies Tz.Converter, @@ -7361,7 +7475,7 @@ const toZigbee2 = { saswell_thermostat_mode: { key: ['system_mode'], convertSet: async (entity, key, value, meta) => { - const schedule = (value === 'auto'); + const schedule = value === 'auto'; const enable = !(value === 'off'); await sendDataPointBool(entity, dataPoints.saswellState, enable); // Older versions of Saswell TRVs need the delay to work reliably @@ -7408,7 +7522,7 @@ const toZigbee2 = { saswell_thermostat_calibration: { key: ['local_temperature_calibration'], convertSet: async (entity, key, value: any, meta) => { - if (value < 0) value = 0xFFFFFFFF + value + 1; + if (value < 0) value = 0xffffffff + value + 1; await sendDataPointValue(entity, dataPoints.saswellTempCalibration, value); }, } satisfies Tz.Converter, @@ -7423,15 +7537,15 @@ const toZigbee2 = { key: ['system_mode'], convertSet: async (entity, key, value, meta) => { switch (value) { - case 'off': - await sendDataPointEnum(entity, dataPoints.evanellMode, 3 /* off */); - break; - case 'heat': - await sendDataPointEnum(entity, dataPoints.evanellMode, 2 /* manual */); - break; - case 'auto': - await sendDataPointEnum(entity, dataPoints.evanellMode, 0 /* auto */); - break; + case 'off': + await sendDataPointEnum(entity, dataPoints.evanellMode, 3 /* off */); + break; + case 'heat': + await sendDataPointEnum(entity, dataPoints.evanellMode, 2 /* manual */); + break; + case 'auto': + await sendDataPointEnum(entity, dataPoints.evanellMode, 0 /* auto */); + break; } }, } satisfies Tz.Converter, @@ -7517,10 +7631,7 @@ const toZigbee2 = { const scaled = utils.mapNumberRange(value, 0, 255, 0, 1000); data = data.concat(convertDecimalValueTo2ByteHexArray(scaled)); - await sendDataPoint( - entity, - {dp: dataPoints.silvercrestSetBrightness, datatype: dataTypes.value, data: data}, - ); + await sendDataPoint(entity, {dp: dataPoints.silvercrestSetBrightness, datatype: dataTypes.value, data: data}); } else if (key === 'color') { await sendDataPointEnum(entity, dataPoints.silvercrestChangeMode, silvercrestModes.color); @@ -7582,7 +7693,8 @@ const toZigbee2 = { value.h || value.hue || null, value.s || value.saturation || null, value.b || value.brightness || null, - meta.state); + meta.state, + ); } let data: any = []; @@ -7603,164 +7715,180 @@ const toZigbee2 = { const data = []; switch (mode) { - case 'raw': - for (let i = 2; i < args.length; i++) { - data.push(parseInt(args[i])); - } - await sendDataPointRaw(entity, dp, data); - break; - case 'bool': - await sendDataPointBool(entity, dp, args[2] === '1'); - break; - case 'value': - await sendDataPointValue(entity, dp, parseInt(args[2])); - break; - case 'enum': - await sendDataPointEnum(entity, dp, parseInt(args[2])); - break; - case 'bitmap': - for (let i = 2; i < args.length; i++) { - data.push(parseInt(args[i])); - } - await sendDataPointBitmap(entity, dp, data); - break; + case 'raw': + for (let i = 2; i < args.length; i++) { + data.push(parseInt(args[i])); + } + await sendDataPointRaw(entity, dp, data); + break; + case 'bool': + await sendDataPointBool(entity, dp, args[2] === '1'); + break; + case 'value': + await sendDataPointValue(entity, dp, parseInt(args[2])); + break; + case 'enum': + await sendDataPointEnum(entity, dp, parseInt(args[2])); + break; + case 'bitmap': + for (let i = 2; i < args.length; i++) { + data.push(parseInt(args[i])); + } + await sendDataPointBitmap(entity, dp, data); + break; } }, } satisfies Tz.Converter, hy_thermostat: { key: [ - 'child_lock', 'current_heating_setpoint', 'local_temperature_calibration', - 'max_temperature_protection', 'min_temperature_protection', 'state', - 'hysteresis', 'hysteresis_for_protection', - 'max_temperature_for_protection', 'min_temperature_for_protection', - 'max_temperature', 'min_temperature', - 'sensor_type', 'power_on_behavior', 'week', 'system_mode', - 'away_preset_days', 'away_preset_temperature', + 'child_lock', + 'current_heating_setpoint', + 'local_temperature_calibration', + 'max_temperature_protection', + 'min_temperature_protection', + 'state', + 'hysteresis', + 'hysteresis_for_protection', + 'max_temperature_for_protection', + 'min_temperature_for_protection', + 'max_temperature', + 'min_temperature', + 'sensor_type', + 'power_on_behavior', + 'week', + 'system_mode', + 'away_preset_days', + 'away_preset_temperature', ], convertSet: async (entity, key, value: any, meta) => { switch (key) { - case 'max_temperature_protection': - await sendDataPointBool(entity, dataPoints.hyMaxTempProtection, value === 'ON'); - break; - case 'min_temperature_protection': - await sendDataPointBool(entity, dataPoints.hyMinTempProtection, value === 'ON'); - break; - case 'state': - await sendDataPointBool(entity, dataPoints.hyState, value === 'ON'); - break; - case 'child_lock': - await sendDataPointBool(entity, dataPoints.hyChildLock, value === 'LOCK'); - break; - case 'away_preset_days': - await sendDataPointValue(entity, dataPoints.hyAwayDays, value); - break; - case 'away_preset_temperature': - await sendDataPointValue(entity, dataPoints.hyAwayTemp, value); - break; - case 'local_temperature_calibration': - value = Math.round(value * 10); - if (value < 0) value = 0xFFFFFFFF + value + 1; - await sendDataPointValue(entity, dataPoints.hyTempCalibration, value); - break; - case 'hysteresis': - value = Math.round(value * 10); - await sendDataPointValue(entity, dataPoints.hyHysteresis, value); - break; - case 'hysteresis_for_protection': - await sendDataPointValue(entity, dataPoints.hyProtectionHysteresis, value); - break; - case 'max_temperature_for_protection': - await sendDataPointValue(entity, dataPoints.hyProtectionMaxTemp, value); - break; - case 'min_temperature_for_protection': - await sendDataPointValue(entity, dataPoints.hyProtectionMinTemp, value); - break; - case 'max_temperature': - await sendDataPointValue(entity, dataPoints.hyMaxTemp, value); - break; - case 'min_temperature': - await sendDataPointValue(entity, dataPoints.hyMinTemp, value); - break; - case 'current_heating_setpoint': - value = Math.round(value * 10); - await sendDataPointValue(entity, dataPoints.hyHeatingSetpoint, value); - break; - case 'sensor_type': - await sendDataPointEnum( - entity, - dataPoints.hySensor, - // @ts-ignore - {'internal': 0, 'external': 1, 'both': 2}[value]); - break; - case 'power_on_behavior': - await sendDataPointEnum( - entity, - dataPoints.hyPowerOnBehavior, - // @ts-ignore - {'restore': 0, 'off': 1, 'on': 2}[value]); - break; - case 'week': - await sendDataPointEnum( - entity, - dataPoints.hyWeekFormat, - utils.getKey(thermostatWeekFormat, value, value, Number)); - break; - case 'system_mode': - await sendDataPointEnum( - entity, - dataPoints.hyMode, - // @ts-ignore - {'manual': 0, 'auto': 1, 'away': 2}[value]); - break; - default: // Unknown key - throw new Error(`Unhandled key ${key}`); + case 'max_temperature_protection': + await sendDataPointBool(entity, dataPoints.hyMaxTempProtection, value === 'ON'); + break; + case 'min_temperature_protection': + await sendDataPointBool(entity, dataPoints.hyMinTempProtection, value === 'ON'); + break; + case 'state': + await sendDataPointBool(entity, dataPoints.hyState, value === 'ON'); + break; + case 'child_lock': + await sendDataPointBool(entity, dataPoints.hyChildLock, value === 'LOCK'); + break; + case 'away_preset_days': + await sendDataPointValue(entity, dataPoints.hyAwayDays, value); + break; + case 'away_preset_temperature': + await sendDataPointValue(entity, dataPoints.hyAwayTemp, value); + break; + case 'local_temperature_calibration': + value = Math.round(value * 10); + if (value < 0) value = 0xffffffff + value + 1; + await sendDataPointValue(entity, dataPoints.hyTempCalibration, value); + break; + case 'hysteresis': + value = Math.round(value * 10); + await sendDataPointValue(entity, dataPoints.hyHysteresis, value); + break; + case 'hysteresis_for_protection': + await sendDataPointValue(entity, dataPoints.hyProtectionHysteresis, value); + break; + case 'max_temperature_for_protection': + await sendDataPointValue(entity, dataPoints.hyProtectionMaxTemp, value); + break; + case 'min_temperature_for_protection': + await sendDataPointValue(entity, dataPoints.hyProtectionMinTemp, value); + break; + case 'max_temperature': + await sendDataPointValue(entity, dataPoints.hyMaxTemp, value); + break; + case 'min_temperature': + await sendDataPointValue(entity, dataPoints.hyMinTemp, value); + break; + case 'current_heating_setpoint': + value = Math.round(value * 10); + await sendDataPointValue(entity, dataPoints.hyHeatingSetpoint, value); + break; + case 'sensor_type': + await sendDataPointEnum( + entity, + dataPoints.hySensor, + // @ts-ignore + {internal: 0, external: 1, both: 2}[value], + ); + break; + case 'power_on_behavior': + await sendDataPointEnum( + entity, + dataPoints.hyPowerOnBehavior, + // @ts-ignore + {restore: 0, off: 1, on: 2}[value], + ); + break; + case 'week': + await sendDataPointEnum(entity, dataPoints.hyWeekFormat, utils.getKey(thermostatWeekFormat, value, value, Number)); + break; + case 'system_mode': + await sendDataPointEnum( + entity, + dataPoints.hyMode, + // @ts-ignore + {manual: 0, auto: 1, away: 2}[value], + ); + break; + default: // Unknown key + throw new Error(`Unhandled key ${key}`); } }, } satisfies Tz.Converter, ZB003X: { key: [ - 'reporting_time', 'temperature_calibration', 'humidity_calibration', - 'illuminance_calibration', 'pir_enable', 'led_enable', - 'reporting_enable', 'sensitivity', 'keep_time', + 'reporting_time', + 'temperature_calibration', + 'humidity_calibration', + 'illuminance_calibration', + 'pir_enable', + 'led_enable', + 'reporting_enable', + 'sensitivity', + 'keep_time', ], convertSet: async (entity, key, value: any, meta) => { switch (key) { - case 'reporting_time': - await sendDataPointValue(entity, dataPoints.fantemReportingTime, value, 'sendData'); - break; - case 'temperature_calibration': - value = Math.round(value * 10); - if (value < 0) value = 0xFFFFFFFF + value + 1; - await sendDataPointValue(entity, dataPoints.fantemTempCalibration, value, 'sendData'); - break; - case 'humidity_calibration': - if (value < 0) value = 0xFFFFFFFF + value + 1; - await sendDataPointValue(entity, dataPoints.fantemHumidityCalibration, value, 'sendData'); - break; - case 'illuminance_calibration': - if (value < 0) value = 0xFFFFFFFF + value + 1; - await sendDataPointValue(entity, dataPoints.fantemLuxCalibration, value, 'sendData'); - break; - case 'pir_enable': - await sendDataPointBool(entity, dataPoints.fantemMotionEnable, value, 'sendData'); - break; - case 'led_enable': - await sendDataPointBool(entity, dataPoints.fantemLedEnable, value === false, 'sendData'); - break; - case 'reporting_enable': - await sendDataPointBool(entity, dataPoints.fantemReportingEnable, value, 'sendData'); - break; - case 'sensitivity': - // @ts-ignore - await entity.write('ssIasZone', {currentZoneSensitivityLevel: {'low': 0, 'medium': 1, 'high': 2}[value]}); - break; - case 'keep_time': - // @ts-ignore - await entity.write('ssIasZone', {61441: {value: {'0': 0, '30': 1, '60': 2, '120': 3, - '240': 4, '480': 5}[value], type: 0x20}}); - break; - default: // Unknown key - throw new Error(`tz.ZB003X: Unhandled key ${key}`); + case 'reporting_time': + await sendDataPointValue(entity, dataPoints.fantemReportingTime, value, 'sendData'); + break; + case 'temperature_calibration': + value = Math.round(value * 10); + if (value < 0) value = 0xffffffff + value + 1; + await sendDataPointValue(entity, dataPoints.fantemTempCalibration, value, 'sendData'); + break; + case 'humidity_calibration': + if (value < 0) value = 0xffffffff + value + 1; + await sendDataPointValue(entity, dataPoints.fantemHumidityCalibration, value, 'sendData'); + break; + case 'illuminance_calibration': + if (value < 0) value = 0xffffffff + value + 1; + await sendDataPointValue(entity, dataPoints.fantemLuxCalibration, value, 'sendData'); + break; + case 'pir_enable': + await sendDataPointBool(entity, dataPoints.fantemMotionEnable, value, 'sendData'); + break; + case 'led_enable': + await sendDataPointBool(entity, dataPoints.fantemLedEnable, value === false, 'sendData'); + break; + case 'reporting_enable': + await sendDataPointBool(entity, dataPoints.fantemReportingEnable, value, 'sendData'); + break; + case 'sensitivity': + // @ts-ignore + await entity.write('ssIasZone', {currentZoneSensitivityLevel: {low: 0, medium: 1, high: 2}[value]}); + break; + case 'keep_time': + // @ts-ignore + await entity.write('ssIasZone', {61441: {value: {'0': 0, '30': 1, '60': 2, '120': 3, '240': 4, '480': 5}[value], type: 0x20}}); + break; + default: // Unknown key + throw new Error(`tz.ZB003X: Unhandled key ${key}`); } }, } satisfies Tz.Converter, @@ -7768,173 +7896,184 @@ const toZigbee2 = { key: ['switch_type', 'load_detection_mode', 'control_mode'], convertSet: async (entity, key, value: any, meta) => { switch (key) { - case 'switch_type': - // @ts-ignore - await sendDataPointEnum(entity, dataPoints.fantemExtSwitchType, {'unknown': 0, 'toggle': 1, - 'momentary': 2, 'rotary': 3, 'auto_config': 4}[value], 'sendData'); - break; - case 'load_detection_mode': - // @ts-ignore - await sendDataPointEnum(entity, dataPoints.fantemLoadDetectionMode, {'none': 0, 'first_power_on': 1, - 'every_power_on': 2}[value], 'sendData'); - break; - case 'control_mode': - // @ts-ignore - await sendDataPointEnum(entity, dataPoints.fantemControlMode, {'ext_switch': 0, 'remote': 1, - 'both': 2}[value], 'sendData'); - break; - default: // Unknown key - throw new Error(`tz.ZB006X_settings: Unhandled key ${key}`); + case 'switch_type': + await sendDataPointEnum( + entity, + dataPoints.fantemExtSwitchType, + // @ts-expect-error + {unknown: 0, toggle: 1, momentary: 2, rotary: 3, auto_config: 4}[value], + 'sendData', + ); + break; + case 'load_detection_mode': + await sendDataPointEnum( + entity, + dataPoints.fantemLoadDetectionMode, + // @ts-expect-error + {none: 0, first_power_on: 1, every_power_on: 2}[value], + 'sendData', + ); + break; + case 'control_mode': + // @ts-expect-error + await sendDataPointEnum(entity, dataPoints.fantemControlMode, {ext_switch: 0, remote: 1, both: 2}[value], 'sendData'); + break; + default: // Unknown key + throw new Error(`tz.ZB006X_settings: Unhandled key ${key}`); } }, } satisfies Tz.Converter, tuya_motion_sensor: { - key: ['o_sensitivity', 'v_sensitivity', 'led_status', 'vacancy_delay', - 'light_on_luminance_prefer', 'light_off_luminance_prefer', 'mode'], + key: ['o_sensitivity', 'v_sensitivity', 'led_status', 'vacancy_delay', 'light_on_luminance_prefer', 'light_off_luminance_prefer', 'mode'], convertSet: async (entity, key, value: any, meta) => { switch (key) { - case 'o_sensitivity': - await sendDataPointEnum(entity, dataPoints.msOSensitivity, utils.getKey(msLookups.OSensitivity, value)); - break; - case 'v_sensitivity': - await sendDataPointEnum(entity, dataPoints.msVSensitivity, utils.getKey(msLookups.VSensitivity, value)); - break; - case 'led_status': - // @ts-ignore - await sendDataPointEnum(entity, dataPoints.msLedStatus, {'on': 0, 'off': 1}[value.toLowerCase()]); - break; - case 'vacancy_delay': - await sendDataPointValue(entity, dataPoints.msVacancyDelay, value); - break; - case 'light_on_luminance_prefer': - await sendDataPointValue(entity, dataPoints.msLightOnLuminancePrefer, value); - break; - case 'light_off_luminance_prefer': - await sendDataPointValue(entity, dataPoints.msLightOffLuminancePrefer, value); - break; - case 'mode': - await sendDataPointEnum(entity, dataPoints.msMode, utils.getKey(msLookups.Mode, value)); - break; - default: // Unknown key - logger.warning(`toZigbee.tuya_motion_sensor: Unhandled key ${key}`, 'zhc:legacy:tz:tuya_motion_sensor'); + case 'o_sensitivity': + await sendDataPointEnum(entity, dataPoints.msOSensitivity, utils.getKey(msLookups.OSensitivity, value)); + break; + case 'v_sensitivity': + await sendDataPointEnum(entity, dataPoints.msVSensitivity, utils.getKey(msLookups.VSensitivity, value)); + break; + case 'led_status': + // @ts-ignore + await sendDataPointEnum(entity, dataPoints.msLedStatus, {on: 0, off: 1}[value.toLowerCase()]); + break; + case 'vacancy_delay': + await sendDataPointValue(entity, dataPoints.msVacancyDelay, value); + break; + case 'light_on_luminance_prefer': + await sendDataPointValue(entity, dataPoints.msLightOnLuminancePrefer, value); + break; + case 'light_off_luminance_prefer': + await sendDataPointValue(entity, dataPoints.msLightOffLuminancePrefer, value); + break; + case 'mode': + await sendDataPointEnum(entity, dataPoints.msMode, utils.getKey(msLookups.Mode, value)); + break; + default: // Unknown key + logger.warning(`toZigbee.tuya_motion_sensor: Unhandled key ${key}`, 'zhc:legacy:tz:tuya_motion_sensor'); } }, } satisfies Tz.Converter, javis_microwave_sensor: { - key: [ - 'illuminance_calibration', 'led_enable', - 'sensitivity', 'keep_time', - ], + key: ['illuminance_calibration', 'led_enable', 'sensitivity', 'keep_time'], convertSet: async (entity, key, value, meta) => { switch (key) { - case 'illuminance_calibration':// (10--100) sensor illuminance sensitivity - if (meta.device.manufacturerName === '_TZE200_kagkgk0i') { - await sendDataPointRaw(entity, 102, [value]); - break; - } else { - await sendDataPointRaw(entity, 105, [value]); - break; - } - case 'led_enable':// OK (value true/false or 1/0) - if (meta.device.manufacturerName === '_TZE200_kagkgk0i') { - await sendDataPointRaw(entity, 107, [value ? 1 : 0]); - break; - } else { - await sendDataPointRaw(entity, 103, [value ? 1 : 0]); - break; - } + case 'illuminance_calibration': // (10--100) sensor illuminance sensitivity + if (meta.device.manufacturerName === '_TZE200_kagkgk0i') { + await sendDataPointRaw(entity, 102, [value]); + break; + } else { + await sendDataPointRaw(entity, 105, [value]); + break; + } + case 'led_enable': // OK (value true/false or 1/0) + if (meta.device.manufacturerName === '_TZE200_kagkgk0i') { + await sendDataPointRaw(entity, 107, [value ? 1 : 0]); + break; + } else { + await sendDataPointRaw(entity, 103, [value ? 1 : 0]); + break; + } - case 'sensitivity':// value: 25, 50, 75, 100 - await sendDataPointRaw(entity, 2, [value]); - break; - case 'keep_time': // value 0 --> 7 corresponding 5s, 30s, 1, 3, 5, 10, 20, 30 min - if (meta.device.manufacturerName === '_TZE200_kagkgk0i') { - await sendDataPointRaw(entity, 106, [value]); + case 'sensitivity': // value: 25, 50, 75, 100 + await sendDataPointRaw(entity, 2, [value]); break; - } else { - await sendDataPointRaw(entity, 102, [value]); - break; - } - default: // Unknown key - throw new Error(`Unhandled key ${key}`); + case 'keep_time': // value 0 --> 7 corresponding 5s, 30s, 1, 3, 5, 10, 20, 30 min + if (meta.device.manufacturerName === '_TZE200_kagkgk0i') { + await sendDataPointRaw(entity, 106, [value]); + break; + } else { + await sendDataPointRaw(entity, 102, [value]); + break; + } + default: // Unknown key + throw new Error(`Unhandled key ${key}`); } }, } satisfies Tz.Converter, moes_thermostat_tv: { key: [ - 'system_mode', 'window_detection', 'frost_detection', 'child_lock', - 'current_heating_setpoint', 'local_temperature_calibration', - 'holiday_temperature', 'comfort_temperature', 'eco_temperature', - 'open_window_temperature', 'heating_stop', 'preset', + 'system_mode', + 'window_detection', + 'frost_detection', + 'child_lock', + 'current_heating_setpoint', + 'local_temperature_calibration', + 'holiday_temperature', + 'comfort_temperature', + 'eco_temperature', + 'open_window_temperature', + 'heating_stop', + 'preset', ], convertSet: async (entity, key, value: any, meta) => { switch (key) { - case 'system_mode': - if (value != 'off') { - await sendDataPointBool(entity, dataPoints.tvHeatingStop, 0); - await sendDataPointEnum(entity, dataPoints.tvMode, utils.getKey(tvThermostatMode, value)); - } else { - await sendDataPointBool(entity, dataPoints.tvHeatingStop, 1); - } - break; - case 'window_detection': - await sendDataPointBool(entity, dataPoints.tvWindowDetection, value); - break; - case 'frost_detection': - if (value == false) { - await sendDataPointBool(entity, dataPoints.tvFrostDetection, 0); - await sendDataPointEnum(entity, dataPoints.tvMode, 1); - } else { - await sendDataPointBool(entity, dataPoints.tvFrostDetection, 1); - } - break; - case 'child_lock': - await sendDataPointBool(entity, dataPoints.tvChildLock, value === 'LOCK'); - break; - case 'local_temperature_calibration': - value = Math.round(value * 10); - value = (value < 0) ? 0xFFFFFFFF + value + 1 : value; - await sendDataPointValue(entity, dataPoints.tvTempCalibration, value); - break; - case 'current_heating_setpoint': - value = Math.round(value * 10); - await sendDataPointValue(entity, dataPoints.tvHeatingSetpoint, value); - break; - case 'holiday_temperature': - value = Math.round(value * 10); - await sendDataPointValue(entity, dataPoints.tvHolidayTemp, value); - break; - case 'comfort_temperature': - value = Math.round(value * 10); - await sendDataPointValue(entity, dataPoints.tvComfortTemp, value); - break; - case 'eco_temperature': - value = Math.round(value * 10); - await sendDataPointValue(entity, dataPoints.tvEcoTemp, value); - break; - case 'heating_stop': - if (value == true) { - await sendDataPointBool(entity, dataPoints.tvHeatingStop, 1); - } else { + case 'system_mode': + if (value != 'off') { + await sendDataPointBool(entity, dataPoints.tvHeatingStop, 0); + await sendDataPointEnum(entity, dataPoints.tvMode, utils.getKey(tvThermostatMode, value)); + } else { + await sendDataPointBool(entity, dataPoints.tvHeatingStop, 1); + } + break; + case 'window_detection': + await sendDataPointBool(entity, dataPoints.tvWindowDetection, value); + break; + case 'frost_detection': + if (value == false) { + await sendDataPointBool(entity, dataPoints.tvFrostDetection, 0); + await sendDataPointEnum(entity, dataPoints.tvMode, 1); + } else { + await sendDataPointBool(entity, dataPoints.tvFrostDetection, 1); + } + break; + case 'child_lock': + await sendDataPointBool(entity, dataPoints.tvChildLock, value === 'LOCK'); + break; + case 'local_temperature_calibration': + value = Math.round(value * 10); + value = value < 0 ? 0xffffffff + value + 1 : value; + await sendDataPointValue(entity, dataPoints.tvTempCalibration, value); + break; + case 'current_heating_setpoint': + value = Math.round(value * 10); + await sendDataPointValue(entity, dataPoints.tvHeatingSetpoint, value); + break; + case 'holiday_temperature': + value = Math.round(value * 10); + await sendDataPointValue(entity, dataPoints.tvHolidayTemp, value); + break; + case 'comfort_temperature': + value = Math.round(value * 10); + await sendDataPointValue(entity, dataPoints.tvComfortTemp, value); + break; + case 'eco_temperature': + value = Math.round(value * 10); + await sendDataPointValue(entity, dataPoints.tvEcoTemp, value); + break; + case 'heating_stop': + if (value == true) { + await sendDataPointBool(entity, dataPoints.tvHeatingStop, 1); + } else { + await sendDataPointBool(entity, dataPoints.tvHeatingStop, 0); + await sendDataPointEnum(entity, dataPoints.tvMode, 1); + } + break; + // case 'boost_mode': + // // set 300sec boost time + // await sendDataPointValue(entity, dataPoints.tvBoostTime, 300); + // await sendDataPointEnum(entity, dataPoints.tvBoostMode, (value) ? 0 : 1); + // break; + case 'open_window_temperature': + value = Math.round(value * 10); + await sendDataPointValue(entity, dataPoints.tvOpenWindowTemp, value); + break; + case 'preset': await sendDataPointBool(entity, dataPoints.tvHeatingStop, 0); - await sendDataPointEnum(entity, dataPoints.tvMode, 1); - } - break; - // case 'boost_mode': - // // set 300sec boost time - // await sendDataPointValue(entity, dataPoints.tvBoostTime, 300); - // await sendDataPointEnum(entity, dataPoints.tvBoostMode, (value) ? 0 : 1); - // break; - case 'open_window_temperature': - value = Math.round(value * 10); - await sendDataPointValue(entity, dataPoints.tvOpenWindowTemp, value); - break; - case 'preset': - await sendDataPointBool(entity, dataPoints.tvHeatingStop, 0); - await sendDataPointEnum(entity, dataPoints.tvMode, utils.getKey(tvThermostatPreset, value)); - break; - default: // Unknown key - logger.warning(`Unhandled key ${key}`, 'zhc:legacy:tz:moes_thermostat_tv'); + await sendDataPointEnum(entity, dataPoints.tvMode, utils.getKey(tvThermostatPreset, value)); + break; + default: // Unknown key + logger.warning(`Unhandled key ${key}`, 'zhc:legacy:tz:moes_thermostat_tv'); } }, } satisfies Tz.Converter, @@ -7942,8 +8081,8 @@ const toZigbee2 = { key: ['color', 'color_temp', 'brightness', 'white_brightness'], convertSet: async (entity, key, value: any, meta) => { if (Array.isArray(meta.mapped)) throw new Error(`Not supported for groups`); - const separateWhite = (meta.mapped.meta && meta.mapped.meta.separateWhite); - if (key == 'white_brightness' || (!separateWhite && (key == 'brightness'))) { + const separateWhite = meta.mapped.meta && meta.mapped.meta.separateWhite; + if (key == 'white_brightness' || (!separateWhite && key == 'brightness')) { // upscale to 1000 let newValue; if (value >= 0 && value <= 255) { @@ -7951,20 +8090,24 @@ const toZigbee2 = { } else { throw new Error('Dimmer brightness is out of range 0..255'); } - await sendDataPoints(entity, [ - dpValueFromEnum(dataPoints.silvercrestChangeMode, silvercrestModes.white), - dpValueFromIntValue(dataPoints.dimmerLevel, newValue), - ], 'dataRequest'); + await sendDataPoints( + entity, + [ + dpValueFromEnum(dataPoints.silvercrestChangeMode, silvercrestModes.white), + dpValueFromIntValue(dataPoints.dimmerLevel, newValue), + ], + 'dataRequest', + ); - return {state: (key == 'white_brightness') ? {white_brightness: value} : {brightness: value}}; + return {state: key == 'white_brightness' ? {white_brightness: value} : {brightness: value}}; } else if (key == 'color_temp') { const [colorTempMin, colorTempMax] = [250, 454]; const preset: KeyValueAny = { - 'warmest': colorTempMax, - 'warm': 454, - 'neutral': 370, - 'cool': 250, - 'coolest': colorTempMin, + warmest: colorTempMax, + warm: 454, + neutral: 370, + cool: 250, + coolest: colorTempMin, }; // @ts-ignore if (typeof value === 'string' && isNaN(value)) { @@ -7979,13 +8122,17 @@ const toZigbee2 = { } const data = utils.mapNumberRange(value, colorTempMax, colorTempMin, 0, 1000); - await sendDataPoints(entity, [ - dpValueFromEnum(dataPoints.silvercrestChangeMode, silvercrestModes.white), - dpValueFromIntValue(dataPoints.silvercrestSetColorTemp, data), - ], 'dataRequest'); + await sendDataPoints( + entity, + [ + dpValueFromEnum(dataPoints.silvercrestChangeMode, silvercrestModes.white), + dpValueFromIntValue(dataPoints.silvercrestSetColorTemp, data), + ], + 'dataRequest', + ); return {state: {color_temp: value}}; - } else if (key == 'color' || (separateWhite && (key == 'brightness'))) { + } else if (key == 'color' || (separateWhite && key == 'brightness')) { const newState: KeyValueAny = {}; if (key == 'brightness') { newState.brightness = value; @@ -8046,11 +8193,10 @@ const toZigbee2 = { const hsb = fillInHSB( value.h || value.hue || null, value.s || value.saturation || null, - value.b || value.brightness || (key == 'brightness') ? value : null, + value.b || value.brightness || key == 'brightness' ? value : null, meta.state, ); - let data: any = []; data = data.concat(convertStringToHexArray(hsb.h)); data = data.concat(convertStringToHexArray(hsb.s)); @@ -8073,8 +8219,7 @@ const toZigbee2 = { convertSet: async (entity, key, value: any, meta) => { if (key === 'position') { if (value >= 0 && value <= 100) { - const invert = isCoverInverted(meta.device.manufacturerName) ? - !meta.options.invert_cover : meta.options.invert_cover; + const invert = isCoverInverted(meta.device.manufacturerName) ? !meta.options.invert_cover : meta.options.invert_cover; value = invert ? 100 - value : value; await sendDataPointValue(entity, dataPoints.coverPosition, value); @@ -8086,60 +8231,56 @@ const toZigbee2 = { logger.debug(`Using state enums for ${meta.device.manufacturerName}: ${JSON.stringify(stateEnums)}`, 'zhc:legacy:tz:zmam02'); value = value.toLowerCase(); switch (value) { - case 'close': - await sendDataPointEnum(entity, dataPoints.AM02Control, stateEnums.close); - break; - case 'open': - await sendDataPointEnum(entity, dataPoints.AM02Control, stateEnums.open); - break; - case 'stop': - await sendDataPointEnum(entity, dataPoints.AM02Control, stateEnums.stop); - break; - default: - throw new Error('ZMAM02: Invalid command received'); + case 'close': + await sendDataPointEnum(entity, dataPoints.AM02Control, stateEnums.close); + break; + case 'open': + await sendDataPointEnum(entity, dataPoints.AM02Control, stateEnums.open); + break; + case 'stop': + await sendDataPointEnum(entity, dataPoints.AM02Control, stateEnums.stop); + break; + default: + throw new Error('ZMAM02: Invalid command received'); } } switch (key) { - case 'mode': - await sendDataPointEnum(entity, dataPoints.AM02Mode, utils.getKey(ZMLookups.AM02Mode, value)); - break; - case 'motor_direction': - await sendDataPointEnum(entity, dataPoints.AM02Direction, utils.getKey(ZMLookups.AM02Direction, value)); - break; - case 'border': - await sendDataPointEnum(entity, dataPoints.AM02Border, utils.getKey(ZMLookups.AM02Border, value)); - break; - case 'motor_working_mode': - await sendDataPointEnum( - entity, - dataPoints.AM02MotorWorkingMode, - utils.getKey(ZMLookups.AM02MotorWorkingMode, - value)); - break; + case 'mode': + await sendDataPointEnum(entity, dataPoints.AM02Mode, utils.getKey(ZMLookups.AM02Mode, value)); + break; + case 'motor_direction': + await sendDataPointEnum(entity, dataPoints.AM02Direction, utils.getKey(ZMLookups.AM02Direction, value)); + break; + case 'border': + await sendDataPointEnum(entity, dataPoints.AM02Border, utils.getKey(ZMLookups.AM02Border, value)); + break; + case 'motor_working_mode': + await sendDataPointEnum(entity, dataPoints.AM02MotorWorkingMode, utils.getKey(ZMLookups.AM02MotorWorkingMode, value)); + break; } }, } satisfies Tz.Converter, tuya_smart_human_presense_sensor: { key: ['radar_sensitivity', 'minimum_range', 'maximum_range', 'detection_delay', 'fading_time'], - convertSet: async (entity, key, value:any, meta) => { + convertSet: async (entity, key, value: any, meta) => { switch (key) { - case 'radar_sensitivity': - await sendDataPointValue(entity, dataPoints.tshpscSensitivity, value); - break; - case 'minimum_range': - await sendDataPointValue(entity, dataPoints.tshpsMinimumRange, value*100); - break; - case 'maximum_range': - await sendDataPointValue(entity, dataPoints.tshpsMaximumRange, value*100); - break; - case 'detection_delay': - await sendDataPointValue(entity, dataPoints.tshpsDetectionDelay, value*10); - break; - case 'fading_time': - await sendDataPointValue(entity, dataPoints.tshpsFadingTime, value*10); - break; - default: // Unknown Key - logger.warning(`Unhandled Key ${key}`, 'zhc:legacy:tz:tuya_smart_human_presense_sensor'); + case 'radar_sensitivity': + await sendDataPointValue(entity, dataPoints.tshpscSensitivity, value); + break; + case 'minimum_range': + await sendDataPointValue(entity, dataPoints.tshpsMinimumRange, value * 100); + break; + case 'maximum_range': + await sendDataPointValue(entity, dataPoints.tshpsMaximumRange, value * 100); + break; + case 'detection_delay': + await sendDataPointValue(entity, dataPoints.tshpsDetectionDelay, value * 10); + break; + case 'fading_time': + await sendDataPointValue(entity, dataPoints.tshpsFadingTime, value * 10); + break; + default: // Unknown Key + logger.warning(`Unhandled Key ${key}`, 'zhc:legacy:tz:tuya_smart_human_presense_sensor'); } }, } satisfies Tz.Converter, @@ -8147,28 +8288,28 @@ const toZigbee2 = { key: ['sensitivity', 'keep_time'], convertSet: async (entity, key, value, meta) => { switch (key) { - case 'sensitivity': - // @ts-ignore - await sendDataPointEnum(entity, dataPoints.lmsSensitivity, {'low': 0, 'medium': 1, 'high': 2}[value]); - break; - case 'keep_time': - // @ts-ignore - await sendDataPointEnum(entity, dataPoints.lmsKeepTime, {'10': 0, '30': 1, '60': 2, '120': 3}[value]); - break; - default: // Unknown key - logger.warning(`Unhandled SET key ${key}`, 'zhc:legacy:tz:zg204zl_lms'); + case 'sensitivity': + // @ts-ignore + await sendDataPointEnum(entity, dataPoints.lmsSensitivity, {low: 0, medium: 1, high: 2}[value]); + break; + case 'keep_time': + // @ts-ignore + await sendDataPointEnum(entity, dataPoints.lmsKeepTime, {'10': 0, '30': 1, '60': 2, '120': 3}[value]); + break; + default: // Unknown key + logger.warning(`Unhandled SET key ${key}`, 'zhc:legacy:tz:zg204zl_lms'); } }, convertGet: async (entity, key, meta) => { switch (key) { - case 'sensitivity': - await sendDataPointEnum(entity, dataPoints.lmsSensitivity, 0, 'dataQuery' ); - break; - case 'keep_time': - await sendDataPointEnum(entity, dataPoints.lmsKeepTime, 0, 'dataQuery' ); - break; - default: // Unknown key - logger.warning(`Unhandled GET key ${key}`, 'zhc:legacy:tz:zg204zl_lms'); + case 'sensitivity': + await sendDataPointEnum(entity, dataPoints.lmsSensitivity, 0, 'dataQuery'); + break; + case 'keep_time': + await sendDataPointEnum(entity, dataPoints.lmsKeepTime, 0, 'dataQuery'); + break; + default: // Unknown key + logger.warning(`Unhandled GET key ${key}`, 'zhc:legacy:tz:zg204zl_lms'); } }, } satisfies Tz.Converter, @@ -8177,41 +8318,41 @@ const toZigbee2 = { options: [exposes.options.invert_cover()], convertSet: async (entity, key, value: any, meta) => { switch (key) { - case 'position': - if (value >= 0 && value <= 100) { - const invert = !isCoverInverted(meta.device.manufacturerName) ? - !meta.options.invert_cover : meta.options.invert_cover; - const position = invert ? 100 - value : value; - await sendDataPointValue(entity, dataPoints.coverPosition, position); - return {state: {position: value}}; + case 'position': + if (value >= 0 && value <= 100) { + const invert = !isCoverInverted(meta.device.manufacturerName) ? !meta.options.invert_cover : meta.options.invert_cover; + const position = invert ? 100 - value : value; + await sendDataPointValue(entity, dataPoints.coverPosition, position); + return {state: {position: value}}; + } + break; + case 'state': { + // @ts-ignore + const state = {OPEN: 0, STOP: 1, CLOSE: 2}[value.toUpperCase()]; + await sendDataPointEnum(entity, dataPoints.state, state); + break; + } + case 'backlight': { + const backlight = value.toUpperCase() === 'ON' ? true : false; + await sendDataPointBool(entity, dataPoints.moesCoverBacklight, backlight); + return {state: {backlight: value}}; + } + case 'calibration': { + const calibration = value.toUpperCase() === 'ON' ? 0 : 1; + await sendDataPointEnum(entity, dataPoints.moesCoverCalibration, calibration); + break; + } + case 'motor_reversal': { + const motorReversal = value.toUpperCase() === 'ON' ? 1 : 0; + await sendDataPointEnum(entity, dataPoints.moesCoverMotorReversal, motorReversal); + return {state: {motor_reversal: value}}; } - break; - case 'state': { - // @ts-ignore - const state = {'OPEN': 0, 'STOP': 1, 'CLOSE': 2}[value.toUpperCase()]; - await sendDataPointEnum(entity, dataPoints.state, state); - break; - } - case 'backlight': { - const backlight = value.toUpperCase() === 'ON' ? true : false; - await sendDataPointBool(entity, dataPoints.moesCoverBacklight, backlight); - return {state: {backlight: value}}; - } - case 'calibration': { - const calibration = value.toUpperCase() === 'ON' ? 0 : 1; - await sendDataPointEnum(entity, dataPoints.moesCoverCalibration, calibration); - break; - } - case 'motor_reversal': { - const motorReversal = value.toUpperCase() === 'ON' ? 1 : 0; - await sendDataPointEnum(entity, dataPoints.moesCoverMotorReversal, motorReversal); - return {state: {motor_reversal: value}}; - } } }, } satisfies Tz.Converter, hoch_din: { - key: ['state', + key: [ + 'state', 'child_lock', 'countdown_timer', 'power_on_behavior', @@ -8234,7 +8375,7 @@ const toZigbee2 = { await sendDataPointValue(entity, dataPoints.hochCountdownTimer, value); return {state: {countdown_timer: value}}; } else if (key === 'power_on_behavior') { - const lookup: KeyValueAny = {'off': 0, 'on': 1, 'previous': 2}; + const lookup: KeyValueAny = {off: 0, on: 1, previous: 2}; await sendDataPointEnum(entity, dataPoints.hochRelayStatus, lookup[value], 'sendData'); return {state: {power_on_behavior: value}}; } else if (key === 'trip') { @@ -8244,7 +8385,7 @@ const toZigbee2 = { return {state: {trip: 'clear'}}; } else if (key === 'clear_device_data') { await sendDataPointBool(entity, dataPoints.hochClearEnergy, true, 'sendData'); - /* TODO: Release the below with other toZigbee converters for device composites + /* TODO: Release the below with other toZigbee converters for device composites } else if (key === 'temperature_setting') { if (value.over_temperature_threshold && value.over_temperature_trip && value.over_temperature_alarm){ const payload = []; @@ -8289,7 +8430,6 @@ const thermostatSystemModes = { const fromZigbee = {...fromZigbee1, ...fromZigbee2}; const toZigbee = {...toZigbee1, ...toZigbee2}; - export { fromZigbee as fz, fromZigbee, diff --git a/src/lib/legrand.ts b/src/lib/legrand.ts index 5ed5e1d7dd562..605ea0e9bd9d0 100644 --- a/src/lib/legrand.ts +++ b/src/lib/legrand.ts @@ -1,14 +1,15 @@ import {Zcl} from 'zigbee-herdsman'; + import {Fz, Tz, Zh, OnEvent, KeyValueString, KeyValueAny} from '../lib/types'; -import * as exposes from './exposes'; import * as utils from '../lib/utils'; +import * as exposes from './exposes'; import {logger} from './logger'; const NS = 'zhc:legrand'; const e = exposes.presets; const ea = exposes.access; -const shutterCalibrationModes: {[k: number]: {description: string, onlyNLLV: boolean, supportsTilt: boolean}} = { +const shutterCalibrationModes: {[k: number]: {description: string; onlyNLLV: boolean; supportsTilt: boolean}} = { 0: {description: 'classic_nllv', onlyNLLV: true, supportsTilt: false}, 1: {description: 'specific_nllv', onlyNLLV: true, supportsTilt: false}, 2: {description: 'up_down_stop', onlyNLLV: false, supportsTilt: false}, @@ -16,19 +17,19 @@ const shutterCalibrationModes: {[k: number]: {description: string, onlyNLLV: boo 4: {description: 'venetian_bso', onlyNLLV: false, supportsTilt: true}, }; -const ledModes:{[k: number]: string} = { +const ledModes: {[k: number]: string} = { 1: 'led_in_dark', 2: 'led_if_on', }; -const ledEffects:{[k: number]: string} = { +const ledEffects: {[k: number]: string} = { 0: 'blink 3', 1: 'fixed', 2: 'blink green', 3: 'blink blue', }; -const ledColors:{[k: number]: string} = { +const ledColors: {[k: number]: string} = { 0: 'default', 1: 'red', 2: 'green', @@ -41,7 +42,8 @@ const ledColors:{[k: number]: string} = { const optsLegrand = { identityEffect: () => { - return e.composite('Identity effect', 'identity_effect', ea.SET) + return e + .composite('Identity effect', 'identity_effect', ea.SET) .withDescription('Defines the identification effect to simplify the device identification.') .withFeature(e.enum('effect', ea.SET, Object.values(ledEffects)).withLabel('Effect')) .withFeature(e.enum('color', ea.SET, Object.values(ledColors)).withLabel('Color')); @@ -49,9 +51,11 @@ const optsLegrand = { }; const getApplicableCalibrationModes = (isNLLVSwitch: boolean): KeyValueString => { - return Object.fromEntries(Object.entries(shutterCalibrationModes) - .filter((e) => isNLLVSwitch ? true : e[1].onlyNLLV === false) - .map((e) => [e[0], e[1].description])); + return Object.fromEntries( + Object.entries(shutterCalibrationModes) + .filter((e) => (isNLLVSwitch ? true : e[1].onlyNLLV === false)) + .map((e) => [e[0], e[1].description]), + ); }; export const legrandOptions = {manufacturerCode: Zcl.ManufacturerCode.LEGRAND_GROUP, disableDefaultResponse: true}; @@ -61,25 +65,29 @@ export const _067776 = { const c = e.cover_position(); const calMode = Number(device?.getEndpoint(1)?.clusters?.closuresWindowCovering?.attributes?.calibrationMode); - const showTilt = calMode ? (shutterCalibrationModes[calMode]?.supportsTilt === true): false; + const showTilt = calMode ? shutterCalibrationModes[calMode]?.supportsTilt === true : false; if (showTilt) { - c.addFeature(new exposes.Numeric('tilt', ea.ALL) - .withValueMin(0).withValueMax(100) - .withValueStep(25) - .withPreset('Closed', 0, 'Vertical') - .withPreset('25 %', 25, '25%') - .withPreset('50 %', 50, '50%') - .withPreset('75 %', 75, '75%') - .withPreset('Open', 100, 'Horizontal') - .withUnit('%') - .withDescription('Tilt percentage of that cover')); + c.addFeature( + new exposes.Numeric('tilt', ea.ALL) + .withValueMin(0) + .withValueMax(100) + .withValueStep(25) + .withPreset('Closed', 0, 'Vertical') + .withPreset('25 %', 25, '25%') + .withPreset('50 %', 50, '50%') + .withPreset('75 %', 75, '75%') + .withPreset('Open', 100, 'Horizontal') + .withUnit('%') + .withDescription('Tilt percentage of that cover'), + ); } return c; }, getCalibrationModes: (isNLLVSwitch: boolean) => { const modes = getApplicableCalibrationModes(isNLLVSwitch); - return e.enum('calibration_mode', ea.ALL, Object.values(modes)) + return e + .enum('calibration_mode', ea.ALL, Object.values(modes)) .withDescription('Defines the calibration mode of the switch. (Caution: Changing modes requires a recalibration of the shutter switch!)') .withCategory('config'); }, @@ -87,19 +95,19 @@ export const _067776 = { export const eLegrand = { identify: () => { - return e.enum('identify', ea.SET, ['identify']) + return e + .enum('identify', ea.SET, ['identify']) .withDescription('Blinks the built-in LED to make it easier to identify the device') .withCategory('config'); }, ledInDark: () => { - return e.binary('led_in_dark', ea.ALL, 'ON', 'OFF') + return e + .binary('led_in_dark', ea.ALL, 'ON', 'OFF') .withDescription('Enables the built-in LED allowing to see the switch in the dark') .withCategory('config'); }, ledIfOn: () => { - return e.binary('led_if_on', ea.ALL, 'ON', 'OFF') - .withDescription('Enables the LED on activity') - .withCategory('config'); + return e.binary('led_if_on', ea.ALL, 'ON', 'OFF').withDescription('Enables the LED on activity').withCategory('config'); }, }; @@ -114,10 +122,10 @@ export const tzLegrand = { auto_mode: { key: ['auto_mode'], convertSet: async (entity, key, value, meta) => { - const mode = utils.getFromLookup(value, {'off': 0x00, 'auto': 0x02, 'on_override': 0x03}); + const mode = utils.getFromLookup(value, {off: 0x00, auto: 0x02, on_override: 0x03}); const payload = {data: Buffer.from([mode])}; await entity.command('manuSpecificLegrandDevices3', 'command0', payload); - return {state: {'auto_mode': value}}; + return {state: {auto_mode: value}}; }, } satisfies Tz.Converter, calibration_mode: (isNLLVSwitch: boolean) => { @@ -127,7 +135,7 @@ export const tzLegrand = { const applicableModes = getApplicableCalibrationModes(isNLLVSwitch); utils.validateValue(value, Object.values(applicableModes)); const idx = utils.getKey(applicableModes, value); - await entity.write('closuresWindowCovering', {'calibrationMode': idx}, legrandOptions); + await entity.write('closuresWindowCovering', {calibrationMode: idx}, legrandOptions); }, convertGet: async (entity, key, meta) => { await entity.read('closuresWindowCovering', ['calibrationMode'], legrandOptions); @@ -154,7 +162,7 @@ export const tzLegrand = { key: ['identify'], options: [optsLegrand.identityEffect()], convertSet: async (entity, key, value, meta) => { - const identityEffect = (meta.options.identity_effect as KeyValueAny); + const identityEffect = meta.options.identity_effect as KeyValueAny; const selEffect = identityEffect?.effect ?? ledEffects[0]; const selColor = identityEffect?.color ?? ledColors[0]; diff --git a/src/lib/light.ts b/src/lib/light.ts index 82e9c0b4c8e41..6b30e48c59190 100644 --- a/src/lib/light.ts +++ b/src/lib/light.ts @@ -12,15 +12,15 @@ async function readColorTempMinMax(endpoint: Zh.Endpoint) { await endpoint.read('lightingColorCtrl', ['colorTempPhysicalMin', 'colorTempPhysicalMax']); } -export function readColorAttributes(entity: Zh.Endpoint | Zh.Group, meta: Tz.Meta, additionalAttributes: string[]=[]) { +export function readColorAttributes(entity: Zh.Endpoint | Zh.Group, meta: Tz.Meta, additionalAttributes: string[] = []) { /** - * Not all bulbs support the same features, we need to take care we read what is supported. - * `supportsHueAndSaturation` indicates support for currentHue and currentSaturation - * `supportsEnhancedHue` indicates support for enhancedCurrentHue - * - * e.g. IKEA Tådfri LED1624G9 only supports XY (https://github.com/Koenkk/zigbee-herdsman-converters/issues/1340) - * - * Additionally when we get a "get payload", only request the fields included. + * Not all bulbs support the same features, we need to take care we read what is supported. + * `supportsHueAndSaturation` indicates support for currentHue and currentSaturation + * `supportsEnhancedHue` indicates support for enhancedCurrentHue + * + * e.g. IKEA Tådfri LED1624G9 only supports XY (https://github.com/Koenkk/zigbee-herdsman-converters/issues/1340) + * + * Additionally when we get a "get payload", only request the fields included. */ const attributes = ['colorMode']; if (meta && meta.message) { @@ -52,13 +52,17 @@ export function findColorTempRange(entity: Zh.Endpoint | Zh.Group) { let colorTempMin; let colorTempMax; if (utils.isGroup(entity)) { - const minCandidates = entity.members.map((m) => m.getClusterAttributeValue('lightingColorCtrl', 'colorTempPhysicalMin')) - .filter((v) => v != null).map((v) => Number(v)); + const minCandidates = entity.members + .map((m) => m.getClusterAttributeValue('lightingColorCtrl', 'colorTempPhysicalMin')) + .filter((v) => v != null) + .map((v) => Number(v)); if (minCandidates.length > 0) { colorTempMin = Math.max(...minCandidates); } - const maxCandidates = entity.members.map((m) => m.getClusterAttributeValue('lightingColorCtrl', 'colorTempPhysicalMax')) - .filter((v) => v != null).map((v) => Number(v)); + const maxCandidates = entity.members + .map((m) => m.getClusterAttributeValue('lightingColorCtrl', 'colorTempPhysicalMax')) + .filter((v) => v != null) + .map((v) => Number(v)); if (maxCandidates.length > 0) { colorTempMax = Math.min(...maxCandidates); } @@ -66,7 +70,7 @@ export function findColorTempRange(entity: Zh.Endpoint | Zh.Group) { colorTempMin = entity.getClusterAttributeValue('lightingColorCtrl', 'colorTempPhysicalMin') as number; colorTempMax = entity.getClusterAttributeValue('lightingColorCtrl', 'colorTempPhysicalMax') as number; } - if ((colorTempMin == null) || (colorTempMax == null)) { + if (colorTempMin == null || colorTempMax == null) { const entityId = utils.isGroup(entity) ? entity.groupID : entity.deviceIeeeAddress; logger.debug(`Missing colorTempPhysicalMin and/or colorTempPhysicalMax for ${utils.isGroup(entity) ? 'group' : 'endpoint'} ${entityId}!`, NS); } @@ -74,11 +78,11 @@ export function findColorTempRange(entity: Zh.Endpoint | Zh.Group) { } export function clampColorTemp(colorTemp: number, colorTempMin: number, colorTempMax: number) { - if ((colorTempMin != null) && (colorTemp < colorTempMin)) { + if (colorTempMin != null && colorTemp < colorTempMin) { logger.debug(`Requested color_temp ${colorTemp} is lower than minimum supported ${colorTempMin}, using minimum!`, NS); return colorTempMin; } - if ((colorTempMax != null) && (colorTemp > colorTempMax)) { + if (colorTempMax != null && colorTemp > colorTempMax) { logger.debug(`Requested color_temp ${colorTemp} is higher than maximum supported ${colorTempMax}, using maximum!`, NS); return colorTempMax; } @@ -97,7 +101,9 @@ export async function configure(device: Zh.Device, coordinatorEndpoint: Zh.Endpo if (readColorTempMinMaxAttribute) { await readColorTempMinMax(endpoint); } - } catch (e) {/* Fails for some, e.g. https://github.com/Koenkk/zigbee2mqtt/issues/5717 */} + } catch (e) { + /* Fails for some, e.g. https://github.com/Koenkk/zigbee2mqtt/issues/5717 */ + } } } diff --git a/src/lib/lumi.ts b/src/lib/lumi.ts index d498bf8febe5b..bb9993c8c3d56 100644 --- a/src/lib/lumi.ts +++ b/src/lib/lumi.ts @@ -1,4 +1,12 @@ import {Buffer} from 'node:buffer'; + +import fz from '../converters/fromZigbee'; +import * as exposes from './exposes'; +import {logger} from './logger'; +import * as modernExtend from './modernExtend'; +import * as ota from './ota'; +import * as globalStore from './store'; +import {Fz, Definition, KeyValue, KeyValueAny, Tz, ModernExtend, Range, KeyValueNumberString, OnEvent, Expose, Configure} from './types'; import { batteryVoltageToPercentage, postfixWithEndpointName, @@ -20,17 +28,6 @@ import { calibrateAndPrecisionRoundOptions, } from './utils'; -import * as ota from './ota'; -import fz from '../converters/fromZigbee'; -import * as globalStore from './store'; -import { - Fz, Definition, KeyValue, KeyValueAny, Tz, ModernExtend, Range, - KeyValueNumberString, OnEvent, Expose, Configure, -} from './types'; -import * as modernExtend from './modernExtend'; -import * as exposes from './exposes'; -import {logger} from './logger'; - const NS = 'zhc:lumi'; const legacyFromZigbeeStore: KeyValueAny = {}; const e = exposes.presets; @@ -58,110 +55,110 @@ export const buffer2DataObject = (model: Definition, buffer: Buffer) => { let value = null; switch (buffer[i + 1]) { - case 16: - case 32: - // 0x10 ZclBoolean - // 0x20 Zcl8BitUint - value = buffer.readUInt8(i + 2); - i += 2; - break; - case 33: - // 0x21 Zcl16BitUint - value = buffer.readUInt16LE(i + 2); - i += 3; - break; - case 34: - // 0x22 Zcl24BitUint - value = buffer.readUIntLE(i + 2, 3); - i += 4; - break; - case 35: - // 0x23 Zcl32BitUint - value = buffer.readUInt32LE(i + 2); - i += 5; - break; - case 36: - // 0x24 Zcl40BitUint - value = buffer.readUIntLE(i + 2, 5); - i += 6; - break; - case 37: - // 0x25 Zcl48BitUint - value = buffer.readUIntLE(i + 2, 6); - i += 7; - break; - case 38: - // 0x26 Zcl56BitUint - value = buffer.readUIntLE(i + 2, 7); - i += 8; - break; - case 39: - // 0x27 Zcl64BitUint - value = buffer.readBigUInt64BE(i + 2); - i += 9; - break; - case 40: - // 0x28 Zcl8BitInt - value = buffer.readInt8(i + 2); - i += 2; - break; - case 41: - // 0x29 Zcl16BitInt - value = buffer.readInt16LE(i + 2); - i += 3; - break; - case 42: - // 0x2A Zcl24BitInt - value = buffer.readIntLE(i + 2, 3); - i += 4; - break; - case 43: - // 0x2B Zcl32BitInt - value = buffer.readInt32LE(i+2); - i += 5; - break; - case 44: - // 0x2C Zcl40BitInt - value = buffer.readIntLE(i + 2, 5); - i += 6; - break; - case 45: - // 0x2D Zcl48BitInt - value = buffer.readIntLE(i + 2, 6); - i += 7; - break; - case 46: - // 0x2E Zcl56BitInt - value = buffer.readIntLE(i + 2, 7); - i += 8; - break; - case 47: - // 0x2F Zcl64BitInt - value = buffer.readBigInt64BE(i + 2); - i += 9; - break; - case 57: - // 0x39 ZclSingleFloat - value = buffer.readFloatLE(i + 2); - i += 5; - break; - case 58: - // 0x3a ZclDoubleFloat - value = buffer.readDoubleLE(i + 2); - i += 5; - break; - case 66: - // 0x42 unknown, length taken from what seems correct in the logs, maybe is wrong - logger.debug(`${model.model}: unknown vtype=${buffer[i+1]}, pos=${i+1}, moving length 1`, NS); - i += 2; - break; - case 95: - // 0x5f unknown, length taken from what seems correct in the logs, maybe is wrong - logger.debug(`${model.model}: unknown vtype=${buffer[i+1]}, pos=${i+1}, moving length 4`, NS); - i += 5; - break; - default: - logger.debug(`${model.model}: unknown vtype=${buffer[i + 1]}, pos=${i + 1}`, NS); + case 16: + case 32: + // 0x10 ZclBoolean + // 0x20 Zcl8BitUint + value = buffer.readUInt8(i + 2); + i += 2; + break; + case 33: + // 0x21 Zcl16BitUint + value = buffer.readUInt16LE(i + 2); + i += 3; + break; + case 34: + // 0x22 Zcl24BitUint + value = buffer.readUIntLE(i + 2, 3); + i += 4; + break; + case 35: + // 0x23 Zcl32BitUint + value = buffer.readUInt32LE(i + 2); + i += 5; + break; + case 36: + // 0x24 Zcl40BitUint + value = buffer.readUIntLE(i + 2, 5); + i += 6; + break; + case 37: + // 0x25 Zcl48BitUint + value = buffer.readUIntLE(i + 2, 6); + i += 7; + break; + case 38: + // 0x26 Zcl56BitUint + value = buffer.readUIntLE(i + 2, 7); + i += 8; + break; + case 39: + // 0x27 Zcl64BitUint + value = buffer.readBigUInt64BE(i + 2); + i += 9; + break; + case 40: + // 0x28 Zcl8BitInt + value = buffer.readInt8(i + 2); + i += 2; + break; + case 41: + // 0x29 Zcl16BitInt + value = buffer.readInt16LE(i + 2); + i += 3; + break; + case 42: + // 0x2A Zcl24BitInt + value = buffer.readIntLE(i + 2, 3); + i += 4; + break; + case 43: + // 0x2B Zcl32BitInt + value = buffer.readInt32LE(i + 2); + i += 5; + break; + case 44: + // 0x2C Zcl40BitInt + value = buffer.readIntLE(i + 2, 5); + i += 6; + break; + case 45: + // 0x2D Zcl48BitInt + value = buffer.readIntLE(i + 2, 6); + i += 7; + break; + case 46: + // 0x2E Zcl56BitInt + value = buffer.readIntLE(i + 2, 7); + i += 8; + break; + case 47: + // 0x2F Zcl64BitInt + value = buffer.readBigInt64BE(i + 2); + i += 9; + break; + case 57: + // 0x39 ZclSingleFloat + value = buffer.readFloatLE(i + 2); + i += 5; + break; + case 58: + // 0x3a ZclDoubleFloat + value = buffer.readDoubleLE(i + 2); + i += 5; + break; + case 66: + // 0x42 unknown, length taken from what seems correct in the logs, maybe is wrong + logger.debug(`${model.model}: unknown vtype=${buffer[i + 1]}, pos=${i + 1}, moving length 1`, NS); + i += 2; + break; + case 95: + // 0x5f unknown, length taken from what seems correct in the logs, maybe is wrong + logger.debug(`${model.model}: unknown vtype=${buffer[i + 1]}, pos=${i + 1}, moving length 4`, NS); + i += 5; + break; + default: + logger.debug(`${model.model}: unknown vtype=${buffer[i + 1]}, pos=${i + 1}`, NS); } if (value != null) { @@ -172,11 +169,10 @@ export const buffer2DataObject = (model: Definition, buffer: Buffer) => { logger.debug( `${model.model}: Processed buffer into data \ - ${JSON.stringify(dataObject, (key, value) => typeof value === 'bigint' ? value.toString() : value)}`, + ${JSON.stringify(dataObject, (key, value) => (typeof value === 'bigint' ? value.toString() : value))}`, NS, ); - return dataObject; }; @@ -185,647 +181,735 @@ export const numericAttributes2Payload = async (msg: Fz.Message, meta: Fz.Meta, for (const [key, value] of Object.entries(dataObject)) { switch (key) { - case '0': - payload.detection_period = value; - break; - case '1': - payload.voltage = value; - if (model.meta && model.meta.battery && model.meta.battery.voltageToPercentage) { - assertNumber(value); - payload.battery = batteryVoltageToPercentage(value, model.meta.battery.voltageToPercentage); - } - break; - case '2': - if (['JT-BZ-01AQ/A'].includes(model.model)) { + case '0': + payload.detection_period = value; + break; + case '1': + payload.voltage = value; + if (model.meta && model.meta.battery && model.meta.battery.voltageToPercentage) { + assertNumber(value); + payload.battery = batteryVoltageToPercentage(value, model.meta.battery.voltageToPercentage); + } + break; + case '2': + if (['JT-BZ-01AQ/A'].includes(model.model)) { + assertNumber(value); + payload.power_outage_count = value - 1; + } + break; + case '3': + if (['WXCJKG11LM', 'WXCJKG12LM', 'WXCJKG13LM', 'MCCGQ14LM', 'GZCGQ01LM', 'JY-GZ-01AQ', 'CTP-R01'].includes(model.model)) { + // The temperature value is constant 25 °C and does not change, so we ignore it + // https://github.com/Koenkk/zigbee2mqtt/issues/11126 + // https://github.com/Koenkk/zigbee-herdsman-converters/pull/3585 + // https://github.com/Koenkk/zigbee2mqtt/issues/13253 + } else { + assertNumber(value); + payload.device_temperature = value; // 0x03 + } + break; + case '4': + if ( + [ + 'WS-USC01', + 'WS-USC02', + 'WS-EUK01', + 'WS-EUK02', + 'QBKG27LM', + 'QBKG28LM', + 'QBKG29LM', + 'QBKG25LM', + 'QBKG38LM', + 'QBKG39LM', + 'ZNQBKG42LM', + 'ZNQBKG43LM', + 'ZNQBKG44LM', + 'ZNQBKG45LM', + ].includes(model.model) + ) { + payload.mode_switch = getFromLookup(value, {4: 'anti_flicker_mode', 1: 'quick_mode'}); + } + break; + case '5': assertNumber(value); payload.power_outage_count = value - 1; - } - break; - case '3': - if (['WXCJKG11LM', 'WXCJKG12LM', 'WXCJKG13LM', 'MCCGQ14LM', 'GZCGQ01LM', 'JY-GZ-01AQ', 'CTP-R01'].includes(model.model)) { - // The temperature value is constant 25 °C and does not change, so we ignore it - // https://github.com/Koenkk/zigbee2mqtt/issues/11126 - // https://github.com/Koenkk/zigbee-herdsman-converters/pull/3585 - // https://github.com/Koenkk/zigbee2mqtt/issues/13253 - } else { - assertNumber(value); - payload.device_temperature = value; // 0x03 - } - break; - case '4': - if (['WS-USC01', 'WS-USC02', 'WS-EUK01', 'WS-EUK02', 'QBKG27LM', 'QBKG28LM', 'QBKG29LM', - 'QBKG25LM', 'QBKG38LM', 'QBKG39LM', 'ZNQBKG42LM', 'ZNQBKG43LM', 'ZNQBKG44LM', 'ZNQBKG45LM'].includes(model.model)) { - payload.mode_switch = getFromLookup(value, {4: 'anti_flicker_mode', 1: 'quick_mode'}); - } - break; - case '5': - assertNumber(value); - payload.power_outage_count = value - 1; - break; - case '6': - if (['MCCGQ11LM', 'SJCGQ11LM'].includes(model.model) && Array.isArray(value)) { - assertNumber(value[1]); - let count = value[1]; - // Sometimes, especially when the device is connected through another lumi router, the sensor - // send random values after 16 bit (>65536), so we truncate and read this as 16BitUInt. - count = parseInt(count.toString(16).slice(-4), 16); - payload.trigger_count = count - 1; - } - break; - case '8': - if (['ZNLDP13LM'].includes(model.model)) { - // We don't know what the value means for these devices. - } - break; - case '9': - if (['ZNLDP13LM', 'ZNXDD01LM'].includes(model.model)) { - // We don't know what the value means for these devices. - } - break; - case '10': - // Value 29146 is received for SSM-U02 sometimes here: - // https://github.com/Koenkk/zigbee2mqtt/issues/17961#issuecomment-1616170548 - if (['SSM-U01', 'DLKZMK11LM', 'SSM-U02', 'DLKZMK12LM'].includes(model.model) && (value === 1 || value === 2)) { - payload.switch_type = getFromLookup(value, {1: 'toggle', 2: 'momentary'}); - } - break; - case '11': - if (['RTCGQ11LM'].includes(model.model)) { - assertNumber(value); - payload.illuminance = value; - // DEPRECATED: remove illuminance_lux here. - payload.illuminance_lux = value; - } - break; - case '12': - if (['ZNLDP13LM', 'ZNXDD01LM'].includes(model.model)) { - // We don't know what the value means for these devices. - } - break; - case '13': - if (['ZNXDD01LM'].includes(model.model)) { - // We don't know what the value means for these devices. - } else if (['ZNCLBL01LM'].includes(model.model)) { - // Overwrite version advertised by `genBasic` and `genOta` with correct version: - // https://github.com/Koenkk/zigbee2mqtt/issues/15745 - assertNumber(value); - meta.device.meta.lumiFileVersion = value; - meta.device.softwareBuildID = trv.decodeFirmwareVersionString(value); - meta.device.save(); - } - break; - case '17': - if (['ZNXDD01LM'].includes(model.model)) { - // We don't know what the value means for these devices. - } - break; - case '100': - if (['QBKG18LM', 'QBKG20LM', 'QBKG31LM', 'QBKG39LM', 'QBKG41LM', 'QBCZ15LM', 'LLKZMK11LM', 'QBKG12LM', 'QBKG03LM', 'QBKG25LM'] - .includes(model.model)) { - let mapping; - switch (model.model) { - case 'QBCZ15LM': - mapping = 'relay'; - break; - case 'LLKZMK11LM': - mapping = 'l1'; - break; - default: - mapping = 'left'; + break; + case '6': + if (['MCCGQ11LM', 'SJCGQ11LM'].includes(model.model) && Array.isArray(value)) { + assertNumber(value[1]); + let count = value[1]; + // Sometimes, especially when the device is connected through another lumi router, the sensor + // send random values after 16 bit (>65536), so we truncate and read this as 16BitUInt. + count = parseInt(count.toString(16).slice(-4), 16); + payload.trigger_count = count - 1; } - payload[`state_${mapping}`] = value === 1 ? 'ON' : 'OFF'; - } else if (['WXKG14LM', 'WXKG16LM', 'WXKG17LM'].includes(model.model)) { - payload.click_mode = getFromLookup(value, {1: 'fast', 2: 'multi'}); - } else if (['WXCJKG11LM', 'WXCJKG12LM', 'WXCJKG13LM', 'ZNMS12LM', 'ZNCLBL01LM', 'RTCGQ12LM', 'RTCGQ13LM', - 'RTCGQ14LM'].includes(model.model)) { - // We don't know what the value means for these devices. - // https://github.com/Koenkk/zigbee2mqtt/issues/11126 - // https://github.com/Koenkk/zigbee2mqtt/issues/12279 - } else if (['RTCGQ15LM'].includes(model.model)) { - payload.occupancy = value; - } else if (['WSDCGQ01LM', 'WSDCGQ11LM', 'WSDCGQ12LM', 'VOCKQJK11LM'].includes(model.model)) { - // https://github.com/Koenkk/zigbee2mqtt/issues/798 - // Sometimes the sensor publishes non-realistic vales, filter these - // @ts-expect-error - const temperature = parseFloat(value) / 100.0; - if (temperature > -65 && temperature < 65) { - payload.temperature = temperature; + break; + case '8': + if (['ZNLDP13LM'].includes(model.model)) { + // We don't know what the value means for these devices. } - } else if (['RTCGQ11LM'].includes(model.model)) { - // It contains the occupancy, but in z2m we use a custom timer to do it, so we ignore it - // payload.occupancy = value === 1; - } else if (['MCCGQ11LM', 'MCCGQ14LM'].includes(model.model)) { - payload.contact = value === 0; - } else if (['SJCGQ11LM'].includes(model.model)) { - // Ignore the message. It seems not reliable. See discussion here https://github.com/Koenkk/zigbee2mqtt/issues/12018 - // payload.water_leak = value === 1; - } else if (['SJCGQ13LM'].includes(model.model)) { - payload.water_leak = value === 1; - } else if (['JTYJ-GD-01LM/BW'].includes(model.model)) { - payload.smoke_density = value; - } else if (['GZCGQ01LM'].includes(model.model)) { - // DEPRECATED: change illuminance_lux -> illuminance - assertNumber(value); - payload.illuminance_lux = value; - } else { - payload.state = value === 1 ? 'ON' : 'OFF'; - } - break; - case '101': - if (['QBKG18LM', 'QBKG20LM', 'QBKG31LM', 'QBKG39LM', 'QBKG41LM', 'QBCZ15LM', 'QBKG25LM', 'QBKG33LM', 'QBKG34LM', 'LLKZMK11LM', 'QBKG12LM', - 'QBKG03LM'] - .includes(model.model)) { - let mapping; - switch (model.model) { - case 'QBCZ15LM': - mapping = 'usb'; - break; - case 'QBKG25LM': - case 'QBKG33LM': - case 'QBKG34LM': - mapping = 'center'; - break; - case 'LLKZMK11LM': - mapping = 'l2'; - break; - default: - mapping = 'right'; + break; + case '9': + if (['ZNLDP13LM', 'ZNXDD01LM'].includes(model.model)) { + // We don't know what the value means for these devices. } - payload[`state_${mapping}`] = value === 1 ? 'ON' : 'OFF'; - } else if (['RTCGQ12LM', 'RTCGQ14LM', 'RTCGQ15LM'].includes(model.model)) { - // Sometimes RTCGQ14LM reports high illuminance values in the dark - // https://github.com/Koenkk/zigbee2mqtt/issues/12596 - assertNumber(value); - const illuminance = value > 65000 ? 0 : value; - payload.illuminance = illuminance; - } else if (['WSDCGQ01LM', 'WSDCGQ11LM', 'WSDCGQ12LM', 'VOCKQJK11LM'].includes(model.model)) { - // https://github.com/Koenkk/zigbee2mqtt/issues/798 - // Sometimes the sensor publishes non-realistic vales, filter these - // @ts-expect-error - const humidity = parseFloat(value) / 100.0; - if (humidity >= 0 && humidity <= 100) { - payload.humidity = humidity; + break; + case '10': + // Value 29146 is received for SSM-U02 sometimes here: + // https://github.com/Koenkk/zigbee2mqtt/issues/17961#issuecomment-1616170548 + if (['SSM-U01', 'DLKZMK11LM', 'SSM-U02', 'DLKZMK12LM'].includes(model.model) && (value === 1 || value === 2)) { + payload.switch_type = getFromLookup(value, {1: 'toggle', 2: 'momentary'}); } - } else if (['ZNJLBL01LM', 'ZNCLDJ12LM'].includes(model.model)) { - payload.battery = value; - } else if (['ZNCLBL01LM'].includes(model.model)) { - assertNumber(value); - const battery = value / 2; - payload.battery = precisionRound(battery, 2); - } else if (['RTCZCGQ11LM'].includes(model.model)) { - payload.presence = getFromLookup(value, {0: false, 1: true, 255: null}); - } else if (['ZNXDD01LM'].includes(model.model)) { - payload.brightness = value; - } - break; - case '102': - if (['QBKG25LM', 'QBKG33LM', 'QBKG34LM'].includes(model.model)) { - payload.state_right = value === 1 ? 'ON' : 'OFF'; - } else if (['WSDCGQ01LM', 'WSDCGQ11LM'].includes(model.model)) { - assertNumber(value); - payload.pressure = value/100.0; - } else if (['WSDCGQ12LM'].includes(model.model)) { - // This pressure value is ignored because it is less accurate than reported in the 'scaledValue' attribute - // of the 'msPressureMeasurement' cluster - } else if (['RTCZCGQ11LM'].includes(model.model)) { - if (meta.device.applicationVersion < 50) { - payload.presence_event = getFromLookup(value, {0: 'enter', 1: 'leave', 2: 'left_enter', 3: 'right_leave', 4: 'right_enter', - 5: 'left_leave', 6: 'approach', 7: 'away', 255: null}); + break; + case '11': + if (['RTCGQ11LM'].includes(model.model)) { + assertNumber(value); + payload.illuminance = value; + // DEPRECATED: remove illuminance_lux here. + payload.illuminance_lux = value; + } + break; + case '12': + if (['ZNLDP13LM', 'ZNXDD01LM'].includes(model.model)) { + // We don't know what the value means for these devices. + } + break; + case '13': + if (['ZNXDD01LM'].includes(model.model)) { + // We don't know what the value means for these devices. + } else if (['ZNCLBL01LM'].includes(model.model)) { + // Overwrite version advertised by `genBasic` and `genOta` with correct version: + // https://github.com/Koenkk/zigbee2mqtt/issues/15745 + assertNumber(value); + meta.device.meta.lumiFileVersion = value; + meta.device.softwareBuildID = trv.decodeFirmwareVersionString(value); + meta.device.save(); + } + break; + case '17': + if (['ZNXDD01LM'].includes(model.model)) { + // We don't know what the value means for these devices. + } + break; + case '100': + if ( + [ + 'QBKG18LM', + 'QBKG20LM', + 'QBKG31LM', + 'QBKG39LM', + 'QBKG41LM', + 'QBCZ15LM', + 'LLKZMK11LM', + 'QBKG12LM', + 'QBKG03LM', + 'QBKG25LM', + ].includes(model.model) + ) { + let mapping; + switch (model.model) { + case 'QBCZ15LM': + mapping = 'relay'; + break; + case 'LLKZMK11LM': + mapping = 'l1'; + break; + default: + mapping = 'left'; + } + payload[`state_${mapping}`] = value === 1 ? 'ON' : 'OFF'; + } else if (['WXKG14LM', 'WXKG16LM', 'WXKG17LM'].includes(model.model)) { + payload.click_mode = getFromLookup(value, {1: 'fast', 2: 'multi'}); + } else if ( + ['WXCJKG11LM', 'WXCJKG12LM', 'WXCJKG13LM', 'ZNMS12LM', 'ZNCLBL01LM', 'RTCGQ12LM', 'RTCGQ13LM', 'RTCGQ14LM'].includes(model.model) + ) { + // We don't know what the value means for these devices. + // https://github.com/Koenkk/zigbee2mqtt/issues/11126 + // https://github.com/Koenkk/zigbee2mqtt/issues/12279 + } else if (['RTCGQ15LM'].includes(model.model)) { + payload.occupancy = value; + } else if (['WSDCGQ01LM', 'WSDCGQ11LM', 'WSDCGQ12LM', 'VOCKQJK11LM'].includes(model.model)) { + // https://github.com/Koenkk/zigbee2mqtt/issues/798 + // Sometimes the sensor publishes non-realistic vales, filter these + // @ts-expect-error + const temperature = parseFloat(value) / 100.0; + if (temperature > -65 && temperature < 65) { + payload.temperature = temperature; + } + } else if (['RTCGQ11LM'].includes(model.model)) { + // It contains the occupancy, but in z2m we use a custom timer to do it, so we ignore it + // payload.occupancy = value === 1; + } else if (['MCCGQ11LM', 'MCCGQ14LM'].includes(model.model)) { + payload.contact = value === 0; + } else if (['SJCGQ11LM'].includes(model.model)) { + // Ignore the message. It seems not reliable. See discussion here https://github.com/Koenkk/zigbee2mqtt/issues/12018 + // payload.water_leak = value === 1; + } else if (['SJCGQ13LM'].includes(model.model)) { + payload.water_leak = value === 1; + } else if (['JTYJ-GD-01LM/BW'].includes(model.model)) { + payload.smoke_density = value; + } else if (['GZCGQ01LM'].includes(model.model)) { + // DEPRECATED: change illuminance_lux -> illuminance + assertNumber(value); + payload.illuminance_lux = value; } else { + payload.state = value === 1 ? 'ON' : 'OFF'; + } + break; + case '101': + if ( + [ + 'QBKG18LM', + 'QBKG20LM', + 'QBKG31LM', + 'QBKG39LM', + 'QBKG41LM', + 'QBCZ15LM', + 'QBKG25LM', + 'QBKG33LM', + 'QBKG34LM', + 'LLKZMK11LM', + 'QBKG12LM', + 'QBKG03LM', + ].includes(model.model) + ) { + let mapping; + switch (model.model) { + case 'QBCZ15LM': + mapping = 'usb'; + break; + case 'QBKG25LM': + case 'QBKG33LM': + case 'QBKG34LM': + mapping = 'center'; + break; + case 'LLKZMK11LM': + mapping = 'l2'; + break; + default: + mapping = 'right'; + } + payload[`state_${mapping}`] = value === 1 ? 'ON' : 'OFF'; + } else if (['RTCGQ12LM', 'RTCGQ14LM', 'RTCGQ15LM'].includes(model.model)) { + // Sometimes RTCGQ14LM reports high illuminance values in the dark + // https://github.com/Koenkk/zigbee2mqtt/issues/12596 + assertNumber(value); + const illuminance = value > 65000 ? 0 : value; + payload.illuminance = illuminance; + } else if (['WSDCGQ01LM', 'WSDCGQ11LM', 'WSDCGQ12LM', 'VOCKQJK11LM'].includes(model.model)) { + // https://github.com/Koenkk/zigbee2mqtt/issues/798 + // Sometimes the sensor publishes non-realistic vales, filter these + // @ts-expect-error + const humidity = parseFloat(value) / 100.0; + if (humidity >= 0 && humidity <= 100) { + payload.humidity = humidity; + } + } else if (['ZNJLBL01LM', 'ZNCLDJ12LM'].includes(model.model)) { + payload.battery = value; + } else if (['ZNCLBL01LM'].includes(model.model)) { + assertNumber(value); + const battery = value / 2; + payload.battery = precisionRound(battery, 2); + } else if (['RTCZCGQ11LM'].includes(model.model)) { + payload.presence = getFromLookup(value, {0: false, 1: true, 255: null}); + } else if (['ZNXDD01LM'].includes(model.model)) { + payload.brightness = value; + } + break; + case '102': + if (['QBKG25LM', 'QBKG33LM', 'QBKG34LM'].includes(model.model)) { + payload.state_right = value === 1 ? 'ON' : 'OFF'; + } else if (['WSDCGQ01LM', 'WSDCGQ11LM'].includes(model.model)) { + assertNumber(value); + payload.pressure = value / 100.0; + } else if (['WSDCGQ12LM'].includes(model.model)) { + // This pressure value is ignored because it is less accurate than reported in the 'scaledValue' attribute + // of the 'msPressureMeasurement' cluster + } else if (['RTCZCGQ11LM'].includes(model.model)) { + if (meta.device.applicationVersion < 50) { + payload.presence_event = getFromLookup(value, { + 0: 'enter', + 1: 'leave', + 2: 'left_enter', + 3: 'right_leave', + 4: 'right_enter', + 5: 'left_leave', + 6: 'approach', + 7: 'away', + 255: null, + }); + } else { + payload.motion_sensitivity = getFromLookup(value, {1: 'low', 2: 'medium', 3: 'high'}); + } + } else if (['ZNXDD01LM'].includes(model.model)) { + payload.color_temp = value; + } + break; + case '103': + if (['RTCZCGQ11LM'].includes(model.model)) { + payload.monitoring_mode = getFromLookup(value, {0: 'undirected', 1: 'left_right'}); + } else if (['ZNXDD01LM'].includes(model.model)) { + // const color_temp_min = (value & 0xffff); // 2700 + // const color_temp_max = (value >> 16) & 0xffff; // 6500 + } + break; + case '105': + if (['RTCGQ13LM'].includes(model.model)) { payload.motion_sensitivity = getFromLookup(value, {1: 'low', 2: 'medium', 3: 'high'}); + } else if (['RTCZCGQ11LM'].includes(model.model)) { + payload.approach_distance = getFromLookup(value, {0: 'far', 1: 'medium', 2: 'near'}); + } else if (['RTCGQ14LM'].includes(model.model)) { + payload.detection_interval = value; } - } else if (['ZNXDD01LM'].includes(model.model)) { - payload.color_temp = value; - } - break; - case '103': - if (['RTCZCGQ11LM'].includes(model.model)) { - payload.monitoring_mode = getFromLookup(value, {0: 'undirected', 1: 'left_right'}); - } else if (['ZNXDD01LM'].includes(model.model)) { - // const color_temp_min = (value & 0xffff); // 2700 - // const color_temp_max = (value >> 16) & 0xffff; // 6500 - } - break; - case '105': - if (['RTCGQ13LM'].includes(model.model)) { - payload.motion_sensitivity = getFromLookup(value, {1: 'low', 2: 'medium', 3: 'high'}); - } else if (['RTCZCGQ11LM'].includes(model.model)) { - payload.approach_distance = getFromLookup(value, {0: 'far', 1: 'medium', 2: 'near'}); - } else if (['RTCGQ14LM'].includes(model.model)) { - payload.detection_interval = value; - } - break; - case '106': - if (['RTCGQ14LM'].includes(model.model)) { - payload.motion_sensitivity = getFromLookup(value, {1: 'low', 2: 'medium', 3: 'high'}); - } - break; - case '107': - if (['RTCGQ14LM'].includes(model.model)) { - payload.trigger_indicator = value === 1; - } else if (['ZNCLBL01LM'].includes(model.model)) { - assertNumber(value); - const position = options.invert_cover ? 100 - value : value; - payload.position = position; - payload.state = options.invert_cover ? (position > 0 ? 'CLOSE' : 'OPEN') : (position > 0 ? 'OPEN' : 'CLOSE'); - } - break; - case '149': - assertNumber(value); - payload.energy = value; // 0x95 - if (['LLKZMK12LM'].includes(model.model)) { - assertNumber(payload.energy); - payload.energy = payload.energy / 1000; - } - // Consumption is deprecated - payload.consumption = payload.energy; - break; - case '150': - if (!['JTYJ-GD-01LM/BW'].includes(model.model)) { - assertNumber(value); - payload.voltage = value * 0.1; // 0x96 - } - break; - case '151': - if (['LLKZMK11LM'].includes(model.model)) { - assertNumber(value); - payload.current = value; - } else { - assertNumber(value); - payload.current = value * 0.001; - } - break; - case '152': - if (['DJT11LM'].includes(model.model)) { - // We don't know what implies for this device, it contains values like 30, 50,... that don't seem to change - } else { - assertNumber(value); - payload.power = value; // 0x98 - } - break; - case '154': - if (['ZNLDP13LM', 'ZNXDD01LM'].includes(model.model)) { - // We don't know what the value means for these devices. - } - break; - case '159': - if (['JT-BZ-01AQ/A'].includes(model.model)) { - payload.gas_sensitivity = getFromLookup(value, {1: '15%LEL', 2: '10%LEL'}); - } else if (['MCCGQ13LM'].includes(model.model)) { - payload.detection_distance = getFromLookup(value, {1: '10mm', 2: '20mm', 3: '30mm'}); - } - break; - case '160': - if (['JT-BZ-01AQ/A'].includes(model.model)) { - payload.gas = value === 1; - } else if (['JY-GZ-01AQ'].includes(model.model)) { - payload.smoke = value === 1; - } - break; - case '161': - if (['JT-BZ-01AQ/A'].includes(model.model)) { - payload.gas_density = value; - } else if (['JY-GZ-01AQ'].includes(model.model)) { - payload.smoke_density = value; - payload.smoke_density_dbm = getFromLookup(value, {0: 0, 1: 0.085, 2: 0.088, 3: 0.093, 4: 0.095, 5: 0.100, 6: 0.105, 7: 0.110, - 8: 0.115, 9: 0.120, 10: 0.125}); - } - break; - case '162': - if (['JT-BZ-01AQ/A', 'JY-GZ-01AQ'].includes(model.model)) { - payload.test = value === 1; - } - break; - case '163': - if (['JT-BZ-01AQ/A', 'JY-GZ-01AQ'].includes(model.model)) { - payload.buzzer_manual_mute = value === 1; - } - break; - case '164': - if (['JT-BZ-01AQ/A'].includes(model.model)) { - payload.state = getFromLookup(value, {0: 'work', 1: 'preparation'}); - } else if (['JY-GZ-01AQ'].includes(model.model)) { - payload.heartbeat_indicator = value === 1; - } - break; - case '165': - if (['JY-GZ-01AQ'].includes(model.model)) { - payload.linkage_alarm = value === 1; - } - break; - case '166': - if (['JT-BZ-01AQ/A'].includes(model.model)) { - payload.linkage_alarm = value === 1; - } - break; - case '238': - if (['ZNXDD01LM'].includes(model.model)) { - // We don't know what the value means for these devices. - } else if (['ZNCLBL01LM'].includes(model.model)) { - // Overwrite version advertised by `genBasic` and `genOta` with correct version: - // https://github.com/Koenkk/zigbee2mqtt/issues/15745 + break; + case '106': + if (['RTCGQ14LM'].includes(model.model)) { + payload.motion_sensitivity = getFromLookup(value, {1: 'low', 2: 'medium', 3: 'high'}); + } + break; + case '107': + if (['RTCGQ14LM'].includes(model.model)) { + payload.trigger_indicator = value === 1; + } else if (['ZNCLBL01LM'].includes(model.model)) { + assertNumber(value); + const position = options.invert_cover ? 100 - value : value; + payload.position = position; + payload.state = options.invert_cover ? (position > 0 ? 'CLOSE' : 'OPEN') : position > 0 ? 'OPEN' : 'CLOSE'; + } + break; + case '149': assertNumber(value); - meta.device.meta.lumiFileVersion = value; - meta.device.softwareBuildID = trv.decodeFirmwareVersionString(value); - meta.device.save(); - } - break; - case '240': - payload.flip_indicator_light = value === 1 ? 'ON' : 'OFF'; - break; - case '247': - { - const dataObject247 = buffer2DataObject(model, value as Buffer); - if (['CTP-R01'].includes(model.model)) { - // execute pending soft switch of operation_mode, if exists - const opModeSwitchTask = globalStore.getValue(meta.device, 'opModeSwitchTask'); - if (opModeSwitchTask) { - const {callback, newMode} = opModeSwitchTask; - try { - await callback(); - payload.operation_mode = newMode; - globalStore.putValue(meta.device, 'opModeSwitchTask', null); - } catch (error) { - // do nothing when callback fails + payload.energy = value; // 0x95 + if (['LLKZMK12LM'].includes(model.model)) { + assertNumber(payload.energy); + payload.energy = payload.energy / 1000; + } + // Consumption is deprecated + payload.consumption = payload.energy; + break; + case '150': + if (!['JTYJ-GD-01LM/BW'].includes(model.model)) { + assertNumber(value); + payload.voltage = value * 0.1; // 0x96 + } + break; + case '151': + if (['LLKZMK11LM'].includes(model.model)) { + assertNumber(value); + payload.current = value; + } else { + assertNumber(value); + payload.current = value * 0.001; + } + break; + case '152': + if (['DJT11LM'].includes(model.model)) { + // We don't know what implies for this device, it contains values like 30, 50,... that don't seem to change + } else { + assertNumber(value); + payload.power = value; // 0x98 + } + break; + case '154': + if (['ZNLDP13LM', 'ZNXDD01LM'].includes(model.model)) { + // We don't know what the value means for these devices. + } + break; + case '159': + if (['JT-BZ-01AQ/A'].includes(model.model)) { + payload.gas_sensitivity = getFromLookup(value, {1: '15%LEL', 2: '10%LEL'}); + } else if (['MCCGQ13LM'].includes(model.model)) { + payload.detection_distance = getFromLookup(value, {1: '10mm', 2: '20mm', 3: '30mm'}); + } + break; + case '160': + if (['JT-BZ-01AQ/A'].includes(model.model)) { + payload.gas = value === 1; + } else if (['JY-GZ-01AQ'].includes(model.model)) { + payload.smoke = value === 1; + } + break; + case '161': + if (['JT-BZ-01AQ/A'].includes(model.model)) { + payload.gas_density = value; + } else if (['JY-GZ-01AQ'].includes(model.model)) { + payload.smoke_density = value; + payload.smoke_density_dbm = getFromLookup(value, { + 0: 0, + 1: 0.085, + 2: 0.088, + 3: 0.093, + 4: 0.095, + 5: 0.1, + 6: 0.105, + 7: 0.11, + 8: 0.115, + 9: 0.12, + 10: 0.125, + }); + } + break; + case '162': + if (['JT-BZ-01AQ/A', 'JY-GZ-01AQ'].includes(model.model)) { + payload.test = value === 1; + } + break; + case '163': + if (['JT-BZ-01AQ/A', 'JY-GZ-01AQ'].includes(model.model)) { + payload.buzzer_manual_mute = value === 1; + } + break; + case '164': + if (['JT-BZ-01AQ/A'].includes(model.model)) { + payload.state = getFromLookup(value, {0: 'work', 1: 'preparation'}); + } else if (['JY-GZ-01AQ'].includes(model.model)) { + payload.heartbeat_indicator = value === 1; + } + break; + case '165': + if (['JY-GZ-01AQ'].includes(model.model)) { + payload.linkage_alarm = value === 1; + } + break; + case '166': + if (['JT-BZ-01AQ/A'].includes(model.model)) { + payload.linkage_alarm = value === 1; + } + break; + case '238': + if (['ZNXDD01LM'].includes(model.model)) { + // We don't know what the value means for these devices. + } else if (['ZNCLBL01LM'].includes(model.model)) { + // Overwrite version advertised by `genBasic` and `genOta` with correct version: + // https://github.com/Koenkk/zigbee2mqtt/issues/15745 + assertNumber(value); + meta.device.meta.lumiFileVersion = value; + meta.device.softwareBuildID = trv.decodeFirmwareVersionString(value); + meta.device.save(); + } + break; + case '240': + payload.flip_indicator_light = value === 1 ? 'ON' : 'OFF'; + break; + case '247': + { + const dataObject247 = buffer2DataObject(model, value as Buffer); + if (['CTP-R01'].includes(model.model)) { + // execute pending soft switch of operation_mode, if exists + const opModeSwitchTask = globalStore.getValue(meta.device, 'opModeSwitchTask'); + if (opModeSwitchTask) { + const {callback, newMode} = opModeSwitchTask; + try { + await callback(); + payload.operation_mode = newMode; + globalStore.putValue(meta.device, 'opModeSwitchTask', null); + } catch (error) { + // do nothing when callback fails + } + } else { + payload.operation_mode = getFromLookup(dataObject247[155], {0: 'action_mode', 1: 'scene_mode'}); } - } else { - payload.operation_mode = getFromLookup(dataObject247[155], {0: 'action_mode', 1: 'scene_mode'}); } + const payload247 = await numericAttributes2Payload(msg, meta, model, options, dataObject247); + payload = {...payload, ...payload247}; } - const payload247 = await numericAttributes2Payload(msg, meta, model, options, dataObject247); - payload = {...payload, ...payload247}; - } - break; - case '258': - payload.detection_interval = value; - break; - case '268': - if (['RTCGQ13LM', 'RTCGQ14LM', 'RTCZCGQ11LM'].includes(model.model)) { - payload.motion_sensitivity = getFromLookup(value, {1: 'low', 2: 'medium', 3: 'high'}); - } else if (['JT-BZ-01AQ/A'].includes(model.model)) { - payload.gas_sensitivity = getFromLookup(value, {1: '15%LEL', 2: '10%LEL'}); - } - break; - case '293': - payload.click_mode = getFromLookup(value, {1: 'fast', 2: 'multi'}); - break; - case '294': - if (['JT-BZ-01AQ/A', 'JY-GZ-01AQ'].includes(model.model)) { - payload.buzzer_manual_mute = value === 1; - } - break; - case '295': - if (['JT-BZ-01AQ/A', 'JY-GZ-01AQ'].includes(model.model)) { - payload.test = value === 1; - } - break; - case '313': - if (['JT-BZ-01AQ/A'].includes(model.model)) { - payload.state = getFromLookup(value, {0: 'work', 1: 'preparation'}); - } - break; - case '314': - if (['JT-BZ-01AQ/A'].includes(model.model)) { - payload.gas = value === 1; - } else if (['JY-GZ-01AQ'].includes(model.model)) { - payload.smoke = value === 1; - } - break; - case '315': - if (['JT-BZ-01AQ/A'].includes(model.model)) { - payload.gas_density = value; - } else if (['JY-GZ-01AQ'].includes(model.model)) { - payload.smoke_density = value; - payload.smoke_density_dbm = getFromLookup(value, {0: 0, 1: 0.085, 2: 0.088, 3: 0.093, 4: 0.095, 5: 0.100, 6: 0.105, 7: 0.110, - 8: 0.115, 9: 0.120, 10: 0.125}); - } - break; - case '316': - if (['JY-GZ-01AQ'].includes(model.model)) { - payload.heartbeat_indicator = value === 1; - } - break; - case '317': - if (['JT-BZ-01AQ/A', 'JY-GZ-01AQ'].includes(model.model)) { - payload.buzzer_manual_alarm = value === 1; - } - break; - case '320': - if (['MCCGQ13LM'].includes(model.model)) { - payload.tamper = getFromLookup(value, {0: false, 1: true}); - } - break; - case '322': - if (['RTCZCGQ11LM'].includes(model.model)) { - payload.presence = getFromLookup(value, {0: false, 1: true, 255: null}); - } - break; - case '323': - if (['RTCZCGQ11LM'].includes(model.model)) { - payload.presence_event = getFromLookup(value, {0: 'enter', 1: 'leave', 2: 'left_enter', 3: 'right_leave', 4: 'right_enter', - 5: 'left_leave', 6: 'approach', 7: 'away'}); - } - break; - case '324': - if (['RTCZCGQ11LM'].includes(model.model)) { - payload.monitoring_mode = getFromLookup(value, {0: 'undirected', 1: 'left_right'}); - } - break; - case '326': - if (['RTCZCGQ11LM'].includes(model.model)) { - payload.approach_distance = getFromLookup(value, {0: 'far', 1: 'medium', 2: 'near'}); - } - break; - case '328': - if (['CTP-R01'].includes(model.model)) { - // detected hard switch of operation_mode (attribute 0x148[328]) - payload.operation_mode = getFromLookup(msg.data[328], {0: 'action_mode', 1: 'scene_mode'}); - } - break; - case '329': - if (['CTP-R01'].includes(model.model)) { - // side_up attribute report (attribute 0x149[329]) - payload.action = 'side_up'; - payload.side = msg.data[329] + 1; - } - break; - case '331': - if (['JT-BZ-01AQ/A', 'JY-GZ-01AQ'].includes(model.model)) { - payload.linkage_alarm = value === 1; - } - break; - case '332': - if (['JT-BZ-01AQ/A', 'JY-GZ-01AQ'].includes(model.model)) { - payload.linkage_alarm_state = value === 1; - } - break; - case '338': - if (['RTCGQ14LM'].includes(model.model)) { - payload.trigger_indicator = value === 1; - } - break; - case '512': - if (['ZNCZ15LM', 'QBCZ14LM', 'QBCZ15LM', 'SP-EUC01'].includes(model.model)) { - payload.button_lock = value === 1 ? 'OFF' : 'ON'; - } else { - const mode = getFromLookup(value, {0x01: 'control_relay', 0x00: 'decoupled'}); - payload[postfixWithEndpointName('operation_mode', msg, model, meta)] = mode; - } - break; - case '513': - payload.power_outage_memory = value === 1; - break; - case '514': - payload.auto_off = value === 1; - break; - case '515': - payload.led_disabled_night = value === 1; - break; - case '519': - payload.consumer_connected = value === 1; - break; - case '523': - assertNumber(value); - payload.overload_protection = precisionRound(value, 2); - break; - case '550': - payload.button_switch_mode = value === 1 ? 'relay_and_usb' : 'relay'; - break; - case '645': - // aqara z1 lock relay - payload.lock_relay = value === 1; - break; - case '1025': - if (['ZNCLBL01LM'].includes(model.model)) { - payload.hand_open = !value; - } else { - // next values update only when curtain finished initial setup and knows current position - // @ts-expect-error - payload.options = {...payload.options, reverse_direction: value[2] == '\u0001', hand_open: value[5] == '\u0000'}; - } - break; - case '1028': - payload = {...payload, - motor_state: getFromLookup(value, (options.invert_cover ? {0: 'stopped', 1: 'closing', 2: 'opening'} : - {0: 'stopped', 1: 'opening', 2: 'closing'})), - running: !!value, - }; - break; - case '1032': - if (['ZNJLBL01LM'].includes(model.model)) { - payload.motor_speed = getFromLookup(value, {0: 'low', 1: 'medium', 2: 'high'}); - } - break; - case '1033': - if (['ZNJLBL01LM'].includes(model.model)) { - payload.charging_status = value === 1; - } - break; - case '1034': - if (['ZNJLBL01LM'].includes(model.model)) { - payload.battery = value; - } - break; - case '1035': - if (['ZNCLBL01LM'].includes(model.model)) { - payload.voltage = value; - } - break; - case '1055': - if (['ZNCLBL01LM'].includes(model.model)) { - assertNumber(value); - payload.target_position = options.invert_cover ? 100 - value : value; - } - break; - case '1056': - if (['ZNCLBL01LM'].includes(model.model)) { - // This is the "target_state" attribute, which takes the following values: 0: 'OPEN', 1: 'CLOSE', 2: 'STOP'. - // It is not used because the values 0 and 1 are not always reported. - // https://github.com/Koenkk/zigbee-herdsman-converters/pull/4307 - } - break; - case '1057': - if (['ZNCLBL01LM'].includes(model.model)) { - payload.motor_state = getFromLookup(value, (options.invert_cover ? {0: 'opening', 1: 'closing', 2: 'stopped'} : - {0: 'closing', 1: 'opening', 2: 'stopped'})); - assertNumber(value); - payload.running = value < 2 ? true : false; - } - break; - case '1061': - if (['ZNCLBL01LM'].includes(model.model)) { - payload.action = getFromLookup(value, (options.invert_cover ? {1: 'manual_close', 2: 'manual_open'} : - {1: 'manual_open', 2: 'manual_close'})); - } - break; - case '1063': - if (['ZNCLBL01LM'].includes(model.model)) { - getFromLookup(value, {0: 'UNLOCK', 1: 'LOCK'}); - } - break; - case '1064': - if (['ZNCLBL01LM'].includes(model.model)) { - payload.hooks_state = getFromLookup(value, {0: 'unlocked', 1: 'locked', 2: 'locking', 3: 'unlocking'}); - payload.hooks_lock = getFromLookup(value, {0: 'UNLOCK', 1: 'LOCK', 2: 'UNLOCK', 3: 'LOCK'}); - } - break; - case '1065': - if (['ZNCLBL01LM'].includes(model.model)) { + break; + case '258': + payload.detection_interval = value; + break; + case '268': + if (['RTCGQ13LM', 'RTCGQ14LM', 'RTCZCGQ11LM'].includes(model.model)) { + payload.motion_sensitivity = getFromLookup(value, {1: 'low', 2: 'medium', 3: 'high'}); + } else if (['JT-BZ-01AQ/A'].includes(model.model)) { + payload.gas_sensitivity = getFromLookup(value, {1: '15%LEL', 2: '10%LEL'}); + } + break; + case '293': + payload.click_mode = getFromLookup(value, {1: 'fast', 2: 'multi'}); + break; + case '294': + if (['JT-BZ-01AQ/A', 'JY-GZ-01AQ'].includes(model.model)) { + payload.buzzer_manual_mute = value === 1; + } + break; + case '295': + if (['JT-BZ-01AQ/A', 'JY-GZ-01AQ'].includes(model.model)) { + payload.test = value === 1; + } + break; + case '313': + if (['JT-BZ-01AQ/A'].includes(model.model)) { + payload.state = getFromLookup(value, {0: 'work', 1: 'preparation'}); + } + break; + case '314': + if (['JT-BZ-01AQ/A'].includes(model.model)) { + payload.gas = value === 1; + } else if (['JY-GZ-01AQ'].includes(model.model)) { + payload.smoke = value === 1; + } + break; + case '315': + if (['JT-BZ-01AQ/A'].includes(model.model)) { + payload.gas_density = value; + } else if (['JY-GZ-01AQ'].includes(model.model)) { + payload.smoke_density = value; + payload.smoke_density_dbm = getFromLookup(value, { + 0: 0, + 1: 0.085, + 2: 0.088, + 3: 0.093, + 4: 0.095, + 5: 0.1, + 6: 0.105, + 7: 0.11, + 8: 0.115, + 9: 0.12, + 10: 0.125, + }); + } + break; + case '316': + if (['JY-GZ-01AQ'].includes(model.model)) { + payload.heartbeat_indicator = value === 1; + } + break; + case '317': + if (['JT-BZ-01AQ/A', 'JY-GZ-01AQ'].includes(model.model)) { + payload.buzzer_manual_alarm = value === 1; + } + break; + case '320': + if (['MCCGQ13LM'].includes(model.model)) { + payload.tamper = getFromLookup(value, {0: false, 1: true}); + } + break; + case '322': + if (['RTCZCGQ11LM'].includes(model.model)) { + payload.presence = getFromLookup(value, {0: false, 1: true, 255: null}); + } + break; + case '323': + if (['RTCZCGQ11LM'].includes(model.model)) { + payload.presence_event = getFromLookup(value, { + 0: 'enter', + 1: 'leave', + 2: 'left_enter', + 3: 'right_leave', + 4: 'right_enter', + 5: 'left_leave', + 6: 'approach', + 7: 'away', + }); + } + break; + case '324': + if (['RTCZCGQ11LM'].includes(model.model)) { + payload.monitoring_mode = getFromLookup(value, {0: 'undirected', 1: 'left_right'}); + } + break; + case '326': + if (['RTCZCGQ11LM'].includes(model.model)) { + payload.approach_distance = getFromLookup(value, {0: 'far', 1: 'medium', 2: 'near'}); + } + break; + case '328': + if (['CTP-R01'].includes(model.model)) { + // detected hard switch of operation_mode (attribute 0x148[328]) + payload.operation_mode = getFromLookup(msg.data[328], {0: 'action_mode', 1: 'scene_mode'}); + } + break; + case '329': + if (['CTP-R01'].includes(model.model)) { + // side_up attribute report (attribute 0x149[329]) + payload.action = 'side_up'; + payload.side = msg.data[329] + 1; + } + break; + case '331': + if (['JT-BZ-01AQ/A', 'JY-GZ-01AQ'].includes(model.model)) { + payload.linkage_alarm = value === 1; + } + break; + case '332': + if (['JT-BZ-01AQ/A', 'JY-GZ-01AQ'].includes(model.model)) { + payload.linkage_alarm_state = value === 1; + } + break; + case '338': + if (['RTCGQ14LM'].includes(model.model)) { + payload.trigger_indicator = value === 1; + } + break; + case '512': + if (['ZNCZ15LM', 'QBCZ14LM', 'QBCZ15LM', 'SP-EUC01'].includes(model.model)) { + payload.button_lock = value === 1 ? 'OFF' : 'ON'; + } else { + const mode = getFromLookup(value, {0x01: 'control_relay', 0x00: 'decoupled'}); + payload[postfixWithEndpointName('operation_mode', msg, model, meta)] = mode; + } + break; + case '513': + payload.power_outage_memory = value === 1; + break; + case '514': + payload.auto_off = value === 1; + break; + case '515': + payload.led_disabled_night = value === 1; + break; + case '519': + payload.consumer_connected = value === 1; + break; + case '523': assertNumber(value); - payload.illuminance_lux = value * 50; - } - break; - case '1289': - payload.dimmer_mode = getFromLookup(value, {3: 'rgbw', 1: 'dual_ct'}); - break; - case '1299': - if (['ZNXDD01LM'].includes(model.model)) { - // maximum color temp (6500) - } - break; - case '1300': - if (['ZNXDD01LM'].includes(model.model)) { - // minimum color temp (2700) - } - break; - case '65281': - { + payload.overload_protection = precisionRound(value, 2); + break; + case '550': + payload.button_switch_mode = value === 1 ? 'relay_and_usb' : 'relay'; + break; + case '645': + // aqara z1 lock relay + payload.lock_relay = value === 1; + break; + case '1025': + if (['ZNCLBL01LM'].includes(model.model)) { + payload.hand_open = !value; + } else { + // next values update only when curtain finished initial setup and knows current position + // @ts-expect-error + payload.options = {...payload.options, reverse_direction: value[2] == '\u0001', hand_open: value[5] == '\u0000'}; + } + break; + case '1028': + payload = { + ...payload, + motor_state: getFromLookup( + value, + options.invert_cover ? {0: 'stopped', 1: 'closing', 2: 'opening'} : {0: 'stopped', 1: 'opening', 2: 'closing'}, + ), + running: !!value, + }; + break; + case '1032': + if (['ZNJLBL01LM'].includes(model.model)) { + payload.motor_speed = getFromLookup(value, {0: 'low', 1: 'medium', 2: 'high'}); + } + break; + case '1033': + if (['ZNJLBL01LM'].includes(model.model)) { + payload.charging_status = value === 1; + } + break; + case '1034': + if (['ZNJLBL01LM'].includes(model.model)) { + payload.battery = value; + } + break; + case '1035': + if (['ZNCLBL01LM'].includes(model.model)) { + payload.voltage = value; + } + break; + case '1055': + if (['ZNCLBL01LM'].includes(model.model)) { + assertNumber(value); + payload.target_position = options.invert_cover ? 100 - value : value; + } + break; + case '1056': + if (['ZNCLBL01LM'].includes(model.model)) { + // This is the "target_state" attribute, which takes the following values: 0: 'OPEN', 1: 'CLOSE', 2: 'STOP'. + // It is not used because the values 0 and 1 are not always reported. + // https://github.com/Koenkk/zigbee-herdsman-converters/pull/4307 + } + break; + case '1057': + if (['ZNCLBL01LM'].includes(model.model)) { + payload.motor_state = getFromLookup( + value, + options.invert_cover ? {0: 'opening', 1: 'closing', 2: 'stopped'} : {0: 'closing', 1: 'opening', 2: 'stopped'}, + ); + assertNumber(value); + payload.running = value < 2 ? true : false; + } + break; + case '1061': + if (['ZNCLBL01LM'].includes(model.model)) { + payload.action = getFromLookup( + value, + options.invert_cover ? {1: 'manual_close', 2: 'manual_open'} : {1: 'manual_open', 2: 'manual_close'}, + ); + } + break; + case '1063': + if (['ZNCLBL01LM'].includes(model.model)) { + getFromLookup(value, {0: 'UNLOCK', 1: 'LOCK'}); + } + break; + case '1064': + if (['ZNCLBL01LM'].includes(model.model)) { + payload.hooks_state = getFromLookup(value, {0: 'unlocked', 1: 'locked', 2: 'locking', 3: 'unlocking'}); + payload.hooks_lock = getFromLookup(value, {0: 'UNLOCK', 1: 'LOCK', 2: 'UNLOCK', 3: 'LOCK'}); + } + break; + case '1065': + if (['ZNCLBL01LM'].includes(model.model)) { + assertNumber(value); + payload.illuminance_lux = value * 50; + } + break; + case '1289': + payload.dimmer_mode = getFromLookup(value, {3: 'rgbw', 1: 'dual_ct'}); + break; + case '1299': + if (['ZNXDD01LM'].includes(model.model)) { + // maximum color temp (6500) + } + break; + case '1300': + if (['ZNXDD01LM'].includes(model.model)) { + // minimum color temp (2700) + } + break; + case '65281': + { + // @ts-expect-error + const payload65281 = await numericAttributes2Payload(msg, meta, model, options, value); + payload = {...payload, ...payload65281}; + } + break; + case '65282': + // This is a a complete structure with attributes, like element 0 for state, element 1 for voltage... + // At this moment we only extract what we are sure, for example, position 0 seems to be always 1 for a + // occupancy sensor, so we ignore it at this moment // @ts-expect-error - const payload65281 = await numericAttributes2Payload(msg, meta, model, options, value); - payload = {...payload, ...payload65281}; - } - break; - case '65282': - // This is a a complete structure with attributes, like element 0 for state, element 1 for voltage... - // At this moment we only extract what we are sure, for example, position 0 seems to be always 1 for a - // occupancy sensor, so we ignore it at this moment - // @ts-expect-error - payload.voltage = value[1].elmVal; - if (model.meta && model.meta.battery && model.meta.battery.voltageToPercentage) { - assertNumber(payload.voltage); - payload.battery = batteryVoltageToPercentage(payload.voltage, model.meta.battery.voltageToPercentage); - } - // @ts-expect-error - payload.power_outage_count = value[4].elmVal - 1; - break; - case 'mode': - assertNumber(value); - payload.operation_mode = ['command', 'event'][value]; - break; - case 'modelId': - // We ignore it, but we add it here to not shown an unknown key in the log - break; - case 'illuminance': - // It contains the illuminance and occupancy, but in z2m we use a custom timer to do it, so we ignore it - break; - case 'displayUnit': - // Use lumiDisplayUnit modernExtend, but we add it here to not shown an unknown key in the log - break; - case 'airQuality': - // Use lumiAirQuality modernExtend, but we add it here to not shown an unknown key in the log - break; - default: - logger.debug(`${model.model}: unknown key ${key} with value ${value}`, NS); + payload.voltage = value[1].elmVal; + if (model.meta && model.meta.battery && model.meta.battery.voltageToPercentage) { + assertNumber(payload.voltage); + payload.battery = batteryVoltageToPercentage(payload.voltage, model.meta.battery.voltageToPercentage); + } + // @ts-expect-error + payload.power_outage_count = value[4].elmVal - 1; + break; + case 'mode': + assertNumber(value); + payload.operation_mode = ['command', 'event'][value]; + break; + case 'modelId': + // We ignore it, but we add it here to not shown an unknown key in the log + break; + case 'illuminance': + // It contains the illuminance and occupancy, but in z2m we use a custom timer to do it, so we ignore it + break; + case 'displayUnit': + // Use lumiDisplayUnit modernExtend, but we add it here to not shown an unknown key in the log + break; + case 'airQuality': + // Use lumiAirQuality modernExtend, but we add it here to not shown an unknown key in the log + break; + default: + logger.debug(`${model.model}: unknown key ${key} with value ${value}`, NS); } } @@ -838,28 +922,28 @@ const numericAttributes2Lookup = async (model: Definition, dataObject: KeyValue) let result: KeyValue = {}; for (const [key, value] of Object.entries(dataObject)) { switch (key) { - case '247': - { - const dataObject247 = buffer2DataObject(model, value as Buffer); - const result247 = await numericAttributes2Lookup(model, dataObject247); - result = {...result, ...result247}; - } - break; - case '65281': - { - const result65281 = await numericAttributes2Lookup(model, value as KeyValue); - result = {...result, ...result65281}; - } - break; - default: - result[key] = value; + case '247': + { + const dataObject247 = buffer2DataObject(model, value as Buffer); + const result247 = await numericAttributes2Lookup(model, dataObject247); + result = {...result, ...result247}; + } + break; + case '65281': + { + const result65281 = await numericAttributes2Lookup(model, value as KeyValue); + result = {...result, ...result65281}; + } + break; + default: + result[key] = value; } } return result; }; -type LumiPresenceRegionZone = {x: number, y: number} +type LumiPresenceRegionZone = {x: number; y: number}; const lumiPresenceConstants = { region_event_key: 0x0151, @@ -991,7 +1075,7 @@ export const presence = { ); }, - failure: (error: {reason: string}): { isSuccess: false, error: {reason: string} } => { + failure: (error: {reason: string}): {isSuccess: false; error: {reason: string}} => { return { isSuccess: false, error, @@ -1013,7 +1097,7 @@ function readDaySelection(buffer: Buffer, offset: number): Day[] { const selectedDays: Day[] = []; dayNames.forEach((day, index) => { - if ((buffer[offset] >> index + 1) % 2 !== 0) { + if ((buffer[offset] >> (index + 1)) % 2 !== 0) { selectedDays.push(day); } }); @@ -1022,9 +1106,11 @@ function readDaySelection(buffer: Buffer, offset: number): Day[] { } function validateDaySelection(selectedDays: Day[]) { - selectedDays.filter((selectedDay) => !dayNames.includes(selectedDay)).forEach((invalidValue) => { - throw new Error(`The value "${invalidValue}" is not a valid day (available values: ${dayNames.join(', ')})`); - }); + selectedDays + .filter((selectedDay) => !dayNames.includes(selectedDay)) + .forEach((invalidValue) => { + throw new Error(`The value "${invalidValue}" is not a valid day (available values: ${dayNames.join(', ')})`); + }); } function writeDaySelection(buffer: Buffer, offset: number, selectedDays: Day[]) { @@ -1033,7 +1119,7 @@ function writeDaySelection(buffer: Buffer, offset: number, selectedDays: Day[]) const bitMap = dayNames.reduce((repeat, dayName, index) => { const isDaySelected = selectedDays.includes(dayName); // @ts-expect-error - return repeat | isDaySelected << index + 1; + return repeat | (isDaySelected << (index + 1)); }, 0); buffer.writeUInt8(bitMap, offset); @@ -1126,51 +1212,51 @@ export const trv = { Object.entries(data).forEach(([key, value]) => { switch (parseInt(key)) { - case 3: - payload.device_temperature = value; - break; - case 5: - assertNumber(value); - payload.power_outage_count = value - 1; - break; - case 10: - // unidentified number, e.g. 32274, 3847 - break; - case 13: - assertNumber(value); - payload.firmware_version = trv.decodeFirmwareVersionString(value); - break; - case 17: - // unidentified flag/enum, e.g. 1 - break; - case 101: - assertNumber(value); - Object.assign(payload, trv.decodePreset(value)); - break; - case 102: - assertNumber(value); - payload.local_temperature = value / 100; - break; - case 103: - // This takes the following values: - // - `occupied_heating_setpoint` if `system_mode` is `heat` and `preset` is `manual` - // - `away_preset_temperature` if `system_mode` is `heat` and `preset` is `away` - // - `5` if `system_mode` is `off` - // It thus behaves similar to `occupied_heating_setpoint` except in `off` mode. Due to this difference, - // this value is written to another property to avoid an inconsistency of the `occupied_heating_setpoint`. - // TODO How to handle this value? Find better name? - assertNumber(value); - payload.internal_heating_setpoint = value / 100; - break; - case 104: - payload.valve_alarm = value === 1; - break; - case 105: - payload.battery = value; - break; - case 106: - // unidentified flag/enum, e.g. 0 - break; + case 3: + payload.device_temperature = value; + break; + case 5: + assertNumber(value); + payload.power_outage_count = value - 1; + break; + case 10: + // unidentified number, e.g. 32274, 3847 + break; + case 13: + assertNumber(value); + payload.firmware_version = trv.decodeFirmwareVersionString(value); + break; + case 17: + // unidentified flag/enum, e.g. 1 + break; + case 101: + assertNumber(value); + Object.assign(payload, trv.decodePreset(value)); + break; + case 102: + assertNumber(value); + payload.local_temperature = value / 100; + break; + case 103: + // This takes the following values: + // - `occupied_heating_setpoint` if `system_mode` is `heat` and `preset` is `manual` + // - `away_preset_temperature` if `system_mode` is `heat` and `preset` is `away` + // - `5` if `system_mode` is `off` + // It thus behaves similar to `occupied_heating_setpoint` except in `off` mode. Due to this difference, + // this value is written to another property to avoid an inconsistency of the `occupied_heating_setpoint`. + // TODO How to handle this value? Find better name? + assertNumber(value); + payload.internal_heating_setpoint = value / 100; + break; + case 104: + payload.valve_alarm = value === 1; + break; + case 105: + payload.battery = value; + break; + case 106: + // unidentified flag/enum, e.g. 0 + break; } }); @@ -1238,7 +1324,7 @@ export const trv = { const isNextDay = time < previousTime; if (isNextDay) { - return (fullDay - previousTime) + time; + return fullDay - previousTime + time; } else { return time - previousTime; } @@ -1286,9 +1372,9 @@ export const trv = { const stringifiedScheduleFragments = [schedule.days.join(stringifiedScheduleValueSeparator)]; for (const event of schedule.events) { - const formattedTemperature = Number.isInteger(event.temperature) ? - event.temperature.toFixed(1) : // add ".0" for usability to signal that floats can be used - String(event.temperature); + const formattedTemperature = Number.isInteger(event.temperature) + ? event.temperature.toFixed(1) // add ".0" for usability to signal that floats can be used + : String(event.temperature); const entryFragments = [formatTime(event.time), formattedTemperature]; @@ -1329,14 +1415,24 @@ const manufacturerOptions = { }; export const lumiModernExtend = { - lumiLight: (args?: Omit & {colorTemp?: true, powerOutageMemory?: 'switch' | 'light' | 'enum', - deviceTemperature?: boolean, powerOutageCount?: boolean}) => { + lumiLight: ( + args?: Omit & { + colorTemp?: true; + powerOutageMemory?: 'switch' | 'light' | 'enum'; + deviceTemperature?: boolean; + powerOutageCount?: boolean; + }, + ) => { args = {powerOutageCount: true, deviceTemperature: true, ...args}; - const colorTemp: {range: Range, startup: boolean} = args.colorTemp ? {startup: false, range: [153, 370]} : undefined; + const colorTemp: {range: Range; startup: boolean} = args.colorTemp ? {startup: false, range: [153, 370]} : undefined; const result = modernExtend.light({effect: false, powerOnBehavior: false, ...args, colorTemp}); result.fromZigbee.push( - fromZigbee.lumi_bulb_interval, fz.ignore_occupancy_report, fz.ignore_humidity_report, - fz.ignore_pressure_report, fz.ignore_temperature_report, fromZigbee.lumi_specific, + fromZigbee.lumi_bulb_interval, + fz.ignore_occupancy_report, + fz.ignore_humidity_report, + fz.ignore_pressure_report, + fz.ignore_temperature_report, + fromZigbee.lumi_specific, ); if (args.powerOutageCount) result.exposes.push(e.power_outage_count()); @@ -1349,14 +1445,14 @@ export const lumiModernExtend = { result.toZigbee.push(toZigbee.lumi_light_power_outage_memory); result.exposes.push(e.power_outage_memory().withAccess(ea.STATE_SET)); } else if (args.powerOutageMemory === 'enum') { - const extend = lumiModernExtend.lumiPowerOnBehavior({lookup: {'on': 0, 'previous': 1, 'off': 2}}); + const extend = lumiModernExtend.lumiPowerOnBehavior({lookup: {on: 0, previous: 1, off: 2}}); result.toZigbee.push(...extend.toZigbee); result.exposes.push(...extend.exposes); } return result; }, - lumiOnOff: (args?: modernExtend.OnOffArgs & {operationMode?: boolean, powerOutageMemory?: 'binary' | 'enum', lockRelay?: boolean}) => { + lumiOnOff: (args?: modernExtend.OnOffArgs & {operationMode?: boolean; powerOutageMemory?: 'binary' | 'enum'; lockRelay?: boolean}) => { args = {operationMode: false, lockRelay: false, ...args}; const result = modernExtend.onOff({powerOnBehavior: false, ...args}); result.fromZigbee.push(fromZigbee.lumi_specific); @@ -1373,7 +1469,7 @@ export const lumiModernExtend = { if (args.operationMode === true) { const extend = lumiModernExtend.lumiOperationMode({description: 'Decoupled mode for a button'}); if (args.endpointNames) { - args.endpointNames.forEach(function(ep) { + args.endpointNames.forEach(function (ep) { const epExtend = lumiModernExtend.lumiOperationMode({ description: 'Decoupled mode for ' + ep.toString() + ' button', endpointName: ep, @@ -1389,7 +1485,7 @@ export const lumiModernExtend = { if (args.lockRelay) { const extend = lumiModernExtend.lumiLockRelay(); if (args.endpointNames) { - args.endpointNames.forEach(function(ep) { + args.endpointNames.forEach(function (ep) { const epExtend = lumiModernExtend.lumiLockRelay({ description: 'Locks ' + ep.toString() + ' relay and prevents it from operating', endpointName: ep, @@ -1404,142 +1500,157 @@ export const lumiModernExtend = { } return result; }, - lumiSwitchType: (args?: Partial) => modernExtend.enumLookup({ - name: 'switch_type', - lookup: {'toggle': 1, 'momentary': 2, 'none': 3}, - cluster: 'manuSpecificLumi', - attribute: {ID: 0x000a, type: 0x20}, - description: 'External switch type', - entityCategory: 'config', - zigbeeCommandOptions: {manufacturerCode}, - ...args, - }), - lumiMotorSpeed: (args?: Partial) => modernExtend.enumLookup({ - name: 'motor_speed', - lookup: {'low': 0, 'medium': 1, 'high': 2}, - cluster: 'manuSpecificLumi', - attribute: {ID: 0x0408, type: 0x20}, - description: 'Controls the motor speed', - entityCategory: 'config', - zigbeeCommandOptions: {manufacturerCode}, - ...args, - }), - lumiPowerOnBehavior: (args?: Partial) => modernExtend.enumLookup({ - name: 'power_on_behavior', - lookup: {'on': 0, 'previous': 1, 'off': 2, 'inverted': 3}, - cluster: 'manuSpecificLumi', - attribute: {ID: 0x0517, type: 0x20}, - description: 'Controls the behavior when the device is powered on after power loss', - entityCategory: 'config', - zigbeeCommandOptions: {manufacturerCode}, - ...args, - }), - lumiPowerOutageMemory: (args? :Partial) => modernExtend.binary({ - name: 'power_outage_memory', - cluster: 'manuSpecificLumi', - attribute: {ID: 0x0201, type: 0x10}, - valueOn: [true, 1], - valueOff: [false, 0], - description: 'Controls the behavior when the device is powered on after power loss', - access: 'ALL', - entityCategory: 'config', - zigbeeCommandOptions: {manufacturerCode}, - ...args, - }), - lumiOperationMode: (args?: Partial) => modernExtend.enumLookup({ - name: 'operation_mode', - lookup: {'decoupled': 0, 'control_relay': 1}, - cluster: 'manuSpecificLumi', - attribute: {ID: 0x0200, type: 0x20}, - description: 'Decoupled mode for relay', - entityCategory: 'config', - zigbeeCommandOptions: {manufacturerCode}, - ...args, - }), - lumiAction: (args?: Partial) => modernExtend.actionEnumLookup({ - actionLookup: {'single': 1}, - cluster: 'genMultistateInput', - attribute: 'presentValue', - ...args, - }), - lumiVoc: (args?: Partial) => modernExtend.numeric({ - name: 'voc', - cluster: 'genAnalogInput', - attribute: 'presentValue', - reporting: {min: '10_SECONDS', max: '1_HOUR', change: 5}, - description: 'Measured VOC value', - unit: 'ppb', - access: 'STATE_GET', - ...args, - }), - lumiAirQuality: (args?: Partial) => modernExtend.enumLookup({ - name: 'air_quality', - lookup: {'excellent': 1, 'good': 2, 'moderate': 3, 'poor': 4, 'unhealthy': 5, 'unknown': 0}, - cluster: 'manuSpecificLumi', - attribute: 'airQuality', - zigbeeCommandOptions: {disableDefaultResponse: true}, - description: 'Measured air quality', - access: 'STATE_GET', - ...args, - }), - lumiDisplayUnit: (args?: Partial) => modernExtend.enumLookup({ - name: 'display_unit', - lookup: { - 'mgm3_celsius': 0x00, // mg/m³, °C (default) - 'ppb_celsius': 0x01, // ppb, °C - 'mgm3_fahrenheit': 0x10, // mg/m³, °F - 'ppb_fahrenheit': 0x11, // ppb, °F - }, - cluster: 'manuSpecificLumi', - attribute: 'displayUnit', - zigbeeCommandOptions: {disableDefaultResponse: true}, - description: 'Units to show on the display', - entityCategory: 'config', - ...args, - }), - lumiOutageCountRestoreBindReporting: (): ModernExtend => { - const fromZigbee: Fz.Converter[] = [{ + lumiSwitchType: (args?: Partial) => + modernExtend.enumLookup({ + name: 'switch_type', + lookup: {toggle: 1, momentary: 2, none: 3}, + cluster: 'manuSpecificLumi', + attribute: {ID: 0x000a, type: 0x20}, + description: 'External switch type', + entityCategory: 'config', + zigbeeCommandOptions: {manufacturerCode}, + ...args, + }), + lumiMotorSpeed: (args?: Partial) => + modernExtend.enumLookup({ + name: 'motor_speed', + lookup: {low: 0, medium: 1, high: 2}, + cluster: 'manuSpecificLumi', + attribute: {ID: 0x0408, type: 0x20}, + description: 'Controls the motor speed', + entityCategory: 'config', + zigbeeCommandOptions: {manufacturerCode}, + ...args, + }), + lumiPowerOnBehavior: (args?: Partial) => + modernExtend.enumLookup({ + name: 'power_on_behavior', + lookup: {on: 0, previous: 1, off: 2, inverted: 3}, + cluster: 'manuSpecificLumi', + attribute: {ID: 0x0517, type: 0x20}, + description: 'Controls the behavior when the device is powered on after power loss', + entityCategory: 'config', + zigbeeCommandOptions: {manufacturerCode}, + ...args, + }), + lumiPowerOutageMemory: (args?: Partial) => + modernExtend.binary({ + name: 'power_outage_memory', + cluster: 'manuSpecificLumi', + attribute: {ID: 0x0201, type: 0x10}, + valueOn: [true, 1], + valueOff: [false, 0], + description: 'Controls the behavior when the device is powered on after power loss', + access: 'ALL', + entityCategory: 'config', + zigbeeCommandOptions: {manufacturerCode}, + ...args, + }), + lumiOperationMode: (args?: Partial) => + modernExtend.enumLookup({ + name: 'operation_mode', + lookup: {decoupled: 0, control_relay: 1}, + cluster: 'manuSpecificLumi', + attribute: {ID: 0x0200, type: 0x20}, + description: 'Decoupled mode for relay', + entityCategory: 'config', + zigbeeCommandOptions: {manufacturerCode}, + ...args, + }), + lumiAction: (args?: Partial) => + modernExtend.actionEnumLookup({ + actionLookup: {single: 1}, + cluster: 'genMultistateInput', + attribute: 'presentValue', + ...args, + }), + lumiVoc: (args?: Partial) => + modernExtend.numeric({ + name: 'voc', + cluster: 'genAnalogInput', + attribute: 'presentValue', + reporting: {min: '10_SECONDS', max: '1_HOUR', change: 5}, + description: 'Measured VOC value', + unit: 'ppb', + access: 'STATE_GET', + ...args, + }), + lumiAirQuality: (args?: Partial) => + modernExtend.enumLookup({ + name: 'air_quality', + lookup: {excellent: 1, good: 2, moderate: 3, poor: 4, unhealthy: 5, unknown: 0}, + cluster: 'manuSpecificLumi', + attribute: 'airQuality', + zigbeeCommandOptions: {disableDefaultResponse: true}, + description: 'Measured air quality', + access: 'STATE_GET', + ...args, + }), + lumiDisplayUnit: (args?: Partial) => + modernExtend.enumLookup({ + name: 'display_unit', + lookup: { + mgm3_celsius: 0x00, // mg/m³, °C (default) + ppb_celsius: 0x01, // ppb, °C + mgm3_fahrenheit: 0x10, // mg/m³, °F + ppb_fahrenheit: 0x11, // ppb, °F + }, cluster: 'manuSpecificLumi', - type: ['attributeReport', 'readResponse'], - convert: async (model, msg, publish, options, meta) => { - // At least the Aqara TVOC sensor does not send a deviceAnnounce after comming back online. - // The reconfigureReportingsOnDeviceAnnounce modernExtend is not usable because of this, - // there is however an outage counter published in the 'special' buffer data reported - // under the manuSpecificLumi cluster as attribute 247, we simple decode and grab value with ID 5. - // Normal attribute publishing and decoding will be left to the classic fromZigbee or modernExtends. - if (msg.data.hasOwnProperty('247')) { - const dataDecoded = buffer2DataObject(model, msg.data['247']); - if (dataDecoded.hasOwnProperty('5')) { - assertNumber(dataDecoded['5']); - - const currentOutageCount = dataDecoded['5'] - 1; - const previousOutageCount = meta.device?.meta?.outageCount ? meta.device.meta.outageCount : 0; - - if (currentOutageCount > previousOutageCount) { - logger.debug('Restoring binding and reporting, device came back after losing power.', NS); - for (const endpoint of meta.device.endpoints) { - // restore bindings - for (const b of endpoint.binds) { - await endpoint.bind(b.cluster.name, b.target); + attribute: 'displayUnit', + zigbeeCommandOptions: {disableDefaultResponse: true}, + description: 'Units to show on the display', + entityCategory: 'config', + ...args, + }), + lumiOutageCountRestoreBindReporting: (): ModernExtend => { + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'manuSpecificLumi', + type: ['attributeReport', 'readResponse'], + convert: async (model, msg, publish, options, meta) => { + // At least the Aqara TVOC sensor does not send a deviceAnnounce after comming back online. + // The reconfigureReportingsOnDeviceAnnounce modernExtend is not usable because of this, + // there is however an outage counter published in the 'special' buffer data reported + // under the manuSpecificLumi cluster as attribute 247, we simple decode and grab value with ID 5. + // Normal attribute publishing and decoding will be left to the classic fromZigbee or modernExtends. + if (msg.data.hasOwnProperty('247')) { + const dataDecoded = buffer2DataObject(model, msg.data['247']); + if (dataDecoded.hasOwnProperty('5')) { + assertNumber(dataDecoded['5']); + + const currentOutageCount = dataDecoded['5'] - 1; + const previousOutageCount = meta.device?.meta?.outageCount ? meta.device.meta.outageCount : 0; + + if (currentOutageCount > previousOutageCount) { + logger.debug('Restoring binding and reporting, device came back after losing power.', NS); + for (const endpoint of meta.device.endpoints) { + // restore bindings + for (const b of endpoint.binds) { + await endpoint.bind(b.cluster.name, b.target); + } + + // restore reporting + for (const c of endpoint.configuredReportings) { + await endpoint.configureReporting(c.cluster.name, [ + { + attribute: c.attribute.name, + minimumReportInterval: c.minimumReportInterval, + maximumReportInterval: c.maximumReportInterval, + reportableChange: c.reportableChange, + }, + ]); + } } - // restore reporting - for (const c of endpoint.configuredReportings) { - await endpoint.configureReporting(c.cluster.name, [{ - attribute: c.attribute.name, minimumReportInterval: c.minimumReportInterval, - maximumReportInterval: c.maximumReportInterval, reportableChange: c.reportableChange, - }]); - } + // update outageCount in database + meta.device.meta.outageCount = currentOutageCount; + meta.device.save(); } - - // update outageCount in database - meta.device.meta.outageCount = currentOutageCount; - meta.device.save(); } } - } + }, }, - }]; + ]; return {fromZigbee, isModernExtend: true}; }, @@ -1553,95 +1664,99 @@ export const lumiModernExtend = { result.ota = ota.zigbeeOTA; return result; }, - lumiPower: (args?: Partial) => modernExtend.numeric({ - name: 'power', - cluster: 'genAnalogInput', - attribute: 'presentValue', - reporting: {min: '10_SECONDS', max: '1_HOUR', change: 5}, - description: 'Instantaneous measured power', - unit: 'W', - access: 'STATE', - entityCategory: 'diagnostic', - zigbeeCommandOptions: {manufacturerCode}, - ...args, - }), + lumiPower: (args?: Partial) => + modernExtend.numeric({ + name: 'power', + cluster: 'genAnalogInput', + attribute: 'presentValue', + reporting: {min: '10_SECONDS', max: '1_HOUR', change: 5}, + description: 'Instantaneous measured power', + unit: 'W', + access: 'STATE', + entityCategory: 'diagnostic', + zigbeeCommandOptions: {manufacturerCode}, + ...args, + }), lumiElectricityMeter: (): ModernExtend => { - const exposes = [ - e.energy(), - e.voltage(), - e.current(), - ]; - const fromZigbee: Fz.Converter[] = [{ - cluster: 'manuSpecificLumi', - type: ['attributeReport', 'readResponse'], - convert: async (model, msg, publish, options, meta) => { - return await numericAttributes2Payload(msg, meta, model, options, msg.data); + const exposes = [e.energy(), e.voltage(), e.current()]; + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'manuSpecificLumi', + type: ['attributeReport', 'readResponse'], + convert: async (model, msg, publish, options, meta) => { + return await numericAttributes2Payload(msg, meta, model, options, msg.data); + }, }, - }]; + ]; return {exposes, fromZigbee, isModernExtend: true}; }, - lumiOverloadProtection: (args?: Partial) => modernExtend.numeric({ - name: 'overload_protection', - cluster: 'manuSpecificLumi', - attribute: {ID: 0x020b, type: 0x39}, - description: 'Maximum allowed load, turns off if exceeded', - valueMin: 100, - valueMax: 3840, - unit: 'W', - access: 'ALL', - entityCategory: 'config', - zigbeeCommandOptions: {manufacturerCode}, - ...args, - }), - lumiLedIndicator: (args? :Partial) => modernExtend.binary({ - name: 'led_indicator', - cluster: 'manuSpecificLumi', - attribute: {ID: 0x0203, type: 0x10}, - valueOn: ['ON', 1], - valueOff: ['OFF', 0], - description: 'LED indicator', - access: 'ALL', - entityCategory: 'config', - zigbeeCommandOptions: {manufacturerCode}, - ...args, - }), - lumiLedDisabledNight: (args? :Partial) => modernExtend.binary({ - name: 'led_disabled_night', - cluster: 'manuSpecificLumi', - attribute: {ID: 0x0203, type: 0x10}, - valueOn: [true, 1], - valueOff: [false, 0], - description: 'Enables/disables LED indicator at night', - access: 'ALL', - entityCategory: 'config', - zigbeeCommandOptions: {manufacturerCode}, - ...args, - }), - lumiButtonLock: (args? :Partial) => modernExtend.binary({ - name: 'button_lock', - cluster: 'manuSpecificLumi', - attribute: {ID: 0x0200, type: 0x20}, - valueOn: ['ON', 0], - valueOff: ['OFF', 1], - description: 'Disables the physical switch button', - access: 'ALL', - entityCategory: 'config', - zigbeeCommandOptions: {manufacturerCode}, - ...args, - }), - lumiFlipIndicatorLight: (args? :Partial) => modernExtend.binary({ - name: 'flip_indicator_light', - cluster: 'manuSpecificLumi', - attribute: {ID: 0x00F0, type: 0x20}, - valueOn: ['ON', 1], - valueOff: ['OFF', 0], - description: 'After turn on, the indicator light turns on while switch is off, and vice versa', - access: 'ALL', - entityCategory: 'config', - zigbeeCommandOptions: {manufacturerCode}, - ...args, - }), + lumiOverloadProtection: (args?: Partial) => + modernExtend.numeric({ + name: 'overload_protection', + cluster: 'manuSpecificLumi', + attribute: {ID: 0x020b, type: 0x39}, + description: 'Maximum allowed load, turns off if exceeded', + valueMin: 100, + valueMax: 3840, + unit: 'W', + access: 'ALL', + entityCategory: 'config', + zigbeeCommandOptions: {manufacturerCode}, + ...args, + }), + lumiLedIndicator: (args?: Partial) => + modernExtend.binary({ + name: 'led_indicator', + cluster: 'manuSpecificLumi', + attribute: {ID: 0x0203, type: 0x10}, + valueOn: ['ON', 1], + valueOff: ['OFF', 0], + description: 'LED indicator', + access: 'ALL', + entityCategory: 'config', + zigbeeCommandOptions: {manufacturerCode}, + ...args, + }), + lumiLedDisabledNight: (args?: Partial) => + modernExtend.binary({ + name: 'led_disabled_night', + cluster: 'manuSpecificLumi', + attribute: {ID: 0x0203, type: 0x10}, + valueOn: [true, 1], + valueOff: [false, 0], + description: 'Enables/disables LED indicator at night', + access: 'ALL', + entityCategory: 'config', + zigbeeCommandOptions: {manufacturerCode}, + ...args, + }), + lumiButtonLock: (args?: Partial) => + modernExtend.binary({ + name: 'button_lock', + cluster: 'manuSpecificLumi', + attribute: {ID: 0x0200, type: 0x20}, + valueOn: ['ON', 0], + valueOff: ['OFF', 1], + description: 'Disables the physical switch button', + access: 'ALL', + entityCategory: 'config', + zigbeeCommandOptions: {manufacturerCode}, + ...args, + }), + lumiFlipIndicatorLight: (args?: Partial) => + modernExtend.binary({ + name: 'flip_indicator_light', + cluster: 'manuSpecificLumi', + attribute: {ID: 0x00f0, type: 0x20}, + valueOn: ['ON', 1], + valueOff: ['OFF', 0], + description: 'After turn on, the indicator light turns on while switch is off, and vice versa', + access: 'ALL', + entityCategory: 'config', + zigbeeCommandOptions: {manufacturerCode}, + ...args, + }), lumiPreventReset: (): ModernExtend => { const onEvent: OnEvent = async (type, data, device) => { if ( @@ -1651,7 +1766,7 @@ export const lumiModernExtend = { data.cluster !== 'genBasic' || !data.data[0xfff0] || // eg: [0xaa, 0x10, 0x05, 0x41, 0x87, 0x01, 0x01, 0x10, 0x00] - !data.data[0xFFF0].slice(0, 5).equals(Buffer.from([0xaa, 0x10, 0x05, 0x41, 0x87])) + !data.data[0xfff0].slice(0, 5).equals(Buffer.from([0xaa, 0x10, 0x05, 0x41, 0x87])) ) { return; } @@ -1665,40 +1780,44 @@ export const lumiModernExtend = { }; return {onEvent, isModernExtend: true}; }, - lumiClickMode: (args?: Partial) => modernExtend.enumLookup({ - name: 'click_mode', - lookup: {'fast': 1, 'multi': 2}, - cluster: 'manuSpecificLumi', - attribute: {ID: 0x0125, type: 0x20}, - description: 'Click mode for wireless button. fast: only supports single click but allows faster reponse time.' + - 'multi: supports multiple types of clicks but is slower, because it awaits multiple clicks.', - entityCategory: 'config', - zigbeeCommandOptions: {manufacturerCode}, - ...args, - }), - lumiSlider: (): ModernExtend => { - const fromZigbee: Fz.Converter[] = [{ + lumiClickMode: (args?: Partial) => + modernExtend.enumLookup({ + name: 'click_mode', + lookup: {fast: 1, multi: 2}, cluster: 'manuSpecificLumi', - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - if (msg.data.hasOwnProperty(652)) { - const actionLookup: KeyValueNumberString = { - 1: 'slider_single', - 2: 'slider_double', - 3: 'slider_hold', - 4: 'slider_up', - 5: 'slider_down', - }; - return { - action_slide_time: msg.data[561], - action_slide_speed: msg.data[562], - action_slide_relative_displacement: msg.data[563], - action: actionLookup[msg.data[652]], - action_slide_time_delta: msg.data[769], - }; - } + attribute: {ID: 0x0125, type: 0x20}, + description: + 'Click mode for wireless button. fast: only supports single click but allows faster reponse time.' + + 'multi: supports multiple types of clicks but is slower, because it awaits multiple clicks.', + entityCategory: 'config', + zigbeeCommandOptions: {manufacturerCode}, + ...args, + }), + lumiSlider: (): ModernExtend => { + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'manuSpecificLumi', + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + if (msg.data.hasOwnProperty(652)) { + const actionLookup: KeyValueNumberString = { + 1: 'slider_single', + 2: 'slider_double', + 3: 'slider_hold', + 4: 'slider_up', + 5: 'slider_down', + }; + return { + action_slide_time: msg.data[561], + action_slide_speed: msg.data[562], + action_slide_relative_displacement: msg.data[563], + action: actionLookup[msg.data[652]], + action_slide_time_delta: msg.data[769], + }; + } + }, }, - }]; + ]; const exposes: Expose[] = [ e.numeric('action_slide_time', ea.STATE).withUnit('ms').withCategory('diagnostic'), @@ -1710,18 +1829,19 @@ export const lumiModernExtend = { return {fromZigbee, exposes, isModernExtend: true}; }, - lumiLockRelay: (args? :Partial) => modernExtend.binary({ - name: 'lock_relay', - cluster: 'manuSpecificLumi', - attribute: {ID: 0x0285, type: 0x20}, - valueOn: [true, 1], - valueOff: [false, 0], - description: 'Locks relay and prevents it from operating', - access: 'ALL', - entityCategory: 'config', - zigbeeCommandOptions: {manufacturerCode}, - ...args, - }), + lumiLockRelay: (args?: Partial) => + modernExtend.binary({ + name: 'lock_relay', + cluster: 'manuSpecificLumi', + attribute: {ID: 0x0285, type: 0x20}, + valueOn: [true, 1], + valueOff: [false, 0], + description: 'Locks relay and prevents it from operating', + access: 'ALL', + entityCategory: 'config', + zigbeeCommandOptions: {manufacturerCode}, + ...args, + }), lumiSetEventMode: (): ModernExtend => { // I have no idea, why it is used everywhere, even if not supported // modes: @@ -1729,44 +1849,46 @@ export const lumiModernExtend = { // 1 - 'event' mode. keys send events. useful for handling const configure: Configure[] = [ async (device, coordinatorEndpoint, definition) => { - await device.getEndpoint(1).write('manuSpecificLumi', {'mode': 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); + await device.getEndpoint(1).write('manuSpecificLumi', {mode: 1}, {manufacturerCode: manufacturerCode, disableResponse: true}); }, ]; return {configure, isModernExtend: true}; }, - lumiSwitchMode: (args?: Partial) => modernExtend.enumLookup({ - name: 'mode_switch', - lookup: {'quick_mode': 1, 'anti_flicker_mode': 4}, - cluster: 'manuSpecificLumi', - attribute: {ID: 0x0004, type: 0x21}, - description: 'Anti flicker mode can be used to solve blinking issues of some lights.' + - 'Quick mode makes the device respond faster.', - entityCategory: 'config', - zigbeeCommandOptions: {manufacturerCode}, - ...args, - }), + lumiSwitchMode: (args?: Partial) => + modernExtend.enumLookup({ + name: 'mode_switch', + lookup: {quick_mode: 1, anti_flicker_mode: 4}, + cluster: 'manuSpecificLumi', + attribute: {ID: 0x0004, type: 0x21}, + description: 'Anti flicker mode can be used to solve blinking issues of some lights.' + 'Quick mode makes the device respond faster.', + entityCategory: 'config', + zigbeeCommandOptions: {manufacturerCode}, + ...args, + }), lumiVibration: (): ModernExtend => { const exposes: Expose[] = [e.action(['shake', 'triple_strike'])]; - const fromZigbee: Fz.Converter[] = [{ - cluster: 'ssIasZone', - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - if (msg.data.hasOwnProperty(45)) { - const zoneStatus = msg.data[45]; - const actionLookup: KeyValueNumberString = {1: 'shake', 2: 'triple_strike'}; - return {action: actionLookup[zoneStatus]}; - } + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'ssIasZone', + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + if (msg.data.hasOwnProperty(45)) { + const zoneStatus = msg.data[45]; + const actionLookup: KeyValueNumberString = {1: 'shake', 2: 'triple_strike'}; + return {action: actionLookup[zoneStatus]}; + } + }, }, - }]; + ]; return {exposes, fromZigbee, isModernExtend: true}; }, lumiMiscellaneous: (args?: { - cluster: 'genBasic' | 'manuSpecificLumi', - deviceTemperatureAttribute?: number, - powerOutageCountAttribute?: number, - resetsWhenPairing?: boolean, + cluster: 'genBasic' | 'manuSpecificLumi'; + deviceTemperatureAttribute?: number; + powerOutageCountAttribute?: number; + resetsWhenPairing?: boolean; }): ModernExtend => { args = {cluster: 'manuSpecificLumi', deviceTemperatureAttribute: 3, powerOutageCountAttribute: 5, resetsWhenPairing: false, ...args}; const exposes: Expose[] = [e.device_temperature(), e.power_outage_count(args.resetsWhenPairing)]; @@ -1797,67 +1919,72 @@ export const lumiModernExtend = { lumiKnobRotation: (): ModernExtend => { const exposes: Expose[] = [ e.action(['start_rotating', 'rotation', 'stop_rotating']), - e.enum('action_rotation_button_state', ea.STATE, ['released', 'pressed']) - .withDescription('Button state during rotation').withCategory('diagnostic'), - e.numeric('action_rotation_angle', ea.STATE) - .withUnit('*').withDescription('Rotation angle').withCategory('diagnostic'), - e.numeric('action_rotation_angle_speed', ea.STATE) - .withUnit('*').withDescription('Rotation angle speed').withCategory('diagnostic'), - e.numeric('action_rotation_percent', ea.STATE).withUnit('%') - .withDescription('Rotation percent').withCategory('diagnostic'), - e.numeric('action_rotation_percent_speed', ea.STATE) - .withUnit('%').withDescription('Rotation percent speed').withCategory('diagnostic'), - e.numeric('action_rotation_time', ea.STATE) - .withUnit('ms').withDescription('Rotation time').withCategory('diagnostic'), + e + .enum('action_rotation_button_state', ea.STATE, ['released', 'pressed']) + .withDescription('Button state during rotation') + .withCategory('diagnostic'), + e.numeric('action_rotation_angle', ea.STATE).withUnit('*').withDescription('Rotation angle').withCategory('diagnostic'), + e.numeric('action_rotation_angle_speed', ea.STATE).withUnit('*').withDescription('Rotation angle speed').withCategory('diagnostic'), + e.numeric('action_rotation_percent', ea.STATE).withUnit('%').withDescription('Rotation percent').withCategory('diagnostic'), + e.numeric('action_rotation_percent_speed', ea.STATE).withUnit('%').withDescription('Rotation percent speed').withCategory('diagnostic'), + e.numeric('action_rotation_time', ea.STATE).withUnit('ms').withDescription('Rotation time').withCategory('diagnostic'), ]; - const fromZigbee: Fz.Converter[] = [{ - cluster: 'manuSpecificLumi', - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - if (msg.data.hasOwnProperty(570)) { - const act: KeyValueNumberString = {1: 'start_rotating', 2: 'rotation', 3: 'stop_rotating'}; - const state: KeyValueNumberString = {0: 'released', 128: 'pressed'}; - return { - action: act[msg.data[570] & ~128], - action_rotation_button_state: state[msg.data[570] & 128], - action_rotation_angle: msg.data[558], - action_rotation_angle_speed: msg.data[560], - action_rotation_percent: msg.data[563], - action_rotation_percent_speed: msg.data[562], - action_rotation_time: msg.data[561], - }; - } + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'manuSpecificLumi', + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + if (msg.data.hasOwnProperty(570)) { + const act: KeyValueNumberString = {1: 'start_rotating', 2: 'rotation', 3: 'stop_rotating'}; + const state: KeyValueNumberString = {0: 'released', 128: 'pressed'}; + return { + action: act[msg.data[570] & ~128], + action_rotation_button_state: state[msg.data[570] & 128], + action_rotation_angle: msg.data[558], + action_rotation_angle_speed: msg.data[560], + action_rotation_percent: msg.data[563], + action_rotation_percent_speed: msg.data[562], + action_rotation_time: msg.data[561], + }; + } + }, }, - }]; + ]; return {exposes, fromZigbee, isModernExtend: true}; }, lumiCommandMode: (args?: {setEventMode: boolean}): ModernExtend => { args = {setEventMode: true, ...args}; const exposes: Expose[] = [ - e.enum('operation_mode', ea.ALL, ['event', 'command']) + e + .enum('operation_mode', ea.ALL, ['event', 'command']) .withDescription('Command mode is usefull for binding. Event mode is usefull for processing.'), ]; - const toZigbee: Tz.Converter[] = [{ - key: ['operation_mode'], - convertSet: async (entity, key, value, meta) => { - assertString(value); - // modes: - // 0 - 'command' mode. keys send commands. useful for binding - // 1 - 'event' mode. keys send events. useful for handling - const lookup = {command: 0, event: 1}; - const endpoint = meta.device.getEndpoint(1); - await endpoint.write('manuSpecificLumi', {'mode': getFromLookup(value.toLowerCase(), lookup)}, - {manufacturerCode: manufacturerOptions.lumi.manufacturerCode}); - return {state: {operation_mode: value.toLowerCase()}}; - }, - convertGet: async (entity, key, meta) => { - const endpoint = meta.device.getEndpoint(1); - await endpoint.read('manuSpecificLumi', ['mode'], {manufacturerCode: manufacturerOptions.lumi.manufacturerCode}); + const toZigbee: Tz.Converter[] = [ + { + key: ['operation_mode'], + convertSet: async (entity, key, value, meta) => { + assertString(value); + // modes: + // 0 - 'command' mode. keys send commands. useful for binding + // 1 - 'event' mode. keys send events. useful for handling + const lookup = {command: 0, event: 1}; + const endpoint = meta.device.getEndpoint(1); + await endpoint.write( + 'manuSpecificLumi', + {mode: getFromLookup(value.toLowerCase(), lookup)}, + {manufacturerCode: manufacturerOptions.lumi.manufacturerCode}, + ); + return {state: {operation_mode: value.toLowerCase()}}; + }, + convertGet: async (entity, key, meta) => { + const endpoint = meta.device.getEndpoint(1); + await endpoint.read('manuSpecificLumi', ['mode'], {manufacturerCode: manufacturerOptions.lumi.manufacturerCode}); + }, }, - }]; + ]; const result: ModernExtend = {exposes, toZigbee, isModernExtend: true}; if (args.setEventMode) { @@ -1867,10 +1994,10 @@ export const lumiModernExtend = { return result; }, lumiBattery: (args?: { - cluster?: 'genBasic' | 'manuSpecificLumi', - voltageToPercentage?: string | {min: number, max: number}, - percentageAtrribute?: number, - voltageAttribute?: number, + cluster?: 'genBasic' | 'manuSpecificLumi'; + voltageToPercentage?: string | {min: number; max: number}; + percentageAtrribute?: number; + voltageAttribute?: number; }): ModernExtend => { args = { cluster: 'manuSpecificLumi', @@ -2013,8 +2140,10 @@ export const fromZigbee = { if (['WXKG02LM_rev1', 'WXKG02LM_rev2', 'WXKG07LM'].includes(model.model)) mapping = {1: 'left', 2: 'right', 3: 'both'}; // Maybe other QKBG also support release/hold? - const actionLookup: KeyValueAny = !isLegacyEnabled(options) && ['QBKG03LM', 'QBKG22LM', 'QBKG04LM', 'QBKG21LM'].includes(model.model) ? - {0: 'hold', 1: 'release', 2: 'double'} : {0: 'single', 1: 'single'}; + const actionLookup: KeyValueAny = + !isLegacyEnabled(options) && ['QBKG03LM', 'QBKG22LM', 'QBKG04LM', 'QBKG21LM'].includes(model.model) + ? {0: 'hold', 1: 'release', 2: 'double'} + : {0: 'single', 1: 'single'}; const action = actionLookup[msg.data['onOff']]; const button = mapping && mapping[msg.endpoint.ID] ? `_${mapping[msg.endpoint.ID]}` : ''; @@ -2063,13 +2192,18 @@ export const fromZigbee = { else if (value === 1) result = {action: 'throw'}; else if (value === 2) result = {action: 'wakeup'}; else if (value === 3) result = {action: 'fall'}; - else if (value >= 512) result = {action: 'tap', side: value-512}; - else if (value >= 256) result = {action: 'slide', side: value-256}; - else if (value >= 128) result = {action: 'flip180', side: value-128}; + else if (value >= 512) result = {action: 'tap', side: value - 512}; + else if (value >= 256) result = {action: 'slide', side: value - 256}; + else if (value >= 128) result = {action: 'flip180', side: value - 128}; else if (value >= 64) { result = { - action: 'flip90', action_from_side: Math.floor((value-64) / 8), action_to_side: value % 8, action_side: value % 8, - from_side: Math.floor((value-64) / 8), to_side: value % 8, side: value % 8, + action: 'flip90', + action_from_side: Math.floor((value - 64) / 8), + action_to_side: value % 8, + action_side: value % 8, + from_side: Math.floor((value - 64) / 8), + to_side: value % 8, + side: value % 8, }; } @@ -2092,12 +2226,14 @@ export const fromZigbee = { else if (value >= 256) payload = {action: 'slide', side: value - 255}; else if (value >= 128) { payload = { - action: 'flip180', side: value - 127, + action: 'flip180', + side: value - 127, action_from_side: 7 - value + 127, }; } else if (value >= 64) { payload = { - action: 'flip90', side: value % 8 + 1, + action: 'flip90', + side: (value % 8) + 1, action_from_side: Math.floor((value - 64) / 8) + 1, }; } else { @@ -2122,14 +2258,19 @@ export const fromZigbee = { buttonLookup = {1: 'left', 2: 'right', 3: 'both'}; } if (['QBKG12LM', 'QBKG24LM'].includes(model.model)) buttonLookup = {5: 'left', 6: 'right', 7: 'both'}; - if (['QBKG39LM', 'QBKG41LM', 'WS-EUK02', 'WS-EUK04', 'QBKG18LM', 'QBKG20LM', 'QBKG28LM', 'QBKG31LM', 'ZNQBKG25LM'] - .includes(model.model)) { + if ( + ['QBKG39LM', 'QBKG41LM', 'WS-EUK02', 'WS-EUK04', 'QBKG18LM', 'QBKG20LM', 'QBKG28LM', 'QBKG31LM', 'ZNQBKG25LM'].includes(model.model) + ) { buttonLookup = {41: 'left', 42: 'right', 51: 'both'}; } if (['QBKG25LM', 'QBKG26LM', 'QBKG29LM', 'QBKG32LM', 'QBKG33LM', 'QBKG34LM', 'ZNQBKG31LM', 'ZNQBKG26LM'].includes(model.model)) { buttonLookup = { - 41: 'left', 42: 'center', 43: 'right', - 51: 'left_center', 52: 'left_right', 53: 'center_right', + 41: 'left', + 42: 'center', + 43: 'right', + 51: 'left_center', + 52: 'left_right', + 53: 'center_right', 61: 'all', }; } @@ -2233,85 +2374,88 @@ export const fromZigbee = { const result: KeyValue = {}; Object.entries(msg.data).forEach(([key, value]) => { switch (parseInt(key)) { - case 0xfff1: { - // @ts-expect-error - if (value.length < 8) { - logger.debug(`Cannot handle ${value}, frame too small`, 'zhc:lumi:feeder'); - return; - } - // @ts-expect-error - const attr = value.slice(3, 7); - // @ts-expect-error - const len = value.slice(7, 8).readUInt8(); - // @ts-expect-error - const val = value.slice(8, 8 + len); - switch (attr.readInt32BE()) { - case 0x04150055: // feeding - result['feed'] = ''; - break; - case 0x041502bc: { // feeding report - const report = val.toString(); - result['feeding_source'] = {0: 'schedule', 1: 'manual', 2: 'remote'}[parseInt(report.slice(0, 2))]; - result['feeding_size'] = parseInt(report.slice(3, 4)); - break; - } - case 0x0d680055: // portions per day - result['portions_per_day'] = val.readUInt16BE(); - break; - case 0x0d690055: // weight per day - result['weight_per_day'] = val.readUInt32BE(); - break; - case 0x0d0b0055: // error ? - result['error'] = getFromLookup(val.readUInt8(), {1: true, 0: false}); - break; - case 0x080008c8: { // schedule string - const schlist = val.toString().split(','); - const schedule: unknown[] = []; - schlist.forEach((str: string) => { // 7f13000100 - if (str !== '//') { - const feedtime = Buffer.from(str, 'hex'); - schedule.push({ - 'days': getFromLookup(feedtime[0], feederDaysLookup), - 'hour': feedtime[1], - 'minute': feedtime[2], - 'size': feedtime[3], + case 0xfff1: { + // @ts-expect-error + if (value.length < 8) { + logger.debug(`Cannot handle ${value}, frame too small`, 'zhc:lumi:feeder'); + return; + } + // @ts-expect-error + const attr = value.slice(3, 7); + // @ts-expect-error + const len = value.slice(7, 8).readUInt8(); + // @ts-expect-error + const val = value.slice(8, 8 + len); + switch (attr.readInt32BE()) { + case 0x04150055: // feeding + result['feed'] = ''; + break; + case 0x041502bc: { + // feeding report + const report = val.toString(); + result['feeding_source'] = {0: 'schedule', 1: 'manual', 2: 'remote'}[parseInt(report.slice(0, 2))]; + result['feeding_size'] = parseInt(report.slice(3, 4)); + break; + } + case 0x0d680055: // portions per day + result['portions_per_day'] = val.readUInt16BE(); + break; + case 0x0d690055: // weight per day + result['weight_per_day'] = val.readUInt32BE(); + break; + case 0x0d0b0055: // error ? + result['error'] = getFromLookup(val.readUInt8(), {1: true, 0: false}); + break; + case 0x080008c8: { + // schedule string + const schlist = val.toString().split(','); + const schedule: unknown[] = []; + schlist.forEach((str: string) => { + // 7f13000100 + if (str !== '//') { + const feedtime = Buffer.from(str, 'hex'); + schedule.push({ + days: getFromLookup(feedtime[0], feederDaysLookup), + hour: feedtime[1], + minute: feedtime[2], + size: feedtime[3], + }); + } }); + result['schedule'] = schedule; + break; } - }); - result['schedule'] = schedule; + case 0x04170055: // indicator + result['led_indicator'] = getFromLookup(val.readUInt8(), {1: 'ON', 0: 'OFF'}); + break; + case 0x04160055: // child lock + result['child_lock'] = getFromLookup(val.readUInt8(), {1: 'LOCK', 0: 'UNLOCK'}); + break; + case 0x04180055: // mode + result['mode'] = getFromLookup(val.readUInt8(), {1: 'schedule', 0: 'manual'}); + break; + case 0x0e5c0055: // serving size + result['serving_size'] = val.readUInt8(); + break; + case 0x0e5f0055: // portion weight + result['portion_weight'] = val.readUInt8(); + break; + case 0x080007d1: // ? 64 + case 0x0d090055: // ? 00 + logger.debug(`Unhandled attribute ${attr} = ${val}`, 'zhc:lumi:feeder'); + break; + default: + logger.debug(`Unknown attribute ${attr} = ${val}`, 'zhc:lumi:feeder'); + } break; } - case 0x04170055: // indicator - result['led_indicator'] = getFromLookup(val.readUInt8(), {1: 'ON', 0: 'OFF'}); - break; - case 0x04160055: // child lock - result['child_lock'] = getFromLookup(val.readUInt8(), {1: 'LOCK', 0: 'UNLOCK'}); - break; - case 0x04180055: // mode - result['mode'] = getFromLookup(val.readUInt8(), {1: 'schedule', 0: 'manual'}); - break; - case 0x0e5c0055: // serving size - result['serving_size'] = val.readUInt8(); - break; - case 0x0e5f0055: // portion weight - result['portion_weight'] = val.readUInt8(); - break; - case 0x080007d1: // ? 64 - case 0x0d090055: // ? 00 - logger.debug(`Unhandled attribute ${attr} = ${val}`, 'zhc:lumi:feeder'); + case 0x00ff: // 80:13:58:91:24:33:20:24:58:53:44:07:05:97:75:17 + case 0x0007: // 00:00:00:00:1d:b5:a6:ed + case 0x00f7: // 05:21:14:00:0d:23:21:25:00:00:09:21:00:01 + logger.debug(`Unhandled key ${key} = ${value}`, 'zhc:lumi:feeder'); break; default: - logger.debug(`Unknown attribute ${attr} = ${val}`, 'zhc:lumi:feeder'); - } - break; - } - case 0x00ff: // 80:13:58:91:24:33:20:24:58:53:44:07:05:97:75:17 - case 0x0007: // 00:00:00:00:1d:b5:a6:ed - case 0x00f7: // 05:21:14:00:0d:23:21:25:00:00:09:21:00:01 - logger.debug(`Unhandled key ${key} = ${value}`, 'zhc:lumi:feeder'); - break; - default: - logger.debug(`Unknown key ${key} = ${value}`, 'zhc:lumi:feeder'); + logger.debug(`Unknown key ${key} = ${value}`, 'zhc:lumi:feeder'); } }); return result; @@ -2324,87 +2468,87 @@ export const fromZigbee = { const result: KeyValue = {}; Object.entries(msg.data).forEach(([key, value]) => { switch (parseInt(key)) { - case 0x0271: - result['system_mode'] = getFromLookup(value, {1: 'heat', 0: 'off'}); - break; - case 0x0272: - // @ts-expect-error - Object.assign(result, trv.decodePreset(value)); - break; - case 0x0273: - result['window_detection'] = getFromLookup(value, {1: true, 0: false}); - break; - case 0x0274: - result['valve_detection'] = getFromLookup(value, {1: true, 0: false}); - break; - case 0x0277: - result['child_lock'] = getFromLookup(value, {1: true, 0: false}); - break; - case 0x0279: - assertNumber(value); - result['away_preset_temperature'] = (value / 100).toFixed(1); - break; - case 0x027b: - result['calibrated'] = getFromLookup(value, {1: true, 0: false}); - break; - case 0x027e: - result['sensor'] = getFromLookup(value, {1: 'external', 0: 'internal'}); - break; - case 0x040a: - result['battery'] = value; - break; - case 0x027a: - result['window_open'] = getFromLookup(value, {1: true, 0: false}); - break; - case 0x0275: - result['valve_alarm'] = getFromLookup(value, {1: true, 0: false}); - break; - case 247: { - // @ts-expect-error - const heartbeat = trv.decodeHeartbeat(meta, model, value); - - logger.debug(`${model.model}: Processed heartbeat message into payload ${JSON.stringify(heartbeat)}`, 'zhc:lumi:trv'); - - if (heartbeat.firmware_version) { - // Overwrite the "placeholder" version `0.0.0_0025` advertised by `genBasic` - // with the correct version from the heartbeat. - // This is not reflected in the frontend unless the device is reconfigured - // or the whole service restarted. - // See https://github.com/Koenkk/zigbee-herdsman-converters/pull/5363#discussion_r1081477047 + case 0x0271: + result['system_mode'] = getFromLookup(value, {1: 'heat', 0: 'off'}); + break; + case 0x0272: // @ts-expect-error - meta.device.softwareBuildID = heartbeat.firmware_version; - delete heartbeat.firmware_version; - } + Object.assign(result, trv.decodePreset(value)); + break; + case 0x0273: + result['window_detection'] = getFromLookup(value, {1: true, 0: false}); + break; + case 0x0274: + result['valve_detection'] = getFromLookup(value, {1: true, 0: false}); + break; + case 0x0277: + result['child_lock'] = getFromLookup(value, {1: true, 0: false}); + break; + case 0x0279: + assertNumber(value); + result['away_preset_temperature'] = (value / 100).toFixed(1); + break; + case 0x027b: + result['calibrated'] = getFromLookup(value, {1: true, 0: false}); + break; + case 0x027e: + result['sensor'] = getFromLookup(value, {1: 'external', 0: 'internal'}); + break; + case 0x040a: + result['battery'] = value; + break; + case 0x027a: + result['window_open'] = getFromLookup(value, {1: true, 0: false}); + break; + case 0x0275: + result['valve_alarm'] = getFromLookup(value, {1: true, 0: false}); + break; + case 247: { + // @ts-expect-error + const heartbeat = trv.decodeHeartbeat(meta, model, value); + + logger.debug(`${model.model}: Processed heartbeat message into payload ${JSON.stringify(heartbeat)}`, 'zhc:lumi:trv'); + + if (heartbeat.firmware_version) { + // Overwrite the "placeholder" version `0.0.0_0025` advertised by `genBasic` + // with the correct version from the heartbeat. + // This is not reflected in the frontend unless the device is reconfigured + // or the whole service restarted. + // See https://github.com/Koenkk/zigbee-herdsman-converters/pull/5363#discussion_r1081477047 + // @ts-expect-error + meta.device.softwareBuildID = heartbeat.firmware_version; + delete heartbeat.firmware_version; + } - Object.assign(result, heartbeat); - break; - } - case 0x027d: - result['schedule'] = getFromLookup(value, {1: true, 0: false}); - break; - case 0x0276: { - const buffer = value as Buffer; - // Buffer is empty first message after pairing - // https://github.com/Koenkk/zigbee-herdsman-converters/issues/7128 - if (buffer.length) { - const schedule = trv.decodeSchedule(buffer); - result['schedule_settings'] = trv.stringifySchedule(schedule); + Object.assign(result, heartbeat); + break; } - break; - } - case 0x00EE: { - meta.device.meta.lumiFileVersion = value; - meta.device.save(); - break; - } - case 0xfff2: - case 0x00ff: // 4e:27:49:bb:24:b6:30:dd:74:de:53:76:89:44:c4:81 - case 0x027c: // 0x00 - case 0x0280: // 0x00/0x01 - logger.debug(`Unhandled key ${key} = ${value}`, 'zhc:lumi:trv'); - break; - default: - logger.warning(`Unknown key ${key} = ${value}`, 'zhc:lumi:trv'); + case 0x027d: + result['schedule'] = getFromLookup(value, {1: true, 0: false}); + break; + case 0x0276: { + const buffer = value as Buffer; + // Buffer is empty first message after pairing + // https://github.com/Koenkk/zigbee-herdsman-converters/issues/7128 + if (buffer.length) { + const schedule = trv.decodeSchedule(buffer); + result['schedule_settings'] = trv.stringifySchedule(schedule); + } + break; + } + case 0x00ee: { + meta.device.meta.lumiFileVersion = value; + meta.device.save(); + break; + } + case 0xfff2: + case 0x00ff: // 4e:27:49:bb:24:b6:30:dd:74:de:53:76:89:44:c4:81 + case 0x027c: // 0x00 + case 0x0280: // 0x00/0x01 + logger.debug(`Unhandled key ${key} = ${value}`, 'zhc:lumi:trv'); + break; + default: + logger.warning(`Unknown key ${key} = ${value}`, 'zhc:lumi:trv'); } }); return result; @@ -2420,36 +2564,36 @@ export const fromZigbee = { const eventKey = parseInt(key); switch (eventKey) { - case presence.constants.region_event_key: { - if ( - !Buffer.isBuffer(value) || - !(typeof value[0] === 'string' || typeof value[0] === 'number') || - !(typeof value[1] === 'string' || typeof value[1] === 'number') - ) { - logger.warning(`Action: Unrecognized payload structure '${JSON.stringify(value)}'`, NS); - break; - } + case presence.constants.region_event_key: { + if ( + !Buffer.isBuffer(value) || + !(typeof value[0] === 'string' || typeof value[0] === 'number') || + !(typeof value[1] === 'string' || typeof value[1] === 'number') + ) { + logger.warning(`Action: Unrecognized payload structure '${JSON.stringify(value)}'`, NS); + break; + } - const [regionIdRaw, eventTypeCodeRaw] = value; - // @ts-expect-error - const regionId = parseInt(regionIdRaw, 10); - // @ts-expect-error - const eventTypeCode = parseInt(eventTypeCodeRaw, 10); + const [regionIdRaw, eventTypeCodeRaw] = value; + // @ts-expect-error + const regionId = parseInt(regionIdRaw, 10); + // @ts-expect-error + const eventTypeCode = parseInt(eventTypeCodeRaw, 10); - if (Number.isNaN(regionId)) { - logger.warning(`Action: Invalid regionId "${regionIdRaw}"`, NS); - break; - } - if (!Object.values(presence.constants.region_event_types).includes(eventTypeCode)) { - logger.warning(`Action: Unknown region event type "${eventTypeCode}"`, NS); + if (Number.isNaN(regionId)) { + logger.warning(`Action: Invalid regionId "${regionIdRaw}"`, NS); + break; + } + if (!Object.values(presence.constants.region_event_types).includes(eventTypeCode)) { + logger.warning(`Action: Unknown region event type "${eventTypeCode}"`, NS); + break; + } + + const eventTypeName = presence.mappers.lumi_presence.region_event_type_names[eventTypeCode]; + logger.debug(`Action: Triggered event (region "${regionId}", type "${eventTypeName}")`, NS); + payload.action = `region_${regionId}_${eventTypeName}`; break; } - - const eventTypeName = presence.mappers.lumi_presence.region_event_type_names[eventTypeCode]; - logger.debug(`Action: Triggered event (region "${regionId}", type "${eventTypeName}")`, NS); - payload.action = `region_${regionId}_${eventTypeName}`; - break; - } } }); @@ -2501,10 +2645,16 @@ export const fromZigbee = { if (msg.data.hasOwnProperty('illuminance')) { // The occupancy sensor only sends a message when motion detected. // Therefore we need to publish the no_motion detected by ourselves. - let timeout = meta && meta.state && meta.state.hasOwnProperty('detection_interval') ? - Number(meta.state.detection_interval) : ['RTCGQ14LM'].includes(model.model) ? 30 : 60; - timeout = options && options.hasOwnProperty('occupancy_timeout') && Number(options.occupancy_timeout) >= timeout ? - Number(options.occupancy_timeout) : timeout + 2; + let timeout = + meta && meta.state && meta.state.hasOwnProperty('detection_interval') + ? Number(meta.state.detection_interval) + : ['RTCGQ14LM'].includes(model.model) + ? 30 + : 60; + timeout = + options && options.hasOwnProperty('occupancy_timeout') && Number(options.occupancy_timeout) >= timeout + ? Number(options.occupancy_timeout) + : timeout + 2; // Stop existing timers because motion is detected and set a new one. clearTimeout(globalStore.getValue(msg.endpoint, 'occupancy_timer', null)); @@ -2532,8 +2682,7 @@ export const fromZigbee = { type: ['attributeReport', 'readResponse'], options: [exposes.options.invert_cover()], convert: (model, msg, publish, options, meta) => { - if ((model.model === 'ZNCLDJ12LM') && - msg.type === 'attributeReport' && [0, 2].includes(msg.data['presentValue'])) { + if (model.model === 'ZNCLDJ12LM' && msg.type === 'attributeReport' && [0, 2].includes(msg.data['presentValue'])) { // Incorrect reports from the device, ignore (re-read by onEvent of ZNCLDJ12LM) // https://github.com/Koenkk/zigbee-herdsman-converters/pull/1427#issuecomment-663862724 return; @@ -2554,7 +2703,7 @@ export const fromZigbee = { if (msg.data.hasOwnProperty('currentPositionLiftPercentage') && msg.data['currentPositionLiftPercentage'] <= 100) { const value = msg.data['currentPositionLiftPercentage']; const position = invert ? 100 - value : value; - const state = invert ? (position > 0 ? 'CLOSE' : 'OPEN') : (position > 0 ? 'OPEN' : 'CLOSE'); + const state = invert ? (position > 0 ? 'CLOSE' : 'OPEN') : position > 0 ? 'OPEN' : 'CLOSE'; result[postfixWithEndpointName('position', msg, model, meta)] = position; result[postfixWithEndpointName('state', msg, model, meta)] = state; } @@ -2572,14 +2721,14 @@ export const fromZigbee = { const payload: KeyValueAny = {}; if (model.meta && !model.meta.multiEndpoint) { - const mappingMode: KeyValueNumberString = {0x12: 'control_relay', 0xFE: 'decoupled'}; - const key = 0xFF22; + const mappingMode: KeyValueNumberString = {0x12: 'control_relay', 0xfe: 'decoupled'}; + const key = 0xff22; if (msg.data.hasOwnProperty(key)) { payload.operation_mode = mappingMode[msg.data[key]]; } } else { - const mappingButton: KeyValueNumberString = {0xFF22: 'left', 0xFF23: 'right'}; - const mappingMode: KeyValueNumberString = {0x12: 'control_left_relay', 0x22: 'control_right_relay', 0xFE: 'decoupled'}; + const mappingButton: KeyValueNumberString = {0xff22: 'left', 0xff23: 'right'}; + const mappingMode: KeyValueNumberString = {0x12: 'control_left_relay', 0x22: 'control_right_relay', 0xfe: 'decoupled'}; for (const key in mappingButton) { if (msg.data.hasOwnProperty(key)) { payload[`operation_mode_${mappingButton[key]}`] = mappingMode[msg.data[key]]; @@ -2649,7 +2798,7 @@ export const fromZigbee = { } else if (msg.data.hasOwnProperty('curtainReverse')) { return {reverse_direction: msg.data['curtainReverse'] === 1}; } else if (msg.data.hasOwnProperty('curtainCalibrated')) { - return {limits_calibration: (msg.data['curtainCalibrated'] === 1) ? 'calibrated' : 'recalibrate'}; + return {limits_calibration: msg.data['curtainCalibrated'] === 1 ? 'calibrated' : 'recalibrate'}; } }, } satisfies Fz.Converter, @@ -2698,9 +2847,9 @@ export const fromZigbee = { if (msg.data['1285']) { // https://github.com/dresden-elektronik/deconz-rest-plugin/issues/748#issuecomment-419669995 // Only first 2 bytes are relevant. - const data = (msg.data['1285'] >> 8); + const data = msg.data['1285'] >> 8; // Swap byte order - result.strength = ((data & 0xFF) << 8) | ((data >> 8) & 0xFF); + result.strength = ((data & 0xff) << 8) | ((data >> 8) & 0xff); } if (msg.data['1288']) { @@ -2712,30 +2861,30 @@ export const fromZigbee = { // data[1][bit16..bit31]: y // data[0][bit0..bit15] : z // left shift first to preserve sign extension for 'x' - let x = ((data['1'] << 16) >> 16); - let y = (data['1'] >> 16); + let x = (data['1'] << 16) >> 16; + let y = data['1'] >> 16; // left shift first to preserve sign extension for 'z' - let z = ((data['0'] << 16) >> 16); + let z = (data['0'] << 16) >> 16; // simple offset calibration - x=calibrateAndPrecisionRoundOptions(x, options, 'x'); - y=calibrateAndPrecisionRoundOptions(y, options, 'y'); - z=calibrateAndPrecisionRoundOptions(z, options, 'z'); + x = calibrateAndPrecisionRoundOptions(x, options, 'x'); + y = calibrateAndPrecisionRoundOptions(y, options, 'y'); + z = calibrateAndPrecisionRoundOptions(z, options, 'z'); // calibrated accelerometer values - result.x_axis=x; - result.y_axis=y; - result.z_axis=z; + result.x_axis = x; + result.y_axis = y; + result.z_axis = z; // calculate angle - result.angle_x = Math.round(Math.atan(x/Math.sqrt(y*y+z*z)) * 180 / Math.PI); - result.angle_y = Math.round(Math.atan(y/Math.sqrt(x*x+z*z)) * 180 / Math.PI); - result.angle_z = Math.round(Math.atan(z/Math.sqrt(x*x+y*y)) * 180 / Math.PI); + result.angle_x = Math.round((Math.atan(x / Math.sqrt(y * y + z * z)) * 180) / Math.PI); + result.angle_y = Math.round((Math.atan(y / Math.sqrt(x * x + z * z)) * 180) / Math.PI); + result.angle_z = Math.round((Math.atan(z / Math.sqrt(x * x + y * y)) * 180) / Math.PI); // calculate absolute angle const R = Math.sqrt(x * x + y * y + z * z); - result.angle_x_absolute = Math.round((Math.acos(x / R)) * 180 / Math.PI); - result.angle_y_absolute = Math.round((Math.acos(y / R)) * 180 / Math.PI); + result.angle_x_absolute = Math.round((Math.acos(x / R) * 180) / Math.PI); + result.angle_y_absolute = Math.round((Math.acos(y / R) * 180) / Math.PI); } return result; @@ -2774,10 +2923,11 @@ export const fromZigbee = { // The occupancy sensor only sends a message when motion detected. // Therefore we need to publish the no_motion detected by ourselves. - let timeout: number = meta && meta.state && meta.state.hasOwnProperty('detection_interval') ? - Number(meta.state.detection_interval) : 60; - timeout = options && options.hasOwnProperty('occupancy_timeout') && Number(options.occupancy_timeout) >= timeout ? - Number(options.occupancy_timeout) : timeout + 2; + let timeout: number = meta && meta.state && meta.state.hasOwnProperty('detection_interval') ? Number(meta.state.detection_interval) : 60; + timeout = + options && options.hasOwnProperty('occupancy_timeout') && Number(options.occupancy_timeout) >= timeout + ? Number(options.occupancy_timeout) + : timeout + 2; // Stop existing timers because motion is detected and set a new one. clearTimeout(globalStore.getValue(msg.endpoint, 'occupancy_timer', null)); @@ -2801,7 +2951,7 @@ export const fromZigbee = { convert: (model, msg, publish, options, meta) => { const result = fz.ias_smoke_alarm_1.convert(model, msg, publish, options, meta); const zoneStatus = msg.data.zonestatus; - if (result) result.test = (zoneStatus & 1<<1) > 0; + if (result) result.test = (zoneStatus & (1 << 1)) > 0; return result; }, } satisfies Fz.Converter, @@ -2871,16 +3021,21 @@ export const fromZigbee = { }; if (model.model === 'ZNMS11LM') { - if (msg.data['65296']) { // finger/password success + if (msg.data['65296']) { + // finger/password success const data = msg.data['65296'].toString(16); const command = data.substr(0, 1); // 1 finger open, 2 password open const userId = data.substr(5, 2); const userType = data.substr(1, 1); // 1 admin, 2 user result.data = data; - result.action = (lockStatusLookup[14+parseInt(command, 16)] + - (userType === '1' ? '_admin' : '_user') + '_id' + parseInt(userId, 16).toString()); + result.action = + lockStatusLookup[14 + parseInt(command, 16)] + + (userType === '1' ? '_admin' : '_user') + + '_id' + + parseInt(userId, 16).toString(); result.action_user = parseInt(userId, 16); - } else if (msg.data['65297']) { // finger, password failed or bell + } else if (msg.data['65297']) { + // finger, password failed or bell const data = msg.data['65297'].toString(16); const times = data.substr(0, 1); const type = data.substr(5, 2); // 00 bell, 02 password, 40 error finger @@ -2894,12 +3049,13 @@ export const fromZigbee = { } else if (type === '00') { result.action = lockStatusLookup[13]; } - } else if (msg.data['65281'] && msg.data['65281']['1']) { // user added/delete + } else if (msg.data['65281'] && msg.data['65281']['1']) { + // user added/delete const data = msg.data['65281']['1'].toString(16); const command = data.substr(0, 1); // 1 add, 2 delete const userId = data.substr(5, 2); result.data = data; - result.action = lockStatusLookup[6+parseInt(command, 16)]; + result.action = lockStatusLookup[6 + parseInt(command, 16)]; result.action_user = parseInt(userId, 16); } @@ -2911,82 +3067,87 @@ export const fromZigbee = { } } if (['ZNMS12LM', 'ZNMS13LM'].includes(model.model)) { - if (msg.data['65526']) { // lock final status + if (msg.data['65526']) { + // lock final status // Convert data back to hex to decode const data = Buffer.from(msg.data['65526'], 'ascii').toString('hex'); const command = data.substr(6, 4); if ( command === '0301' || // ZNMS12LM - command === '0341' // ZNMS13LM + command === '0341' // ZNMS13LM ) { result.action = lockStatusLookup[4]; result.state = 'UNLOCK'; result.reverse = 'UNLOCK'; } else if ( command === '0311' || // ZNMS12LM - command === '0351' // ZNMS13LM + command === '0351' // ZNMS13LM ) { result.action = lockStatusLookup[4]; result.state = 'LOCK'; result.reverse = 'UNLOCK'; } else if ( command === '0205' || // ZNMS12LM - command === '0245' // ZNMS13LM + command === '0245' // ZNMS13LM ) { result.action = lockStatusLookup[3]; result.state = 'UNLOCK'; result.reverse = 'LOCK'; } else if ( command === '0215' || // ZNMS12LM - command === '0255' || // ZNMS13LM - command === '1355' // ZNMS13LM + command === '0255' || // ZNMS13LM + command === '1355' // ZNMS13LM ) { result.action = lockStatusLookup[3]; result.state = 'LOCK'; result.reverse = 'LOCK'; } else if ( command === '0111' || // ZNMS12LM - command === '1351' || // ZNMS13LM locked from inside - command === '1451' // ZNMS13LM locked from outside + command === '1351' || // ZNMS13LM locked from inside + command === '1451' // ZNMS13LM locked from outside ) { result.action = lockStatusLookup[5]; result.state = 'LOCK'; result.reverse = 'UNLOCK'; } else if ( command === '0b00' || // ZNMS12LM - command === '0640' || // ZNMS13LM - command === '0600' // ZNMS13LM - + command === '0640' || // ZNMS13LM + command === '0600' // ZNMS13LM ) { result.action = lockStatusLookup[12]; result.state = 'UNLOCK'; result.reverse = 'UNLOCK'; } else if ( command === '0c00' || // ZNMS12LM - command === '2300' || // ZNMS13LM - command === '0540' || // ZNMS13LM - command === '0440' // ZNMS13LM + command === '2300' || // ZNMS13LM + command === '0540' || // ZNMS13LM + command === '0440' // ZNMS13LM ) { result.action = lockStatusLookup[11]; result.state = 'UNLOCK'; result.reverse = 'UNLOCK'; } else if ( command === '2400' || // ZNMS13LM door closed from insed - command === '2401' // ZNMS13LM door closed from outside + command === '2401' // ZNMS13LM door closed from outside ) { result.action = lockStatusLookup[17]; result.state = 'UNLOCK'; result.reverse = 'UNLOCK'; } - } else if (msg.data['65296']) { // finger/password success + } else if (msg.data['65296']) { + // finger/password success const data = Buffer.from(msg.data['65296'], 'ascii').toString('hex'); const command = data.substr(6, 2); // 1 finger open, 2 password open const userId = data.substr(12, 2); const userType = data.substr(8, 1); // 1 admin, 2 user - result.action = (lockStatusLookup[14+parseInt(command, 16)] + - (userType === '1' ? '_admin' : '_user') + '_id' + parseInt(userId, 16).toString()); + result.action = + lockStatusLookup[14 + parseInt(command, 16)] + + (userType === '1' ? '_admin' : '_user') + + '_id' + + parseInt(userId, 16).toString(); result.action_user = parseInt(userId, 16); - } else if (msg.data['65297']) { // finger, password failed or bell + } else if (msg.data['65297']) { + // finger, password failed or bell const data = Buffer.from(msg.data['65297'], 'ascii').toString('hex'); const times = data.substr(6, 2); const type = data.substr(12, 2); // 00 bell, 02 password, 40 error finger @@ -3000,16 +3161,18 @@ export const fromZigbee = { result.action = lockStatusLookup[2]; result.action_repeat = parseInt(times, 16); } - } else if (msg.data['65281']) { // password added/delete + } else if (msg.data['65281']) { + // password added/delete const data = Buffer.from(msg.data['65281'], 'ascii').toString('hex'); const command = data.substr(18, 2); // 1 add, 2 delete const userId = data.substr(12, 2); - result.action = lockStatusLookup[6+parseInt(command, 16)]; + result.action = lockStatusLookup[6 + parseInt(command, 16)]; result.action_user = parseInt(userId, 16); - } else if (msg.data['65522']) { // set language + } else if (msg.data['65522']) { + // set language const data = Buffer.from(msg.data['65522'], 'ascii').toString('hex'); const langId = data.substr(6, 2); // 1 chinese, 2: english - result.action = (lockStatusLookup[14])+ (langId==='2'?'_english':'_chinese'); + result.action = lockStatusLookup[14] + (langId === '2' ? '_english' : '_chinese'); } if (isLegacyEnabled(options)) { @@ -3079,10 +3242,10 @@ export const fromZigbee = { let action; if (model.model === 'WXCJKG12LM') { // for WXCJKG12LM model it's double click event on buttons 3 and 4 - action = (msg.data.stepmode === 1) ? '3_double' : '4_double'; + action = msg.data.stepmode === 1 ? '3_double' : '4_double'; } else { // but for WXCJKG13LM model it's single click event on buttons 5 and 6 - action = (msg.data.stepmode === 1) ? '5_single' : '6_single'; + action = msg.data.stepmode === 1 ? '5_single' : '6_single'; } return {action: `button_${action}`}; }, @@ -3115,13 +3278,23 @@ export const fromZigbee = { cluster: 'genOnOff', type: ['attributeReport', 'readResponse'], options: [ - e.numeric('hold_timeout', ea.SET).withValueMin(0).withDescription(`The WXKG01LM only reports a button press and release.` + - `By default, a hold action is published when there is at least 1000 ms between both events. It could be that due to ` + - `delays in the network the release message is received late. This causes a single click to be identified as a hold ` + - `action. If you are experiencing this you can try experimenting with this option (e.g. set it to 2000) (value is in ms).`), - e.numeric('hold_timeout_expire', ea.SET).withValueMin(0).withDescription(`Sometimes it happens that the button does not send a ` + - `release. To avoid problems a release is automatically send after a timeout. The default timeout is 4000 ms, you can ` + - `increase it with this option (value is in ms).`), + e + .numeric('hold_timeout', ea.SET) + .withValueMin(0) + .withDescription( + `The WXKG01LM only reports a button press and release.` + + `By default, a hold action is published when there is at least 1000 ms between both events. It could be that due to ` + + `delays in the network the release message is received late. This causes a single click to be identified as a hold ` + + `action. If you are experiencing this you can try experimenting with this option (e.g. set it to 2000) (value is in ms).`, + ), + e + .numeric('hold_timeout_expire', ea.SET) + .withValueMin(0) + .withDescription( + `Sometimes it happens that the button does not send a ` + + `release. To avoid problems a release is automatically send after a timeout. The default timeout is 4000 ms, you can ` + + `increase it with this option (value is in ms).`, + ), ], convert: (model, msg, publish, options: KeyValueAny, meta) => { if (hasAlreadyProcessedMessage(msg, model)) return; @@ -3247,17 +3420,49 @@ export const toZigbee = { key: ['led_disabled_night'], convertSet: async (entity, key, value, meta) => { if (Array.isArray(meta.mapped)) throw new Error(`Not supported for groups`); - if (['ZNCZ04LM', 'ZNCZ12LM', 'ZNCZ15LM', 'QBCZ14LM', 'QBCZ15LM', 'QBKG17LM', 'QBKG18LM', 'QBKG19LM', 'QBKG20LM', 'QBKG25LM', 'QBKG26LM', - 'QBKG27LM', 'QBKG28LM', 'QBKG29LM', 'QBKG30LM', 'QBKG31LM', 'QBKG32LM', 'QBKG33LM', 'QBKG34LM', 'DLKZMK11LM', 'SSM-U01', 'WS-EUK01', - 'WS-EUK02', 'WS-EUK03', 'WS-EUK04', 'SP-EUC01', 'ZNQBKG24LM', 'ZNQBKG25LM', - 'ZNQBKG38LM', 'ZNQBKG39LM', 'ZNQBKG40LM', 'ZNQBKG41LM'].includes(meta.mapped.model)) { + if ( + [ + 'ZNCZ04LM', + 'ZNCZ12LM', + 'ZNCZ15LM', + 'QBCZ14LM', + 'QBCZ15LM', + 'QBKG17LM', + 'QBKG18LM', + 'QBKG19LM', + 'QBKG20LM', + 'QBKG25LM', + 'QBKG26LM', + 'QBKG27LM', + 'QBKG28LM', + 'QBKG29LM', + 'QBKG30LM', + 'QBKG31LM', + 'QBKG32LM', + 'QBKG33LM', + 'QBKG34LM', + 'DLKZMK11LM', + 'SSM-U01', + 'WS-EUK01', + 'WS-EUK02', + 'WS-EUK03', + 'WS-EUK04', + 'SP-EUC01', + 'ZNQBKG24LM', + 'ZNQBKG25LM', + 'ZNQBKG38LM', + 'ZNQBKG39LM', + 'ZNQBKG40LM', + 'ZNQBKG41LM', + ].includes(meta.mapped.model) + ) { await entity.write('manuSpecificLumi', {0x0203: {value: value ? 1 : 0, type: 0x10}}, manufacturerOptions.lumi); } else if (['ZNCZ11LM'].includes(meta.mapped.model)) { - const payload = value ? - [0xaa, 0x80, 0x05, 0xd1, 0x47, 0x00, 0x03, 0x10, 0x00] : - [0xaa, 0x80, 0x05, 0xd1, 0x47, 0x01, 0x03, 0x10, 0x01]; + const payload = value + ? [0xaa, 0x80, 0x05, 0xd1, 0x47, 0x00, 0x03, 0x10, 0x00] + : [0xaa, 0x80, 0x05, 0xd1, 0x47, 0x01, 0x03, 0x10, 0x01]; - await entity.write('genBasic', {0xFFF0: {value: payload, type: 0x41}}, manufacturerOptions.lumi); + await entity.write('genBasic', {0xfff0: {value: payload, type: 0x41}}, manufacturerOptions.lumi); } else { throw new Error('Not supported'); } @@ -3265,10 +3470,42 @@ export const toZigbee = { }, convertGet: async (entity, key, meta) => { if (Array.isArray(meta.mapped)) throw new Error(`Not supported for groups`); - if (['ZNCZ04LM', 'ZNCZ12LM', 'ZNCZ15LM', 'QBCZ15LM', 'QBCZ14LM', 'QBKG17LM', 'QBKG18LM', 'QBKG19LM', 'QBKG20LM', 'QBKG25LM', 'QBKG26LM', - 'QBKG27LM', 'QBKG28LM', 'QBKG29LM', 'QBKG30LM', 'QBKG31LM', 'QBKG32LM', 'QBKG33LM', 'QBKG34LM', 'DLKZMK11LM', 'SSM-U01', 'WS-EUK01', - 'WS-EUK02', 'WS-EUK03', 'WS-EUK04', 'SP-EUC01', 'ZNQBKG24LM', 'ZNQBKG25LM', - 'ZNQBKG38LM', 'ZNQBKG39LM', 'ZNQBKG40LM', 'ZNQBKG41LM'].includes(meta.mapped.model)) { + if ( + [ + 'ZNCZ04LM', + 'ZNCZ12LM', + 'ZNCZ15LM', + 'QBCZ15LM', + 'QBCZ14LM', + 'QBKG17LM', + 'QBKG18LM', + 'QBKG19LM', + 'QBKG20LM', + 'QBKG25LM', + 'QBKG26LM', + 'QBKG27LM', + 'QBKG28LM', + 'QBKG29LM', + 'QBKG30LM', + 'QBKG31LM', + 'QBKG32LM', + 'QBKG33LM', + 'QBKG34LM', + 'DLKZMK11LM', + 'SSM-U01', + 'WS-EUK01', + 'WS-EUK02', + 'WS-EUK03', + 'WS-EUK04', + 'SP-EUC01', + 'ZNQBKG24LM', + 'ZNQBKG25LM', + 'ZNQBKG38LM', + 'ZNQBKG39LM', + 'ZNQBKG40LM', + 'ZNQBKG41LM', + ].includes(meta.mapped.model) + ) { await entity.read('manuSpecificLumi', [0x0203], manufacturerOptions.lumi); } else { throw new Error('Not supported'); @@ -3278,12 +3515,12 @@ export const toZigbee = { lumi_flip_indicator_light: { key: ['flip_indicator_light'], convertSet: async (entity, key, value, meta) => { - const lookup = {'OFF': 0, 'ON': 1}; - await entity.write('manuSpecificLumi', {0x00F0: {value: getFromLookup(value, lookup), type: 0x20}}, manufacturerOptions.lumi); + const lookup = {OFF: 0, ON: 1}; + await entity.write('manuSpecificLumi', {0x00f0: {value: getFromLookup(value, lookup), type: 0x20}}, manufacturerOptions.lumi); return {state: {flip_indicator_light: value}}; }, convertGet: async (entity, key, meta) => { - await entity.read('manuSpecificLumi', [0x00F0], manufacturerOptions.lumi); + await entity.read('manuSpecificLumi', [0x00f0], manufacturerOptions.lumi); }, } satisfies Tz.Converter, lumi_power_outage_count: { @@ -3299,7 +3536,7 @@ export const toZigbee = { convertSet: async (entity, key, value, meta) => { const sendAttr = async (attrCode: number, value: number, length: number) => { // @ts-expect-error - entity.sendSeq = ((entity.sendSeq || 0)+1) % 256; + entity.sendSeq = ((entity.sendSeq || 0) + 1) % 256; // @ts-expect-error const val = Buffer.from([0x00, 0x02, entity.sendSeq, 0, 0, 0, 0, 0]); // @ts-expect-error @@ -3308,63 +3545,56 @@ export const toZigbee = { val.writeUInt8(length, 7); let v = Buffer.alloc(length); switch (length) { - case 1: - v.writeUInt8(value); + case 1: + v.writeUInt8(value); + break; + case 2: + v.writeUInt16BE(value); + break; + case 4: + v.writeUInt32BE(value); + break; + default: + // @ts-expect-error + v = value; + } + await entity.write('manuSpecificLumi', {0xfff1: {value: Buffer.concat([val, v]), type: 0x41}}, {manufacturerCode: manufacturerCode}); + }; + switch (key) { + case 'feed': + await sendAttr(0x04150055, 1, 1); + break; + case 'schedule': { + const schedule: string[] = []; + // @ts-expect-error + value.forEach((item) => { + const schedItem = Buffer.from([getKey(feederDaysLookup, item.days, 0x7f), item.hour, item.minute, item.size, 0]); + schedule.push(schedItem.toString('hex')); + }); + const val = Buffer.concat([Buffer.from(schedule.join(',')), Buffer.from([0])]); + // @ts-expect-error + await sendAttr(0x080008c8, val, val.length); break; - case 2: - v.writeUInt16BE(value); + } + case 'led_indicator': + await sendAttr(0x04170055, getFromLookup(value, {ON: 0, OFF: 1}), 1); break; - case 4: - v.writeUInt32BE(value); + case 'child_lock': + await sendAttr(0x04160055, getFromLookup(value, {UNLOCK: 0, LOCK: 1}), 1); break; - default: + case 'mode': + await sendAttr(0x04180055, getFromLookup(value, {manual: 0, schedule: 1}), 1); + break; + case 'serving_size': // @ts-expect-error - v = value; - } - await entity.write('manuSpecificLumi', {0xfff1: {value: Buffer.concat([val, v]), type: 0x41}}, - {manufacturerCode: manufacturerCode}); - }; - switch (key) { - case 'feed': - await sendAttr(0x04150055, 1, 1); - break; - case 'schedule': { - const schedule: string[] = []; - // @ts-expect-error - value.forEach((item) => { - const schedItem = Buffer.from([ - getKey(feederDaysLookup, item.days, 0x7f), - item.hour, - item.minute, - item.size, - 0, - ]); - schedule.push(schedItem.toString('hex')); - }); - const val = Buffer.concat([Buffer.from(schedule.join(',')), Buffer.from([0])]); - // @ts-expect-error - await sendAttr(0x080008c8, val, val.length); - break; - } - case 'led_indicator': - await sendAttr(0x04170055, getFromLookup(value, {'ON': 0, 'OFF': 1}), 1); - break; - case 'child_lock': - await sendAttr(0x04160055, getFromLookup(value, {'UNLOCK': 0, 'LOCK': 1}), 1); - break; - case 'mode': - await sendAttr(0x04180055, getFromLookup(value, {'manual': 0, 'schedule': 1}), 1); - break; - case 'serving_size': - // @ts-expect-error - await sendAttr(0x0e5c0055, value, 4); - break; - case 'portion_weight': - // @ts-expect-error - await sendAttr(0x0e5f0055, value, 4); - break; - default: // Unknown key - logger.warning(`Unhandled key ${key}`, 'zhc:lumi:feeder'); + await sendAttr(0x0e5c0055, value, 4); + break; + case 'portion_weight': + // @ts-expect-error + await sendAttr(0x0e5f0055, value, 4); + break; + default: // Unknown key + logger.warning(`Unhandled key ${key}`, 'zhc:lumi:feeder'); } return {state: {[key]: value}}; }, @@ -3375,16 +3605,28 @@ export const toZigbee = { assertString(value, 'detection_distance'); value = value.toLowerCase(); const lookup = {'10mm': 1, '20mm': 2, '30mm': 3}; - await entity.write('manuSpecificLumi', {0x010C: {value: getFromLookup(value, lookup), type: 0x20}}, {manufacturerCode}); + await entity.write('manuSpecificLumi', {0x010c: {value: getFromLookup(value, lookup), type: 0x20}}, {manufacturerCode}); return {state: {detection_distance: value}}; }, convertGet: async (entity, key, meta) => { - await entity.read('manuSpecificLumi', [0x010C], {manufacturerCode}); + await entity.read('manuSpecificLumi', [0x010c], {manufacturerCode}); }, } satisfies Tz.Converter, lumi_trv: { - key: ['system_mode', 'preset', 'window_detection', 'valve_detection', 'child_lock', 'away_preset_temperature', - 'calibrate', 'sensor', 'external_temperature_input', 'identify', 'schedule', 'schedule_settings'], + key: [ + 'system_mode', + 'preset', + 'window_detection', + 'valve_detection', + 'child_lock', + 'away_preset_temperature', + 'calibrate', + 'sensor', + 'external_temperature_input', + 'identify', + 'schedule', + 'schedule_settings', + ], convertSet: async (entity, key, value, meta) => { const lumiHeader = (counter: number, params: number[], action: number) => { const header = [0xaa, 0x71, params.length + 3, 0x44, counter]; @@ -3394,132 +3636,200 @@ export const toZigbee = { const sensor = Buffer.from('00158d00019d1b98', 'hex'); switch (key) { - case 'system_mode': - await entity.write('manuSpecificLumi', {0x0271: {value: getFromLookup(value, {'off': 0, 'heat': 1}), type: 0x20}}, - {manufacturerCode: manufacturerCode}); - break; - case 'preset': - await entity.write('manuSpecificLumi', {0x0272: {value: getFromLookup(value, {'manual': 0, 'auto': 1, 'away': 2}), type: 0x20}}, - {manufacturerCode: manufacturerCode}); - break; - case 'window_detection': - await entity.write('manuSpecificLumi', { - 0x0273: {value: getFromLookup(value, {'false': 0, 'true': 1}, undefined, true), type: 0x20}, - }, {manufacturerCode: manufacturerCode}); - break; - case 'valve_detection': - await entity.write('manuSpecificLumi', { - 0x0274: {value: getFromLookup(value, {'false': 0, 'true': 1}, undefined, true), type: 0x20}, - }, {manufacturerCode: manufacturerCode}); - break; - case 'child_lock': - await entity.write('manuSpecificLumi', { - 0x0277: {value: getFromLookup(value, {'false': 0, 'true': 1}, undefined, true), type: 0x20}, - }, {manufacturerCode: manufacturerCode}); - break; - case 'away_preset_temperature': - await entity.write('manuSpecificLumi', { - 0x0279: {value: Math.round(toNumber(value, 'away_preset_temperature') * 100), type: 0x23}, - }, {manufacturerCode: manufacturerCode}); - break; - case 'sensor': { - assertEndpoint(entity); - const device = Buffer.from(entity.deviceIeeeAddress.substring(2), 'hex'); - const timestamp = Buffer.alloc(4); - timestamp.writeUint32BE(Date.now()/1000); - - if (value === 'external') { - const params1 = [ - ...timestamp, - 0x3d, 0x04, - ...device, - ...sensor, - 0x00, 0x01, 0x00, 0x55, - 0x13, 0x0a, 0x02, 0x00, 0x00, 0x64, 0x04, 0xce, 0xc2, 0xb6, 0xc8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3d, - 0x64, - 0x65, - ]; - const params2 = [ - ...timestamp, - 0x3d, 0x05, - ...device, - ...sensor, - 0x08, 0x00, 0x07, 0xfd, - 0x16, 0x0a, 0x02, 0x0a, 0xc9, 0xe8, 0xb1, 0xb8, 0xd4, 0xda, 0xcf, 0xdf, 0xc0, 0xeb, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3d, - 0x04, - 0x65, - ]; - - const val1 = [...(lumiHeader(0x12, params1, 0x02)), ...params1]; - const val2 = [...(lumiHeader(0x13, params2, 0x02)), ...params2]; - - await entity.write('manuSpecificLumi', {0xfff2: {value: val1, type: 0x41}}, {manufacturerCode: manufacturerCode}); - await entity.write('manuSpecificLumi', {0xfff2: {value: val2, type: 0x41}}, {manufacturerCode: manufacturerCode}); - } else if (value === 'internal') { - const params1 = [ - ...timestamp, - 0x3d, 0x05, - ...device, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ]; - const params2 = [ - ...timestamp, - 0x3d, 0x04, - ...device, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ]; - - const val1 = [...(lumiHeader(0x12, params1, 0x04)), ...params1]; - const val2 = [...(lumiHeader(0x13, params2, 0x04)), ...params2]; - - await entity.write('manuSpecificLumi', {0xfff2: {value: val1, type: 0x41}}, {manufacturerCode: manufacturerCode}); - await entity.write('manuSpecificLumi', {0xfff2: {value: val2, type: 0x41}}, {manufacturerCode: manufacturerCode}); - - await entity.read('hvacThermostat', ['localTemp']); + case 'system_mode': + await entity.write( + 'manuSpecificLumi', + {0x0271: {value: getFromLookup(value, {off: 0, heat: 1}), type: 0x20}}, + {manufacturerCode: manufacturerCode}, + ); + break; + case 'preset': + await entity.write( + 'manuSpecificLumi', + {0x0272: {value: getFromLookup(value, {manual: 0, auto: 1, away: 2}), type: 0x20}}, + {manufacturerCode: manufacturerCode}, + ); + break; + case 'window_detection': + await entity.write( + 'manuSpecificLumi', + { + 0x0273: {value: getFromLookup(value, {false: 0, true: 1}, undefined, true), type: 0x20}, + }, + {manufacturerCode: manufacturerCode}, + ); + break; + case 'valve_detection': + await entity.write( + 'manuSpecificLumi', + { + 0x0274: {value: getFromLookup(value, {false: 0, true: 1}, undefined, true), type: 0x20}, + }, + {manufacturerCode: manufacturerCode}, + ); + break; + case 'child_lock': + await entity.write( + 'manuSpecificLumi', + { + 0x0277: {value: getFromLookup(value, {false: 0, true: 1}, undefined, true), type: 0x20}, + }, + {manufacturerCode: manufacturerCode}, + ); + break; + case 'away_preset_temperature': + await entity.write( + 'manuSpecificLumi', + { + 0x0279: {value: Math.round(toNumber(value, 'away_preset_temperature') * 100), type: 0x23}, + }, + {manufacturerCode: manufacturerCode}, + ); + break; + case 'sensor': { + assertEndpoint(entity); + const device = Buffer.from(entity.deviceIeeeAddress.substring(2), 'hex'); + const timestamp = Buffer.alloc(4); + timestamp.writeUint32BE(Date.now() / 1000); + + if (value === 'external') { + const params1 = [ + ...timestamp, + 0x3d, + 0x04, + ...device, + ...sensor, + 0x00, + 0x01, + 0x00, + 0x55, + 0x13, + 0x0a, + 0x02, + 0x00, + 0x00, + 0x64, + 0x04, + 0xce, + 0xc2, + 0xb6, + 0xc8, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x3d, + 0x64, + 0x65, + ]; + const params2 = [ + ...timestamp, + 0x3d, + 0x05, + ...device, + ...sensor, + 0x08, + 0x00, + 0x07, + 0xfd, + 0x16, + 0x0a, + 0x02, + 0x0a, + 0xc9, + 0xe8, + 0xb1, + 0xb8, + 0xd4, + 0xda, + 0xcf, + 0xdf, + 0xc0, + 0xeb, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x3d, + 0x04, + 0x65, + ]; + + const val1 = [...lumiHeader(0x12, params1, 0x02), ...params1]; + const val2 = [...lumiHeader(0x13, params2, 0x02), ...params2]; + + await entity.write('manuSpecificLumi', {0xfff2: {value: val1, type: 0x41}}, {manufacturerCode: manufacturerCode}); + await entity.write('manuSpecificLumi', {0xfff2: {value: val2, type: 0x41}}, {manufacturerCode: manufacturerCode}); + } else if (value === 'internal') { + const params1 = [...timestamp, 0x3d, 0x05, ...device, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + const params2 = [...timestamp, 0x3d, 0x04, ...device, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + + const val1 = [...lumiHeader(0x12, params1, 0x04), ...params1]; + const val2 = [...lumiHeader(0x13, params2, 0x04), ...params2]; + + await entity.write('manuSpecificLumi', {0xfff2: {value: val1, type: 0x41}}, {manufacturerCode: manufacturerCode}); + await entity.write('manuSpecificLumi', {0xfff2: {value: val2, type: 0x41}}, {manufacturerCode: manufacturerCode}); + + await entity.read('hvacThermostat', ['localTemp']); + } + break; } - break; - } - case 'external_temperature_input': - if (meta.state['sensor'] === 'external') { - const temperatureBuf = Buffer.alloc(4); - const number = toNumber(value); - temperatureBuf.writeFloatBE(Math.round(number * 100)); + case 'external_temperature_input': + if (meta.state['sensor'] === 'external') { + const temperatureBuf = Buffer.alloc(4); + const number = toNumber(value); + temperatureBuf.writeFloatBE(Math.round(number * 100)); - const params = [...sensor, 0x00, 0x01, 0x00, 0x55, ...temperatureBuf]; - const data = [...(lumiHeader(0x12, params, 0x05)), ...params]; + const params = [...sensor, 0x00, 0x01, 0x00, 0x55, ...temperatureBuf]; + const data = [...lumiHeader(0x12, params, 0x05), ...params]; - await entity.write('manuSpecificLumi', {0xfff2: {value: data, type: 0x41}}, {manufacturerCode: manufacturerCode}); + await entity.write('manuSpecificLumi', {0xfff2: {value: data, type: 0x41}}, {manufacturerCode: manufacturerCode}); + } + break; + case 'calibrate': + await entity.write('manuSpecificLumi', {0x0270: {value: 1, type: 0x20}}, {manufacturerCode: manufacturerCode}); + break; + case 'identify': + await entity.command('genIdentify', 'identify', {identifytime: 5}, {}); + break; + case 'schedule': + await entity.write( + 'manuSpecificLumi', + { + 0x027d: {value: getFromLookup(value, {false: 0, true: 1}, undefined, true), type: 0x20}, + }, + {manufacturerCode: manufacturerCode}, + ); + break; + case 'schedule_settings': { + // @ts-expect-error + const schedule = trv.parseSchedule(value); + trv.validateSchedule(schedule); + const buffer = trv.encodeSchedule(schedule); + await entity.write('manuSpecificLumi', {0x0276: {value: buffer, type: 0x41}}, {manufacturerCode: manufacturerCode}); + break; } - break; - case 'calibrate': - await entity.write('manuSpecificLumi', {0x0270: {value: 1, type: 0x20}}, {manufacturerCode: manufacturerCode}); - break; - case 'identify': - await entity.command('genIdentify', 'identify', {identifytime: 5}, {}); - break; - case 'schedule': - await entity.write('manuSpecificLumi', { - 0x027d: {value: getFromLookup(value, {'false': 0, 'true': 1}, undefined, true), type: 0x20}, - }, {manufacturerCode: manufacturerCode}); - break; - case 'schedule_settings': { - // @ts-expect-error - const schedule = trv.parseSchedule(value); - trv.validateSchedule(schedule); - const buffer = trv.encodeSchedule(schedule); - await entity.write('manuSpecificLumi', {0x0276: {value: buffer, type: 0x41}}, {manufacturerCode: manufacturerCode}); - break; - } - default: // Unknown key - logger.warning(`Unhandled key ${key}`, 'zhc:lumi:trv'); + default: // Unknown key + logger.warning(`Unhandled key ${key}`, 'zhc:lumi:trv'); } }, convertGet: async (entity, key, meta) => { - const dict = {'system_mode': 0x0271, 'preset': 0x0272, 'window_detection': 0x0273, 'valve_detection': 0x0274, - 'child_lock': 0x0277, 'away_preset_temperature': 0x0279, 'calibrated': 0x027b, 'sensor': 0x027e, - 'schedule': 0x027d, 'schedule_settings': 0x0276}; + const dict = { + system_mode: 0x0271, + preset: 0x0272, + window_detection: 0x0273, + valve_detection: 0x0274, + child_lock: 0x0277, + away_preset_temperature: 0x0279, + calibrated: 0x027b, + sensor: 0x027e, + schedule: 0x027d, + schedule_settings: 0x0276, + }; if (dict.hasOwnProperty(key)) { await entity.read('manuSpecificLumi', [getFromLookup(key, dict)], {manufacturerCode: manufacturerCode}); @@ -3546,24 +3856,26 @@ export const toZigbee = { logger.debug(`Trying to create region ${command.region_id}`, NS); const sortedZonesAccumulator = {}; - const sortedZonesWithSets: {[s: number]: [number]} = command.zones - .reduce( - (accumulator: {[s: number]: Set}, zone: {x: number, y: number}) => { - if (!accumulator[zone.y]) { - accumulator[zone.y] = new Set(); - } + const sortedZonesWithSets: {[s: number]: [number]} = command.zones.reduce( + (accumulator: {[s: number]: Set}, zone: {x: number; y: number}) => { + if (!accumulator[zone.y]) { + accumulator[zone.y] = new Set(); + } - accumulator[zone.y].add(zone.x); + accumulator[zone.y].add(zone.x); - return accumulator; - }, - sortedZonesAccumulator, - ); - const sortedZones = Object.entries(sortedZonesWithSets).reduce((acc, [key, value]) => { - const numKey = parseInt(key, 10); // Convert string key back to number - acc[numKey] = Array.from(value); - return acc; - }, {} as {[s: number]: number[]}); + return accumulator; + }, + sortedZonesAccumulator, + ); + const sortedZones = Object.entries(sortedZonesWithSets).reduce( + (acc, [key, value]) => { + const numKey = parseInt(key, 10); // Convert string key back to number + acc[numKey] = Array.from(value); + return acc; + }, + {} as {[s: number]: number[]}, + ); const deviceConfig = new Uint8Array(7); @@ -3580,7 +3892,7 @@ export const toZigbee = { deviceConfig[4] |= presence.encodeXCellsDefinition(sortedZones['6']) << 4; deviceConfig[5] |= presence.encodeXCellsDefinition(sortedZones['7']); - logger.info( `Create region ${command.region_id} ${printNumbersAsHexSequence([...deviceConfig], 2)}`, NS); + logger.info(`Create region ${command.region_id} ${printNumbersAsHexSequence([...deviceConfig], 2)}`, NS); const payload = { [presence.constants.region_config_write_attribute]: { @@ -3663,19 +3975,19 @@ export const toZigbee = { let attrId; let attrValue: number; if (meta.mapped.meta && meta.mapped.meta.multiEndpoint) { - attrId = {left: 0xFF22, right: 0xFF23}[meta.endpoint_name]; + attrId = {left: 0xff22, right: 0xff23}[meta.endpoint_name]; // Allow usage of control_relay for 2 gang switches by mapping it to the default side. if (targetValue === 'control_relay') { targetValue = `control_${meta.endpoint_name}_relay`; } - attrValue = getFromLookup(targetValue, {control_left_relay: 0x12, control_right_relay: 0x22, decoupled: 0xFE}); + attrValue = getFromLookup(targetValue, {control_left_relay: 0x12, control_right_relay: 0x22, decoupled: 0xfe}); if (attrId == null) { throw new Error(`Unsupported endpoint ${meta.endpoint_name} for changing operation_mode.`); } } else { - attrId = 0xFF22; - attrValue = getFromLookup(targetValue, {control_relay: 0x12, decoupled: 0xFE}); + attrId = 0xff22; + attrValue = getFromLookup(targetValue, {control_relay: 0x12, decoupled: 0xfe}); } if (attrValue == null) { @@ -3693,12 +4005,12 @@ export const toZigbee = { let attrId; if (Array.isArray(meta.mapped)) throw new Error(`Not supported for groups`); if (meta.mapped.meta && meta.mapped.meta.multiEndpoint) { - attrId = {left: 0xFF22, right: 0xFF23}[meta.endpoint_name]; + attrId = {left: 0xff22, right: 0xff23}[meta.endpoint_name]; if (attrId == null) { throw new Error(`Unsupported endpoint ${meta.endpoint_name} for getting operation_mode.`); } } else { - attrId = 0xFF22; + attrId = 0xff22; } await entity.read('genBasic', [attrId], manufacturerOptions.lumi); }, @@ -3710,8 +4022,7 @@ export const toZigbee = { const targetValue = isObject(value) && value.hasOwnProperty('state') ? value.state : value; // Switches using manuSpecificLumi 0x0200 on the same endpoints as the onOff clusters. const lookupState = {control_relay: 0x01, decoupled: 0x00}; - await entity.write('manuSpecificLumi', {0x0200: - {value: getFromLookup(targetValue, lookupState), type: 0x20}}, manufacturerOptions.lumi); + await entity.write('manuSpecificLumi', {0x0200: {value: getFromLookup(targetValue, lookupState), type: 0x20}}, manufacturerOptions.lumi); }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificLumi', [0x0200], manufacturerOptions.lumi); @@ -3744,7 +4055,7 @@ export const toZigbee = { lumi_switch_mode_switch: { key: ['mode_switch'], convertSet: async (entity, key, value, meta) => { - const lookup = {'anti_flicker_mode': 4, 'quick_mode': 1}; + const lookup = {anti_flicker_mode: 4, quick_mode: 1}; await entity.write('manuSpecificLumi', {0x0004: {value: getFromLookup(value, lookup), type: 0x21}}, manufacturerOptions.lumi); return {state: {mode_switch: value}}; }, @@ -3755,7 +4066,7 @@ export const toZigbee = { lumi_button_switch_mode: { key: ['button_switch_mode'], convertSet: async (entity, key, value, meta) => { - const lookup = {'relay': 0, 'relay_and_usb': 1}; + const lookup = {relay: 0, relay_and_usb: 1}; await entity.write('manuSpecificLumi', {0x0226: {value: getFromLookup(value, lookup), type: 0x20}}, manufacturerOptions.lumi); return {state: {button_switch_mode: value}}; }, @@ -3766,7 +4077,7 @@ export const toZigbee = { lumi_socket_button_lock: { key: ['button_lock'], convertSet: async (entity, key, value, meta) => { - const lookup = {'ON': 0, 'OFF': 1}; + const lookup = {ON: 0, OFF: 1}; await entity.write('manuSpecificLumi', {0x0200: {value: getFromLookup(value, lookup), type: 0x20}}, manufacturerOptions.lumi); return {state: {button_lock: value}}; }, @@ -3777,17 +4088,17 @@ export const toZigbee = { lumi_dimmer_mode: { key: ['dimmer_mode'], convertSet: async (entity, key, value, meta) => { - const lookup = {'rgbw': 3, 'dual_ct': 1}; + const lookup = {rgbw: 3, dual_ct: 1}; assertString(value, key); value = value.toLowerCase(); // @ts-expect-error if (['rgbw'].includes(value)) { await entity.write('manuSpecificLumi', {0x0509: {value: getFromLookup(value, lookup), type: 0x23}}, manufacturerOptions.lumi); - await entity.write('manuSpecificLumi', {0x050F: {value: 1, type: 0x23}}, manufacturerOptions.lumi); + await entity.write('manuSpecificLumi', {0x050f: {value: 1, type: 0x23}}, manufacturerOptions.lumi); } else { await entity.write('manuSpecificLumi', {0x0509: {value: getFromLookup(value, lookup), type: 0x23}}, manufacturerOptions.lumi); // Turn on dimming channel 1 and channel 2 - await entity.write('manuSpecificLumi', {0x050F: {value: 3, type: 0x23}}, manufacturerOptions.lumi); + await entity.write('manuSpecificLumi', {0x050f: {value: 3, type: 0x23}}, manufacturerOptions.lumi); } return {state: {dimmer_mode: value}}; }, @@ -3805,46 +4116,102 @@ export const toZigbee = { lumi_switch_type: { key: ['switch_type'], convertSet: async (entity, key, value, meta) => { - const lookup = {'toggle': 1, 'momentary': 2}; + const lookup = {toggle: 1, momentary: 2}; assertString(value, key); value = value.toLowerCase(); - await entity.write('manuSpecificLumi', {0x000A: {value: getFromLookup(value, lookup), type: 0x20}}, manufacturerOptions.lumi); + await entity.write('manuSpecificLumi', {0x000a: {value: getFromLookup(value, lookup), type: 0x20}}, manufacturerOptions.lumi); return {state: {switch_type: value}}; }, convertGet: async (entity, key, meta) => { - await entity.read('manuSpecificLumi', [0x000A], manufacturerOptions.lumi); + await entity.read('manuSpecificLumi', [0x000a], manufacturerOptions.lumi); }, } satisfies Tz.Converter, lumi_switch_power_outage_memory: { key: ['power_outage_memory'], convertSet: async (entity, key, value, meta) => { if (Array.isArray(meta.mapped)) throw new Error(`Not supported for groups`); - if (['SP-EUC01', 'ZNCZ04LM', 'ZNCZ15LM', 'QBCZ14LM', 'QBCZ15LM', 'SSM-U01', 'SSM-U02', 'DLKZMK11LM', 'DLKZMK12LM', - 'WS-EUK01', 'WS-EUK02', 'WS-EUK03', 'WS-EUK04', 'QBKG17LM', 'QBKG18LM', 'QBKG19LM', 'QBKG20LM', 'QBKG25LM', 'QBKG26LM', 'QBKG27LM', - 'QBKG28LM', 'QBKG29LM', 'QBKG30LM', 'QBKG31LM', 'QBKG32LM', 'QBKG33LM', 'QBKG34LM', 'QBKG38LM', 'QBKG39LM', 'QBKG40LM', 'QBKG41LM', - 'ZNDDMK11LM', 'ZNLDP13LM', 'ZNQBKG31LM', 'WS-USC02', 'WS-USC03', 'WS-USC04', 'ZNQBKG24LM', 'ZNQBKG25LM', 'JWDL001A', 'SSWQD02LM', - 'SSWQD03LM', 'XDD11LM', 'XDD12LM', 'XDD13LM', 'ZNLDP12LM', 'ZNLDP13LM', 'ZNXDD01LM', 'WS-USC01', - ].includes(meta.mapped.model)) { + if ( + [ + 'SP-EUC01', + 'ZNCZ04LM', + 'ZNCZ15LM', + 'QBCZ14LM', + 'QBCZ15LM', + 'SSM-U01', + 'SSM-U02', + 'DLKZMK11LM', + 'DLKZMK12LM', + 'WS-EUK01', + 'WS-EUK02', + 'WS-EUK03', + 'WS-EUK04', + 'QBKG17LM', + 'QBKG18LM', + 'QBKG19LM', + 'QBKG20LM', + 'QBKG25LM', + 'QBKG26LM', + 'QBKG27LM', + 'QBKG28LM', + 'QBKG29LM', + 'QBKG30LM', + 'QBKG31LM', + 'QBKG32LM', + 'QBKG33LM', + 'QBKG34LM', + 'QBKG38LM', + 'QBKG39LM', + 'QBKG40LM', + 'QBKG41LM', + 'ZNDDMK11LM', + 'ZNLDP13LM', + 'ZNQBKG31LM', + 'WS-USC02', + 'WS-USC03', + 'WS-USC04', + 'ZNQBKG24LM', + 'ZNQBKG25LM', + 'JWDL001A', + 'SSWQD02LM', + 'SSWQD03LM', + 'XDD11LM', + 'XDD12LM', + 'XDD13LM', + 'ZNLDP12LM', + 'ZNLDP13LM', + 'ZNXDD01LM', + 'WS-USC01', + ].includes(meta.mapped.model) + ) { await entity.write('manuSpecificLumi', {0x0201: {value: value ? 1 : 0, type: 0x10}}, manufacturerOptions.lumi); } else if (['ZNCZ02LM', 'QBCZ11LM', 'LLKZMK11LM'].includes(meta.mapped.model)) { - const payload = value ? - [[0xaa, 0x80, 0x05, 0xd1, 0x47, 0x07, 0x01, 0x10, 0x01], [0xaa, 0x80, 0x03, 0xd3, 0x07, 0x08, 0x01]] : - [[0xaa, 0x80, 0x05, 0xd1, 0x47, 0x09, 0x01, 0x10, 0x00], [0xaa, 0x80, 0x03, 0xd3, 0x07, 0x0a, 0x01]]; - - await entity.write('genBasic', {0xFFF0: {value: payload[0], type: 0x41}}, manufacturerOptions.lumi); - await entity.write('genBasic', {0xFFF0: {value: payload[1], type: 0x41}}, manufacturerOptions.lumi); + const payload = value + ? [ + [0xaa, 0x80, 0x05, 0xd1, 0x47, 0x07, 0x01, 0x10, 0x01], + [0xaa, 0x80, 0x03, 0xd3, 0x07, 0x08, 0x01], + ] + : [ + [0xaa, 0x80, 0x05, 0xd1, 0x47, 0x09, 0x01, 0x10, 0x00], + [0xaa, 0x80, 0x03, 0xd3, 0x07, 0x0a, 0x01], + ]; + + await entity.write('genBasic', {0xfff0: {value: payload[0], type: 0x41}}, manufacturerOptions.lumi); + await entity.write('genBasic', {0xfff0: {value: payload[1], type: 0x41}}, manufacturerOptions.lumi); } else if (['ZNCZ11LM', 'ZNCZ12LM'].includes(meta.mapped.model)) { - const payload = value ? - [0xaa, 0x80, 0x05, 0xd1, 0x47, 0x00, 0x01, 0x10, 0x01] : - [0xaa, 0x80, 0x05, 0xd1, 0x47, 0x01, 0x01, 0x10, 0x00]; + const payload = value + ? [0xaa, 0x80, 0x05, 0xd1, 0x47, 0x00, 0x01, 0x10, 0x01] + : [0xaa, 0x80, 0x05, 0xd1, 0x47, 0x01, 0x01, 0x10, 0x00]; - await entity.write('genBasic', {0xFFF0: {value: payload, type: 0x41}}, manufacturerOptions.lumi); + await entity.write('genBasic', {0xfff0: {value: payload, type: 0x41}}, manufacturerOptions.lumi); } else if (['ZNQBKG38LM', 'ZNQBKG39LM', 'ZNQBKG40LM', 'ZNQBKG41LM'].includes(meta.mapped.model)) { // Support existing syntax of a nested object just for the state field. Though it's quite silly IMO. const targetValue = isObject(value) && value.hasOwnProperty('state') ? value.state : value; const lookupState = {on: 0x01, electric_appliances_on: 0x00, electric_appliances_off: 0x02, inverted: 0x03}; - await entity.write('manuSpecificLumi', - {0x0517: {value: getFromLookup(targetValue, lookupState), type: 0x20}}, manufacturerOptions.lumi); + await entity.write( + 'manuSpecificLumi', + {0x0517: {value: getFromLookup(targetValue, lookupState), type: 0x20}}, + manufacturerOptions.lumi, + ); } else { throw new Error('Not supported'); } @@ -3852,15 +4219,62 @@ export const toZigbee = { }, convertGet: async (entity, key, meta) => { if (Array.isArray(meta.mapped)) throw new Error(`Not supported for groups`); - if (['SP-EUC01', 'ZNCZ04LM', 'ZNCZ15LM', 'QBCZ14LM', 'QBCZ15LM', 'SSM-U01', 'SSM-U02', 'DLKZMK11LM', 'DLKZMK12LM', - 'WS-EUK01', 'WS-EUK02', 'WS-EUK03', 'WS-EUK04', 'QBKG17LM', 'QBKG18LM', 'QBKG19LM', 'QBKG20LM', 'QBKG25LM', 'QBKG26LM', 'QBKG27LM', - 'QBKG28LM', 'QBKG29LM', 'QBKG30LM', 'QBKG31LM', 'QBKG32LM', 'QBKG33LM', 'QBKG34LM', 'QBKG38LM', 'QBKG39LM', 'QBKG40LM', 'QBKG41LM', - 'ZNDDMK11LM', 'ZNLDP13LM', 'ZNQBKG31LM', 'WS-USC02', 'WS-USC03', 'WS-USC04', 'ZNQBKG24LM', 'ZNQBKG25LM', 'JWDL001A', 'SSWQD02LM', - 'SSWQD03LM', 'XDD11LM', 'XDD12LM', 'XDD13LM', 'ZNLDP12LM', 'ZNLDP13LM', 'ZNXDD01LM', 'WS-USC01', - ].includes(meta.mapped.model)) { + if ( + [ + 'SP-EUC01', + 'ZNCZ04LM', + 'ZNCZ15LM', + 'QBCZ14LM', + 'QBCZ15LM', + 'SSM-U01', + 'SSM-U02', + 'DLKZMK11LM', + 'DLKZMK12LM', + 'WS-EUK01', + 'WS-EUK02', + 'WS-EUK03', + 'WS-EUK04', + 'QBKG17LM', + 'QBKG18LM', + 'QBKG19LM', + 'QBKG20LM', + 'QBKG25LM', + 'QBKG26LM', + 'QBKG27LM', + 'QBKG28LM', + 'QBKG29LM', + 'QBKG30LM', + 'QBKG31LM', + 'QBKG32LM', + 'QBKG33LM', + 'QBKG34LM', + 'QBKG38LM', + 'QBKG39LM', + 'QBKG40LM', + 'QBKG41LM', + 'ZNDDMK11LM', + 'ZNLDP13LM', + 'ZNQBKG31LM', + 'WS-USC02', + 'WS-USC03', + 'WS-USC04', + 'ZNQBKG24LM', + 'ZNQBKG25LM', + 'JWDL001A', + 'SSWQD02LM', + 'SSWQD03LM', + 'XDD11LM', + 'XDD12LM', + 'XDD13LM', + 'ZNLDP12LM', + 'ZNLDP13LM', + 'ZNXDD01LM', + 'WS-USC01', + ].includes(meta.mapped.model) + ) { await entity.read('manuSpecificLumi', [0x0201]); } else if (['ZNCZ02LM', 'QBCZ11LM', 'ZNCZ11LM', 'ZNCZ12LM'].includes(meta.mapped.model)) { - await entity.read('manuSpecificLumi', [0xFFF0]); + await entity.read('manuSpecificLumi', [0xfff0]); } else if (['ZNQBKG38LM', 'ZNQBKG39LM', 'ZNQBKG40LM', 'ZNQBKG41LM'].includes(meta.mapped.model)) { await entity.read('manuSpecificLumi', [0x0517]); } else { @@ -3871,7 +4285,7 @@ export const toZigbee = { lumi_light_power_outage_memory: { key: ['power_outage_memory'], convertSet: async (entity, key, value, meta) => { - await entity.write('genBasic', {0xFF19: {value: value ? 1 : 0, type: 0x10}}, manufacturerOptions.lumi); + await entity.write('genBasic', {0xff19: {value: value ? 1 : 0, type: 0x10}}, manufacturerOptions.lumi); return {state: {power_outage_memory: value}}; }, } satisfies Tz.Converter, @@ -3882,11 +4296,11 @@ export const toZigbee = { if (['ZNCZ04LM', 'ZNCZ12LM', 'SP-EUC01'].includes(meta.mapped.model)) { await entity.write('manuSpecificLumi', {0x0202: {value: value ? 1 : 0, type: 0x10}}, manufacturerOptions.lumi); } else if (['ZNCZ11LM'].includes(meta.mapped.model)) { - const payload = value ? - [0xaa, 0x80, 0x05, 0xd1, 0x47, 0x00, 0x02, 0x10, 0x01] : - [0xaa, 0x80, 0x05, 0xd1, 0x47, 0x01, 0x02, 0x10, 0x00]; + const payload = value + ? [0xaa, 0x80, 0x05, 0xd1, 0x47, 0x00, 0x02, 0x10, 0x01] + : [0xaa, 0x80, 0x05, 0xd1, 0x47, 0x01, 0x02, 0x10, 0x00]; - await entity.write('genBasic', {0xFFF0: {value: payload, type: 0x41}}, manufacturerOptions.lumi); + await entity.write('genBasic', {0xfff0: {value: payload, type: 0x41}}, manufacturerOptions.lumi); } else { throw new Error('Not supported'); } @@ -3916,7 +4330,7 @@ export const toZigbee = { lumi_motion_sensitivity: { key: ['motion_sensitivity'], convertSet: async (entity, key, value, meta) => { - const lookup = {'low': 1, 'medium': 2, 'high': 3}; + const lookup = {low: 1, medium: 2, high: 3}; assertString(value, key); value = value.toLowerCase(); await entity.write('manuSpecificLumi', {0x010c: {value: getFromLookup(value, lookup), type: 0x20}}, manufacturerOptions.lumi); @@ -3937,7 +4351,7 @@ export const toZigbee = { convertSet: async (entity, key, value, meta) => { assertString(value, key); value = value.toLowerCase(); - const lookup = {'undirected': 0, 'left_right': 1}; + const lookup = {undirected: 0, left_right: 1}; await entity.write('manuSpecificLumi', {0x0144: {value: getFromLookup(value, lookup), type: 0x20}}, manufacturerOptions.lumi); return {state: {monitoring_mode: value}}; }, @@ -3950,7 +4364,7 @@ export const toZigbee = { convertSet: async (entity, key, value, meta) => { assertString(value, key); value = value.toLowerCase(); - const lookup = {'far': 0, 'medium': 1, 'near': 2}; + const lookup = {far: 0, medium: 1, near: 2}; await entity.write('manuSpecificLumi', {0x0146: {value: getFromLookup(value, lookup), type: 0x20}}, manufacturerOptions.lumi); return {state: {approach_distance: value}}; }, @@ -3969,14 +4383,18 @@ export const toZigbee = { convertSet: async (entity, key, value, meta) => { if (Array.isArray(meta.mapped)) throw new Error(`Not supported for groups`); if (['ZNQBKG38LM', 'ZNQBKG39LM', 'ZNQBKG40LM', 'ZNQBKG41LM'].includes(meta.mapped.model)) { - await entity.write('manuSpecificLumi', - {0x0286: {value: getFromLookup(value, {'fast': 0x1, 'multi': 0x02}), type: 0x20}}, - manufacturerOptions.lumi); + await entity.write( + 'manuSpecificLumi', + {0x0286: {value: getFromLookup(value, {fast: 0x1, multi: 0x02}), type: 0x20}}, + manufacturerOptions.lumi, + ); return {state: {click_mode: value}}; } else { - await entity.write('manuSpecificLumi', - {0x0125: {value: getFromLookup(value, {'fast': 0x1, 'multi': 0x02}), type: 0x20}}, - manufacturerOptions.lumi); + await entity.write( + 'manuSpecificLumi', + {0x0125: {value: getFromLookup(value, {fast: 0x1, multi: 0x02}), type: 0x20}}, + manufacturerOptions.lumi, + ); return {state: {click_mode: value}}; } }, @@ -3992,8 +4410,7 @@ export const toZigbee = { lumi_switch_lock_relay_opple: { key: ['lock_relay'], convertSet: async (entity, key, value, meta) => { - await entity.write('manuSpecificLumi', {0x0285: {value: (value ? 1 : 0), type: 0x20}}, - manufacturerOptions.lumi); + await entity.write('manuSpecificLumi', {0x0285: {value: value ? 1 : 0, type: 0x20}}, manufacturerOptions.lumi); return {state: {lock_relay: value}}; }, convertGet: async (entity, key, meta) => { @@ -4009,8 +4426,11 @@ export const toZigbee = { // 1 - 'event' mode. keys send events. useful for handling const lookup = {command: 0, event: 1}; const endpoint = meta.device.getEndpoint(1); - await endpoint.write('manuSpecificLumi', {'mode': getFromLookup(value.toLowerCase(), lookup)}, - {manufacturerCode: manufacturerOptions.lumi.manufacturerCode}); + await endpoint.write( + 'manuSpecificLumi', + {mode: getFromLookup(value.toLowerCase(), lookup)}, + {manufacturerCode: manufacturerOptions.lumi.manufacturerCode}, + ); return {state: {operation_mode: value.toLowerCase()}}; }, convertGet: async (entity, key, meta) => { @@ -4023,10 +4443,10 @@ export const toZigbee = { convertSet: async (entity, key, value, meta) => { assertString(value, key); value = value.toLowerCase(); - const lookup = {'low': 0x15, 'medium': 0x0B, 'high': 0x01}; + const lookup = {low: 0x15, medium: 0x0b, high: 0x01}; const options = {...manufacturerOptions.lumi, timeout: 35000}; - await entity.write('genBasic', {0xFF0D: {value: getFromLookup(value, lookup), type: 0x20}}, options); + await entity.write('genBasic', {0xff0d: {value: getFromLookup(value, lookup), type: 0x20}}, options); return {state: {sensitivity: value}}; }, } satisfies Tz.Converter, @@ -4062,8 +4482,14 @@ export const toZigbee = { } } else if (meta.mapped.model === 'ZNCLDJ11LM') { const payload = [ - 0x07, 0x00, opts.reset_limits ? 0x01 : 0x02, 0x00, opts.reverse_direction ? 0x01 : 0x00, 0x04, - !opts.hand_open ? 0x01 : 0x00, 0x12, + 0x07, + 0x00, + opts.reset_limits ? 0x01 : 0x02, + 0x00, + opts.reverse_direction ? 0x01 : 0x00, + 0x04, + !opts.hand_open ? 0x01 : 0x00, + 0x12, ]; await entity.write('genBasic', {0x0401: {value: payload, type: 0x42}}, manufacturerOptions.lumi); @@ -4095,7 +4521,7 @@ export const toZigbee = { if (Array.isArray(meta.mapped)) throw new Error(`Not supported for groups`); if (key === 'state' && typeof value === 'string' && value.toLowerCase() === 'stop') { if (['ZNJLBL01LM', 'ZNCLDJ14LM'].includes(meta.mapped.model)) { - const payload = {'presentValue': 2}; + const payload = {presentValue: 2}; await entity.write('genMultistateOutput', payload); } else { await entity.command('closuresWindowCovering', 'stop', {}, getOptions(meta.mapped, entity)); @@ -4117,7 +4543,7 @@ export const toZigbee = { return {state: {state: 'STOP'}}; } else { - const lookup = {'open': 100, 'close': 0, 'on': 100, 'off': 0}; + const lookup = {open: 100, close: 0, on: 100, off: 0}; value = typeof value === 'string' ? value.toLowerCase() : value; if (isString(value)) { @@ -4127,10 +4553,14 @@ export const toZigbee = { value = meta.options.invert_cover ? 100 - value : value; if (['ZNCLBL01LM'].includes(meta.mapped.model)) { - await entity.command('closuresWindowCovering', 'goToLiftPercentage', {percentageliftvalue: value}, - getOptions(meta.mapped, entity)); + await entity.command( + 'closuresWindowCovering', + 'goToLiftPercentage', + {percentageliftvalue: value}, + getOptions(meta.mapped, entity), + ); } else { - const payload = {'presentValue': value}; + const payload = {presentValue: value}; await entity.write('genAnalogOutput', payload); } } @@ -4148,11 +4578,11 @@ export const toZigbee = { convertGet: async (entity, key, meta) => { if (Array.isArray(meta.mapped)) throw new Error(`Not supported for groups`); switch (meta.mapped.model) { - case 'ZNCLBL01LM': - await entity.read('manuSpecificLumi', [0x040B], manufacturerOptions.lumi); - break; - default: - throw new Error(`lumi_curtain_battery_voltage - unsupported model: ${meta.mapped.model}`); + case 'ZNCLBL01LM': + await entity.read('manuSpecificLumi', [0x040b], manufacturerOptions.lumi); + break; + default: + throw new Error(`lumi_curtain_battery_voltage - unsupported model: ${meta.mapped.model}`); } }, } satisfies Tz.Converter, @@ -4181,7 +4611,7 @@ export const toZigbee = { lumi_curtain_hooks_lock: { key: ['hooks_lock'], convertSet: async (entity, key, value, meta) => { - const lookup = {'UNLOCK': 0, 'LOCK': 1}; + const lookup = {UNLOCK: 0, LOCK: 1}; await entity.write('manuSpecificLumi', {0x0427: {value: getFromLookup(value, lookup), type: 0x20}}, manufacturerOptions.lumi); return {state: {[key]: value}}; }, @@ -4195,7 +4625,7 @@ export const toZigbee = { lumi_curtain_hand_open: { key: ['hand_open'], convertSet: async (entity, key, value, meta) => { - await entity.write('manuSpecificLumi', {'curtainHandOpen': !value}, manufacturerOptions.lumi); + await entity.write('manuSpecificLumi', {curtainHandOpen: !value}, manufacturerOptions.lumi); }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificLumi', ['curtainHandOpen'], manufacturerOptions.lumi); @@ -4204,7 +4634,7 @@ export const toZigbee = { lumi_curtain_reverse: { key: ['reverse_direction'], convertSet: async (entity, key, value, meta) => { - await entity.write('manuSpecificLumi', {'curtainReverse': value}, manufacturerOptions.lumi); + await entity.write('manuSpecificLumi', {curtainReverse: value}, manufacturerOptions.lumi); }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificLumi', ['curtainReverse'], manufacturerOptions.lumi); @@ -4214,36 +4644,35 @@ export const toZigbee = { key: ['limits_calibration'], convertSet: async (entity, key, value, meta) => { switch (value) { - case 'start': - await entity.write('manuSpecificLumi', {0x0407: {value: 0x01, type: 0x20}}, manufacturerOptions.lumi); - break; - case 'end': - await entity.write('manuSpecificLumi', {0x0407: {value: 0x02, type: 0x20}}, manufacturerOptions.lumi); - break; - case 'reset': - await entity.write('manuSpecificLumi', {0x0407: {value: 0x00, type: 0x20}}, manufacturerOptions.lumi); - // also? await entity.write('manuSpecificLumi', {0x0402: {value: 0x00, type: 0x10}}, manufacturerOptions.lumi); - break; + case 'start': + await entity.write('manuSpecificLumi', {0x0407: {value: 0x01, type: 0x20}}, manufacturerOptions.lumi); + break; + case 'end': + await entity.write('manuSpecificLumi', {0x0407: {value: 0x02, type: 0x20}}, manufacturerOptions.lumi); + break; + case 'reset': + await entity.write('manuSpecificLumi', {0x0407: {value: 0x00, type: 0x20}}, manufacturerOptions.lumi); + // also? await entity.write('manuSpecificLumi', {0x0402: {value: 0x00, type: 0x10}}, manufacturerOptions.lumi); + break; } }, } satisfies Tz.Converter, lumi_curtain_limits_calibration_ZNCLDJ14LM: { key: ['limits_calibration'], options: [ - e.enum('limits_calibration', ea.ALL, ['calibrated', 'recalibrate', 'open', 'close']) - .withDescription('Recalibrate the position limits'), + e.enum('limits_calibration', ea.ALL, ['calibrated', 'recalibrate', 'open', 'close']).withDescription('Recalibrate the position limits'), ], convertSet: async (entity, key, value, meta) => { switch (value) { - case 'recalibrate': - await entity.write('manuSpecificLumi', {'curtainCalibrated': false}, manufacturerOptions.lumi); - break; - case 'open': - await entity.write('genMultistateOutput', {'presentValue': 1}, manufacturerOptions.lumi); - break; - case 'close': - await entity.write('genMultistateOutput', {'presentValue': 0}, manufacturerOptions.lumi); - break; + case 'recalibrate': + await entity.write('manuSpecificLumi', {curtainCalibrated: false}, manufacturerOptions.lumi); + break; + case 'open': + await entity.write('genMultistateOutput', {presentValue: 1}, manufacturerOptions.lumi); + break; + case 'close': + await entity.write('genMultistateOutput', {presentValue: 0}, manufacturerOptions.lumi); + break; } }, } satisfies Tz.Converter, @@ -4253,9 +4682,9 @@ export const toZigbee = { assertString(value, key); if (Array.isArray(meta.mapped)) throw new Error(`Not supported for groups`); const attribute = ['JY-GZ-01AQ'].includes(meta.mapped.model) ? 0x013e : 0x013f; - value = (value.toLowerCase() === 'alarm') ? 15361 : 15360; + value = value.toLowerCase() === 'alarm' ? 15361 : 15360; await entity.write('manuSpecificLumi', {[`${attribute}`]: {value: [`${value}`], type: 0x23}}, manufacturerOptions.lumi); - value = (value === 15361) ? 0 : 1; + value = value === 15361 ? 0 : 1; await entity.write('manuSpecificLumi', {0x0126: {value: [`${value}`], type: 0x20}}, manufacturerOptions.lumi); }, } satisfies Tz.Converter, @@ -4286,7 +4715,7 @@ export const toZigbee = { if (['JTYJ-GD-01LM/BW', 'JTQJ-BF-01LM/BW'].includes(meta.mapped.model)) { // Timeout of 30 seconds + required (https://github.com/Koenkk/zigbee2mqtt/issues/2287) const options = {...manufacturerOptions.lumi, timeout: 35000}; - await entity.write('ssIasZone', {0xFFF1: {value: 0x03010000, type: 0x23}}, options); + await entity.write('ssIasZone', {0xfff1: {value: 0x03010000, type: 0x23}}, options); } else { await entity.write('manuSpecificLumi', {0x0127: {value: true, type: 0x10}}, manufacturerOptions.lumi); } @@ -4325,11 +4754,11 @@ export const toZigbee = { convertSet: async (entity, key, value, meta) => { assertString(value, key); value = value.toLowerCase(); - const lookup = {'low': 0x04010000, 'medium': 0x04020000, 'high': 0x04030000}; + const lookup = {low: 0x04010000, medium: 0x04020000, high: 0x04030000}; // Timeout of 30 seconds + required (https://github.com/Koenkk/zigbee2mqtt/issues/2287) const options = {...manufacturerOptions.lumi, timeout: 35000}; - await entity.write('ssIasZone', {0xFFF1: {value: getFromLookup(value, lookup), type: 0x23}}, options); + await entity.write('ssIasZone', {0xfff1: {value: getFromLookup(value, lookup), type: 0x23}}, options); return {state: {sensitivity: value}}; }, } satisfies Tz.Converter, @@ -4349,7 +4778,8 @@ export const toZigbee = { // lumi device specific lumi_smart_panel_ZNCJMB14LM: { - key: ['theme', + key: [ + 'theme', 'standby_enabled', 'beep_volume', 'lcd_brightness', @@ -4368,39 +4798,39 @@ export const toZigbee = { ], convertSet: async (entity, key, value, meta) => { if (key === 'theme') { - const lookup = {'classic': 0, 'concise': 1}; + const lookup = {classic: 0, concise: 1}; await entity.write('manuSpecificLumi', {0x0215: {value: getFromLookup(value, lookup), type: 0x20}}, manufacturerOptions.lumi); return {state: {theme: value}}; } else if (key === 'standby_enabled') { await entity.write('manuSpecificLumi', {0x0213: {value: value, type: 0x10}}, manufacturerOptions.lumi); return {state: {standby_enabled: value}}; } else if (key === 'beep_volume') { - const lookup = {'mute': 0, 'low': 1, 'medium': 2, 'high': 3}; + const lookup = {mute: 0, low: 1, medium: 2, high: 3}; await entity.write('manuSpecificLumi', {0x0212: {value: getFromLookup(value, lookup), type: 0x20}}, manufacturerOptions.lumi); return {state: {beep_volume: value}}; } else if (key === 'lcd_brightness') { await entity.write('manuSpecificLumi', {0x0211: {value: value, type: 0x20}}, manufacturerOptions.lumi); return {state: {lcd_brightness: value}}; } else if (key === 'language') { - const lookup = {'chinese': 0, 'english': 1}; + const lookup = {chinese: 0, english: 1}; await entity.write('manuSpecificLumi', {0x0210: {value: getFromLookup(value, lookup), type: 0x20}}, manufacturerOptions.lumi); return {state: {language: value}}; } else if (key === 'screen_saver_style') { - const lookup = {'classic': 1, 'analog clock': 2}; + const lookup = {classic: 1, 'analog clock': 2}; await entity.write('manuSpecificLumi', {0x0214: {value: getFromLookup(value, lookup), type: 0x20}}, manufacturerOptions.lumi); return {state: {screen_saver_style: value}}; } else if (key === 'standby_time') { await entity.write('manuSpecificLumi', {0x0216: {value: value, type: 0x23}}, manufacturerOptions.lumi); return {state: {standby_time: value}}; } else if (key === 'font_size') { - const lookup = {'small': 3, 'medium': 4, 'large': 5}; + const lookup = {small: 3, medium: 4, large: 5}; await entity.write('manuSpecificLumi', {0x0217: {value: getFromLookup(value, lookup), type: 0x20}}, manufacturerOptions.lumi); return {state: {font_size: value}}; } else if (key === 'lcd_auto_brightness_enabled') { await entity.write('manuSpecificLumi', {0x0218: {value: value, type: 0x10}}, manufacturerOptions.lumi); return {state: {lcd_auto_brightness_enabled: value}}; } else if (key === 'homepage') { - const lookup = {'scene': 0, 'feel': 1, 'thermostat': 2, 'switch': 3}; + const lookup = {scene: 0, feel: 1, thermostat: 2, switch: 3}; await entity.write('manuSpecificLumi', {0x0219: {value: getFromLookup(value, lookup), type: 0x20}}, manufacturerOptions.lumi); return {state: {homepage: value}}; } else if (key === 'screen_saver_enabled') { @@ -4410,7 +4840,7 @@ export const toZigbee = { await entity.write('manuSpecificLumi', {0x0222: {value: value, type: 0x20}}, manufacturerOptions.lumi); return {state: {standby_lcd_brightness: value}}; } else if (key === 'available_switches') { - const lookup = {'none': 0, '1': 1, '2': 2, '1 and 2': 3, '3': 4, '1 and 3': 5, '2 and 3': 6, 'all': 7}; + const lookup = {none: 0, '1': 1, '2': 2, '1 and 2': 3, '3': 4, '1 and 3': 5, '2 and 3': 6, all: 7}; await entity.write('manuSpecificLumi', {0x022b: {value: getFromLookup(value, lookup), type: 0x20}}, manufacturerOptions.lumi); return {state: {available_switches: value}}; } else if (key === 'switch_1_text_icon') { @@ -4735,7 +5165,7 @@ export const legacyFromZigbee = { const action = actionLookup[value]; if (button) { - return {action: `${button}${(action ? `_${action}` : '')}`}; + return {action: `${button}${action ? `_${action}` : ''}`}; } } else { return fromZigbee.lumi_action_multistate.convert(model, msg, publish, options, meta); diff --git a/src/lib/modernExtend.ts b/src/lib/modernExtend.ts index ee1e14eed76b0..33db6cd27c392 100644 --- a/src/lib/modernExtend.ts +++ b/src/lib/modernExtend.ts @@ -1,32 +1,58 @@ import {Zcl} from 'zigbee-herdsman'; import {ClusterDefinition} from 'zigbee-herdsman/dist/zspec/zcl/definition/tstype'; -import tz from '../converters/toZigbee'; + import fz from '../converters/fromZigbee'; +import tz from '../converters/toZigbee'; import * as globalLegacy from '../lib/legacy'; -import { - Fz, Tz, ModernExtend, Range, Zh, DefinitionOta, OnEvent, Access, - KeyValueString, KeyValue, Configure, Expose, DefinitionMeta, KeyValueAny, - DefinitionExposesFunction, -} from './types'; +import {logger} from '../lib/logger'; import {zigbeeOTA} from '../lib/ota'; import * as globalStore from '../lib/store'; import {presets as e, access as ea, options as opt, Cover} from './exposes'; import {configure as lightConfigure} from './light'; import { - getFromLookupByValue, isString, isNumber, isObject, isEndpoint, - getFromLookup, getEndpointName, assertNumber, postfixWithEndpointName, - noOccupancySince, precisionRound, batteryVoltageToPercentage, getOptions, - hasAlreadyProcessedMessage, addActionGroup, isLegacyEnabled, + Fz, + Tz, + ModernExtend, + Range, + Zh, + DefinitionOta, + OnEvent, + Access, + KeyValueString, + KeyValue, + Configure, + Expose, + DefinitionMeta, + KeyValueAny, + DefinitionExposesFunction, +} from './types'; +import { + getFromLookupByValue, + isString, + isNumber, + isObject, + isEndpoint, + getFromLookup, + getEndpointName, + assertNumber, + postfixWithEndpointName, + noOccupancySince, + precisionRound, + batteryVoltageToPercentage, + getOptions, + hasAlreadyProcessedMessage, + addActionGroup, + isLegacyEnabled, } from './utils'; -import {logger} from '../lib/logger'; function getEndpointsWithCluster(device: Zh.Device, cluster: string | number, type: 'input' | 'output') { if (!device.endpoints) { throw new Error(device.ieeeAddr + ' ' + device.endpoints); } - const endpoints = (type === 'input' ? - device.endpoints.filter((ep) => ep.getInputClusters().find((c) => isNumber(cluster) ? c.ID === cluster : c.name === cluster)) : - device.endpoints.filter((ep) => ep.getOutputClusters().find((c) => isNumber(cluster) ? c.ID === cluster : c.name === cluster))); + const endpoints = + type === 'input' + ? device.endpoints.filter((ep) => ep.getInputClusters().find((c) => (isNumber(cluster) ? c.ID === cluster : c.name === cluster))) + : device.endpoints.filter((ep) => ep.getOutputClusters().find((c) => (isNumber(cluster) ? c.ID === cluster : c.name === cluster))); if (endpoints.length === 0) { throw new Error(`Device ${device.ieeeAddr} has no ${type} cluster ${cluster}`); } @@ -34,7 +60,7 @@ function getEndpointsWithCluster(device: Zh.Device, cluster: string | number, ty } export const timeLookup = { - 'MAX': 65000, + MAX: 65000, '4_HOURS': 14400, '1_HOUR': 3600, '30_MINUTES': 1800, @@ -44,12 +70,12 @@ export const timeLookup = { '10_SECONDS': 10, '5_SECONDS': 5, '1_SECOND': 1, - 'MIN': 0, + MIN: 0, }; type ReportingConfigTime = number | keyof typeof timeLookup; -type ReportingConfigAttribute = string | number | {ID: number, type: number}; -type ReportingConfig = {min: ReportingConfigTime, max: ReportingConfigTime, change: number | [number, number], attribute: ReportingConfigAttribute} +type ReportingConfigAttribute = string | number | {ID: number; type: number}; +type ReportingConfig = {min: ReportingConfigTime; max: ReportingConfigTime; change: number | [number, number]; attribute: ReportingConfigAttribute}; export type ReportingConfigWithoutAttribute = Omit; function convertReportingConfigTime(time: ReportingConfigTime): number { @@ -62,8 +88,12 @@ function convertReportingConfigTime(time: ReportingConfigTime): number { } export async function setupAttributes( - entity: Zh.Device | Zh.Endpoint, coordinatorEndpoint: Zh.Endpoint, cluster: string | number, config: ReportingConfig[], - configureReporting: boolean=true, read: boolean=true, + entity: Zh.Device | Zh.Endpoint, + coordinatorEndpoint: Zh.Endpoint, + cluster: string | number, + config: ReportingConfig[], + configureReporting: boolean = true, + read: boolean = true, ) { const endpoints = isEndpoint(entity) ? [entity] : getEndpointsWithCluster(entity, cluster, 'input'); const ieeeAddr = isEndpoint(entity) ? entity.deviceIeeeAddress : entity.ieeeAddr; @@ -74,18 +104,24 @@ export async function setupAttributes( ); if (configureReporting) { await endpoint.bind(cluster, coordinatorEndpoint); - await endpoint.configureReporting(cluster, config.map((a) => ({ - minimumReportInterval: convertReportingConfigTime(a.min), - maximumReportInterval: convertReportingConfigTime(a.max), - reportableChange: a.change, - attribute: a.attribute, - }))); + await endpoint.configureReporting( + cluster, + config.map((a) => ({ + minimumReportInterval: convertReportingConfigTime(a.min), + maximumReportInterval: convertReportingConfigTime(a.max), + reportableChange: a.change, + attribute: a.attribute, + })), + ); } if (read) { try { // Don't fail configuration if reading this attribute fails // https://github.com/Koenkk/zigbee-herdsman-converters/pull/7074 - await endpoint.read(cluster, config.map((a) => isString(a) ? a : (isObject(a.attribute) ? a.attribute.ID : a.attribute))); + await endpoint.read( + cluster, + config.map((a) => (isString(a) ? a : isObject(a.attribute) ? a.attribute.ID : a.attribute)), + ); } catch (e) { logger.debug(`Reading attribute failed: ${e}`, 'zhc:setupattribute'); } @@ -94,7 +130,10 @@ export async function setupAttributes( } export function setupConfigureForReporting( - cluster: string | number, attribute: ReportingConfigAttribute, config: ReportingConfigWithoutAttribute, access: Access, + cluster: string | number, + attribute: ReportingConfigAttribute, + config: ReportingConfigWithoutAttribute, + access: Access, endpointNames?: string[], ) { const configureReporting = !!config; @@ -138,9 +177,7 @@ export function setupConfigureForBinding(cluster: string | number, clusterType: return configure; } -export function setupConfigureForReading( - cluster: string | number, attributes: (string | number)[], endpointNames?: string[], -) { +export function setupConfigureForReading(cluster: string | number, attributes: (string | number)[], endpointNames?: string[]) { const configure: Configure = async (device, coordinatorEndpoint, definition) => { if (endpointNames) { const definitionEndpoints = definition.endpoint(device); @@ -182,117 +219,135 @@ export function forcePowerSource(args: {powerSource: 'Mains (single phase)' | 'B } export interface LinkQualityArgs { - reporting?: boolean, attribute?: string | {ID: number, type: number}, reportingConfig?: ReportingConfigWithoutAttribute + reporting?: boolean; + attribute?: string | {ID: number; type: number}; + reportingConfig?: ReportingConfigWithoutAttribute; } export function linkQuality(args?: LinkQualityArgs): ModernExtend { args = {reporting: false, attribute: 'modelId', reportingConfig: {min: '1_HOUR', max: '4_HOURS', change: 0}, ...args}; const exposes: Expose[] = [ - e.numeric('linkquality', ea.STATE).withUnit('lqi').withDescription('Link quality (signal strength)') - .withValueMin(0).withValueMax(255).withCategory('diagnostic'), + e + .numeric('linkquality', ea.STATE) + .withUnit('lqi') + .withDescription('Link quality (signal strength)') + .withValueMin(0) + .withValueMax(255) + .withCategory('diagnostic'), ]; - const fromZigbee: Fz.Converter[] = [{ - cluster: 'genBasic', - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - return {linkquality: msg.linkquality}; + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'genBasic', + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + return {linkquality: msg.linkquality}; + }, }, - }]; + ]; const result: ModernExtend = {exposes, fromZigbee, isModernExtend: true}; if (args.reporting) { - result.configure = [ - setupConfigureForReporting('genBasic', args.attribute, args.reportingConfig, ea.GET), - ]; + result.configure = [setupConfigureForReporting('genBasic', args.attribute, args.reportingConfig, ea.GET)]; } return result; } export interface BatteryArgs { - voltageToPercentage?: string | {min: number, max: number}, dontDividePercentage?: boolean, - percentage?: boolean, voltage?: boolean, lowStatus?: boolean, - percentageReportingConfig?: ReportingConfigWithoutAttribute, percentageReporting?: boolean, - voltageReportingConfig?: ReportingConfigWithoutAttribute, voltageReporting?: boolean, + voltageToPercentage?: string | {min: number; max: number}; + dontDividePercentage?: boolean; + percentage?: boolean; + voltage?: boolean; + lowStatus?: boolean; + percentageReportingConfig?: ReportingConfigWithoutAttribute; + percentageReporting?: boolean; + voltageReportingConfig?: ReportingConfigWithoutAttribute; + voltageReporting?: boolean; } export function battery(args?: BatteryArgs): ModernExtend { args = { - percentage: true, voltage: false, lowStatus: false, percentageReporting: true, voltageReporting: false, dontDividePercentage: false, - percentageReportingConfig: {min: '1_HOUR', max: 'MAX', change: 10}, voltageReportingConfig: {min: '1_HOUR', max: 'MAX', change: 10}, ...args, + percentage: true, + voltage: false, + lowStatus: false, + percentageReporting: true, + voltageReporting: false, + dontDividePercentage: false, + percentageReportingConfig: {min: '1_HOUR', max: 'MAX', change: 10}, + voltageReportingConfig: {min: '1_HOUR', max: 'MAX', change: 10}, + ...args, }; const exposes: Expose[] = []; if (args.percentage) { exposes.push( - e.numeric('battery', ea.STATE_GET).withUnit('%') + e + .numeric('battery', ea.STATE_GET) + .withUnit('%') .withDescription('Remaining battery in %') - .withValueMin(0).withValueMax(100).withCategory('diagnostic'), + .withValueMin(0) + .withValueMax(100) + .withCategory('diagnostic'), ); } if (args.voltage) { exposes.push( - e.numeric('voltage', ea.STATE_GET).withUnit('mV') - .withDescription('Reported battery voltage in millivolts').withCategory('diagnostic'), + e.numeric('voltage', ea.STATE_GET).withUnit('mV').withDescription('Reported battery voltage in millivolts').withCategory('diagnostic'), ); } if (args.lowStatus) { - exposes.push( - e.binary('battery_low', ea.STATE, true, false) - .withDescription('Empty battery indicator').withCategory('diagnostic'), - ); + exposes.push(e.binary('battery_low', ea.STATE, true, false).withDescription('Empty battery indicator').withCategory('diagnostic')); } - const fromZigbee: Fz.Converter[] = [{ - cluster: 'genPowerCfg', - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - const payload: KeyValueAny = {}; - if (msg.data.hasOwnProperty('batteryPercentageRemaining') && (msg.data['batteryPercentageRemaining'] < 255)) { - // Some devices do not comply to the ZCL and report a - // batteryPercentageRemaining of 100 when the battery is full (should be 200). - const dontDividePercentage = args.dontDividePercentage; - let percentage = msg.data['batteryPercentageRemaining']; - percentage = dontDividePercentage ? percentage : percentage / 2; - if (args.percentage) payload.battery = precisionRound(percentage, 2); - } + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'genPowerCfg', + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + const payload: KeyValueAny = {}; + if (msg.data.hasOwnProperty('batteryPercentageRemaining') && msg.data['batteryPercentageRemaining'] < 255) { + // Some devices do not comply to the ZCL and report a + // batteryPercentageRemaining of 100 when the battery is full (should be 200). + const dontDividePercentage = args.dontDividePercentage; + let percentage = msg.data['batteryPercentageRemaining']; + percentage = dontDividePercentage ? percentage : percentage / 2; + if (args.percentage) payload.battery = precisionRound(percentage, 2); + } - if (msg.data.hasOwnProperty('batteryVoltage') && (msg.data['batteryVoltage'] < 255)) { - // Deprecated: voltage is = mV now but should be V - if (args.voltage) payload.voltage = msg.data['batteryVoltage'] * 100; + if (msg.data.hasOwnProperty('batteryVoltage') && msg.data['batteryVoltage'] < 255) { + // Deprecated: voltage is = mV now but should be V + if (args.voltage) payload.voltage = msg.data['batteryVoltage'] * 100; - if (args.voltageToPercentage) { - payload.battery = batteryVoltageToPercentage(payload.voltage, args.voltageToPercentage); + if (args.voltageToPercentage) { + payload.battery = batteryVoltageToPercentage(payload.voltage, args.voltageToPercentage); + } } - } - if (msg.data.hasOwnProperty('batteryAlarmState')) { - const battery1Low = ( - msg.data.batteryAlarmState & 1<<0 || - msg.data.batteryAlarmState & 1<<1 || - msg.data.batteryAlarmState & 1<<2 || - msg.data.batteryAlarmState & 1<<3 - ) > 0; - const battery2Low = ( - msg.data.batteryAlarmState & 1<<10 || - msg.data.batteryAlarmState & 1<<11 || - msg.data.batteryAlarmState & 1<<12 || - msg.data.batteryAlarmState & 1<<13 - ) > 0; - const battery3Low = ( - msg.data.batteryAlarmState & 1<<20 || - msg.data.batteryAlarmState & 1<<21 || - msg.data.batteryAlarmState & 1<<22 || - msg.data.batteryAlarmState & 1<<23 - ) > 0; - if (args.lowStatus) payload.battery_low = battery1Low || battery2Low || battery3Low; - } + if (msg.data.hasOwnProperty('batteryAlarmState')) { + const battery1Low = + (msg.data.batteryAlarmState & (1 << 0) || + msg.data.batteryAlarmState & (1 << 1) || + msg.data.batteryAlarmState & (1 << 2) || + msg.data.batteryAlarmState & (1 << 3)) > 0; + const battery2Low = + (msg.data.batteryAlarmState & (1 << 10) || + msg.data.batteryAlarmState & (1 << 11) || + msg.data.batteryAlarmState & (1 << 12) || + msg.data.batteryAlarmState & (1 << 13)) > 0; + const battery3Low = + (msg.data.batteryAlarmState & (1 << 20) || + msg.data.batteryAlarmState & (1 << 21) || + msg.data.batteryAlarmState & (1 << 22) || + msg.data.batteryAlarmState & (1 << 23)) > 0; + if (args.lowStatus) payload.battery_low = battery1Low || battery2Low || battery3Low; + } - return payload; + return payload; + }, }, - }]; + ]; const toZigbee: Tz.Converter[] = [ { @@ -319,24 +374,10 @@ export function battery(args?: BatteryArgs): ModernExtend { if (args.percentageReporting || args.voltageReporting) { const configure: Configure[] = []; if (args.percentageReporting) { - configure.push( - setupConfigureForReporting( - 'genPowerCfg', - 'batteryPercentageRemaining', - args.percentageReportingConfig, - ea.STATE_GET, - ), - ); + configure.push(setupConfigureForReporting('genPowerCfg', 'batteryPercentageRemaining', args.percentageReportingConfig, ea.STATE_GET)); } if (args.voltageReporting) { - configure.push( - setupConfigureForReporting( - 'genPowerCfg', - 'batteryVoltage', - args.voltageReportingConfig, - ea.STATE_GET, - ), - ); + configure.push(setupConfigureForReporting('genPowerCfg', 'batteryVoltage', args.voltageReportingConfig, ea.STATE_GET)); } result.configure = configure; } @@ -368,40 +409,52 @@ export function deviceTemperature(args?: Partial) { export function identify(args?: {isSleepy: boolean}): ModernExtend { args = {isSleepy: false, ...args}; const normal: Expose = e.enum('identify', ea.SET, ['identify']).withDescription('Initiate device identification').withCategory('config'); - const sleepy: Expose = e.enum('identify', ea.SET, ['identify']) - .withDescription('Initiate device identification. This device is asleep by default.' + - 'You may need to wake it up first before sending the identify command.') + const sleepy: Expose = e + .enum('identify', ea.SET, ['identify']) + .withDescription( + 'Initiate device identification. This device is asleep by default.' + + 'You may need to wake it up first before sending the identify command.', + ) .withCategory('config'); const exposes: Expose[] = args.isSleepy ? [sleepy] : [normal]; - const identifyTimeout = e.numeric('identify_timeout', ea.SET) - .withDescription('Sets the duration of the identification procedure in seconds (i.e., how long the device would flash).' + - 'The value ranges from 1 to 30 seconds (default: 3).') - .withValueMin(1).withValueMax(30); - - const toZigbee: Tz.Converter[] = [{ - key: ['identify'], - options: [identifyTimeout], - convertSet: async (entity, key, value, meta) => { - const identifyTimeout = meta.options.identify_timeout ?? 3; - await entity.command('genIdentify', 'identify', {identifytime: identifyTimeout}, getOptions(meta.mapped, entity)); + const identifyTimeout = e + .numeric('identify_timeout', ea.SET) + .withDescription( + 'Sets the duration of the identification procedure in seconds (i.e., how long the device would flash).' + + 'The value ranges from 1 to 30 seconds (default: 3).', + ) + .withValueMin(1) + .withValueMax(30); + + const toZigbee: Tz.Converter[] = [ + { + key: ['identify'], + options: [identifyTimeout], + convertSet: async (entity, key, value, meta) => { + const identifyTimeout = meta.options.identify_timeout ?? 3; + await entity.command('genIdentify', 'identify', {identifytime: identifyTimeout}, getOptions(meta.mapped, entity)); + }, }, - }]; + ]; return {exposes, toZigbee, isModernExtend: true}; } export interface OnOffArgs { - powerOnBehavior?: boolean, ota?: DefinitionOta, skipDuplicateTransaction?: boolean, endpointNames?: string[], - configureReporting?: boolean, + powerOnBehavior?: boolean; + ota?: DefinitionOta; + skipDuplicateTransaction?: boolean; + endpointNames?: string[]; + configureReporting?: boolean; } export function onOff(args?: OnOffArgs): ModernExtend { args = {powerOnBehavior: true, skipDuplicateTransaction: false, configureReporting: true, ...args}; const exposes: Expose[] = args.endpointNames ? args.endpointNames.map((ep) => e.switch().withEndpoint(ep)) : [e.switch()]; - const fromZigbee: Fz.Converter[] = [(args.skipDuplicateTransaction ? fz.on_off_skip_duplicate_transaction : fz.on_off)]; + const fromZigbee: Fz.Converter[] = [args.skipDuplicateTransaction ? fz.on_off_skip_duplicate_transaction : fz.on_off]; const toZigbee: Tz.Converter[] = [tz.on_off]; if (args.powerOnBehavior) { @@ -419,8 +472,13 @@ export function onOff(args?: OnOffArgs): ModernExtend { if (args.powerOnBehavior) { try { // Don't fail configure if reading this attribute fails, some devices don't support it. - await setupAttributes(device, coordinatorEndpoint, 'genOnOff', - [{attribute: 'startUpOnOff', min: 'MIN', max: 'MAX', change: 1}], false); + await setupAttributes( + device, + coordinatorEndpoint, + 'genOnOff', + [{attribute: 'startUpOnOff', min: 'MIN', max: 'MAX', change: 1}], + false, + ); } catch (e) { if (e.message.includes('UNSUPPORTED_ATTRIBUTE')) { logger.debug('Reading startUpOnOff failed, this features is unsupported', 'zhc:onoff'); @@ -436,7 +494,10 @@ export function onOff(args?: OnOffArgs): ModernExtend { } export interface CommandsOnOffArgs { - commands?: ('on' | 'off' | 'toggle')[], bind?: boolean, endpointNames?: string[], legacyAction?: boolean, + commands?: ('on' | 'off' | 'toggle')[]; + bind?: boolean; + endpointNames?: string[]; + legacyAction?: boolean; } export function commandsOnOff(args?: CommandsOnOffArgs): ModernExtend { args = {commands: ['on', 'off', 'toggle'], bind: true, legacyAction: false, ...args}; @@ -444,15 +505,13 @@ export function commandsOnOff(args?: CommandsOnOffArgs): ModernExtend { if (args.endpointNames) { actions = args.commands.map((c) => args.endpointNames.map((e) => `${c}_${e}`)).flat(); } - const exposes: Expose[] = [ - e.enum('action', ea.STATE, actions).withDescription('Triggered action (e.g. a button click)'), - ]; + const exposes: Expose[] = [e.enum('action', ea.STATE, actions).withDescription('Triggered action (e.g. a button click)')]; const actionPayloadLookup: KeyValueString = { - 'commandOn': 'on', - 'commandOff': 'off', - 'commandOffWithEffect': 'off', - 'commandToggle': 'toggle', + commandOn: 'on', + commandOff: 'off', + commandOffWithEffect: 'off', + commandToggle: 'toggle', }; const fromZigbee: Fz.Converter[] = [ @@ -491,13 +550,13 @@ export function customTimeResponse(start: '1970_UTC' | '2000_LOCAL'): ModernExte if (frame.isCluster('genTime')) { const payload: KeyValue = {}; if (start === '1970_UTC') { - const time = Math.round(((new Date()).getTime()) / 1000); + const time = Math.round(new Date().getTime() / 1000); payload.time = time; - payload.localTime = time - (new Date()).getTimezoneOffset() * 60; + payload.localTime = time - new Date().getTimezoneOffset() * 60; } else if (start === '2000_LOCAL') { const oneJanuary2000 = new Date('January 01, 2000 00:00:00 UTC+00:00').getTime(); - const secondsUTC = Math.round(((new Date()).getTime() - oneJanuary2000) / 1000); - payload.time = secondsUTC - (new Date()).getTimezoneOffset() * 60; + const secondsUTC = Math.round((new Date().getTime() - oneJanuary2000) / 1000); + payload.time = secondsUTC - new Date().getTimezoneOffset() * 60; } endpoint.readResponse('genTime', frame.header.transactionSequenceNumber, payload).catch((e) => { logger.warning(`Custom time response failed for '${device.ieeeAddr}': ${e}`, 'zhc:customtimeresponse'); @@ -625,38 +684,45 @@ export function soilMoisture(args?: Partial) { } export interface OccupancyArgs { - pirConfig?: ('otu_delay' | 'uto_delay' | 'uto_threshold')[], - ultrasonicConfig?: ('otu_delay' | 'uto_delay' | 'uto_threshold')[], - contactConfig?: ('otu_delay' | 'uto_delay' | 'uto_threshold')[], - reporting?: boolean, reportingConfig?: ReportingConfigWithoutAttribute, endpointNames?: string[], + pirConfig?: ('otu_delay' | 'uto_delay' | 'uto_threshold')[]; + ultrasonicConfig?: ('otu_delay' | 'uto_delay' | 'uto_threshold')[]; + contactConfig?: ('otu_delay' | 'uto_delay' | 'uto_threshold')[]; + reporting?: boolean; + reportingConfig?: ReportingConfigWithoutAttribute; + endpointNames?: string[]; } export function occupancy(args?: OccupancyArgs): ModernExtend { args = {reporting: true, reportingConfig: {min: '10_SECONDS', max: '1_MINUTE', change: 0}, ...args}; const templateExposes: Expose[] = [e.occupancy().withAccess(ea.STATE_GET)]; - const exposes: (Expose | DefinitionExposesFunction)[] = args.endpointNames ? - templateExposes.map((exp) => args.endpointNames.map((ep) => exp.withEndpoint(ep))).flat() : templateExposes; + const exposes: (Expose | DefinitionExposesFunction)[] = args.endpointNames + ? templateExposes.map((exp) => args.endpointNames.map((ep) => exp.withEndpoint(ep))).flat() + : templateExposes; - const fromZigbee: Fz.Converter[] = [{ - cluster: 'msOccupancySensing', - type: ['attributeReport', 'readResponse'], - options: [opt.no_occupancy_since_false()], - convert: (model, msg, publish, options, meta) => { - if ('occupancy' in msg.data && (!args.endpointNames || args.endpointNames.includes(getEndpointName(msg, model, meta).toString()))) { - const propertyName = postfixWithEndpointName('occupancy', msg, model, meta); - const payload = {[propertyName]: (msg.data['occupancy'] & 1) > 0}; - noOccupancySince(msg.endpoint, options, publish, payload[propertyName] ? 'stop' : 'start'); - return payload; - } + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'msOccupancySensing', + type: ['attributeReport', 'readResponse'], + options: [opt.no_occupancy_since_false()], + convert: (model, msg, publish, options, meta) => { + if ('occupancy' in msg.data && (!args.endpointNames || args.endpointNames.includes(getEndpointName(msg, model, meta).toString()))) { + const propertyName = postfixWithEndpointName('occupancy', msg, model, meta); + const payload = {[propertyName]: (msg.data['occupancy'] & 1) > 0}; + noOccupancySince(msg.endpoint, options, publish, payload[propertyName] ? 'stop' : 'start'); + return payload; + } + }, }, - }]; + ]; - const toZigbee: Tz.Converter[] = [{ - key: ['occupancy'], - convertGet: async (entity, key, meta) => { - await entity.read('msOccupancySensing', ['occupancy']); + const toZigbee: Tz.Converter[] = [ + { + key: ['occupancy'], + convertGet: async (entity, key, meta) => { + await entity.read('msOccupancySensing', ['occupancy']); + }, }, - }]; + ]; const settingsExtends: ModernExtend[] = []; @@ -672,99 +738,117 @@ export function occupancy(args?: OccupancyArgs): ModernExtend { if (args.pirConfig) { if (args.pirConfig.includes('otu_delay')) { - settingsExtends.push(numeric({ - name: 'pir_otu_delay', - attribute: 'pirOToUDelay', - valueMin: 0, - valueMax: 65534, - ...settingsTemplate, - })); + settingsExtends.push( + numeric({ + name: 'pir_otu_delay', + attribute: 'pirOToUDelay', + valueMin: 0, + valueMax: 65534, + ...settingsTemplate, + }), + ); attributesForReading.push('pirOToUDelay'); } if (args.pirConfig.includes('uto_delay')) { - settingsExtends.push(numeric({ - name: 'pir_uto_delay', - attribute: 'pirUToODelay', - valueMin: 0, - valueMax: 65534, - ...settingsTemplate, - })); + settingsExtends.push( + numeric({ + name: 'pir_uto_delay', + attribute: 'pirUToODelay', + valueMin: 0, + valueMax: 65534, + ...settingsTemplate, + }), + ); attributesForReading.push('pirUToODelay'); } if (args.pirConfig.includes('uto_threshold')) { - settingsExtends.push(numeric({ - name: 'pir_uto_threshold', - attribute: 'pirUToOThreshold', - valueMin: 1, - valueMax: 254, - ...settingsTemplate, - })); + settingsExtends.push( + numeric({ + name: 'pir_uto_threshold', + attribute: 'pirUToOThreshold', + valueMin: 1, + valueMax: 254, + ...settingsTemplate, + }), + ); attributesForReading.push('pirUToOThreshold'); } } if (args.ultrasonicConfig) { if (args.pirConfig.includes('otu_delay')) { - settingsExtends.push(numeric({ - name: 'ultrasonic_otu_delay', - attribute: 'ultrasonicOToUDelay', - valueMin: 0, - valueMax: 65534, - ...settingsTemplate, - })); + settingsExtends.push( + numeric({ + name: 'ultrasonic_otu_delay', + attribute: 'ultrasonicOToUDelay', + valueMin: 0, + valueMax: 65534, + ...settingsTemplate, + }), + ); attributesForReading.push('ultrasonicOToUDelay'); } if (args.pirConfig.includes('uto_delay')) { - settingsExtends.push(numeric({ - name: 'ultrasonic_uto_delay', - attribute: 'ultrasonicUToODelay', - valueMin: 0, - valueMax: 65534, - ...settingsTemplate, - })); + settingsExtends.push( + numeric({ + name: 'ultrasonic_uto_delay', + attribute: 'ultrasonicUToODelay', + valueMin: 0, + valueMax: 65534, + ...settingsTemplate, + }), + ); attributesForReading.push('ultrasonicUToODelay'); } if (args.pirConfig.includes('uto_threshold')) { - settingsExtends.push(numeric({ - name: 'ultrasonic_uto_threshold', - attribute: 'ultrasonicUToOThreshold', - valueMin: 1, - valueMax: 254, - ...settingsTemplate, - })); + settingsExtends.push( + numeric({ + name: 'ultrasonic_uto_threshold', + attribute: 'ultrasonicUToOThreshold', + valueMin: 1, + valueMax: 254, + ...settingsTemplate, + }), + ); attributesForReading.push('ultrasonicUToOThreshold'); } } if (args.contactConfig) { if (args.pirConfig.includes('otu_delay')) { - settingsExtends.push(numeric({ - name: 'contact_otu_delay', - attribute: 'contactOToUDelay', - valueMin: 0, - valueMax: 65534, - ...settingsTemplate, - })); + settingsExtends.push( + numeric({ + name: 'contact_otu_delay', + attribute: 'contactOToUDelay', + valueMin: 0, + valueMax: 65534, + ...settingsTemplate, + }), + ); attributesForReading.push('contactOToUDelay'); } if (args.pirConfig.includes('uto_delay')) { - settingsExtends.push(numeric({ - name: 'contact_uto_delay', - attribute: 'contactUToODelay', - valueMin: 0, - valueMax: 65534, - ...settingsTemplate, - })); + settingsExtends.push( + numeric({ + name: 'contact_uto_delay', + attribute: 'contactUToODelay', + valueMin: 0, + valueMax: 65534, + ...settingsTemplate, + }), + ); attributesForReading.push('contactUToODelay'); } if (args.pirConfig.includes('uto_threshold')) { - settingsExtends.push(numeric({ - name: 'contact_uto_threshold', - attribute: 'contactUToOThreshold', - valueMin: 1, - valueMax: 254, - ...settingsTemplate, - })); + settingsExtends.push( + numeric({ + name: 'contact_uto_threshold', + attribute: 'contactUToOThreshold', + valueMin: 1, + valueMax: 254, + ...settingsTemplate, + }), + ); attributesForReading.push('contactUToOThreshold'); } } @@ -778,9 +862,7 @@ export function occupancy(args?: OccupancyArgs): ModernExtend { if (attributesForReading.length > 0) configure.push(setupConfigureForReading('msOccupancySensing', attributesForReading, args.endpointNames)); if (args.reporting) { - configure.push( - setupConfigureForReporting('msOccupancySensing', 'occupancy', args.reportingConfig, ea.STATE_GET, args.endpointNames), - ); + configure.push(setupConfigureForReporting('msOccupancySensing', 'occupancy', args.reportingConfig, ea.STATE_GET, args.endpointNames)); } return {exposes, fromZigbee, toZigbee, configure, isModernExtend: true}; @@ -819,25 +901,42 @@ export function pm25(args?: Partial): ModernExtend { // #region Lighting export interface LightArgs { - effect?: boolean, powerOnBehavior?: boolean, colorTemp?: {startup?: boolean, range: Range}, - color?: boolean | {modes?: ('xy' | 'hs')[], applyRedFix?: boolean, enhancedHue?: boolean}, turnsOffAtBrightness1?: boolean, - configureReporting?: boolean, endpointNames?: string[], ota?: DefinitionOta, levelConfig?: {disabledFeatures?: string[]}, + effect?: boolean; + powerOnBehavior?: boolean; + colorTemp?: {startup?: boolean; range: Range}; + color?: boolean | {modes?: ('xy' | 'hs')[]; applyRedFix?: boolean; enhancedHue?: boolean}; + turnsOffAtBrightness1?: boolean; + configureReporting?: boolean; + endpointNames?: string[]; + ota?: DefinitionOta; + levelConfig?: {disabledFeatures?: string[]}; } export function light(args?: LightArgs): ModernExtend { args = {effect: true, powerOnBehavior: true, configureReporting: false, ...args}; if (args.colorTemp) { args.colorTemp = {startup: true, ...args.colorTemp}; } - const argsColor = args.color ? { - modes: ['xy'] satisfies ('xy' | 'hs')[], applyRedFix: false, enhancedHue: true, ...(isObject(args.color) ? args.color : {}), - } : false; - - const lightExpose = args.endpointNames ? - args.endpointNames.map((ep) => e.light().withBrightness().withEndpoint(ep)) : [e.light().withBrightness()]; + const argsColor = args.color + ? { + modes: ['xy'] satisfies ('xy' | 'hs')[], + applyRedFix: false, + enhancedHue: true, + ...(isObject(args.color) ? args.color : {}), + } + : false; + + const lightExpose = args.endpointNames + ? args.endpointNames.map((ep) => e.light().withBrightness().withEndpoint(ep)) + : [e.light().withBrightness()]; const fromZigbee: Fz.Converter[] = [fz.on_off, fz.brightness, fz.ignore_basic_report, fz.level_config]; const toZigbee: Tz.Converter[] = [ - tz.light_onoff_brightness, tz.ignore_transition, tz.level_config, tz.ignore_rate, tz.light_brightness_move, tz.light_brightness_step, + tz.light_onoff_brightness, + tz.ignore_transition, + tz.level_config, + tz.ignore_rate, + tz.light_brightness_move, + tz.light_brightness_step, ]; const meta: DefinitionMeta = {}; @@ -904,11 +1003,13 @@ export function light(args?: LightArgs): ModernExtend { if (args.configureReporting) { await setupAttributes(device, coordinatorEndpoint, 'genOnOff', [{attribute: 'onOff', min: 'MIN', max: 'MAX', change: 1}]); - await setupAttributes(device, coordinatorEndpoint, 'genLevelCtrl', - [{attribute: 'currentLevel', min: '10_SECONDS', max: 'MAX', change: 1}]); + await setupAttributes(device, coordinatorEndpoint, 'genLevelCtrl', [ + {attribute: 'currentLevel', min: '10_SECONDS', max: 'MAX', change: 1}, + ]); if (args.colorTemp) { - await setupAttributes(device, coordinatorEndpoint, 'lightingColorCtrl', - [{attribute: 'colorTemperature', min: '10_SECONDS', max: 'MAX', change: 1}]); + await setupAttributes(device, coordinatorEndpoint, 'lightingColorCtrl', [ + {attribute: 'colorTemperature', min: '10_SECONDS', max: 'MAX', change: 1}, + ]); } if (argsColor) { const attributes: ReportingConfig[] = []; @@ -937,14 +1038,31 @@ export function light(args?: LightArgs): ModernExtend { export interface CommandsLevelCtrl { commands?: ( - 'brightness_move_to_level' | 'brightness_move_up' | 'brightness_move_down' | 'brightness_step_up' | 'brightness_step_down' | 'brightness_stop' - )[], - bind?: boolean, endpointNames?: string[], legacyAction?: boolean, + | 'brightness_move_to_level' + | 'brightness_move_up' + | 'brightness_move_down' + | 'brightness_step_up' + | 'brightness_step_down' + | 'brightness_stop' + )[]; + bind?: boolean; + endpointNames?: string[]; + legacyAction?: boolean; } export function commandsLevelCtrl(args?: CommandsLevelCtrl): ModernExtend { - args = {commands: [ - 'brightness_move_to_level', 'brightness_move_up', 'brightness_move_down', 'brightness_step_up', 'brightness_step_down', 'brightness_stop', - ], bind: true, legacyAction: false, ...args}; + args = { + commands: [ + 'brightness_move_to_level', + 'brightness_move_up', + 'brightness_move_down', + 'brightness_step_up', + 'brightness_step_down', + 'brightness_stop', + ], + bind: true, + legacyAction: false, + ...args, + }; let actions: string[] = args.commands; if (args.endpointNames) { actions = args.commands.map((c) => args.endpointNames.map((e) => `${c}_${e}`)).flat(); @@ -953,12 +1071,7 @@ export function commandsLevelCtrl(args?: CommandsLevelCtrl): ModernExtend { e.enum('action', ea.STATE, actions).withDescription('Triggered action (e.g. a button click)').withCategory('diagnostic'), ]; - const fromZigbee: Fz.Converter[] = [ - fz.command_move_to_level, - fz.command_move, - fz.command_step, - fz.command_stop, - ]; + const fromZigbee: Fz.Converter[] = [fz.command_move_to_level, fz.command_move, fz.command_step, fz.command_stop]; if (args.legacyAction) { // Legacy converters with removed hasAlreadyProcessedMessage and redirects @@ -1009,27 +1122,29 @@ export function commandsLevelCtrl(args?: CommandsLevelCtrl): ModernExtend { return result; } -export type ColorCtrlCommand = 'color_temperature_move_stop' | - 'color_temperature_move_up' | - 'color_temperature_move_down' | - 'color_temperature_step_up' | - 'color_temperature_step_down' | - 'enhanced_move_to_hue_and_saturation' | - 'move_to_hue_and_saturation' | - 'color_hue_step_up' | - 'color_hue_step_down' | - 'color_saturation_step_up' | - 'color_saturation_step_down' | - 'color_loop_set' | - 'color_temperature_move' | - 'color_move' | - 'hue_move' | - 'hue_stop' | - 'move_to_saturation' | - 'move_to_hue' +export type ColorCtrlCommand = + | 'color_temperature_move_stop' + | 'color_temperature_move_up' + | 'color_temperature_move_down' + | 'color_temperature_step_up' + | 'color_temperature_step_down' + | 'enhanced_move_to_hue_and_saturation' + | 'move_to_hue_and_saturation' + | 'color_hue_step_up' + | 'color_hue_step_down' + | 'color_saturation_step_up' + | 'color_saturation_step_down' + | 'color_loop_set' + | 'color_temperature_move' + | 'color_move' + | 'hue_move' + | 'hue_stop' + | 'move_to_saturation' + | 'move_to_hue'; export interface CommandsColorCtrl { - commands?: ColorCtrlCommand[], - bind?: boolean, endpointNames?: string[] + commands?: ColorCtrlCommand[]; + bind?: boolean; + endpointNames?: string[]; } export function commandsColorCtrl(args?: CommandsColorCtrl): ModernExtend { args = { @@ -1093,15 +1208,23 @@ export function commandsColorCtrl(args?: CommandsColorCtrl): ModernExtend { // #region Closures -export interface LockArgs {pinCodeCount: number} +export interface LockArgs { + pinCodeCount: number; +} export function lock(args?: LockArgs): ModernExtend { args = {...args}; - const fromZigbee = [fz.lock, fz.lock_operation_event, fz.lock_programming_event, fz.lock_pin_code_response, - fz.lock_user_status_response]; + const fromZigbee = [fz.lock, fz.lock_operation_event, fz.lock_programming_event, fz.lock_pin_code_response, fz.lock_user_status_response]; const toZigbee = [tz.lock, tz.pincode_lock, tz.lock_userstatus, tz.lock_auto_relock_time, tz.lock_sound_volume]; - const exposes = [e.lock(), e.pincode(), e.lock_action(), e.lock_action_source_name(), e.lock_action_user(), - e.auto_relock_time().withValueMin(0).withValueMax(3600), e.sound_volume()]; + const exposes = [ + e.lock(), + e.pincode(), + e.lock_action(), + e.lock_action_source_name(), + e.lock_action_user(), + e.auto_relock_time().withValueMin(0).withValueMax(3600), + e.sound_volume(), + ]; const configure: Configure[] = [ setupConfigureForReporting('closuresDoorLock', 'lockState', {min: 'MIN', max: '1_HOUR', change: 0}, ea.STATE_GET), ]; @@ -1111,8 +1234,11 @@ export function lock(args?: LockArgs): ModernExtend { } export interface WindowCoveringArgs { - controls: ('lift' | 'tilt')[], coverInverted?: boolean, stateSource?: 'lift' | 'tilt', configureReporting?: boolean, - coverMode?: boolean, + controls: ('lift' | 'tilt')[]; + coverInverted?: boolean; + stateSource?: 'lift' | 'tilt'; + configureReporting?: boolean; + coverMode?: boolean; } export function windowCovering(args: WindowCoveringArgs): ModernExtend { args = {stateSource: 'lift', configureReporting: true, ...args}; @@ -1167,7 +1293,10 @@ export function windowCovering(args: WindowCoveringArgs): ModernExtend { } export interface CommandsWindowCoveringArgs { - commands?: ('open' | 'close' | 'stop')[], bind?: boolean, endpointNames?: string[], legacyAction: boolean, + commands?: ('open' | 'close' | 'stop')[]; + bind?: boolean; + endpointNames?: string[]; + legacyAction: boolean; } export function commandsWindowCovering(args?: CommandsWindowCoveringArgs): ModernExtend { args = {commands: ['open', 'close', 'stop'], bind: true, legacyAction: false, ...args}; @@ -1180,9 +1309,9 @@ export function commandsWindowCovering(args?: CommandsWindowCoveringArgs): Moder ]; const actionPayloadLookup: KeyValueString = { - 'commandUpOpen': 'open', - 'commandDownClose': 'close', - 'commandStop': 'stop', + commandUpOpen: 'open', + commandDownClose: 'close', + commandStop: 'stop', }; const fromZigbee: Fz.Converter[] = [ @@ -1214,45 +1343,64 @@ export function commandsWindowCovering(args?: CommandsWindowCoveringArgs): Moder // #region Security and Safety export type iasZoneType = 'occupancy' | 'contact' | 'smoke' | 'water_leak' | 'carbon_monoxide' | 'sos' | 'vibration' | 'alarm' | 'gas' | 'generic'; -export type iasZoneAttribute = 'alarm_1' | 'alarm_2' | 'tamper' | 'battery_low' | 'supervision_reports' | 'restore_reports' | 'ac_status' | 'test' | - 'battery_defect'; +export type iasZoneAttribute = + | 'alarm_1' + | 'alarm_2' + | 'tamper' + | 'battery_low' + | 'supervision_reports' + | 'restore_reports' + | 'ac_status' + | 'test' + | 'battery_defect'; export interface IasArgs { - zoneType: iasZoneType, zoneAttributes: iasZoneAttribute[], alarmTimeout?: boolean + zoneType: iasZoneType; + zoneAttributes: iasZoneAttribute[]; + alarmTimeout?: boolean; } export function iasZoneAlarm(args: IasArgs): ModernExtend { const exposeList = { - 'occupancy': e.binary('occupancy', ea.STATE, true, false).withDescription('Indicates whether the device detected occupancy'), - 'contact': e.binary('contact', ea.STATE, false, true).withDescription('Indicates whether the device is opened or closed'), - 'smoke': e.binary('smoke', ea.STATE, true, false).withDescription('Indicates whether the device detected smoke'), - 'water_leak': e.binary('water_leak', ea.STATE, true, false).withDescription('Indicates whether the device detected a water leak'), - 'carbon_monoxide': e.binary('carbon_monoxide', ea.STATE, true, false) - .withDescription('Indicates whether the device detected carbon monoxide'), - 'sos': e.binary('sos', ea.STATE, true, false).withLabel('SOS').withDescription('Indicates whether the SOS alarm is triggered'), - 'vibration': e.binary('vibration', ea.STATE, true, false).withDescription('Indicates whether the device detected vibration'), - 'alarm': e.binary('alarm', ea.STATE, true, false).withDescription('Indicates whether the alarm is triggered'), - 'gas': e.binary('gas', ea.STATE, true, false).withDescription('Indicates whether the device detected gas'), - 'alarm_1': e.binary('alarm_1', ea.STATE, true, false).withDescription('Indicates whether IAS Zone alarm 1 is active'), - 'alarm_2': e.binary('alarm_2', ea.STATE, true, false).withDescription('Indicates whether IAS Zone alarm 2 is active'), - 'tamper': e.binary('tamper', ea.STATE, true, false).withDescription('Indicates whether the device is tampered').withCategory('diagnostic'), - 'battery_low': e.binary('battery_low', ea.STATE, true, false).withDescription('Indicates whether the battery of the device is almost empty') + occupancy: e.binary('occupancy', ea.STATE, true, false).withDescription('Indicates whether the device detected occupancy'), + contact: e.binary('contact', ea.STATE, false, true).withDescription('Indicates whether the device is opened or closed'), + smoke: e.binary('smoke', ea.STATE, true, false).withDescription('Indicates whether the device detected smoke'), + water_leak: e.binary('water_leak', ea.STATE, true, false).withDescription('Indicates whether the device detected a water leak'), + carbon_monoxide: e.binary('carbon_monoxide', ea.STATE, true, false).withDescription('Indicates whether the device detected carbon monoxide'), + sos: e.binary('sos', ea.STATE, true, false).withLabel('SOS').withDescription('Indicates whether the SOS alarm is triggered'), + vibration: e.binary('vibration', ea.STATE, true, false).withDescription('Indicates whether the device detected vibration'), + alarm: e.binary('alarm', ea.STATE, true, false).withDescription('Indicates whether the alarm is triggered'), + gas: e.binary('gas', ea.STATE, true, false).withDescription('Indicates whether the device detected gas'), + alarm_1: e.binary('alarm_1', ea.STATE, true, false).withDescription('Indicates whether IAS Zone alarm 1 is active'), + alarm_2: e.binary('alarm_2', ea.STATE, true, false).withDescription('Indicates whether IAS Zone alarm 2 is active'), + tamper: e.binary('tamper', ea.STATE, true, false).withDescription('Indicates whether the device is tampered').withCategory('diagnostic'), + battery_low: e + .binary('battery_low', ea.STATE, true, false) + .withDescription('Indicates whether the battery of the device is almost empty') .withCategory('diagnostic'), - 'supervision_reports': e.binary('supervision_reports', ea.STATE, true, false) + supervision_reports: e + .binary('supervision_reports', ea.STATE, true, false) .withDescription('Indicates whether the device issues reports on zone operational status') .withCategory('diagnostic'), - 'restore_reports': e.binary('restore_reports', ea.STATE, true, false) + restore_reports: e + .binary('restore_reports', ea.STATE, true, false) .withDescription('Indicates whether the device issues reports on alarm no longer being present') .withCategory('diagnostic'), - 'ac_status': e.binary('ac_status', ea.STATE, true, false).withDescription('Indicates whether the device mains voltage supply is at fault') + ac_status: e + .binary('ac_status', ea.STATE, true, false) + .withDescription('Indicates whether the device mains voltage supply is at fault') .withCategory('diagnostic'), - 'test': e.binary('test', ea.STATE, true, false).withDescription('Indicates whether the device is currently performing a test') + test: e + .binary('test', ea.STATE, true, false) + .withDescription('Indicates whether the device is currently performing a test') .withCategory('diagnostic'), - 'battery_defect': e.binary('battery_defect', ea.STATE, true, false).withDescription('Indicates whether the device battery is defective') + battery_defect: e + .binary('battery_defect', ea.STATE, true, false) + .withDescription('Indicates whether the device battery is defective') .withCategory('diagnostic'), }; const exposes: Expose[] = []; const invertAlarmPayload = args.zoneType === 'contact'; - const bothAlarms = args.zoneAttributes.includes('alarm_1') && (args.zoneAttributes.includes('alarm_2')); + const bothAlarms = args.zoneAttributes.includes('alarm_1') && args.zoneAttributes.includes('alarm_2'); let alarm1Name = 'alarm_1'; let alarm2Name = 'alarm_2'; @@ -1261,11 +1409,13 @@ export function iasZoneAlarm(args: IasArgs): ModernExtend { args.zoneAttributes.map((attr) => exposes.push(exposeList[attr])); } else { if (bothAlarms) { - exposes.push(e.binary(args.zoneType + '_alarm_1', ea.STATE, true, false) - .withDescription(exposeList[args.zoneType].description + ' (alarm_1)')); + exposes.push( + e.binary(args.zoneType + '_alarm_1', ea.STATE, true, false).withDescription(exposeList[args.zoneType].description + ' (alarm_1)'), + ); alarm1Name = args.zoneType + '_alarm_1'; - exposes.push(e.binary(args.zoneType + '_alarm_2', ea.STATE, true, false) - .withDescription(exposeList[args.zoneType].description + ' (alarm_2)')); + exposes.push( + e.binary(args.zoneType + '_alarm_2', ea.STATE, true, false).withDescription(exposeList[args.zoneType].description + ' (alarm_2)'), + ); alarm2Name = args.zoneType + '_alarm_2'; } else { exposes.push(exposeList[args.zoneType]); @@ -1279,68 +1429,77 @@ export function iasZoneAlarm(args: IasArgs): ModernExtend { const timeoutProperty = `${args.zoneType}_timeout`; - const fromZigbee: Fz.Converter[] = [{ - cluster: 'ssIasZone', - type: ['commandStatusChangeNotification', 'attributeReport', 'readResponse'], - options: args.alarmTimeout ? [e.numeric(timeoutProperty, ea.SET).withValueMin(0) - .withDescription(`Time in seconds after which ${args.zoneType} is cleared after detecting it (default 90 seconds).`)] : [], - convert: (model, msg, publish, options, meta) => { - const zoneStatus = msg.type === 'commandStatusChangeNotification' ? msg.data.zonestatus : msg.data.zoneStatus; - - if (args.alarmTimeout) { - const timeout = options?.hasOwnProperty(timeoutProperty) ? Number(options[timeoutProperty]) : 90; - clearTimeout(globalStore.getValue(msg.endpoint, 'timer')); - if (timeout !== 0) { - const timer = setTimeout(() => publish({[alarm1Name]: false, [alarm2Name]: false}), timeout * 1000); - globalStore.putValue(msg.endpoint, 'timer', timer); + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'ssIasZone', + type: ['commandStatusChangeNotification', 'attributeReport', 'readResponse'], + options: args.alarmTimeout + ? [ + e + .numeric(timeoutProperty, ea.SET) + .withValueMin(0) + .withDescription(`Time in seconds after which ${args.zoneType} is cleared after detecting it (default 90 seconds).`), + ] + : [], + convert: (model, msg, publish, options, meta) => { + const zoneStatus = msg.type === 'commandStatusChangeNotification' ? msg.data.zonestatus : msg.data.zoneStatus; + + if (args.alarmTimeout) { + const timeout = options?.hasOwnProperty(timeoutProperty) ? Number(options[timeoutProperty]) : 90; + clearTimeout(globalStore.getValue(msg.endpoint, 'timer')); + if (timeout !== 0) { + const timer = setTimeout(() => publish({[alarm1Name]: false, [alarm2Name]: false}), timeout * 1000); + globalStore.putValue(msg.endpoint, 'timer', timer); + } } - } - let payload = { - tamper: (zoneStatus & 1 << 2) > 0, - battery_low: (zoneStatus & 1 << 3) > 0, - supervision_reports: (zoneStatus & 1 << 4) > 0, - restore_reports: (zoneStatus & 1 << 5) > 0, - trouble: (zoneStatus & 1 << 6) > 0, - ac_status: (zoneStatus & 1 << 7) > 0, - test: (zoneStatus & 1 << 8) > 0, - battery_defect: (zoneStatus & 1 << 9) > 0, - }; + let payload = { + tamper: (zoneStatus & (1 << 2)) > 0, + battery_low: (zoneStatus & (1 << 3)) > 0, + supervision_reports: (zoneStatus & (1 << 4)) > 0, + restore_reports: (zoneStatus & (1 << 5)) > 0, + trouble: (zoneStatus & (1 << 6)) > 0, + ac_status: (zoneStatus & (1 << 7)) > 0, + test: (zoneStatus & (1 << 8)) > 0, + battery_defect: (zoneStatus & (1 << 9)) > 0, + }; - let alarm1Payload = (zoneStatus & 1) > 0; - let alarm2Payload = (zoneStatus & 1 << 1) > 0; + let alarm1Payload = (zoneStatus & 1) > 0; + let alarm2Payload = (zoneStatus & (1 << 1)) > 0; - if (invertAlarmPayload) { - alarm1Payload = !alarm1Payload; - alarm2Payload = !alarm2Payload; - } + if (invertAlarmPayload) { + alarm1Payload = !alarm1Payload; + alarm2Payload = !alarm2Payload; + } - if (bothAlarms) { - payload = {[alarm1Name]: alarm1Payload, ...payload}; - payload = {[alarm2Name]: alarm2Payload, ...payload}; - } else if (args.zoneAttributes.includes('alarm_1')) { - payload = {[alarm1Name]: alarm1Payload, ...payload}; - } else if (args.zoneAttributes.includes('alarm_2')) { - payload = {[alarm2Name]: alarm2Payload, ...payload}; - } + if (bothAlarms) { + payload = {[alarm1Name]: alarm1Payload, ...payload}; + payload = {[alarm2Name]: alarm2Payload, ...payload}; + } else if (args.zoneAttributes.includes('alarm_1')) { + payload = {[alarm1Name]: alarm1Payload, ...payload}; + } else if (args.zoneAttributes.includes('alarm_2')) { + payload = {[alarm2Name]: alarm2Payload, ...payload}; + } - return payload; + return payload; + }, }, - }]; + ]; return {fromZigbee, exposes, isModernExtend: true}; } export interface IasWarningArgs { - reversePayload?: boolean, + reversePayload?: boolean; } export function iasWarning(args?: IasWarningArgs): ModernExtend { - const warningMode = {'stop': 0, 'burglar': 1, 'fire': 2, 'emergency': 3, 'police_panic': 4, 'fire_panic': 5, 'emergency_panic': 6}; + const warningMode = {stop: 0, burglar: 1, fire: 2, emergency: 3, police_panic: 4, fire_panic: 5, emergency_panic: 6}; // levels for siren, strobe and squawk are identical - const level = {'low': 0, 'medium': 1, 'high': 2, 'very_high': 3}; + const level = {low: 0, medium: 1, high: 2, very_high: 3}; const exposes: Expose[] = [ - e.composite('warning', 'warning', ea.SET) + e + .composite('warning', 'warning', ea.SET) .withFeature(e.enum('mode', ea.SET, Object.keys(warningMode)).withDescription('Mode of the warning (sound effect)')) .withFeature(e.enum('level', ea.SET, Object.keys(level)).withDescription('Sound level')) .withFeature(e.enum('strobe_level', ea.SET, Object.keys(level)).withDescription('Intensity of the strobe')) @@ -1349,41 +1508,43 @@ export function iasWarning(args?: IasWarningArgs): ModernExtend { .withFeature(e.numeric('duration', ea.SET).withUnit('s').withDescription('Duration in seconds of the alarm')), ]; - const toZigbee: Tz.Converter[] = [{ - key: ['warning'], - convertSet: async (entity, key, value, meta) => { - const values = { - // @ts-expect-error - mode: value.mode || 'emergency', - // @ts-expect-error - level: value.level || 'medium', - // @ts-expect-error - strobe: value.hasOwnProperty('strobe') ? value.strobe : true, - // @ts-expect-error - duration: value.hasOwnProperty('duration') ? value.duration : 10, - // @ts-expect-error - strobeDutyCycle: value.hasOwnProperty('strobe_duty_cycle') ? value.strobe_duty_cycle * 10 : 0, - // @ts-expect-error - strobeLevel: value.hasOwnProperty('strobe_level') ? utils.getFromLookup(value.strobe_level, strobeLevel) : 1, - }; + const toZigbee: Tz.Converter[] = [ + { + key: ['warning'], + convertSet: async (entity, key, value, meta) => { + const values = { + // @ts-expect-error + mode: value.mode || 'emergency', + // @ts-expect-error + level: value.level || 'medium', + // @ts-expect-error + strobe: value.hasOwnProperty('strobe') ? value.strobe : true, + // @ts-expect-error + duration: value.hasOwnProperty('duration') ? value.duration : 10, + // @ts-expect-error + strobeDutyCycle: value.hasOwnProperty('strobe_duty_cycle') ? value.strobe_duty_cycle * 10 : 0, + // @ts-expect-error + strobeLevel: value.hasOwnProperty('strobe_level') ? utils.getFromLookup(value.strobe_level, strobeLevel) : 1, + }; - let info; - if (args?.reversePayload) { - info = (getFromLookup(values.mode, warningMode)) + ((values.strobe ? 1 : 0) << 4) + (getFromLookup(values.level, level) << 6); - } else { - info = (getFromLookup(values.mode, warningMode) << 4) + ((values.strobe ? 1 : 0) << 2) + (getFromLookup(values.level, level)); - } + let info; + if (args?.reversePayload) { + info = getFromLookup(values.mode, warningMode) + ((values.strobe ? 1 : 0) << 4) + (getFromLookup(values.level, level) << 6); + } else { + info = (getFromLookup(values.mode, warningMode) << 4) + ((values.strobe ? 1 : 0) << 2) + getFromLookup(values.level, level); + } - const payload = { - startwarninginfo: info, - warningduration: values.duration, - strobedutycycle: values.strobeDutyCycle, - strobelevel: values.strobeLevel, - }; + const payload = { + startwarninginfo: info, + warningduration: values.duration, + strobedutycycle: values.strobeDutyCycle, + strobelevel: values.strobeLevel, + }; - await entity.command('ssIasWd', 'startWarning', payload, getOptions(meta.mapped, entity)); + await entity.command('ssIasWd', 'startWarning', payload, getOptions(meta.mapped, entity)); + }, }, - }]; + ]; return {toZigbee, exposes, isModernExtend: true}; } @@ -1392,19 +1553,23 @@ export function iasWarning(args?: IasWarningArgs): ModernExtend { // #region Smart Energy // Uses Electrical Measurement and/or Metering, but for simplicity was put here. -type MultiplierDivisor = {multiplier?: number, divisor?: number} +type MultiplierDivisor = {multiplier?: number; divisor?: number}; export interface ElectricityMeterArgs { - cluster?: 'both' | 'metering' | 'electrical', - current?: false | MultiplierDivisor, - power?: false | MultiplierDivisor, - voltage?: false | MultiplierDivisor, - energy?: false | MultiplierDivisor - configureReporting?: boolean + cluster?: 'both' | 'metering' | 'electrical'; + current?: false | MultiplierDivisor; + power?: false | MultiplierDivisor; + voltage?: false | MultiplierDivisor; + energy?: false | MultiplierDivisor; + configureReporting?: boolean; } export function electricityMeter(args?: ElectricityMeterArgs): ModernExtend { args = {cluster: 'both', configureReporting: true, ...args}; - if (args.cluster === 'metering' && isObject(args.power) && isObject(args.energy) && - (args.power?.divisor !== args.energy?.divisor || args.power?.multiplier !== args.energy?.multiplier)) { + if ( + args.cluster === 'metering' && + isObject(args.power) && + isObject(args.energy) && + (args.power?.divisor !== args.energy?.divisor || args.power?.multiplier !== args.energy?.multiplier) + ) { throw new Error(`When cluster is metering, power and energy divisor/multiplier should be equal`); } @@ -1440,8 +1605,10 @@ export function electricityMeter(args?: ElectricityMeterArgs): ModernExtend { if (args.cluster === 'both') { exposes = [ - e.power().withAccess(ea.STATE_GET), e.voltage().withAccess(ea.STATE_GET), - e.current().withAccess(ea.STATE_GET), e.energy().withAccess(ea.STATE_GET), + e.power().withAccess(ea.STATE_GET), + e.voltage().withAccess(ea.STATE_GET), + e.current().withAccess(ea.STATE_GET), + e.energy().withAccess(ea.STATE_GET), ]; fromZigbee = [fz.electrical_measurement, fz.metering]; toZigbee = [tz.electrical_measurement_power, tz.acvoltage, tz.accurrent, tz.currentsummdelivered]; @@ -1522,16 +1689,14 @@ export function commandsScenes(args?: CommandsScenesArgs) { if (args.endpointNames) { actions = args.commands.map((c) => args.endpointNames.map((e) => `${c}_${e}`)).flat(); } - const exposesArray = [ - e.enum('action', ea.STATE, actions).withDescription('Triggered scene action (e.g. recall a scene)'), - ]; - - const actionPayloadLookup: { [key: string]: string } = { - 'commandRecall': 'recall', - 'commandStore': 'store', - 'commandAdd': 'add', - 'commandRemove': 'remove', - 'commandRemoveAll': 'remove_all', + const exposesArray = [e.enum('action', ea.STATE, actions).withDescription('Triggered scene action (e.g. recall a scene)')]; + + const actionPayloadLookup: {[key: string]: string} = { + commandRecall: 'recall', + commandStore: 'store', + commandAdd: 'add', + commandRemove: 'remove', + commandRemoveAll: 'remove_all', }; const fromZigbee: Fz.Converter[] = [ @@ -1561,9 +1726,16 @@ export function commandsScenes(args?: CommandsScenesArgs) { } export interface EnumLookupArgs { - name: string, lookup: KeyValue, cluster: string | number, attribute: string | {ID: number, type: number}, description: string, - zigbeeCommandOptions?: {manufacturerCode?: number, disableDefaultResponse?: boolean}, access?: 'STATE' | 'STATE_GET' | 'ALL', - endpointName?: string, reporting?: ReportingConfigWithoutAttribute, entityCategory?: 'config' | 'diagnostic', + name: string; + lookup: KeyValue; + cluster: string | number; + attribute: string | {ID: number; type: number}; + description: string; + zigbeeCommandOptions?: {manufacturerCode?: number; disableDefaultResponse?: boolean}; + access?: 'STATE' | 'STATE_GET' | 'ALL'; + endpointName?: string; + reporting?: ReportingConfigWithoutAttribute; + entityCategory?: 'config' | 'diagnostic'; } export function enumLookup(args: EnumLookupArgs): ModernExtend { const {name, lookup, cluster, attribute, description, zigbeeCommandOptions, endpointName, reporting, entityCategory} = args; @@ -1574,28 +1746,40 @@ export function enumLookup(args: EnumLookupArgs): ModernExtend { if (endpointName) expose = expose.withEndpoint(endpointName); if (entityCategory) expose = expose.withCategory(entityCategory); - const fromZigbee: Fz.Converter[] = [{ - cluster: cluster.toString(), - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - if (attributeKey in msg.data && (!endpointName || getEndpointName(msg, model, meta) === endpointName)) { - return {[expose.property]: getFromLookupByValue(msg.data[attributeKey], lookup)}; - } + const fromZigbee: Fz.Converter[] = [ + { + cluster: cluster.toString(), + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + if (attributeKey in msg.data && (!endpointName || getEndpointName(msg, model, meta) === endpointName)) { + return {[expose.property]: getFromLookupByValue(msg.data[attributeKey], lookup)}; + } + }, }, - }]; - - const toZigbee: Tz.Converter[] = [{ - key: [name], - convertSet: access & ea.SET ? async (entity, key, value, meta) => { - const payloadValue = getFromLookup(value, lookup); - const payload = isString(attribute) ? {[attribute]: payloadValue} : {[attribute.ID]: {value: payloadValue, type: attribute.type}}; - await entity.write(cluster, payload, zigbeeCommandOptions); - return {state: {[key]: value}}; - } : undefined, - convertGet: access & ea.GET ? async (entity, key, meta) => { - await entity.read(cluster, [attributeKey], zigbeeCommandOptions); - } : undefined, - }]; + ]; + + const toZigbee: Tz.Converter[] = [ + { + key: [name], + convertSet: + access & ea.SET + ? async (entity, key, value, meta) => { + const payloadValue = getFromLookup(value, lookup); + const payload = isString(attribute) + ? {[attribute]: payloadValue} + : {[attribute.ID]: {value: payloadValue, type: attribute.type}}; + await entity.write(cluster, payload, zigbeeCommandOptions); + return {state: {[key]: value}}; + } + : undefined, + convertGet: + access & ea.GET + ? async (entity, key, meta) => { + await entity.read(cluster, [attributeKey], zigbeeCommandOptions); + } + : undefined, + }, + ]; const configure: Configure[] = [setupConfigureForReporting(cluster, attribute, reporting, access)]; @@ -1606,16 +1790,39 @@ export function enumLookup(args: EnumLookupArgs): ModernExtend { export type ScaleFunction = (value: number, type: 'from' | 'to') => number; export interface NumericArgs { - name: string, cluster: string | number, attribute: string | {ID: number, type: number}, description: string, - zigbeeCommandOptions?: {manufacturerCode?: number, disableDefaultResponse?: boolean}, access?: 'STATE' | 'STATE_GET' | 'ALL', unit?: string, - endpointNames?: string[], reporting?: ReportingConfigWithoutAttribute, - valueMin?: number, valueMax?: number, valueStep?: number, scale?: number | ScaleFunction, label?: string, - entityCategory?: 'config' | 'diagnostic', precision?: number, + name: string; + cluster: string | number; + attribute: string | {ID: number; type: number}; + description: string; + zigbeeCommandOptions?: {manufacturerCode?: number; disableDefaultResponse?: boolean}; + access?: 'STATE' | 'STATE_GET' | 'ALL'; + unit?: string; + endpointNames?: string[]; + reporting?: ReportingConfigWithoutAttribute; + valueMin?: number; + valueMax?: number; + valueStep?: number; + scale?: number | ScaleFunction; + label?: string; + entityCategory?: 'config' | 'diagnostic'; + precision?: number; } export function numeric(args: NumericArgs): ModernExtend { const { - name, cluster, attribute, description, zigbeeCommandOptions, unit, reporting, valueMin, valueMax, valueStep, scale, label, - entityCategory, precision, + name, + cluster, + attribute, + description, + zigbeeCommandOptions, + unit, + reporting, + valueMin, + valueMax, + valueStep, + scale, + label, + entityCategory, + precision, } = args; const endpoints = args.endpointNames; @@ -1645,48 +1852,60 @@ export function numeric(args: NumericArgs): ModernExtend { } } - const fromZigbee: Fz.Converter[] = [{ - cluster: cluster.toString(), - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - if (attributeKey in msg.data) { - const endpoint = endpoints?.find((e) => getEndpointName(msg, model, meta) === e); - if (endpoints && !endpoint) { - return; - } + const fromZigbee: Fz.Converter[] = [ + { + cluster: cluster.toString(), + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + if (attributeKey in msg.data) { + const endpoint = endpoints?.find((e) => getEndpointName(msg, model, meta) === e); + if (endpoints && !endpoint) { + return; + } + + let value = msg.data[attributeKey]; + assertNumber(value); + if (scale !== undefined) { + value = typeof scale === 'number' ? value / scale : scale(value, 'from'); + } + assertNumber(value); + if (precision != null) value = precisionRound(value, precision); - let value = msg.data[attributeKey]; - assertNumber(value); - if (scale !== undefined) { - value = typeof scale === 'number' ? value / scale : scale(value, 'from'); + const expose = exposes.length === 1 ? exposes[0] : exposes.find((e) => e.endpoint === endpoint); + return {[expose.property]: value}; } - assertNumber(value); - if (precision != null) value = precisionRound(value, precision); + }, + }, + ]; - const expose = exposes.length === 1 ? exposes[0] : exposes.find((e) => e.endpoint === endpoint); - return {[expose.property]: value}; - } + const toZigbee: Tz.Converter[] = [ + { + key: [name], + convertSet: + access & ea.SET + ? async (entity, key, value, meta) => { + assertNumber(value, key); + let payloadValue = value; + if (scale !== undefined) { + payloadValue = typeof scale === 'number' ? payloadValue * scale : scale(payloadValue, 'to'); + } + assertNumber(payloadValue); + if (precision != null) payloadValue = precisionRound(value, precision); + const payload = isString(attribute) + ? {[attribute]: payloadValue} + : {[attribute.ID]: {value: payloadValue, type: attribute.type}}; + await entity.write(cluster, payload, zigbeeCommandOptions); + return {state: {[key]: value}}; + } + : undefined, + convertGet: + access & ea.GET + ? async (entity, key, meta) => { + await entity.read(cluster, [attributeKey], zigbeeCommandOptions); + } + : undefined, }, - }]; - - const toZigbee: Tz.Converter[] = [{ - key: [name], - convertSet: access & ea.SET ? async (entity, key, value, meta) => { - assertNumber(value, key); - let payloadValue = value; - if (scale !== undefined) { - payloadValue = typeof scale === 'number' ? payloadValue * scale : scale(payloadValue, 'to'); - } - assertNumber(payloadValue); - if (precision != null) payloadValue = precisionRound(value, precision); - const payload = isString(attribute) ? {[attribute]: payloadValue} : {[attribute.ID]: {value: payloadValue, type: attribute.type}}; - await entity.write(cluster, payload, zigbeeCommandOptions); - return {state: {[key]: value}}; - } : undefined, - convertGet: access & ea.GET ? async (entity, key, meta) => { - await entity.read(cluster, [attributeKey], zigbeeCommandOptions); - } : undefined, - }]; + ]; const configure: Configure[] = [setupConfigureForReporting(cluster, attribute, reporting, access, endpoints)]; @@ -1694,9 +1913,17 @@ export function numeric(args: NumericArgs): ModernExtend { } export interface BinaryArgs { - name: string, valueOn: [string | boolean, unknown], valueOff: [string | boolean, unknown], cluster: string | number, - attribute: string | {ID: number, type: number}, description: string, zigbeeCommandOptions?: {manufacturerCode: number}, - endpointName?: string, reporting?: ReportingConfig, access?: 'STATE' | 'STATE_GET' | 'ALL', entityCategory?: 'config' | 'diagnostic', + name: string; + valueOn: [string | boolean, unknown]; + valueOff: [string | boolean, unknown]; + cluster: string | number; + attribute: string | {ID: number; type: number}; + description: string; + zigbeeCommandOptions?: {manufacturerCode: number}; + endpointName?: string; + reporting?: ReportingConfig; + access?: 'STATE' | 'STATE_GET' | 'ALL'; + entityCategory?: 'config' | 'diagnostic'; } export function binary(args: BinaryArgs): ModernExtend { const {name, valueOn, valueOff, cluster, attribute, description, zigbeeCommandOptions, endpointName, reporting, entityCategory} = args; @@ -1707,28 +1934,40 @@ export function binary(args: BinaryArgs): ModernExtend { if (endpointName) expose = expose.withEndpoint(endpointName); if (entityCategory) expose = expose.withCategory(entityCategory); - const fromZigbee: Fz.Converter[] = [{ - cluster: cluster.toString(), - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - if (attributeKey in msg.data && (!endpointName || getEndpointName(msg, model, meta) === endpointName)) { - return {[expose.property]: msg.data[attributeKey] === valueOn[1] ? valueOn[0] : valueOff[0]}; - } + const fromZigbee: Fz.Converter[] = [ + { + cluster: cluster.toString(), + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + if (attributeKey in msg.data && (!endpointName || getEndpointName(msg, model, meta) === endpointName)) { + return {[expose.property]: msg.data[attributeKey] === valueOn[1] ? valueOn[0] : valueOff[0]}; + } + }, }, - }]; - - const toZigbee: Tz.Converter[] = [{ - key: [name], - convertSet: access & ea.SET ? async (entity, key, value, meta) => { - const payloadValue = value === valueOn[0] ? valueOn[1] : valueOff[1]; - const payload = isString(attribute) ? {[attribute]: payloadValue} : {[attribute.ID]: {value: payloadValue, type: attribute.type}}; - await entity.write(cluster, payload, zigbeeCommandOptions); - return {state: {[key]: value}}; - } : undefined, - convertGet: access & ea.GET ? async (entity, key, meta) => { - await entity.read(cluster, [attributeKey], zigbeeCommandOptions); - } : undefined, - }]; + ]; + + const toZigbee: Tz.Converter[] = [ + { + key: [name], + convertSet: + access & ea.SET + ? async (entity, key, value, meta) => { + const payloadValue = value === valueOn[0] ? valueOn[1] : valueOff[1]; + const payload = isString(attribute) + ? {[attribute]: payloadValue} + : {[attribute.ID]: {value: payloadValue, type: attribute.type}}; + await entity.write(cluster, payload, zigbeeCommandOptions); + return {state: {[key]: value}}; + } + : undefined, + convertGet: + access & ea.GET + ? async (entity, key, meta) => { + await entity.read(cluster, [attributeKey], zigbeeCommandOptions); + } + : undefined, + }, + ]; const configure: Configure[] = [setupConfigureForReporting(cluster, attribute, reporting, access)]; @@ -1737,8 +1976,14 @@ export function binary(args: BinaryArgs): ModernExtend { export type Parse = (msg: Fz.Message, attributeKey: string | number) => unknown; export interface ActionEnumLookupArgs { - actionLookup: KeyValue, cluster: string | number, attribute: string | {ID: number, type: number}, endpointNames?: string[], - buttonLookup?: KeyValue, extraActions?: string[], commands?: string[], parse?: Parse, + actionLookup: KeyValue; + cluster: string | number; + attribute: string | {ID: number; type: number}; + endpointNames?: string[]; + buttonLookup?: KeyValue; + extraActions?: string[]; + commands?: string[]; + parse?: Parse; } export function actionEnumLookup(args: ActionEnumLookupArgs): ModernExtend { const {actionLookup: lookup, attribute, cluster, buttonLookup} = args; @@ -1746,35 +1991,41 @@ export function actionEnumLookup(args: ActionEnumLookupArgs): ModernExtend { const commands = args.commands || ['attributeReport', 'readResponse']; const parse = args.parse; - let actions = Object.keys(lookup).map((a) => args.endpointNames ? args.endpointNames.map((e) => `${a}_${e}`) : [a]).flat(); + let actions = Object.keys(lookup) + .map((a) => (args.endpointNames ? args.endpointNames.map((e) => `${a}_${e}`) : [a])) + .flat(); // allows direct external input to be used by other extends in the same device if (args.extraActions) actions = actions.concat(args.extraActions); const expose = e.enum('action', ea.STATE, actions).withDescription('Triggered action (e.g. a button click)').withCategory('diagnostic'); - const fromZigbee: Fz.Converter[] = [{ - cluster: cluster.toString(), - type: commands, - convert: (model, msg, publish, options, meta) => { - if (attributeKey in msg.data) { - let value = (parse) ? parse(msg, attributeKey) : msg.data[attributeKey]; - value = getFromLookupByValue(value, lookup); - // endpointNames is used when action endpoint names don't overlap with other endpoint names - if (args.endpointNames) value = postfixWithEndpointName(value, msg, model, meta); - // buttonLookup is used when action endpoint names overlap with other endpoint names - if (args.buttonLookup) { - const endpointName = getFromLookupByValue(msg.endpoint.ID, buttonLookup); - value =`${value}_${endpointName}`; + const fromZigbee: Fz.Converter[] = [ + { + cluster: cluster.toString(), + type: commands, + convert: (model, msg, publish, options, meta) => { + if (attributeKey in msg.data) { + let value = parse ? parse(msg, attributeKey) : msg.data[attributeKey]; + value = getFromLookupByValue(value, lookup); + // endpointNames is used when action endpoint names don't overlap with other endpoint names + if (args.endpointNames) value = postfixWithEndpointName(value, msg, model, meta); + // buttonLookup is used when action endpoint names overlap with other endpoint names + if (args.buttonLookup) { + const endpointName = getFromLookupByValue(msg.endpoint.ID, buttonLookup); + value = `${value}_${endpointName}`; + } + return {[expose.property]: value}; } - return {[expose.property]: value}; - } + }, }, - }]; + ]; return {exposes: [expose], fromZigbee, isModernExtend: true}; } export interface QuirkAddEndpointClusterArgs { - endpointID: number, inputClusters?: string[] | number[], outputClusters?: string[] | number[], + endpointID: number; + inputClusters?: string[] | number[]; + outputClusters?: string[] | number[]; } export function quirkAddEndpointCluster(args: QuirkAddEndpointClusterArgs): ModernExtend { const {endpointID, inputClusters, outputClusters} = args; @@ -1789,9 +2040,7 @@ export function quirkAddEndpointCluster(args: QuirkAddEndpointClusterArgs): Mode } inputClusters?.forEach((cluster: number | string) => { - const clusterID = isString(cluster) ? - Zcl.Utils.getCluster(cluster, device.manufacturerID, device.customClusters).ID : - cluster; + const clusterID = isString(cluster) ? Zcl.Utils.getCluster(cluster, device.manufacturerID, device.customClusters).ID : cluster; if (!endpoint.inputClusters.includes(clusterID)) { logger.debug(`Quirk: adding input cluster ${clusterID} to endpoint ${endpointID}.`, 'zhc:quirkaddendpointcluster'); @@ -1800,9 +2049,7 @@ export function quirkAddEndpointCluster(args: QuirkAddEndpointClusterArgs): Mode }); outputClusters?.forEach((cluster: number | string) => { - const clusterID = isString(cluster) ? - Zcl.Utils.getCluster(cluster, device.manufacturerID, device.customClusters).ID : - cluster; + const clusterID = isString(cluster) ? Zcl.Utils.getCluster(cluster, device.manufacturerID, device.customClusters).ID : cluster; if (!endpoint.outputClusters.includes(clusterID)) { logger.debug(`Quirk: adding output cluster ${clusterID} to endpoint ${endpointID}.`, 'zhc:quirkaddendpointcluster'); @@ -1820,7 +2067,7 @@ export function quirkAddEndpointCluster(args: QuirkAddEndpointClusterArgs): Mode export function quirkCheckinInterval(timeout: number | keyof typeof timeLookup): ModernExtend { const configure: Configure[] = [ async (device, coordinatorEndpoint, definition) => { - device.checkinInterval = (typeof timeout == 'number') ? timeout : timeLookup[timeout]; + device.checkinInterval = typeof timeout == 'number' ? timeout : timeLookup[timeout]; device.save(); }, ]; @@ -1833,10 +2080,14 @@ export function reconfigureReportingsOnDeviceAnnounce(): ModernExtend { if (type === 'deviceAnnounce') { for (const endpoint of device.endpoints) { for (const c of endpoint.configuredReportings) { - await endpoint.configureReporting(c.cluster.name, [{ - attribute: c.attribute.name, minimumReportInterval: c.minimumReportInterval, - maximumReportInterval: c.maximumReportInterval, reportableChange: c.reportableChange, - }]); + await endpoint.configureReporting(c.cluster.name, [ + { + attribute: c.attribute.name, + minimumReportInterval: c.minimumReportInterval, + maximumReportInterval: c.maximumReportInterval, + reportableChange: c.reportableChange, + }, + ]); } } } @@ -1845,7 +2096,7 @@ export function reconfigureReportingsOnDeviceAnnounce(): ModernExtend { return {onEvent, isModernExtend: true}; } -export function deviceEndpoints(args: {endpoints: {[n: string]: number}, multiEndpointSkip?: string[]}): ModernExtend { +export function deviceEndpoints(args: {endpoints: {[n: string]: number}; multiEndpointSkip?: string[]}): ModernExtend { const result: ModernExtend = { meta: {multiEndpoint: true}, endpoint: (d) => args.endpoints, @@ -1868,16 +2119,18 @@ export function deviceAddCustomCluster(clusterName: string, clusterDefinition: C } export function ignoreClusterReport(args: {cluster: string | number}): ModernExtend { - const fromZigbee: Fz.Converter[] = [{ - cluster: args.cluster.toString(), - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => {}, - }]; + const fromZigbee: Fz.Converter[] = [ + { + cluster: args.cluster.toString(), + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => {}, + }, + ]; return {fromZigbee, isModernExtend: true}; } -export function bindCluster(args: {cluster: string | number, clusterType: 'input' | 'output', endpointNames?: string[]}): ModernExtend { +export function bindCluster(args: {cluster: string | number; clusterType: 'input' | 'output'; endpointNames?: string[]}): ModernExtend { const configure: Configure[] = [setupConfigureForBinding(args.cluster, args.clusterType, args.endpointNames)]; return {configure, isModernExtend: true}; } diff --git a/src/lib/ota/OTA_URLs.md b/src/lib/ota/OTA_URLs.md index b3a932a77da71..2c033cbf8b2fe 100644 --- a/src/lib/ota/OTA_URLs.md +++ b/src/lib/ota/OTA_URLs.md @@ -42,11 +42,11 @@ EUROTRONICS Zigbee OTA firmware images are made publicly available by EUROTRONIC https://github.com/EUROTRONIC-Technology/Spirit-ZigBee/releases/download/ -### IKEA Trådfri +### IKEA Trådfri IKEA Tradfi Zigbee OTA firmware images are made publicly available by IKEA (first-party) at the following URL: -Download-URL: +Download-URL: http://fw.ota.homesmart.ikea.net/feed/version_info.json diff --git a/src/lib/ota/common.ts b/src/lib/ota/common.ts index 4adc53d51cb71..f11b31cbb0f87 100644 --- a/src/lib/ota/common.ts +++ b/src/lib/ota/common.ts @@ -1,26 +1,41 @@ -import crypto from 'crypto'; -import {HttpsProxyAgent} from 'https-proxy-agent'; -import {Zh, Ota, KeyValueAny, KeyValue, OtaUpdateAvailableResult} from '../types'; import assert from 'assert'; -import crc32 from 'buffer-crc32'; import axios from 'axios'; -import * as URI from 'uri-js'; +import crc32 from 'buffer-crc32'; +import crypto from 'crypto'; import {readFileSync} from 'fs'; +import https from 'https'; +import {HttpsProxyAgent} from 'https-proxy-agent'; import path from 'path'; +import tls from 'tls'; +import * as URI from 'uri-js'; import {Zcl} from 'zigbee-herdsman'; + import {logger} from '../logger'; -import https from 'https'; -import tls from 'tls'; +import {Zh, Ota, KeyValueAny, KeyValue, OtaUpdateAvailableResult} from '../types'; import {sleep} from '../utils'; -interface Request {cancel: () => void, promise: Promise<{header: Zh.ZclHeader, payload: KeyValue}>} -interface Waiters {imageBlockOrPageRequest?: Request, upgradeEndRequest?: Request} +interface Request { + cancel: () => void; + promise: Promise<{header: Zh.ZclHeader; payload: KeyValue}>; +} +interface Waiters { + imageBlockOrPageRequest?: Request; + upgradeEndRequest?: Request; +} type CommandResult = {header: Zcl.Header; payload: KeyValueAny}; -type IsNewImageAvailable = (current: Ota.ImageInfo, device: Zh.Device, getImageMeta: Ota.GetImageMeta) => - Promise<{available: number, currentFileVersion: number, otaFileVersion: number}> +type IsNewImageAvailable = ( + current: Ota.ImageInfo, + device: Zh.Device, + getImageMeta: Ota.GetImageMeta, +) => Promise<{available: number; currentFileVersion: number; otaFileVersion: number}>; type DownloadImage = (meta: Ota.ImageMeta) => Promise<{data: Buffer}>; -type GetNewImage = (current: Ota.Version, device: Zh.Device, getImageMeta: Ota.GetImageMeta, downloadImage: DownloadImage, - suppressElementImageParseFailure: boolean) => Promise; +type GetNewImage = ( + current: Ota.Version, + device: Zh.Device, + getImageMeta: Ota.GetImageMeta, + downloadImage: DownloadImage, + suppressElementImageParseFailure: boolean, +) => Promise; type ImageBlockResponsePayload = { status: number; manufacturerCode: Zcl.ManufacturerCode; @@ -37,9 +52,9 @@ let dataDir: string = null; const MAX_TIMEOUT = 2147483647; // +- 24 days const IMAGE_BLOCK_RESPONSE_DELAY = 250; -export const UPGRADE_FILE_IDENTIFIER = Buffer.from([0x1E, 0xF1, 0xEE, 0x0B]); +export const UPGRADE_FILE_IDENTIFIER = Buffer.from([0x1e, 0xf1, 0xee, 0x0b]); -const VALID_SILABS_CRC = 0x2144DF1C; +const VALID_SILABS_CRC = 0x2144df1c; const EBL_TAG_HEADER = 0x0; const EBL_TAG_ENC_HEADER = 0xfb05; const EBL_TAG_END = 0xfc04; @@ -48,7 +63,6 @@ const EBL_IMAGE_SIGNATURE = 0xe350; const GBL_TAG_HEADER = 0xeb17a603; const GBL_TAG_END = 0xfc0404fc; - // ---- // Helper functions // ---- @@ -132,7 +146,7 @@ export function readLocalFile(fileName: string): Buffer { return readFileSync(fileName); } -export async function getFirmwareFile(image: KeyValueAny): Promise<{ data: Buffer; }> { +export async function getFirmwareFile(image: KeyValueAny): Promise<{data: Buffer}> { const urlOrName = image.url; // First try to download firmware file with the URL provided @@ -380,8 +394,12 @@ async function requestOTA(endpoint: Zh.Endpoint): Promise<[transNum: number, Ota } } -function getImageBlockResponsePayload(image: Ota.Image, imageBlockRequest: CommandResult, pageOffset: number, pageSize: number) - : ImageBlockResponsePayload { +function getImageBlockResponsePayload( + image: Ota.Image, + imageBlockRequest: CommandResult, + pageOffset: number, + pageSize: number, +): ImageBlockResponsePayload { let start = imageBlockRequest.payload.fileOffset + pageOffset; // When the data size is too big, OTA gets unstable, so default it to 50 bytes maximum. // - Insta devices, OTA only works for data sizes 40 and smaller (= manufacturerCode 4474). @@ -397,8 +415,11 @@ function getImageBlockResponsePayload(image: Ota.Image, imageBlockRequest: Comma let dataSize = Math.min(maximumDataSize, imageBlockRequest.payload.maximumDataSize); // Hack for https://github.com/Koenkk/zigbee-OTA/issues/328 (Legrand OTA not working) - if (imageBlockRequest.payload.manufacturerCode === Zcl.ManufacturerCode.LEGRAND_GROUP && imageBlockRequest.payload.fileOffset === 50 && - imageBlockRequest.payload.maximumDataSize === 12) { + if ( + imageBlockRequest.payload.manufacturerCode === Zcl.ManufacturerCode.LEGRAND_GROUP && + imageBlockRequest.payload.fileOffset === 50 && + imageBlockRequest.payload.maximumDataSize === 12 + ) { logger.info(`Detected Legrand firmware issue, attempting to reset the OTA stack`, NS); // The following vector seems to buffer overflow the device to reset the OTA stack! start = 78; @@ -415,8 +436,11 @@ function getImageBlockResponsePayload(image: Ota.Image, imageBlockRequest: Comma end = image.raw.length; } - logger.debug(`Request offsets: fileOffset=${imageBlockRequest.payload.fileOffset} pageOffset=${pageOffset} ` + - `maximumDataSize=${imageBlockRequest.payload.maximumDataSize}`, NS); + logger.debug( + `Request offsets: fileOffset=${imageBlockRequest.payload.fileOffset} pageOffset=${pageOffset} ` + + `maximumDataSize=${imageBlockRequest.payload.maximumDataSize}`, + NS, + ); logger.debug(`Payload offsets: start=${start} end=${end} dataSize=${dataSize}`, NS); return { @@ -430,13 +454,18 @@ function getImageBlockResponsePayload(image: Ota.Image, imageBlockRequest: Comma }; } -function callOnProgress(startTime: number, lastUpdate: number, imageBlockRequest: CommandResult, image: Ota.Image, onProgress: Ota.OnProgress) - : number { +function callOnProgress( + startTime: number, + lastUpdate: number, + imageBlockRequest: CommandResult, + image: Ota.Image, + onProgress: Ota.OnProgress, +): number { const now = Date.now(); // Call on progress every +- 30 seconds - if (lastUpdate === null || (now - lastUpdate) > 30000) { - const totalDuration = (now - startTime) / 1000;// in seconds + if (lastUpdate === null || now - lastUpdate > 30000) { + const totalDuration = (now - startTime) / 1000; // in seconds const bytesPerSecond = imageBlockRequest.payload.fileOffset / totalDuration; const remaining = (image.header.totalImageSize - imageBlockRequest.payload.fileOffset) / bytesPerSecond; let percentage = imageBlockRequest.payload.fileOffset / image.header.totalImageSize; @@ -451,8 +480,12 @@ function callOnProgress(startTime: number, lastUpdate: number, imageBlockRequest } } -export async function isUpdateAvailable(device: Zh.Device, requestPayload: Ota.ImageInfo, isNewImageAvailable: IsNewImageAvailable = null, - getImageMeta: Ota.GetImageMeta = null): Promise { +export async function isUpdateAvailable( + device: Zh.Device, + requestPayload: Ota.ImageInfo, + isNewImageAvailable: IsNewImageAvailable = null, + getImageMeta: Ota.GetImageMeta = null, +): Promise { const logId = `'${device.ieeeAddr}' (${device.modelID})`; logger.debug(`Checking if an update is available for ${logId}`, NS); @@ -480,8 +513,11 @@ export async function isUpdateAvailable(device: Zh.Device, requestPayload: Ota.I return {...availableResult, available: availableResult.available < 0}; } -export async function isNewImageAvailable(current: Ota.ImageInfo, device: Zh.Device, getImageMeta: Ota.GetImageMeta) - : ReturnType { +export async function isNewImageAvailable( + current: Ota.ImageInfo, + device: Zh.Device, + getImageMeta: Ota.GetImageMeta, +): ReturnType { const currentS = JSON.stringify(current); logger.debug(`Is new image available for '${device.ieeeAddr}' (${device.modelID}), current '${currentS}'`, NS); @@ -512,8 +548,14 @@ export async function isNewImageAvailable(current: Ota.ImageInfo, device: Zh.Dev /** * @see https://zigbeealliance.org/wp-content/uploads/2021/10/07-5123-08-Zigbee-Cluster-Library.pdf 11.12 */ -export async function updateToLatest(device: Zh.Device, onProgress: Ota.OnProgress, getNewImage: GetNewImage, getImageMeta: Ota.GetImageMeta = null, - downloadImage: DownloadImage = null, suppressElementImageParseFailure: boolean = false): Promise { +export async function updateToLatest( + device: Zh.Device, + onProgress: Ota.OnProgress, + getNewImage: GetNewImage, + getImageMeta: Ota.GetImageMeta = null, + downloadImage: DownloadImage = null, + suppressElementImageParseFailure: boolean = false, +): Promise { const logId = `'${device.ieeeAddr}' (${device.modelID})`; logger.debug(`Updating ${logId} to latest`, NS); @@ -544,7 +586,7 @@ export async function updateToLatest(device: Zh.Device, onProgress: Ota.OnProgre // Reduce network congestion by throttling response if necessary { const blockResponseTime = Date.now(); - const delay = (blockResponseTime - lastBlockResponseTime); + const delay = blockResponseTime - lastBlockResponseTime; if (delay < IMAGE_BLOCK_RESPONSE_DELAY) { await sleep(IMAGE_BLOCK_RESPONSE_DELAY - delay); @@ -556,13 +598,7 @@ export async function updateToLatest(device: Zh.Device, onProgress: Ota.OnProgre try { const blockPayload = getImageBlockResponsePayload(image, imageBlockRequest, pageOffset, pageSize); - await endpoint.commandResponse( - 'genOta', - 'imageBlockResponse', - blockPayload, - null, - imageBlockRequest.header.transactionSequenceNumber, - ); + await endpoint.commandResponse('genOta', 'imageBlockResponse', blockPayload, null, imageBlockRequest.header.transactionSequenceNumber); pageOffset += blockPayload.dataSize; } catch (error) { @@ -653,8 +689,11 @@ export async function updateToLatest(device: Zh.Device, onProgress: Ota.OnProgre if (endResult.payload.status === Zcl.Status.SUCCESS) { const payload = { - manufacturerCode: image.header.manufacturerCode, imageType: image.header.imageType, - fileVersion: image.header.fileVersion, currentTime: 0, upgradeTime: 1, + manufacturerCode: image.header.manufacturerCode, + imageType: image.header.imageType, + fileVersion: image.header.fileVersion, + currentTime: 0, + upgradeTime: 1, }; try { @@ -696,7 +735,7 @@ export async function updateToLatest(device: Zh.Device, onProgress: Ota.OnProgre Zcl.Clusters.genOta.commands.upgradeEndRequest.ID, Zcl.Status.SUCCESS, Zcl.Clusters.genOta.ID, - endResult.header.transactionSequenceNumber + endResult.header.transactionSequenceNumber, ); } catch (error) { logger.debug(`Upgrade end request default response failed: ${error}`, NS); @@ -706,8 +745,13 @@ export async function updateToLatest(device: Zh.Device, onProgress: Ota.OnProgre } } -export async function getNewImage(current: Ota.ImageInfo, device: Zh.Device, getImageMeta: Ota.GetImageMeta, downloadImage: DownloadImage, - suppressElementImageParseFailure: boolean): Promise { +export async function getNewImage( + current: Ota.ImageInfo, + device: Zh.Device, + getImageMeta: Ota.GetImageMeta, + downloadImage: DownloadImage, + suppressElementImageParseFailure: boolean, +): Promise { // TODO: better errors (these are reported in frontend notifies) const logId = `'${device.ieeeAddr}' (${device.modelID})`; const meta = await getImageMeta(current, device); @@ -719,7 +763,7 @@ export async function getNewImage(current: Ota.ImageInfo, device: Zh.Device, get const download = downloadImage ? await downloadImage(meta) : await getAxios().get(meta.url, {responseType: 'arraybuffer'}); - const checksum = (meta.sha512 || meta.sha256); + const checksum = meta.sha512 || meta.sha256; if (checksum) { const hash = crypto.createHash(meta.sha512 ? 'sha512' : 'sha256'); diff --git a/src/lib/ota/gmmts.ts b/src/lib/ota/gmmts.ts index 58c9c65904557..9ea7ecd8d1d38 100644 --- a/src/lib/ota/gmmts.ts +++ b/src/lib/ota/gmmts.ts @@ -1,6 +1,6 @@ +import {logger} from '../logger'; import {Zh, Ota} from '../types'; import * as common from './common'; -import {logger} from '../logger'; const axios = common.getAxios(); const firmwareManifest = 'https://update.gammatroniques.fr/ticmeter/manifest.json'; @@ -13,8 +13,9 @@ export async function getImageMeta(current: Ota.ImageInfo, device: Zh.Device): P throw new Error(`GMMTS OTA: No builds available for ${device.modelID}`); } - const appUrl: { path: string, offset: number, type: string, ota: string } | undefined = - releases.builds[0].parts.find((part: { type: string }) => part.type === 'app'); + const appUrl: {path: string; offset: number; type: string; ota: string} | undefined = releases.builds[0].parts.find( + (part: {type: string}) => part.type === 'app', + ); logger.info(`GMMTS OTA: Using firmware file ` + appUrl.path + ` for ${device.modelID}`, 'TICMeter'); const image = common.parseImage((await common.getAxios().get(appUrl.ota, {responseType: 'arraybuffer'})).data); @@ -35,7 +36,7 @@ export async function getImageMeta(current: Ota.ImageInfo, device: Zh.Device): P * Interface implementation */ -export async function isUpdateAvailable(device: Zh.Device, requestPayload:Ota.ImageInfo=null) { +export async function isUpdateAvailable(device: Zh.Device, requestPayload: Ota.ImageInfo = null) { return common.isUpdateAvailable(device, requestPayload, common.isNewImageAvailable, getImageMeta); } diff --git a/src/lib/ota/index.ts b/src/lib/ota/index.ts index 8b7601a02690d..5e1b448392ad1 100644 --- a/src/lib/ota/index.ts +++ b/src/lib/ota/index.ts @@ -13,18 +13,6 @@ import {Ota} from '../types'; const {setDataDir} = common; -export { - inovelli, - ledvance, - salus, - lixee, - securifi, - tradfri, - ubisys, - zigbeeOTA, - jethome, - gmmts, - setDataDir, -}; +export {inovelli, ledvance, salus, lixee, securifi, tradfri, ubisys, zigbeeOTA, jethome, gmmts, setDataDir}; export type ImageInfo = Ota.ImageInfo; diff --git a/src/lib/ota/inovelli.ts b/src/lib/ota/inovelli.ts index 183d1cbafd3df..b954271bb9e2e 100644 --- a/src/lib/ota/inovelli.ts +++ b/src/lib/ota/inovelli.ts @@ -5,9 +5,9 @@ */ const url = 'https://files.inovelli.com/firmware/firmware.json'; -import * as common from './common'; -import {Zh, Ota, KeyValueAny} from '../types'; import {logger} from '../logger'; +import {Zh, Ota, KeyValueAny} from '../types'; +import * as common from './common'; const NS = 'zhc:ota:inovelli'; const axios = common.getAxios(); @@ -64,22 +64,12 @@ export async function getImageMeta(current: Ota.ImageInfo, device: Zh.Device): P * Interface implementation */ -export async function isUpdateAvailable(device: Zh.Device, requestPayload:Ota.ImageInfo=null) { - return common.isUpdateAvailable( - device, - requestPayload, - common.isNewImageAvailable, - getImageMeta, - ); +export async function isUpdateAvailable(device: Zh.Device, requestPayload: Ota.ImageInfo = null) { + return common.isUpdateAvailable(device, requestPayload, common.isNewImageAvailable, getImageMeta); } export async function updateToLatest(device: Zh.Device, onProgress: Ota.OnProgress) { - return common.updateToLatest( - device, - onProgress, - common.getNewImage, - getImageMeta, - ); + return common.updateToLatest(device, onProgress, common.getNewImage, getImageMeta); } exports.isUpdateAvailable = isUpdateAvailable; diff --git a/src/lib/ota/jethome.ts b/src/lib/ota/jethome.ts index 585c9f857d1f2..66e032e630de8 100644 --- a/src/lib/ota/jethome.ts +++ b/src/lib/ota/jethome.ts @@ -1,8 +1,8 @@ const baseurl = 'https://fw.jethome.ru'; const deviceurl = `${baseurl}/api/devices/`; -import * as common from './common'; -import {Zh, Ota} from '../types'; import {logger} from '../logger'; +import {Zh, Ota} from '../types'; +import * as common from './common'; const NS = 'zhc:ota:jethome'; const axios = common.getAxios(); @@ -61,7 +61,7 @@ export async function getImageMeta(current: Ota.ImageInfo, device: Zh.Device): P * Interface implementation */ -export async function isUpdateAvailable(device: Zh.Device, requestPayload:Ota.ImageInfo=null) { +export async function isUpdateAvailable(device: Zh.Device, requestPayload: Ota.ImageInfo = null) { return common.isUpdateAvailable(device, requestPayload, common.isNewImageAvailable, getImageMeta); } diff --git a/src/lib/ota/ledvance.ts b/src/lib/ota/ledvance.ts index 931d09d62f93c..b35cd9d4d03e9 100644 --- a/src/lib/ota/ledvance.ts +++ b/src/lib/ota/ledvance.ts @@ -1,8 +1,8 @@ const updateCheckUrl = 'https://api.update.ledvance.com/v1/zigbee/firmwares/newer'; const updateDownloadUrl = 'https://api.update.ledvance.com/v1/zigbee/firmwares/download'; -import * as common from './common'; -import {Zh, Ota} from '../types'; import {logger} from '../logger'; +import {Zh, Ota} from '../types'; +import * as common from './common'; const NS = 'zhc:ota:ledvance'; const axios = common.getAxios(); @@ -41,7 +41,7 @@ export async function getImageMeta(current: Ota.ImageInfo, device: Zh.Device): P * Interface implementation */ -export async function isUpdateAvailable(device: Zh.Device, requestPayload:Ota.ImageInfo=null) { +export async function isUpdateAvailable(device: Zh.Device, requestPayload: Ota.ImageInfo = null) { return common.isUpdateAvailable(device, requestPayload, common.isNewImageAvailable, getImageMeta); } diff --git a/src/lib/ota/lixee.ts b/src/lib/ota/lixee.ts index 1887a502698d6..9e7ff18818af5 100644 --- a/src/lib/ota/lixee.ts +++ b/src/lib/ota/lixee.ts @@ -49,7 +49,7 @@ export async function getImageMeta(current: Ota.ImageInfo, device: Zh.Device): P * Interface implementation */ -export async function isUpdateAvailable(device: Zh.Device, requestPayload:Ota.ImageInfo=null) { +export async function isUpdateAvailable(device: Zh.Device, requestPayload: Ota.ImageInfo = null) { return common.isUpdateAvailable(device, requestPayload, common.isNewImageAvailable, getImageMeta); } diff --git a/src/lib/ota/salus.ts b/src/lib/ota/salus.ts index 3206c4c2ad065..f640ea21cd5cc 100644 --- a/src/lib/ota/salus.ts +++ b/src/lib/ota/salus.ts @@ -1,8 +1,9 @@ const url = 'https://eu.salusconnect.io/demo/default/status/firmware?timestamp=0'; -import * as common from './common'; import tar from 'tar-stream'; -import {Zh, Ota, KeyValue, KeyValueAny} from '../types'; + import {logger} from '../logger'; +import {Zh, Ota, KeyValue, KeyValueAny} from '../types'; +import * as common from './common'; const NS = 'zhc:ota:salus'; const axios = common.getAxios(); @@ -42,11 +43,11 @@ async function untar(tarStream: NodeJS.ReadStream) { extract.on('entry', (headers, stream, next) => { const buffers: Buffer[] = []; - stream.on('data', function(data) { + stream.on('data', function (data) { buffers.push(data); }); - stream.on('end', function() { + stream.on('end', function () { result.push({ headers, data: Buffer.concat(buffers), @@ -81,7 +82,7 @@ async function downloadImage(meta: KeyValueAny) { * Interface implementation */ -export async function isUpdateAvailable(device: Zh.Device, requestPayload:Ota.ImageInfo=null) { +export async function isUpdateAvailable(device: Zh.Device, requestPayload: Ota.ImageInfo = null) { return common.isUpdateAvailable(device, requestPayload, common.isNewImageAvailable, getImageMeta); } diff --git a/src/lib/ota/securifi.ts b/src/lib/ota/securifi.ts index 05f59bf69d5af..e5f023da5c79d 100644 --- a/src/lib/ota/securifi.ts +++ b/src/lib/ota/securifi.ts @@ -2,7 +2,7 @@ import {Zh, Ota} from '../types'; import * as common from './common'; import * as zigbeeOTA from './zigbeeOTA'; -export async function isUpdateAvailable(device: Zh.Device, requestPayload:Ota.ImageInfo=null) { +export async function isUpdateAvailable(device: Zh.Device, requestPayload: Ota.ImageInfo = null) { if (device.modelID === 'PP-WHT-US') { // see https://github.com/Koenkk/zigbee-OTA/pull/14 const scenesEndpoint = device.endpoints.find((e) => e.supportsOutputCluster('genScenes')); diff --git a/src/lib/ota/tradfri.ts b/src/lib/ota/tradfri.ts index 9c262a0edac55..f09a11fb4ca4a 100644 --- a/src/lib/ota/tradfri.ts +++ b/src/lib/ota/tradfri.ts @@ -37,7 +37,7 @@ export async function getImageMeta(current: Ota.ImageInfo, device: Zh.Device): P * Interface implementation */ -export async function isUpdateAvailable(device: Zh.Device, requestPayload:Ota.ImageInfo=null) { +export async function isUpdateAvailable(device: Zh.Device, requestPayload: Ota.ImageInfo = null) { return common.isUpdateAvailable(device, requestPayload, common.isNewImageAvailable, getImageMeta); } diff --git a/src/lib/ota/ubisys.ts b/src/lib/ota/ubisys.ts index c399ee206becc..01fb2b095b0be 100644 --- a/src/lib/ota/ubisys.ts +++ b/src/lib/ota/ubisys.ts @@ -1,9 +1,10 @@ const firmwareHtmlPageUrl = 'http://fwu.ubisys.de/smarthome/OTA/release/index'; const imageRegex = /10F2-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{8})\S*ota1?\.zigbee/gi; import url from 'url'; -import * as common from './common'; -import {Ota, Zh} from '../types'; + import {logger} from '../logger'; +import {Ota, Zh} from '../types'; +import * as common from './common'; const NS = 'zhc:ota:ubisys'; const axios = common.getAxios(); @@ -38,8 +39,11 @@ export async function getImageMeta(current: Ota.ImageInfo, device: Zh.Device): P while (imageMatch != null) { logger.debug(`Image found: ${imageMatch[0]}`, NS); - if (parseInt(imageMatch[1], 16) === current.imageType && - parseInt(imageMatch[2], 16) <= device.hardwareVersion && device.hardwareVersion <= parseInt(imageMatch[3], 16)) { + if ( + parseInt(imageMatch[1], 16) === current.imageType && + parseInt(imageMatch[2], 16) <= device.hardwareVersion && + device.hardwareVersion <= parseInt(imageMatch[3], 16) + ) { if (highestMatch === null || parseInt(highestMatch[4], 16) < parseInt(imageMatch[4], 16)) { highestMatch = imageMatch; } @@ -63,7 +67,7 @@ export async function getImageMeta(current: Ota.ImageInfo, device: Zh.Device): P * Interface implementation */ -export async function isUpdateAvailable(device: Zh.Device, requestPayload:Ota.ImageInfo=null) { +export async function isUpdateAvailable(device: Zh.Device, requestPayload: Ota.ImageInfo = null) { return common.isUpdateAvailable(device, requestPayload, common.isNewImageAvailable, getImageMeta); } diff --git a/src/lib/ota/zigbeeOTA.ts b/src/lib/ota/zigbeeOTA.ts index 1b81725ff29d1..2e059ddfada72 100644 --- a/src/lib/ota/zigbeeOTA.ts +++ b/src/lib/ota/zigbeeOTA.ts @@ -1,8 +1,8 @@ const url = 'https://raw.githubusercontent.com/Koenkk/zigbee-OTA/master/index.json'; const caBundleUrl = 'https://raw.githubusercontent.com/Koenkk/zigbee-OTA/master/cacerts.pem'; -import * as common from './common'; -import {Zh, Ota, KeyValueAny} from '../types'; import {logger} from '../logger'; +import {Zh, Ota, KeyValueAny} from '../types'; +import * as common from './common'; const NS = 'zhc:ota'; const axios = common.getAxios(); @@ -20,9 +20,7 @@ function fillImageInfo(meta: KeyValueAny) { } // Nothing to do if needed fields were filled already - if (meta.hasOwnProperty('imageType') && - meta.hasOwnProperty('manufacturerCode') && - meta.hasOwnProperty('fileVersion')) { + if (meta.hasOwnProperty('imageType') && meta.hasOwnProperty('manufacturerCode') && meta.hasOwnProperty('fileVersion')) { return meta; } @@ -51,7 +49,7 @@ async function getIndex() { const localIndex = await common.getOverrideIndexFile(overrideIndexFileName); // Resulting index will have overridden items first - return localIndex.concat(mainIndex).map((item: KeyValueAny) => common.isValidUrl(item.url) ? item : fillImageInfo(item)); + return localIndex.concat(mainIndex).map((item: KeyValueAny) => (common.isValidUrl(item.url) ? item : fillImageInfo(item))); } return mainIndex; } @@ -63,9 +61,15 @@ export async function getImageMeta(current: Ota.ImageInfo, device: Zh.Device): P // However Gledopto pro products use the same imageType (0) for every device while the image is different. // For this case additional identification through the modelId is done. // In the case of Tuya and Moes, additional identification is carried out through the manufacturerName. - const image = images.find((i: KeyValueAny) => i.imageType === current.imageType && i.manufacturerCode === current.manufacturerCode && - (!i.minFileVersion || current.fileVersion >= i.minFileVersion) && (!i.maxFileVersion || current.fileVersion <= i.maxFileVersion) && - (!i.modelId || i.modelId === device.modelID) && (!i.manufacturerName || i.manufacturerName.includes(device.manufacturerName))); + const image = images.find( + (i: KeyValueAny) => + i.imageType === current.imageType && + i.manufacturerCode === current.manufacturerCode && + (!i.minFileVersion || current.fileVersion >= i.minFileVersion) && + (!i.maxFileVersion || current.fileVersion <= i.maxFileVersion) && + (!i.modelId || i.modelId === device.modelID) && + (!i.manufacturerName || i.manufacturerName.includes(device.manufacturerName)), + ); if (!image) { return null; @@ -113,7 +117,7 @@ export async function getFirmwareFile(image: KeyValueAny) { * Interface implementation */ -export async function isUpdateAvailable(device: Zh.Device, requestPayload:Ota.ImageInfo=null) { +export async function isUpdateAvailable(device: Zh.Device, requestPayload: Ota.ImageInfo = null) { return common.isUpdateAvailable(device, requestPayload, isNewImageAvailable, getImageMeta); } diff --git a/src/lib/philips.ts b/src/lib/philips.ts index ea23997c41046..7cf6c6450c8f7 100644 --- a/src/lib/philips.ts +++ b/src/lib/philips.ts @@ -1,15 +1,16 @@ -import {ColorXY, ColorRGB} from './color'; -import * as ota from './ota'; -import * as exposes from './exposes'; +import {Zcl} from 'zigbee-herdsman'; + import tz from '../converters/toZigbee'; +import {ColorXY, ColorRGB} from './color'; import * as libColor from './color'; -import * as utils from './utils'; -import {Zcl} from 'zigbee-herdsman'; +import * as exposes from './exposes'; +import {logger} from './logger'; +import * as modernExtend from './modernExtend'; +import * as ota from './ota'; import * as globalStore from './store'; import {Fz, KeyValue, KeyValueAny, Tz} from './types'; -import * as modernExtend from './modernExtend'; +import * as utils from './utils'; import {isObject} from './utils'; -import {logger} from './logger'; const NS = 'zhc:philips'; const ea = exposes.access; @@ -17,24 +18,20 @@ const e = exposes.presets; const encodeRGBToScaledGradient = (hex: string) => { const xy = ColorRGB.fromHex(hex).toXY(); - const x = xy.x * 4095 / 0.7347; - const y = xy.y * 4095 / 0.8413; + const x = (xy.x * 4095) / 0.7347; + const y = (xy.y * 4095) / 0.8413; const xx = Math.round(x).toString(16); const yy = Math.round(y).toString(16); - return [ - xx[1], xx[2], - yy[2], xx[0], - yy[0], yy[1], - ].join(''); + return [xx[1], xx[2], yy[2], xx[0], yy[0], yy[1]].join(''); }; const decodeScaledGradientToRGB = (p: string) => { const x = p[3] + p[0] + p[1]; const y = p[4] + p[5] + p[2]; - const xx = Number((parseInt(x, 16) * 0.7347 / 4095).toFixed(4)); - const yy = Number((parseInt(y, 16) * 0.8413 / 4095).toFixed(4)); + const xx = Number(((parseInt(x, 16) * 0.7347) / 4095).toFixed(4)); + const yy = Number(((parseInt(y, 16) * 0.8413) / 4095).toFixed(4)); return new ColorXY(xx, yy).toRGB().toHEX(); }; @@ -55,7 +52,7 @@ const knownEffects = { '0c80': 'glisten', }; -export function philipsLight(args?: modernExtend.LightArgs & {hueEffect?: boolean, gradient?: true | {extraEffects: string[]}}) { +export function philipsLight(args?: modernExtend.LightArgs & {hueEffect?: boolean; gradient?: true | {extraEffects: string[]}}) { args = {hueEffect: true, turnsOffAtBrightness1: true, ota: ota.zigbeeOTA, ...args}; if (args.hueEffect || args.gradient) args.effect = false; if (args.color) args.color = {modes: ['xy', 'hs'], ...(isObject(args.color) ? args.color : {})}; @@ -75,18 +72,17 @@ export function philipsLight(args?: modernExtend.LightArgs & {hueEffect?: boolea result.exposes.push( // gradient_scene is deprecated, use gradient instead e.enum('gradient_scene', ea.SET, Object.keys(gradientScenes)), - e.list('gradient', ea.ALL, e.text('hex', ea.ALL).withDescription('Color in RGB HEX format (eg #663399)')) + e + .list('gradient', ea.ALL, e.text('hex', ea.ALL).withDescription('Color in RGB HEX format (eg #663399)')) .withLengthMin(1) .withLengthMax(9) .withDescription('List of RGB HEX colors'), ); - result.configure.push( - async (device, coordinatorEndpoint, definition) => { - for (const ep of device.endpoints) { - await ep.bind('manuSpecificPhilips2', coordinatorEndpoint); - } - }, - ); + result.configure.push(async (device, coordinatorEndpoint, definition) => { + for (const ep of device.endpoints) { + await ep.bind('manuSpecificPhilips2', coordinatorEndpoint); + } + }); } effects.push('finish_effect', 'stop_effect', 'stop_hue_effect'); result.exposes.push(e.enum('effect', ea.SET, effects)); @@ -147,8 +143,8 @@ export const philipsTz = { if (utils.isEndpoint(entity) && entity.supportsInputCluster('lightingColorCtrl')) { const readResult = await entity.read('lightingColorCtrl', ['colorCapabilities']); supports = { - colorTemperature: (readResult.colorCapabilities & 1 << 4) > 0, - colorXY: (readResult.colorCapabilities & 1 << 3) > 0, + colorTemperature: (readResult.colorCapabilities & (1 << 4)) > 0, + colorXY: (readResult.colorCapabilities & (1 << 3)) > 0, }; } else if (entity.constructor.name === 'Group') { supports = {colorTemperature: true, colorXY: true}; @@ -171,8 +167,7 @@ export const philipsTz = { } else if (value === 'on') { await entity.write('genOnOff', {0x4003: {value: 0x01, type: 0x30}}); - let brightness = meta.message.hasOwnProperty('hue_power_on_brightness') ? - meta.message.hue_power_on_brightness : 0xfe; + let brightness = meta.message.hasOwnProperty('hue_power_on_brightness') ? meta.message.hue_power_on_brightness : 0xfe; if (brightness === 255) { // 255 (0xFF) is the value for recover, therefore set it to 254 (0xFE) brightness = 254; @@ -181,18 +176,15 @@ export const philipsTz = { utils.assertEndpoint(entity); if (entity.supportsInputCluster('lightingColorCtrl')) { - if ( - meta.message.hasOwnProperty('hue_power_on_color_temperature') && - meta.message.hasOwnProperty('hue_power_on_color') - ) { + if (meta.message.hasOwnProperty('hue_power_on_color_temperature') && meta.message.hasOwnProperty('hue_power_on_color')) { logger.error(`Provide either color temperature or color, not both`, NS); } else if (meta.message.hasOwnProperty('hue_power_on_color_temperature')) { const colortemp = meta.message.hue_power_on_color_temperature; await entity.write('lightingColorCtrl', {0x4010: {value: colortemp, type: 0x21}}); // Set color to default if (supports.colorXY) { - await entity.write('lightingColorCtrl', {0x0003: {value: 0xFFFF, type: 0x21}}, manufacturerOptions); - await entity.write('lightingColorCtrl', {0x0004: {value: 0xFFFF, type: 0x21}}, manufacturerOptions); + await entity.write('lightingColorCtrl', {0x0003: {value: 0xffff, type: 0x21}}, manufacturerOptions); + await entity.write('lightingColorCtrl', {0x0004: {value: 0xffff, type: 0x21}}, manufacturerOptions); } } else if (meta.message.hasOwnProperty('hue_power_on_color')) { // @ts-expect-error @@ -213,8 +205,8 @@ export const philipsTz = { } if (supports.colorXY) { - await entity.write('lightingColorCtrl', {0x0003: {value: 0xFFFF, type: 0x21}}, manufacturerOptions); - await entity.write('lightingColorCtrl', {0x0004: {value: 0xFFFF, type: 0x21}}, manufacturerOptions); + await entity.write('lightingColorCtrl', {0x0003: {value: 0xffff, type: 0x21}}, manufacturerOptions); + await entity.write('lightingColorCtrl', {0x0004: {value: 0xffff, type: 0x21}}, manufacturerOptions); } } } @@ -236,7 +228,7 @@ export const philipsTz = { key: ['motion_sensitivity'], convertSet: async (entity, key, value, meta) => { // make sure you write to second endpoint! - const lookup = {'low': 0, 'medium': 1, 'high': 2, 'very_high': 3, 'max': 4}; + const lookup = {low: 0, medium: 1, high: 2, very_high: 3, max: 4}; const payload = {48: {value: utils.getFromLookup(value, lookup), type: 32}}; await entity.write('msOccupancySensing', payload, manufacturerOptions); return {state: {motion_sensitivity: value}}; @@ -262,92 +254,92 @@ export {philipsTz as tz}; const manufacturerOptions = {manufacturerCode: Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V}; const gradientScenes = { - 'blossom': '50010400135000000039d553d2955ba5287a9f697e25fb802800', - 'crocus': '50010400135000000050389322f97f2b597343764cc664282800', - 'precious': '5001040013500000007fa8838bb9789a786d7577499a773f2800', - 'narcissa': '500104001350000000b0498a5c0a888fea89eb0b7ee15c742800', - 'beginnings': '500104001350000000b3474def153e2ad42e98232c7483292800', - 'first_light': '500104001350000000b28b7900e959d3f648a614389723362800', - 'horizon': '500104001350000000488b7d6cbb750c6642f1133cc4033c2800', - 'valley_dawn': '500104001350000000c1aa7de03a7a8ce861c7c4410d94412800', - 'sunflare': '500104001350000000d0aa7d787a7daf197590154d6c14472800', - 'emerald_flutter': '5001040013500000006a933977e34bb0d35e916468f246792800', - 'memento': '500104001350000000f87318a3e31962331ec3532cceea892800', - 'resplendent': '500104001350000000278b6d257a58efe84204273a35f5252800', - 'scarlet_dream': '500104001350000000b02c654e4c5b45ab51fb0950d6c84d2800', - 'lovebirds': '50010400135000000053ab84ea1a7e35fb7c098c73994c772800', - 'smitten': '500104001350000000fe7b70a74b6aa42b65811b60550a592800', - 'glitz_and_glam': '500104001350000000cc193cb9b845bad9521d1c77bf6c712800', - 'promise': '500104001350000000258b606eca6b28d6382db445df26812800', - 'ruby_romance': '5001040013500000000edb63cbcb6bac0c670b2d58204e572800', - 'city_of_love': '50010400135000000055830e5cf31b6aa339d2ec70908b802800', - 'honolulu': '500104001350000000dbfd59866c6378ec6c45cc765c0a822800', - 'savanna_sunset': '50010400135000000005ae65c38c6c6b4b7573ca820fc9832800', - 'golden_pond': '5001040013500000007e4a88cc4a8605db8728ec7b666c792800', - 'runy_glow': '50010400135000000095bb53ac2a56eb99591e095c54985e2800', - 'tropical_twilight': '500104001350000000408523a0b636e777524c0a71a76c6e2800', - 'miami': '50010400135000000022ec61e6d94902d83766c3305a43182800', - 'cancun': '500104001350000000a7eb54673d55944e6265fd6e26bb842800', - 'rio': '500104001350000000a26526088c51a74b58ea6b7137ba892800', - 'chinatown': '500104001350000000b33e5b408e59d90d5b4c6c6360ac792800', - 'ibiza': '500104001350000000014d6d708c73827b7b6c7a8887f98a2800', - 'osaka': '500104001350000000d649510b5c4deb7c5d8b6d6d2b9b802800', - 'tokyo': '500104001350000000d1c311665331d3451fd59c4e394c7b2800', - 'motown': '50010400135000000055730e5db3156623306c533d7a235c2800', - 'fairfax': '50010400135000000072d34a3664477d7a61581d5fc08e5b2800', - 'galaxy': '500104001350000000a6cb638b2a4f8cfa549bb9549ff73a2800', - 'starlight': '5001040013500000008d897134a9653ec854d2963ed1d4282800', + blossom: '50010400135000000039d553d2955ba5287a9f697e25fb802800', + crocus: '50010400135000000050389322f97f2b597343764cc664282800', + precious: '5001040013500000007fa8838bb9789a786d7577499a773f2800', + narcissa: '500104001350000000b0498a5c0a888fea89eb0b7ee15c742800', + beginnings: '500104001350000000b3474def153e2ad42e98232c7483292800', + first_light: '500104001350000000b28b7900e959d3f648a614389723362800', + horizon: '500104001350000000488b7d6cbb750c6642f1133cc4033c2800', + valley_dawn: '500104001350000000c1aa7de03a7a8ce861c7c4410d94412800', + sunflare: '500104001350000000d0aa7d787a7daf197590154d6c14472800', + emerald_flutter: '5001040013500000006a933977e34bb0d35e916468f246792800', + memento: '500104001350000000f87318a3e31962331ec3532cceea892800', + resplendent: '500104001350000000278b6d257a58efe84204273a35f5252800', + scarlet_dream: '500104001350000000b02c654e4c5b45ab51fb0950d6c84d2800', + lovebirds: '50010400135000000053ab84ea1a7e35fb7c098c73994c772800', + smitten: '500104001350000000fe7b70a74b6aa42b65811b60550a592800', + glitz_and_glam: '500104001350000000cc193cb9b845bad9521d1c77bf6c712800', + promise: '500104001350000000258b606eca6b28d6382db445df26812800', + ruby_romance: '5001040013500000000edb63cbcb6bac0c670b2d58204e572800', + city_of_love: '50010400135000000055830e5cf31b6aa339d2ec70908b802800', + honolulu: '500104001350000000dbfd59866c6378ec6c45cc765c0a822800', + savanna_sunset: '50010400135000000005ae65c38c6c6b4b7573ca820fc9832800', + golden_pond: '5001040013500000007e4a88cc4a8605db8728ec7b666c792800', + runy_glow: '50010400135000000095bb53ac2a56eb99591e095c54985e2800', + tropical_twilight: '500104001350000000408523a0b636e777524c0a71a76c6e2800', + miami: '50010400135000000022ec61e6d94902d83766c3305a43182800', + cancun: '500104001350000000a7eb54673d55944e6265fd6e26bb842800', + rio: '500104001350000000a26526088c51a74b58ea6b7137ba892800', + chinatown: '500104001350000000b33e5b408e59d90d5b4c6c6360ac792800', + ibiza: '500104001350000000014d6d708c73827b7b6c7a8887f98a2800', + osaka: '500104001350000000d649510b5c4deb7c5d8b6d6d2b9b802800', + tokyo: '500104001350000000d1c311665331d3451fd59c4e394c7b2800', + motown: '50010400135000000055730e5db3156623306c533d7a235c2800', + fairfax: '50010400135000000072d34a3664477d7a61581d5fc08e5b2800', + galaxy: '500104001350000000a6cb638b2a4f8cfa549bb9549ff73a2800', + starlight: '5001040013500000008d897134a9653ec854d2963ed1d4282800', 'blood moon': '500104001350000000202a6987c8599ee647ec632779c3142800', - 'artic_aurora': '50010400135000000082548922057511046571c32d5b93192800', - 'moonlight': '50010400135000000055730e5e9320c1832e96243ebec7652800', - 'nebula': '50010400135000000026c852e106460d653ee745342964142800', - 'sundown': '500104001350000000f37c68157c6d8efa755ac5512e24332800', - 'blue_lagoon': '50010400135000000088c3623975699ea672a0c8831ada6d2800', - 'palm_beach': '5001040013500000005ec4679ba56077f85a80ea64639c6a2800', - 'lake_placid': '5001040013500000002eab69239a692d996552c54c39743a2800', - 'mountain_breeze': '500104001350000000df843d2355419195465a98674ca97b2800', - 'lake_mist': '500104001350000000e3286f39b96859f86266e54ded943f2800', - 'ocean_dawn': '5001040013500000005cf9779da97105b96b07485e32564a2800', - 'frosty_dawn': '5001040013500000006d6883bca87e3029758ec9722d6a722800', - 'sunday_morning': '5001040013500000002c586dc6f87345997c63f983f777892800', - 'emerald_isle': '500104001350000000e535628dc57ed2667d8b687d1e2a812800', - 'spring_blossom': '500104001350000000a8b75fd0c75826b851a7094d305b652800', - 'midsummer_sun': '500104001350000000002984799984dd29848eba836c0b7f2800', - 'autumn_gold': '500104001350000000435a7817aa7ba3f979a8a981f3c9852800', - 'spring_lake': '5001040013500000004a976d3347736e677561b77a4b07812800', - 'winter_mountain': '5001040013500000002c555c68c55d7c555ef165606136622800', - 'midwinter': '500104001350000000bda5532c554dbd254cd5a4428d94392800', - 'amber_bloom': '500104001350000000739d67f2bc7372ec78a0ab78be8a6f2800', - 'lily': '5001040013500000009cfc76c5ab793d4a6a1a9b586b9c522800', - 'painted_sky': '500104001350000000d1c424c3d63783384c3f7a6a83bd6d2800', - 'winter_beauty': '500104001350000000e2335ea7b4942467952db986a7ab7b2800', - 'orange_fields': '500104001350000000409c69694c79eafa88498a8fb867aa2800', - 'forest_adventure': '50010400135000000023999bbd76b363d4b674d3415fb3222800', - 'blue_planet': '50010400135000000037a7a3a403b489737b2b746e6873362800', - 'soho': '500104001350000000c52c4e220b6eed8a53d404192b04782800', - 'vapor_wave': '500104001350000000e1c32401251acb183ac31b8051ea842800', - 'magneto': '50010400135000000077b3286d9340b9e3662d99943c9b852800', - 'tyrell': '500104001350000000ef4419a898370ea84698353574434e2800', - 'disturbia': '50010400135000000084f371a4845e6998388c3b4f57ce582800', - 'hal': '50010400135000000075f351a6244cf6dc5d480c658cda862800', - 'golden_star': '5001040013500000007a4a8702eb8372ac7892cd61d51e5c2800', - 'under_the_tree': '5001040013500000001de498b9a3cc0c9b8563bb6cc1ae5d2800', - 'silent_night': '5001040013500000009e296a245a6f660a75086b70953b6e2800', - 'rosy_sparkle': '500104001350000000810967c63a6cb2aa5ea7094eddd73c2800', - 'festive_fun': '5001040013500000005a9318de53123e9414fdcc67839d612800', - 'colour_burst': '500104001350000000f2731ff0c6266a6c64246e57d4f98f2800', - 'crystalline': '5001040013500000006ea96a92a85e58074e18543d9cf3332800', + artic_aurora: '50010400135000000082548922057511046571c32d5b93192800', + moonlight: '50010400135000000055730e5e9320c1832e96243ebec7652800', + nebula: '50010400135000000026c852e106460d653ee745342964142800', + sundown: '500104001350000000f37c68157c6d8efa755ac5512e24332800', + blue_lagoon: '50010400135000000088c3623975699ea672a0c8831ada6d2800', + palm_beach: '5001040013500000005ec4679ba56077f85a80ea64639c6a2800', + lake_placid: '5001040013500000002eab69239a692d996552c54c39743a2800', + mountain_breeze: '500104001350000000df843d2355419195465a98674ca97b2800', + lake_mist: '500104001350000000e3286f39b96859f86266e54ded943f2800', + ocean_dawn: '5001040013500000005cf9779da97105b96b07485e32564a2800', + frosty_dawn: '5001040013500000006d6883bca87e3029758ec9722d6a722800', + sunday_morning: '5001040013500000002c586dc6f87345997c63f983f777892800', + emerald_isle: '500104001350000000e535628dc57ed2667d8b687d1e2a812800', + spring_blossom: '500104001350000000a8b75fd0c75826b851a7094d305b652800', + midsummer_sun: '500104001350000000002984799984dd29848eba836c0b7f2800', + autumn_gold: '500104001350000000435a7817aa7ba3f979a8a981f3c9852800', + spring_lake: '5001040013500000004a976d3347736e677561b77a4b07812800', + winter_mountain: '5001040013500000002c555c68c55d7c555ef165606136622800', + midwinter: '500104001350000000bda5532c554dbd254cd5a4428d94392800', + amber_bloom: '500104001350000000739d67f2bc7372ec78a0ab78be8a6f2800', + lily: '5001040013500000009cfc76c5ab793d4a6a1a9b586b9c522800', + painted_sky: '500104001350000000d1c424c3d63783384c3f7a6a83bd6d2800', + winter_beauty: '500104001350000000e2335ea7b4942467952db986a7ab7b2800', + orange_fields: '500104001350000000409c69694c79eafa88498a8fb867aa2800', + forest_adventure: '50010400135000000023999bbd76b363d4b674d3415fb3222800', + blue_planet: '50010400135000000037a7a3a403b489737b2b746e6873362800', + soho: '500104001350000000c52c4e220b6eed8a53d404192b04782800', + vapor_wave: '500104001350000000e1c32401251acb183ac31b8051ea842800', + magneto: '50010400135000000077b3286d9340b9e3662d99943c9b852800', + tyrell: '500104001350000000ef4419a898370ea84698353574434e2800', + disturbia: '50010400135000000084f371a4845e6998388c3b4f57ce582800', + hal: '50010400135000000075f351a6244cf6dc5d480c658cda862800', + golden_star: '5001040013500000007a4a8702eb8372ac7892cd61d51e5c2800', + under_the_tree: '5001040013500000001de498b9a3cc0c9b8563bb6cc1ae5d2800', + silent_night: '5001040013500000009e296a245a6f660a75086b70953b6e2800', + rosy_sparkle: '500104001350000000810967c63a6cb2aa5ea7094eddd73c2800', + festive_fun: '5001040013500000005a9318de53123e9414fdcc67839d612800', + colour_burst: '500104001350000000f2731ff0c6266a6c64246e57d4f98f2800', + crystalline: '5001040013500000006ea96a92a85e58074e18543d9cf3332800', }; const hueEffects = { - 'candle': '21000101', - 'fireplace': '21000102', - 'colorloop': '21000103', - 'sunrise': '21000109', - 'sparkle': '2100010a', - 'opal': '2100010b', - 'glisten': '2100010c', - 'stop_hue_effect': '200000', + candle: '21000101', + fireplace: '21000102', + colorloop: '21000103', + sunrise: '21000109', + sparkle: '2100010a', + opal: '2100010b', + glisten: '2100010c', + stop_hue_effect: '200000', }; export const philipsFz = { @@ -369,7 +361,7 @@ export const philipsFz = { convert: (model, msg, publish, options, meta) => { const buttonLookup: KeyValue = {1: 'button_1', 2: 'button_2', 3: 'button_3', 4: 'button_4', 20: 'dial'}; const button = buttonLookup[msg.data['button']]; - const direction = msg.data['unknown2'] <127 ? 'right' : 'left'; + const direction = msg.data['unknown2'] < 127 ? 'right' : 'left'; const time = msg.data['time']; const payload: KeyValue = {}; @@ -531,15 +523,16 @@ function decodeGradientColors(input: string, opts: KeyValue) { const yLow = parseInt(input.slice(0, 2), 16) << 8; input = input.slice(2); - - const x = Math.round((xHigh | xLow) / 65535 * 10000) / 10000; - const y = Math.round((yHigh | yLow) / 65535 * 10000) / 10000; + const x = Math.round(((xHigh | xLow) / 65535) * 10000) / 10000; + const y = Math.round(((yHigh | yLow) / 65535) * 10000) / 10000; if (mode === COLOR_MODE_COLOR_XY) { return { color_mode: 'xy', - x, y, - brightness, on, + x, + y, + brightness, + on, }; } @@ -549,8 +542,10 @@ function decodeGradientColors(input: string, opts: KeyValue) { const name = knownEffects[effect] || `unknown_${effect}`; return { color_mode: 'xy', - x, y, - brightness, on, + x, + y, + brightness, + on, name, }; } else if (mode === COLOR_MODE_COLOR_TEMP) { diff --git a/src/lib/reporting.ts b/src/lib/reporting.ts index 2618beede80f7..c54214cce1c36 100644 --- a/src/lib/reporting.ts +++ b/src/lib/reporting.ts @@ -10,7 +10,6 @@ export function payload(attribute: string | number, min: number, max: number, ch reportableChange: change, }; - if (overrides) { if (overrides.hasOwnProperty('min')) payload.minimumReportInterval = overrides.min; if (overrides.hasOwnProperty('max')) payload.maximumReportInterval = overrides.max; @@ -53,9 +52,7 @@ export const currentPositionTiltPercentage = async (endpoint: Zh.Endpoint, overr await endpoint.configureReporting('closuresWindowCovering', p); }; export const batteryPercentageRemaining = async (endpoint: Zh.Endpoint, overrides?: Reporting.Override) => { - const p = payload( - 'batteryPercentageRemaining', repInterval.HOUR, repInterval.MAX, 0, overrides, - ); + const p = payload('batteryPercentageRemaining', repInterval.HOUR, repInterval.MAX, 0, overrides); await endpoint.configureReporting('genPowerCfg', p); await endpoint.read('genPowerCfg', ['batteryPercentageRemaining']); }; diff --git a/src/lib/store.ts b/src/lib/store.ts index df749c1a7aec4..f4a6d6f8cc56e 100644 --- a/src/lib/store.ts +++ b/src/lib/store.ts @@ -20,7 +20,7 @@ export function hasValue(entity: Zh.Endpoint | Zh.Group | Zh.Device, key: string return store.has(entityKey) && store.get(entityKey).hasOwnProperty(key); } -export function getValue(entity: Zh.Endpoint | Zh.Group | Zh.Device, key: string, default_:unknown=undefined) { +export function getValue(entity: Zh.Endpoint | Zh.Group | Zh.Device, key: string, default_: unknown = undefined) { const entityKey = getEntityKey(entity); if (store.has(entityKey) && store.get(entityKey).hasOwnProperty(key)) { return store.get(entityKey)[key]; diff --git a/src/lib/tuya.ts b/src/lib/tuya.ts index f5bdb5342aa61..411949bf60a78 100644 --- a/src/lib/tuya.ts +++ b/src/lib/tuya.ts @@ -1,21 +1,35 @@ +import fz from '../converters/fromZigbee'; +import tz from '../converters/toZigbee'; import * as constants from './constants'; -import * as globalStore from './store'; import * as exposes from './exposes'; -import tz from '../converters/toZigbee'; -import fz from '../converters/fromZigbee'; -import * as utils from './utils'; +import {logger} from './logger'; import * as modernExtend from './modernExtend'; +import * as globalStore from './store'; import { - Tuya, OnEventType, OnEventData, Zh, KeyValue, Tz, Fz, Expose, OnEvent, ModernExtend, Range, KeyValueNumberString, DefinitionExposesFunction, + Tuya, + OnEventType, + OnEventData, + Zh, + KeyValue, + Tz, + Fz, + Expose, + OnEvent, + ModernExtend, + Range, + KeyValueNumberString, + DefinitionExposesFunction, } from './types'; -import {logger} from './logger'; +import * as utils from './utils'; // import {Color} from './color'; const NS = 'zhc:tuya'; const e = exposes.presets; const ea = exposes.access; -interface KeyValueStringEnum {[s: string]: Enum} +interface KeyValueStringEnum { + [s: string]: Enum; +} export const dataTypes = { raw: 0, // [ bytes ] @@ -37,14 +51,17 @@ export function convertBufferToNumber(chunks: Buffer | number[]) { function convertStringToHexArray(value: string) { const asciiKeys = []; - for (let i = 0; i < value.length; i ++) { + for (let i = 0; i < value.length; i++) { asciiKeys.push(value[i].charCodeAt(0)); } return asciiKeys; } export function onEvent(args?: { - queryOnDeviceAnnounce?: boolean, timeStart?: '1970' | '2000', respondToMcuVersionResponse?: boolean, queryIntervalSeconds?: number + queryOnDeviceAnnounce?: boolean; + timeStart?: '1970' | '2000'; + respondToMcuVersionResponse?: boolean; + queryIntervalSeconds?: number; }): OnEvent { return async (type, data, device, settings, state) => { args = {queryOnDeviceAnnounce: false, timeStart: '1970', respondToMcuVersionResponse: true, ...args}; @@ -53,7 +70,7 @@ export function onEvent(args?: { if (type === 'message' && data.cluster === 'manuSpecificTuya') { if (args.respondToMcuVersionResponse && data.type === 'commandMcuVersionResponse') { - await endpoint.command('manuSpecificTuya', 'mcuVersionRequest', {'seq': 0x0002}); + await endpoint.command('manuSpecificTuya', 'mcuVersionRequest', {seq: 0x0002}); } else if (data.type === 'commandMcuGatewayConnectionStatus') { // "payload" can have the following values: // 0x00: The gateway is not connected to the internet. @@ -67,14 +84,11 @@ export function onEvent(args?: { if (data.type === 'commandMcuSyncTime' && data.cluster === 'manuSpecificTuya') { try { const offset = args.timeStart === '2000' ? constants.OneJanuary2000 : 0; - const utcTime = Math.round(((new Date()).getTime() - offset) / 1000); - const localTime = utcTime - (new Date()).getTimezoneOffset() * 60; + const utcTime = Math.round((new Date().getTime() - offset) / 1000); + const localTime = utcTime - new Date().getTimezoneOffset() * 60; const payload = { payloadSize: 8, - payload: [ - ...convertDecimalValueTo4ByteHexArray(utcTime), - ...convertDecimalValueTo4ByteHexArray(localTime), - ], + payload: [...convertDecimalValueTo4ByteHexArray(utcTime), ...convertDecimalValueTo4ByteHexArray(localTime)], }; await endpoint.command('manuSpecificTuya', 'mcuSyncTime', payload, {}); } catch (error) { @@ -96,7 +110,9 @@ export function onEvent(args?: { const timer = setTimeout(async () => { try { await endpoint.command('manuSpecificTuya', 'dataQuery', {}); - } catch (error) {/* Do nothing*/} + } catch (error) { + /* Do nothing*/ + } setTimer(); }, args.queryIntervalSeconds * 1000); globalStore.putValue(device, 'query_interval', timer); @@ -110,22 +126,22 @@ export function onEvent(args?: { function getDataValue(dpValue: Tuya.DpValue) { let dataString = ''; switch (dpValue.datatype) { - case dataTypes.raw: - return dpValue.data; - case dataTypes.bool: - return dpValue.data[0] === 1; - case dataTypes.number: - return convertBufferToNumber(dpValue.data); - case dataTypes.string: - // Don't use .map here, doesn't work: https://github.com/Koenkk/zigbee-herdsman-converters/pull/1799/files#r530377091 - for (let i = 0; i < dpValue.data.length; ++i) { - dataString += String.fromCharCode(dpValue.data[i]); - } - return dataString; - case dataTypes.enum: - return dpValue.data[0]; - case dataTypes.bitmap: - return convertBufferToNumber(dpValue.data); + case dataTypes.raw: + return dpValue.data; + case dataTypes.bool: + return dpValue.data[0] === 1; + case dataTypes.number: + return convertBufferToNumber(dpValue.data); + case dataTypes.string: + // Don't use .map here, doesn't work: https://github.com/Koenkk/zigbee-herdsman-converters/pull/1799/files#r530377091 + for (let i = 0; i < dpValue.data.length; ++i) { + dataString += String.fromCharCode(dpValue.data[i]); + } + return dataString; + case dataTypes.enum: + return dpValue.data[0]; + case dataTypes.bitmap: + return convertBufferToNumber(dpValue.data); } } @@ -145,15 +161,23 @@ function convertDecimalValueTo2ByteHexArray(value: number) { return [chunk1, chunk2].map((hexVal) => parseInt(hexVal, 16)); } -export async function onEventMeasurementPoll(type: OnEventType, data: OnEventData, device: Zh.Device, options: KeyValue, - electricalMeasurement=true, metering=false) { +export async function onEventMeasurementPoll( + type: OnEventType, + data: OnEventData, + device: Zh.Device, + options: KeyValue, + electricalMeasurement = true, + metering = false, +) { const endpoint = device.getEndpoint(1); if (type === 'stop') { clearTimeout(globalStore.getValue(device, 'measurement_poll')); globalStore.clearValue(device, 'measurement_poll'); } else if (!globalStore.hasValue(device, 'measurement_poll')) { const seconds = utils.toNumber( - options && options.measurement_poll_interval ? options.measurement_poll_interval : 60, 'measurement_poll_interval'); + options && options.measurement_poll_interval ? options.measurement_poll_interval : 60, + 'measurement_poll_interval', + ); if (seconds === -1) return; const setTimer = () => { const timer = setTimeout(async () => { @@ -164,7 +188,9 @@ export async function onEventMeasurementPoll(type: OnEventType, data: OnEventDat if (metering) { await endpoint.read('seMetering', ['currentSummDelivered']); } - } catch (error) {/* Do nothing*/} + } catch (error) { + /* Do nothing*/ + } setTimer(); }, seconds * 1000); globalStore.putValue(device, 'measurement_poll', timer); @@ -178,16 +204,13 @@ export async function onEventSetTime(type: OnEventType, data: KeyValue, device: if (data.type === 'commandMcuSyncTime' && data.cluster === 'manuSpecificTuya') { try { - const utcTime = Math.round(((new Date()).getTime() - constants.OneJanuary2000) / 1000); - const localTime = utcTime - (new Date()).getTimezoneOffset() * 60; + const utcTime = Math.round((new Date().getTime() - constants.OneJanuary2000) / 1000); + const localTime = utcTime - new Date().getTimezoneOffset() * 60; const endpoint = device.getEndpoint(1); const payload = { payloadSize: 8, - payload: [ - ...convertDecimalValueTo4ByteHexArray(utcTime), - ...convertDecimalValueTo4ByteHexArray(localTime), - ], + payload: [...convertDecimalValueTo4ByteHexArray(utcTime), ...convertDecimalValueTo4ByteHexArray(localTime)], }; await endpoint.command('manuSpecificTuya', 'mcuSyncTime', payload, {}); } catch (error) { @@ -214,16 +237,13 @@ export async function onEventSetLocalTime(type: OnEventType, data: KeyValue, dev globalStore.putValue(device, 'nextLocalTimeUpdate', new Date().getTime() + 3600 * 1000); try { - const utcTime = Math.round(((new Date()).getTime()) / 1000); - const localTime = utcTime - (new Date()).getTimezoneOffset() * 60; + const utcTime = Math.round(new Date().getTime() / 1000); + const localTime = utcTime - new Date().getTimezoneOffset() * 60; const endpoint = device.getEndpoint(1); const payload = { payloadSize: 8, - payload: [ - ...convertDecimalValueTo4ByteHexArray(utcTime), - ...convertDecimalValueTo4ByteHexArray(localTime), - ], + payload: [...convertDecimalValueTo4ByteHexArray(utcTime), ...convertDecimalValueTo4ByteHexArray(localTime)], }; await endpoint.command('manuSpecificTuya', 'mcuSyncTime', payload, {}); } catch (error) { @@ -235,10 +255,10 @@ export async function onEventSetLocalTime(type: OnEventType, data: KeyValue, dev } // Return `seq` - transaction ID for handling concrete response -async function sendDataPoints(entity: Zh.Endpoint | Zh.Group, dpValues: Tuya.DpValue[], cmd='dataRequest', seq?:number) { +async function sendDataPoints(entity: Zh.Endpoint | Zh.Group, dpValues: Tuya.DpValue[], cmd = 'dataRequest', seq?: number) { if (seq === undefined) { seq = globalStore.getValue(entity, 'sequence', 0); - globalStore.putValue(entity, 'sequence', (seq + 1) % 0xFFFF); + globalStore.putValue(entity, 'sequence', (seq + 1) % 0xffff); } await entity.command('manuSpecificTuya', cmd, {seq, dpValues}, {disableDefaultResponse: true}); @@ -294,70 +314,98 @@ export async function sendDataPointStringBuffer(entity: Zh.Group | Zh.Endpoint, } const tuyaExposes = { - lightType: () => e.enum('light_type', ea.STATE_SET, ['led', 'incandescent', 'halogen']) - .withDescription('Type of light attached to the device'), - lightBrightnessWithMinMax: () => e.light_brightness().withMinBrightness().withMaxBrightness() - .setAccess('state', ea.STATE_SET) - .setAccess('brightness', ea.STATE_SET) - .setAccess('min_brightness', ea.STATE_SET) - .setAccess('max_brightness', ea.STATE_SET), - lightBrightness: () => e.light_brightness() - .setAccess('state', ea.STATE_SET) - .setAccess('brightness', ea.STATE_SET), - countdown: () => e.numeric('countdown', ea.STATE_SET).withValueMin(0).withValueMax(43200).withValueStep(1).withUnit('s') - .withDescription('Countdown to turn device off after a certain time'), + lightType: () => e.enum('light_type', ea.STATE_SET, ['led', 'incandescent', 'halogen']).withDescription('Type of light attached to the device'), + lightBrightnessWithMinMax: () => + e + .light_brightness() + .withMinBrightness() + .withMaxBrightness() + .setAccess('state', ea.STATE_SET) + .setAccess('brightness', ea.STATE_SET) + .setAccess('min_brightness', ea.STATE_SET) + .setAccess('max_brightness', ea.STATE_SET), + lightBrightness: () => e.light_brightness().setAccess('state', ea.STATE_SET).setAccess('brightness', ea.STATE_SET), + countdown: () => + e + .numeric('countdown', ea.STATE_SET) + .withValueMin(0) + .withValueMax(43200) + .withValueStep(1) + .withUnit('s') + .withDescription('Countdown to turn device off after a certain time'), switch: () => e.switch().setAccess('state', ea.STATE_SET), - selfTest: () => e.binary('self_test', ea.STATE_SET, true, false) - .withDescription('Indicates whether the device is being self-tested'), - selfTestResult: () => e.enum('self_test_result', ea.STATE, ['checking', 'success', 'failure', 'others']) - .withDescription('Result of the self-test'), + selfTest: () => e.binary('self_test', ea.STATE_SET, true, false).withDescription('Indicates whether the device is being self-tested'), + selfTestResult: () => + e.enum('self_test_result', ea.STATE, ['checking', 'success', 'failure', 'others']).withDescription('Result of the self-test'), faultAlarm: () => e.binary('fault_alarm', ea.STATE, true, false).withDescription('Indicates whether a fault was detected'), silence: () => e.binary('silence', ea.STATE_SET, true, false).withDescription('Silence the alarm'), - frostProtection: (extraNote='') => e.binary('frost_protection', ea.STATE_SET, 'ON', 'OFF').withDescription( - `When Anti-Freezing function is activated, the temperature in the house is kept at 8 °C.${extraNote}`), + frostProtection: (extraNote = '') => + e + .binary('frost_protection', ea.STATE_SET, 'ON', 'OFF') + .withDescription(`When Anti-Freezing function is activated, the temperature in the house is kept at 8 °C.${extraNote}`), errorStatus: () => e.numeric('error_status', ea.STATE).withDescription('Error status'), - scheduleAllDays: (access: number, format: string) => ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'] - .map((day) => e.text(`schedule_${day}`, access).withDescription(`Schedule for ${day}, format: "${format}"`)), + scheduleAllDays: (access: number, format: string) => + ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'].map((day) => + e.text(`schedule_${day}`, access).withDescription(`Schedule for ${day}, format: "${format}"`), + ), temperatureUnit: () => e.enum('temperature_unit', ea.STATE_SET, ['celsius', 'fahrenheit']).withDescription('Temperature unit'), - temperatureCalibration: () => e.numeric('temperature_calibration', ea.STATE_SET).withValueMin(-2.0).withValueMax(2.0) - .withValueStep(0.1).withUnit('°C').withDescription('Temperature calibration'), - humidityCalibration: () => e.numeric('humidity_calibration', ea.STATE_SET).withValueMin(-30).withValueMax(30) - .withValueStep(1).withUnit('%').withDescription('Humidity calibration'), + temperatureCalibration: () => + e + .numeric('temperature_calibration', ea.STATE_SET) + .withValueMin(-2.0) + .withValueMax(2.0) + .withValueStep(0.1) + .withUnit('°C') + .withDescription('Temperature calibration'), + humidityCalibration: () => + e + .numeric('humidity_calibration', ea.STATE_SET) + .withValueMin(-30) + .withValueMax(30) + .withValueStep(1) + .withUnit('%') + .withDescription('Humidity calibration'), gasValue: () => e.numeric('gas_value', ea.STATE).withDescription('Measured gas concentration'), - energyWithPhase: (phase: string) => e.numeric(`energy_${phase}`, ea.STATE).withUnit('kWh') - .withDescription(`Sum of consumed energy (phase ${phase.toUpperCase()})`), - energyProducedWithPhase: (phase: string) => e.numeric(`energy_produced_${phase}`, ea.STATE).withUnit('kWh') - .withDescription(`Sum of produced energy (phase ${phase.toUpperCase()})`), - energyFlowWithPhase: (phase: string, more: [string]) => e.enum(`energy_flow_${phase}`, ea.STATE, ['consuming', 'producing', ...more]) - .withDescription(`Direction of energy (phase ${phase.toUpperCase()})`), - voltageWithPhase: (phase: string) => e.numeric(`voltage_${phase}`, ea.STATE).withUnit('V') - .withDescription(`Measured electrical potential value (phase ${phase.toUpperCase()})`), - powerWithPhase: (phase: string) => e.numeric(`power_${phase}`, ea.STATE).withUnit('W') - .withDescription(`Instantaneous measured power (phase ${phase.toUpperCase()})`), - currentWithPhase: (phase: string) => e.numeric(`current_${phase}`, ea.STATE).withUnit('A') - .withDescription(`Instantaneous measured electrical current (phase ${phase.toUpperCase()})`), - powerFactorWithPhase: (phase: string) => e.numeric(`power_factor_${phase}`, ea.STATE).withUnit('%') - .withDescription(`Instantaneous measured power factor (phase ${phase.toUpperCase()})`), + energyWithPhase: (phase: string) => + e.numeric(`energy_${phase}`, ea.STATE).withUnit('kWh').withDescription(`Sum of consumed energy (phase ${phase.toUpperCase()})`), + energyProducedWithPhase: (phase: string) => + e.numeric(`energy_produced_${phase}`, ea.STATE).withUnit('kWh').withDescription(`Sum of produced energy (phase ${phase.toUpperCase()})`), + energyFlowWithPhase: (phase: string, more: [string]) => + e + .enum(`energy_flow_${phase}`, ea.STATE, ['consuming', 'producing', ...more]) + .withDescription(`Direction of energy (phase ${phase.toUpperCase()})`), + voltageWithPhase: (phase: string) => + e.numeric(`voltage_${phase}`, ea.STATE).withUnit('V').withDescription(`Measured electrical potential value (phase ${phase.toUpperCase()})`), + powerWithPhase: (phase: string) => + e.numeric(`power_${phase}`, ea.STATE).withUnit('W').withDescription(`Instantaneous measured power (phase ${phase.toUpperCase()})`), + currentWithPhase: (phase: string) => + e + .numeric(`current_${phase}`, ea.STATE) + .withUnit('A') + .withDescription(`Instantaneous measured electrical current (phase ${phase.toUpperCase()})`), + powerFactorWithPhase: (phase: string) => + e + .numeric(`power_factor_${phase}`, ea.STATE) + .withUnit('%') + .withDescription(`Instantaneous measured power factor (phase ${phase.toUpperCase()})`), switchType: () => e.enum('switch_type', ea.ALL, ['toggle', 'state', 'momentary']).withDescription('Type of the switch'), - backlightModeLowMediumHigh: () => e.enum('backlight_mode', ea.ALL, ['low', 'medium', 'high']) - .withDescription('Intensity of the backlight'), - backlightModeOffNormalInverted: () => e.enum('backlight_mode', ea.ALL, ['off', 'normal', 'inverted']) - .withDescription('Mode of the backlight'), + backlightModeLowMediumHigh: () => e.enum('backlight_mode', ea.ALL, ['low', 'medium', 'high']).withDescription('Intensity of the backlight'), + backlightModeOffNormalInverted: () => e.enum('backlight_mode', ea.ALL, ['off', 'normal', 'inverted']).withDescription('Mode of the backlight'), backlightModeOffOn: () => e.binary('backlight_mode', ea.ALL, 'ON', 'OFF').withDescription(`Mode of the backlight`), indicatorMode: () => e.enum('indicator_mode', ea.ALL, ['off', 'off/on', 'on/off', 'on']).withDescription('LED indicator mode'), - indicatorModeNoneRelayPos: () => e.enum('indicator_mode', ea.ALL, ['none', 'relay', 'pos']) - .withDescription('Mode of the indicator light'), - powerOutageMemory: () => e.enum('power_outage_memory', ea.ALL, ['on', 'off', 'restore']) - .withDescription('Recover state after power outage'), + indicatorModeNoneRelayPos: () => e.enum('indicator_mode', ea.ALL, ['none', 'relay', 'pos']).withDescription('Mode of the indicator light'), + powerOutageMemory: () => e.enum('power_outage_memory', ea.ALL, ['on', 'off', 'restore']).withDescription('Recover state after power outage'), batteryState: () => e.enum('battery_state', ea.STATE, ['low', 'medium', 'high']).withDescription('State of the battery'), - doNotDisturb: () => e.binary('do_not_disturb', ea.STATE_SET, true, false) - .withDescription('Do not disturb mode, when enabled this function will keep the light OFF after a power outage'), - colorPowerOnBehavior: () => e.enum('color_power_on_behavior', ea.STATE_SET, ['initial', 'previous', 'customized']) - .withDescription('Power on behavior state'), - switchMode: () => e.enum('switch_mode', ea.STATE_SET, ['switch', 'scene']) - .withDescription('Sets the mode of the switch to act as a switch or as a scene'), - lightMode: () => e.enum('light_mode', ea.STATE_SET, ['normal', 'on', 'off', 'flash']) - .withDescription(`'Sets the indicator mode of l1. + doNotDisturb: () => + e + .binary('do_not_disturb', ea.STATE_SET, true, false) + .withDescription('Do not disturb mode, when enabled this function will keep the light OFF after a power outage'), + colorPowerOnBehavior: () => + e.enum('color_power_on_behavior', ea.STATE_SET, ['initial', 'previous', 'customized']).withDescription('Power on behavior state'), + switchMode: () => + e.enum('switch_mode', ea.STATE_SET, ['switch', 'scene']).withDescription('Sets the mode of the switch to act as a switch or as a scene'), + lightMode: () => + e.enum('light_mode', ea.STATE_SET, ['normal', 'on', 'off', 'flash']).withDescription(`'Sets the indicator mode of l1. Normal: Orange while off and white while on. On: Always white. Off: Always orange. Flash: Flashes white when triggered. @@ -430,10 +478,9 @@ export class Bitmap extends Base { } } -type LookupMap = {[s: (string)]: number | boolean | Enum | string}; +type LookupMap = {[s: string]: number | boolean | Enum | string}; export const valueConverterBasic = { - lookup: (map: LookupMap | ((options: KeyValue, device: Zh.Device) => LookupMap), - fallbackValue?: number | boolean | KeyValue | string | null) => { + lookup: (map: LookupMap | ((options: KeyValue, device: Zh.Device) => LookupMap), fallbackValue?: number | boolean | KeyValue | string | null) => { return { to: (v: string, meta: Tz.Meta) => utils.getFromLookup(v, typeof map === 'function' ? map(meta.options, meta.device) : map), from: (v: number, _meta: Fz.Meta, options: KeyValue) => { @@ -454,7 +501,7 @@ export const valueConverterBasic = { }; }, raw: () => { - return {to: (v: string|number|boolean) => v, from: (v: string|number|boolean) => v}; + return {to: (v: string | number | boolean) => v, from: (v: string | number | boolean) => v}; }, divideBy: (value: number) => { return {to: (v: number) => v * value, from: (v: number) => v / value}; @@ -465,91 +512,6 @@ export const valueConverterBasic = { trueFalse: (valueTrue: number | Enum) => { return {from: (v: number) => v === valueTrue.valueOf()}; }, - // color1000: () => { - // return { - // // eslint-disable-next-line - // to: (value: any, meta?: Tz.Meta) => { - // const make4sizedString = (v: string) => { - // if (v.length >= 4) { - // return v; - // } else if (v.length === 3) { - // return '0' + v; - // } else if (v.length === 2) { - // return '00' + v; - // } else if (v.length === 1) { - // return '000' + v; - // } else { - // return '0000'; - // } - // }; - - // const fillInHSB = (h: number, s: number, b: number, state: KeyValueAny) => { - // // Define default values. Device expects leading zero in string. - // const hsb = { - // h: '0168', // 360 - // s: '03e8', // 1000 - // b: '03e8', // 1000 - // }; - - // if (h) { - // // The device expects 0-359 - // if (h >= 360) { - // h = 359; - // } - // hsb.h = make4sizedString(h.toString(16)); - // } else if (state.color && state.color.hue) { - // hsb.h = make4sizedString(state.color.hue.toString(16)); - // } - - // // Device expects 0-1000, saturation normally is 0-100 so we expect that from the user - // // The device expects a round number, otherwise everything breaks - // if (s) { - // hsb.s = make4sizedString(utils.mapNumberRange(s, 0, 100, 0, 1000).toString(16)); - // } else if (state.color && state.color.saturation) { - // hsb.s = make4sizedString(utils.mapNumberRange(state.color.saturation, 0, 100, 0, 1000).toString(16)); - // } - - // // Scale 0-255 to 0-1000 what the device expects. - // if (b != null) { - // hsb.b = make4sizedString(utils.mapNumberRange(b, 0, 255, 0, 1000).toString(16)); - // } else if (state.brightness != null) { - // hsb.b = make4sizedString(utils.mapNumberRange(state.brightness, 0, 255, 0, 1000).toString(16)); - // } - // return hsb; - // }; - // const newColor = Color.fromConverterArg(value); - // let hsv; - // if (newColor.isRGB()) { - // hsv = newColor.rgb.toHSV(); - // } else { - // if (newColor.isHSV()) { - // hsv = newColor.hsv; - // } - // } - // const hsb = fillInHSB( - // utils.precisionRound(hsv.hue, 0) || null, - // utils.precisionRound(hsv.saturation, 0) || null, - // utils.precisionRound(hsv.brightness, 0) || null, - // meta.state, - // ); - // const data: string = hsb.h + hsb.s + hsb.b; - - // return data; - // }, - // // eslint-disable-next-line - // from: (value: any) => { - // const result: KeyValueAny = {}; - // const h = parseInt(value.substring(0, 4), 16); - // const s = parseInt(value.substring(4, 8), 16); - // const b = parseInt(value.substring(8, 12), 16); - // result.color_mode = 'hs'; - // result.color = {hue: h, saturation: utils.mapNumberRange(s, 0, 1000, 0, 100)}; - // result.brightness = utils.mapNumberRange(b, 0, 1000, 0, 255); - - // return result; - // }, - // }; - // }, }; export const valueConverter = { @@ -561,31 +523,31 @@ export const valueConverter = { }, trueFalseEnum0: valueConverterBasic.trueFalse(new Enum(0)), trueFalseEnum1: valueConverterBasic.trueFalse(new Enum(1)), - onOff: valueConverterBasic.lookup({'ON': true, 'OFF': false}), - powerOnBehavior: valueConverterBasic.lookup({'off': 0, 'on': 1, 'previous': 2}), - powerOnBehaviorEnum: valueConverterBasic.lookup({'off': new Enum(0), 'on': new Enum(1), 'previous': new Enum(2)}), - switchType: valueConverterBasic.lookup({'momentary': new Enum(0), 'toggle': new Enum(1), 'state': new Enum(2)}), - switchType2: valueConverterBasic.lookup({'toggle': new Enum(0), 'state': new Enum(1), 'momentary': new Enum(2)}), - backlightModeOffNormalInverted: valueConverterBasic.lookup({'off': new Enum(0), 'normal': new Enum(1), 'inverted': new Enum(2)}), - backlightModeOffLowMediumHigh: valueConverterBasic.lookup({'off': new Enum(0), 'low': new Enum(1), 'medium': new Enum(2), 'high': new Enum(3)}), - lightType: valueConverterBasic.lookup({'led': 0, 'incandescent': 1, 'halogen': 2}), + onOff: valueConverterBasic.lookup({ON: true, OFF: false}), + powerOnBehavior: valueConverterBasic.lookup({off: 0, on: 1, previous: 2}), + powerOnBehaviorEnum: valueConverterBasic.lookup({off: new Enum(0), on: new Enum(1), previous: new Enum(2)}), + switchType: valueConverterBasic.lookup({momentary: new Enum(0), toggle: new Enum(1), state: new Enum(2)}), + switchType2: valueConverterBasic.lookup({toggle: new Enum(0), state: new Enum(1), momentary: new Enum(2)}), + backlightModeOffNormalInverted: valueConverterBasic.lookup({off: new Enum(0), normal: new Enum(1), inverted: new Enum(2)}), + backlightModeOffLowMediumHigh: valueConverterBasic.lookup({off: new Enum(0), low: new Enum(1), medium: new Enum(2), high: new Enum(3)}), + lightType: valueConverterBasic.lookup({led: 0, incandescent: 1, halogen: 2}), countdown: valueConverterBasic.raw(), scale0_254to0_1000: valueConverterBasic.scale(0, 254, 0, 1000), scale0_1to0_1000: valueConverterBasic.scale(0, 1, 0, 1000), divideBy100: valueConverterBasic.divideBy(100), - temperatureUnit: valueConverterBasic.lookup({'celsius': 0, 'fahrenheit': 1}), - temperatureUnitEnum: valueConverterBasic.lookup({'celsius': new Enum(0), 'fahrenheit': new Enum(1)}), - batteryState: valueConverterBasic.lookup({'low': 0, 'medium': 1, 'high': 2}), + temperatureUnit: valueConverterBasic.lookup({celsius: 0, fahrenheit: 1}), + temperatureUnitEnum: valueConverterBasic.lookup({celsius: new Enum(0), fahrenheit: new Enum(1)}), + batteryState: valueConverterBasic.lookup({low: 0, medium: 1, high: 2}), divideBy10: valueConverterBasic.divideBy(10), divideBy1000: valueConverterBasic.divideBy(1000), divideBy10FromOnly: valueConverterBasic.divideByFromOnly(10), - switchMode: valueConverterBasic.lookup({'switch': new Enum(0), 'scene': new Enum(1)}), - lightMode: valueConverterBasic.lookup({'normal': new Enum(0), 'on': new Enum(1), 'off': new Enum(2), 'flash': new Enum(3)}), + switchMode: valueConverterBasic.lookup({switch: new Enum(0), scene: new Enum(1)}), + lightMode: valueConverterBasic.lookup({normal: new Enum(0), on: new Enum(1), off: new Enum(2), flash: new Enum(3)}), raw: valueConverterBasic.raw(), - workingDay: valueConverterBasic.lookup({'disabled': new Enum(0), '6-1': new Enum(1), '5-2': new Enum(2), '7': new Enum(3)}), + workingDay: valueConverterBasic.lookup({disabled: new Enum(0), '6-1': new Enum(1), '5-2': new Enum(2), '7': new Enum(3)}), localTemperatureCalibration: { - from: (value: number) => value > 4000 ? value - 4096 : value, - to: (value: number) => value < 0 ? 4096 + value : value, + from: (value: number) => (value > 4000 ? value - 4096 : value), + to: (value: number) => (value < 0 ? 4096 + value : value), }, setLimit: { to: (v: number) => { @@ -610,7 +572,7 @@ export const valueConverter = { return options.invert_cover ? v : 100 - v; }, }, - tubularMotorDirection: valueConverterBasic.lookup({'normal': new Enum(0), 'reversed': new Enum(1)}), + tubularMotorDirection: valueConverterBasic.lookup({normal: new Enum(0), reversed: new Enum(1)}), plus1: { from: (v: number) => v + 1, to: (v: number) => v - 1, @@ -625,13 +587,13 @@ export const valueConverter = { phaseVariant1: { from: (v: string) => { const buffer = Buffer.from(v, 'base64'); - return {voltage: (buffer[14] | buffer[13] << 8) / 10, current: (buffer[12] | buffer[11] << 8) / 1000}; + return {voltage: (buffer[14] | (buffer[13] << 8)) / 10, current: (buffer[12] | (buffer[11] << 8)) / 1000}; }, }, phaseVariant2: { from: (v: string) => { const buf = Buffer.from(v, 'base64'); - return {voltage: (buf[1] | buf[0] << 8) / 10, current: (buf[4] | buf[3] << 8) / 1000, power: (buf[7] | buf[6] << 8)}; + return {voltage: (buf[1] | (buf[0] << 8)) / 10, current: (buf[4] | (buf[3] << 8)) / 1000, power: buf[7] | (buf[6] << 8)}; }, }, phaseVariant2WithPhase: (phase: string) => { @@ -639,9 +601,10 @@ export const valueConverter = { from: (v: string) => { const buf = Buffer.from(v, 'base64'); return { - [`voltage_${phase}`]: (buf[1] | buf[0] << 8) / 10, - [`current_${phase}`]: (buf[4] | buf[3] << 8) / 1000, - [`power_${phase}`]: (buf[7] | buf[6] << 8)}; + [`voltage_${phase}`]: (buf[1] | (buf[0] << 8)) / 10, + [`current_${phase}`]: (buf[4] | (buf[3] << 8)) / 1000, + [`power_${phase}`]: buf[7] | (buf[6] << 8), + }; }, }; }, @@ -651,7 +614,7 @@ export const valueConverter = { return { voltage: ((buf[0] << 8) | buf[1]) / 10, current: ((buf[2] << 16) | (buf[3] << 8) | buf[4]) / 1000, - power: ((buf[5] << 16) | (buf[6] << 8) | buf[7]), + power: (buf[5] << 16) | (buf[6] << 8) | buf[7], }; }, }, @@ -663,15 +626,15 @@ export const valueConverter = { return { threshold_1_protection: protectionLookup[buffer[1]], threshold_1: stateLookup[buffer[0]], - threshold_1_value: (buffer[3] | buffer[2] << 8), + threshold_1_value: buffer[3] | (buffer[2] << 8), threshold_2_protection: protectionLookup[buffer[5]], threshold_2: stateLookup[buffer[4]], - threshold_2_value: (buffer[7] | buffer[6] << 8), + threshold_2_value: buffer[7] | (buffer[6] << 8), }; }, }, - selfTestResult: valueConverterBasic.lookup({'checking': 0, 'success': 1, 'failure': 2, 'others': 3}), - lockUnlock: valueConverterBasic.lookup({'LOCK': true, 'UNLOCK': false}), + selfTestResult: valueConverterBasic.lookup({checking: 0, success: 1, failure: 2, others: 3}), + lockUnlock: valueConverterBasic.lookup({LOCK: true, UNLOCK: false}), localTempCalibration1: { from: (v: number) => { if (v > 55) v -= 0x100000000; @@ -692,7 +655,7 @@ export const valueConverter = { }, localTempCalibration3: { from: (v: number) => { - if (v > 0x7FFFFFFF) v -= 0x100000000; + if (v > 0x7fffffff) v -= 0x100000000; return v / 10; }, to: (v: number) => { @@ -704,12 +667,18 @@ export const valueConverter = { thermostatHolidayStartStop: { from: (v: string) => { const start = { - year: v.slice(0, 4), month: v.slice(4, 6), day: v.slice(6, 8), - hours: v.slice(8, 10), minutes: v.slice(10, 12), + year: v.slice(0, 4), + month: v.slice(4, 6), + day: v.slice(6, 8), + hours: v.slice(8, 10), + minutes: v.slice(10, 12), }; const end = { - year: v.slice(12, 16), month: v.slice(16, 18), day: v.slice(18, 20), - hours: v.slice(20, 22), minutes: v.slice(22, 24), + year: v.slice(12, 16), + month: v.slice(16, 18), + day: v.slice(18, 20), + hours: v.slice(20, 22), + minutes: v.slice(22, 24), }; const startStr = `${start.year}/${start.month}/${start.day} ${start.hours}:${start.minutes}`; const endStr = `${end.year}/${end.month}/${end.day} ${end.hours}:${end.minutes}`; @@ -748,8 +717,13 @@ export const valueConverter = { }, to: (v: KeyValue, meta: Tz.Meta) => { const dayByte: KeyValue = { - monday: 1, tuesday: 2, wednesday: 4, thursday: 8, - friday: 16, saturday: 32, sunday: 64, + monday: 1, + tuesday: 2, + wednesday: 4, + thursday: 8, + friday: 16, + saturday: 32, + sunday: 64, }; const weekDay = v.week_day; utils.assertString(weekDay, 'week_day'); @@ -761,21 +735,21 @@ export const valueConverter = { const payload = []; switch (weekScheduleType) { - case 'mon_sun': - payload.push(127); - break; - case 'mon_fri+sat+sun': - if (['saturday', 'sunday'].indexOf(weekDay) === -1) { - payload.push(31); + case 'mon_sun': + payload.push(127); break; - } - payload.push(dayByte[weekDay]); - break; - case 'separate': - payload.push(dayByte[weekDay]); - break; - default: - throw new Error('Invalid "working_day" property, need to set it before'); + case 'mon_fri+sat+sun': + if (['saturday', 'sunday'].indexOf(weekDay) === -1) { + payload.push(31); + break; + } + payload.push(dayByte[weekDay]); + break; + case 'separate': + payload.push(dayByte[weekDay]); + break; + default: + throw new Error('Invalid "working_day" property, need to set it before'); } // day split to 10 min segments = total 144 segments @@ -818,10 +792,12 @@ export const valueConverter = { const schedule = []; for (let index = 1; index < 17; index = index + 4) { schedule.push( - String(parseInt(v[index+0])).padStart(2, '0') + ':' + - String(parseInt(v[index+1])).padStart(2, '0') + '/' + - // @ts-ignore - (parseFloat((v[index+2] << 8) + v[index+3]) / 10.0).toFixed(1), + String(parseInt(v[index + 0])).padStart(2, '0') + + ':' + + String(parseInt(v[index + 1])).padStart(2, '0') + + '/' + + // @ts-ignore + (parseFloat((v[index + 2] << 8) + v[index + 3]) / 10.0).toFixed(1), ); } return schedule.join(' '); @@ -844,12 +820,7 @@ export const valueConverter = { if (hour < 0 || hour > 24 || min < 0 || min > 60 || temperature < 50 || temperature > 300) { throw new Error('Invalid hour, minute or temperature of: ' + transition); } - payload.push( - hour, - min, - (temperature & 0xff00) >> 8, - temperature & 0xff, - ); + payload.push(hour, min, (temperature & 0xff00) >> 8, temperature & 0xff); } return payload; }, @@ -891,8 +862,8 @@ export const valueConverter = { return {preset: presetLookup[v], system_mode: systemModeLookup[v]}; }, to: (v: string) => { - const presetLookup: KeyValueStringEnum = {'auto': new Enum(0), 'manual': new Enum(1), 'off': new Enum(2), 'on': new Enum(3)}; - const systemModeLookup: KeyValueStringEnum = {'auto': new Enum(1), 'off': new Enum(2), 'heat': new Enum(3)}; + const presetLookup: KeyValueStringEnum = {auto: new Enum(0), manual: new Enum(1), off: new Enum(2), on: new Enum(3)}; + const systemModeLookup: KeyValueStringEnum = {auto: new Enum(1), off: new Enum(2), heat: new Enum(3)}; const lookup: KeyValueStringEnum = toKey === 'preset' ? presetLookup : systemModeLookup; return utils.getFromLookup(v, lookup); }, @@ -902,13 +873,13 @@ export const valueConverter = { from: (value: number[], meta: Fz.Meta, options: KeyValue) => { const programmingMode = []; - for (let i=0; i<8; i++) { - const start=i*4; + for (let i = 0; i < 8; i++) { + const start = i * 4; - const time = value[start].toString().padStart(2, '0')+':'+ value[start+1].toString().padStart(2, '0'); - const temp = (value[start+2]*256+value[start+3])/10; - const tempStr = temp.toFixed(1)+'°C'; - programmingMode.push(time+'/'+tempStr); + const time = value[start].toString().padStart(2, '0') + ':' + value[start + 1].toString().padStart(2, '0'); + const temp = (value[start + 2] * 256 + value[start + 3]) / 10; + const tempStr = temp.toFixed(1) + '°C'; + programmingMode.push(time + '/' + tempStr); } return { schedule_weekday: programmingMode.slice(0, 6).join(' '), @@ -917,7 +888,7 @@ export const valueConverter = { }, to: async (v: string, meta: Tz.Meta) => { const dpId = 109; - const payload:number[] = []; + const payload: number[] = []; let weekdayFormat: string; let holidayFormat: string; @@ -933,7 +904,7 @@ export const valueConverter = { const items = input.trim().split(/\s+/); if (items.length != number) { - throw new Error('Wrong number of items for '+ key +' :' + items.length); + throw new Error('Wrong number of items for ' + key + ' :' + items.length); } else { for (let i = 0; i < number; i++) { const timeTemperature = items[i].split('/'); @@ -945,21 +916,28 @@ export const valueConverter = { const minute = parseInt(hourMinute[1]); const temperature = parseFloat(timeTemperature[1]); - if (!utils.isNumber(hour) || !utils.isNumber(temperature) || !utils.isNumber(minute) || - hour < 0 || hour >= 24 || - minute < 0 || minute >= 60 || - temperature < 5 || temperature >= 35) { - throw new Error('Invalid hour, minute or temperature (5= 24 || + minute < 0 || + minute >= 60 || + temperature < 5 || + temperature >= 35 + ) { + throw new Error( + 'Invalid hour, minute or temperature (5> 8) & 0xFF, - temperature10 & 0xFF, - ); + const temperature10 = Math.round(temperature * 10); + + payload.push(hour, minute, (temperature10 >> 8) & 0xff, temperature10 & 0xff); } } return; @@ -1008,12 +986,12 @@ export const valueConverter = { }, }, inverse: {to: (v: boolean) => !v, from: (v: boolean) => !v}, - onOffNotStrict: {from: (v: string) => v ? 'ON' : 'OFF', to: (v: string) => v === 'ON'}, + onOffNotStrict: {from: (v: string) => (v ? 'ON' : 'OFF'), to: (v: string) => v === 'ON'}, errorOrBatteryLow: { from: (v: number) => { - if (v === 0) return {'battery_low': false}; - if (v === 1) return {'battery_low': true}; - return {'error': v}; + if (v === 0) return {battery_low: false}; + if (v === 1) return {battery_low: true}; + return {error: v}; }, }, }; @@ -1023,8 +1001,10 @@ const tuyaTz = { key: ['power_on_behavior', 'power_outage_memory'], convertSet: async (entity, key, value, meta) => { // Deprecated: remove power_outage_memory - const moesStartUpOnOff = utils.getFromLookup(value, key === 'power_on_behavior' ? - {'off': 0, 'on': 1, 'previous': 2} : {'off': 0, 'on': 1, 'restore': 2}); + const moesStartUpOnOff = utils.getFromLookup( + value, + key === 'power_on_behavior' ? {off: 0, on: 1, previous: 2} : {off: 0, on: 1, restore: 2}, + ); await entity.write('genOnOff', {moesStartUpOnOff}); return {state: {[key]: value}}; }, @@ -1035,7 +1015,7 @@ const tuyaTz = { power_on_behavior_2: { key: ['power_on_behavior'], convertSet: async (entity, key, value, meta) => { - const powerOnBehavior = utils.getFromLookup(value, {'off': 0, 'on': 1, 'previous': 2}); + const powerOnBehavior = utils.getFromLookup(value, {off: 0, on: 1, previous: 2}); await entity.write('manuSpecificTuya_3', {powerOnBehavior}); return {state: {[key]: value}}; }, @@ -1046,7 +1026,7 @@ const tuyaTz = { switch_type: { key: ['switch_type'], convertSet: async (entity, key, value, meta) => { - const switchType = utils.getFromLookup(value, {'toggle': 0, 'state': 1, 'momentary': 2}); + const switchType = utils.getFromLookup(value, {toggle: 0, state: 1, momentary: 2}); await entity.write('manuSpecificTuya_3', {switchType}, {disableDefaultResponse: true}); return {state: {[key]: value}}; }, @@ -1057,9 +1037,10 @@ const tuyaTz = { backlight_indicator_mode_1: { key: ['backlight_mode', 'indicator_mode'], convertSet: async (entity, key, value, meta) => { - const tuyaBacklightMode = utils.getFromLookup(value, key === 'backlight_mode' ? - {'low': 0, 'medium': 1, 'high': 2, 'off': 0, 'normal': 1, 'inverted': 2} : - {'off': 0, 'off/on': 1, 'on/off': 2, 'on': 3}); + const tuyaBacklightMode = utils.getFromLookup( + value, + key === 'backlight_mode' ? {low: 0, medium: 1, high: 2, off: 0, normal: 1, inverted: 2} : {off: 0, 'off/on': 1, 'on/off': 2, on: 3}, + ); await entity.write('genOnOff', {tuyaBacklightMode}); return {state: {[key]: value}}; }, @@ -1070,7 +1051,7 @@ const tuyaTz = { backlight_indicator_mode_2: { key: ['backlight_mode'], convertSet: async (entity, key, value, meta) => { - const tuyaBacklightSwitch = utils.getFromLookup(value, {'off': 0, 'on': 1}); + const tuyaBacklightSwitch = utils.getFromLookup(value, {off: 0, on: 1}); await entity.write('genOnOff', {tuyaBacklightSwitch}); return {state: {[key]: value}}; }, @@ -1081,7 +1062,7 @@ const tuyaTz = { child_lock: { key: ['child_lock'], convertSet: async (entity, key, value, meta) => { - const v = utils.getFromLookup(value, {'lock': true, 'unlock': false}); + const v = utils.getFromLookup(value, {lock: true, unlock: false}); await entity.write('genOnOff', {0x8000: {value: v, type: 0x10}}); }, } satisfies Tz.Converter, @@ -1117,43 +1098,201 @@ const tuyaTz = { color_power_on_behavior: { key: ['color_power_on_behavior'], convertSet: async (entity, key, value, meta) => { - const v = utils.getFromLookup(value, {'initial': 0, 'previous': 1, 'customized': 2}); - await entity.command('lightingColorCtrl', 'tuyaOnStartUp', {mode: v*256, data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}); + const v = utils.getFromLookup(value, {initial: 0, previous: 1, customized: 2}); + await entity.command('lightingColorCtrl', 'tuyaOnStartUp', {mode: v * 256, data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}); return {state: {color_power_on_behavior: value}}; }, } satisfies Tz.Converter, datapoints: { key: [ - 'temperature_unit', 'temperature_calibration', 'humidity_calibration', 'alarm_switch', 'tamper_alarm_switch', - 'state', 'brightness', 'min_brightness', 'max_brightness', 'power_on_behavior', 'position', 'alarm_melody', 'alarm_mode', - 'countdown', 'light_type', 'silence', 'self_test', 'child_lock', 'open_window', 'open_window_temperature', 'frost_protection', - 'system_mode', 'heating_stop', 'current_heating_setpoint', 'local_temperature_calibration', 'preset', 'boost_timeset_countdown', - 'holiday_start_stop', 'holiday_temperature', 'comfort_temperature', 'eco_temperature', 'working_day', - 'week_schedule_programming', 'online', 'holiday_mode_date', 'schedule', 'schedule_monday', 'schedule_tuesday', - 'schedule_wednesday', 'schedule_thursday', 'schedule_friday', 'schedule_saturday', 'schedule_sunday', 'clear_fault', - 'scale_protection', 'error', 'radar_scene', 'radar_sensitivity', 'tumble_alarm_time', 'tumble_switch', 'fall_sensitivity', - 'min_temperature', 'max_temperature', 'window_detection', 'boost_heating', 'alarm_ringtone', 'alarm_time', 'fan_speed', - 'reverse_direction', 'border', 'click_control', 'motor_direction', 'opening_mode', 'factory_reset', 'set_upper_limit', 'set_bottom_limit', - 'motor_speed', 'timer', 'reset_frost_lock', 'schedule_periodic', 'schedule_weekday', 'schedule_holiday', 'backlight_mode', 'calibration', - 'motor_steering', 'mode', 'lower', 'upper', 'delay', 'reverse', 'touch', 'program', 'light_mode', 'switch_mode', - ...[1, 2, 3, 4, 5, 6].map((no) => `schedule_slot_${no}`), 'minimum_range', 'maximum_range', 'detection_delay', 'fading_time', - 'radar_sensitivity', 'entry_sensitivity', 'illuminance_threshold', 'detection_range', 'shield_range', 'entry_distance_indentation', - 'entry_filter_time', 'departure_delay', 'block_time', 'status_indication', 'breaker_mode', 'breaker_status', - 'alarm', 'alarm_time', 'alarm_volume', 'type', 'volume', 'ringtone', 'duration', 'medium_motion_detection_distance', - 'large_motion_detection_distance', 'large_motion_detection_sensitivity', 'small_motion_detection_distance', - 'small_motion_detection_sensitivity', 'static_detection_distance', 'static_detection_sensitivity', 'keep_time', 'indicator', - 'motion_sensitivity', 'detection_distance_max', 'detection_distance_min', 'presence_sensitivity', 'sensitivity', 'illuminance_interval', - 'medium_motion_detection_sensitivity', 'small_detection_distance', 'small_detection_sensitivity', 'fan_mode', 'deadzone_temperature', - 'eco_mode', 'max_temperature_limit', 'min_temperature_limit', 'manual_mode', - 'medium_motion_detection_sensitivity', 'small_detection_distance', 'small_detection_sensitivity', 'switch_type', - 'ph_max', 'ph_min', 'ec_max', 'ec_min', 'orp_max', 'orp_min', 'free_chlorine_max', 'free_chlorine_min', 'target_distance', - 'illuminance_treshold_max', 'illuminance_treshold_min', 'presence_illuminance_switch', 'light_switch', 'light_linkage', - 'indicator_light', 'find_switch', 'detection_method', 'sensor', 'hysteresis', 'max_temperature_protection', 'display_brightness', - 'screen_orientation', 'regulator_period', 'regulator_set_point', 'upper_stroke_limit', 'middle_stroke_limit', 'lower_stroke_limit', - 'buzzer_feedback', 'rf_pairing', 'max_temperature_alarm', 'min_temperature_alarm', 'max_humidity_alarm', 'min_humidity_alarm', - 'temperature_periodic_report', 'humidity_periodic_report', 'temperature_sensitivity', 'humidity_sensitivity', 'temperature_alarm', - 'humidity_alarm', 'move_sensitivity', 'radar_range', 'presence_timeout', 'update_frequency', 'remote_pair', 'motor_working_mode', - 'restart_mode', 'rf_remote_control', 'motion_detection_sensitivity', 'motion_detection_mode', 'vacation', 'keysound', 'handlesound', + 'temperature_unit', + 'temperature_calibration', + 'humidity_calibration', + 'alarm_switch', + 'tamper_alarm_switch', + 'state', + 'brightness', + 'min_brightness', + 'max_brightness', + 'power_on_behavior', + 'position', + 'alarm_melody', + 'alarm_mode', + 'countdown', + 'light_type', + 'silence', + 'self_test', + 'child_lock', + 'open_window', + 'open_window_temperature', + 'frost_protection', + 'system_mode', + 'heating_stop', + 'current_heating_setpoint', + 'local_temperature_calibration', + 'preset', + 'boost_timeset_countdown', + 'holiday_start_stop', + 'holiday_temperature', + 'comfort_temperature', + 'eco_temperature', + 'working_day', + 'week_schedule_programming', + 'online', + 'holiday_mode_date', + 'schedule', + 'schedule_monday', + 'schedule_tuesday', + 'schedule_wednesday', + 'schedule_thursday', + 'schedule_friday', + 'schedule_saturday', + 'schedule_sunday', + 'clear_fault', + 'scale_protection', + 'error', + 'radar_scene', + 'radar_sensitivity', + 'tumble_alarm_time', + 'tumble_switch', + 'fall_sensitivity', + 'min_temperature', + 'max_temperature', + 'window_detection', + 'boost_heating', + 'alarm_ringtone', + 'alarm_time', + 'fan_speed', + 'reverse_direction', + 'border', + 'click_control', + 'motor_direction', + 'opening_mode', + 'factory_reset', + 'set_upper_limit', + 'set_bottom_limit', + 'motor_speed', + 'timer', + 'reset_frost_lock', + 'schedule_periodic', + 'schedule_weekday', + 'schedule_holiday', + 'backlight_mode', + 'calibration', + 'motor_steering', + 'mode', + 'lower', + 'upper', + 'delay', + 'reverse', + 'touch', + 'program', + 'light_mode', + 'switch_mode', + ...[1, 2, 3, 4, 5, 6].map((no) => `schedule_slot_${no}`), + 'minimum_range', + 'maximum_range', + 'detection_delay', + 'fading_time', + 'radar_sensitivity', + 'entry_sensitivity', + 'illuminance_threshold', + 'detection_range', + 'shield_range', + 'entry_distance_indentation', + 'entry_filter_time', + 'departure_delay', + 'block_time', + 'status_indication', + 'breaker_mode', + 'breaker_status', + 'alarm', + 'alarm_time', + 'alarm_volume', + 'type', + 'volume', + 'ringtone', + 'duration', + 'medium_motion_detection_distance', + 'large_motion_detection_distance', + 'large_motion_detection_sensitivity', + 'small_motion_detection_distance', + 'small_motion_detection_sensitivity', + 'static_detection_distance', + 'static_detection_sensitivity', + 'keep_time', + 'indicator', + 'motion_sensitivity', + 'detection_distance_max', + 'detection_distance_min', + 'presence_sensitivity', + 'sensitivity', + 'illuminance_interval', + 'medium_motion_detection_sensitivity', + 'small_detection_distance', + 'small_detection_sensitivity', + 'fan_mode', + 'deadzone_temperature', + 'eco_mode', + 'max_temperature_limit', + 'min_temperature_limit', + 'manual_mode', + 'medium_motion_detection_sensitivity', + 'small_detection_distance', + 'small_detection_sensitivity', + 'switch_type', + 'ph_max', + 'ph_min', + 'ec_max', + 'ec_min', + 'orp_max', + 'orp_min', + 'free_chlorine_max', + 'free_chlorine_min', + 'target_distance', + 'illuminance_treshold_max', + 'illuminance_treshold_min', + 'presence_illuminance_switch', + 'light_switch', + 'light_linkage', + 'indicator_light', + 'find_switch', + 'detection_method', + 'sensor', + 'hysteresis', + 'max_temperature_protection', + 'display_brightness', + 'screen_orientation', + 'regulator_period', + 'regulator_set_point', + 'upper_stroke_limit', + 'middle_stroke_limit', + 'lower_stroke_limit', + 'buzzer_feedback', + 'rf_pairing', + 'max_temperature_alarm', + 'min_temperature_alarm', + 'max_humidity_alarm', + 'min_humidity_alarm', + 'temperature_periodic_report', + 'humidity_periodic_report', + 'temperature_sensitivity', + 'humidity_sensitivity', + 'temperature_alarm', + 'humidity_alarm', + 'move_sensitivity', + 'radar_range', + 'presence_timeout', + 'update_frequency', + 'remote_pair', + 'motor_working_mode', + 'restart_mode', + 'rf_remote_control', + 'motion_detection_sensitivity', + 'motion_detection_mode', + 'vacation', + 'keysound', + 'handlesound', 'calibrate', ], convertSet: async (entity, key, value, meta) => { @@ -1163,8 +1302,8 @@ const tuyaTz = { const datapoints = meta.mapped.meta?.tuyaDatapoints; if (!datapoints) throw new Error('No datapoints map defined'); for (const [attr, value] of Object.entries(meta.message)) { - const convertedKey: string = meta.mapped.meta.multiEndpoint && meta.endpoint_name && !attr.startsWith(`${key}_`) ? - `${attr}_${meta.endpoint_name}` : attr; + const convertedKey: string = + meta.mapped.meta.multiEndpoint && meta.endpoint_name && !attr.startsWith(`${key}_`) ? `${attr}_${meta.endpoint_name}` : attr; const dpEntry = datapoints.find((d) => d[1] === convertedKey); if (!dpEntry?.[1] || !dpEntry?.[2].to) { throw new Error(`No datapoint defined for '${attr}'`); @@ -1213,17 +1352,19 @@ const tuyaTz = { // provide datapoints so there is little reason to provide support. key: ['state', 'countdown'], convertSet: async (entity, key, value, meta) => { - const state = meta.message.hasOwnProperty('state') ? - ( utils.isString(meta.message.state) ? meta.message.state.toLowerCase() : null ) : - undefined; + const state = meta.message.hasOwnProperty('state') + ? utils.isString(meta.message.state) + ? meta.message.state.toLowerCase() + : null + : undefined; const countdown = meta.message.hasOwnProperty('countdown') ? meta.message.countdown : undefined; const result: KeyValue = {}; - if ( countdown !== undefined ) { + if (countdown !== undefined) { // OnTime is a 16bit register and so might very well work up to 0xFFFF seconds but // the Tuya documentation says that the maximum is 43200 (so 12 hours). // @ts-expect-error - if ( !Number.isInteger(countdown) || countdown < 0 || countdown > 12*3600 ) { - throw new Error('countdown must be an integer between 1 and 43200 (12 hours) or 0 to cancel' ); + if (!Number.isInteger(countdown) || countdown < 0 || countdown > 12 * 3600) { + throw new Error('countdown must be an integer between 1 and 43200 (12 hours) or 0 to cancel'); } } // The order of the commands matters because 'on/off/toggle' cancels 'onWithTimedOff'. @@ -1253,9 +1394,9 @@ const tuyaTz = { return {state: result}; }, convertGet: async (entity, key, meta) => { - if (key=='state') { + if (key == 'state') { await entity.read('genOnOff', ['onOff']); - } else if (key=='countdown') { + } else if (key == 'countdown') { await entity.read('genOnOff', ['onTime']); } }, @@ -1421,8 +1562,8 @@ const tuyaFz = { const clickMapping: KeyValueNumberString = {0: 'single', 1: 'double', 2: 'hold'}; const buttonMapping: KeyValueNumberString = {1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8'}; // TS004F has single endpoint, TS0041A/TS0041 can have multiple but have just one button - const button = msg.device.endpoints.length == 1 || ['TS0041A', 'TS0041'].includes(msg.device.modelID) ? - '' : `${buttonMapping[msg.endpoint.ID]}_`; + const button = + msg.device.endpoints.length == 1 || ['TS0041A', 'TS0041'].includes(msg.device.modelID) ? '' : `${buttonMapping[msg.endpoint.ID]}_`; return {action: `${button}${clickMapping[msg.data.value]}`}; }, } satisfies Fz.Converter, @@ -1445,92 +1586,130 @@ const tuyaFz = { }; export {tuyaFz as fz}; -export function getHandlersForDP(name: string, dp: number, type: number, converter: Tuya.ValueConverterSingle, - readOnly?: boolean, skip?: (meta: Tz.Meta) => boolean, endpoint?: string, useGlobalSequence?: boolean): [Fz.Converter[], Tz.Converter[]] { - const keyName = (endpoint) ? `${name}_${endpoint}` : name; - const fromZigbee: Fz.Converter[] = [{ - cluster: 'manuSpecificTuya', - type: ['commandDataResponse', 'commandDataReport', 'commandActiveStatusReport', 'commandActiveStatusReportAlt'], - convert: (model, msg, publish, options, meta) => { - const dpValue = msg.data.dpValues.find((d: Tuya.DpValue) => d.dp === dp); - if (dpValue) { - return {[keyName]: converter.from(getDataValue(dpValue))}; - } - }, - }]; - - const toZigbee: Tz.Converter[] = (readOnly) ? undefined : [{ - key: [name], - endpoint: endpoint, - convertSet: async (entity, key, value, meta) => { - // A set converter is only called once; therefore we need to loop - const state: KeyValue = {}; - if (Array.isArray(meta.mapped)) throw new Error(`Not supported for groups`); - for (const [attr, value] of Object.entries(meta.message)) { - const convertedKey: string = meta.mapped.meta && meta.mapped.meta.multiEndpoint && meta.endpoint_name && !attr.startsWith(`${key}_`) ? - `${attr}_${meta.endpoint_name}` : attr; - // logger.debug(`key: ${key}, convertedKey: ${convertedKey}, keyName: ${keyName}`); - if (convertedKey !== keyName) continue; - if (skip && skip(meta)) continue; - - const convertedValue = await converter.to(value, meta); - const sendCommand = utils.getMetaValue(entity, meta.mapped, 'tuyaSendCommand', undefined, 'dataRequest'); - const seq = (useGlobalSequence) ? undefined : 1; - // logger.debug(`dp: ${dp}, value: ${value}, convertedValue: ${convertedValue}`); - - if (convertedValue === undefined) { - // conversion done inside converter, ignore. - } else if (type == dataTypes.bool) { - await sendDataPointBool(entity, dp, convertedValue as boolean, sendCommand, seq); - } else if (type == dataTypes.number) { - await sendDataPointValue(entity, dp, convertedValue as number, sendCommand, seq); - } else if (type == dataTypes.string) { - await sendDataPointStringBuffer(entity, dp, convertedValue as string, sendCommand, seq); - } else if (type == dataTypes.raw) { - await sendDataPointRaw(entity, dp, convertedValue as number[], sendCommand, seq); - } else if (type == dataTypes.enum) { - await sendDataPointEnum(entity, dp, convertedValue as number, sendCommand, seq); - } else if (type == dataTypes.bitmap) { - await sendDataPointBitmap(entity, dp, convertedValue as number, sendCommand, seq); - } else { - throw new Error(`Don't know how to send type '${typeof convertedValue}'`); +export function getHandlersForDP( + name: string, + dp: number, + type: number, + converter: Tuya.ValueConverterSingle, + readOnly?: boolean, + skip?: (meta: Tz.Meta) => boolean, + endpoint?: string, + useGlobalSequence?: boolean, +): [Fz.Converter[], Tz.Converter[]] { + const keyName = endpoint ? `${name}_${endpoint}` : name; + const fromZigbee: Fz.Converter[] = [ + { + cluster: 'manuSpecificTuya', + type: ['commandDataResponse', 'commandDataReport', 'commandActiveStatusReport', 'commandActiveStatusReportAlt'], + convert: (model, msg, publish, options, meta) => { + const dpValue = msg.data.dpValues.find((d: Tuya.DpValue) => d.dp === dp); + if (dpValue) { + return {[keyName]: converter.from(getDataValue(dpValue))}; } - - state[convertedKey] = value; - } - return {state}; + }, }, - }]; + ]; + + const toZigbee: Tz.Converter[] = readOnly + ? undefined + : [ + { + key: [name], + endpoint: endpoint, + convertSet: async (entity, key, value, meta) => { + // A set converter is only called once; therefore we need to loop + const state: KeyValue = {}; + if (Array.isArray(meta.mapped)) throw new Error(`Not supported for groups`); + for (const [attr, value] of Object.entries(meta.message)) { + const convertedKey: string = + meta.mapped.meta && meta.mapped.meta.multiEndpoint && meta.endpoint_name && !attr.startsWith(`${key}_`) + ? `${attr}_${meta.endpoint_name}` + : attr; + // logger.debug(`key: ${key}, convertedKey: ${convertedKey}, keyName: ${keyName}`); + if (convertedKey !== keyName) continue; + if (skip && skip(meta)) continue; + + const convertedValue = await converter.to(value, meta); + const sendCommand = utils.getMetaValue(entity, meta.mapped, 'tuyaSendCommand', undefined, 'dataRequest'); + const seq = useGlobalSequence ? undefined : 1; + // logger.debug(`dp: ${dp}, value: ${value}, convertedValue: ${convertedValue}`); + + if (convertedValue === undefined) { + // conversion done inside converter, ignore. + } else if (type == dataTypes.bool) { + await sendDataPointBool(entity, dp, convertedValue as boolean, sendCommand, seq); + } else if (type == dataTypes.number) { + await sendDataPointValue(entity, dp, convertedValue as number, sendCommand, seq); + } else if (type == dataTypes.string) { + await sendDataPointStringBuffer(entity, dp, convertedValue as string, sendCommand, seq); + } else if (type == dataTypes.raw) { + await sendDataPointRaw(entity, dp, convertedValue as number[], sendCommand, seq); + } else if (type == dataTypes.enum) { + await sendDataPointEnum(entity, dp, convertedValue as number, sendCommand, seq); + } else if (type == dataTypes.bitmap) { + await sendDataPointBitmap(entity, dp, convertedValue as number, sendCommand, seq); + } else { + throw new Error(`Don't know how to send type '${typeof convertedValue}'`); + } + + state[convertedKey] = value; + } + return {state}; + }, + }, + ]; return [fromZigbee, toZigbee]; } export interface TuyaDPEnumLookupArgs { - name: string, dp: number, type?: number, lookup?: KeyValue, - description?: string, readOnly?: boolean, endpoint?: string, skip?: (meta: Tz.Meta) => boolean, - expose?: Expose, + name: string; + dp: number; + type?: number; + lookup?: KeyValue; + description?: string; + readOnly?: boolean; + endpoint?: string; + skip?: (meta: Tz.Meta) => boolean; + expose?: Expose; } export interface TuyaDPBinaryArgs { - name: string, dp: number, type: number, valueOn: [string | boolean, unknown], valueOff: [string | boolean, unknown], - description?: string, readOnly?: boolean, endpoint?: string, skip?: (meta: Tz.Meta) => boolean, - expose?: Expose, + name: string; + dp: number; + type: number; + valueOn: [string | boolean, unknown]; + valueOff: [string | boolean, unknown]; + description?: string; + readOnly?: boolean; + endpoint?: string; + skip?: (meta: Tz.Meta) => boolean; + expose?: Expose; } export interface TuyaDPNumericArgs { - name: string, dp: number, type: number, - description?: string, readOnly?: boolean, endpoint?: string, unit?: string, skip?: (meta: Tz.Meta) => boolean, - valueMin?: number, valueMax?: number, valueStep?: number, scale?: number | [number, number, number, number], - expose?: exposes.Numeric, + name: string; + dp: number; + type: number; + description?: string; + readOnly?: boolean; + endpoint?: string; + unit?: string; + skip?: (meta: Tz.Meta) => boolean; + valueMin?: number; + valueMax?: number; + valueStep?: number; + scale?: number | [number, number, number, number]; + expose?: exposes.Numeric; } export interface TuyaDPLightArgs { - state: {dp: number, type: number, valueOn: [string | boolean, unknown], valueOff: [string | boolean, unknown], skip?: (meta: Tz.Meta) => boolean}, - brightness: {dp: number, type: number, scale?: number | [number, number, number, number]}, - max?: {dp: number, type: number, scale?: number | [number, number, number, number]}, - min?: {dp: number, type: number, scale?: number | [number, number, number, number]}, - colorTemp?: {dp: number, type: number, range: Range, scale?: number | [number, number, number, number]}, + state: {dp: number; type: number; valueOn: [string | boolean, unknown]; valueOff: [string | boolean, unknown]; skip?: (meta: Tz.Meta) => boolean}; + brightness: {dp: number; type: number; scale?: number | [number, number, number, number]}; + max?: {dp: number; type: number; scale?: number | [number, number, number, number]}; + min?: {dp: number; type: number; scale?: number | [number, number, number, number]}; + colorTemp?: {dp: number; type: number; range: Range; scale?: number | [number, number, number, number]}; // color?: {dp: number, type: number, scale?: number | [number, number, number, number]}, - endpoint?: string, + endpoint?: string; } const tuyaModernExtend = { @@ -1544,10 +1723,18 @@ const tuyaModernExtend = { } if (endpoint) exp = exp.withEndpoint(endpoint); - const handlers: [Fz.Converter[], Tz.Converter[]] = getHandlersForDP(name, dp, type, { - from: (value) => utils.getFromLookupByValue(value, lookup), - to: (value) => utils.getFromLookup(value, lookup), - }, readOnly, skip, endpoint); + const handlers: [Fz.Converter[], Tz.Converter[]] = getHandlersForDP( + name, + dp, + type, + { + from: (value) => utils.getFromLookupByValue(value, lookup), + to: (value) => utils.getFromLookup(value, lookup), + }, + readOnly, + skip, + endpoint, + ); return {exposes: [exp], fromZigbee: handlers[0], toZigbee: handlers[1], isModernExtend: true}; }, @@ -1561,10 +1748,18 @@ const tuyaModernExtend = { } if (endpoint) exp = exp.withEndpoint(endpoint); - const handlers: [Fz.Converter[], Tz.Converter[]] = getHandlersForDP(name, dp, type, { - from: (value) => (value === valueOn[1]) ? valueOn[0] : valueOff[0], - to: (value) => (value === valueOn[0]) ? valueOn[1] : valueOff[1], - }, readOnly, skip, endpoint); + const handlers: [Fz.Converter[], Tz.Converter[]] = getHandlersForDP( + name, + dp, + type, + { + from: (value) => (value === valueOn[1] ? valueOn[0] : valueOff[0]), + to: (value) => (value === valueOn[0] ? valueOn[1] : valueOff[1]), + }, + readOnly, + skip, + endpoint, + ); return {exposes: [exp], fromZigbee: handlers[0], toZigbee: handlers[1], isModernExtend: true}; }, @@ -1616,29 +1811,38 @@ const tuyaModernExtend = { // exp = exp.withColor(['hs']).setAccess('color_hs', ea.STATE_SET); // } if (endpoint) exp = exp.withEndpoint(endpoint); - ext = tuyaModernExtend.dpBinary({name: 'state', dp: state.dp, type: state.type, - valueOn: state.valueOn, valueOff: state.valueOff, skip: state.skip, endpoint: endpoint}); + ext = tuyaModernExtend.dpBinary({ + name: 'state', + dp: state.dp, + type: state.type, + valueOn: state.valueOn, + valueOff: state.valueOff, + skip: state.skip, + endpoint: endpoint, + }); fromZigbee = [...fromZigbee, ...ext.fromZigbee]; toZigbee = [...toZigbee, ...ext.toZigbee]; - ext = tuyaModernExtend.dpNumeric({name: 'brightness', dp: brightness.dp, type: brightness.type, - scale: brightness.scale, endpoint: endpoint}); + ext = tuyaModernExtend.dpNumeric({name: 'brightness', dp: brightness.dp, type: brightness.type, scale: brightness.scale, endpoint: endpoint}); fromZigbee = [...fromZigbee, ...ext.fromZigbee]; toZigbee = [...toZigbee, ...ext.toZigbee]; if (min) { - ext = tuyaModernExtend.dpNumeric({name: 'min_brightness', dp: min.dp, type: min.type, - scale: min.scale, endpoint: endpoint}); + ext = tuyaModernExtend.dpNumeric({name: 'min_brightness', dp: min.dp, type: min.type, scale: min.scale, endpoint: endpoint}); fromZigbee = [...fromZigbee, ...ext.fromZigbee]; toZigbee = [...toZigbee, ...ext.toZigbee]; } if (max) { - ext = tuyaModernExtend.dpNumeric({name: 'max_brightness', dp: max.dp, type: max.type, - scale: max.scale, endpoint: endpoint}); + ext = tuyaModernExtend.dpNumeric({name: 'max_brightness', dp: max.dp, type: max.type, scale: max.scale, endpoint: endpoint}); fromZigbee = [...fromZigbee, ...ext.fromZigbee]; toZigbee = [...toZigbee, ...ext.toZigbee]; } if (colorTemp) { - ext = tuyaModernExtend.dpNumeric({name: 'color_temp', dp: colorTemp.dp, type: colorTemp.type, - scale: colorTemp.scale, endpoint: endpoint}); + ext = tuyaModernExtend.dpNumeric({ + name: 'color_temp', + dp: colorTemp.dp, + type: colorTemp.type, + scale: colorTemp.scale, + endpoint: endpoint, + }); fromZigbee = [...fromZigbee, ...ext.fromZigbee]; toZigbee = [...toZigbee, ...ext.toZigbee]; } @@ -1663,44 +1867,84 @@ const tuyaModernExtend = { return tuyaModernExtend.dpNumeric({name: 'battery', type: dataTypes.number, readOnly: true, expose: e.battery(), ...args}); }, dpBatteryState(args?: Partial): ModernExtend { - return tuyaModernExtend.dpEnumLookup({name: 'battery_state', type: dataTypes.number, lookup: {'low': 0, 'medium': 1, 'high': 2}, - readOnly: true, expose: tuyaExposes.batteryState(), ...args}); + return tuyaModernExtend.dpEnumLookup({ + name: 'battery_state', + type: dataTypes.number, + lookup: {low: 0, medium: 1, high: 2}, + readOnly: true, + expose: tuyaExposes.batteryState(), + ...args, + }); }, dpTemperatureUnit(args?: Partial): ModernExtend { - return tuyaModernExtend.dpEnumLookup({name: 'temperature_unit', type: dataTypes.enum, lookup: {'celsius': 0, 'fahrenheit': 1}, - readOnly: true, expose: tuyaExposes.temperatureUnit(), ...args}); + return tuyaModernExtend.dpEnumLookup({ + name: 'temperature_unit', + type: dataTypes.enum, + lookup: {celsius: 0, fahrenheit: 1}, + readOnly: true, + expose: tuyaExposes.temperatureUnit(), + ...args, + }); }, dpContact(args?: Partial, invert?: boolean): ModernExtend { - return tuyaModernExtend.dpBinary({name: 'contact', type: dataTypes.bool, - valueOn: (invert) ? [true, true] : [true, false], valueOff: (invert) ? [false, false] : [false, true], - readOnly: true, expose: e.contact(), ...args}); + return tuyaModernExtend.dpBinary({ + name: 'contact', + type: dataTypes.bool, + valueOn: invert ? [true, true] : [true, false], + valueOff: invert ? [false, false] : [false, true], + readOnly: true, + expose: e.contact(), + ...args, + }); }, dpAction(args?: Partial): ModernExtend { const {lookup} = args; - return tuyaModernExtend.dpEnumLookup({name: 'action', type: dataTypes.number, readOnly: true, - expose: e.action(Object.keys(lookup)), ...args}); + return tuyaModernExtend.dpEnumLookup({ + name: 'action', + type: dataTypes.number, + readOnly: true, + expose: e.action(Object.keys(lookup)), + ...args, + }); }, dpIlluminance(args?: Partial): ModernExtend { - return tuyaModernExtend.dpNumeric({name: 'illuminance', type: dataTypes.number, readOnly: true, - expose: e.illuminance(), ...args}); + return tuyaModernExtend.dpNumeric({name: 'illuminance', type: dataTypes.number, readOnly: true, expose: e.illuminance(), ...args}); }, dpGas(args?: Partial, invert?: boolean): ModernExtend { - return tuyaModernExtend.dpBinary({name: 'gas', type: dataTypes.enum, - valueOn: (invert) ? [true, 1] : [true, 0], valueOff: (invert) ? [false, 0] : [false, 1], - readOnly: true, expose: e.gas(), ...args}); + return tuyaModernExtend.dpBinary({ + name: 'gas', + type: dataTypes.enum, + valueOn: invert ? [true, 1] : [true, 0], + valueOff: invert ? [false, 0] : [false, 1], + readOnly: true, + expose: e.gas(), + ...args, + }); }, dpOnOff(args?: Partial): ModernExtend { const {readOnly} = args; - return tuyaModernExtend.dpBinary({name: 'state', type: dataTypes.bool, - valueOn: ['ON', true], valueOff: ['OFF', false], expose: e.switch().setAccess('state', readOnly ? ea.STATE : ea.STATE_SET), ...args}); + return tuyaModernExtend.dpBinary({ + name: 'state', + type: dataTypes.bool, + valueOn: ['ON', true], + valueOff: ['OFF', false], + expose: e.switch().setAccess('state', readOnly ? ea.STATE : ea.STATE_SET), + ...args, + }); }, dpPowerOnBehavior(args?: Partial): ModernExtend { - let {readOnly, lookup} = args; - lookup = lookup || {'off': 0, 'on': 1, 'previous': 2}; - return tuyaModernExtend.dpEnumLookup({name: 'power_on_behavior', lookup: lookup, type: dataTypes.enum, - expose: e.power_on_behavior(Object.keys(lookup)).withAccess(readOnly ? ea.STATE : ea.STATE_SET), ...args}); + const {readOnly} = args; + let {lookup} = args; + lookup = lookup || {off: 0, on: 1, previous: 2}; + return tuyaModernExtend.dpEnumLookup({ + name: 'power_on_behavior', + lookup: lookup, + type: dataTypes.enum, + expose: e.power_on_behavior(Object.keys(lookup)).withAccess(readOnly ? ea.STATE : ea.STATE_SET), + ...args, + }); }, - tuyaLight(args?: modernExtend.LightArgs & {minBrightness?: 'none' | 'attribute' | 'command', switchType?: boolean}) { + tuyaLight(args?: modernExtend.LightArgs & {minBrightness?: 'none' | 'attribute' | 'command'; switchType?: boolean}) { args = {minBrightness: 'none', powerOnBehavior: false, switchType: false, ...args}; if (args.colorTemp) { args.colorTemp = {startup: false, ...args.colorTemp}; @@ -1734,11 +1978,12 @@ const tuyaModernExtend = { if (args.minBrightness === 'attribute') { result.fromZigbee.push(tuyaFz.min_brightness_attribute); result.toZigbee.push(tuyaTz.min_brightness_attribute); - result.exposes = result.exposes.map((e) => typeof e !== 'function' && utils.isLightExpose(e) ? e.withMinBrightness() : e); + result.exposes = result.exposes.map((e) => (typeof e !== 'function' && utils.isLightExpose(e) ? e.withMinBrightness() : e)); } else if (args.minBrightness === 'command') { result.toZigbee.push(tuyaTz.min_brightness_command); - result.exposes = result.exposes.map((e) => typeof e !== 'function' && utils.isLightExpose(e) ? - e.withMinBrightness().setAccess('min_brightness', ea.STATE_SET) : e); + result.exposes = result.exposes.map((e) => + typeof e !== 'function' && utils.isLightExpose(e) ? e.withMinBrightness().setAccess('min_brightness', ea.STATE_SET) : e, + ); } if (args.color) { @@ -1748,13 +1993,26 @@ const tuyaModernExtend = { return result; }, - tuyaOnOff: (args: { - endpoints?: string[], powerOutageMemory?: boolean, powerOnBehavior2?: boolean, switchType?: boolean, backlightModeLowMediumHigh?: boolean, - indicatorMode?: boolean, backlightModeOffNormalInverted?: boolean, backlightModeOffOn?: boolean, electricalMeasurements?: boolean, - electricalMeasurementsFzConverter?: Fz.Converter, childLock?: boolean, switchMode?: boolean, onOffCountdown?: boolean, - }={}): ModernExtend => { - const exposes: (Expose | DefinitionExposesFunction)[] = - args.endpoints ? args.endpoints.map((ee) => e.switch().withEndpoint(ee)) : [e.switch()]; + tuyaOnOff: ( + args: { + endpoints?: string[]; + powerOutageMemory?: boolean; + powerOnBehavior2?: boolean; + switchType?: boolean; + backlightModeLowMediumHigh?: boolean; + indicatorMode?: boolean; + backlightModeOffNormalInverted?: boolean; + backlightModeOffOn?: boolean; + electricalMeasurements?: boolean; + electricalMeasurementsFzConverter?: Fz.Converter; + childLock?: boolean; + switchMode?: boolean; + onOffCountdown?: boolean; + } = {}, + ): ModernExtend => { + const exposes: (Expose | DefinitionExposesFunction)[] = args.endpoints + ? args.endpoints.map((ee) => e.switch().withEndpoint(ee)) + : [e.switch()]; const fromZigbee: Fz.Converter[] = [fz.on_off, fz.ignore_basic_report]; const toZigbee: Tz.Converter[] = []; if (args.onOffCountdown) { @@ -1815,7 +2073,7 @@ const tuyaModernExtend = { } if (args.electricalMeasurements) { - fromZigbee.push((args.electricalMeasurementsFzConverter || fz.electrical_measurement), fz.metering); + fromZigbee.push(args.electricalMeasurementsFzConverter || fz.electrical_measurement, fz.metering); exposes.push(e.power(), e.current(), e.voltage(), e.energy()); } if (args.childLock) { @@ -1826,7 +2084,7 @@ const tuyaModernExtend = { if (args.switchMode) { if (args.endpoints) { - args.endpoints.forEach(function(ep) { + args.endpoints.forEach(function (ep) { const epExtend = tuyaModernExtend.tuyaSwitchMode({ description: `Switch mode ${ep}`, endpointName: ep, @@ -1846,13 +2104,19 @@ const tuyaModernExtend = { return {exposes, fromZigbee, toZigbee, isModernExtend: true}; }, dpBacklightMode(args?: Partial): ModernExtend { - let {readOnly, lookup} = args; - lookup = lookup || {'off': 0, 'normal': 1, 'inverted': 2}; - return tuyaModernExtend.dpEnumLookup({name: 'backlight_mode', lookup: lookup, type: dataTypes.enum, - expose: tuyaExposes.backlightModeOffNormalInverted().withAccess(readOnly ? ea.STATE : ea.STATE_SET), ...args}); + const {readOnly} = args; + let {lookup} = args; + lookup = lookup || {off: 0, normal: 1, inverted: 2}; + return tuyaModernExtend.dpEnumLookup({ + name: 'backlight_mode', + lookup: lookup, + type: dataTypes.enum, + expose: tuyaExposes.backlightModeOffNormalInverted().withAccess(readOnly ? ea.STATE : ea.STATE_SET), + ...args, + }); }, combineActions(actions: ModernExtend[]): ModernExtend { - let newValues: (string|number)[] = []; + let newValues: (string | number)[] = []; let newFromZigbee: Fz.Converter[] = []; let description: string; // collect action values and handlers @@ -1868,15 +2132,16 @@ const tuyaModernExtend = { return {exposes: [exp], fromZigbee: newFromZigbee, isModernExtend: true}; }, - tuyaSwitchMode: (args?: Partial) => modernExtend.enumLookup({ - name: 'switch_mode', - lookup: {'switch': 0, 'scene': 1}, - cluster: 'manuSpecificTuya_3', - attribute: 'switchMode', - description: 'Work mode for switch', - entityCategory: 'config', - ...args, - }), + tuyaSwitchMode: (args?: Partial) => + modernExtend.enumLookup({ + name: 'switch_mode', + lookup: {switch: 0, scene: 1}, + cluster: 'manuSpecificTuya_3', + attribute: 'switchMode', + description: 'Work mode for switch', + entityCategory: 'config', + ...args, + }), tuyaLedIndicator(): ModernExtend { const fromZigbee = [tuyaFz.backlight_mode_off_normal_inverted]; const exp = tuyaExposes.backlightModeOffNormalInverted(); @@ -1895,16 +2160,22 @@ const tuyaModernExtend = { attribute: 'value', }); }, - tuyaOnOffActionLegacy(args: {actions: ('single' | 'double' | 'hold')[], endpointNames?: string[]}): ModernExtend { + tuyaOnOffActionLegacy(args: {actions: ('single' | 'double' | 'hold')[]; endpointNames?: string[]}): ModernExtend { // For new devices use tuyaOnOffAction instead - const actions = args.actions.map((a) => args.endpointNames ? args.endpointNames.map((e) => `${e}_${a}`) : [a]).flat(); + const actions = args.actions.map((a) => (args.endpointNames ? args.endpointNames.map((e) => `${e}_${a}`) : [a])).flat(); const exposes: Expose[] = [e.action(actions)]; const fromZigbee: Fz.Converter[] = [tuyaFz.on_off_action]; return {exposes, fromZigbee, isModernExtend: true}; }, dpChildLock(args?: Partial): ModernExtend { - return tuyaModernExtend.dpBinary({name: 'child_lock', type: dataTypes.bool, - valueOn: ['LOCK', true], valueOff: ['UNLOCK', false], expose: e.child_lock(), ...args}); + return tuyaModernExtend.dpBinary({ + name: 'child_lock', + type: dataTypes.bool, + valueOn: ['LOCK', true], + valueOff: ['UNLOCK', false], + expose: e.child_lock(), + ...args, + }); }, }; export {tuyaModernExtend as modernExtend}; diff --git a/src/lib/types.ts b/src/lib/types.ts index b27c4823d66a0..21af41dc16aab 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -1,12 +1,8 @@ /* eslint-disable no-unused-vars */ /* eslint-disable @typescript-eslint/no-namespace */ -import type { - Device as ZHDevice, - Endpoint as ZHEndpoint, - Group as ZHGroup, -} from 'zigbee-herdsman/dist/controller/model'; -import type {FrameControl} from 'zigbee-herdsman/dist/zspec/zcl/definition/tstype'; +import type {Device as ZHDevice, Endpoint as ZHEndpoint, Group as ZHGroup} from 'zigbee-herdsman/dist/controller/model'; import type {Header as ZHZclHeader} from 'zigbee-herdsman/dist/zspec/zcl'; +import type {FrameControl} from 'zigbee-herdsman/dist/zspec/zcl/definition/tstype'; import * as exposes from './exposes'; @@ -18,36 +14,82 @@ export interface Logger { } export type Range = [number, number]; -export interface KeyValue {[s: string]: unknown} -export interface KeyValueString {[s: string]: string} -export interface KeyValueNumberString {[s: number]: string} -// eslint-disable-next-line -export interface KeyValueAny {[s: string]: any} +export interface KeyValue { + [s: string]: unknown; +} +export interface KeyValueString { + [s: string]: string; +} +export interface KeyValueNumberString { + [s: number]: string; +} +export interface KeyValueAny { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [s: string]: any; +} export type Publish = (payload: KeyValue) => void; -export type OnEventType = 'start' | 'stop' | 'message' | 'deviceJoined' | 'deviceInterview' | 'deviceAnnounce' | - 'deviceNetworkAddressChanged' | 'deviceOptionsChanged'; +export type OnEventType = + | 'start' + | 'stop' + | 'message' + | 'deviceJoined' + | 'deviceInterview' + | 'deviceAnnounce' + | 'deviceNetworkAddressChanged' + | 'deviceOptionsChanged'; export type Access = 0b001 | 0b010 | 0b100 | 0b011 | 0b101 | 0b111; -export type Expose = exposes.Numeric | exposes.Binary | exposes.Enum | exposes.Composite | exposes.List | exposes.Light | exposes.Switch | - exposes.Lock | exposes.Cover | exposes.Climate | exposes.Text; +export type Expose = + | exposes.Numeric + | exposes.Binary + | exposes.Enum + | exposes.Composite + | exposes.List + | exposes.Light + | exposes.Switch + | exposes.Lock + | exposes.Cover + | exposes.Climate + | exposes.Text; export type Option = exposes.Numeric | exposes.Binary | exposes.Composite | exposes.Enum | exposes.List | exposes.Text; export interface Fingerprint { - applicationVersion?: number, manufacturerID?: number, type?: 'EndDevice' | 'Router', dateCode?: string, - hardwareVersion?: number, manufacturerName?: string, modelID?: string, powerSource?: 'Battery' | 'Mains (single phase)', - softwareBuildID?: string, stackVersion?: number, zclVersion?: number, ieeeAddr?: RegExp, - endpoints?: {ID?: number, profileID?: number, deviceID?: number, inputClusters?: number[], outputClusters?: number[]}[], - priority?: number, + applicationVersion?: number; + manufacturerID?: number; + type?: 'EndDevice' | 'Router'; + dateCode?: string; + hardwareVersion?: number; + manufacturerName?: string; + modelID?: string; + powerSource?: 'Battery' | 'Mains (single phase)'; + softwareBuildID?: string; + stackVersion?: number; + zclVersion?: number; + ieeeAddr?: RegExp; + endpoints?: {ID?: number; profileID?: number; deviceID?: number; inputClusters?: number[]; outputClusters?: number[]}[]; + priority?: number; } export type WhiteLabel = - {vendor: string, model: string, description: string, fingerprint: Fingerprint[]} | - {vendor: string, model: string, description?: string}; -export interface OtaUpdateAvailableResult {available: boolean, currentFileVersion: number, otaFileVersion: number} + | {vendor: string; model: string; description: string; fingerprint: Fingerprint[]} + | {vendor: string; model: string; description?: string}; +export interface OtaUpdateAvailableResult { + available: boolean; + currentFileVersion: number; + otaFileVersion: number; +} -export interface MockProperty {property: string, value: KeyValue | string} +export interface MockProperty { + property: string; + value: KeyValue | string; +} // eslint-disable-next-line camelcase -export interface DiscoveryEntry {mockProperties: MockProperty[], type: string, object_id: string, discovery_payload: KeyValue} +export interface DiscoveryEntry { + mockProperties: MockProperty[]; + type: string; + object_id: string; + discovery_payload: KeyValue; +} export interface DefinitionMeta { - separateWhite?: boolean, + separateWhite?: boolean; /** * Enables the multi endpoint functionality in e.g. `fromZigbee.on_off`, example: normally this converter would return `{"state": "OFF"}`, when * multiEndpoint is enabled the `endpoint` method of the device definition will be called to determine the endpoint name which is then used as ke @@ -55,59 +97,59 @@ export interface DefinitionMeta { * * @defaultValue false */ - multiEndpoint?: boolean, + multiEndpoint?: boolean; /** * enforce a certain endpoint for an attribute, e.g. `{"power": 4}` see `utils.enforceEndpoint()` */ - multiEndpointEnforce?: {[s: string]: number}, + multiEndpointEnforce?: {[s: string]: number}; /** * Attributes to not suffix with the endpoint name */ - multiEndpointSkip?: string[], - publishDuplicateTransaction?: boolean, - tuyaDatapoints?: Tuya.MetaTuyaDataPoints, + multiEndpointSkip?: string[]; + publishDuplicateTransaction?: boolean; + tuyaDatapoints?: Tuya.MetaTuyaDataPoints; /** * used by toZigbee converters to disable the default response of some devices as they don't provide one. * * @defaultValue false */ - disableDefaultResponse?: boolean | ((entity: Zh.Endpoint) => boolean), + disableDefaultResponse?: boolean | ((entity: Zh.Endpoint) => boolean); /** * Amount of pincodes the lock can handle */ - pinCodeCount?: number, + pinCodeCount?: number; /** * Set to true for cover controls that report position=100 as open. * * @defaultValue false */ - coverInverted?: boolean, + coverInverted?: boolean; /** * timeout for commands to this device used in toZigbee. * * @defaultValue 10000 */ - timeout?: number, - tuyaSendCommand?: 'sendData' | 'dataRequest', + timeout?: number; + tuyaSendCommand?: 'sendData' | 'dataRequest'; /** * Set cover state based on tilt */ - coverStateFromTilt?: boolean, + coverStateFromTilt?: boolean; /** * see e.g. HT-08 definition */ thermostat?: { - weeklyScheduleMaxTransitions?: number, - weeklyScheduleSupportedModes?: number[], - weeklyScheduleFirstDayDpId?: number, - weeklyScheduleConversion?: string, + weeklyScheduleMaxTransitions?: number; + weeklyScheduleSupportedModes?: number[]; + weeklyScheduleFirstDayDpId?: number; + weeklyScheduleConversion?: string; /** * Do not map `pIHeatingDemand`/`pICoolingDemand` from 0-255 -\> 0-100, see `fromZigbee.thermostat` * * @defaultValue false */ - dontMapPIHeatingDemand?: boolean - }, + dontMapPIHeatingDemand?: boolean; + }; battery?: { /** * convert voltage to percentage using specified option. See `utils.batteryVoltageToPercentage()` @@ -115,88 +157,88 @@ export interface DefinitionMeta { * @example '3V_2100' * @defaultValue null */ - voltageToPercentage?: string | {min: number, max: number}, + voltageToPercentage?: string | {min: number; max: number}; /** * Prevents batteryPercentageRemaining from being divided (ZCL 200=100%, but some report 100=100%) * * @defaultValue false */ - dontDividePercentage?: boolean - }, + dontDividePercentage?: boolean; + }; /** * see `toZigbee.light_color` * * @defaultValue false */ - applyRedFix?: boolean, + applyRedFix?: boolean; /** * Indicates light turns off when brightness 1 is set * * @defaultValue false */ turnsOffAtBrightness1?: boolean; - tuyaThermostatPreset?: {[s: number]: string}, + tuyaThermostatPreset?: {[s: number]: string}; /** Tuya specific thermostat options */ - tuyaThermostatSystemMode?: {[s: number]: string}, + tuyaThermostatSystemMode?: {[s: number]: string}; /** Tuya specific thermostat options */ - tuyaThermostatPresetToSystemMode?: {[s: number]: string}, + tuyaThermostatPresetToSystemMode?: {[s: number]: string}; /** * see `toZigbee.light_color` * * @defaultValue true */ - supportsEnhancedHue?: boolean | ((entity: Zh.Endpoint) => boolean), + supportsEnhancedHue?: boolean | ((entity: Zh.Endpoint) => boolean); /** * Prevents some converters adding the `action_group` to the payload. * * @defaultValue false */ - disableActionGroup?: boolean, + disableActionGroup?: boolean; /** * see `toZigbee.light_color`, usually set by `light_*` extends via options. * * @defaultValue true */ - supportsHueAndSaturation?: boolean, + supportsHueAndSaturation?: boolean; /** * Do not set `position` or `tilt` to target value on /set. See `toZigbee.cover_position_tilt` * * @defaultValue false */ - coverPositionTiltDisableReport?: boolean, + coverPositionTiltDisableReport?: boolean; /** * Override the Home Assistant discovery payload using a custom function. */ - overrideHaConfig?(configs: DiscoveryEntry[]): void, + overrideHaConfig?(configs: DiscoveryEntry[]): void; } export type Configure = (device: Zh.Device, coordinatorEndpoint: Zh.Endpoint, definition: Definition) => Promise; export type OnEvent = (type: OnEventType, data: OnEventData, device: Zh.Device, settings: KeyValue, state: KeyValue) => Promise; export interface ModernExtend { - fromZigbee?: Fz.Converter[], - toZigbee?: Tz.Converter[], - exposes?: (Expose | DefinitionExposesFunction)[], - configure?: Configure[], - meta?: DefinitionMeta, - ota?: DefinitionOta, - onEvent?: OnEvent, - endpoint?: (device: Zh.Device) => {[s: string]: number}, - isModernExtend: true, + fromZigbee?: Fz.Converter[]; + toZigbee?: Tz.Converter[]; + exposes?: (Expose | DefinitionExposesFunction)[]; + configure?: Configure[]; + meta?: DefinitionMeta; + ota?: DefinitionOta; + onEvent?: OnEvent; + endpoint?: (device: Zh.Device) => {[s: string]: number}; + isModernExtend: true; } export interface OnEventData { - endpoint?: Zh.Endpoint, - meta?: {zclTransactionSequenceNumber?: number, manufacturerCode?: number}, - cluster?: string, - type?: string, - data?: KeyValueAny, + endpoint?: Zh.Endpoint; + meta?: {zclTransactionSequenceNumber?: number; manufacturerCode?: number}; + cluster?: string; + type?: string; + data?: KeyValueAny; } export type DefinitionOta = { - isUpdateAvailable: (device: Zh.Device, requestPayload:Ota.ImageInfo) => Promise; + isUpdateAvailable: (device: Zh.Device, requestPayload: Ota.ImageInfo) => Promise; updateToLatest: (device: Zh.Device, onProgress: Ota.OnProgress) => Promise; -} +}; export type DefinitionExposesFunction = (device: Zh.Device | undefined, options: KeyValue | undefined) => Expose[]; @@ -214,31 +256,41 @@ export type Definition = { onEvent?: OnEvent; ota?: DefinitionOta; generated?: boolean; -} & ({ zigbeeModel: string[]; fingerprint?: Fingerprint[]; } | { zigbeeModel?: string[]; fingerprint: Fingerprint[]; }) - & ({ - extend: ModernExtend[]; - fromZigbee?: Fz.Converter[]; - toZigbee?: Tz.Converter[]; - exposes?: DefinitionExposes; - } | { - fromZigbee: Fz.Converter[]; - toZigbee: Tz.Converter[]; - exposes: DefinitionExposes; - }); +} & ({zigbeeModel: string[]; fingerprint?: Fingerprint[]} | {zigbeeModel?: string[]; fingerprint: Fingerprint[]}) & + ( + | { + extend: ModernExtend[]; + fromZigbee?: Fz.Converter[]; + toZigbee?: Tz.Converter[]; + exposes?: DefinitionExposes; + } + | { + fromZigbee: Fz.Converter[]; + toZigbee: Tz.Converter[]; + exposes: DefinitionExposes; + } + ); export namespace Fz { export interface Message { - // eslint-disable-next-line - data: any, - endpoint: Zh.Endpoint, device: Zh.Device, - meta: {zclTransactionSequenceNumber?: number; manufacturerCode?: number, frameControl?: FrameControl}, - groupID: number, type: string, - cluster: string | number, linkquality: number + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: any; + endpoint: Zh.Endpoint; + device: Zh.Device; + meta: {zclTransactionSequenceNumber?: number; manufacturerCode?: number; frameControl?: FrameControl}; + groupID: number; + type: string; + cluster: string | number; + linkquality: number; + } + export interface Meta { + state: KeyValue; + device: Zh.Device; + deviceExposesChanged: () => void; } - export interface Meta {state: KeyValue, device: Zh.Device, deviceExposesChanged: () => void} export interface Converter { - cluster: string | number, - type: string[] | string, + cluster: string | number; + type: string[] | string; options?: Option[] | ((definition: Definition) => Option[]); convert: (model: Definition, msg: Message, publish: Publish, options: KeyValue, meta: Fz.Meta) => KeyValueAny | void | Promise; } @@ -246,21 +298,21 @@ export namespace Fz { export namespace Tz { export interface Meta { - message: KeyValue, - device: Zh.Device, - mapped: Definition | Definition[], - options: KeyValue, - state: KeyValue, - endpoint_name: string, - membersState?: {[s: string]: KeyValue}, + message: KeyValue; + device: Zh.Device; + mapped: Definition | Definition[]; + options: KeyValue; + state: KeyValue; + endpoint_name: string; + membersState?: {[s: string]: KeyValue}; } - export type ConvertSetResult = {state?: KeyValue, readAfterWriteTime?: number, membersState?: {[s: string]: KeyValue}} | void + export type ConvertSetResult = {state?: KeyValue; readAfterWriteTime?: number; membersState?: {[s: string]: KeyValue}} | void; export interface Converter { - key: string[], + key: string[]; options?: Option[] | ((definition: Definition) => Option[]); - endpoint?: string, - convertSet?: (entity: Zh.Endpoint | Zh.Group, key: string, value: unknown, meta: Tz.Meta) => Promise, - convertGet?: (entity: Zh.Endpoint | Zh.Group, key: string, meta: Tz.Meta) => Promise, + endpoint?: string; + convertSet?: (entity: Zh.Endpoint | Zh.Group, key: string, value: unknown, meta: Tz.Meta) => Promise; + convertGet?: (entity: Zh.Endpoint | Zh.Group, key: string, meta: Tz.Meta) => Promise; } } @@ -272,53 +324,70 @@ export namespace Zh { } export namespace Tuya { - export interface DpValue {dp: number, datatype: number, data: Buffer | number[]} + export interface DpValue { + dp: number; + datatype: number; + data: Buffer | number[]; + } export interface ValueConverterSingle { - to?: (value: unknown, meta?: Tz.Meta) => unknown, - from?: (value: unknown, meta?: Fz.Meta, options?: KeyValue, publish?: Publish, msg?: Fz.Message) => number|string|boolean|KeyValue|null, + to?: (value: unknown, meta?: Tz.Meta) => unknown; + from?: ( + value: unknown, + meta?: Fz.Meta, + options?: KeyValue, + publish?: Publish, + msg?: Fz.Message, + ) => number | string | boolean | KeyValue | null; } export interface ValueConverterMulti { - to?: (value: unknown, meta?: Tz.Meta) => unknown, - from?: (value: unknown, meta?: Fz.Meta, options?: KeyValue, publish?: Publish, msg?: Fz.Message) => KeyValue, + to?: (value: unknown, meta?: Tz.Meta) => unknown; + from?: (value: unknown, meta?: Fz.Meta, options?: KeyValue, publish?: Publish, msg?: Fz.Message) => KeyValue; + } + export interface MetaTuyaDataPointsMeta { + skip?: (meta: Tz.Meta) => boolean; + optimistic?: boolean; } - export interface MetaTuyaDataPointsMeta {skip?: (meta: Tz.Meta) => boolean, optimistic?: boolean} export type MetaTuyaDataPointsSingle = [number, string, Tuya.ValueConverterSingle, MetaTuyaDataPointsMeta?]; export type MetaTuyaDataPoints = MetaTuyaDataPointsSingle[]; } export namespace Ota { export type OnProgress = (progress: number, remaining: number) => void; - export interface Version {imageType: number, manufacturerCode: number, fileVersion: number} + export interface Version { + imageType: number; + manufacturerCode: number; + fileVersion: number; + } export interface ImageHeader { - otaUpgradeFileIdentifier: Buffer, - otaHeaderVersion: number, - otaHeaderLength: number, - otaHeaderFieldControl: number, - manufacturerCode: number, - imageType: number, - fileVersion: number, - zigbeeStackVersion: number, - otaHeaderString: string, - totalImageSize: number, - securityCredentialVersion?: number, - upgradeFileDestination?: Buffer - minimumHardwareVersion?: number, - maximumHardwareVersion?: number, + otaUpgradeFileIdentifier: Buffer; + otaHeaderVersion: number; + otaHeaderLength: number; + otaHeaderFieldControl: number; + manufacturerCode: number; + imageType: number; + fileVersion: number; + zigbeeStackVersion: number; + otaHeaderString: string; + totalImageSize: number; + securityCredentialVersion?: number; + upgradeFileDestination?: Buffer; + minimumHardwareVersion?: number; + maximumHardwareVersion?: number; } export interface ImageElement { - tagID: number, - length: number, - data: Buffer, + tagID: number; + length: number; + data: Buffer; } export interface Image { - header: ImageHeader, - elements: ImageElement[], - raw: Buffer, + header: ImageHeader; + elements: ImageElement[]; + raw: Buffer; } export interface ImageInfo { - imageType: number, - fileVersion: number, - manufacturerCode: number, + imageType: number; + fileVersion: number; + manufacturerCode: number; } export interface ImageMeta { fileVersion: number; @@ -327,15 +396,15 @@ export namespace Ota { sha256?: string; force?: boolean; sha512?: string; - hardwareVersionMin?: number, - hardwareVersionMax?: number, + hardwareVersionMin?: number; + hardwareVersionMax?: number; } export type GetImageMeta = (current: ImageInfo, device: Zh.Device) => Promise; } export namespace Reporting { export interface Override { - min?: number, - max?: number, - change?: number | [number, number], + min?: number; + max?: number; + change?: number | [number, number]; } } diff --git a/src/lib/ubisys.ts b/src/lib/ubisys.ts index c101d5a65b3be..d47a43b393a90 100644 --- a/src/lib/ubisys.ts +++ b/src/lib/ubisys.ts @@ -1,126 +1,130 @@ -import {Fz, Tz, ModernExtend, Configure} from './types'; -import {presets as e, access as ea} from './exposes'; -import {numeric, NumericArgs, deviceAddCustomCluster, setupConfigureForReporting} from './modernExtend'; import {Zcl} from 'zigbee-herdsman'; + +import {presets as e, access as ea} from './exposes'; import {logger} from './logger'; +import {numeric, NumericArgs, deviceAddCustomCluster, setupConfigureForReporting} from './modernExtend'; +import {Fz, Tz, ModernExtend, Configure} from './types'; const NS = 'zhc:ubisys'; export const ubisysModernExtend = { - addCustomClusterHvacThermostat: () => deviceAddCustomCluster( - 'hvacThermostat', - { + addCustomClusterHvacThermostat: () => + deviceAddCustomCluster('hvacThermostat', { ID: 0x0201, attributes: { // H10 - ubisysClassBTemperatureOffset: {ID: 0x0000, type: Zcl.DataType.INT8, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysReturnFlowTemperatureWeight: {ID: 0x0001, type: Zcl.DataType.INT8, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysRawOutdoorTemperature: {ID: 0x0002, type: Zcl.DataType.STRUCT, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysRawLocalTemperatureA: {ID: 0x0003, type: Zcl.DataType.STRUCT, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysRawLocalTemperatureB: {ID: 0x0004, type: Zcl.DataType.STRUCT, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysRawForwardFlowTemperature: {ID: 0x0005, type: Zcl.DataType.STRUCT, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysRawReturnFlowTemperature: {ID: 0x0006, type: Zcl.DataType.STRUCT, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysInstalledExtensions: {ID: 0x0007, type: Zcl.DataType.BITMAP64, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysClassBTemperatureOffset: {ID: 0x0000, type: Zcl.DataType.INT8, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysReturnFlowTemperatureWeight: { + ID: 0x0001, + type: Zcl.DataType.INT8, + manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH, + }, + ubisysRawOutdoorTemperature: {ID: 0x0002, type: Zcl.DataType.STRUCT, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysRawLocalTemperatureA: {ID: 0x0003, type: Zcl.DataType.STRUCT, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysRawLocalTemperatureB: {ID: 0x0004, type: Zcl.DataType.STRUCT, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysRawForwardFlowTemperature: { + ID: 0x0005, + type: Zcl.DataType.STRUCT, + manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH, + }, + ubisysRawReturnFlowTemperature: { + ID: 0x0006, + type: Zcl.DataType.STRUCT, + manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH, + }, + ubisysInstalledExtensions: {ID: 0x0007, type: Zcl.DataType.BITMAP64, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, // H1 - ubisysTemperatureOffset: {ID: 0x0010, type: Zcl.DataType.INT8, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysDefaultOccupiedHeatingSetpoint: {ID: 0x0011, type: Zcl.DataType.INT16, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysVacationMode: {ID: 0x0012, type: Zcl.DataType.BOOLEAN, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysRemoteTemperature: {ID: 0x0013, type: Zcl.DataType.INT16, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysRemoteTemperatureValidDuration: {ID: 0x0014, type: Zcl.DataType.UINT8, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysProportionalGain: {ID: 0x0020, type: Zcl.DataType.INT16, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysProportionalShift: {ID: 0x0021, type: Zcl.DataType.INT8, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysIntegralFactor: {ID: 0x0022, type: Zcl.DataType.INT16, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysTemperatureOffset: {ID: 0x0010, type: Zcl.DataType.INT8, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysDefaultOccupiedHeatingSetpoint: { + ID: 0x0011, + type: Zcl.DataType.INT16, + manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH, + }, + ubisysVacationMode: {ID: 0x0012, type: Zcl.DataType.BOOLEAN, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysRemoteTemperature: {ID: 0x0013, type: Zcl.DataType.INT16, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysRemoteTemperatureValidDuration: { + ID: 0x0014, + type: Zcl.DataType.UINT8, + manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH, + }, + ubisysProportionalGain: {ID: 0x0020, type: Zcl.DataType.INT16, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysProportionalShift: {ID: 0x0021, type: Zcl.DataType.INT8, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysIntegralFactor: {ID: 0x0022, type: Zcl.DataType.INT16, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, }, commands: {}, commandsResponse: {}, - }, - ), - addCustomClusterGenLevelCtrl: () => deviceAddCustomCluster( - 'genLevelCtrl', - { + }), + addCustomClusterGenLevelCtrl: () => + deviceAddCustomCluster('genLevelCtrl', { ID: 0x0008, attributes: { // D1(-R) - ubisysMinimumOnLevel: {ID: 0x0000, type: Zcl.DataType.UINT8, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysMinimumOnLevel: {ID: 0x0000, type: Zcl.DataType.UINT8, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, // H10 - ubisysValveType: {ID: 0x0001, type: Zcl.DataType.BITMAP8, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysCyclePeriod: {ID: 0x0002, type: Zcl.DataType.UINT8, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysSeason: {ID: 0x0003, type: Zcl.DataType.ENUM8, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysBackupLevel: {ID: 0x0004, type: Zcl.DataType.UINT8, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysAlternateBackupLevel: {ID: 0x0005, type: Zcl.DataType.UINT8, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysLowerRange: {ID: 0x0006, type: Zcl.DataType.UINT8, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysUpperRange: {ID: 0x0007, type: Zcl.DataType.UINT8, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysPumpThresholdOn: {ID: 0x0008, type: Zcl.DataType.UINT8, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysPumpThresholdOff: {ID: 0x0009, type: Zcl.DataType.UINT8, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysHeatingDemandEnableThreshold: {ID: 0x000A, type: Zcl.DataType.UINT8, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysHeatingDemandDisableThreshold: {ID: 0x000B, type: Zcl.DataType.UINT8, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysCoolingDemandEnableThreshold: {ID: 0x000C, type: Zcl.DataType.UINT8, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysCoolingDemandDisableThreshold: {ID: 0x000D, type: Zcl.DataType.UINT8, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysValveType: {ID: 0x0001, type: Zcl.DataType.BITMAP8, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysCyclePeriod: {ID: 0x0002, type: Zcl.DataType.UINT8, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysSeason: {ID: 0x0003, type: Zcl.DataType.ENUM8, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysBackupLevel: {ID: 0x0004, type: Zcl.DataType.UINT8, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysAlternateBackupLevel: {ID: 0x0005, type: Zcl.DataType.UINT8, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysLowerRange: {ID: 0x0006, type: Zcl.DataType.UINT8, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysUpperRange: {ID: 0x0007, type: Zcl.DataType.UINT8, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysPumpThresholdOn: {ID: 0x0008, type: Zcl.DataType.UINT8, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysPumpThresholdOff: {ID: 0x0009, type: Zcl.DataType.UINT8, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysHeatingDemandEnableThreshold: { + ID: 0x000a, + type: Zcl.DataType.UINT8, + manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH, + }, + ubisysHeatingDemandDisableThreshold: { + ID: 0x000b, + type: Zcl.DataType.UINT8, + manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH, + }, + ubisysCoolingDemandEnableThreshold: { + ID: 0x000c, + type: Zcl.DataType.UINT8, + manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH, + }, + ubisysCoolingDemandDisableThreshold: { + ID: 0x000d, + type: Zcl.DataType.UINT8, + manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH, + }, }, commands: {}, commandsResponse: {}, - }, - ), - addCustomClusterClosuresWindowCovering: () => deviceAddCustomCluster( - 'closuresWindowCovering', - { + }), + addCustomClusterClosuresWindowCovering: () => + deviceAddCustomCluster('closuresWindowCovering', { ID: 0x0102, attributes: { // J1(-R) - ubisysTurnaroundGuardTime: {ID: 0x1000, type: Zcl.DataType.UINT8, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysLiftToTiltTransitionSteps: {ID: 0x1001, type: Zcl.DataType.UINT16, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysTotalSteps: {ID: 0x1002, type: Zcl.DataType.UINT16, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysLiftToTiltTransitionSteps2: {ID: 0x1003, type: Zcl.DataType.UINT16, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysTotalSteps2: {ID: 0x1004, type: Zcl.DataType.UINT16, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysAdditionalSteps: {ID: 0x1005, type: Zcl.DataType.UINT8, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysInactivePowerThreshold: {ID: 0x1006, type: Zcl.DataType.UINT16, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, - ubisysStartupSteps: {ID: 0x1007, type: Zcl.DataType.UINT16, - manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysTurnaroundGuardTime: {ID: 0x1000, type: Zcl.DataType.UINT8, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysLiftToTiltTransitionSteps: { + ID: 0x1001, + type: Zcl.DataType.UINT16, + manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH, + }, + ubisysTotalSteps: {ID: 0x1002, type: Zcl.DataType.UINT16, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysLiftToTiltTransitionSteps2: { + ID: 0x1003, + type: Zcl.DataType.UINT16, + manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH, + }, + ubisysTotalSteps2: {ID: 0x1004, type: Zcl.DataType.UINT16, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysAdditionalSteps: {ID: 0x1005, type: Zcl.DataType.UINT8, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ubisysInactivePowerThreshold: { + ID: 0x1006, + type: Zcl.DataType.UINT16, + manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH, + }, + ubisysStartupSteps: {ID: 0x1007, type: Zcl.DataType.UINT16, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, }, commands: {}, commandsResponse: {}, - }, - ), - addCustomClusterManuSpecificUbisysDeviceSetup: () => deviceAddCustomCluster( - 'manuSpecificUbisysDeviceSetup', - { + }), + addCustomClusterManuSpecificUbisysDeviceSetup: () => + deviceAddCustomCluster('manuSpecificUbisysDeviceSetup', { ID: 0xfc00, // XXX: once we moved all manuSpecific ones out of zh, we should revisit this // Doesn't use manufacturerCode: https://github.com/Koenkk/zigbee-herdsman-converters/pull/4412 @@ -130,11 +134,9 @@ export const ubisysModernExtend = { }, commands: {}, commandsResponse: {}, - }, - ), - addCustomClusterManuSpecificUbisysDimmerSetup: () => deviceAddCustomCluster( - 'manuSpecificUbisysDimmerSetup', - { + }), + addCustomClusterManuSpecificUbisysDimmerSetup: () => + deviceAddCustomCluster('manuSpecificUbisysDimmerSetup', { ID: 0xfc00, manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH, attributes: { @@ -144,42 +146,46 @@ export const ubisysModernExtend = { }, commands: {}, commandsResponse: {}, - }, - ), - localTemperatureOffset: (args?: Partial) => numeric({ - name: 'local_temperature_offset', - cluster: 'hvacThermostat', - attribute: 'ubisysTemperatureOffset', - description: 'Specifies the temperature offset for the locally measured temperature value.', - valueMin: -10, - valueMax: 10, - unit: 'ºC', - ...args, - }), - occupiedHeatingSetpointDefault: (args?: Partial) => numeric({ - name: 'occupied_heating_default_setpoint', - cluster: 'hvacThermostat', - attribute: 'ubisysDefaultOccupiedHeatingSetpoint', - description: 'Specifies the default heating setpoint during occupancy, ' + - 'representing the targeted temperature when a recurring weekly schedule ends without a follow-up schedule.', - scale: 100, - valueStep: 0.5, // H1 interface uses 0.5 step - valueMin: 7, - valueMax: 30, - unit: 'ºC', - ...args, - }), - remoteTemperatureDuration: (args?: Partial) => numeric({ - name: 'remote_temperature_duration', - cluster: 'hvacThermostat', - attribute: 'ubisysRemoteTemperatureValidDuration', - description: 'Specifies the duration period in seconds, during which a remotely measured temperature value ' + - 'remains valid since its reception as attribute report.', - valueMin: 0, - valueMax: 86400, - unit: 's', - ...args, - }), + }), + localTemperatureOffset: (args?: Partial) => + numeric({ + name: 'local_temperature_offset', + cluster: 'hvacThermostat', + attribute: 'ubisysTemperatureOffset', + description: 'Specifies the temperature offset for the locally measured temperature value.', + valueMin: -10, + valueMax: 10, + unit: 'ºC', + ...args, + }), + occupiedHeatingSetpointDefault: (args?: Partial) => + numeric({ + name: 'occupied_heating_default_setpoint', + cluster: 'hvacThermostat', + attribute: 'ubisysDefaultOccupiedHeatingSetpoint', + description: + 'Specifies the default heating setpoint during occupancy, ' + + 'representing the targeted temperature when a recurring weekly schedule ends without a follow-up schedule.', + scale: 100, + valueStep: 0.5, // H1 interface uses 0.5 step + valueMin: 7, + valueMax: 30, + unit: 'ºC', + ...args, + }), + remoteTemperatureDuration: (args?: Partial) => + numeric({ + name: 'remote_temperature_duration', + cluster: 'hvacThermostat', + attribute: 'ubisysRemoteTemperatureValidDuration', + description: + 'Specifies the duration period in seconds, during which a remotely measured temperature value ' + + 'remains valid since its reception as attribute report.', + valueMin: 0, + valueMax: 86400, + unit: 's', + ...args, + }), vacationMode: (): ModernExtend => { const clusterName = 'hvacThermostat'; const writeableAttributeName = 'ubisysVacationMode'; @@ -187,36 +193,44 @@ export const ubisysModernExtend = { const propertyName = 'vacation_mode'; const access = ea.ALL; - const expose = e.binary(propertyName, access, true, false) + const expose = e + .binary(propertyName, access, true, false) .withDescription('When Vacation Mode is active the schedule is disabled and unoccupied_heating_setpoint is used.'); - const fromZigbee: Fz.Converter[] = [{ - cluster: clusterName, - type: ['attributeReport', 'readResponse'], - convert: (model, msg, publish, options, meta) => { - if (msg.data.hasOwnProperty(readableAttributeName)) { - return {[propertyName]: (msg.data.occupancy === 0)}; - } + const fromZigbee: Fz.Converter[] = [ + { + cluster: clusterName, + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + if (msg.data.hasOwnProperty(readableAttributeName)) { + return {[propertyName]: msg.data.occupancy === 0}; + } + }, }, - }]; + ]; - const toZigbee: Tz.Converter[] = [{ - key: [propertyName], - convertSet: async (entity, key, value, meta) => { - if (typeof value === 'boolean') { - // NOTE: DataType is BOOLEAN in zcl definition as per the device technical reference - // passing a BOOLEAN type 'value' throws INVALID_DATA_TYPE, we need to pass 1 (true) or 0 (false) - // ZCL DataType used does still need to be 0x0010 (BOOLEAN) - await entity.write(clusterName, {[writeableAttributeName]: value ? 1 : 0}, - {manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}); - } else { - logger.error(`${propertyName} must be a boolean!`, NS); - } - }, - convertGet: async (entity, key, meta) => { - await entity.read(clusterName, [readableAttributeName]); + const toZigbee: Tz.Converter[] = [ + { + key: [propertyName], + convertSet: async (entity, key, value, meta) => { + if (typeof value === 'boolean') { + // NOTE: DataType is BOOLEAN in zcl definition as per the device technical reference + // passing a BOOLEAN type 'value' throws INVALID_DATA_TYPE, we need to pass 1 (true) or 0 (false) + // ZCL DataType used does still need to be 0x0010 (BOOLEAN) + await entity.write( + clusterName, + {[writeableAttributeName]: value ? 1 : 0}, + {manufacturerCode: Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH}, + ); + } else { + logger.error(`${propertyName} must be a boolean!`, NS); + } + }, + convertGet: async (entity, key, meta) => { + await entity.read(clusterName, [readableAttributeName]); + }, }, - }]; + ]; const configure: Configure[] = [setupConfigureForReporting(clusterName, readableAttributeName, {min: 0, max: '1_HOUR', change: 0}, access)]; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 9088f00446a5a..58ebbf62c379a 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,8 +1,9 @@ -import * as globalStore from './store'; import {Zcl} from 'zigbee-herdsman'; -import {Definition, Expose, Fz, KeyValue, KeyValueAny, Publish, Tz, Zh} from './types'; + import {Feature, Light, Numeric} from './exposes'; import {logger} from './logger'; +import * as globalStore from './store'; +import {Definition, Expose, Fz, KeyValue, KeyValueAny, Publish, Tz, Zh} from './types'; const NS = 'zhc:utils'; @@ -15,9 +16,11 @@ export function precisionRound(number: number, precision: number): 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); + const thresholds = Object.keys(precision) + .map(Number) + .sort((a, b) => b - a); for (const t of thresholds) { - if (! isNaN(t) && number >= t) { + if (!isNaN(t) && number >= t) { return precisionRound(number, precision[t]); } } @@ -28,19 +31,28 @@ export function precisionRound(number: number, precision: number): number { export function toLocalISOString(dDate: Date) { const tzOffset = -dDate.getTimezoneOffset(); const plusOrMinus = tzOffset >= 0 ? '+' : '-'; - const pad = function(num: number) { + const pad = function (num: number) { 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); + 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) + ); } export function numberWithinRange(number: number, min: number, max: number) { @@ -64,13 +76,13 @@ export function numberWithinRange(number: number, min: number, max: number) { * @param number - of decimal places to which result should be rounded * @returns value mapped to new range */ -export function mapNumberRange(value: number, fromLow: number, fromHigh: number, toLow: number, toHigh: number, precision=0): number { - const mappedValue = toLow + (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow); +export function mapNumberRange(value: number, fromLow: number, fromHigh: number, toLow: number, toHigh: number, precision = 0): number { + const mappedValue = toLow + ((value - fromLow) * (toHigh - toLow)) / (fromHigh - fromLow); return precisionRound(mappedValue, precision); } const transactionStore: {[s: string]: number[]} = {}; -export function hasAlreadyProcessedMessage(msg: Fz.Message, model: Definition, ID: number=null, key: string=null) { +export function hasAlreadyProcessedMessage(msg: Fz.Message, model: Definition, ID: number = null, key: string = null) { if (model.meta && model.meta.publishDuplicateTransaction) return false; const currentID = ID !== null ? ID : msg.meta.zclTransactionSequenceNumber; key = key || msg.device.ieeeAddr; @@ -81,35 +93,57 @@ export function hasAlreadyProcessedMessage(msg: Fz.Message, model: Definition, I } export const calibrateAndPrecisionRoundOptionsDefaultPrecision: KeyValue = { - 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, + 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, }; export function calibrateAndPrecisionRoundOptionsIsPercentual(type: string) { - return type.startsWith('current') || type.startsWith('energy') || type.startsWith('voltage') || type.startsWith('power') || - type.startsWith('illuminance'); + return ( + type.startsWith('current') || + type.startsWith('energy') || + type.startsWith('voltage') || + type.startsWith('power') || + type.startsWith('illuminance') + ); } export function calibrateAndPrecisionRoundOptions(number: number, options: KeyValue, type: string) { // Calibrate const calibrateKey = `${type}_calibration`; - let calibrationOffset = toNumber( - options && options.hasOwnProperty(calibrateKey) ? options[calibrateKey] : 0, calibrateKey); + 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; + calibrationOffset = (number * calibrationOffset) / 100; } number = number + calibrationOffset; // Precision round const precisionKey = `${type}_precision`; const defaultValue = calibrateAndPrecisionRoundOptionsDefaultPrecision[type] || 0; - const precision = toNumber( - options && options.hasOwnProperty(precisionKey) ? options[precisionKey] : defaultValue, precisionKey); + const precision = toNumber(options && options.hasOwnProperty(precisionKey) ? options[precisionKey] : defaultValue, precisionKey); return precisionRound(number, precision); } -export function toPercentage(value: number, min: number, max: number, log=false) { +export function toPercentage(value: number, min: number, max: number, log = false) { if (value > max) { value = max; } else if (value < min) { @@ -142,10 +176,12 @@ export function postfixWithEndpointName(value: string, msg: Fz.Message, definiti 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; + 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. @@ -167,7 +203,7 @@ export function enforceEndpoint(entity: Zh.Endpoint, key: string, meta: Tz.Meta) export function getKey(object: {[s: string]: T} | {[s: number]: T}, value: T, fallback?: T, convertTo?: (v: unknown) => T) { for (const key in object) { // @ts-expect-error - if (object[key]===value) { + if (object[key] === value) { return convertTo ? convertTo(key) : key; } } @@ -175,7 +211,7 @@ export function getKey(object: {[s: string]: T} | {[s: number]: T}, value: T, return fallback; } -export function batteryVoltageToPercentage(voltage: number, option: string | {min: number, max: number}) { +export function batteryVoltageToPercentage(voltage: number, option: string | {min: number; max: number}) { let percentage = null; if (option === '3V_2100') { if (voltage < 2100) { @@ -214,8 +250,8 @@ export function batteryVoltageToPercentage(voltage: number, option: string | {mi } 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}}} + // 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 { @@ -227,8 +263,13 @@ export function batteryVoltageToPercentage(voltage: number, option: string | {mi // groupStrategy: allEqual: return only if all members in the groups have the same meta property value. // first: return the first property -export function getMetaValue(entity: Zh.Group | Zh.Endpoint, definition: Definition | Definition[], key: string, - groupStrategy='first', defaultValue: T=undefined): T { +export function getMetaValue( + entity: Zh.Group | Zh.Endpoint, + definition: Definition | Definition[], + key: string, + groupStrategy = 'first', + defaultValue: T = undefined, +): T { if (isGroup(entity) && entity.members.length > 0) { const values = []; for (let i = 0; i < entity.members.length; i++) { @@ -246,7 +287,7 @@ export function getMetaValue(entity: Zh.Group | Zh.Endpoint, definition: Defi } } - if (groupStrategy === 'allEqual' && (new Set(values)).size === 1) { + if (groupStrategy === 'allEqual' && new Set(values).size === 1) { // @ts-expect-error return values[0]; } @@ -275,7 +316,7 @@ export function isInRange(min: number, max: number, value: number) { return value >= min && value <= max; } -export function replaceInArray(arr: T[], oldElements: T[], newElements: T[], errorIfNotInArray=true) { +export function replaceInArray(arr: T[], oldElements: T[], newElements: T[], errorIfNotInArray = true) { const clone = [...arr]; for (let i = 0; i < oldElements.length; i++) { const index = clone.indexOf(oldElements[i]); @@ -318,7 +359,10 @@ export function toSnakeCase(value: string | KeyValueAny) { } return value; } else { - return value.replace(/\.?([A-Z])/g, (x, y) => '_' + y.toLowerCase()).replace(/^_/, '').replace('_i_d', '_id'); + return value + .replace(/\.?([A-Z])/g, (x, y) => '_' + y.toLowerCase()) + .replace(/^_/, '') + .replace('_i_d', '_id'); } } @@ -351,7 +395,7 @@ export function saveSceneState(entity: Zh.Endpoint, sceneID: number, groupID: nu entity.save(); } -export function deleteSceneState(entity: Zh.Endpoint, sceneID: number=null, groupID: number=null) { +export function deleteSceneState(entity: Zh.Endpoint, sceneID: number = null, groupID: number = null) { if (entity.meta.scenes) { if (sceneID == null && groupID == null) { entity.meta.scenes = {}; @@ -415,12 +459,12 @@ export function getTransition(entity: Zh.Endpoint | Zh.Group, key: string, meta: } } -export function getOptions(definition: Definition | Definition[], entity: Zh.Endpoint | Zh.Group, options={}) { +export function getOptions(definition: Definition | Definition[], entity: Zh.Endpoint | Zh.Group, options = {}) { const allowed = ['disableDefaultResponse', 'timeout']; return getMetaValues(definition, entity, allowed, options); } -export function getMetaValues(definitions: Definition | Definition[], entity: Zh.Endpoint | Zh.Group, allowed?: string[], options={}) { +export function getMetaValues(definitions: Definition | Definition[], entity: Zh.Endpoint | Zh.Group, allowed?: string[], options = {}) { const result: KeyValue = {...options}; for (const definition of Array.isArray(definitions) ? definitions : [definitions]) { if (definition && definition.meta) { @@ -465,9 +509,9 @@ export async function getClusterAttributeValue(endpoint: Zh.Endpoint, cluster } export function normalizeCelsiusVersionOfFahrenheit(value: number) { - const fahrenheit = (value * 1.8) + 32; + 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)); + return Number(((roundedFahrenheit - 32) / 1.8).toFixed(2)); } export function noOccupancySince(endpoint: Zh.Endpoint, options: KeyValueAny, publish: Publish, action: 'start' | 'stop') { @@ -508,7 +552,7 @@ export function printNumbersAsHexSequence(numbers: number[], hexLength: number): return numbers.map((v) => v.toString(16).padStart(hexLength, '0')).join(':'); } -// eslint-disable-next-line +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function assertObject(value: unknown, property?: string): asserts value is {[s: string]: any} { const isObject = typeof value === 'object' && !Array.isArray(value) && value !== null; if (!isObject) { @@ -530,7 +574,7 @@ export function isNumber(value: unknown): value is number { return typeof value === 'number'; } -// eslint-disable-next-line +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function isObject(value: unknown): value is {[s: string]: any} { return typeof value === 'object' && !Array.isArray(value); } @@ -558,7 +602,7 @@ export function toNumber(value: unknown, property?: string): number { return result; } -export function getFromLookup(value: unknown, lookup: {[s: number | string]: V}, defaultValue: V=undefined, keyIsBool: boolean=false): V { +export function getFromLookup(value: unknown, lookup: {[s: number | string]: V}, defaultValue: V = undefined, keyIsBool: boolean = false): V { let result = undefined; if (!keyIsBool) { if (typeof value === 'string') { @@ -572,7 +616,7 @@ export function getFromLookup(value: unknown, lookup: {[s: number | string]: // 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()]); + result = lookup[stringValue] ?? lookup[stringValue.toLowerCase()] ?? lookup[stringValue.toUpperCase()]; } else { throw new Error(`Expected boolean, got: ${typeof value}`); } @@ -583,7 +627,7 @@ export function getFromLookup(value: unknown, lookup: {[s: number | string]: return result ?? defaultValue; } -export function getFromLookupByValue(value: unknown, lookup: {[s: string]: unknown}, defaultValue: string=undefined): string { +export function getFromLookupByValue(value: unknown, lookup: {[s: string]: unknown}, defaultValue: string = undefined): string { for (const entry of Object.entries(lookup)) { if (entry[1] === value) { return entry[0]; diff --git a/src/lib/zosung.ts b/src/lib/zosung.ts index 4da4ae28eae93..986aab4b2ad0e 100644 --- a/src/lib/zosung.ts +++ b/src/lib/zosung.ts @@ -13,8 +13,8 @@ function nextSeq(entity: Zh.Endpoint | Zh.Group) { function messagesGet(entity: Zh.Endpoint | Zh.Group, seq: number) { const info = globalStore.getValue(entity, 'irMessageInfo'); - const expected = (info&&info.seq) || 0; - if (expected!==seq) { + const expected = (info && info.seq) || 0; + if (expected !== seq) { throw new Error(`Unexpected sequence value (expected: ${expected} current: ${seq}).`); } return info.data; @@ -25,22 +25,26 @@ function messagesSet(entity: Zh.Endpoint | Zh.Group, seq: number, data: unknown) function messagesClear(entity: Zh.Endpoint | Zh.Group, seq: number) { const info = globalStore.getValue(entity, 'irMessageInfo'); - const expected = (info&&info.seq) || 0; - if (expected!==seq) { + const expected = (info && info.seq) || 0; + if (expected !== seq) { throw new Error(`Unexpected sequence value (expected: ${expected} current: ${seq}).`); } globalStore.clearValue(entity, 'irMessageInfo'); } function calcArrayCrc(values: number[]) { - return Array.from(values.values()).reduce((a, b)=>a+b, 0)%0x100; + return Array.from(values.values()).reduce((a, b) => a + b, 0) % 0x100; } function calcStringCrc(str: string) { - return str.split('').map((x)=>x.charCodeAt(0)).reduce((a, b)=>a+b, 0)%0x100; + return ( + str + .split('') + .map((x) => x.charCodeAt(0)) + .reduce((a, b) => a + b, 0) % 0x100 + ); } - export const fzZosung = { zosung_send_ir_code_01: { cluster: 'zosungIRTransmit', @@ -60,9 +64,11 @@ export const fzZosung = { const seq = msg.data.seq; const position = msg.data.position; const irMsg = messagesGet(msg.endpoint, seq); - const part = irMsg.substring(position, position+0x32); + const part = irMsg.substring(position, position + 0x32); const sum = calcStringCrc(part); - await msg.endpoint.command('zosungIRTransmit', 'zosungSendIRCode03', + await msg.endpoint.command( + 'zosungIRTransmit', + 'zosungSendIRCode03', { zero: 0, seq: seq, @@ -70,7 +76,8 @@ export const fzZosung = { msgpart: Buffer.from(part), msgpartcrc: sum, }, - {disableDefaultResponse: true}); + {disableDefaultResponse: true}, + ); logger.debug(`Sent IRCode part: ${part} (sum: ${sum}, seq:${seq})`, NS); }, } satisfies Fz.Converter, @@ -80,12 +87,15 @@ export const fzZosung = { convert: async (model, msg, publish, options, meta) => { logger.debug(`"IR-Message-Code04" received (msg:${JSON.stringify(msg.data)})`, NS); const seq = msg.data.seq; - await msg.endpoint.command('zosungIRTransmit', 'zosungSendIRCode05', + await msg.endpoint.command( + 'zosungIRTransmit', + 'zosungSendIRCode05', { seq: seq, zero: 0, }, - {disableDefaultResponse: true}); + {disableDefaultResponse: true}, + ); messagesClear(msg.endpoint, seq); logger.debug(`IRCode has been successfully sent. (seq:${seq})`, NS); }, @@ -98,7 +108,9 @@ export const fzZosung = { const seq = msg.data.seq; const length = msg.data.length; messagesSet(msg.endpoint, seq, {position: 0, buf: Buffer.alloc(length)}); - await msg.endpoint.command('zosungIRTransmit', 'zosungSendIRCode01', + await msg.endpoint.command( + 'zosungIRTransmit', + 'zosungSendIRCode01', { zero: 0, seq: seq, @@ -109,15 +121,19 @@ export const fzZosung = { cmd: msg.data.cmd, unk4: msg.data.unk4, }, - {disableDefaultResponse: true}); + {disableDefaultResponse: true}, + ); logger.debug(`"IR-Message-Code00" response sent.`, NS); - await msg.endpoint.command('zosungIRTransmit', 'zosungSendIRCode02', + await msg.endpoint.command( + 'zosungIRTransmit', + 'zosungSendIRCode02', { seq: msg.data.seq, position: 0, maxlen: 0x38, }, - {disableDefaultResponse: true}); + {disableDefaultResponse: true}, + ); logger.debug(`"IR-Message-Code00" transfer started.`, NS); }, } satisfies Fz.Converter, @@ -128,29 +144,35 @@ export const fzZosung = { logger.debug(`"IR-Message-Code03" received (msg:${JSON.stringify(msg.data)})`, NS); const seq = msg.data.seq; const rcv = messagesGet(msg.endpoint, seq); - if (rcv.position==msg.data.position) { + if (rcv.position == msg.data.position) { const rcvMsgPart = msg.data.msgpart; const sum = calcArrayCrc(rcvMsgPart); const expectedPartCrc = msg.data.msgpartcrc; - if (sum==expectedPartCrc) { + if (sum == expectedPartCrc) { const position = rcvMsgPart.copy(rcv.buf, rcv.position); rcv.position += position; - if (rcv.position { logger.debug(`Starting IR Code Learning...`, NS); - await entity.command('zosungIRControl', 'zosungControlIRCommand00', + await entity.command( + 'zosungIRControl', + 'zosungControlIRCommand00', { - data: Buffer.from(JSON.stringify({'study': 0})), + data: Buffer.from(JSON.stringify({study: 0})), }, - {disableDefaultResponse: true}); + {disableDefaultResponse: true}, + ); logger.debug(`IR Code Learning started.`, NS); }, } satisfies Tz.Converter, }; - export const presetsZosung = { learn_ir_code: () => e.switch_().withState('learn_ir_code', false, 'Turn on to learn new IR code', ea.SET), learned_ir_code: () => e.text('learned_ir_code', ea.STATE).withDescription('The IR code learned by device'), diff --git a/test/color.test.js b/test/color.test.js index 1d7cfc9e75841..5de64d97970b2 100644 --- a/test/color.test.js +++ b/test/color.test.js @@ -2,9 +2,7 @@ const libColor = require('../src/lib/color'); describe('lib/color.js', () => { describe('ColorRGB', () => { - test.each([ - [{red: 0.5, green: 0.5, blue: 0.5}], - ])('.{from,to}Object() - %j', (color) => { + test.each([[{red: 0.5, green: 0.5, blue: 0.5}]])('.{from,to}Object() - %j', (color) => { expect(libColor.ColorRGB.fromObject(color).toObject()).toEqual(color); }); @@ -29,15 +27,24 @@ describe('lib/color.js', () => { }); test.each([ - [{red: 1.0, green: 1.0, blue: 1.0}, {red: 1.0, green: 1.0, blue: 1.0}], - [{red: 0.5, green: 0.5, blue: 0.5}, {red: 0.2140, green: 0.2140, blue: 0.2140}], - [{red: 0.0, green: 0.0, blue: 0.0}, {red: 0.0, green: 0.0, blue: 0.0}], + [ + {red: 1.0, green: 1.0, blue: 1.0}, + {red: 1.0, green: 1.0, blue: 1.0}, + ], + [ + {red: 0.5, green: 0.5, blue: 0.5}, + {red: 0.214, green: 0.214, blue: 0.214}, + ], + [ + {red: 0.0, green: 0.0, blue: 0.0}, + {red: 0.0, green: 0.0, blue: 0.0}, + ], ])('.gammaCorrected - %j', (input, output) => { expect(libColor.ColorRGB.fromObject(input).gammaCorrected().rounded(4).toObject()).toStrictEqual(output); }); - expect(libColor.ColorRGB.fromHex("#663399").toHEX()).toBe("#663399"); - expect(libColor.ColorRGB.fromHex("#020202").toHEX()).toBe("#020202"); + expect(libColor.ColorRGB.fromHex('#663399').toHEX()).toBe('#663399'); + expect(libColor.ColorRGB.fromHex('#020202').toHEX()).toBe('#020202'); }); describe('ColorHSV', () => { @@ -54,14 +61,23 @@ describe('lib/color.js', () => { test.each([ ...cases, - [{hue: 359.31231, saturation: 99.969123, value: 99.983131}, {hue: 359.3, saturation: 100, value: 100}] + [ + {hue: 359.31231, saturation: 99.969123, value: 99.983131}, + {hue: 359.3, saturation: 100, value: 100}, + ], ])('.{from,to}Object(), rounded - %j', (input, output) => { expect(libColor.ColorHSV.fromObject(input).rounded(1).toObject()).toStrictEqual(output || input); }); test.each([ - [{hue: 0, saturation: 100, value: 100}, {h: 0, s: 100, v: 100}], - [{hue: 0, saturation: 100}, {h: 0, s: 100}], + [ + {hue: 0, saturation: 100, value: 100}, + {h: 0, s: 100, v: 100}, + ], + [ + {hue: 0, saturation: 100}, + {h: 0, s: 100}, + ], [{hue: 0}, {h: 0}], [{saturation: 100}, {s: 100}], ])('.toObject() short - %j', (input, output) => { @@ -98,9 +114,7 @@ describe('lib/color.js', () => { }); describe('ColorXY', () => { - test.each([ - [{x: 5, y: 0.5}], - ])('.{from,to}Object() - %j', (color) => { + test.each([[{x: 5, y: 0.5}]])('.{from,to}Object() - %j', (color) => { expect(libColor.ColorXY.fromObject(color).toObject()).toStrictEqual(color); }); @@ -112,11 +126,7 @@ describe('lib/color.js', () => { expect(libColor.ColorXY.fromObject(xy).toRGB().rounded(4).toObject()).toStrictEqual(rgb, 4); }); - test.each([ - [500], - [370], - [150], - ])('.{to,from}Mireds() - %j', (mireds) => { + test.each([[500], [370], [150]])('.{to,from}Mireds() - %j', (mireds) => { const asXY = libColor.ColorXY.fromMireds(mireds); const backConv = asXY.toMireds(); const error = Math.abs(backConv - mireds); @@ -170,11 +180,7 @@ describe('lib/color.js', () => { } }); - test.each([ - [{}], - [{v: 100}], - [{unknown_property: 42}], - ])('.fromConverterArg() invalid - %j', (value) => { + test.each([[{}], [{v: 100}], [{unknown_property: 42}]])('.fromConverterArg() invalid - %j', (value) => { expect(() => libColor.Color.fromConverterArg(value)).toThrow(); }); }); diff --git a/test/fromZigbee.test.js b/test/fromZigbee.test.js index e1cc7ce4673d9..3a9d8a4d5d9e3 100644 --- a/test/fromZigbee.test.js +++ b/test/fromZigbee.test.js @@ -5,19 +5,11 @@ const fs = require('fs'); describe('converters/fromZigbee', () => { describe('tuya', () => { - const meta = {device: {ieeeAddr: "0x123456789abcdef"}}; + const meta = {device: {ieeeAddr: '0x123456789abcdef'}}; describe('wls100z_water_leak', () => { it.each([ - [ - 'water_leak', - [legacy.dpValueFromEnum(legacy.dataPoints.wlsWaterLeak, 0)], - {water_leak: true}, - ], - [ - 'no water_leak', - [legacy.dpValueFromEnum(legacy.dataPoints.wlsWaterLeak, 1)], - {water_leak: false}, - ], + ['water_leak', [legacy.dpValueFromEnum(legacy.dataPoints.wlsWaterLeak, 0)], {water_leak: true}], + ['no water_leak', [legacy.dpValueFromEnum(legacy.dataPoints.wlsWaterLeak, 1)], {water_leak: false}], [ 'water leak & battery', [ @@ -28,29 +20,17 @@ describe('converters/fromZigbee', () => { ], [ 'battery & unknown DP', - [ - legacy.dpValueFromBool(255, false), - legacy.dpValueFromIntValue(legacy.dataPoints.wlsBatteryPercentage, 75), - ], + [legacy.dpValueFromBool(255, false), legacy.dpValueFromIntValue(legacy.dataPoints.wlsBatteryPercentage, 75)], {battery: 75}, ], - ]) - ("Receives '%s' indication", (_name, dpValues, result) => { + ])("Receives '%s' indication", (_name, dpValues, result) => { expect(legacy.fromZigbee.wls100z_water_leak.convert(null, {data: {dpValues}}, null, null, meta)).toEqual(result); }); }); describe('tuya_smart_vibration_sensor', () => { it.each([ - [ - 'no contact', - [legacy.dpValueFromBool(legacy.dataPoints.state, false)], - {contact: true}, - ], - [ - 'no vibration', - [legacy.dpValueFromEnum(legacy.dataPoints.tuyaVibration, 0)], - {vibration: false}, - ], + ['no contact', [legacy.dpValueFromBool(legacy.dataPoints.state, false)], {contact: true}], + ['no vibration', [legacy.dpValueFromEnum(legacy.dataPoints.tuyaVibration, 0)], {vibration: false}], [ 'contact & vibration & battery', [ @@ -60,8 +40,7 @@ describe('converters/fromZigbee', () => { ], {contact: false, battery: 97, vibration: true}, ], - ]) - ("Receives '%s' indication", (_name, dpValues, result) => { + ])("Receives '%s' indication", (_name, dpValues, result) => { expect(legacy.fromZigbee.tuya_smart_vibration_sensor.convert(null, {data: {dpValues}}, null, null, meta)).toEqual(result); }); }); diff --git a/test/generateDefinition.test.ts b/test/generateDefinition.test.ts index 7656bfa7dfb99..3edcf5da2b499 100644 --- a/test/generateDefinition.test.ts +++ b/test/generateDefinition.test.ts @@ -1,25 +1,25 @@ -import { Definition } from '../src/lib/types'; -import fz from '../src/converters/fromZigbee' +import {Definition} from '../src/lib/types'; +import fz from '../src/converters/fromZigbee'; import * as zh from 'zigbee-herdsman/dist'; -import { repInterval } from '../src/lib/constants'; +import {repInterval} from '../src/lib/constants'; import {assertDefintion, AssertDefinitionArgs, mockDevice, reportingItem} from './utils'; -import { findByDevice, generateExternalDefinitionSource } from '../src'; +import {findByDevice, generateExternalDefinitionSource} from '../src'; import Device from 'zigbee-herdsman/dist/controller/model/device'; const assertGeneratedDefinition = async (args: AssertDefinitionArgs & {externalDefintionSource?: string}) => { const getDefinition = async (device: Device): Promise => findByDevice(device, true); - const definition = await getDefinition(args.device) + const definition = await getDefinition(args.device); expect(definition.model).toEqual(args.device.modelID); if (args.externalDefintionSource) { expect((await generateExternalDefinitionSource(args.device)).trim()).toEqual(args.externalDefintionSource.trim()); } - return await assertDefintion({findByDeviceFn: getDefinition, ...args}) -} + return await assertDefintion({findByDeviceFn: getDefinition, ...args}); +}; describe('GenerateDefinition', () => { test('empty', async () => { await assertGeneratedDefinition({ - device: mockDevice({modelID: 'empty', endpoints: [{inputClusters: [], outputClusters:[]}]}), + device: mockDevice({modelID: 'empty', endpoints: [{inputClusters: [], outputClusters: []}]}), meta: undefined, fromZigbee: [], toZigbee: [], @@ -32,7 +32,7 @@ describe('GenerateDefinition', () => { test('input(msTemperatureMeasurement),output(genIdentify)', async () => { await assertGeneratedDefinition({ - device: mockDevice({modelID: 'temp', endpoints: [{inputClusters: ['msTemperatureMeasurement'], outputClusters:['genIdentify']}]}), + device: mockDevice({modelID: 'temp', endpoints: [{inputClusters: ['msTemperatureMeasurement'], outputClusters: ['genIdentify']}]}), meta: undefined, fromZigbee: [expect.objectContaining({cluster: 'msTemperatureMeasurement'})], toZigbee: ['temperature'], @@ -40,16 +40,14 @@ describe('GenerateDefinition', () => { bind: {1: ['msTemperatureMeasurement']}, read: {1: [['msTemperatureMeasurement', ['measuredValue']]]}, configureReporting: { - 1: [ - ['msTemperatureMeasurement', [reportingItem('measuredValue', 10, repInterval.HOUR, 100)]], - ], + 1: [['msTemperatureMeasurement', [reportingItem('measuredValue', 10, repInterval.HOUR, 100)]]], }, }); }); test('input(msPressureMeasurement)', async () => { await assertGeneratedDefinition({ - device: mockDevice({modelID: 'pressure', endpoints: [{inputClusters: ['msPressureMeasurement'], outputClusters:[]}]}), + device: mockDevice({modelID: 'pressure', endpoints: [{inputClusters: ['msPressureMeasurement'], outputClusters: []}]}), meta: undefined, fromZigbee: [expect.objectContaining({cluster: 'msPressureMeasurement'})], toZigbee: ['pressure'], @@ -57,16 +55,14 @@ describe('GenerateDefinition', () => { bind: {1: ['msPressureMeasurement']}, read: {1: [['msPressureMeasurement', ['measuredValue']]]}, configureReporting: { - 1: [ - ['msPressureMeasurement', [reportingItem('measuredValue', 10, repInterval.HOUR, 50)]], - ], + 1: [['msPressureMeasurement', [reportingItem('measuredValue', 10, repInterval.HOUR, 50)]]], }, }); }); test('input(msRelativeHumidity)', async () => { await assertGeneratedDefinition({ - device: mockDevice({modelID: 'humidity', endpoints: [{inputClusters: ['msRelativeHumidity'], outputClusters:[]}]}), + device: mockDevice({modelID: 'humidity', endpoints: [{inputClusters: ['msRelativeHumidity'], outputClusters: []}]}), meta: undefined, fromZigbee: [expect.objectContaining({cluster: 'msRelativeHumidity'})], toZigbee: ['humidity'], @@ -74,25 +70,29 @@ describe('GenerateDefinition', () => { bind: {1: ['msRelativeHumidity']}, read: {1: [['msRelativeHumidity', ['measuredValue']]]}, configureReporting: { - 1: [ - ['msRelativeHumidity', [reportingItem('measuredValue', 10, repInterval.HOUR, 100)]], - ], + 1: [['msRelativeHumidity', [reportingItem('measuredValue', 10, repInterval.HOUR, 100)]]], }, }); }); test('input(msTemperatureMeasurement, genOnOff)', async () => { await assertGeneratedDefinition({ - device: mockDevice({modelID: 'combo', manufacturerName:'vendor', endpoints: [{inputClusters: ['msTemperatureMeasurement', 'genOnOff'], outputClusters:[]}]}), + device: mockDevice({ + modelID: 'combo', + manufacturerName: 'vendor', + endpoints: [{inputClusters: ['msTemperatureMeasurement', 'genOnOff'], outputClusters: []}], + }), meta: undefined, fromZigbee: [expect.objectContaining({cluster: 'msTemperatureMeasurement'}), fz.on_off], toZigbee: ['temperature', 'state', 'on_time', 'off_wait_time'], exposes: ['linkquality', 'switch(state)', 'temperature'], bind: {1: ['msTemperatureMeasurement', 'genOnOff']}, - read: {1: [ - ['msTemperatureMeasurement', ['measuredValue']], - ['genOnOff', ['onOff']], - ]}, + read: { + 1: [ + ['msTemperatureMeasurement', ['measuredValue']], + ['genOnOff', ['onOff']], + ], + }, configureReporting: { 1: [ ['msTemperatureMeasurement', [reportingItem('measuredValue', 10, repInterval.HOUR, 100)]], @@ -112,22 +112,28 @@ const definition = { }; module.exports = definition; - ` + `, }); }); test('input(msTemperatureMeasurement_2, genOnOff_2)', async () => { await assertGeneratedDefinition({ - device: mockDevice({modelID: 'combo', manufacturerName:'vendor', endpoints: [{ID: 2, inputClusters: ['msTemperatureMeasurement', 'genOnOff'], outputClusters:[]}]}), + device: mockDevice({ + modelID: 'combo', + manufacturerName: 'vendor', + endpoints: [{ID: 2, inputClusters: ['msTemperatureMeasurement', 'genOnOff'], outputClusters: []}], + }), meta: undefined, fromZigbee: [expect.objectContaining({cluster: 'msTemperatureMeasurement'}), fz.on_off], toZigbee: ['temperature', 'state', 'on_time', 'off_wait_time'], exposes: ['linkquality', 'switch(state)', 'temperature'], bind: {2: ['msTemperatureMeasurement', 'genOnOff']}, - read: {2: [ - ['msTemperatureMeasurement', ['measuredValue']], - ['genOnOff', ['onOff']], - ]}, + read: { + 2: [ + ['msTemperatureMeasurement', ['measuredValue']], + ['genOnOff', ['onOff']], + ], + }, configureReporting: { 2: [ ['msTemperatureMeasurement', [reportingItem('measuredValue', 10, repInterval.HOUR, 100)]], @@ -147,18 +153,21 @@ const definition = { }; module.exports = definition; - ` + `, }); }); test('input(msTemperatureMeasurement, genOnOff, msTemperatureMeasurement)', async () => { await assertGeneratedDefinition({ - device: mockDevice({modelID: 'combo', endpoints: [ - {inputClusters: ['msTemperatureMeasurement', 'genOnOff'], outputClusters:[]}, - {ID: 2, inputClusters: ['msTemperatureMeasurement'], outputClusters: []}, - ]}), + device: mockDevice({ + modelID: 'combo', + endpoints: [ + {inputClusters: ['msTemperatureMeasurement', 'genOnOff'], outputClusters: []}, + {ID: 2, inputClusters: ['msTemperatureMeasurement'], outputClusters: []}, + ], + }), meta: {multiEndpoint: true}, - endpoints: {"1":1,"2":2}, + endpoints: {'1': 1, '2': 2}, fromZigbee: [expect.objectContaining({cluster: 'msTemperatureMeasurement'}), fz.on_off], toZigbee: ['temperature', 'state', 'on_time', 'off_wait_time'], exposes: ['linkquality', 'switch(state)', 'temperature', 'temperature'], @@ -168,18 +177,14 @@ module.exports = definition; ['msTemperatureMeasurement', ['measuredValue']], ['genOnOff', ['onOff']], ], - 2: [ - ['msTemperatureMeasurement', ['measuredValue']], - ] + 2: [['msTemperatureMeasurement', ['measuredValue']]], }, configureReporting: { 1: [ ['msTemperatureMeasurement', [reportingItem('measuredValue', 10, repInterval.HOUR, 100)]], ['genOnOff', [reportingItem('onOff', 0, repInterval.MAX, 1)]], ], - 2: [ - ['msTemperatureMeasurement', [reportingItem('measuredValue', 10, repInterval.HOUR, 100)]], - ], + 2: [['msTemperatureMeasurement', [reportingItem('measuredValue', 10, repInterval.HOUR, 100)]]], }, externalDefintionSource: ` const {deviceEndpoints, temperature, onOff} = require('zigbee-herdsman-converters/lib/modernExtend'); @@ -194,32 +199,61 @@ const definition = { }; module.exports = definition; - ` + `, }); }); test('input(genOnOff, lightingColorCtrl)', async () => { - const attributes = {lightingColorCtrl: { - colorCapabilities: 254, - colorTempPhysicalMin: 100, - colorTempPhysicalMax: 500, - }}; + const attributes = { + lightingColorCtrl: { + colorCapabilities: 254, + colorTempPhysicalMin: 100, + colorTempPhysicalMax: 500, + }, + }; await assertGeneratedDefinition({ - device: mockDevice({modelID: 'combo', endpoints: [{inputClusters: ['genOnOff', 'lightingColorCtrl'], outputClusters:[], attributes}]}), + device: mockDevice({modelID: 'combo', endpoints: [{inputClusters: ['genOnOff', 'lightingColorCtrl'], outputClusters: [], attributes}]}), meta: {}, fromZigbee: [fz.on_off, fz.brightness, fz.ignore_basic_report, fz.level_config, fz.color_colortemp, fz.power_on_behavior], toZigbee: [ - 'state', 'brightness', 'brightness_percent', 'on_time', 'transition', 'level_config', 'rate', 'brightness_move', 'brightness_move_onoff', - 'brightness_step', 'brightness_step_onoff', 'color', 'color_temp', 'color_temp_percent', 'color_mode', 'color_options', 'colortemp_move', 'color_temp_move', - 'color_temp_step', 'color_temp_startup', 'hue_move', 'saturation_move', 'hue_step', 'saturation_step', 'effect', 'alert', 'flash', 'power_on_behavior', + 'state', + 'brightness', + 'brightness_percent', + 'on_time', + 'transition', + 'level_config', + 'rate', + 'brightness_move', + 'brightness_move_onoff', + 'brightness_step', + 'brightness_step_onoff', + 'color', + 'color_temp', + 'color_temp_percent', + 'color_mode', + 'color_options', + 'colortemp_move', + 'color_temp_move', + 'color_temp_step', + 'color_temp_startup', + 'hue_move', + 'saturation_move', + 'hue_step', + 'saturation_step', + 'effect', + 'alert', + 'flash', + 'power_on_behavior', ], exposes: ['effect', 'light(state,brightness,color_temp,color_temp_startup,color_xy)', 'linkquality', 'power_on_behavior'], bind: {}, - read: {1: [ - ['lightingColorCtrl', ['colorCapabilities']], - ['lightingColorCtrl', ['colorTempPhysicalMin', 'colorTempPhysicalMax']], - ]}, + read: { + 1: [ + ['lightingColorCtrl', ['colorCapabilities']], + ['lightingColorCtrl', ['colorTempPhysicalMin', 'colorTempPhysicalMax']], + ], + }, configureReporting: {}, externalDefintionSource: ` const {light} = require('zigbee-herdsman-converters/lib/modernExtend'); @@ -234,32 +268,61 @@ const definition = { }; module.exports = definition; - ` + `, }); }); test('light with color and color temperature', async () => { - const attributes = {lightingColorCtrl: { - colorCapabilities: 254, - colorTempPhysicalMin: 100, - colorTempPhysicalMax: 500, - }}; + const attributes = { + lightingColorCtrl: { + colorCapabilities: 254, + colorTempPhysicalMin: 100, + colorTempPhysicalMax: 500, + }, + }; await assertGeneratedDefinition({ - device: mockDevice({modelID: 'combo', endpoints: [{inputClusters: ['genOnOff', 'lightingColorCtrl'], outputClusters:[], attributes}]}), + device: mockDevice({modelID: 'combo', endpoints: [{inputClusters: ['genOnOff', 'lightingColorCtrl'], outputClusters: [], attributes}]}), meta: {}, fromZigbee: [fz.on_off, fz.brightness, fz.ignore_basic_report, fz.level_config, fz.color_colortemp, fz.power_on_behavior], toZigbee: [ - 'state', 'brightness', 'brightness_percent', 'on_time', 'transition', 'level_config', 'rate', 'brightness_move', 'brightness_move_onoff', - 'brightness_step', 'brightness_step_onoff', 'color', 'color_temp', 'color_temp_percent', 'color_mode', 'color_options', 'colortemp_move', 'color_temp_move', - 'color_temp_step', 'color_temp_startup', 'hue_move', 'saturation_move', 'hue_step', 'saturation_step', 'effect', 'alert', 'flash', 'power_on_behavior', + 'state', + 'brightness', + 'brightness_percent', + 'on_time', + 'transition', + 'level_config', + 'rate', + 'brightness_move', + 'brightness_move_onoff', + 'brightness_step', + 'brightness_step_onoff', + 'color', + 'color_temp', + 'color_temp_percent', + 'color_mode', + 'color_options', + 'colortemp_move', + 'color_temp_move', + 'color_temp_step', + 'color_temp_startup', + 'hue_move', + 'saturation_move', + 'hue_step', + 'saturation_step', + 'effect', + 'alert', + 'flash', + 'power_on_behavior', ], exposes: ['effect', 'light(state,brightness,color_temp,color_temp_startup,color_xy)', 'linkquality', 'power_on_behavior'], bind: {}, - read: {1: [ - ['lightingColorCtrl', ['colorCapabilities']], - ['lightingColorCtrl', ['colorTempPhysicalMin', 'colorTempPhysicalMax']], - ]}, + read: { + 1: [ + ['lightingColorCtrl', ['colorCapabilities']], + ['lightingColorCtrl', ['colorTempPhysicalMin', 'colorTempPhysicalMax']], + ], + }, configureReporting: {}, externalDefintionSource: ` const {light} = require('zigbee-herdsman-converters/lib/modernExtend'); @@ -274,33 +337,67 @@ const definition = { }; module.exports = definition; - ` + `, }); }); test('Philips light with color and color temperature', async () => { - const attributes = {lightingColorCtrl: { - colorCapabilities: 254, - colorTempPhysicalMin: 100, - colorTempPhysicalMax: 500, - }}; + const attributes = { + lightingColorCtrl: { + colorCapabilities: 254, + colorTempPhysicalMin: 100, + colorTempPhysicalMax: 500, + }, + }; await assertGeneratedDefinition({ - device: mockDevice({modelID: 'combo', manufacturerID: zh.Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V, endpoints: [{inputClusters: ['genOnOff', 'lightingColorCtrl'], outputClusters:[], attributes}]}), + device: mockDevice({ + modelID: 'combo', + manufacturerID: zh.Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V, + endpoints: [{inputClusters: ['genOnOff', 'lightingColorCtrl'], outputClusters: [], attributes}], + }), meta: {supportsHueAndSaturation: true, turnsOffAtBrightness1: true}, fromZigbee: [fz.on_off, fz.brightness, fz.ignore_basic_report, fz.level_config, fz.color_colortemp, fz.power_on_behavior], toZigbee: [ - 'state', 'brightness', 'brightness_percent', 'on_time', 'transition', 'level_config', 'rate', 'brightness_move', 'brightness_move_onoff', - 'brightness_step', 'brightness_step_onoff', 'color', 'color_temp', 'color_temp_percent', 'color_mode', 'color_options', 'colortemp_move', - 'color_temp_move', 'color_temp_step', 'color_temp_startup', 'hue_move', 'saturation_move', 'hue_step', 'saturation_step', 'power_on_behavior', - 'hue_power_on_behavior', 'hue_power_on_brightness', 'hue_power_on_color_temperature', 'hue_power_on_color', 'effect', + 'state', + 'brightness', + 'brightness_percent', + 'on_time', + 'transition', + 'level_config', + 'rate', + 'brightness_move', + 'brightness_move_onoff', + 'brightness_step', + 'brightness_step_onoff', + 'color', + 'color_temp', + 'color_temp_percent', + 'color_mode', + 'color_options', + 'colortemp_move', + 'color_temp_move', + 'color_temp_step', + 'color_temp_startup', + 'hue_move', + 'saturation_move', + 'hue_step', + 'saturation_step', + 'power_on_behavior', + 'hue_power_on_behavior', + 'hue_power_on_brightness', + 'hue_power_on_color_temperature', + 'hue_power_on_color', + 'effect', ], exposes: ['effect', 'light(state,brightness,color_temp,color_temp_startup,color_xy,color_hs)', 'linkquality', 'power_on_behavior'], bind: {}, - read: {1: [ - ['lightingColorCtrl', ['colorCapabilities']], - ['lightingColorCtrl', ['colorTempPhysicalMin', 'colorTempPhysicalMax']], - ]}, + read: { + 1: [ + ['lightingColorCtrl', ['colorCapabilities']], + ['lightingColorCtrl', ['colorTempPhysicalMin', 'colorTempPhysicalMax']], + ], + }, configureReporting: [], externalDefintionSource: ` const {philipsLight} = require('zigbee-herdsman-converters/lib/philips'); @@ -315,44 +412,61 @@ const definition = { }; module.exports = definition; - ` + `, }); }); test('Electricity meter', async () => { const attributes = { haElectricalMeasurement: { - acPowerDivisor: 1000, acPowerMultiplier: 1, - acCurrentDivisor: 1000, acCurrentMultiplier: 1, - acVoltageDivisor: 1000, acVoltageMultiplier: 1, + acPowerDivisor: 1000, + acPowerMultiplier: 1, + acCurrentDivisor: 1000, + acCurrentMultiplier: 1, + acVoltageDivisor: 1000, + acVoltageMultiplier: 1, }, seMetering: { - divisor: 1000, multiplier: 1, - } + divisor: 1000, + multiplier: 1, + }, }; await assertGeneratedDefinition({ - device: mockDevice({modelID: 'combo', endpoints: [{inputClusters: ['genOnOff', 'seMetering', 'haElectricalMeasurement'], outputClusters:[], attributes}]}), + device: mockDevice({ + modelID: 'combo', + endpoints: [{inputClusters: ['genOnOff', 'seMetering', 'haElectricalMeasurement'], outputClusters: [], attributes}], + }), meta: undefined, fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering], toZigbee: ['state', 'on_time', 'off_wait_time', 'power', 'voltage', 'current', 'energy'], exposes: ['current', 'energy', 'linkquality', 'power', 'switch(state)', 'voltage'], bind: {1: ['genOnOff', 'haElectricalMeasurement', 'seMetering']}, - read: {1: [ - ['genOnOff', ['onOff']], - ['haElectricalMeasurement', ['acPowerDivisor', 'acPowerMultiplier']], - ['haElectricalMeasurement', ['acCurrentDivisor', 'acCurrentMultiplier']], - ['haElectricalMeasurement', ['acVoltageDivisor', 'acVoltageMultiplier']], - ['haElectricalMeasurement', ['activePower', 'rmsCurrent', 'rmsVoltage']], - ['seMetering', ['divisor', 'multiplier']], - ['seMetering', ['currentSummDelivered']], - - ]}, - configureReporting: {1: [ - ['genOnOff', [reportingItem('onOff', 0, repInterval.MAX, 1)]], - ['haElectricalMeasurement', [reportingItem('activePower', 10, 65000, 5000), reportingItem('rmsCurrent', 10, 65000, 50), reportingItem('rmsVoltage', 10, 65000, 5000)]], - ['seMetering', [reportingItem('currentSummDelivered', 10, 65000, [0, 100])]], - ]}, + read: { + 1: [ + ['genOnOff', ['onOff']], + ['haElectricalMeasurement', ['acPowerDivisor', 'acPowerMultiplier']], + ['haElectricalMeasurement', ['acCurrentDivisor', 'acCurrentMultiplier']], + ['haElectricalMeasurement', ['acVoltageDivisor', 'acVoltageMultiplier']], + ['haElectricalMeasurement', ['activePower', 'rmsCurrent', 'rmsVoltage']], + ['seMetering', ['divisor', 'multiplier']], + ['seMetering', ['currentSummDelivered']], + ], + }, + configureReporting: { + 1: [ + ['genOnOff', [reportingItem('onOff', 0, repInterval.MAX, 1)]], + [ + 'haElectricalMeasurement', + [ + reportingItem('activePower', 10, 65000, 5000), + reportingItem('rmsCurrent', 10, 65000, 50), + reportingItem('rmsVoltage', 10, 65000, 5000), + ], + ], + ['seMetering', [reportingItem('currentSummDelivered', 10, 65000, [0, 100])]], + ], + }, externalDefintionSource: ` const {onOff, electricityMeter} = require('zigbee-herdsman-converters/lib/modernExtend'); @@ -366,7 +480,7 @@ const definition = { }; module.exports = definition; - ` + `, }); }); -}); \ No newline at end of file +}); diff --git a/test/index.test.js b/test/index.test.js index 69619d75e8c14..802fa2c348b9a 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -7,10 +7,10 @@ const equals = require('fast-deep-equal/es6'); const fs = require('fs'); const path = require('path'); -function containsOnly(array1, array2){ +function containsOnly(array1, array2) { for (const elem of array2) { if (!array1.includes(elem)) { - throw new Error(`Contains '${elem}' while it should only contains: '${array1}'`) + throw new Error(`Contains '${elem}' while it should only contains: '${array1}'`); } } @@ -44,10 +44,8 @@ describe('index.js', () => { expect(definition.model).toBe('XBee'); }); - it('Find by device shouldn\'t match when modelID is null and there is no fingerprint match', async () => { - const endpoints = [ - {ID: 1, profileID: undefined, deviceID: undefined, inputClusters: [], outputClusters: []}, - ]; + it("Find by device shouldn't match when modelID is null and there is no fingerprint match", async () => { + const endpoints = [{ID: 1, profileID: undefined, deviceID: undefined, inputClusters: [], outputClusters: []}]; const device = { type: undefined, manufacturerID: undefined, @@ -63,12 +61,14 @@ describe('index.js', () => { it('Find by device should generate for unknown', async () => { const endpoints = [ { - ID: 1, profileID: undefined, deviceID: undefined, + ID: 1, + profileID: undefined, + deviceID: undefined, getInputClusters() { return []; }, getOutputClusters() { - return [{name: 'genIdentify'}] + return [{name: 'genIdentify'}]; }, }, ]; @@ -92,19 +92,17 @@ describe('index.js', () => { }); it('Find by device when device has modelID should match', async () => { - const endpoints = [ - {ID: 1, profileID: undefined, deviceID: undefined, inputClusters: [], outputClusters: []}, - ]; + const endpoints = [{ID: 1, profileID: undefined, deviceID: undefined, inputClusters: [], outputClusters: []}]; const device = { type: undefined, manufacturerID: undefined, - modelID: "lumi.sensor_motion", + modelID: 'lumi.sensor_motion', endpoints, getEndpoint: (ID) => endpoints.find((e) => e.ID === ID), }; const definition = await index.findByDevice(device); - expect(definition.model).toBe("RTCGQ01LM"); + expect(definition.model).toBe('RTCGQ01LM'); }); it('Find by fingerprint with priority', async () => { @@ -163,9 +161,7 @@ describe('index.js', () => { it('Find by device when fingerprint has zigbeeModel of other definition', async () => { // https://github.com/Koenkk/zigbee-herdsman-converters/issues/1449 - const endpoints = [ - {ID: 1, profileID: 260, deviceID: 1026, inputClusters: [0,3,1280,1], outputClusters: [3]}, - ]; + const endpoints = [{ID: 1, profileID: 260, deviceID: 1026, inputClusters: [0, 3, 1280, 1], outputClusters: [3]}]; const device = { type: 'EndDevice', manufacturerID: 0, @@ -177,14 +173,12 @@ describe('index.js', () => { }; const definition = await index.findByDevice(device); - expect(definition.model).toBe("SNZB-04"); + expect(definition.model).toBe('SNZB-04'); }); - it('Find by device when fingerprint has zigbeeModel of other definition shouldn\'t match when fingerprint doesn\t match', async () => { + it("Find by device when fingerprint has zigbeeModel of other definition shouldn't match when fingerprint doesn\t match", async () => { // https://github.com/Koenkk/zigbee-herdsman-converters/issues/1449 - const endpoints = [ - {ID: 1, profileID: 260, deviceID: 770, inputClusters: [0,3,1026,1029,1], outputClusters: [3]}, - ]; + const endpoints = [{ID: 1, profileID: 260, deviceID: 770, inputClusters: [0, 3, 1026, 1029, 1], outputClusters: [3]}]; const device = { type: 'EndDevice', manufacturerID: 0, @@ -196,14 +190,14 @@ describe('index.js', () => { }; const definition = await index.findByDevice(device); - expect(definition.model).toBe("SNZB-02"); + expect(definition.model).toBe('SNZB-02'); }); it('Verify definitions', () => { function verifyKeys(expected, actual, id) { expected.forEach((key) => { if (!actual.includes(key)) { - throw new Error(`${id}: missing key '${key}'`) + throw new Error(`${id}: missing key '${key}'`); } }); } @@ -214,11 +208,7 @@ describe('index.js', () => { index.definitions.forEach((device) => { // Verify device attributes. - verifyKeys( - ['model', 'vendor', 'description', 'fromZigbee', 'toZigbee', 'exposes'], - Object.keys(device), - device.model, - ); + verifyKeys(['model', 'vendor', 'description', 'fromZigbee', 'toZigbee', 'exposes'], Object.keys(device), device.model); if (!device.hasOwnProperty('zigbeeModel') && !device.hasOwnProperty('fingerprint')) { throw new Error(`'${device.model}' has no zigbeeModel or fingerprint`); @@ -234,7 +224,7 @@ describe('index.js', () => { Object.keys(device.fromZigbee).forEach((converterKey) => { const converter = device.fromZigbee[converterKey]; - if(!converter) { + if (!converter) { throw new Error(`fromZigbee[${converterKey}] not defined on device ${device.model}.`); } @@ -242,7 +232,7 @@ describe('index.js', () => { verifyKeys(['cluster', 'type', 'convert'], keys, converterKey); if (5 != converter.convert.length) { - throw new Error(`${converterKey}: convert() invalid arguments length`) + throw new Error(`${converterKey}: convert() invalid arguments length`); } }); @@ -250,20 +240,16 @@ describe('index.js', () => { Object.keys(device.toZigbee).forEach((converterKey) => { const converter = device.toZigbee[converterKey]; - if(!converter) { + if (!converter) { throw new Error(`toZigbee[${converterKey}] not defined on device ${device.model}.`); } - verifyKeys( - ['key'], - Object.keys(converter), - converterKey, - ); + verifyKeys(['key'], Object.keys(converter), converterKey); expect(Array.isArray(converter.key)).toBe(true); if (converter.convertSet && 4 != converter.convertSet.length) { - throw new Error(`${converterKey}: convert() invalid arguments length`) + throw new Error(`${converterKey}: convert() invalid arguments length`); } }); @@ -271,14 +257,14 @@ describe('index.js', () => { if (device.hasOwnProperty('zigbeeModel')) { device.zigbeeModel.forEach((m) => { if (foundZigbeeModels.includes(m.toLowerCase())) { - throw new Error(`Duplicate zigbee model ${m}`) + throw new Error(`Duplicate zigbee model ${m}`); } }); } // Check for duplicate model ids if (foundModels.includes(device.model.toLowerCase())) { - throw new Error(`Duplicate model ${device.model}`) + throw new Error(`Duplicate model ${device.model}`); } // Check for duplicate foundFingerprints @@ -299,7 +285,7 @@ describe('index.js', () => { verifyKeys(['vendor', 'model'], Object.keys(whiteLabel), `whitelabel-of-${device.model}`); containsOnly(['vendor', 'model', 'description', 'fingerprint'], Object.keys(whiteLabel)); if (whiteLabel.fingerprint && foundModels.includes(whiteLabel.model.toLowerCase())) { - throw new Error(`Duplicate whitelabel zigbee model ${whiteLabel.model}`) + throw new Error(`Duplicate whitelabel zigbee model ${whiteLabel.model}`); } } } @@ -311,9 +297,36 @@ describe('index.js', () => { foundModels.push(...device.whiteLabel.filter((w) => w.fingerprint).map((w) => w.model.toLowerCase())); } - if (device.meta) { - containsOnly(['disableActionGroup', 'multiEndpoint', 'multiEndpointSkip', 'multiEndpointEnforce', 'applyRedFix', 'disableDefaultResponse', 'supportsEnhancedHue', 'timeout', 'supportsHueAndSaturation', 'battery', 'coverInverted', 'turnsOffAtBrightness1', 'coverStateFromTilt', 'pinCodeCount', 'tuyaThermostatSystemMode', 'tuyaThermostatPreset', 'tuyaDatapoints', 'tuyaThermostatPresetToSystemMode', 'thermostat', 'separateWhite', 'publishDuplicateTransaction', 'tuyaSendCommand', 'coverPositionTiltDisableReport', 'overrideHaConfig'], Object.keys(device.meta)); + containsOnly( + [ + 'disableActionGroup', + 'multiEndpoint', + 'multiEndpointSkip', + 'multiEndpointEnforce', + 'applyRedFix', + 'disableDefaultResponse', + 'supportsEnhancedHue', + 'timeout', + 'supportsHueAndSaturation', + 'battery', + 'coverInverted', + 'turnsOffAtBrightness1', + 'coverStateFromTilt', + 'pinCodeCount', + 'tuyaThermostatSystemMode', + 'tuyaThermostatPreset', + 'tuyaDatapoints', + 'tuyaThermostatPresetToSystemMode', + 'thermostat', + 'separateWhite', + 'publishDuplicateTransaction', + 'tuyaSendCommand', + 'coverPositionTiltDisableReport', + 'overrideHaConfig', + ], + Object.keys(device.meta), + ); } if (device.zigbeeModel) { @@ -328,9 +341,9 @@ describe('index.js', () => { const undefinedDevice = index.findByModel('mock-model'); expect(undefinedDevice).toBeUndefined(); const beforeAdditionDeviceCount = index.definitions.length; - expect(()=> index.addDefinition(mockDevice)).toThrow("Converter field model is undefined"); + expect(() => index.addDefinition(mockDevice)).toThrow('Converter field model is undefined'); mockDevice.model = 'mock-model'; - expect(()=> index.addDefinition(mockDevice)).toThrow("Converter field vendor is undefined"); + expect(() => index.addDefinition(mockDevice)).toThrow('Converter field vendor is undefined'); mockDevice = { model: 'mock-model', vendor: 'dummy', @@ -338,7 +351,7 @@ describe('index.js', () => { description: 'dummy', fromZigbee: [], toZigbee: [], - exposes: [] + exposes: [], }; index.addDefinition(mockDevice); expect(beforeAdditionDeviceCount + 1).toBe(index.definitions.length); @@ -357,7 +370,7 @@ describe('index.js', () => { description: '', fromZigbee: [], toZigbee: [], - exposes: [] + exposes: [], }; index.addDefinition(overwriteDefinition); expect((await index.findByDevice(device)).vendor).toBe('other-vendor'); @@ -365,58 +378,58 @@ describe('index.js', () => { it('Exposes light with endpoint', () => { const expected = { - "type":"light", - "features":[ - { - "type":"binary", - "name":"state", - "label": "State", - "description": "On/off state of this light", - "property":"state_rgb", - "access":7, - "value_on":"ON", - "value_off":"OFF", - "value_toggle":"TOGGLE", - "endpoint":"rgb" - }, - { - "type":"numeric", - "name":"brightness", - "label": "Brightness", - "description": "Brightness of this light", - "property":"brightness_rgb", - "access":7, - "value_min":0, - "value_max":254, - "endpoint":"rgb" - }, - { - "type":"composite", - "property":"color_rgb", - "name":"color_xy", - "label": "Color (X/Y)", - "description": "Color of this light in the CIE 1931 color space (x/y)", - "access":7, - "features":[ - { - "type":"numeric", - "name":"x", - "label": "X", - "property":"x", - "access":7 - }, - { - "type":"numeric", - "name":"y", - "label": "Y", - "property":"y", - "access":7 - } - ], - "endpoint":"rgb" - } + type: 'light', + features: [ + { + type: 'binary', + name: 'state', + label: 'State', + description: 'On/off state of this light', + property: 'state_rgb', + access: 7, + value_on: 'ON', + value_off: 'OFF', + value_toggle: 'TOGGLE', + endpoint: 'rgb', + }, + { + type: 'numeric', + name: 'brightness', + label: 'Brightness', + description: 'Brightness of this light', + property: 'brightness_rgb', + access: 7, + value_min: 0, + value_max: 254, + endpoint: 'rgb', + }, + { + type: 'composite', + property: 'color_rgb', + name: 'color_xy', + label: 'Color (X/Y)', + description: 'Color of this light in the CIE 1931 color space (x/y)', + access: 7, + features: [ + { + type: 'numeric', + name: 'x', + label: 'X', + property: 'x', + access: 7, + }, + { + type: 'numeric', + name: 'y', + label: 'Y', + property: 'y', + access: 7, + }, + ], + endpoint: 'rgb', + }, ], - "endpoint":"rgb" + endpoint: 'rgb', }; const actual = exposes.presets.light_brightness_colorxy().withEndpoint('rgb'); expect(expected).toStrictEqual(deepClone(actual)); @@ -432,9 +445,9 @@ describe('index.js', () => { const expss = typeof device.exposes == 'function' ? device.exposes() : device.exposes; for (const expose of expss) { if (expose.access !== undefined) { - toCheck.push(expose) + toCheck.push(expose); } else if (expose.features) { - toCheck.push(...expose.features.filter(e => e.access !== undefined)); + toCheck.push(...expose.features.filter((e) => e.access !== undefined)); } } @@ -444,7 +457,7 @@ describe('index.js', () => { property = expose.property.slice(0, (expose.endpoint.length + 1) * -1); } - const toZigbee = device.toZigbee.find(item => item.key.includes(property)); + const toZigbee = device.toZigbee.find((item) => item.key.includes(property)); if ((expose.access & exposes.access.SET) != (toZigbee && toZigbee.convertSet ? exposes.access.SET : 0)) { throw new Error(`${device.model} - ${property}, supports set: ${!!(toZigbee && toZigbee.convertSet)}`); @@ -472,12 +485,12 @@ describe('index.js', () => { endpoints: [], }; - const HG06492B_match = await index.findByDevice(HG06492B) + const HG06492B_match = await index.findByDevice(HG06492B); expect(HG06492B_match.model).toBe('HG06492B'); expect(HG06492B_match.description).toBe('Livarno Lux E14 candle CCT'); expect(HG06492B_match.vendor).toBe('Lidl'); - const TS0502A_match = await index.findByDevice(TS0502A) + const TS0502A_match = await index.findByDevice(TS0502A); expect(TS0502A_match.model).toBe('TS0502A'); expect(TS0502A_match.description).toBe('Light controller'); expect(TS0502A_match.vendor).toBe('Tuya'); @@ -487,45 +500,58 @@ describe('index.js', () => { const allowed = fs.readFileSync(path.join(__dirname, 'colortemp_range_missing_allowed.txt'), 'utf8').split('\n'); for (const definition of index.definitions) { const exposes = Array.isArray(definition.exposes) ? definition.exposes : definition.exposes(); - for (const expose of exposes.filter(e => e.type === 'light')) { - const colorTemp = expose.features.find(f => f.name === 'color_temp'); + for (const expose of exposes.filter((e) => e.type === 'light')) { + const colorTemp = expose.features.find((f) => f.name === 'color_temp'); if (colorTemp && !colorTemp._colorTempRangeProvided && !allowed.includes(definition.model)) { - throw new Error(`'${definition.model}' is missing color temp range, see https://github.com/Koenkk/zigbee2mqtt.io/blob/develop/docs/how_tos/how_to_support_new_devices.md#31-retrieving-color-temperature-range-only-required-for-lights-which-support-color-temperature`); + throw new Error( + `'${definition.model}' is missing color temp range, see https://github.com/Koenkk/zigbee2mqtt.io/blob/develop/docs/how_tos/how_to_support_new_devices.md#31-retrieving-color-temperature-range-only-required-for-lights-which-support-color-temperature`, + ); } } } }); it('Calculate configure key', () => { - const definition = {configure: () => { - console.log('hello world'); - console.log('bye world'); - }} + const definition = { + configure: () => { + console.log('hello world'); + console.log('bye world'); + }, + }; expect(index.getConfigureKey(definition)).toBe(-1738355762); }); it('Calculate configure key whitespace shouldnt matter', () => { - const definition1 = {configure: () => { - console.log('hello world'); - console.log('bye world'); - }} - - const definition2 = {configure: () => { - console.log('hello world');console.log('bye world'); - }} + const definition1 = { + configure: () => { + console.log('hello world'); + console.log('bye world'); + }, + }; + + const definition2 = { + configure: () => { + console.log('hello world'); + console.log('bye world'); + }, + }; expect(index.getConfigureKey(definition1)).toBe(index.getConfigureKey(definition2)); }); it('Calculate configure diff', () => { - const definition1 = {configure: () => { - console.log('hello world'); - console.log('bye world'); - }} - - const definition2 = {configure: () => { - console.log('hello world'); - console.log('bye mars'); - }} + const definition1 = { + configure: () => { + console.log('hello world'); + console.log('bye world'); + }, + }; + + const definition2 = { + configure: () => { + console.log('hello world'); + console.log('bye mars'); + }, + }; expect(index.getConfigureKey(definition1)).not.toBe(index.getConfigureKey(definition2)); }); @@ -569,7 +595,12 @@ describe('index.js', () => { it('Calibration/precision', () => { const TS0601_soil = index.definitions.find((d) => d.model == 'TS0601_soil'); - expect(TS0601_soil.options.map((t) => t.name)).toStrictEqual(['temperature_calibration','temperature_precision', 'soil_moisture_calibration', 'soil_moisture_precision']); + expect(TS0601_soil.options.map((t) => t.name)).toStrictEqual([ + 'temperature_calibration', + 'temperature_precision', + 'soil_moisture_calibration', + 'soil_moisture_precision', + ]); let payload = {temperature: 1.193}; let options = {temperature_calibration: 2.5, temperature_precision: 1}; index.postProcessConvertedFromZigbeeMessage(TS0601_soil, payload, options); @@ -585,8 +616,15 @@ describe('index.js', () => { const TS011F_plug_1 = index.definitions.find((d) => d.model == 'TS011F_plug_1'); expect(TS011F_plug_1.options.map((t) => t.name)).toStrictEqual([ - 'power_calibration','power_precision', 'current_calibration', 'current_precision', 'voltage_calibration', - 'voltage_precision', 'energy_calibration', 'energy_precision', 'state_action' + 'power_calibration', + 'power_precision', + 'current_calibration', + 'current_precision', + 'voltage_calibration', + 'voltage_precision', + 'energy_calibration', + 'energy_precision', + 'state_action', ]); payload = {current: 0.0585}; options = {current_calibration: -50}; @@ -597,13 +635,22 @@ describe('index.js', () => { it('Should allow definition with both modern extend and exposes as function', () => { const MOSZB140 = index.findByModel('MOSZB-140'); const exposes = MOSZB140.exposes(); - expect(exposes.map((e) => e.name)).toStrictEqual(['occupancy', 'temperature', 'tamper', 'battery_low', 'battery', 'linkquality', 'illuminance_lux', 'illuminance']); + expect(exposes.map((e) => e.name)).toStrictEqual([ + 'occupancy', + 'temperature', + 'tamper', + 'battery_low', + 'battery', + 'linkquality', + 'illuminance_lux', + 'illuminance', + ]); }); it('Check getFromLookup', () => { - expect(utils.getFromLookup('OFF', {'off': 0, 'on': 1, 'previous': 2})).toStrictEqual(0); - expect(utils.getFromLookup('On', {'off': 0, 'on': 1, 'previous': 2})).toStrictEqual(1); - expect(utils.getFromLookup('previous', {'OFF': 0, 'ON': 1, 'PREVIOUS': 2})).toStrictEqual(2); + expect(utils.getFromLookup('OFF', {off: 0, on: 1, previous: 2})).toStrictEqual(0); + expect(utils.getFromLookup('On', {off: 0, on: 1, previous: 2})).toStrictEqual(1); + expect(utils.getFromLookup('previous', {OFF: 0, ON: 1, PREVIOUS: 2})).toStrictEqual(2); expect(utils.getFromLookup(1, {0: 'OFF', 1: 'on'})).toStrictEqual('on'); }); @@ -613,12 +660,12 @@ describe('index.js', () => { const itemType = exposes.numeric('temperature', exposes.access.STATE_SET); const list = exposes.list('temperatures', exposes.access.STATE_SET, itemType); expect(JSON.parse(JSON.stringify(list))).toStrictEqual({ - "access": 3, - "item_type": {"access": 3, "name": "temperature", "label": "Temperature", "type": "numeric"}, - "name": "temperatures", - "label": "Temperatures", - "property": "temperatures", - "type": "list" + access: 3, + item_type: {access: 3, name: 'temperature', label: 'Temperature', type: 'numeric'}, + name: 'temperatures', + label: 'Temperatures', + property: 'temperatures', + type: 'list', }); }); @@ -626,10 +673,11 @@ describe('index.js', () => { // Example payload: // {"schedule": [{"day":"monday","hour":13,"minute":37}, {"day":"tuesday","hour":14,"minute":59}]} - const itemType = exposes.composite('dayTime', exposes.access.STATE_SET) + const itemType = exposes + .composite('dayTime', exposes.access.STATE_SET) .withFeature(exposes.enum('day', exposes.access.STATE_SET, ['monday', 'tuesday', 'wednesday'])) .withFeature(exposes.numeric('hour', exposes.access.STATE_SET)) - .withFeature(exposes.numeric('minute', exposes.access.STATE_SET)) + .withFeature(exposes.numeric('minute', exposes.access.STATE_SET)); const list = exposes.list('schedule', exposes.access.STATE_SET, itemType); expect(JSON.parse(JSON.stringify(list))).toStrictEqual({ @@ -644,29 +692,29 @@ describe('index.js', () => { label: 'DayTime', features: [ { - access: 3, - name: "day", - label: "Day", - property: "day", - type: "enum", + access: 3, + name: 'day', + label: 'Day', + property: 'day', + type: 'enum', values: ['monday', 'tuesday', 'wednesday'], }, { - access: 3, - name: "hour", - label: "Hour", - property: "hour", - type: "numeric", + access: 3, + name: 'hour', + label: 'Hour', + property: 'hour', + type: 'numeric', }, { - access: 3, - name: "minute", - label: "Minute", - property: "minute", - type: "numeric", + access: 3, + name: 'minute', + label: 'Minute', + property: 'minute', + type: 'numeric', }, - ] - } + ], + }, }); }); }); diff --git a/test/lumi.test.js b/test/lumi.test.js index dba3a9dfb2730..a5dcf0dc8781c 100644 --- a/test/lumi.test.js +++ b/test/lumi.test.js @@ -35,9 +35,18 @@ describe('lib/lumi', () => { describe(trv.decodeHeartbeat, () => { // Samples copied from the debug logs, e.g., Received Zigbee message from 'Thermostat1', type 'attributeReport', cluster 'manuSpecificLumi', data '{"247":{"data":[3,40,28,... - const heartbeatSetup = Buffer.from([3, 40, 28, 5, 33, 3, 0, 10, 33, 18, 126, 13, 35, 25, 9, 0, 0, 17, 35, 1, 0, 0, 0, 101, 32, 3, 102, 41, 156, 9, 103, 41, 96, 9, 104, 35, 0, 0, 0, 0, 105, 32, 100, 106, 32, 0]); - const heartbeatNormalOperation = Buffer.from([3, 40, 23, 5, 33, 4, 0, 10, 33, 7, 15, 13, 35, 25, 8, 0, 0, 17, 35, 1, 0, 0, 0, 101, 32, 0, 102, 41, 118, 7, 103, 41, 108, 7, 104, 35, 0, 0, 0, 0, 105, 32, 99, 106, 32, 0]); - const heartbeatValveAlarm = Buffer.from([3, 40, 22, 5, 33, 4, 0, 10, 33, 7, 15, 13, 35, 25, 8, 0, 0, 17, 35, 1, 0, 0, 0, 101, 32, 0, 102, 41, 98, 7, 103, 41, 244, 1, 104, 35, 1, 0, 0, 0, 105, 32, 96, 106, 32, 0]); + const heartbeatSetup = Buffer.from([ + 3, 40, 28, 5, 33, 3, 0, 10, 33, 18, 126, 13, 35, 25, 9, 0, 0, 17, 35, 1, 0, 0, 0, 101, 32, 3, 102, 41, 156, 9, 103, 41, 96, 9, 104, + 35, 0, 0, 0, 0, 105, 32, 100, 106, 32, 0, + ]); + const heartbeatNormalOperation = Buffer.from([ + 3, 40, 23, 5, 33, 4, 0, 10, 33, 7, 15, 13, 35, 25, 8, 0, 0, 17, 35, 1, 0, 0, 0, 101, 32, 0, 102, 41, 118, 7, 103, 41, 108, 7, 104, 35, + 0, 0, 0, 0, 105, 32, 99, 106, 32, 0, + ]); + const heartbeatValveAlarm = Buffer.from([ + 3, 40, 22, 5, 33, 4, 0, 10, 33, 7, 15, 13, 35, 25, 8, 0, 0, 17, 35, 1, 0, 0, 0, 101, 32, 0, 102, 41, 98, 7, 103, 41, 244, 1, 104, 35, + 1, 0, 0, 0, 105, 32, 96, 106, 32, 0, + ]); it('decodes heartbeat in setup mode', () => { const heartbeat = trv.decodeHeartbeat({}, {}, heartbeatSetup); @@ -74,9 +83,11 @@ describe('lib/lumi', () => { it('decodes valve alarm', () => { const heartbeat = trv.decodeHeartbeat({}, {}, heartbeatValveAlarm); - expect(heartbeat).toEqual(expect.objectContaining({ - valve_alarm: true, - })); + expect(heartbeat).toEqual( + expect.objectContaining({ + valve_alarm: true, + }), + ); }); }); @@ -116,97 +127,121 @@ describe('lib/lumi', () => { }); it('fails on invalid events type', () => { - expect(() => trv.validateSchedule({ - days: ['mon'], - events: 123, - })).toThrowError(/must contain an array of 4/); + expect(() => + trv.validateSchedule({ + days: ['mon'], + events: 123, + }), + ).toThrowError(/must contain an array of 4/); }); it('fails on empty events', () => { - expect(() => trv.validateSchedule({ - days: ['mon'], - events: [], - })).toThrowError(/must contain an array of 4/); + expect(() => + trv.validateSchedule({ + days: ['mon'], + events: [], + }), + ).toThrowError(/must contain an array of 4/); }); it('fails on insufficient number of events', () => { - expect(() => trv.validateSchedule({ - days: ['mon'], - events: [{}], - })).toThrowError(/must contain an array of 4/); + expect(() => + trv.validateSchedule({ + days: ['mon'], + events: [{}], + }), + ).toThrowError(/must contain an array of 4/); }); it('fails on invalid event type', () => { - expect(() => trv.validateSchedule({ - days: ['mon'], - events: [123, {}, {}, {}], - })).toThrowError(/must be an object/); + expect(() => + trv.validateSchedule({ + days: ['mon'], + events: [123, {}, {}, {}], + }), + ).toThrowError(/must be an object/); }); it('fails on missing event time', () => { - expect(() => trv.validateSchedule({ - days: ['mon'], - events: [{}, {}, {}, {}], - })).toThrowError(/Time must be a positive integer number/); + expect(() => + trv.validateSchedule({ + days: ['mon'], + events: [{}, {}, {}, {}], + }), + ).toThrowError(/Time must be a positive integer number/); }); it('fails on invalid event time type', () => { - expect(() => trv.validateSchedule({ - days: ['mon'], - events: [{time: 'foo'}, {}, {}, {}], - })).toThrowError(/Time must be a positive integer number/); + expect(() => + trv.validateSchedule({ + days: ['mon'], + events: [{time: 'foo'}, {}, {}, {}], + }), + ).toThrowError(/Time must be a positive integer number/); }); it('fails on missing event temperature', () => { - expect(() => trv.validateSchedule({ - days: ['mon'], - events: [{time: 0}, {}, {}, {}], - })).toThrowError(/must contain a numeric temperature/); + expect(() => + trv.validateSchedule({ + days: ['mon'], + events: [{time: 0}, {}, {}, {}], + }), + ).toThrowError(/must contain a numeric temperature/); }); it('fails on invalid event temperature type', () => { - expect(() => trv.validateSchedule({ - days: ['mon'], - events: [{time: 0, temperature: 'foo'}, {}, {}, {}], - })).toThrowError(/must contain a numeric temperature/); + expect(() => + trv.validateSchedule({ + days: ['mon'], + events: [{time: 0, temperature: 'foo'}, {}, {}, {}], + }), + ).toThrowError(/must contain a numeric temperature/); }); it('fails on invalid event temperature value', () => { - expect(() => trv.validateSchedule({ - days: ['mon'], - events: [{time: 0, temperature: 4}, {}, {}, {}], - })).toThrowError(/temperature must be between/); + expect(() => + trv.validateSchedule({ + days: ['mon'], + events: [{time: 0, temperature: 4}, {}, {}, {}], + }), + ).toThrowError(/temperature must be between/); }); it('fails on invalid event temperature value', () => { - expect(() => trv.validateSchedule({ - days: ['mon'], - events: [{time: 0, temperature: 30.1}, {}, {}, {}], - })).toThrowError(/temperature must be between/); + expect(() => + trv.validateSchedule({ + days: ['mon'], + events: [{time: 0, temperature: 30.1}, {}, {}, {}], + }), + ).toThrowError(/temperature must be between/); }); it('fails if any individual duration is less than 1 hour', () => { - expect(() => trv.validateSchedule({ - days: ['mon'], - events: [ - {time: 0, temperature: 5}, - {time: 59, temperature: 5}, - {time: 5 * 60, temperature: 5}, - {time: 23 * 60, temperature: 5}, - ], - })).toThrowError(/at least 1 hour apart/); + expect(() => + trv.validateSchedule({ + days: ['mon'], + events: [ + {time: 0, temperature: 5}, + {time: 59, temperature: 5}, + {time: 5 * 60, temperature: 5}, + {time: 23 * 60, temperature: 5}, + ], + }), + ).toThrowError(/at least 1 hour apart/); }); it('fails if minimum total duration is more than 24 hours', () => { - expect(() => trv.validateSchedule({ - days: ['mon'], - events: [ - {time: 8, temperature: 5}, - {time: 10 * 60, temperature: 5}, - {time: 23 * 60, temperature: 5}, - {time: 9 * 60, temperature: 5}, - ], - })).toThrowError(/at most 24 hours apart/); + expect(() => + trv.validateSchedule({ + days: ['mon'], + events: [ + {time: 8, temperature: 5}, + {time: 10 * 60, temperature: 5}, + {time: 23 * 60, temperature: 5}, + {time: 9 * 60, temperature: 5}, + ], + }), + ).toThrowError(/at most 24 hours apart/); }); }); @@ -248,19 +283,19 @@ describe('lib/lumi', () => { describe('Feeder schedule', () => { it('Schedule 0 days', () => { - const data = Buffer.from([0,5,43,8,0,8,200,2,47,47]); - const result = fromZigbee.lumi_feeder.convert(null, {data: {'65521': data}}, null, null); - expect(result).toStrictEqual({ schedule: [] }); + const data = Buffer.from([0, 5, 43, 8, 0, 8, 200, 2, 47, 47]); + const result = fromZigbee.lumi_feeder.convert(null, {data: {65521: data}}, null, null); + expect(result).toStrictEqual({schedule: []}); }); it('Schedule 1 day', () => { - const data = Buffer.from([0,5,9,8,0,8,200,10,55,70,48,49,48,49,48,49,48,48]); - const result = fromZigbee.lumi_feeder.convert(null, {data: {'65521': data}}, null, null); - expect(result).toStrictEqual({ schedule: [ { days: 'everyday', hour: 1, minute: 1, size: 1 } ] }); + const data = Buffer.from([0, 5, 9, 8, 0, 8, 200, 10, 55, 70, 48, 49, 48, 49, 48, 49, 48, 48]); + const result = fromZigbee.lumi_feeder.convert(null, {data: {65521: data}}, null, null); + expect(result).toStrictEqual({schedule: [{days: 'everyday', hour: 1, minute: 1, size: 1}]}); }); it.only('Too small frame', () => { - const data = Buffer.from([128,2,2,48]); - const result = fromZigbee.lumi_feeder.convert(null, {data: {'65521': data}}, null, null); + const data = Buffer.from([128, 2, 2, 48]); + const result = fromZigbee.lumi_feeder.convert(null, {data: {65521: data}}, null, null); expect(result).toStrictEqual({}); }); }); diff --git a/test/modernExtend.test.ts b/test/modernExtend.test.ts index cfd769c2f4da8..e34c1491ceed5 100644 --- a/test/modernExtend.test.ts +++ b/test/modernExtend.test.ts @@ -1,8 +1,8 @@ -import { repInterval } from '../src/lib/constants'; +import {repInterval} from '../src/lib/constants'; import {philipsFz} from '../src/lib/philips'; import {fromZigbee as lumiFz} from '../src/lib/lumi'; -import fz from '../src/converters/fromZigbee' -import { assertDefintion, mockDevice, reportingItem } from './utils'; +import fz from '../src/converters/fromZigbee'; +import {assertDefintion, mockDevice, reportingItem} from './utils'; describe('ModernExtend', () => { test('light({turnsOffAtBrightness1: true})', async () => { @@ -11,15 +11,27 @@ describe('ModernExtend', () => { meta: {turnsOffAtBrightness1: true}, fromZigbee: [fz.on_off, fz.brightness, fz.ignore_basic_report, fz.level_config, fz.power_on_behavior], toZigbee: [ - 'state', 'brightness', 'brightness_percent', 'on_time', 'transition', 'level_config', 'rate', 'brightness_move', 'brightness_move_onoff', - 'brightness_step', 'brightness_step_onoff', 'effect', 'alert', 'flash', 'power_on_behavior' + 'state', + 'brightness', + 'brightness_percent', + 'on_time', + 'transition', + 'level_config', + 'rate', + 'brightness_move', + 'brightness_move_onoff', + 'brightness_step', + 'brightness_step_onoff', + 'effect', + 'alert', + 'flash', + 'power_on_behavior', ], exposes: ['effect', 'light(state,brightness)', 'linkquality', 'power_on_behavior'], bind: [], read: [], configureReporting: [], }); - }); test('light({colorTemp: {range: undefined}})', async () => { @@ -28,16 +40,38 @@ describe('ModernExtend', () => { meta: {}, fromZigbee: [fz.on_off, fz.brightness, fz.ignore_basic_report, fz.level_config, fz.color_colortemp, fz.power_on_behavior], toZigbee: [ - 'state', 'brightness', 'brightness_percent', 'on_time', 'transition', 'level_config', 'rate', 'brightness_move', 'brightness_move_onoff', - 'brightness_step', 'brightness_step_onoff', 'color_temp', 'color_temp_percent', 'color_mode', 'color_options', 'colortemp_move', - 'color_temp_move', 'color_temp_step', 'color_temp_startup', 'effect', 'alert', 'flash', 'power_on_behavior' + 'state', + 'brightness', + 'brightness_percent', + 'on_time', + 'transition', + 'level_config', + 'rate', + 'brightness_move', + 'brightness_move_onoff', + 'brightness_step', + 'brightness_step_onoff', + 'color_temp', + 'color_temp_percent', + 'color_mode', + 'color_options', + 'colortemp_move', + 'color_temp_move', + 'color_temp_step', + 'color_temp_startup', + 'effect', + 'alert', + 'flash', + 'power_on_behavior', ], exposes: ['effect', 'light(state,brightness,color_temp,color_temp_startup)', 'linkquality', 'power_on_behavior'], bind: [], - read: {1: [ - ['lightingColorCtrl', ['colorCapabilities']], - ['lightingColorCtrl', ['colorTempPhysicalMin', 'colorTempPhysicalMax']], - ]}, + read: { + 1: [ + ['lightingColorCtrl', ['colorCapabilities']], + ['lightingColorCtrl', ['colorTempPhysicalMin', 'colorTempPhysicalMax']], + ], + }, configureReporting: [], }); }); @@ -48,17 +82,42 @@ describe('ModernExtend', () => { meta: {applyRedFix: true, supportsHueAndSaturation: true, turnsOffAtBrightness1: true}, fromZigbee: [fz.on_off, fz.brightness, fz.ignore_basic_report, fz.level_config, fz.color_colortemp, fz.power_on_behavior], toZigbee: [ - 'state', 'brightness', 'brightness_percent', 'on_time', 'transition', 'level_config', 'rate', 'brightness_move', 'brightness_move_onoff', - 'brightness_step', 'brightness_step_onoff', 'color', 'color_temp', 'color_temp_percent', 'color_mode', 'color_options', 'colortemp_move', - 'color_temp_move', 'color_temp_step', 'hue_move', 'saturation_move', 'hue_step', 'saturation_step', 'effect', 'alert', - 'flash', 'power_on_behavior', + 'state', + 'brightness', + 'brightness_percent', + 'on_time', + 'transition', + 'level_config', + 'rate', + 'brightness_move', + 'brightness_move_onoff', + 'brightness_step', + 'brightness_step_onoff', + 'color', + 'color_temp', + 'color_temp_percent', + 'color_mode', + 'color_options', + 'colortemp_move', + 'color_temp_move', + 'color_temp_step', + 'hue_move', + 'saturation_move', + 'hue_step', + 'saturation_step', + 'effect', + 'alert', + 'flash', + 'power_on_behavior', ], exposes: ['effect', 'light(state,brightness,color_temp,color_xy,color_hs)', 'linkquality', 'power_on_behavior'], bind: [], - read: {1: [ - ['lightingColorCtrl', ['colorCapabilities']], - ['lightingColorCtrl', ['colorTempPhysicalMin', 'colorTempPhysicalMax']], - ]}, + read: { + 1: [ + ['lightingColorCtrl', ['colorCapabilities']], + ['lightingColorCtrl', ['colorTempPhysicalMin', 'colorTempPhysicalMax']], + ], + }, configureReporting: [], }); }); @@ -69,17 +128,43 @@ describe('ModernExtend', () => { meta: {}, fromZigbee: [fz.on_off, fz.brightness, fz.ignore_basic_report, fz.level_config, fz.color_colortemp, fz.power_on_behavior], toZigbee: [ - 'state', 'brightness', 'brightness_percent', 'on_time', 'transition', 'level_config', 'rate', 'brightness_move', 'brightness_move_onoff', - 'brightness_step', 'brightness_step_onoff', 'color', 'color_temp', 'color_temp_percent', 'color_mode', 'color_options', 'colortemp_move', - 'color_temp_move', 'color_temp_step', 'color_temp_startup', 'hue_move', 'saturation_move', 'hue_step', 'saturation_step', 'effect', 'alert', - 'flash', 'power_on_behavior', + 'state', + 'brightness', + 'brightness_percent', + 'on_time', + 'transition', + 'level_config', + 'rate', + 'brightness_move', + 'brightness_move_onoff', + 'brightness_step', + 'brightness_step_onoff', + 'color', + 'color_temp', + 'color_temp_percent', + 'color_mode', + 'color_options', + 'colortemp_move', + 'color_temp_move', + 'color_temp_step', + 'color_temp_startup', + 'hue_move', + 'saturation_move', + 'hue_step', + 'saturation_step', + 'effect', + 'alert', + 'flash', + 'power_on_behavior', ], exposes: ['effect', 'light(state,brightness,color_temp,color_temp_startup,color_xy)', 'linkquality', 'power_on_behavior'], bind: [], - read: {1: [ - ['lightingColorCtrl', ['colorCapabilities']], - ['lightingColorCtrl', ['colorTempPhysicalMin', 'colorTempPhysicalMax']], - ]}, + read: { + 1: [ + ['lightingColorCtrl', ['colorCapabilities']], + ['lightingColorCtrl', ['colorTempPhysicalMin', 'colorTempPhysicalMax']], + ], + }, configureReporting: [], }); }); @@ -92,16 +177,27 @@ describe('ModernExtend', () => { toZigbee: ['state', 'on_time', 'off_wait_time', 'power', 'voltage', 'current', 'energy'], exposes: ['current', 'energy', 'linkquality', 'power', 'switch(state)', 'voltage'], bind: {1: ['genOnOff', 'haElectricalMeasurement', 'seMetering']}, - read: {1: [ - ['genOnOff', ['onOff']], - ['haElectricalMeasurement', ['activePower', 'rmsCurrent', 'rmsVoltage']], - ['seMetering', ['currentSummDelivered']], - ]}, - configureReporting: {1: [ - ['genOnOff', [reportingItem('onOff', 0, repInterval.MAX, 1)]], - ['haElectricalMeasurement', [reportingItem('activePower', 10, 65000, 5), reportingItem('rmsCurrent', 10, 65000, 50), reportingItem('rmsVoltage', 10, 65000, 5)]], - ['seMetering', [reportingItem('currentSummDelivered', 10, 65000, [0, 10])]], - ]}, + read: { + 1: [ + ['genOnOff', ['onOff']], + ['haElectricalMeasurement', ['activePower', 'rmsCurrent', 'rmsVoltage']], + ['seMetering', ['currentSummDelivered']], + ], + }, + configureReporting: { + 1: [ + ['genOnOff', [reportingItem('onOff', 0, repInterval.MAX, 1)]], + [ + 'haElectricalMeasurement', + [ + reportingItem('activePower', 10, 65000, 5), + reportingItem('rmsCurrent', 10, 65000, 50), + reportingItem('rmsVoltage', 10, 65000, 5), + ], + ], + ['seMetering', [reportingItem('currentSummDelivered', 10, 65000, [0, 10])]], + ], + }, }); }); @@ -109,36 +205,100 @@ describe('ModernExtend', () => { await assertDefintion({ device: mockDevice({modelID: 'LCX012', endpoints: [{inputClusters: ['genOnOff', 'genLevelCtrl', 'lightingColorCtrl']}]}), meta: {supportsHueAndSaturation: true, turnsOffAtBrightness1: true}, - fromZigbee: [fz.on_off, fz.brightness, fz.ignore_basic_report, fz.level_config, fz.color_colortemp, fz.power_on_behavior, philipsFz.gradient], + fromZigbee: [ + fz.on_off, + fz.brightness, + fz.ignore_basic_report, + fz.level_config, + fz.color_colortemp, + fz.power_on_behavior, + philipsFz.gradient, + ], toZigbee: [ - 'state', 'brightness', 'brightness_percent', 'on_time', 'transition', 'level_config', 'rate', 'brightness_move', 'brightness_move_onoff', - 'brightness_step', 'brightness_step_onoff', 'color', 'color_temp', 'color_temp_percent', 'color_mode', 'color_options', 'colortemp_move', - 'color_temp_move', 'color_temp_step', 'color_temp_startup', 'hue_move', 'saturation_move', 'hue_step', 'saturation_step', 'power_on_behavior', - 'hue_power_on_behavior', 'hue_power_on_brightness', 'hue_power_on_color_temperature', 'hue_power_on_color', 'effect', 'gradient_scene', 'gradient' + 'state', + 'brightness', + 'brightness_percent', + 'on_time', + 'transition', + 'level_config', + 'rate', + 'brightness_move', + 'brightness_move_onoff', + 'brightness_step', + 'brightness_step_onoff', + 'color', + 'color_temp', + 'color_temp_percent', + 'color_mode', + 'color_options', + 'colortemp_move', + 'color_temp_move', + 'color_temp_step', + 'color_temp_startup', + 'hue_move', + 'saturation_move', + 'hue_step', + 'saturation_step', + 'power_on_behavior', + 'hue_power_on_behavior', + 'hue_power_on_brightness', + 'hue_power_on_color_temperature', + 'hue_power_on_color', + 'effect', + 'gradient_scene', + 'gradient', + ], + exposes: [ + 'effect', + 'gradient', + 'gradient_scene', + 'light(state,brightness,color_temp,color_temp_startup,color_xy,color_hs)', + 'linkquality', + 'power_on_behavior', ], - exposes: ['effect', 'gradient', 'gradient_scene', 'light(state,brightness,color_temp,color_temp_startup,color_xy,color_hs)', 'linkquality', 'power_on_behavior'], bind: {1: ['manuSpecificPhilips2']}, - read: {1: [ - ['lightingColorCtrl', ['colorCapabilities']], - ['lightingColorCtrl', ['colorTempPhysicalMin', 'colorTempPhysicalMax']], - ]}, + read: { + 1: [ + ['lightingColorCtrl', ['colorCapabilities']], + ['lightingColorCtrl', ['colorTempPhysicalMin', 'colorTempPhysicalMax']], + ], + }, configureReporting: [], }); }); test(`ledvanceLight({configureReporting: true, endpoints: {'l1': 10, 'l2': 11, 's1': 25}, ota: ota.zigbeeOTA})`, async () => { await assertDefintion({ - device: mockDevice({modelID: 'Zigbee 3.0 DALI CONV LI', endpoints: [ - {ID: 10, inputClusters: ['genOnOff', 'genLevelCtrl']}, - {ID: 11, inputClusters: ['genOnOff', 'genLevelCtrl']}, - {ID: 25, inputClusters: ['genOnOff', 'genLevelCtrl']}, - {ID: 242, inputClusters: []}, - ]}), + device: mockDevice({ + modelID: 'Zigbee 3.0 DALI CONV LI', + endpoints: [ + {ID: 10, inputClusters: ['genOnOff', 'genLevelCtrl']}, + {ID: 11, inputClusters: ['genOnOff', 'genLevelCtrl']}, + {ID: 25, inputClusters: ['genOnOff', 'genLevelCtrl']}, + {ID: 242, inputClusters: []}, + ], + }), meta: {multiEndpoint: true}, fromZigbee: [fz.command_toggle, fz.command_move, fz.command_stop, fz.on_off, fz.brightness, fz.ignore_basic_report, fz.level_config], toZigbee: [ - 'state', 'brightness', 'brightness_percent', 'on_time', 'transition', 'level_config', 'rate', 'brightness_move', 'brightness_move_onoff', - 'brightness_step', 'brightness_step_onoff', 'effect', 'alert', 'flash', 'set_transition', 'remember_state', 'osram_set_transition', 'osram_remember_state', + 'state', + 'brightness', + 'brightness_percent', + 'on_time', + 'transition', + 'level_config', + 'rate', + 'brightness_move', + 'brightness_move_onoff', + 'brightness_step', + 'brightness_step_onoff', + 'effect', + 'alert', + 'flash', + 'set_transition', + 'remember_state', + 'osram_set_transition', + 'osram_remember_state', ], exposes: ['action', 'effect', 'light_l1(state,brightness)', 'light_l2(state,brightness)', 'light_s1(state,brightness)', 'linkquality'], bind: { @@ -147,9 +307,18 @@ describe('ModernExtend', () => { 25: ['genOnOff', 'genLevelCtrl'], }, read: { - 10: [['genOnOff', ['onOff']], ['genLevelCtrl', ['currentLevel']]], - 11: [['genOnOff', ['onOff']], ['genLevelCtrl', ['currentLevel']]], - 25: [['genOnOff', ['onOff']], ['genLevelCtrl', ['currentLevel']]], + 10: [ + ['genOnOff', ['onOff']], + ['genLevelCtrl', ['currentLevel']], + ], + 11: [ + ['genOnOff', ['onOff']], + ['genLevelCtrl', ['currentLevel']], + ], + 25: [ + ['genOnOff', ['onOff']], + ['genLevelCtrl', ['currentLevel']], + ], }, configureReporting: { 10: [ @@ -163,7 +332,7 @@ describe('ModernExtend', () => { 25: [ ['genOnOff', [reportingItem('onOff', 0, repInterval.MAX, 1)]], ['genLevelCtrl', [reportingItem('currentLevel', 10, 65000, 1)]], - ] + ], }, endpoints: {l1: 10, l2: 11, s1: 25}, }); @@ -171,10 +340,13 @@ describe('ModernExtend', () => { test(`onOff({endpoints: {top: 1, bottom: 2}})`, async () => { await assertDefintion({ - device: mockDevice({modelID: 'PM-S240R-ZB', endpoints: [ - {ID: 1, inputClusters: ['genOnOff']}, - {ID: 2, inputClusters: ['genOnOff']}, - ]}), + device: mockDevice({ + modelID: 'PM-S240R-ZB', + endpoints: [ + {ID: 1, inputClusters: ['genOnOff']}, + {ID: 2, inputClusters: ['genOnOff']}, + ], + }), meta: {multiEndpoint: true}, fromZigbee: [fz.on_off], toZigbee: ['state', 'on_time', 'off_wait_time'], @@ -188,12 +360,8 @@ describe('ModernExtend', () => { 2: [['genOnOff', ['onOff']]], }, configureReporting: { - 1: [ - ['genOnOff', [reportingItem('onOff', 0, repInterval.MAX, 1)]], - ], - 2: [ - ['genOnOff', [reportingItem('onOff', 0, repInterval.MAX, 1)]], - ], + 1: [['genOnOff', [reportingItem('onOff', 0, repInterval.MAX, 1)]]], + 2: [['genOnOff', [reportingItem('onOff', 0, repInterval.MAX, 1)]]], }, endpoints: {bottom: 2, top: 1}, }); @@ -201,9 +369,7 @@ describe('ModernExtend', () => { test(`VOCKQJK11LM`, async () => { await assertDefintion({ - device: mockDevice({modelID: 'lumi.airmonitor.acn01', endpoints: [ - {ID: 1, inputClusters: []}, - ]}), + device: mockDevice({modelID: 'lumi.airmonitor.acn01', endpoints: [{ID: 1, inputClusters: []}]}), meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, fromZigbee: [ fz.battery, diff --git a/test/ota.test.ts b/test/ota.test.ts index d531ba4a88dba..acb94d2d8ff84 100644 --- a/test/ota.test.ts +++ b/test/ota.test.ts @@ -11,25 +11,33 @@ interface WaitressMatcher { clusterID: number; commandIdentifier: number; transactionSequenceNumber?: number; -}; +} type CommandResult = { clusterID: number; header: { commandIdentifier: number; transactionSequenceNumber: number; }; - payload: KeyValueAny + payload: KeyValueAny; }; // NOTE: takes too long to run this with CI, can enable locally as needed describe('OTA', () => { - const TX_MAX_DELAY = 20000;// arbitrary, but less than min timeout involved (queryNextImageRequest === 60000) + const TX_MAX_DELAY = 20000; // arbitrary, but less than min timeout involved (queryNextImageRequest === 60000) const waitressValidator = (payload: CommandResult, matcher: WaitressMatcher): boolean => { - return payload.header && (payload.clusterID === matcher.clusterID) && (payload.header.commandIdentifier === matcher.commandIdentifier) - && (!matcher.transactionSequenceNumber || (payload.header.transactionSequenceNumber === matcher.transactionSequenceNumber)) + return ( + payload.header && + payload.clusterID === matcher.clusterID && + payload.header.commandIdentifier === matcher.commandIdentifier && + (!matcher.transactionSequenceNumber || payload.header.transactionSequenceNumber === matcher.transactionSequenceNumber) + ); }; - const waitressTimeoutFormatter = (matcher: WaitressMatcher, timeout: number) => `Timeout - ${matcher.clusterID} - ${matcher.commandIdentifier} - ${matcher.transactionSequenceNumber}`; - const waitress: Waitress = new Waitress(waitressValidator, waitressTimeoutFormatter); + const waitressTimeoutFormatter = (matcher: WaitressMatcher, timeout: number) => + `Timeout - ${matcher.clusterID} - ${matcher.commandIdentifier} - ${matcher.transactionSequenceNumber}`; + const waitress: Waitress = new Waitress( + waitressValidator, + waitressTimeoutFormatter, + ); const mockWaitressResolve = async (payload: CommandResult, maxDelay: number = TX_MAX_DELAY): Promise => { // rnd wait time to trigger throttling randomly (min 25ms, can't be instant) await new Promise((resolve) => setTimeout(resolve, Math.floor(Math.random() * maxDelay) + 25)); @@ -37,7 +45,7 @@ describe('OTA', () => { return waitress.resolve(payload); }; let stopRequestingBlocks: boolean = false; - + class MockOTAEndpoint extends EventEmitter { public ID: number; public waiters: []; @@ -47,33 +55,38 @@ describe('OTA', () => { private endFileOffset: number; private reqFileOffset: number; public downloadedImage: Buffer; - + constructor(ID: number, newImageHeader: Ota.ImageHeader) { super(); - + this.ID = ID; this.waiters = []; this.manufacturerCode = newImageHeader.manufacturerCode; this.currentImageType = newImageHeader.imageType; - this.currentImageVersion = (newImageHeader.fileVersion - 1); + this.currentImageVersion = newImageHeader.fileVersion - 1; this.endFileOffset = newImageHeader.totalImageSize; this.reqFileOffset = 0; this.downloadedImage = Buffer.alloc(0); } - + public supportsOutputCluster(clusterKey: number | string): boolean { return true; } - - public async commandResponse(clusterKey: number | string, commandKey: number | string, payload: KeyValueAny, options?: unknown, - transactionSequenceNumber?: number): Promise { + + public async commandResponse( + clusterKey: number | string, + commandKey: number | string, + payload: KeyValueAny, + options?: unknown, + transactionSequenceNumber?: number, + ): Promise { // just because... if (clusterKey !== 'genOta') { return; } - + transactionSequenceNumber = transactionSequenceNumber || ZclTransactionSequenceNumber.next(); - + switch (commandKey) { case 'imageNotify': { // trigger queryNextImageRequest @@ -84,7 +97,7 @@ describe('OTA', () => { fieldControl: 0, manufacturerCode: this.manufacturerCode, imageType: this.currentImageType, - fileVersion: this.currentImageVersion,// version currently installed on the device + fileVersion: this.currentImageVersion, // version currently installed on the device }, }); break; @@ -97,18 +110,18 @@ describe('OTA', () => { console.log('Cannot perform a re-install, not supported by Zigbee spec.'); return; } - + if (payload.fileVersion < this.currentImageVersion) { console.log('Performing downgrade.'); } else { console.log('Performing upgrade.'); } - + // first imageBlockRequest can take a good long while before triggering in practice, fake it await new Promise((resolve) => setTimeout(resolve, 300000)); - - this.reqFileOffset = 0;// starting at zero - + + this.reqFileOffset = 0; // starting at zero + mockWaitressResolve({ clusterID: Zcl.Clusters.genOta.ID, header: {commandIdentifier: Zcl.Clusters.genOta.commands.imageBlockRequest.ID, transactionSequenceNumber}, @@ -131,20 +144,23 @@ describe('OTA', () => { if (this.reqFileOffset >= this.endFileOffset) { // trigger `upgradeEndRequest` - mockWaitressResolve({ - clusterID: Zcl.Clusters.genOta.ID, - header: {commandIdentifier: Zcl.Clusters.genOta.commands.upgradeEndRequest.ID, transactionSequenceNumber}, - payload: { - status: Zcl.Status.SUCCESS, - manufacturerCode: payload.manufacturerCode, - imageType: payload.imageType, - fileVersion: payload.fileVersion, + mockWaitressResolve( + { + clusterID: Zcl.Clusters.genOta.ID, + header: {commandIdentifier: Zcl.Clusters.genOta.commands.upgradeEndRequest.ID, transactionSequenceNumber}, + payload: { + status: Zcl.Status.SUCCESS, + manufacturerCode: payload.manufacturerCode, + imageType: payload.imageType, + fileVersion: payload.fileVersion, + }, }, - }, 150); + 150, + ); } else { // trigger n `imageBlockRequest` this.reqFileOffset += payload.dataSize; - + mockWaitressResolve({ clusterID: Zcl.Clusters.genOta.ID, header: {commandIdentifier: Zcl.Clusters.genOta.commands.imageBlockRequest.ID, transactionSequenceNumber}, @@ -158,7 +174,7 @@ describe('OTA', () => { }, }); } - + this.downloadedImage = Buffer.concat([this.downloadedImage, payload.data]); break; } @@ -168,35 +184,44 @@ describe('OTA', () => { } } } - - public waitForCommand(clusterKey: number | string, commandKey: number | string, transactionSequenceNumber: number, timeout: number) - : {promise: Promise; cancel: () => void} { + + public waitForCommand( + clusterKey: number | string, + commandKey: number | string, + transactionSequenceNumber: number, + timeout: number, + ): {promise: Promise; cancel: () => void} { const cluster = Zcl.Utils.getCluster(clusterKey, null, {}); const command = cluster.getCommand(commandKey); const waiter = waitress.waitFor({clusterID: cluster.ID, commandIdentifier: command.ID, transactionSequenceNumber}, timeout); - + return {cancel: (): void => waitress.remove(waiter.ID), promise: waiter.start().promise}; } - public async defaultResponse(commandID: number, status: number, clusterID: number, transactionSequenceNumber: number, options?: unknown) - : Promise { + public async defaultResponse( + commandID: number, + status: number, + clusterID: number, + transactionSequenceNumber: number, + options?: unknown, + ): Promise { // triggered when `upgradeEndRequest` fails, per spec } } - + class MockDevice extends EventEmitter { public modelID: string; public endpoints: MockOTAEndpoint[]; public hardwareVersion: number; - + constructor(filename: string, otaEndpoint: MockOTAEndpoint, hardwareVersion: number) { super(); - + this.modelID = filename; this.endpoints = [otaEndpoint]; this.hardwareVersion = hardwareVersion; } - + get ieeeAddr(): string { return '0x1234acdb1234abcd'; } @@ -208,7 +233,7 @@ describe('OTA', () => { afterAll(() => { jest.useRealTimers(); - }) + }); beforeEach(() => { stopRequestingBlocks = false; @@ -219,69 +244,77 @@ describe('OTA', () => { // ['100B-0112-01002400-ConfLightBLE-Lamps-EFR32MG13.zigbee', {fileVersion: 16786432, fileSize: 439622}, false], // ['10005778-10.1-TRADFRI-onoff-shortcut-control-2.2.010.ota.ota.signed', {fileVersion: 570492465}, false], ['A60_DIM_Z3_IM003D_00103101-encrypted_11_20_2018_Tue_122925_01_withoutMF.ota', {fileVersion: 1061121, fileSize: 182876}, true], - ])('Updates to latest for %s', async (filename, imageMeta, suppressElementImageParseFailure) => { - const data = readFileSync(join(__dirname, 'stub', 'otaImageFiles', filename)); - const start = data.indexOf(UPGRADE_FILE_IDENTIFIER); - const newImage = parseImage(data.subarray(start)); - console.log(JSON.stringify(newImage.header)); - - const endpoint = new MockOTAEndpoint(0, newImage.header); - const device = new MockDevice(filename, endpoint, newImage.header.maximumHardwareVersion ?? 0); - const onProgress = jest.fn(); - - const update = updateToLatest( - // @ts-expect-error mock - device, - onProgress, - () => newImage, - () => imageMeta, - () => ({data}), - suppressElementImageParseFailure, - ); + ])( + 'Updates to latest for %s', + async (filename, imageMeta, suppressElementImageParseFailure) => { + const data = readFileSync(join(__dirname, 'stub', 'otaImageFiles', filename)); + const start = data.indexOf(UPGRADE_FILE_IDENTIFIER); + const newImage = parseImage(data.subarray(start)); + console.log(JSON.stringify(newImage.header)); + + const endpoint = new MockOTAEndpoint(0, newImage.header); + const device = new MockDevice(filename, endpoint, newImage.header.maximumHardwareVersion ?? 0); + const onProgress = jest.fn(); + + const update = updateToLatest( + // @ts-expect-error mock + device, + onProgress, + () => newImage, + () => imageMeta, + () => ({data}), + suppressElementImageParseFailure, + ); - await jest.runAllTimersAsync(); - const fileVersion = await update; + await jest.runAllTimersAsync(); + const fileVersion = await update; - expect(fileVersion).toStrictEqual(newImage.header.fileVersion); - expect(newImage.raw).toStrictEqual(endpoint.downloadedImage); - }, 60000); + expect(fileVersion).toStrictEqual(newImage.header.fileVersion); + expect(newImage.raw).toStrictEqual(endpoint.downloadedImage); + }, + 60000, + ); it.each([ // ['10F2-7B09-0000-0004-01090206-spo-fmi4.ota.zigbee', {hardwareVersionMin: 0, hardwareVersionMax: 4, fileVersion: 17367558}, false], // ['100B-0112-01002400-ConfLightBLE-Lamps-EFR32MG13.zigbee', {fileVersion: 16786432, fileSize: 439622}, false], // ['10005778-10.1-TRADFRI-onoff-shortcut-control-2.2.010.ota.ota.signed', {fileVersion: 570492465}, false], ['A60_DIM_Z3_IM003D_00103101-encrypted_11_20_2018_Tue_122925_01_withoutMF.ota', {fileVersion: 1061121, fileSize: 182876}, true], - ])('Handles device stop requesting blocks for %s', async (filename, imageMeta, suppressElementImageParseFailure) => { - const data = readFileSync(join(__dirname, 'stub', 'otaImageFiles', filename)); - const start = data.indexOf(UPGRADE_FILE_IDENTIFIER); - const newImage = parseImage(data.subarray(start)); - console.log(JSON.stringify(newImage.header)); - - const endpoint = new MockOTAEndpoint(0, newImage.header); - const device = new MockDevice(filename, endpoint, newImage.header.maximumHardwareVersion ?? 0); - const onProgress = jest.fn(); - - setTimeout(() => { - stopRequestingBlocks = true; - }, 350000);// some time after start of block requests - const update = updateToLatest( - // @ts-expect-error mock - device, - onProgress, - () => newImage, - () => imageMeta, - () => ({data}), - suppressElementImageParseFailure, - ); - // https://github.com/jestjs/jest/issues/6028#issuecomment-567669082 - update.catch(() => {}); + ])( + 'Handles device stop requesting blocks for %s', + async (filename, imageMeta, suppressElementImageParseFailure) => { + const data = readFileSync(join(__dirname, 'stub', 'otaImageFiles', filename)); + const start = data.indexOf(UPGRADE_FILE_IDENTIFIER); + const newImage = parseImage(data.subarray(start)); + console.log(JSON.stringify(newImage.header)); - await jest.runAllTimersAsync(); + const endpoint = new MockOTAEndpoint(0, newImage.header); + const device = new MockDevice(filename, endpoint, newImage.header.maximumHardwareVersion ?? 0); + const onProgress = jest.fn(); - await expect(update).rejects.toThrow( - `Timeout. Device did not start/finish firmware download after being notified. (Error: Timeout - ${Zcl.Clusters.genOta.ID} - ${Zcl.Clusters.genOta.commands.imageBlockRequest.ID} - null)` - ); - }, 30000); + setTimeout(() => { + stopRequestingBlocks = true; + }, 350000); // some time after start of block requests + const update = updateToLatest( + // @ts-expect-error mock + device, + onProgress, + () => newImage, + () => imageMeta, + () => ({data}), + suppressElementImageParseFailure, + ); + // https://github.com/jestjs/jest/issues/6028#issuecomment-567669082 + update.catch(() => {}); + + await jest.runAllTimersAsync(); + + await expect(update).rejects.toThrow( + `Timeout. Device did not start/finish firmware download after being notified. (Error: Timeout - ${Zcl.Clusters.genOta.ID} - ${Zcl.Clusters.genOta.commands.imageBlockRequest.ID} - null)`, + ); + }, + 30000, + ); // TODO: Image block response failed // TODO: Upgrade end response failed diff --git a/test/otaCommon.test.ts b/test/otaCommon.test.ts index 82b7463a2957d..e6b8338115f66 100644 --- a/test/otaCommon.test.ts +++ b/test/otaCommon.test.ts @@ -1,57 +1,84 @@ import crypto from 'crypto'; -import { readFileSync } from 'fs'; -import { join } from 'path'; +import {readFileSync} from 'fs'; +import {join} from 'path'; import * as common from '../src/lib/ota/common'; describe('OTA Common', () => { it.each([ - ['10005777-4.1-TRADFRI-control-outlet-2.0.022.ota.ota.signed', { - manufacturer: 'Ikea', - headerField: 0, - elements: 1 - }], - ['A60_DIM_Z3_IM003D_00103101-encrypted_11_20_2018_Tue_122925_01_withoutMF.ota', { - manufacturer: 'Ledvance', - headerField: 0, - elements: 1 - }], - ['ZLL_MK_0x01020509_CLA60_TW.ota', { - manufacturer: 'Ledvance', - headerField: 0, - elements: 4 - }], - ['ZLL_MK_0x01020510_CLASSIC_A60_RGBW.ota', { - manufacturer: 'Ledvance', - headerField: 0, - elements: 6 - }], - ['SAL2PU1_02015120_OTA.ota', { - manufacturer: 'Salus', - headerField: 0, - elements: 1 - }], - ['10F2-7B09-0000-0004-01090206-spo-fmi4.ota.zigbee', { - manufacturer: 'Ubisys', - headerField: 4, - minimumHardwareVersion: 0, - maximumHardwareVersion: 4, - elements: 1 - }], - ['10005778-10.1-TRADFRI-onoff-shortcut-control-2.2.010.ota.ota.signed', { - manufacturer: '', - headerField: 0, - elements: 1 - }], - ['100B-0112-01001500-ConfLightBLE-Lamps-EFR32MG13.zigbee', { - manufacturer: '', - headerField: 0, - elements: 26 - }], - ['100B-0112-01002400-ConfLightBLE-Lamps-EFR32MG13.zigbee', { - headerField: 0, - elements: 33 - }], - ])("Can correctly parse OTA image file %s", (filename, meta) => { + [ + '10005777-4.1-TRADFRI-control-outlet-2.0.022.ota.ota.signed', + { + manufacturer: 'Ikea', + headerField: 0, + elements: 1, + }, + ], + [ + 'A60_DIM_Z3_IM003D_00103101-encrypted_11_20_2018_Tue_122925_01_withoutMF.ota', + { + manufacturer: 'Ledvance', + headerField: 0, + elements: 1, + }, + ], + [ + 'ZLL_MK_0x01020509_CLA60_TW.ota', + { + manufacturer: 'Ledvance', + headerField: 0, + elements: 4, + }, + ], + [ + 'ZLL_MK_0x01020510_CLASSIC_A60_RGBW.ota', + { + manufacturer: 'Ledvance', + headerField: 0, + elements: 6, + }, + ], + [ + 'SAL2PU1_02015120_OTA.ota', + { + manufacturer: 'Salus', + headerField: 0, + elements: 1, + }, + ], + [ + '10F2-7B09-0000-0004-01090206-spo-fmi4.ota.zigbee', + { + manufacturer: 'Ubisys', + headerField: 4, + minimumHardwareVersion: 0, + maximumHardwareVersion: 4, + elements: 1, + }, + ], + [ + '10005778-10.1-TRADFRI-onoff-shortcut-control-2.2.010.ota.ota.signed', + { + manufacturer: '', + headerField: 0, + elements: 1, + }, + ], + [ + '100B-0112-01001500-ConfLightBLE-Lamps-EFR32MG13.zigbee', + { + manufacturer: '', + headerField: 0, + elements: 26, + }, + ], + [ + '100B-0112-01002400-ConfLightBLE-Lamps-EFR32MG13.zigbee', + { + headerField: 0, + elements: 33, + }, + ], + ])('Can correctly parse OTA image file %s', (filename, meta) => { const data = readFileSync(join(__dirname, 'stub', 'otaImageFiles', filename)); const start = data.indexOf(common.UPGRADE_FILE_IDENTIFIER); @@ -63,11 +90,11 @@ describe('OTA Common', () => { expect(image.header.minimumHardwareVersion).toBe( // @ts-expect-error can be undefined - meta.minimumHardwareVersion + meta.minimumHardwareVersion, ); expect(image.header.maximumHardwareVersion).toBe( // @ts-expect-error can be undefined - meta.maximumHardwareVersion + meta.maximumHardwareVersion, ); expect(image.elements.length).toBe(meta.elements); @@ -85,36 +112,39 @@ describe('OTA Common', () => { fileVersion: image.header.fileVersion, sha512: hash.digest('hex'), }); - const device = { ieeeAddr: '0x000000000000000' }; + const device = {ieeeAddr: '0x000000000000000'}; it('Valid OTA image file passes checksum verification', async () => { - const mockDownloadImage = jest.fn().mockResolvedValue({ data }); - await expect(common.getNewImage( - { - manufacturerCode: image.header.manufacturerCode, - imageType: image.header.imageType, - fileVersion: image.header.fileVersion - 1, - }, - // @ts-expect-error mock - device, - mockGetImageMeta, - mockDownloadImage, - false - )).resolves.toBeInstanceOf(Object); + const mockDownloadImage = jest.fn().mockResolvedValue({data}); + await expect( + common.getNewImage( + { + manufacturerCode: image.header.manufacturerCode, + imageType: image.header.imageType, + fileVersion: image.header.fileVersion - 1, + }, + // @ts-expect-error mock + device, + mockGetImageMeta, + mockDownloadImage, + false, + ), + ).resolves.toBeInstanceOf(Object); }); it('Invalid OTA image file fails checksum verification', async () => { - const mockDownloadImage = jest.fn().mockResolvedValue({ data: 'invalid data' }); + const mockDownloadImage = jest.fn().mockResolvedValue({data: 'invalid data'}); - await expect(common.getNewImage( - // @ts-expect-error mock - { fileVersion: image.header.fileVersion - 1 }, - { ieeeAddr: '0x000000000000000' }, - mockGetImageMeta, - mockDownloadImage, - false - )).rejects.toThrow(/File checksum validation failed/); + await expect( + common.getNewImage( + // @ts-expect-error mock + {fileVersion: image.header.fileVersion - 1}, + {ieeeAddr: '0x000000000000000'}, + mockGetImageMeta, + mockDownloadImage, + false, + ), + ).rejects.toThrow(/File checksum validation failed/); }); }); - }); diff --git a/test/philips.test.js b/test/philips.test.js index acb5ffca4576f..9e08e938b1d2d 100644 --- a/test/philips.test.js +++ b/test/philips.test.js @@ -3,91 +3,91 @@ const philips = require('../src/lib/philips'); describe('lib/philips.js', () => { describe('decodeGradientColors', () => { test.each([ - ["4b0101b2875a25411350000000b3474def153e2ad42e98232c7483292800", true, ["#0c32ff", "#1137ff", "#2538ff", "#7951ff", "#ff77f8"]], - ["4b0101b2875a25411350000000b3474def153e2ad42e98232c7483292800", false, ["#ff77f8", "#7951ff", "#2538ff","#1137ff", "#0c32ff"]], - ["4b010164fb74346b1350000000f3297fda7d55da7d55f3297fda7d552800", true, ["#ff0517", "#ffa52c", "#ff0517", "#ff0517", "#ffa52c"]], - ["4b010127a0526f5410400000000727640e9f5d0727640e9f5d2000", true, ["#ff0500", "#ffffff", "#ff0500", "#ffffff"]], - ])("colors(%s) should be %s", (input, opts_reverse, expected) => { - const ret = philips.decodeGradientColors(input, { reverse: opts_reverse }); + ['4b0101b2875a25411350000000b3474def153e2ad42e98232c7483292800', true, ['#0c32ff', '#1137ff', '#2538ff', '#7951ff', '#ff77f8']], + ['4b0101b2875a25411350000000b3474def153e2ad42e98232c7483292800', false, ['#ff77f8', '#7951ff', '#2538ff', '#1137ff', '#0c32ff']], + ['4b010164fb74346b1350000000f3297fda7d55da7d55f3297fda7d552800', true, ['#ff0517', '#ffa52c', '#ff0517', '#ff0517', '#ffa52c']], + ['4b010127a0526f5410400000000727640e9f5d0727640e9f5d2000', true, ['#ff0500', '#ffffff', '#ff0500', '#ffffff']], + ])('colors(%s) should be %s', (input, opts_reverse, expected) => { + const ret = philips.decodeGradientColors(input, {reverse: opts_reverse}); expect(ret.colors).toStrictEqual(expected); - }) + }); // XY Color test.each([ - ["0b00015c05b1ec4e", [0.6915, 0.3083]], // Red (#ff0500) - ["0b00015c842b32b3", [0.1700, 0.7000]], // Green (#00ff0e) - ["0b00011d37272c0c", [0.1532, 0.0475]], // Blue (#0a00ff) + ['0b00015c05b1ec4e', [0.6915, 0.3083]], // Red (#ff0500) + ['0b00015c842b32b3', [0.17, 0.7]], // Green (#00ff0e) + ['0b00011d37272c0c', [0.1532, 0.0475]], // Blue (#0a00ff) ])(`xy(%s) should be %s`, (input, expected) => { const ret = philips.decodeGradientColors(input); expect([ret.x, ret.y]).toStrictEqual(expected); - }) + }); test.each([ - ["4b010110ee2df18f1350000000e8b3aac7589f2dba903f4a7720ba602800", 16], - ["4b010164ee2df18f1350000000e8b3aac7589f2dba903f4a7720ba602800", 100], - ["0f0001044d01ab6f7067", 4], - ["0f00011a4d01ab6f7067", 26], - ["0f0000b2ff004c628461", 178], - ["0b00015c842b32b3", 92], - ["0b00011d842b32b3", 29], - ["ab000153df7e446a0180", 83], - ["030001b2", 178], - ["03000164", 100], - ["030001fe", 254], - ])("brightness(%s) should be %s", (input, expected) => { + ['4b010110ee2df18f1350000000e8b3aac7589f2dba903f4a7720ba602800', 16], + ['4b010164ee2df18f1350000000e8b3aac7589f2dba903f4a7720ba602800', 100], + ['0f0001044d01ab6f7067', 4], + ['0f00011a4d01ab6f7067', 26], + ['0f0000b2ff004c628461', 178], + ['0b00015c842b32b3', 92], + ['0b00011d842b32b3', 29], + ['ab000153df7e446a0180', 83], + ['030001b2', 178], + ['03000164', 100], + ['030001fe', 254], + ])('brightness(%s) should be %s', (input, expected) => { const ret = philips.decodeGradientColors(input); expect(ret.brightness).toBe(expected); - }) + }); test.each([ - ["4b010164ee2df18f1350000000e8b3aac7589f2dba903f4a7720ba602800", true], - ["4b010026ee2df18f1350000000e8b3aac7589f2dba903f4a7720ba602800", false], - ["0f0000044d01ab6f7067", false], - ["0f0001044d01ab6f7067", true], - ["0b00015c842b32b3", true], - ["0b00001d842b32b3", false], - ["ab000153df7e446a0180", true], - ["ab000053df7e446a0180", false], - ["03000164", true], - ["0300004f", false], - ])("power(%s) should be %s", (input, expected) => { + ['4b010164ee2df18f1350000000e8b3aac7589f2dba903f4a7720ba602800', true], + ['4b010026ee2df18f1350000000e8b3aac7589f2dba903f4a7720ba602800', false], + ['0f0000044d01ab6f7067', false], + ['0f0001044d01ab6f7067', true], + ['0b00015c842b32b3', true], + ['0b00001d842b32b3', false], + ['ab000153df7e446a0180', true], + ['ab000053df7e446a0180', false], + ['03000164', true], + ['0300004f', false], + ])('power(%s) should be %s', (input, expected) => { const ret = philips.decodeGradientColors(input); expect(ret.on).toBe(expected); - }) + }); test.each([ - ["4b010164ee2df18f1350000000e8b3aac7589f2dba903f4a7720ba602800", 0], - ["4b01012701b1ea4e13500000000e9f5d0727640e9f5d0727640e9f5d2810", 2], - ])("offset(%s) should be %s", (input, expected) => { + ['4b010164ee2df18f1350000000e8b3aac7589f2dba903f4a7720ba602800', 0], + ['4b01012701b1ea4e13500000000e9f5d0727640e9f5d0727640e9f5d2810', 2], + ])('offset(%s) should be %s', (input, expected) => { const ret = philips.decodeGradientColors(input); expect(ret.offset).toBe(expected); - }) + }); test.each([ - ["0f00011dfa0094611b61", 250], - ["0f00011d7201ab751969", 370], - ["0f00015cf401d486ce69", 500], + ['0f00011dfa0094611b61', 250], + ['0f00011d7201ab751969', 370], + ['0f00015cf401d486ce69', 500], ])(`colorTemperature(%s) should be %s`, (input, expected) => { const ret = philips.decodeGradientColors(input); expect(ret.color_temp).toBe(expected); - }) + }); test.each([ - ["0f00011dfa0094611b61", "color_temp"], - ["0b00015c842b32b3", "xy"], - ["4b010164ee2df18f1350000000e8b3aac7589f2dba903f4a7720ba602800", "gradient"], - ["ab000153df7e446a0180", "xy"], + ['0f00011dfa0094611b61', 'color_temp'], + ['0b00015c842b32b3', 'xy'], + ['4b010164ee2df18f1350000000e8b3aac7589f2dba903f4a7720ba602800', 'gradient'], + ['ab000153df7e446a0180', 'xy'], ])(`color_mode(%s) should be %s`, (input, expected) => { const ret = philips.decodeGradientColors(input); expect(ret.color_mode).toBe(expected); }); test.each([ - ["ab000153df7e446a0180", "candle"], - ["ab0001febd9238660280", "fireplace"], - ["ab0001febe92ab650380", "colorloop"], - ["ab0001febe92ab659999", "unknown_9999"], - ])("effect(%s) should be %s", (input, expected) => { + ['ab000153df7e446a0180', 'candle'], + ['ab0001febd9238660280', 'fireplace'], + ['ab0001febe92ab650380', 'colorloop'], + ['ab0001febe92ab659999', 'unknown_9999'], + ])('effect(%s) should be %s', (input, expected) => { const ret = philips.decodeGradientColors(input); expect(ret.name).toBe(expected); }); @@ -95,13 +95,17 @@ describe('lib/philips.js', () => { describe('encodeGradientColors', () => { test.each([ - [["#0c32ff", "#1137ff", "#2538ff", "#7951ff", "#ff77f8"], { reverse: true }, "500104001350000000b2474df0353e29e42e98332c7043292800"], - [["#0c32ff", "#1137ff", "#2538ff", "#7951ff", "#ff77f8"], { reverse: false }, "50010400135000000070432998332c29e42ef0353eb2474d2800"], - [["#ff0517", "#ffa52c", "#ff0517", "#ff0517", "#ffa52c"], { reverse: true }, "500104001350000000f3297fd56d55d56d55f3297fd56d552800"], - [["#ff0517", "#ffa52c", "#ff0517", "#ff0517", "#ffa52c"], { reverse: true, offset: 2 }, "500104001350000000f3297fd56d55d56d55f3297fd56d552810"], - [["#ffffff"], { reverse: true }, "5001040007100000000727640800"], - [["#ffffff"], { reverse: false }, "5001040007100000000727640800"], - ])("colors(%s) opts(%s) should be %s", (colors, opts, expected) => { + [['#0c32ff', '#1137ff', '#2538ff', '#7951ff', '#ff77f8'], {reverse: true}, '500104001350000000b2474df0353e29e42e98332c7043292800'], + [['#0c32ff', '#1137ff', '#2538ff', '#7951ff', '#ff77f8'], {reverse: false}, '50010400135000000070432998332c29e42ef0353eb2474d2800'], + [['#ff0517', '#ffa52c', '#ff0517', '#ff0517', '#ffa52c'], {reverse: true}, '500104001350000000f3297fd56d55d56d55f3297fd56d552800'], + [ + ['#ff0517', '#ffa52c', '#ff0517', '#ff0517', '#ffa52c'], + {reverse: true, offset: 2}, + '500104001350000000f3297fd56d55d56d55f3297fd56d552810', + ], + [['#ffffff'], {reverse: true}, '5001040007100000000727640800'], + [['#ffffff'], {reverse: false}, '5001040007100000000727640800'], + ])('colors(%s) opts(%s) should be %s', (colors, opts, expected) => { const ret = philips.encodeGradientColors(colors, opts); expect(ret).toStrictEqual(expected); }); diff --git a/test/sonoff.test.ts b/test/sonoff.test.ts index 8052da3663e78..142a53f534de5 100644 --- a/test/sonoff.test.ts +++ b/test/sonoff.test.ts @@ -1,6 +1,6 @@ -import * as index from "../src/index"; -import {Definition, Fz, KeyValueAny, Tz, Zh} from "../src/lib/types"; -import {Endpoint, Entity} from "zigbee-herdsman/dist/controller/model"; +import * as index from '../src/index'; +import {Definition, Fz, KeyValueAny, Tz, Zh} from '../src/lib/types'; +import {Endpoint, Entity} from 'zigbee-herdsman/dist/controller/model'; interface State { readonly weekly_schedule: { @@ -11,7 +11,7 @@ interface State { readonly thursday: string; readonly friday: string; readonly saturday: string; - } + }; } describe('Sonoff TRVZB', () => { @@ -32,32 +32,35 @@ describe('Sonoff TRVZB', () => { meta = { state: {}, device: null, - deviceExposesChanged: null + deviceExposesChanged: null, }; }); const days = [ - { dayofweek: 0x01, day: 'sunday' }, - { dayofweek: 0x02, day: 'monday' }, - { dayofweek: 0x04, day: 'tuesday' }, - { dayofweek: 0x08, day: 'wednesday' }, - { dayofweek: 0x10, day: 'thursday' }, - { dayofweek: 0x20, day: 'friday' }, - { dayofweek: 0x40, day: 'saturday' }, - ] - - describe.each(days)('when a commandGetWeeklyScheduleRsp message is received for $day', ({dayofweek, day}) => { + {dayofweek: 0x01, day: 'sunday'}, + {dayofweek: 0x02, day: 'monday'}, + {dayofweek: 0x04, day: 'tuesday'}, + {dayofweek: 0x08, day: 'wednesday'}, + {dayofweek: 0x10, day: 'thursday'}, + {dayofweek: 0x20, day: 'friday'}, + {dayofweek: 0x40, day: 'saturday'}, + ]; + + describe.each(days)('when a commandGetWeeklyScheduleRsp message is received for $day', ({dayofweek, day}) => { it('should set state', () => { const msg: Fz.Message = { data: { dayofweek: dayofweek, - transitions: [{ - transitionTime: 0, - heatSetpoint: 500, - },{ - transitionTime: 90, - heatSetpoint: 1000, - }] + transitions: [ + { + transitionTime: 0, + heatSetpoint: 500, + }, + { + transitionTime: 90, + heatSetpoint: 1000, + }, + ], }, endpoint: null, device: null, @@ -65,31 +68,34 @@ describe('Sonoff TRVZB', () => { groupID: null, type: 'commandGetWeeklyScheduleRsp', cluster: 'hvacThermostat', - linkquality: 0 + linkquality: 0, }; const state = fzConverter.convert(trv, msg, null, null, meta) as State; expect(state.weekly_schedule).toEqual({ - [day]: "00:00/5 01:30/10" + [day]: '00:00/5 01:30/10', }); }); }); - describe('when multiple commandGetWeeklyScheduleRsp messages are received for different days', () =>{ + describe('when multiple commandGetWeeklyScheduleRsp messages are received for different days', () => { let state: State; beforeEach(() => { const msg1: Fz.Message = { data: { dayofweek: 0x01, - transitions: [{ - transitionTime: 0, - heatSetpoint: 500, - },{ - transitionTime: 90, - heatSetpoint: 1000, - }] + transitions: [ + { + transitionTime: 0, + heatSetpoint: 500, + }, + { + transitionTime: 90, + heatSetpoint: 1000, + }, + ], }, endpoint: null, device: null, @@ -97,19 +103,22 @@ describe('Sonoff TRVZB', () => { groupID: null, type: 'commandGetWeeklyScheduleRsp', cluster: 'hvacThermostat', - linkquality: 0 + linkquality: 0, }; const msg2: Fz.Message = { data: { dayofweek: 0x02, - transitions: [{ - transitionTime: 60, - heatSetpoint: 550, - },{ - transitionTime: 180, - heatSetpoint: 1250, - }] + transitions: [ + { + transitionTime: 60, + heatSetpoint: 550, + }, + { + transitionTime: 180, + heatSetpoint: 1250, + }, + ], }, endpoint: null, device: null, @@ -117,7 +126,7 @@ describe('Sonoff TRVZB', () => { groupID: null, type: 'commandGetWeeklyScheduleRsp', cluster: 'hvacThermostat', - linkquality: 0 + linkquality: 0, }; meta.state = fzConverter.convert(trv, msg1, null, null, meta) as KeyValueAny; @@ -126,8 +135,8 @@ describe('Sonoff TRVZB', () => { it('should merge the schedules into state', () => { expect(state.weekly_schedule).toEqual({ - sunday: "00:00/5 01:30/10", - monday: "01:00/5.5 03:00/12.5" + sunday: '00:00/5 01:30/10', + monday: '01:00/5.5 03:00/12.5', }); }); }); @@ -140,16 +149,16 @@ describe('Sonoff TRVZB', () => { let endpoint: Endpoint; const invalidTransitions = [ - { transition: '', description: 'empty string' }, - { transition: '0:00/5', description: 'hours not two digits' }, - { transition: '24:00/5', description: 'hours greater than 23' }, - { transition: '23:0/5', description: 'minutes not two digits' }, - { transition: '23:60/5', description: 'minutes greater than 59' }, - { transition: '23:59', description: 'missing slash' }, - { transition: '23:59/', description: 'missing temperature' }, - { transition: '23:59/-1', description: 'negative temperature' }, - { transition: '23:59/523:59/5', description: 'missing space separator' }, - { transition: '00:00/10.1', description: 'temperature decimal point is not 0.5' }, + {transition: '', description: 'empty string'}, + {transition: '0:00/5', description: 'hours not two digits'}, + {transition: '24:00/5', description: 'hours greater than 23'}, + {transition: '23:0/5', description: 'minutes not two digits'}, + {transition: '23:60/5', description: 'minutes greater than 59'}, + {transition: '23:59', description: 'missing slash'}, + {transition: '23:59/', description: 'missing temperature'}, + {transition: '23:59/-1', description: 'negative temperature'}, + {transition: '23:59/523:59/5', description: 'missing space separator'}, + {transition: '00:00/10.1', description: 'temperature decimal point is not 0.5'}, ]; beforeEach(() => { @@ -161,142 +170,231 @@ describe('Sonoff TRVZB', () => { message: null, mapped: null, options: null, - endpoint_name: null + endpoint_name: null, }; commandFn = jest.fn(); endpoint = { - command: commandFn + command: commandFn, } as unknown as Endpoint; }); it.each(invalidTransitions)('should throw error if transition format is invalid ($description)', async ({transition, description}) => { await expect( - tzConverter.convertSet(endpoint, 'weekly_schedule', { - monday: transition - }, meta)).rejects.toEqual(new Error(`Invalid schedule: transitions must be in format HH:mm/temperature (e.g. 12:00/15.5), found: ${transition}`)); + tzConverter.convertSet( + endpoint, + 'weekly_schedule', + { + monday: transition, + }, + meta, + ), + ).rejects.toEqual( + new Error(`Invalid schedule: transitions must be in format HH:mm/temperature (e.g. 12:00/15.5), found: ${transition}`), + ); }); it('should throw error if first transition does not start at 00:00', async () => { await expect( - tzConverter.convertSet(endpoint, 'weekly_schedule', { - monday: '00:01/5' - }, meta)).rejects.toEqual(new Error('Invalid schedule: the first transition of each day should start at 00:00')); + tzConverter.convertSet( + endpoint, + 'weekly_schedule', + { + monday: '00:01/5', + }, + meta, + ), + ).rejects.toEqual(new Error('Invalid schedule: the first transition of each day should start at 00:00')); }); it('should throw error if day has more than 6 transitions', async () => { await expect( - tzConverter.convertSet(endpoint, 'weekly_schedule', { - monday: '00:00/1 00:00/1 00:00/1 00:00/1 00:00/1 00:00/1 00:00/1' - }, meta)).rejects.toEqual(new Error('Invalid schedule: days must have no more than 6 transitions')); + tzConverter.convertSet( + endpoint, + 'weekly_schedule', + { + monday: '00:00/1 00:00/1 00:00/1 00:00/1 00:00/1 00:00/1 00:00/1', + }, + meta, + ), + ).rejects.toEqual(new Error('Invalid schedule: days must have no more than 6 transitions')); }); it.each([3, 36])('should throw error if temperature value is outside of valid range ($temperature) ', async (temperature) => { await expect( - tzConverter.convertSet(endpoint, 'weekly_schedule', { - monday: `00:00/${temperature}` - }, meta)).rejects.toEqual(new Error(`Invalid schedule: temperature value must be between 4-35 (inclusive), found: ${temperature}`)); + tzConverter.convertSet( + endpoint, + 'weekly_schedule', + { + monday: `00:00/${temperature}`, + }, + meta, + ), + ).rejects.toEqual(new Error(`Invalid schedule: temperature value must be between 4-35 (inclusive), found: ${temperature}`)); }); it('should throw error if day name is invalid', async () => { await expect( - tzConverter.convertSet(endpoint, 'weekly_schedule', { - notaday: `00:00/5` - }, meta)).rejects.toEqual(new Error('Invalid schedule: invalid day name, found: notaday')); + tzConverter.convertSet( + endpoint, + 'weekly_schedule', + { + notaday: `00:00/5`, + }, + meta, + ), + ).rejects.toEqual(new Error('Invalid schedule: invalid day name, found: notaday')); }); it('should send setWeeklySchedule command if transitions are valid', async () => { - await tzConverter.convertSet(endpoint, 'weekly_schedule', { - sunday: `00:00/5 06:30/10.5 12:00/15 18:30/20 20:45/15.5 23:00/4` - }, meta); - - expect(commandFn).toHaveBeenCalledWith('hvacThermostat', 'setWeeklySchedule', { - dayofweek: 1, - numoftrans: 6, - mode: 1, - transitions: [{ - heatSetpoint: 500, - transitionTime: 0 - },{ - heatSetpoint: 1050, - transitionTime: 390 - },{ - heatSetpoint: 1500, - transitionTime: 720 - },{ - heatSetpoint: 2000, - transitionTime: 1110 - },{ - heatSetpoint: 1550, - transitionTime: 1245 - },{ - heatSetpoint: 400, - transitionTime: 1380 - }] - }, {}); + await tzConverter.convertSet( + endpoint, + 'weekly_schedule', + { + sunday: `00:00/5 06:30/10.5 12:00/15 18:30/20 20:45/15.5 23:00/4`, + }, + meta, + ); + + expect(commandFn).toHaveBeenCalledWith( + 'hvacThermostat', + 'setWeeklySchedule', + { + dayofweek: 1, + numoftrans: 6, + mode: 1, + transitions: [ + { + heatSetpoint: 500, + transitionTime: 0, + }, + { + heatSetpoint: 1050, + transitionTime: 390, + }, + { + heatSetpoint: 1500, + transitionTime: 720, + }, + { + heatSetpoint: 2000, + transitionTime: 1110, + }, + { + heatSetpoint: 1550, + transitionTime: 1245, + }, + { + heatSetpoint: 400, + transitionTime: 1380, + }, + ], + }, + {}, + ); }); it('should send setWeeklySchedule command with transitions in ascending time order', async () => { - await tzConverter.convertSet(endpoint, 'weekly_schedule', { - sunday: `00:00/5 12:00/15 06:30/10.5` - }, meta); - - expect(commandFn).toHaveBeenCalledWith('hvacThermostat', 'setWeeklySchedule', { - dayofweek: 1, - numoftrans: 3, - mode: 1, - transitions: [{ - heatSetpoint: 500, - transitionTime: 0 - },{ - heatSetpoint: 1050, - transitionTime: 390 - },{ - heatSetpoint: 1500, - transitionTime: 720 - }] - }, {}); + await tzConverter.convertSet( + endpoint, + 'weekly_schedule', + { + sunday: `00:00/5 12:00/15 06:30/10.5`, + }, + meta, + ); + + expect(commandFn).toHaveBeenCalledWith( + 'hvacThermostat', + 'setWeeklySchedule', + { + dayofweek: 1, + numoftrans: 3, + mode: 1, + transitions: [ + { + heatSetpoint: 500, + transitionTime: 0, + }, + { + heatSetpoint: 1050, + transitionTime: 390, + }, + { + heatSetpoint: 1500, + transitionTime: 720, + }, + ], + }, + {}, + ); }); it('should send a setWeeklySchedule command for each day', async () => { - await tzConverter.convertSet(endpoint, 'weekly_schedule', { - sunday: `00:00/5`, - monday: `00:00/10`, - tuesday: `00:00/15`, - }, meta); + await tzConverter.convertSet( + endpoint, + 'weekly_schedule', + { + sunday: `00:00/5`, + monday: `00:00/10`, + tuesday: `00:00/15`, + }, + meta, + ); expect(commandFn).toHaveBeenCalledTimes(3); - expect(commandFn).toHaveBeenCalledWith('hvacThermostat', 'setWeeklySchedule', { - dayofweek: 1, - numoftrans: 1, - mode: 1, - transitions: [{ - heatSetpoint: 500, - transitionTime: 0 - }] - }, {}); - - expect(commandFn).toHaveBeenCalledWith('hvacThermostat', 'setWeeklySchedule', { - dayofweek: 2, - numoftrans: 1, - mode: 1, - transitions: [{ - heatSetpoint: 1000, - transitionTime: 0 - }] - }, {}); - - expect(commandFn).toHaveBeenCalledWith('hvacThermostat', 'setWeeklySchedule', { - dayofweek: 4, - numoftrans: 1, - mode: 1, - transitions: [{ - heatSetpoint: 1500, - transitionTime: 0 - }] - }, {}); + expect(commandFn).toHaveBeenCalledWith( + 'hvacThermostat', + 'setWeeklySchedule', + { + dayofweek: 1, + numoftrans: 1, + mode: 1, + transitions: [ + { + heatSetpoint: 500, + transitionTime: 0, + }, + ], + }, + {}, + ); + + expect(commandFn).toHaveBeenCalledWith( + 'hvacThermostat', + 'setWeeklySchedule', + { + dayofweek: 2, + numoftrans: 1, + mode: 1, + transitions: [ + { + heatSetpoint: 1000, + transitionTime: 0, + }, + ], + }, + {}, + ); + + expect(commandFn).toHaveBeenCalledWith( + 'hvacThermostat', + 'setWeeklySchedule', + { + dayofweek: 4, + numoftrans: 1, + mode: 1, + transitions: [ + { + heatSetpoint: 1500, + transitionTime: 0, + }, + ], + }, + {}, + ); }); }); }); diff --git a/test/utils.test.js b/test/utils.test.js index cbc80702944a8..0419e508537a3 100644 --- a/test/utils.test.js +++ b/test/utils.test.js @@ -30,79 +30,79 @@ describe('lib/utils.js', () => { describe('getTransition', () => { let entity; let key; - let meta + let meta; beforeEach(() => { - entity = mockDevice({ manufacturerID: 4476, endpoints: [{}] }); + entity = mockDevice({manufacturerID: 4476, endpoints: [{}]}); key = 'brightness'; meta = { options: {}, - message: {} + message: {}, }; }); it('should return {time: 0, specified: false} if manufacturerID is 4476 and key is brightness and message has color or color_temp', () => { meta.message = { - color: 'red' + color: 'red', }; const result = getTransition(entity, key, meta); - expect(result).toEqual({ time: 0, specified: false }); + expect(result).toEqual({time: 0, specified: false}); }); it('should return {time: 0, specified: false} if manufacturerID is 4476 and key is brightness and message has color_temp', () => { meta.message = { - color_temp: 3000 + color_temp: 3000, }; const result = getTransition(entity, key, meta); - expect(result).toEqual({ time: 0, specified: false }); + expect(result).toEqual({time: 0, specified: false}); }); it('should return {time: 0, specified: false} if options.transition is an empty string', () => { meta.options = { - transition: '' + transition: '', }; const result = getTransition(entity, key, meta); - expect(result).toEqual({ time: 0, specified: false }); + expect(result).toEqual({time: 0, specified: false}); }); it('should return {time: 0, specified: false} if options.transition is not specified', () => { const result = getTransition(entity, key, meta); - expect(result).toEqual({ time: 0, specified: false }); + expect(result).toEqual({time: 0, specified: false}); }); it('should return {time: 100, specified: true} if message.transition is specified', () => { meta.message = { - transition: 10 + transition: 10, }; const result = getTransition(entity, key, meta); - expect(result).toEqual({ time: 100, specified: true }); + expect(result).toEqual({time: 100, specified: true}); }); it('should return {time: 200, specified: true} if options.transition is specified', () => { meta.options = { - transition: 20 + transition: 20, }; const result = getTransition(entity, key, meta); - expect(result).toEqual({ time: 200, specified: true }); + expect(result).toEqual({time: 200, specified: true}); }); it('should return {time: 0, specified: false} if neither message.transition nor options.transition is specified', () => { const result = getTransition(entity, key, meta); - expect(result).toEqual({ time: 0, specified: false }); + expect(result).toEqual({time: 0, specified: false}); }); }); }); diff --git a/test/utils.ts b/test/utils.ts index 6cdf0896d5636..55e56485ce1a8 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -2,16 +2,21 @@ import {findByDevice} from '../src/index'; import * as utils from '../src/lib/utils'; import {Zh, DefinitionMeta, Fz, Definition} from '../src/lib/types'; import tz from '../src/converters/toZigbee'; -import { Device } from 'zigbee-herdsman/dist/controller/model'; +import {Device} from 'zigbee-herdsman/dist/controller/model'; import {Clusters} from 'zigbee-herdsman/dist/zspec/zcl/definition/cluster'; -interface MockEndpointArgs {ID?: number, inputClusters?: string[], outputClusters?: string[], attributes?: {[s: string]: {[s: string]: unknown}}} +interface MockEndpointArgs { + ID?: number; + inputClusters?: string[]; + outputClusters?: string[]; + attributes?: {[s: string]: {[s: string]: unknown}}; +} export function reportingItem(attribute: string, min: number, max: number, change: number | [number, number]) { return {attribute: attribute, minimumReportInterval: min, maximumReportInterval: max, reportableChange: change}; } -export function mockDevice(args: {modelID: string, manufacturerID?: number, manufacturerName?: string, endpoints: MockEndpointArgs[]}): Zh.Device { +export function mockDevice(args: {modelID: string; manufacturerID?: number; manufacturerName?: string; endpoints: MockEndpointArgs[]}): Zh.Device { const ieeeAddr = '0x12345678'; const device: Zh.Device = { // @ts-expect-error @@ -33,7 +38,7 @@ export function mockDevice(args: {modelID: string, manufacturerID?: number, manu } function getCluster(ID: string | number) { - const cluster = Object.entries(Clusters).find((c) => typeof ID === 'number' ? c[1].ID === ID : c[0] === ID); + const cluster = Object.entries(Clusters).find((c) => (typeof ID === 'number' ? c[1].ID === ID : c[0] === ID)); if (!cluster) throw new Error(`Cluster '${ID}' does not exist`); return {name: cluster[0], ID: cluster[1].ID}; } @@ -57,31 +62,40 @@ function mockEndpoint(args: MockEndpointArgs, device: Zh.Device | undefined): Zh // @ts-expect-error getOutputClusters: () => outputClusters.map((c) => getCluster(c)), supportsInputCluster: (key) => !!inputClusters.find((ID) => ID === getCluster(key).ID), - saveClusterAttributeKeyValue: jest.fn().mockImplementation((cluster, values) => attributes[cluster] = {...attributes[cluster], ...values}), + saveClusterAttributeKeyValue: jest.fn().mockImplementation((cluster, values) => (attributes[cluster] = {...attributes[cluster], ...values})), save: jest.fn(), getClusterAttributeValue: jest.fn().mockImplementation((cluster, attribute) => attributes?.[cluster]?.[attribute]), }; } const DefaultTz = [ - tz.scene_store, tz.scene_recall, tz.scene_add, tz.scene_remove, tz.scene_remove_all, - tz.scene_rename, tz.read, tz.write, tz.command, tz.factory_reset, tz.zcl_command, + tz.scene_store, + tz.scene_recall, + tz.scene_add, + tz.scene_remove, + tz.scene_remove_all, + tz.scene_rename, + tz.read, + tz.write, + tz.command, + tz.factory_reset, + tz.zcl_command, ]; export type AssertDefinitionArgs = { - device: Zh.Device, - meta: DefinitionMeta | undefined, - fromZigbee: Fz.Converter[], - toZigbee: string[], - exposes: string[], - bind: {[s: number]: string[]}, - read: {[s: number]: [string, string[]][]}, - configureReporting: {[s: number]: [string, ReturnType[]][]}, - endpoints?: {[s: string]: number}, - findByDeviceFn?: (device: Device) => Promise, -} + device: Zh.Device; + meta: DefinitionMeta | undefined; + fromZigbee: Fz.Converter[]; + toZigbee: string[]; + exposes: string[]; + bind: {[s: number]: string[]}; + read: {[s: number]: [string, string[]][]}; + configureReporting: {[s: number]: [string, ReturnType[]][]}; + endpoints?: {[s: string]: number}; + findByDeviceFn?: (device: Device) => Promise; +}; export async function assertDefintion(args: AssertDefinitionArgs) { - args.findByDeviceFn = args.findByDeviceFn ?? findByDevice + args.findByDeviceFn = args.findByDeviceFn ?? findByDevice; const coordinatorEndpoint = mockEndpoint({}, undefined); const definition = await args.findByDeviceFn(args.device); @@ -91,7 +105,7 @@ export async function assertDefintion(args: AssertDefinitionArgs) { if (JSON.stringify(expected) !== JSON.stringify(actual)) { console.log(`[${expected?.map((c) => `'${c}'`).join(', ')}]`); } - } + }; expect(definition.meta).toEqual(args.meta); expect(definition.fromZigbee).toEqual(args.fromZigbee); @@ -102,7 +116,9 @@ export async function assertDefintion(args: AssertDefinitionArgs) { expect(expectedToZigbee).toEqual(args.toZigbee); utils.assertArray(definition.exposes); - const expectedExposes = definition.exposes?.map((e) => e.name ?? `${e.type}${e.endpoint ? '_' + e.endpoint : ''}(${e.features?.map((f) => f.name).join(',')})`).sort(); + const expectedExposes = definition.exposes + ?.map((e) => e.name ?? `${e.type}${e.endpoint ? '_' + e.endpoint : ''}(${e.features?.map((f) => f.name).join(',')})`) + .sort(); logIfNotEqual(expectedExposes, args.exposes); expect(expectedExposes).toEqual(args.exposes); @@ -111,20 +127,22 @@ export async function assertDefintion(args: AssertDefinitionArgs) { if (args.bind[endpoint.ID]) { args.bind[endpoint.ID].forEach((bind, idx) => expect(endpoint.bind).toHaveBeenNthCalledWith(idx + 1, bind, coordinatorEndpoint)); } - + expect(endpoint.read).toHaveBeenCalledTimes(args.read[endpoint.ID]?.length ?? 0); if (args.read[endpoint.ID]) { args.read[endpoint.ID].forEach((read, idx) => expect(endpoint.read).toHaveBeenNthCalledWith(idx + 1, read[0], read[1])); } - + expect(endpoint.configureReporting).toHaveBeenCalledTimes(args.configureReporting[endpoint.ID]?.length ?? 0); if (args.configureReporting[endpoint.ID]) { - args.configureReporting[endpoint.ID].forEach((configureReporting, idx) => expect(endpoint.configureReporting).toHaveBeenNthCalledWith(idx + 1, configureReporting[0], configureReporting[1])); + args.configureReporting[endpoint.ID].forEach((configureReporting, idx) => + expect(endpoint.configureReporting).toHaveBeenNthCalledWith(idx + 1, configureReporting[0], configureReporting[1]), + ); } } if (definition.endpoint) { // @ts-expect-error - expect(definition.endpoint()).toStrictEqual(args.endpoints) + expect(definition.endpoint()).toStrictEqual(args.endpoints); } }