UNPKG

zigbee-herdsman-converters

Version:

Collection of device converters to be used with zigbee-herdsman

891 lines 90.5 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 legacy = __importStar(require("../lib/legacy")); const toZigbee_1 = __importDefault(require("../converters/toZigbee")); const constants = __importStar(require("../lib/constants")); const utils = __importStar(require("../lib/utils")); const reporting = __importStar(require("../lib/reporting")); const e = exposes.presets; const ea = exposes.access; const utils_1 = require("../lib/utils"); const modernExtend_1 = require("../lib/modernExtend"); const manuSinope = { manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SINOPE_TECHNOLOGIES }; const fzLocal = { ias_water_leak_alarm: { // RM3500ZB specific cluster: 'ssIasZone', type: ['commandStatusChangeNotification', 'attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { const zoneStatus = msg.data.zoneStatus; return { water_leak: (zoneStatus & 1) > 0, tamper: (zoneStatus & 1 << 2) > 0, }; }, }, thermostat: { cluster: 'hvacThermostat', type: ['attributeReport', 'readResponse'], options: [exposes.options.legacy()], convert: (model, msg, publish, options, meta) => { // @ts-expect-error delete msg['running_state']; const result = {}; 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' }; if (msg.data.hasOwnProperty('1024')) { result.thermostat_occupancy = utils.getFromLookup(msg.data['1024'], occupancyLookup); } if (msg.data.hasOwnProperty('SinopeOccupancy')) { result.thermostat_occupancy = utils.getFromLookup(msg.data['SinopeOccupancy'], occupancyLookup); } if (msg.data.hasOwnProperty('1025')) { result.main_cycle_output = utils.getFromLookup(msg.data['1025'], cycleOutputLookup); } if (msg.data.hasOwnProperty('SinopeMainCycleOutput')) { result.main_cycle_output = utils.getFromLookup(msg.data['SinopeMainCycleOutput'], cycleOutputLookup); } if (msg.data.hasOwnProperty('1026')) { const lookup = { 0: 'on_demand', 1: 'sensing' }; result.backlight_auto_dim = utils.getFromLookup(msg.data['1026'], lookup); } if (msg.data.hasOwnProperty('SinopeBacklight')) { const lookup = { 0: 'on_demand', 1: 'sensing' }; result.backlight_auto_dim = utils.getFromLookup(msg.data['SinopeBacklight'], lookup); } if (msg.data.hasOwnProperty('1028')) { result.aux_cycle_output = utils.getFromLookup(msg.data['1028'], cycleOutputLookup); } if (msg.data.hasOwnProperty('localTemp')) { result.local_temperature = (0, utils_1.precisionRound)(msg.data['localTemp'], 2) / 100; } if (msg.data.hasOwnProperty('localTemperatureCalibration')) { result.local_temperature_calibration = (0, utils_1.precisionRound)(msg.data['localTemperatureCalibration'], 2) / 10; } if (msg.data.hasOwnProperty('outdoorTemp')) { result.outdoor_temperature = (0, utils_1.precisionRound)(msg.data['outdoorTemp'], 2) / 100; } if (msg.data.hasOwnProperty('occupiedHeatingSetpoint')) { result.occupied_heating_setpoint = (0, utils_1.precisionRound)(msg.data['occupiedHeatingSetpoint'], 2) / 100; } if (msg.data.hasOwnProperty('unoccupiedHeatingSetpoint')) { result.unoccupied_heating_setpoint = (0, utils_1.precisionRound)(msg.data['unoccupiedHeatingSetpoint'], 2) / 100; } if (msg.data.hasOwnProperty('occupiedCoolingSetpoint')) { result.occupied_cooling_setpoint = (0, utils_1.precisionRound)(msg.data['occupiedCoolingSetpoint'], 2) / 100; } if (msg.data.hasOwnProperty('unoccupiedCoolingSetpoint')) { result.unoccupied_cooling_setpoint = (0, utils_1.precisionRound)(msg.data['unoccupiedCoolingSetpoint'], 2) / 100; } if (msg.data.hasOwnProperty('ctrlSeqeOfOper')) { result.control_sequence_of_operation = constants.thermostatControlSequenceOfOperations[msg.data['ctrlSeqeOfOper']]; } if (msg.data.hasOwnProperty('systemMode')) { result.system_mode = constants.thermostatSystemModes[msg.data['systemMode']]; } if (msg.data.hasOwnProperty('pIHeatingDemand')) { result.pi_heating_demand = (0, utils_1.precisionRound)(msg.data['pIHeatingDemand'], 0); } if (msg.data.hasOwnProperty('minHeatSetpointLimit')) { result.min_heat_setpoint_limit = (0, utils_1.precisionRound)(msg.data['minHeatSetpointLimit'], 2) / 100; } if (msg.data.hasOwnProperty('maxHeatSetpointLimit')) { result.max_heat_setpoint_limit = (0, utils_1.precisionRound)(msg.data['maxHeatSetpointLimit'], 2) / 100; } if (msg.data.hasOwnProperty('absMinHeatSetpointLimit')) { result.abs_min_heat_setpoint_limit = (0, utils_1.precisionRound)(msg.data['absMinHeatSetpointLimit'], 2) / 100; } if (msg.data.hasOwnProperty('absMaxHeatSetpointLimit')) { result.abs_max_heat_setpoint_limit = (0, utils_1.precisionRound)(msg.data['absMaxHeatSetpointLimit'], 2) / 100; } if (msg.data.hasOwnProperty('pIHeatingDemand')) { result.running_state = msg.data['pIHeatingDemand'] >= 10 ? 'heat' : 'idle'; } return result; }, }, tank_level: { cluster: 'genAnalogInput', type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data.hasOwnProperty('presentValue')) { let x = msg.data['presentValue']; if (x == -1) { result.tank_level = 0; } else { const xMin = 110; const xMax = 406; const delta = 46; if (delta <= x && x <= 70) { x = delta; } if (0 <= x && x <= delta) { x = x + 360; } const y = (x - xMin) / (xMax - xMin); const lowerLimit = 10; const upperLimit = 80; const valueRange = upperLimit - lowerLimit; const pct = y * valueRange + lowerLimit; result.tank_level = utils.precisionRound(pct, 2); } } return result; }, }, sinope: { cluster: 'manuSpecificSinope', type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data.hasOwnProperty('GFCiStatus')) { const lookup = { 0: 'off', 1: 'on' }; result.gfci_status = utils.getFromLookup(msg.data['GFCiStatus'], lookup); } if (msg.data.hasOwnProperty('floorLimitStatus')) { const lookup = { 0: 'off', 1: 'on' }; result.floor_limit_status = utils.getFromLookup(msg.data['floorLimitStatus'], lookup); } if (msg.data.hasOwnProperty('secondScreenBehavior')) { const lookup = { 0: 'auto', 1: 'setpoint', 2: 'outdoor temp' }; result.second_display_mode = utils.getFromLookup(msg.data['secondScreenBehavior'], lookup); } if (msg.data.hasOwnProperty('outdoorTempToDisplayTimeout')) { result.outdoor_temperature_timeout = msg.data['outdoorTempToDisplayTimeout']; // DEPRECATED: Use Second Display Mode or control via set outdoorTempToDisplayTimeout result.enable_outdoor_temperature = msg.data['outdoorTempToDisplayTimeout'] === 12 ? 'OFF' : 'ON'; } if (msg.data.hasOwnProperty('outdoorTempToDisplay')) { result.thermostat_outdoor_temperature = (0, utils_1.precisionRound)(msg.data['outdoorTempToDisplay'], 2) / 100; } if (msg.data.hasOwnProperty('currentTimeToDisplay')) { result.current_time_to_display = msg.data['currentTimeToDisplay']; } if (msg.data.hasOwnProperty('floorControlMode')) { const lookup = { 1: 'ambiant', 2: 'floor' }; result.floor_control_mode = utils.getFromLookup(msg.data['floorControlMode'], lookup); } if (msg.data.hasOwnProperty('ambiantMaxHeatSetpointLimit')) { result.ambiant_max_heat_setpoint = msg.data['ambiantMaxHeatSetpointLimit'] / 100.0; if (result.ambiant_max_heat_setpoint === -327.68) { result.ambiant_max_heat_setpoint = 'off'; } } if (msg.data.hasOwnProperty('floorMinHeatSetpointLimit')) { result.floor_min_heat_setpoint = msg.data['floorMinHeatSetpointLimit'] / 100.0; if (result.floor_min_heat_setpoint === -327.68) { result.floor_min_heat_setpoint = 'off'; } } if (msg.data.hasOwnProperty('floorMaxHeatSetpointLimit')) { result.floor_max_heat_setpoint = msg.data['floorMaxHeatSetpointLimit'] / 100.0; if (result.floor_max_heat_setpoint === -327.68) { result.floor_max_heat_setpoint = 'off'; } } if (msg.data.hasOwnProperty('temperatureSensor')) { const lookup = { 0: '10k', 1: '12k' }; result.floor_temperature_sensor = utils.getFromLookup(msg.data['temperatureSensor'], lookup); } if (msg.data.hasOwnProperty('timeFormatToDisplay')) { const lookup = { 0: '24h', 1: '12h' }; result.time_format = utils.getFromLookup(msg.data['timeFormatToDisplay'], lookup); } if (msg.data.hasOwnProperty('connectedLoad')) { result.connected_load = msg.data['connectedLoad']; } if (msg.data.hasOwnProperty('auxConnectedLoad')) { result.aux_connected_load = msg.data['auxConnectedLoad']; if (result.aux_connected_load == 65535) { result.aux_connected_load = 'disabled'; } } if (msg.data.hasOwnProperty('pumpProtection')) { result.pump_protection = msg.data['pumpProtection'] == 1 ? 'ON' : 'OFF'; } if (msg.data.hasOwnProperty('dimmerTimmer')) { result.timer_seconds = msg.data['dimmerTimmer']; } if (msg.data.hasOwnProperty('ledIntensityOn')) { result.led_intensity_on = msg.data['ledIntensityOn']; } if (msg.data.hasOwnProperty('ledIntensityOff')) { result.led_intensity_off = msg.data['ledIntensityOff']; } if (msg.data.hasOwnProperty('minimumBrightness')) { 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' }; result.action = utils.getFromLookup(msg.data['actionReport'], lookup); } if (msg.data.hasOwnProperty('keypadLockout')) { const lookup = { 0: 'unlock', 1: 'lock' }; result.keypad_lockout = utils.getFromLookup(msg.data['keypadLockout'], lookup); } if (msg.data.hasOwnProperty('drConfigWaterTempMin')) { result.low_water_temp_protection = msg.data['drConfigWaterTempMin']; } return result; }, }, }; const tzLocal = { thermostat_occupancy: { key: ['thermostat_occupancy'], convertSet: async (entity, key, value, meta) => { 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 } }; }, convertGet: async (entity, key, meta) => { await entity.read('hvacThermostat', ['SinopeOccupancy'], manuSinope); }, }, backlight_autodim: { key: ['backlight_auto_dim'], convertSet: async (entity, key, value, meta) => { 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 } }; }, convertGet: async (entity, key, meta) => { await entity.read('hvacThermostat', ['SinopeBacklight'], manuSinope); }, }, main_cycle_output: { key: ['main_cycle_output'], 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 } }; }, convertGet: async (entity, key, meta) => { await entity.read('hvacThermostat', ['SinopeMainCycleOutput'], manuSinope); }, }, aux_cycle_output: { // 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 }; await entity.write('hvacThermostat', { SinopeAuxCycleOutput: utils.getFromLookup(value, lookup) }); return { state: { 'aux_cycle_output': value } }; }, convertGet: async (entity, key, meta) => { await entity.read('hvacThermostat', ['SinopeAuxCycleOutput']); }, }, enable_outdoor_temperature: { key: ['enable_outdoor_temperature'], convertSet: async (entity, key, value, meta) => { utils.assertString(value); if (value.toLowerCase() == 'on') { await entity.write('manuSpecificSinope', { outdoorTempToDisplayTimeout: 10800 }, manuSinope); } else if (value.toLowerCase() == 'off') { // set timer to 12 sec in order to disable outdoor temperature await entity.write('manuSpecificSinope', { outdoorTempToDisplayTimeout: 12 }, manuSinope); } return { readAfterWriteTime: 250, state: { enable_outdoor_temperature: value } }; }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificSinope', ['outdoorTempToDisplayTimeout'], manuSinope); }, }, second_display_mode: { key: ['second_display_mode'], convertSet: async (entity, key, value, meta) => { const lookup = { 'auto': 0, 'setpoint': 1, 'outdoor temp': 2 }; await entity.write('manuSpecificSinope', { secondScreenBehavior: utils.getFromLookup(value, lookup) }); return { state: { second_display_mode: value } }; }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificSinope', ['secondScreenBehavior']); }, }, thermostat_outdoor_temperature: { key: ['thermostat_outdoor_temperature'], convertSet: async (entity, key, value, meta) => { const number = utils.toNumber(value); if (number >= -99.5 && number <= 99.5) { await entity.write('manuSpecificSinope', { outdoorTempToDisplay: number * 100 }, manuSinope); } return { state: { thermostat_outdoor_temperature: number } }; }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificSinope', ['outdoorTempToDisplay'], manuSinope); }, }, outdoor_temperature_timeout: { key: ['outdoor_temperature_timeout'], convertSet: async (entity, key, value, meta) => { const number = utils.toNumber(value); if (number >= 30 && number <= 64800) { await entity.write('manuSpecificSinope', { outdoorTempToDisplayTimeout: number }); return { state: { outdoor_temperature_timeout: number } }; } }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificSinope', ['outdoorTempToDisplayTimeout']); }, }, thermostat_time: { key: ['thermostat_time'], convertSet: async (entity, key, value, meta) => { if (value === '') { const thermostatDate = new Date(); const thermostatTimeSec = thermostatDate.getTime() / 1000; const thermostatTimezoneOffsetSec = thermostatDate.getTimezoneOffset() * 60; const currentTimeToDisplay = Math.round(thermostatTimeSec - thermostatTimezoneOffsetSec - 946684800); await entity.write('manuSpecificSinope', { currentTimeToDisplay }, manuSinope); } else if (value !== '') { await entity.write('manuSpecificSinope', { currentTimeToDisplay: value }, manuSinope); } }, }, floor_control_mode: { // TH1300ZB and TH1400ZB specific key: ['floor_control_mode'], convertSet: async (entity, key, value, meta) => { if (typeof value !== 'string') { return; } const lookup = { 'ambiant': 1, 'floor': 2 }; value = value.toLowerCase(); // @ts-expect-error if (lookup.hasOwnProperty(value)) { await entity.write('manuSpecificSinope', { floorControlMode: utils.getFromLookup(value, lookup) }); } return { readAfterWriteTime: 250, state: { floor_control_mode: value } }; }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificSinope', ['floorControlMode']); }, }, ambiant_max_heat_setpoint: { // TH1300ZB and TH1400ZB specific key: ['ambiant_max_heat_setpoint'], convertSet: async (entity, key, value, meta) => { // @ts-expect-error if ((value >= 5 && value <= 36) || value == 'off') { // @ts-expect-error await entity.write('manuSpecificSinope', { ambiantMaxHeatSetpointLimit: (value == 'off' ? -32768 : value * 100) }); return { readAfterWriteTime: 250, state: { ambiant_max_heat_setpoint: value } }; } }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificSinope', ['ambiantMaxHeatSetpointLimit']); }, }, floor_min_heat_setpoint: { // TH1300ZB and TH1400ZB specific key: ['floor_min_heat_setpoint'], convertSet: async (entity, key, value, meta) => { // @ts-expect-error if ((value >= 5 && value <= 34) || value == 'off') { // @ts-expect-error await entity.write('manuSpecificSinope', { floorMinHeatSetpointLimit: (value == 'off' ? -32768 : value * 100) }); return { readAfterWriteTime: 250, state: { floor_min_heat_setpoint: value } }; } }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificSinope', ['floorMinHeatSetpointLimit']); }, }, floor_max_heat_setpoint: { // TH1300ZB and TH1400ZB specific key: ['floor_max_heat_setpoint'], convertSet: async (entity, key, value, meta) => { // @ts-expect-error if ((value >= 7 && value <= 36) || value == 'off') { // @ts-expect-error await entity.write('manuSpecificSinope', { floorMaxHeatSetpointLimit: (value == 'off' ? -32768 : value * 100) }); return { readAfterWriteTime: 250, state: { floor_max_heat_setpoint: value } }; } }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificSinope', ['floorMaxHeatSetpointLimit']); }, }, temperature_sensor: { // TH1300ZB and TH1400ZB specific key: ['floor_temperature_sensor'], convertSet: async (entity, key, value, meta) => { if (typeof value !== 'string') { return; } const lookup = { '10k': 0, '12k': 1 }; value = value.toLowerCase(); // @ts-expect-error if (lookup.hasOwnProperty(value)) { await entity.write('manuSpecificSinope', { temperatureSensor: utils.getFromLookup(value, lookup) }); } return { readAfterWriteTime: 250, state: { floor_temperature_sensor: value } }; }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificSinope', ['temperatureSensor']); }, }, time_format: { key: ['time_format'], convertSet: async (entity, key, value, meta) => { if (typeof value !== 'string') { return; } const lookup = { '24h': 0, '12h': 1 }; value = value.toLowerCase(); utils.assertString(value); if (lookup.hasOwnProperty(value)) { await entity.write('manuSpecificSinope', { timeFormatToDisplay: utils.getFromLookup(value, lookup) }, manuSinope); return { readAfterWriteTime: 250, state: { time_format: value } }; } }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificSinope', ['timeFormatToDisplay'], manuSinope); }, }, connected_load: { // TH1400ZB and SW2500ZB key: ['connected_load'], convertSet: async (entity, key, value, meta) => { await entity.write('manuSpecificSinope', { connectedLoad: value }); return { state: { connected_load: value } }; }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificSinope', ['connectedLoad']); }, }, aux_connected_load: { // TH1400ZB specific key: ['aux_connected_load'], convertSet: async (entity, key, value, meta) => { await entity.write('manuSpecificSinope', { auxConnectedLoad: value }); return { readAfterWriteTime: 250, state: { aux_connected_load: value } }; }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificSinope', ['auxConnectedLoad']); }, }, pump_protection: { // TH1400ZB specific key: ['pump_protection'], convertSet: async (entity, key, value, meta) => { utils.assertString(value); if (value.toLowerCase() == 'on') { await entity.write('manuSpecificSinope', { pumpProtection: 1 }); } else if (value.toLowerCase() == 'off') { await entity.write('manuSpecificSinope', { pumpProtection: 255 }); } return { readAfterWriteTime: 250, state: { pump_protection: value } }; }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificSinope', ['pumpProtection']); }, }, led_intensity_on: { // DM25x0ZB and SW2500ZB key: ['led_intensity_on'], convertSet: async (entity, key, value, meta) => { const number = utils.toNumber(value); if (number >= 0 && number <= 100) { await entity.write('manuSpecificSinope', { ledIntensityOn: number }); } return { state: { led_intensity_on: number } }; }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificSinope', ['ledIntensityOn']); }, }, led_intensity_off: { // DM25x0ZB and SW2500ZB key: ['led_intensity_off'], convertSet: async (entity, key, value, meta) => { const number = utils.toNumber(value); if (number >= 0 && number <= 100) { await entity.write('manuSpecificSinope', { ledIntensityOff: number }); } return { state: { led_intensity_off: number } }; }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificSinope', ['ledIntensityOff']); }, }, led_color_on: { // DM25x0ZB and SW2500ZB key: ['led_color_on'], convertSet: async (entity, key, value, 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 valueHex = r + g * 256 + (b * 256 ** 2); await entity.write('manuSpecificSinope', { ledColorOn: valueHex }); }, }, led_color_off: { // DM25x0ZB and SW2500ZB key: ['led_color_off'], convertSet: async (entity, key, value, 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 valueHex = r + g * 256 + b * 256 ** 2; await entity.write('manuSpecificSinope', { ledColorOff: valueHex }); }, }, minimum_brightness: { // DM25x0ZB key: ['minimum_brightness'], convertSet: async (entity, key, value, meta) => { const number = utils.toNumber(value); if (number >= 0 && number <= 3000) { await entity.write('manuSpecificSinope', { minimumBrightness: number }); } return { readAfterWriteTime: 250, state: { minimumBrightness: number } }; }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificSinope', ['minimumBrightness']); }, }, timer_seconds: { // DM25x0ZB and SW2500ZB key: ['timer_seconds'], convertSet: async (entity, key, value, meta) => { const number = utils.toNumber(value); if (number >= 0 && number <= 65535) { await entity.write('manuSpecificSinope', { dimmerTimmer: number }); } return { state: { timer_seconds: number } }; }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificSinope', ['dimmerTimmer']); }, }, keypad_lockout: { // SW2500ZB key: ['keypad_lockout'], convertSet: async (entity, key, value, meta) => { const lookup = { 'unlock': 0, 'lock': 1 }; await entity.write('manuSpecificSinope', { keypadLockout: utils.getFromLookup(value, lookup) }); return { state: { keypad_lockout: value } }; }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificSinope', ['keypadLockout']); }, }, low_water_temp_protection: { // RM3500ZB specific key: ['low_water_temp_protection'], convertSet: async (entity, key, value, meta) => { await entity.write('manuSpecificSinope', { drConfigWaterTempMin: value }); return { state: { low_water_temp_protection: value } }; }, convertGet: async (entity, key, meta) => { await entity.read('manuSpecificSinope', ['drConfigWaterTempMin']); }, }, }; const definitions = [ { zigbeeModel: ['TH1123ZB'], model: 'TH1123ZB', vendor: 'Sinopé', description: 'Zigbee line volt thermostat', fromZigbee: [fzLocal.thermostat, fzLocal.sinope, legacy.fz.hvac_user_interface, fromZigbee_1.default.electrical_measurement, fromZigbee_1.default.metering, fromZigbee_1.default.ignore_temperature_report], toZigbee: [toZigbee_1.default.thermostat_local_temperature, toZigbee_1.default.thermostat_occupied_heating_setpoint, toZigbee_1.default.thermostat_unoccupied_heating_setpoint, toZigbee_1.default.thermostat_temperature_display_mode, toZigbee_1.default.thermostat_keypad_lockout, toZigbee_1.default.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, toZigbee_1.default.electrical_measurement_power], exposes: [ 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) .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') .withDescription('Time in seconds after which the outdoor temperature is considered to have expired'), 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']) .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(), ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const binds = [ '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 }]); try { await reporting.thermostatSystemMode(endpoint); } catch (error) { /* Not all support this */ } await reporting.readMeteringMultiplierDivisor(endpoint); await reporting.currentSummDelivered(endpoint, { min: 10, max: 303, change: [1, 1] }); await reporting.readEletricalMeasurementMultiplierDivisors(endpoint); 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 }); } 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 }, }, { zigbeeModel: ['TH1124ZB'], model: 'TH1124ZB', vendor: 'Sinopé', description: 'Zigbee line volt thermostat', fromZigbee: [fzLocal.thermostat, fzLocal.sinope, legacy.fz.hvac_user_interface, fromZigbee_1.default.electrical_measurement, fromZigbee_1.default.metering, fromZigbee_1.default.ignore_temperature_report], toZigbee: [toZigbee_1.default.thermostat_local_temperature, toZigbee_1.default.thermostat_occupied_heating_setpoint, toZigbee_1.default.thermostat_unoccupied_heating_setpoint, toZigbee_1.default.thermostat_temperature_display_mode, toZigbee_1.default.thermostat_keypad_lockout, toZigbee_1.default.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, toZigbee_1.default.electrical_measurement_power], exposes: [ 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) .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') .withDescription('Time in seconds after which the outdoor temperature is considered to have expired'), 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']) .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(), ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const binds = [ '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 }]); try { await reporting.thermostatSystemMode(endpoint); } catch (error) { /* Not all support this */ } await reporting.readMeteringMultiplierDivisor(endpoint); await reporting.currentSummDelivered(endpoint, { min: 10, max: 303, change: [1, 1] }); await reporting.readEletricalMeasurementMultiplierDivisors(endpoint); 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 }); } 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 }, }, { zigbeeModel: ['TH1123ZB-G2'], model: 'TH1123ZB-G2', vendor: 'Sinopé', description: 'Zigbee line volt thermostat', fromZigbee: [fzLocal.thermostat, fzLocal.sinope, legacy.fz.hvac_user_interface, fromZigbee_1.default.electrical_measurement, fromZigbee_1.default.metering, fromZigbee_1.default.ignore_temperature_report], toZigbee: [toZigbee_1.default.thermostat_local_temperature, toZigbee_1.default.thermostat_occupied_heating_setpoint, toZigbee_1.default.thermostat_unoccupied_heating_setpoint, toZigbee_1.default.thermostat_temperature_display_mode, toZigbee_1.default.thermostat_keypad_lockout, toZigbee_1.default.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, toZigbee_1.default.electrical_measurement_power], exposes: [ 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) .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') .withDescription('Time in seconds after which the outdoor temperature is considered to have expired'), 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']) .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(), ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const binds = [ '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 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.readMeteringMultiplierDivisor(endpoint); await reporting.currentSummDelivered(endpoint, { min: 10, max: 303, change: [1, 1] }); await reporting.readEletricalMeasurementMultiplierDivisors(endpoint); await reporting.activePower(endpoint, { min: 10, max: 305, change: 1 }); // divider 1: 1W 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 // Disable default reporting (not used by Sinope) await reporting.thermostatRunningState(endpoint, { min: 1, max: 0xFFFF }); try { await reporting.thermostatUnoccupiedHeatingSetpoint(endpoint); } catch (error) { /* Do nothing */ } }, }, { zigbeeModel: ['TH1124ZB-G2'], model: 'TH1124ZB-G2', vendor: 'Sinopé', description: 'Zigbee line volt thermostat', fromZigbee: [fzLocal.thermostat, fzLocal.sinope, legacy.fz.hvac_user_interface, fromZigbee_1.default.electrical_measurement, fromZigbee_1.default.metering, fromZigbee_1.default.ignore_temperature_report], toZigbee: [toZigbee_1.default.thermostat_local_temperature, toZigbee_1.default.thermostat_occupied_heating_setpoint, toZigbee_1.default.thermostat_unoccupied_heating_setpoint, toZigbee_1.default.thermostat_temperature_display_mode, toZigbee_1.default.thermostat_keypad_lockout, toZigbee_1.default.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, toZigbee_1.default.electrical_measurement_power], exposes: [ 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) .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') .withDescription('Time in seconds after which the outdoor temperature is considered to have expired'), e.binary('enable_outdoor_temperature', ea.ALL, 'ON', 'OFF') .withDescription('DEPRECATED: Use second_display_mode or control via outdoor_temperature_tim