zigbee-herdsman-converters
Version:
Collection of device converters to be used with zigbee-herdsman
891 lines • 90.5 kB
JavaScript
"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