UNPKG

zigbee-herdsman-converters

Version:

Collection of device converters to be used with zigbee-herdsman

984 lines 50.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const zigbee_herdsman_1 = require("zigbee-herdsman"); const exposes = __importStar(require("../lib/exposes")); const fromZigbee_1 = __importDefault(require("../converters/fromZigbee")); const toZigbee_1 = __importDefault(require("../converters/toZigbee")); const constants = __importStar(require("../lib/constants")); const reporting = __importStar(require("../lib/reporting")); const modernExtend_1 = require("../lib/modernExtend"); const utils = __importStar(require("../lib/utils")); const logger_1 = require("../lib/logger"); const ewelink_1 = require("../lib/ewelink"); const { ewelinkAction } = ewelink_1.modernExtend; const NS = 'zhc:sonoff'; const e = exposes.presets; const ea = exposes.access; const fzLocal = { router_config: { cluster: 'genLevelCtrl', type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data.hasOwnProperty('currentLevel')) { result.light_indicator_level = msg.data['currentLevel']; } }, }, }; const sonoffPrivateCluster = 0xFC11; const sonoffExtend = { inchingControlSet: () => { 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.binary('inching_mode', ea.SET, 'ON', 'OFF').withDescription('Set inching off or inching on mode.').withValueToggle('ON')); const fromZigbee = [{ cluster: sonoffPrivateCluster.toString(), type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { }, }]; const toZigbee = [{ key: ['inching_control_set'], convertSet: async (entity, key, value, meta) => { const inchingControl = 'inching_control'; const inchingTime = 'inching_time'; const inchingMode = 'inching_mode'; const tmpTime = Number((Math.round(Number((value[inchingTime] * 2).toFixed(1)))).toFixed(1)); 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] != 'DISABLE') { payloadValue[4] |= 0x80; } if (value[inchingMode] != 'OFF') { payloadValue[4] |= 0x01; } payloadValue[5] = 0x00; // Channel payloadValue[6] = tmpTime; // Timeout payloadValue[7] = tmpTime >> 8; payloadValue[8] = 0x00; // Reserve payloadValue[9] = 0x00; payloadValue[10] = 0x00; // CheckCode for (let i = 0; i < (payloadValue[2] + 3); i++) { payloadValue[10] ^= payloadValue[i]; } await entity.command(sonoffPrivateCluster, 0x01, { data: payloadValue }, { manufacturerCode: 0x1286 }); return { state: { [key]: value } }; }, }]; return { exposes: [exposes], fromZigbee, toZigbee, isModernExtend: true, }; }, weeklySchedule: () => { 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)) .withFeature(e.text('wednesday', ea.STATE_SET)) .withFeature(e.text('thursday', ea.STATE_SET)) .withFeature(e.text('friday', ea.STATE_SET)) .withFeature(e.text('saturday', ea.STATE_SET)); const fromZigbee = [{ 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) => { 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 { weekly_schedule: { ...meta.state.weekly_schedule, [day]: transitions, }, }; }, }]; const toZigbee = [{ 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); 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}`); } const dayOfWeekBit = Number(dayKey); const transitions = value[dayOfWeekName].split(' ').sort(); if (transitions.length > 6) { throw new Error('Invalid schedule: days must have no more than 6 transitions'); } const payload = { dayofweek: (1 << Number(dayOfWeekBit)), numoftrans: transitions.length, mode: (1 << 0), // heat transitions: [], }; 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); } 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}`); } 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'); } await entity.command('hvacThermostat', 'setWeeklySchedule', payload, utils.getOptions(meta.mapped, entity)); } }, }]; return { exposes: [exposes], fromZigbee, toZigbee, isModernExtend: true, }; }, cyclicTimedIrrigation: () => { 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 = [{ cluster: sonoffPrivateCluster.toString(), 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 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); return { cyclic_timed_irrigation: { current_count: currentCountBuffer, total_number: totalNumberBuffer, irrigation_duration: irrigationDurationBuffer, irrigation_interval: irrigationIntervalBuffer, }, }; } }, }]; const toZigbee = [{ 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 = 'total_number'; // logger.debug(`to zigbee cyclic_timed_irrigation ${value[totalNumber as keyof typeof value]}`, NS); const irrigationDuration = 'irrigation_duration'; // logger.debug(`to zigbee cyclic_timed_irrigation ${value[irrigationDuration as keyof typeof value]}`, NS); const irrigationInterval = '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]; payloadValue[3] = value[irrigationDuration] >> 24; payloadValue[4] = value[irrigationDuration] >> 16; payloadValue[5] = value[irrigationDuration] >> 8; payloadValue[6] = value[irrigationDuration]; payloadValue[7] = value[irrigationInterval] >> 24; payloadValue[8] = value[irrigationInterval] >> 16; payloadValue[9] = value[irrigationInterval] >> 8; payloadValue[10] = value[irrigationInterval]; const payload = { [0x5008]: { value: payloadValue, type: 0x42 } }; await entity.write(sonoffPrivateCluster, payload); return { state: { [key]: value } }; }, convertGet: async (entity, key, meta) => { await entity.read(sonoffPrivateCluster, [0x5008]); }, }]; return { exposes: [exposes], fromZigbee, toZigbee, isModernExtend: true, }; }, cyclicQuantitativeIrrigation: () => { 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 = [{ cluster: sonoffPrivateCluster.toString(), 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 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); return { cyclic_quantitative_irrigation: { current_count: currentCountBuffer, total_number: totalNumberBuffer, irrigation_capacity: irrigationCapacityBuffer, irrigation_interval: irrigationIntervalBuffer, }, }; } }, }]; const toZigbee = [{ 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 = 'total_number'; // logger.debug(`to zigbee cyclic_Quantitative_irrigation ${value[totalNumber as keyof typeof value]}`, NS); const irrigationCapacity = 'irrigation_capacity'; // logger.debug(`to zigbee cyclic_Quantitative_irrigation ${value[irrigationCapacity as keyof typeof value]}`, NS); const irrigationInterval = '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]; payloadValue[3] = value[irrigationCapacity] >> 24; payloadValue[4] = value[irrigationCapacity] >> 16; payloadValue[5] = value[irrigationCapacity] >> 8; payloadValue[6] = value[irrigationCapacity]; payloadValue[7] = value[irrigationInterval] >> 24; payloadValue[8] = value[irrigationInterval] >> 16; payloadValue[9] = value[irrigationInterval] >> 8; payloadValue[10] = value[irrigationInterval]; const payload = { [0x5009]: { value: payloadValue, type: 0x42 } }; await entity.write(sonoffPrivateCluster, payload); return { state: { [key]: value } }; }, convertGet: async (entity, key, meta) => { await entity.read(sonoffPrivateCluster, [0x5009]); }, }]; return { exposes: [exposes], fromZigbee, toZigbee, isModernExtend: true, }; }, }; const definitions = [ { zigbeeModel: ['NSPanelP-Router'], model: 'NSPanelP-Router', vendor: 'SONOFF', description: 'Router', fromZigbee: [fromZigbee_1.default.linkquality_from_basic], toZigbee: [], exposes: [], }, { zigbeeModel: ['BASICZBR3'], model: 'BASICZBR3', vendor: 'SONOFF', description: 'Zigbee smart switch', // configureReporting fails for this device extend: [(0, modernExtend_1.onOff)({ powerOnBehavior: false, skipDuplicateTransaction: true, configureReporting: false })], }, { zigbeeModel: ['ZBMINI-L'], model: 'ZBMINI-L', vendor: 'SONOFF', description: 'Zigbee smart switch (no neutral)', extend: [ (0, modernExtend_1.onOff)(), (0, modernExtend_1.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 // to poll slower. // https://github.com/Koenkk/zigbee2mqtt/issues/11676 await device.getEndpoint(1).unbind('genPollCtrl', coordinatorEndpoint); device.powerSource = 'Mains (single phase)'; device.save(); }, }, { zigbeeModel: ['ZBMINIL2'], model: 'ZBMINIL2', vendor: 'SONOFF', description: 'Zigbee smart switch (no neutral)', extend: [ (0, modernExtend_1.onOff)(), (0, modernExtend_1.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 // to poll slower. // https://github.com/Koenkk/zigbee2mqtt/issues/11676 await device.getEndpoint(1).unbind('genPollCtrl', coordinatorEndpoint); device.powerSource = 'Mains (single phase)'; device.save(); }, }, { zigbeeModel: ['01MINIZB'], model: 'ZBMINI', vendor: 'SONOFF', description: 'Zigbee two way smart switch', extend: [(0, modernExtend_1.onOff)({ powerOnBehavior: false }), (0, modernExtend_1.forcePowerSource)({ powerSource: 'Mains (single phase)' })], }, { zigbeeModel: ['S31 Lite zb'], model: 'S31ZB', vendor: 'SONOFF', description: 'Zigbee smart plug (US version)', extend: [(0, modernExtend_1.onOff)({ powerOnBehavior: false, skipDuplicateTransaction: true, configureReporting: false })], configure: async (device, coordinatorEndpoint) => { // Device does not support configureReporting for onOff, therefore just bind here. // https://github.com/Koenkk/zigbee2mqtt/issues/20618 const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']); }, }, { fingerprint: [ // 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] }, ], }, ], zigbeeModel: ['DS01', 'SNZB-04'], model: 'SNZB-04', vendor: 'SONOFF', whiteLabel: [{ vendor: 'eWeLink', model: 'RHK06' }], description: 'Contact sensor', exposes: [e.contact(), e.battery_low(), e.battery(), e.battery_voltage()], fromZigbee: [fromZigbee_1.default.ias_contact_alarm_1, fromZigbee_1.default.battery], toZigbee: [], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']); await reporting.batteryVoltage(endpoint, { min: 3600, max: 7200 }); await reporting.batteryPercentageRemaining(endpoint, { min: 3600, max: 7200 }); }, }, { zigbeeModel: ['WB01', 'WB-01'], model: 'SNZB-01', vendor: 'SONOFF', whiteLabel: [{ vendor: 'eWeLink', model: 'RHK07' }], description: 'Wireless button', exposes: [e.battery(), e.action(['single', 'double', 'long']), e.battery_voltage()], fromZigbee: [fromZigbee_1.default.ewelink_action, fromZigbee_1.default.battery], toZigbee: [], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genPowerCfg']); await reporting.batteryVoltage(endpoint, { min: 3600, max: 7200 }); await reporting.batteryPercentageRemaining(endpoint, { min: 3600, max: 7200 }); }, }, { fingerprint: [ // 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: '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] }, ], }, ], zigbeeModel: ['TH01'], model: 'SNZB-02', vendor: 'SONOFF', whiteLabel: [{ vendor: 'eWeLink', model: 'RHK08' }], description: 'Temperature and humidity sensor', exposes: [e.battery(), e.temperature(), e.humidity(), e.battery_voltage()], fromZigbee: [fromZigbee_1.default.SNZB02_temperature, fromZigbee_1.default.humidity, fromZigbee_1.default.battery], toZigbee: [], configure: async (device, coordinatorEndpoint) => { try { const endpoint = device.getEndpoint(1); const bindClusters = ['msTemperatureMeasurement', 'msRelativeHumidity', 'genPowerCfg']; await reporting.bind(endpoint, coordinatorEndpoint, bindClusters); await reporting.temperature(endpoint, { min: 30, max: constants.repInterval.MINUTES_5, change: 20 }); 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 */ logger_1.logger.error(`Configure failed: ${e}`, NS); } }, }, { zigbeeModel: ['SNZB-02D'], model: 'SNZB-02D', vendor: 'SONOFF', description: 'Temperature and humidity sensor with screen', exposes: [e.battery(), e.temperature(), e.humidity()], fromZigbee: [fromZigbee_1.default.temperature, fromZigbee_1.default.humidity, fromZigbee_1.default.battery], toZigbee: [], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const bindClusters = ['msTemperatureMeasurement', 'msRelativeHumidity', 'genPowerCfg']; await reporting.bind(endpoint, coordinatorEndpoint, bindClusters); await reporting.temperature(endpoint, { min: 30, max: constants.repInterval.MINUTES_5, change: 20 }); await reporting.humidity(endpoint, { min: 30, max: constants.repInterval.MINUTES_5, change: 100 }); await reporting.batteryPercentageRemaining(endpoint, { min: 3600, max: 7200 }); device.powerSource = 'Battery'; device.save(); }, }, { fingerprint: [ { 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] }, ], }, ], zigbeeModel: ['MS01', 'MSO1'], model: 'SNZB-03', vendor: 'SONOFF', whiteLabel: [{ vendor: 'eWeLink', model: 'RHK09' }], description: 'Motion sensor', fromZigbee: [fromZigbee_1.default.ias_occupancy_alarm_1, fromZigbee_1.default.battery], toZigbee: [], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const bindClusters = ['genPowerCfg']; await reporting.bind(endpoint, coordinatorEndpoint, bindClusters); // 3600/7200 prevents disconnect // https://github.com/Koenkk/zigbee2mqtt/issues/13600#issuecomment-1283827935 await reporting.batteryVoltage(endpoint, { min: 3600, max: 7200 }); await reporting.batteryPercentageRemaining(endpoint, { min: 3600, max: 7200 }); }, exposes: [e.occupancy(), e.battery_low(), e.battery(), e.battery_voltage()], }, { zigbeeModel: ['S26R2ZB'], model: 'S26R2ZB', vendor: 'SONOFF', description: 'Zigbee smart plug', extend: [(0, modernExtend_1.onOff)({ powerOnBehavior: false })], }, { zigbeeModel: ['S40LITE'], model: 'S40ZBTPB', vendor: 'SONOFF', description: '15A Zigbee smart plug', extend: [ (0, modernExtend_1.onOff)({ powerOnBehavior: false, skipDuplicateTransaction: true }), (0, modernExtend_1.ota)(), ], }, { zigbeeModel: ['DONGLE-E_R'], model: 'ZBDongle-E', vendor: 'SONOFF', description: 'Sonoff Zigbee 3.0 USB Dongle Plus (EFR32MG21) with router firmware', fromZigbee: [fromZigbee_1.default.linkquality_from_basic, fzLocal.router_config], toZigbee: [], exposes: [e.numeric('light_indicator_level', ea.STATE).withDescription('Brightness of the indicator light').withAccess(ea.STATE)], configure: async (device, coordinatorEndpoint) => { device.powerSource = 'Mains (single phase)'; device.save(); }, }, { zigbeeModel: ['ZBCurtain'], model: 'ZBCurtain', vendor: 'SONOFF', description: 'Zigbee smart curtain motor', fromZigbee: [fromZigbee_1.default.cover_position_tilt, fromZigbee_1.default.battery], toZigbee: [toZigbee_1.default.cover_state, toZigbee_1.default.cover_position_tilt], exposes: [e.cover_position(), e.battery()], }, { zigbeeModel: ['Z111PL0H-1JX', 'SA-029-1'], model: 'SA-028/SA-029', vendor: 'SONOFF', whiteLabel: [{ vendor: 'Woolley', model: 'SA-029-1' }], description: 'Smart Plug', extend: [(0, modernExtend_1.onOff)()], }, { zigbeeModel: ['SNZB-01P'], model: 'SNZB-01P', vendor: 'SONOFF', description: 'Wireless button', extend: [ (0, modernExtend_1.forcePowerSource)({ powerSource: 'Battery' }), ewelinkAction(), (0, modernExtend_1.battery)({ percentageReportingConfig: { min: 3600, max: 7200, change: 0 }, voltage: true, voltageReporting: true, voltageReportingConfig: { min: 3600, max: 7200, change: 0 }, }), (0, modernExtend_1.ota)(), ], }, { zigbeeModel: ['SNZB-02P'], model: 'SNZB-02P', vendor: 'SONOFF', description: 'Temperature and humidity sensor', exposes: [e.battery(), e.temperature(), e.humidity(), e.battery_low(), e.battery_voltage()], fromZigbee: [fromZigbee_1.default.temperature, fromZigbee_1.default.humidity, fromZigbee_1.default.battery], configure: async (device, coordinatorEndpoint) => { try { const endpoint = device.getEndpoint(1); const bindClusters = ['msTemperatureMeasurement', 'msRelativeHumidity', 'genPowerCfg']; await reporting.bind(endpoint, coordinatorEndpoint, bindClusters); await reporting.temperature(endpoint, { min: 30, max: constants.repInterval.MINUTES_5, change: 20 }); await reporting.humidity(endpoint, { min: 30, max: constants.repInterval.MINUTES_5, change: 100 }); await reporting.batteryPercentageRemaining(endpoint, { min: 3600, max: 7200 }); } catch (e) { /* Not required for all: https://github.com/Koenkk/zigbee2mqtt/issues/5562 */ logger_1.logger.error(`Configure failed: ${e}`, NS); } }, extend: [ (0, modernExtend_1.ota)(), ], }, { zigbeeModel: ['SNZB-04P'], model: 'SNZB-04P', vendor: 'SONOFF', description: 'Contact sensor', exposes: [e.contact(), e.battery_low(), e.battery(), e.battery_voltage()], fromZigbee: [fromZigbee_1.default.ias_contact_alarm_1, fromZigbee_1.default.battery], extend: [ (0, modernExtend_1.binary)({ name: 'tamper', cluster: 0xFC11, attribute: { ID: 0x2000, type: 0x20 }, description: 'Tamper-proof status', valueOn: [true, 0x01], valueOff: [false, 0x00], zigbeeCommandOptions: { manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SHENZHEN_COOLKIT_TECHNOLOGY_CO_LTD }, access: 'STATE_GET', }), (0, modernExtend_1.ota)(), ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']); await reporting.batteryVoltage(endpoint, { min: 3600, max: 7200 }); await reporting.batteryPercentageRemaining(endpoint, { min: 3600, max: 7200 }); }, }, { zigbeeModel: ['SNZB-03P'], model: 'SNZB-03P', vendor: 'SONOFF', description: 'Zigbee PIR sensor', fromZigbee: [fromZigbee_1.default.occupancy, fromZigbee_1.default.battery], exposes: [e.occupancy(), e.battery_low(), e.battery()], extend: [ (0, modernExtend_1.numeric)({ name: 'motion_timeout', cluster: 0x0406, attribute: { ID: 0x0020, type: 0x21 }, description: 'Unoccupied to occupied delay', valueMin: 5, valueMax: 60, }), (0, modernExtend_1.enumLookup)({ name: 'illumination', lookup: { 'dim': 0, 'bright': 1 }, cluster: 0xFC11, attribute: { ID: 0x2001, type: 0x20 }, zigbeeCommandOptions: { manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SHENZHEN_COOLKIT_TECHNOLOGY_CO_LTD }, description: 'Only updated when occupancy is detected', access: 'STATE', }), (0, modernExtend_1.ota)(), ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']); await reporting.batteryVoltage(endpoint, { min: 3600, max: 7200 }); await reporting.batteryPercentageRemaining(endpoint, { min: 3600, max: 7200 }); }, }, { zigbeeModel: ['SNZB-06P'], model: 'SNZB-06P', vendor: 'SONOFF', description: 'Zigbee occupancy sensor', fromZigbee: [fromZigbee_1.default.occupancy], exposes: [e.occupancy()], extend: [ (0, modernExtend_1.numeric)({ name: 'occupancy_timeout', cluster: 0x0406, attribute: { ID: 0x0020, type: 0x21 }, description: 'Unoccupied to occupied delay', valueMin: 15, valueMax: 65535, }), (0, modernExtend_1.enumLookup)({ name: 'occupancy_sensitivity', lookup: { 'low': 1, 'medium': 2, 'high': 3 }, cluster: 0x0406, attribute: { ID: 0x0022, type: 0x20 }, description: 'Sensitivity of human presence detection', }), (0, modernExtend_1.enumLookup)({ name: 'illumination', lookup: { 'dim': 0, 'bright': 1 }, cluster: 0xFC11, attribute: { ID: 0x2001, type: 0x20 }, description: 'Only updated when occupancy is detected', zigbeeCommandOptions: { manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SHENZHEN_COOLKIT_TECHNOLOGY_CO_LTD }, access: 'STATE', }), (0, modernExtend_1.ota)(), ], }, { zigbeeModel: ['TRVZB'], model: 'TRVZB', vendor: 'SONOFF', description: 'Zigbee thermostatic radiator valve', exposes: [ e.climate() .withSetpoint('occupied_heating_setpoint', 4, 35, 0.5) .withLocalTemperature() .withLocalTemperatureCalibration(-7.0, 7.0, 0.2) .withSystemMode(['off', 'auto', 'heat'], ea.ALL, 'Mode of the thermostat') .withRunningState(['idle', 'heat'], ea.STATE_GET), e.battery(), e.battery_low(), ], fromZigbee: [ fromZigbee_1.default.thermostat, fromZigbee_1.default.battery, ], toZigbee: [ toZigbee_1.default.thermostat_local_temperature, toZigbee_1.default.thermostat_local_temperature_calibration, toZigbee_1.default.thermostat_occupied_heating_setpoint, toZigbee_1.default.thermostat_system_mode, toZigbee_1.default.thermostat_running_state, ], extend: [ (0, modernExtend_1.binary)({ name: 'child_lock', cluster: 'customSonoffTrvzb', attribute: 'childLock', description: 'Enables/disables physical input on the device', valueOn: ['LOCK', 0x01], valueOff: ['UNLOCK', 0x00], }), (0, modernExtend_1.binary)({ name: 'open_window', cluster: 'customSonoffTrvzb', attribute: 'openWindow', description: 'Automatically turns off the radiator when local temperature drops by more than 1.5°C in 4.5 minutes.', valueOn: ['ON', 0x01], valueOff: ['OFF', 0x00], }), (0, modernExtend_1.numeric)({ 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.', valueMin: 4.0, valueMax: 35.0, valueStep: 0.5, unit: '°C', scale: 100, }), (0, modernExtend_1.numeric)({ name: 'idle_steps', cluster: 'customSonoffTrvzb', attribute: 'idleSteps', description: 'Number of steps used for calibration (no-load steps)', access: 'STATE_GET', }), (0, modernExtend_1.numeric)({ name: 'closing_steps', cluster: 'customSonoffTrvzb', attribute: 'closingSteps', description: 'Number of steps it takes to close the valve', access: 'STATE_GET', }), (0, modernExtend_1.numeric)({ name: 'valve_opening_limit_voltage', cluster: 'customSonoffTrvzb', attribute: 'valveOpeningLimitVoltage', description: 'Valve opening limit voltage', unit: 'mV', access: 'STATE_GET', }), (0, modernExtend_1.numeric)({ name: 'valve_closing_limit_voltage', cluster: 'customSonoffTrvzb', attribute: 'valveClosingLimitVoltage', description: 'Valve closing limit voltage', unit: 'mV', access: 'STATE_GET', }), (0, modernExtend_1.numeric)({ name: 'valve_motor_running_voltage', cluster: 'customSonoffTrvzb', attribute: 'valveMotorRunningVoltage', description: 'Valve motor running voltage', unit: 'mV', access: 'STATE_GET', }), (0, modernExtend_1.numeric)({ name: 'valve_opening_degree', cluster: 'customSonoffTrvzb', attribute: 'valveOpeningDegree', 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%. ' + 'Note: only version v1.1.4 or higher is supported.', valueMin: 0.0, valueMax: 100.0, valueStep: 1.0, unit: '%', }), (0, modernExtend_1.numeric)({ name: 'valve_closing_degree', cluster: 'customSonoffTrvzb', attribute: 'valveClosingDegree', 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%. ' + 'Note: Only version v1.1.4 or higher is supported.', valueMin: 0.0, valueMax: 100.0, valueStep: 1.0, unit: '%', }), sonoffExtend.weeklySchedule(), (0, modernExtend_1.customTimeResponse)('1970_UTC'), (0, modernExtend_1.ota)(), ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['hvacThermostat']); await reporting.thermostatTemperature(endpoint); 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]); }, onEvent: async (type, data, device, settings, state) => { const name = 'customSonoffTrvzb'; if (!device.customClusters[name]) { device.addCustomCluster(name, { ID: 0xfc11, attributes: { childLock: { ID: 0x0000, type: zigbee_herdsman_1.Zcl.DataType.boolean }, tamper: { ID: 0x2000, type: zigbee_herdsman_1.Zcl.DataType.uint8 }, illumination: { ID: 0x2001, type: zigbee_herdsman_1.Zcl.DataType.uint8 }, openWindow: { ID: 0x6000, type: zigbee_herdsman_1.Zcl.DataType.boolean }, frostProtectionTemperature: { ID: 0x6002, type: zigbee_herdsman_1.Zcl.DataType.int16 }, idleSteps: { ID: 0x6003, type: zigbee_herdsman_1.Zcl.DataType.uint16 }, closingSteps: { ID: 0x6004, type: zigbee_herdsman_1.Zcl.DataType.uint16 }, valveOpeningLimitVoltage: { ID: 0x6005, type: zigbee_herdsman_1.Zcl.DataType.uint16 }, valveClosingLimitVoltage: { ID: 0x6006, type: zigbee_herdsman_1.Zcl.DataType.uint16 }, valveMotorRunningVoltage: { ID: 0x6007, type: zigbee_herdsman_1.Zcl.DataType.uint16 }, valveOpeningDegree: { ID: 0x600B, type: zigbee_herdsman_1.Zcl.DataType.uint8 }, valveClosingDegree: { ID: 0x600C, type: zigbee_herdsman_1.Zcl.DataType.uint8 }, }, commands: {}, commandsResponse: {}, }); } }, }, { zigbeeModel: ['S60ZBTPF'], model: 'S60ZBTPF', vendor: 'SONOFF', description: 'Zigbee smart plug', extend: [(0, modernExtend_1.onOff)()], }, { zigbeeModel: ['S60ZBTPG'], model: 'S60ZBTPG', vendor: 'SONOFF', description: 'Zigbee smart plug', extend: [(0, modernExtend_1.onOff)()], }, { zigbeeModel: ['SWV'], model: 'SWV', vendor: 'SONOFF', description: 'Zigbee smart water valve', fromZigbee: [ fromZigbee_1.default.flow, ], exposes: [ e.numeric('flow', ea.STATE).withDescription('Current water flow').withUnit('m³/h'), ], extend: [ (0, modernExtend_1.battery)(), (0, modernExtend_1.enumLookup)({ name: 'current_device_status', lookup: { 'normal_state': 0, 'water_shortage': 1, 'water_leakage': 2, 'water_shortage & water_leakage': 3 }, cluster: 0xFC11, attribute: { ID: 0x500C, type: 0x20 }, description: 'The water valve is in normal state, water shortage or water leakage', access: 'STATE_GET', }), (0, modernExtend_1.onOff)({ powerOnBehavior: false, skipDuplicateTransaction: true, configureReporting: true, }), sonoffExtend.cyclicTimedIrrigation(), sonoffExtend.cyclicQuantitativeIrrigation(), (0, modernExtend_1.ota)(), ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']); await reporting.bind(endpoint, coordinatorEndpoint, ['msFlowMeasurement']); await endpoint.read(0xFC11, [0x500C]); }, }, { zigbeeModel: ['ZBMicro'], model: 'ZBMicro', vendor: 'SONOFF', description: 'Zigbee USB repeater plug', extend: [ (0, modernExtend_1.onOff)(), (0, modernExtend_1.binary)({ name: 'rf_turbo_mode', cluster: 0xFC11, attribute: { ID: 0x0012, type: 0x29 }, zigbeeCommandOptions: { manufacturerCode: 0x1286 }, description: 'Enable/disable Radio power turbo mode', valueOff: [false, 0x09], valueOn: [true, 0x14], }), sonoffExtend.inchingContr