UNPKG

zigbee-herdsman-converters

Version:

Collection of device converters to be used with zigbee-herdsman

977 lines 81.9 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 () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.definitions = void 0; const zigbee_herdsman_1 = require("zigbee-herdsman"); const fz = __importStar(require("../converters/fromZigbee")); const tz = __importStar(require("../converters/toZigbee")); const constants = __importStar(require("../lib/constants")); const ewelink_1 = require("../lib/ewelink"); const exposes = __importStar(require("../lib/exposes")); const logger_1 = require("../lib/logger"); const m = __importStar(require("../lib/modernExtend")); const reporting = __importStar(require("../lib/reporting")); const tuya = __importStar(require("../lib/tuya")); const utils = __importStar(require("../lib/utils")); const { ewelinkAction, ewelinkBattery } = ewelink_1.modernExtend; const NS = "zhc:sonoff"; const manufacturerOptions = { manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SHENZHEN_COOLKIT_TECHNOLOGY_CO_LTD, disableDefaultResponse: false, }; const defaultResponseOptions = { disableDefaultResponse: false }; 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.currentLevel !== undefined) { result.light_indicator_level = msg.data.currentLevel; } }, }, }; const sonoffExtend = { addCustomClusterEwelink: () => m.deviceAddCustomCluster("customClusterEwelink", { ID: 0xfc11, attributes: { networkLed: { ID: 0x0001, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN }, backLight: { ID: 0x0002, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN }, faultCode: { ID: 0x0010, type: zigbee_herdsman_1.Zcl.DataType.INT32 }, radioPower: { ID: 0x0012, type: zigbee_herdsman_1.Zcl.DataType.INT16 }, radioPowerWithManuCode: { ID: 0x0012, type: zigbee_herdsman_1.Zcl.DataType.INT16, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SHENZHEN_COOLKIT_TECHNOLOGY_CO_LTD, }, delayedPowerOnState: { ID: 0x0014, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN }, delayedPowerOnTime: { ID: 0x0015, type: zigbee_herdsman_1.Zcl.DataType.UINT16 }, externalTriggerMode: { ID: 0x0016, type: zigbee_herdsman_1.Zcl.DataType.UINT8 }, detachRelayMode: { ID: 0x0017, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN }, deviceWorkMode: { ID: 0x0018, type: zigbee_herdsman_1.Zcl.DataType.UINT8 }, detachRelayMode2: { ID: 0x0019, type: zigbee_herdsman_1.Zcl.DataType.BITMAP8 }, lackWaterCloseValveTimeout: { ID: 0x5011, type: zigbee_herdsman_1.Zcl.DataType.UINT16 }, }, commands: { protocolData: { ID: 0x01, parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.BuffaloZclDataType.LIST_UINT8 }] }, }, commandsResponse: {}, }), inchingControlSet: (args = {}) => { const { entityCategory } = args; const clusterName = "customClusterEwelink"; const commandName = "protocolData"; let 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")); if (entityCategory) exposes = exposes.withCategory(entityCategory); const fromZigbee = []; 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 = new Uint8Array(11); 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 & 0xff; // Timeout payloadValue[7] = (tmpTime >> 8) & 0xff; 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(clusterName, commandName, { data: payloadValue }, { manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SHENZHEN_COOLKIT_TECHNOLOGY_CO_LTD }); 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 = Number.parseInt(matches[1]); const mins = Number.parseInt(matches[2]); const temp = Number.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("times") .withValueMin(0) .withValueMax(100)) .withFeature(e .numeric("irrigation_duration", ea.STATE_SET) .withDescription("Single irrigation duration") .withUnit("seconds") .withValueMin(0) .withValueMax(86400)) .withFeature(e .numeric("irrigation_interval", ea.STATE_SET) .withDescription("Time interval between two adjacent irrigation") .withUnit("seconds") .withValueMin(0) .withValueMax(86400)); const fromZigbee = [ { cluster: "customClusterEwelink", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const attributeKey = 0x5008; // attr if (attributeKey in msg.data) { // logger.debug(` from zigbee 0x5008 cluster ${msg.data[attributeKey]} `, NS); // logger.debug(msg.data[attributeKey]); const buffer = Buffer.from(msg.data[attributeKey]); // logger.debug(`buffer====> ${buffer[0]} ${buffer[1]} ${buffer[2]} ${buffer[3]} ${buffer[4]} ${buffer[5]} `, NS); // logger.debug(`buffer====> ${buffer[6]} ${buffer[7]} ${buffer[8]} ${buffer[9]} `, NS); const currentCountBuffer = buffer[0]; const totalNumberBuffer = buffer[1]; const irrigationDurationBuffer = (buffer[2] << 24) | (buffer[3] << 16) | (buffer[4] << 8) | buffer[5]; const 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 = []; const payloadValue = new Uint8Array(11); payloadValue[0] = 0x0a; payloadValue[1] = 0x00; payloadValue[2] = value[totalNumber] & 0xff; payloadValue[3] = (value[irrigationDuration] >> 24) & 0xff; payloadValue[4] = (value[irrigationDuration] >> 16) & 0xff; payloadValue[5] = (value[irrigationDuration] >> 8) & 0xff; payloadValue[6] = value[irrigationDuration] & 0xff; payloadValue[7] = (value[irrigationInterval] >> 24) & 0xff; payloadValue[8] = (value[irrigationInterval] >> 16) & 0xff; payloadValue[9] = (value[irrigationInterval] >> 8) & 0xff; payloadValue[10] = value[irrigationInterval] & 0xff; const payload = { [0x5008]: { value: payloadValue, type: 0x42 } }; await entity.write("customClusterEwelink", payload, defaultResponseOptions); return { state: { [key]: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("customClusterEwelink", [0x5008], defaultResponseOptions); }, }, ]; return { exposes: [exposes], 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("times") .withValueMin(0) .withValueMax(100)) .withFeature(e .numeric("irrigation_capacity", ea.STATE_SET) .withDescription("Single irrigation capacity") .withUnit("liter") .withValueMin(0) .withValueMax(6500)) .withFeature(e .numeric("irrigation_interval", ea.STATE_SET) .withDescription("Time interval between two adjacent irrigation") .withUnit("seconds") .withValueMin(0) .withValueMax(86400)); const fromZigbee = [ { cluster: "customClusterEwelink", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const attributeKey = 0x5009; // attr if (attributeKey in msg.data) { // logger.debug(` from zigbee 0x5009 cluster ${msg.data[attributeKey]} `, NS); // logger.debug(msg.data[attributeKey]); const buffer = Buffer.from(msg.data[attributeKey]); // logger.debug(`buffer====> ${buffer[0]} ${buffer[1]} ${buffer[2]} ${buffer[3]} ${buffer[4]} ${buffer[5]} `, NS); // logger.debug(`buffer====> ${buffer[6]} ${buffer[7]} ${buffer[8]} ${buffer[9]} `, NS); const currentCountBuffer = buffer[0]; const totalNumberBuffer = buffer[1]; const irrigationCapacityBuffer = (buffer[2] << 24) | (buffer[3] << 16) | (buffer[4] << 8) | buffer[5]; const 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 = new Uint8Array(11); payloadValue[0] = 0x0a; payloadValue[1] = 0x00; payloadValue[2] = value[totalNumber] & 0xff; payloadValue[3] = (value[irrigationCapacity] >> 24) & 0xff; payloadValue[4] = (value[irrigationCapacity] >> 16) & 0xff; payloadValue[5] = (value[irrigationCapacity] >> 8) & 0xff; payloadValue[6] = value[irrigationCapacity] & 0xff; payloadValue[7] = (value[irrigationInterval] >> 24) & 0xff; payloadValue[8] = (value[irrigationInterval] >> 16) & 0xff; payloadValue[9] = (value[irrigationInterval] >> 8) & 0xff; payloadValue[10] = value[irrigationInterval] & 0xff; const payload = { [0x5009]: { value: payloadValue, type: 0x42 } }; await entity.write("customClusterEwelink", payload, defaultResponseOptions); return { state: { [key]: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("customClusterEwelink", [0x5009], defaultResponseOptions); }, }, ]; return { exposes: [exposes], fromZigbee, toZigbee, isModernExtend: true, }; }, externalSwitchTriggerMode: (args = {}) => { const { entityCategory } = args; const clusterName = "customClusterEwelink"; const attributeName = "externalTriggerMode"; let exposes = e .enum("external_trigger_mode", ea.ALL, ["edge", "pulse", "following(off)", "following(on)"]) .withDescription("External trigger mode, which can be one of edge, pulse, " + "following(off), following(on). The appropriate triggering mode can be selected according to the type of " + "external switch to achieve a better use experience."); if (entityCategory) exposes = exposes.withCategory(entityCategory); const fromZigbee = [ { cluster: clusterName, type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const lookup = { edge: 0, pulse: 1, "following(off)": 2, "following(on)": 130 }; // logger.debug(`from zigbee msg.data['externalTriggerMode'] ${msg.data['externalTriggerMode']}`, NS); if (msg.data.externalTriggerMode !== undefined) { let switchType = "edge"; for (const name in lookup) { if (lookup[name] === msg.data.externalTriggerMode) { switchType = name; break; } } // logger.debug(`form zigbee switchType ${switchType}`, NS); return { external_trigger_mode: switchType }; } }, }, ]; const toZigbee = [ { key: ["external_trigger_mode"], convertSet: async (entity, key, value, meta) => { utils.assertString(value, key); // biome-ignore lint/style/noParameterAssign: ignored using `--suppress` value = value.toLowerCase(); const lookup = { edge: 0, pulse: 1, "following(off)": 2, "following(on)": 130 }; const tmpValue = utils.getFromLookup(value, lookup); await entity.write(clusterName, { [attributeName]: tmpValue }, defaultResponseOptions); return { state: { [key]: value } }; }, convertGet: async (entity, key, meta) => { await entity.read(clusterName, [attributeName], defaultResponseOptions); }, }, ]; return { exposes: [exposes], fromZigbee, toZigbee, isModernExtend: true, }; }, detachRelayModeControl: (relayCount) => { const clusterName = "customClusterEwelink"; const attributeName = "detachRelayMode2"; const exposes = e.composite("detach_relay_mode", "detach_relay_mode", ea.ALL); if (1 === relayCount) { exposes .withDescription("Relay separation mode. Can be used when the load is a smart device (such as smart light), " + "when we control the wall switch, do not want to turn off the power of the smart light, but through " + "a scene command to control the smart light on or off, then we can enable the relay separation mode.") .withFeature(e.binary("detach_relay_outlet1", ea.SET, "ENABLE", "DISABLE").withDescription("Enable/disable detach relay.")); } else if (2 === relayCount) { exposes .withDescription("Relay separation mode. Can be used when the load is a smart device (such as smart light), " + "when we control the wall switch, do not want to turn off the power of the smart light, but through " + "a scene command to control the smart light on or off, then we can enable the relay separation mode.") .withFeature(e.binary("detach_relay_outlet1", ea.SET, "ENABLE", "DISABLE").withDescription("Enable/disable detach relay.")) .withFeature(e.binary("detach_relay_outlet2", ea.SET, "ENABLE", "DISABLE").withDescription("Enable/disable detach relay.")); } else if (3 === relayCount) { exposes .withDescription("Relay separation mode. Can be used when the load is a smart device (such as smart light), " + "when we control the wall switch, do not want to turn off the power of the smart light, but through " + "a scene command to control the smart light on or off, then we can enable the relay separation mode.") .withFeature(e.binary("detach_relay_outlet1", ea.SET, "ENABLE", "DISABLE").withDescription("Enable/disable detach relay.")) .withFeature(e.binary("detach_relay_outlet2", ea.SET, "ENABLE", "DISABLE").withDescription("Enable/disable detach relay.")) .withFeature(e.binary("detach_relay_outlet3", ea.SET, "ENABLE", "DISABLE").withDescription("Enable/disable detach relay.")); } const fromZigbee = [ { cluster: clusterName, type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { if (msg.data.detachRelayMode2 !== undefined) { const detachMode = msg.data.detachRelayMode2; logger_1.logger.debug(`form zigbee detachRelayMode2 ${detachMode}`, NS); const datachRelayStatus = { detach_relay_outlet1: "DISABLE", detach_relay_outlet2: "DISABLE", detach_relay_outlet3: "DISABLE", }; if ((detachMode & 0x01) !== 0) { datachRelayStatus.detach_relay_outlet1 = "ENABLE"; } if ((detachMode & 0x02) !== 0) { datachRelayStatus.detach_relay_outlet2 = "ENABLE"; } if ((detachMode & 0x04) !== 0) { datachRelayStatus.detach_relay_outlet3 = "ENABLE"; } return { detach_relay_mode: datachRelayStatus }; } }, }, ]; const toZigbee = [ { key: ["detach_relay_mode"], convertSet: async (entity, key, value, meta) => { // logger.debug(`from zigbee 'key' ${key}`, NS); const detachRelay1 = "detach_relay_outlet1"; // logger.debug(`from zigbee detachRelay1: ${value[detachRelay1 as keyof typeof value]}`, NS); const detachRelay2 = "detach_relay_outlet2"; // logger.debug(`from zigbee detachRelay2: ${value[detachRelay2 as keyof typeof value]}`, NS); const detachRelay3 = "detach_relay_outlet3"; // logger.debug(`from zigbee detachRelay3: ${value[detachRelay3 as keyof typeof value]}`, NS); let detachRelayMask = 0; if (value[detachRelay1] === "ENABLE") { detachRelayMask |= 0x01; } else { detachRelayMask &= ~0x01; } if (value[detachRelay2] === "ENABLE") { detachRelayMask |= 0x02; } else { detachRelayMask &= ~0x02; } if (value[detachRelay3] === "ENABLE") { detachRelayMask |= 0x04; } else { detachRelayMask &= ~0x04; } // logger.info(`from zigbee detachRelayMask: ${detachRelayMask}`, NS); await entity.write(clusterName, { [attributeName]: detachRelayMask }, defaultResponseOptions); return { state: { [key]: value } }; }, convertGet: async (entity, key, meta) => { await entity.read(clusterName, [attributeName], defaultResponseOptions); }, }, ]; return { exposes: [exposes], fromZigbee, toZigbee, isModernExtend: true, }; }, }; exports.definitions = [ { zigbeeModel: ["NSPanelP-Router"], model: "NSPanelP-Router", vendor: "SONOFF", description: "Router", fromZigbee: [fz.linkquality_from_basic], toZigbee: [], exposes: [], }, { zigbeeModel: ["BASICZBR3"], model: "BASICZBR3", vendor: "SONOFF", description: "Zigbee smart switch", // configureReporting fails for this device extend: [m.onOff({ powerOnBehavior: false, skipDuplicateTransaction: true, configureReporting: false })], }, { zigbeeModel: ["ZBMINI-L"], model: "ZBMINI-L", vendor: "SONOFF", description: "Zigbee smart switch (no neutral)", extend: [m.onOff()], ota: true, 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 const endpoint = device.getEndpoint(1); if (endpoint.binds.some((b) => b.cluster.name === "genPollCtrl")) { 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: [m.onOff()], ota: true, 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 const endpoint = device.getEndpoint(1); if (endpoint.binds.some((b) => b.cluster.name === "genPollCtrl")) { 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: [m.onOff({ powerOnBehavior: false }), m.forcePowerSource({ powerSource: "Mains (single phase)" })], }, { zigbeeModel: ["S31 Lite zb"], model: "S31ZB", vendor: "SONOFF", description: "Zigbee smart plug (US version)", extend: [m.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", "CK-TLSR8656-SS5-01(7003)"], model: "SNZB-04", vendor: "SONOFF", whiteLabel: [ { vendor: "eWeLink", model: "RHK06" }, { vendor: "eWeLink", model: "SNZB-04", fingerprint: [{ modelID: "SNZB-04", manufacturerName: "eWeLink" }], }, { vendor: "eWeLink", model: "CK-TLSR8656-SS5-01(7003)", fingerprint: [{ modelID: "CK-TLSR8656-SS5-01(7003)", manufacturerName: "eWeLink" }], }, tuya.whitelabel("Tuya", "WL-19DWZ", "Contact sensor", ["_TZ3000_n2egfsli"]), ], description: "Contact sensor", extend: [ewelinkBattery(), m.iasZoneAlarm({ zoneType: "contact", zoneAttributes: ["alarm_1", "battery_low"] })], }, { zigbeeModel: ["WB01", "WB-01", "SNZB-01", "CK-TLSR8656-SS5-01(7000)"], model: "SNZB-01", vendor: "SONOFF", whiteLabel: [ { vendor: "eWeLink", model: "RHK07" }, { vendor: "eWeLink", model: "SNZB-01", fingerprint: [{ modelID: "SNZB-01", manufacturerName: "eWeLink" }], }, { vendor: "eWeLink", model: "CK-TLSR8656-SS5-01(7000)", fingerprint: [{ modelID: "CK-TLSR8656-SS5-01(7000)", manufacturerName: "eWeLink" }], }, ], description: "Wireless button", extend: [ewelinkBattery()], exposes: [e.action(["single", "double", "long"])], fromZigbee: [fz.ewelink_action], toZigbee: [], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff"]); }, }, { zigbeeModel: ["KF01", "KF-01"], model: "SNZB-01-KF", vendor: "SONOFF", description: "Wireless button", extend: [ewelinkBattery()], exposes: [e.action(["off", "single"])], fromZigbee: [fz.command_status_change_notification_action], toZigbee: [], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ["ssIasZone"]); }, }, { 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] }], }, { type: "EndDevice", manufacturerName: "Zbeacon", modelID: "TH01", }, ], zigbeeModel: ["TH01", "SNZB-02", "CK-TLSR8656-SS5-01(7014)"], model: "SNZB-02", vendor: "SONOFF", whiteLabel: [ { vendor: "eWeLink", model: "RHK08" }, { vendor: "eWeLink", model: "SNZB-02", fingerprint: [{ modelID: "SNZB-02", manufacturerName: "eWeLink" }], }, { vendor: "eWeLink", model: "CK-TLSR8656-SS5-01(7014)", fingerprint: [{ modelID: "CK-TLSR8656-SS5-01(7014)", manufacturerName: "eWeLink" }], }, { vendor: "Zbeacon", model: "TH01", fingerprint: [{ modelID: "TH01", manufacturerName: "Zbeacon" }], }, ], description: "Temperature and humidity sensor", exposes: [e.battery(), e.temperature(), e.humidity(), e.battery_voltage()], fromZigbee: [fz.SNZB02_temperature, fz.humidity, fz.battery], toZigbee: [], configure: async (device, coordinatorEndpoint) => { device.powerSource = "Battery"; device.save(); 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", ota: true, extend: [ m.deviceAddCustomCluster("customSonoffSnzb02d", { ID: 0xfc11, attributes: { comfortTemperatureMax: { ID: 0x0003, type: zigbee_herdsman_1.Zcl.DataType.INT16 }, comfortTemperatureMin: { ID: 0x0004, type: zigbee_herdsman_1.Zcl.DataType.INT16 }, comfortHumidityMin: { ID: 0x0005, type: zigbee_herdsman_1.Zcl.DataType.UINT16 }, comfortHumidityMax: { ID: 0x0006, type: zigbee_herdsman_1.Zcl.DataType.UINT16 }, temperatureUnits: { ID: 0x0007, type: zigbee_herdsman_1.Zcl.DataType.UINT16 }, temperatureCalibration: { ID: 0x2003, type: zigbee_herdsman_1.Zcl.DataType.INT16 }, humidityCalibration: { ID: 0x2004, type: zigbee_herdsman_1.Zcl.DataType.INT16 }, }, commands: {}, commandsResponse: {}, }), m.battery(), m.temperature(), m.humidity(), m.bindCluster({ cluster: "genPollCtrl", clusterType: "input" }), m.numeric({ name: "comfort_temperature_min", cluster: "customSonoffSnzb02d", attribute: "comfortTemperatureMin", description: "Minimum temperature that is considered comfortable. The device will display ❄️ when the temperature is lower than this value. Note: wake up the device by pressing the button on the back before changing this value.", valueMin: -10, valueMax: 60, scale: 100, valueStep: 0.1, unit: "°C", }), m.numeric({ name: "comfort_temperature_max", cluster: "customSonoffSnzb02d", attribute: "comfortTemperatureMax", description: "Maximum temperature that is considered comfortable. The device will display 🔥 when the temperature is higher than this value. Note: wake up the device by pressing the button on the back before changing this value.", valueMin: -10, valueMax: 60, scale: 100, valueStep: 0.1, unit: "°C", }), m.numeric({ name: "comfort_humidity_min", cluster: "customSonoffSnzb02d", attribute: "comfortHumidityMin", description: "Minimum relative humidity that is considered comfortable. The device will display ☀️ when the humidity is lower than this value. Note: wake up the device by pressing the button on the back before changing this value.", valueMin: 5, valueMax: 95, scale: 100, valueStep: 0.1, unit: "%", }), m.numeric({ name: "comfort_humidity_max", cluster: "customSonoffSnzb02d", attribute: "comfortHumidityMax", description: "Maximum relative humidity that is considered comfortable. The device will display 💧 when the humidity is higher than this value. Note: wake up the device by pressing the button on the back before changing this value.", valueMin: 5, valueMax: 95, scale: 100, valueStep: 0.1, unit: "%", }), m.enumLookup({ name: "temperature_units", lookup: { celsius: 0, fahrenheit: 1 }, cluster: "customSonoffSnzb02d", attribute: "temperatureUnits", description: "The unit of the temperature displayed on the device screen. Note: wake up the device by pressing the button on the back before changing this value.", }), m.numeric({ name: "temperature_calibration", cluster: "customSonoffSnzb02d", attribute: "temperatureCalibration", description: "Offset to add/subtract to the reported temperature", valueMin: -50, valueMax: 50, scale: 100, valueStep: 0.1, unit: "°C", }), m.numeric({ name: "humidity_calibration", cluster: "customSonoffSnzb02d", attribute: "humidityCalibration", description: "Offset to add/subtract to the reported relative humidity", valueMin: -50, valueMax: 50, scale: 100, valueStep: 0.1, unit: "%", }), ], }, { zigbeeModel: ["SNZB-02LD"], model: "SNZB-02LD", vendor: "SONOFF", description: "Temperature sensor with screen", extend: [ m.deviceAddCustomCluster("customSonoffSnzb02ld", { ID: 0xfc11, attributes: { comfortTemperatureMax: { ID: 0x0003, type: zigbee_herdsman_1.Zcl.DataType.INT16 }, comfortTemperatureMin: { ID: 0x0004, type: zigbee_herdsman_1.Zcl.DataType.INT16 }, temperatureUnits: { ID: 0x0007, type: zigbee_herdsman_1.Zcl.DataType.UINT16 }, temperatureCalibration: { ID: 0x2003, type: zigbee_herdsman_1.Zcl.DataType.INT16 }, }, commands: {}, commandsResponse: {}, }), m.battery(), m.temperature(), m.bindCluster({ cluster: "genPollCtrl", clusterType: "input" }), m.numeric({ name: "comfort_temperature_min", cluster: "customSonoffSnzb02ld", attribute: "comfortTemperatureMin", description: "Minimum temperature that is considered comfortable. The device will display ❄️ when the temperature is lower than this value. Note: wake up the device by pressing the button on the back before changing this value.", valueMin: -10, valueMax: 60, scale: 100, valueStep: 0.1, unit: "°C", }), m.numeric({ name: "comfort_temperature_max", cluster: "customSonoffSnzb02ld", attribute: "comfortTemperatureMax", description: "Maximum temperature that is considered comfortable. The device will display 🔥 when the temperature is higher than this value. Note: wake up the device by pressing the button on the back before changing this value.", valueMin: -10, valueMax: 60, scale: 100, valueStep: 0.1, unit: "°C", }), m.enumLookup({ name: "temperature_units", lookup: { celsius: 0, fahrenheit: 1 }, cluster: "customSonoffSnzb02ld", attribute: "temperatureUnits", description: "The unit of the temperature displayed on the device screen. Note: wake up the device by pressing the button on the back before changing this value.", }), m.numeric({ name: "temperature_calibration", cluster: "customSonoffSnzb02ld", attribute: "temperatureCalibrat