zigbee-herdsman-converters
Version:
Collection of device converters to be used with zigbee-herdsman
2,069 lines • 101 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 () {
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 },
acCurrentCurrentValue: { ID: 0x7004, type: zigbee_herdsman_1.Zcl.DataType.UINT32 },
acCurrentVoltageValue: { ID: 0x7005, type: zigbee_herdsman_1.Zcl.DataType.UINT32 },
acCurrentPowerValue: { ID: 0x7006, type: zigbee_herdsman_1.Zcl.DataType.UINT32 },
outlet_control_protect: { ID: 0x7007, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
energyToday: { ID: 0x7009, type: zigbee_herdsman_1.Zcl.DataType.UINT32 },
energyMonth: { ID: 0x700a, type: zigbee_herdsman_1.Zcl.DataType.UINT32 },
energyYesterday: { ID: 0x700b, type: zigbee_herdsman_1.Zcl.DataType.UINT32 },
},
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,
};
},
overloadProtection: (powerMaxLimit, currentMaxLimit) => {
const clusterName = "customClusterEwelink";
const exposes = e
.composite("overload_protection", "overload_protection", ea.ALL)
.withDescription("Over load protection, max power and max current are required,other is optional")
.withFeature(e
.numeric("max_power", ea.SET)
.withDescription("max power")
.withUnit("W")
.withValueMin(0.1)
.withValueMax(powerMaxLimit)
.withValueStep(0.1))
.withFeature(e.binary("enable_min_power", ea.SET, "ENABLE", "DISABLE").withDescription("Enable/disable lower limit of power overload protection."))
.withFeature(e
.numeric("min_power", ea.SET)
.withDescription("Lower limit of power overload protection")
.withUnit("W")
.withValueMin(0.1)
.withValueMax(powerMaxLimit)
.withValueStep(0.1))
.withFeature(e
.binary("enable_max_voltage", ea.SET, "ENABLE", "DISABLE")
.withDescription("Enable/disable upper limit of voltage overload protection.."))
.withFeature(e
.numeric("max_voltage", ea.SET)
.withDescription("Upper limit of voltage overload protection.")
.withUnit("V")
.withValueMin(165)
.withValueMax(277)
.withValueStep(1))
.withFeature(e
.binary("enable_min_voltage", ea.SET, "ENABLE", "DISABLE")
.withDescription("Enable/disable lower limit of voltage overload protection."))
.withFeature(e
.numeric("min_voltage", ea.SET)
.withDescription("Lower limit of voltage overload protection.")
.withUnit("V")
.withValueMin(165)
.withValueMax(277)
.withValueStep(1))
.withFeature(e
.numeric("max_current", ea.SET)
.withDescription("Upper limit of current overload protection.")
.withUnit("A")
.withValueMin(0.1)
.withValueMax(currentMaxLimit)
.withValueStep(0.1))
.withFeature(e
.binary("enable_min_current", ea.SET, "ENABLE", "DISABLE")
.withDescription("Enable/disable lower limit of current overload protection."))
.withFeature(e
.numeric("min_current", ea.SET)
.withDescription("Lower limit of current overload protection.")
.withUnit("A")
.withValueMin(0.1)
.withValueMax(currentMaxLimit)
.withValueStep(0.1));
const fromZigbee = [
{
cluster: "customClusterEwelink",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const attributeKey = 0x7003; // attr
if (attributeKey in msg.data) {
// "enable_max_voltage": "ENABLE",
// "enable_min_current": "ENABLE",
// "enable_min_power": "ENABLE",
// "enable_min_voltage": "ENABLE",
// "max_current": 23,
// "max_power": 23,
// "max_voltage": 23,
// "min_current": 23,
// "min_power": 23,
// "min_voltage": 23
// }: value:
logger_1.logger.debug("attr_value is:", JSON.stringify(msg.data[attributeKey]));
const buffer = Buffer.from(msg.data[attributeKey], "binary");
const hexString = buffer.toString("hex").toUpperCase();
console.log(`Hex: ${hexString}`);
let index = 0;
let enableMaxVoltageBuffer = "DISABLE";
let enableMinCurrentBuffer = "DISABLE";
let enableMinPowerBuffer = "DISABLE";
let enableMinVoltageBuffer = "DISABLE";
const tag = buffer[index++];
const len = buffer[index++];
if (buffer[index++] === 3) {
enableMinCurrentBuffer = "ENABLE";
}
const voltage_set_flag = buffer[index++];
if (voltage_set_flag & 0x01) {
enableMaxVoltageBuffer = "ENABLE";
}
if (voltage_set_flag & 0x02) {
enableMinVoltageBuffer = "ENABLE";
}
if (buffer[index++] === 3) {
enableMinPowerBuffer = "ENABLE";
}
let minCurrentBuffer = 0;
let maxVoltageBuffer = 0;
let minVoltageBuffer = 0;
let maxPowerBuffer = 0;
let minPowerBuffer = 0;
let maxCurrentBuffer = buffer[index++];
maxCurrentBuffer |= buffer[index++] << 8;
maxCurrentBuffer |= buffer[index++] << 16;
maxCurrentBuffer |= buffer[index++] << 24;
maxCurrentBuffer /= 1000;
if (enableMinCurrentBuffer === "ENABLE") {
minCurrentBuffer = buffer[index++];
minCurrentBuffer |= buffer[index++] << 8;
minCurrentBuffer |= buffer[index++] << 16;
minCurrentBuffer |= buffer[index++] << 24;
minCurrentBuffer /= 1000;
}
if (enableMaxVoltageBuffer === "ENABLE") {
for (let i = 0; i < 4; i++) {
logger_1.logger.debug("max voltage is:", JSON.stringify(buffer[index + i]));
}
logger_1.logger.debug("index is", JSON.stringify(index));
maxVoltageBuffer = buffer[index++];
maxVoltageBuffer |= buffer[index++] << 8;
maxVoltageBuffer |= buffer[index++] << 16;
maxVoltageBuffer |= buffer[index++] << 24;
maxVoltageBuffer /= 1000;
}
if (enableMinVoltageBuffer === "ENABLE") {
minVoltageBuffer = buffer[index++];
minVoltageBuffer |= buffer[index++] << 8;
minVoltageBuffer |= buffer[index++] << 16;
minVoltageBuffer |= buffer[index++] << 24;
minVoltageBuffer /= 1000;
}
maxPowerBuffer = buffer[index++];
maxPowerBuffer |= buffer[index++] << 8;
maxPowerBuffer |= buffer[index++] << 16;
maxPowerBuffer |= buffer[index++] << 24;
maxPowerBuffer /= 1000;
if (enableMinPowerBuffer === "ENABLE") {
minPowerBuffer = buffer[index++];
minPowerBuffer |= buffer[index++] << 8;
minPowerBuffer |= buffer[index++] << 16;
minPowerBuffer |= buffer[index++] << 24;
minPowerBuffer /= 1000;
}
return {
overload_protection: {
enable_max_voltage: enableMaxVoltageBuffer,
enable_min_current: enableMinCurrentBuffer,
enable_min_power: enableMinPowerBuffer,
enable_min_voltage: enableMinPowerBuffer,
max_current: maxCurrentBuffer,
max_power: maxPowerBuffer,
max_voltage: maxVoltageBuffer,
min_current: minCurrentBuffer,
min_power: minPowerBuffer,
min_voltage: minVoltageBuffer,
},
};
}
},
},
];
const toZigbee = [
{
key: ["overload_protection"],
convertSet: async (entity, key, value, meta) => {
logger_1.logger.debug("value:", JSON.stringify(value, null, 2)); // 将 value 转换为格式化的 JSON 字符串
const maxC = 1000 * value["max_current"];
const minC = 1000 * value["min_current"];
const maxV = 1000 * value["max_voltage"];
const minV = 1000 * value["min_voltage"];
const maxP = 1000 * value["max_power"];
const minP = 1000 * value["min_power"];
const enMinC = value["enable_min_current"];
const enMaxV = value["enable_max_voltage"];
const enMinV = value["enable_min_voltage"];
const enMinP = value["enable_min_power"];
const params = { maxC, minC, maxV, minV, maxP, minP, enMinC, enMaxV, enMinV, enMinP };
logger_1.logger.debug("value:", JSON.stringify(params));
const payloadValue = [];
let index = 0;
const length = 0;
payloadValue[index++] = 0;
payloadValue[index++] = 0x04;
payloadValue[index++] = 27;
payloadValue[index++] = 1;
payloadValue[index++] = 0;
payloadValue[index++] = 1;
payloadValue[index++] = maxC & 0xff;
payloadValue[index++] = (maxC >> 8) & 0xff;
payloadValue[index++] = (maxC >> 16) & 0xff;
payloadValue[index++] = (maxC >> 24) & 0xff;
if (enMinC === "ENABLE") {
payloadValue[3] |= 2;
payloadValue[index++] = minC & 0xff;
payloadValue[index++] = (minC >> 8) & 0xff;
payloadValue[index++] = (minC >> 16) & 0xff;
payloadValue[index++] = (minC >> 24) & 0xff;
}
if (enMaxV === "ENABLE") {
payloadValue[4] |= 1;
payloadValue[index++] = maxV & 0xff;
payloadValue[index++] = (maxV >> 8) & 0xff;
payloadValue[index++] = (maxV >> 16) & 0xff;
payloadValue[index++] = (maxV >> 24) & 0xff;
}
if (enMinV === "ENABLE") {
payloadValue[4] |= 2;
payloadValue[index++] = minV & 0xff;
payloadValue[index++] = (minV >> 8) & 0xff;
payloadValue[index++] = (minV >> 16) & 0xff;
payloadValue[index++] = (minV >> 24) & 0xff;
}
payloadValue[index++] = maxP & 0xff;
payloadValue[index++] = (maxP >> 8) & 0xff;
payloadValue[index++] = (maxP >> 16) & 0xff;
payloadValue[index++] = (maxP >> 24) & 0xff;
if (enMinP === "ENABLE") {
payloadValue[5] |= 2;
payloadValue[index++] = minP & 0xff;
payloadValue[index++] = (minP >> 8) & 0xff;
payloadValue[index++] = (minP >> 16) & 0xff;
payloadValue[index++] = (minP >> 24) & 0xff;
}
payloadValue[0] = index - 1;
payloadValue[2] = payloadValue[0] - 2;
if (payloadValue[3] === 3) {
if (minC >= maxC) {
throw new Error("Invalid input: maximum current must be greater than the minimum current ");
}
}
if (payloadValue[4] === 3) {
if (minV >= maxV) {
throw new Error("Invalid input: maximum voltage must be greater than the minimum voltage ");
}
}
if (payloadValue[5] === 3) {
if (minP >= maxP) {
throw new Error("Invalid input: maximum power must be greater than the minimum power ");
}
}
const payload = { [0x7003]: { value: payloadValue, type: 0x42 } };
await entity.write("customClusterEwelink", payload, defaultResponseOptions);
return { state: { [key]: value } };
},
convertGet: async (entity, key, meta) => {
await entity.read("customClusterEwelink", [0x7003], defaultResponseOptions);
},
},
];
return {
exposes: [exposes],
toZigbee,
fromZigbee,
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_eWeLink",
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_eWeLink",
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_eWeLink",
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: "Waterproof (IP65) sensor with screen and probe temperature detection",
extend: [
m.deviceAddCustomCluster("customSonoffSnzb02ld", {
ID: 0xfc11,
attributes: {
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.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: "temperatureCalibration",
description: "Offset to add/subtract to the reported temperature",
valueMin: -50,
valueMax: 50,
scale: 100,
valueStep: 0.1,
unit: "°C",
}),
],
},
{
zigbeeModel: ["SNZB-02WD"],
model: "SNZB-02WD",
vendor: "SONOFF",
description: "Waterproof (IP65) temperature and humidity sensor with screen",
extend: [
m.deviceAddCustomCluster("customSonoffSnzb02wd", {
ID: 0xfc11,
attributes: {
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({ voltage: true, voltageReporting: true }),
m.temperature(),
m.humidity(),
m.bindCluster({ cluster: "genPollCtrl", clusterType: "input" }),
m.enumLookup({
name: "temperature_units",
lookup: { celsius: 0, fahrenheit: 1 },
cluster: "customSonoffSnzb02wd",
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: "customSonoffSnzb02wd",
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: "customSonoffSnzb02wd",
attribute: "humidityCalibration",
description: "Offset to add/subtract to the reported relative humidity",
valueMin: -50,
valueMax: 50,
scale: 100,
valueStep: 0.1,
unit: "%",
}),
],
},
{
fingerprint: [
{
type: "EndDevice",
manufacturerName: "eWeLink",
modelID: "66666",
endpoints: [{ ID: 1, profileID: 260, deviceID: 1026, inputClusters: [0, 3, 1280, 1], outputClusters: [3] }],
},
{
// SNZB-O3 OUVOPO Wireless Motion Sensor (2023)
type: "EndDevice",
manufacturerName: "eWeLink",
modelID: "SNZB-03",
endpoints: [{ ID: 1, profileID: 260, deviceID: 1026, inputClusters: [0, 3, 1280, 1], outputClusters: [3] }],
},
{
type: "EndDevice",
manufacturerName: "eWeLink",
modelID: "SNZB-03",
endpoints: [{ ID: 1, profileID: 260, deviceID: 1026, inputClusters: [0, 3, 1, 1280, 32], outputClusters: [25] }],
},
],
zigbeeModel: ["MS01", "MSO1", "SNZB-03", "CK-TLSR8656-SS5-01(7002)"],
model: "SNZB-03",
vendor: "SONOFF",
whiteLabel: [
{ vendor: "eWeLink", model: "RHK09" },
{ vendor: "eWeLink", model: "SQ510A" },
{
vendor: "eWeLink",
model: "SNZB-03_eWeLink",
fingerprint: [
{
// SNZB-O3 OUVOPO Wireless Motion Sensor (2023)
type: "EndDevice",
manufacturerName: "eWeLink",
modelID: "SNZB-03",
endpoints: [{ ID: 1, profileID: 260, deviceID: 1026, inputClusters: [0, 3, 1280, 1], outputClusters: [3] }],
},
{
type: "EndDevice",
manufacturerName: "eWeLink",
modelID: "SNZB-03",
endpoints: [{ ID: 1, profileID: 260, deviceID: 1026, inputClusters: [0, 3, 1, 1280, 32], outputClusters: [25] }],
},
],
},
{
vendor: "eWeLink",
model: "CK-TLSR8656-SS5-01(7002)",
fingerprint: [
{
type: "EndDevice",
manufacturerName: "eWeLink",
modelID: "CK-TLSR8656-SS5-01(7002)",
},
],
},
],
description: "Motion sensor",
extend: [ewelinkBattery(), m.iasZoneAlarm({ zoneType: "occupancy", zoneAttributes: ["alarm_1", "battery_low"] })],
},
{
zigbeeModel: ["S26R2ZB"],
model: "S26R2ZB",
vendor: "SONOFF",
description: "Zigbee smart plug",
extend: [m.onOff({ powerOnBehavior: false })],
},
{
zigbeeModel: ["S40LITE"],
model: "S40ZBTPB",
vendor: "SONOFF",
description: "15A Zigbee smart plug",
extend: [m.onOff({ powerOnBehavior: false, skipDuplicateTransaction: true })],
ota: true,
},
{
zigbeeModel: ["DONGLE-E_R"],
model: "ZBDongle-E",
vendor: "SONOFF",
description: "Sonoff Zigbee 3.0 USB Dongle Plus (EFR32MG21) with router firmware",
fromZigbee: [fz.linkquality_from_basic, fzLocal.router_config],
toZigbee: [],
exposes: [e.numeric("light_indicator_level", ea.STATE).withDescription("Brightness of the indicator light").withAccess(ea.STATE)],
extend: [m.forcePowerSource({ powerSource: "Mains (single phase)" })],
},
{
zigbeeModel: ["ZBCurtain"],
model: "ZBCurtain",
vendor: "SONOFF",
description: "Zigbee smart curtain motor",
fromZigbee: [fz.cover_position_tilt, fz.battery],
toZigbee: [tz.cover_state, tz.cover_position_tilt],
exposes: [e.cover_position(), e.battery()],
},
{
zigbeeModel: ["SA-029-1", "SA-028-1"],
model: "SA-028/SA-029",
vendor: "SONOFF",
whiteLabel: [{ vendor: "Woolley", model: "SA-029-1" }],
description: "Smart Plug",
extend: [m.onOff(), m.forcePowerSource({ powerSource: "Mains (single phase)" })],
},
{
zigbeeModel: ["Z111PL0H-1JX"],
model: "Z111PL0H-1JX",
vendor: "SONOFF",
whiteLabel: [{ vendor: "Woolley", model: "SA-028-1" }],
description: "Smart Plug",
extend: [m.onOff({ powerOnBehavior: false }), m.forcePowerSource({ powerSource: "Mains (single phase)" })],
},
{
zigbeeModel: ["SNZB-01P"],
model: "SNZB-01P",
vendor: "SONOFF",
description: "Wireless button",
extend: [
ewelinkAction(),
m.battery({
percentageReportingConfig: { min: 3600, max: 7200, change: 0 },
voltage: true,
voltageReporting: true,
voltageReportingConfig: { min: 3600, max: 7200, change: 0 },
}),
],
ota: true,
},
{
zigbeeModel: ["SNZB-02P"],
model: "SNZB-02P",
vendor: "SONOFF",
description: "Temperature and humidity sensor",
extend: [m.battery({ percentage: true }), m.temperature(), m.humidity(), m.bindCluster({ cluster: "genPollCtrl", clusterType: "input" })],
},
{
zigbeeModel: ["SNZB-04P"],
model: "SNZB-04P",
vendor: "SONOFF",
description: "Contact sensor",
extend: [
m.iasZoneAlarm({ zoneType: "contact", zoneAttributes: ["alarm_1", "battery_low"] }),
m.binary({
name: "tamper",
cluster: 0xfc11,
attribute: { ID: 0x2000, type: 0x20 },
description: "Tamper-proof status",
valueOn: [true, 0x01],
valueOff: [false, 0x00],
zigbeeCommandOptions: { manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SHENZHEN_COOLKIT_TECHNOLOGY_CO_LTD },
access: "STATE_GET",
}),
ewelinkBattery(),
],
ota: true,
},
{
zigbeeModel: ["SNZB-03P"],
model: "SNZB-03P",
vendor: "SONOFF",
description: "Zigbee PIR sensor",
extend: [
m.occupancy(),
m.numeric({
name: "motion_timeout",
cluster: 0x0406,
attribute: { ID: 0x0020, type: 0x21 },
description: "Occupied to unoccupied delay",
valueMin: 5,
valueMax: 60,
}),
m.enumLookup({
name: "illumination",
lookup: { dim: 0, bright: 1 },
cluster: 0xfc11,
attribute: { ID: 0x2001, type: 0x20 },
zigbeeCommandOptions: { manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SHENZHEN_COOLKIT_TECHNOLOGY_CO_LTD },
description: "Only updated when occupancy is detected",
access: "STATE",
}),
ewelinkBattery(),
],
ota: true,
},
{
zigbeeModel: ["SNZB-05P"],
model: "SNZB-05P",
vendor: "SONOFF",
description: "Zigbee water sensor",
extend: [m.battery(), m.iasZoneAlarm({ zoneType: "water_leak", zoneAttributes: ["alarm_1", "battery_low"] })],
},
{
zigbeeModel: ["SNZB-06P"],
model: "SNZB-06P",
vendor: "SONOFF",
description: "Zigbee occupancy sensor",
extend: [
m.occupancy({ reporting: false }),
m.numeric({
name: "occupancy_timeout",
cluster: 0x0406,
attribute: { ID: 0x0020, type: 0x21 },
description: "Unoccupied to occupied delay",
valueMin: 15,
valueMax: 65535,
}),
m.enumLookup({
name: "occupancy_sensitivity",
lookup: { low: 1, medium: 2, high: 3 },
cluster: 0x0406,
attribute: { ID: 0x0022, type: 0x20 },
description: "Sensitivity of human presence detection",
}),
m.enumLookup({
name: "illumination",
lookup: { dim: 0, bright: 1 },
cluster: 0xfc11,
attribute: { ID: 0x2001, type: 0x20 },
description: "Only updated when occupancy is detected",
zigbeeCommandOptions: { manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SHENZHEN_COOLKIT_TECHNOLOGY_CO_LTD },
access: "STATE",
}),
],
ota: true,
},
{
zigbeeModel: ["TRVZB"],
model: "TRVZB",
vendor: "SONOFF",
description: "Zigbee thermostatic radiator valve",
exposes: [
e
.climate()
.withSetpoint("occupied_heating_setpoint", 4, 35, 0.5)
.withLocalTemperature()
.withLocalTemperatureCalibration(-12.8, 12.7, 0.2)
.withSystemMode(["off", "auto", "heat"], ea.ALL, "Mode of the thermostat")
.withRunningState(["idle", "heat"], ea.STATE_GET),
e.battery(),
],
fromZigbee: [fz.thermostat, fz.battery],
toZigbee: [
tz.thermostat_local_temperature,
tz.thermostat_local_temperature_calibration,
tz.thermostat_occupied_heating_setpoint,
tz.thermostat_system_mode,
tz.thermostat_running_state,
],
extend: [
m.deviceAddCustomCluster("customSonoffTrvzb", {
ID: 0xfc11,
attributes: {
childLock: { ID: 0x0000, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN },
tamper: { ID: 0x2000, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
illumination: { ID: 0x2001, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
openWindow: { ID: 0x6000, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN },
frostProtectionTemperature: { ID: 0x6002, type: zigbee_herdsman_1.Zcl.DataType.INT16 },
idleSteps: { ID: 0x6003, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
closingSteps: { ID: 0x6004, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
valveOpeningLimitVoltage: { ID: 0x6005, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
valveClosingLimitVoltage: { ID: 0x6006, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
valveMotorRunningVoltage: { ID: 0x6007, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
valveOpeningDegree: { ID: 0x600b, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
valveClosingDegree: { ID: 0x600c, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
tempAccuracy: { ID: 0x6011, type: zigbee_herdsman_1.Zcl.DataType.INT16 },
externalTemperatureInput: {
ID: 0x600d,
type: zigbee_herdsman_1.Zcl.DataType.INT16,
},
temperatureSensorSelect: {
ID: 0x600e,
type: zigbee_herdsman_1.Zcl.DataType.UINT8,
},
},
commands: {},
commandsResponse: {},
}),
m.binary({
name: "child_lock",
cluster: "customSonoffTrvzb",
attribute: "childLock",
entityCategory: "config",
description: "Enables/disables physical input on the device",
valueOn: ["LOCK", 0x01],
valueOff: ["UNLOCK", 0x00],
}),
m.binary({
name: "open_window",
cluster: "customSonoffTrvzb",
attribute: "openWindow",
entityCategory: "config",
description: "Automatically turns off the radiator when local temperature drops by more than 1.5°C in 4.5 minutes.",
valueOn: ["ON", 0x01],
valueOff: ["OFF", 0x00],
}),
m.numeric({
name: "frost_protection_temperature",
cluster: "customSonoffTrvzb",
attribute: "frostProtectionTemperature",
entityCategory: "config",
description: "Minimum temperature at which to automatically turn on the radiator, if system mode is off, to prevent pipes freezing.",
valueMin: 4.0,
valueMax: 35.0,
valueStep: 0.5,
unit: "°C",
scale: 100,
}),
m.enumLookup({
name: "temperature_sensor_select",
label: "Temperature sensor",
lookup: { internal: 0, external: 1, external_2: 2, external_3: 3 },
cluster: "customSonoffTrvzb",
attribute: "temperatureSensorSelect",
description: "Whether to use the value of the internal temperature sensor or an external temperature sensor for the perceived local temperature. Using an external sensor does not require local temperature calibration.",
}),
m.numeric({
name: "external_temperature_input",
label: "External temperature",
cluster: "customSonoffTrvzb",
attribute: "externalTemperatureInput",
entityCategory: "config",
description: "The value of an external temperature sensor. Note: synchronisation of this value with the external temperature sensor needs to happen outside of Zigbee2MQTT.",
valueMin: 0.0,
valueMax: 99.9,
valueStep: 0.1,
unit: "°C",
scale: 100,
precision: 1,
}),
m.numeric({
name: "idle_steps",
cluster: "customSonoffTrvzb",
attribute: "idleSteps",
entityCategory: "diagnostic",
description: "Number of steps used for calibration (no-load steps)",
access: "STATE_GET",
}),
m.numeric({
name: "closing_steps",
cluster: "customSonoffTrvzb",
attribute: "closingSteps",
entityCategory: "diagnostic",
description: "Number of steps it takes to close the valve",
access: "STATE_GET",
}),
m.numeric({
name: "valve_opening_limit_voltage",
cluster: "customSonoffTrvzb",
attribute: "valveOpeningLimitVoltage",
entityCategory: "diagnostic",
description: "Valve opening limit voltage",
unit: "mV",
access: "STATE_GET",
}),
m.numeric({
name: "valve_closing_limit_voltage",
cluster: "customSonoffTrvzb",
attribute: "valveClosingLimitVoltage",
entityCategory: "diagnostic",
description: "Valve closing limit voltage",
unit: "mV",
access: "STATE_GET",
}),
m.numeric({
name: "valve_motor_running_voltage",
cluster: "customSonoffTrvzb",
attribute: "valveMotorRunningVoltage",
entityCategory: "diagnostic",
description: "Valve motor running voltage",
unit: "mV",
access: "STATE_GET",
}),
m.numeric({
name: "valve_opening_degree",
cluster: "customSonoffTrvzb",
attribute: "valveOpeningDegree",
entityCategory: "config",
description: "Valve open position (percentage) control. " +
"If the opening degree is set to 100%, the valve is fully open when it is opened. " +
"If the opening degree is set to 0%, the valve is fully closed when it is opened, " +
"and the default value is 100%. " +
"Note: only version v1.1.4 or higher is supported.",
valueMin: 0.0,
valueMax: 100.0,
valueStep: 1.0,
unit: "%",
}),
m.numeric({
name: "valve_closing_degree",
cluster: "customSonoffTrvzb",
attribute: "valveClosingDegree",
entityCategory: "config",
description: "Valve closed position (percentage) control. " +
"If the closing degree is set to 100%, the valve is fully closed when it is closed. " +
"If the closing degree is set to 0%, the valve is fully opened when it is closed, " +
"and the default value is 100%. " +
"Note: Only version v1.1.4 or higher is supported.",
valueMin: 0.0,
valueMax: 100.0,
valueStep: 1.0,
unit: "%",
}),
m.numeric({
name: "temperature_accuracy",
cluster: "customSonoffTrvzb",
attribute: "tempAccuracy",
entityCategory: "config",
description: "Temperature control accuracy. " +
"The range is -0.2 ~ -1°C, with an interval of 0.2, and the default is -1. " +
"If the temperature control accuracy is selected as -1°C (default value) and the target temperature is 26 degrees, " +
"then TRVZB will close the valve when the room temperature reaches 26 degrees and open the valve at 25 degrees. " +
"If -0.4°C is chosen as the temperature control accuracy, then the valve will close when the room temperature reaches 26 degrees and open at 25.6 degrees." +
"Note: Only version v1.3.0 or higher is supported.",
valueMin: -1.0,
valueMax: -0.2,
valueStep: 0.2,
unit: "°C",
scale: 100,
}),
sonoffExtend.weeklySchedule(),
m.customTimeResponse("1970_UTC"),
],
ota: true,
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["hvacThermostat"]);
await reporting.thermostatTemperature(endpoint);
await reporting.thermostatOccupiedHeatingSetpoint(endpoint);
await reporting.thermostatSystemMode(endpoint);
await endpoint.read("hvacThermostat", ["localTemperatureCalibration"]);
await endpoint.read(0xfc11, [0x0000, 0x6000, 0x6002, 0x6003, 0x6004, 0x6005, 0x6006, 0x6007, 0x600e]);
},
},
{
zigbeeModel: ["S60ZBTPF", "S60ZBTPG"],
model: "S60ZBTPF",
vendor: "SONOFF",
description: "Zigbee smart plug",
whiteLabel: [{ vendor: "SONOFF", model: "S60ZBTPG", fingerprint: [{ modelID: "S60ZBTPG" }] }],
extend: [
m.onOff({
powerOnBehavior: true,
skipDuplicateTransaction: true,
configureReporting: true,
}),
// m.electricityMeter({current: {divisor: 100}, voltage: {divisor: 100}, power: {divisor: 1}, energy: {divisor: 1000}}),
sonoffExtend.addCustomClusterEwelink(),
m.numeric({
name: "current",
cluster: "customClusterEwelink",
attribute: "acCurrentCurrentValue",
description: "Current",
unit: "A",
scale: 1000,
access: "STATE_GET",
}),
m.numeric({
name: "voltage",
cluster: "customClusterEwelink",
attribute: "acCurrentVoltageValue",
description: "Voltage",
unit: "V",
scale: 1000,
access: "STATE_GET",
}),
m.numeric({
name: "power",
cluster: "customClusterEwelink",
attribute: "acCurrentPowerValue",
description: "Active power",
unit: "W",
scale: 1000,
access: "STATE_GET",
}),
m.numeric({
name: "energy_yesterday",
cluster: "customClusterEwelink",
attribute: "energyYesterday",
description: "Electricity consumption for the yesterday",
unit: "kWh",
scale: 1000,
access: "STATE_GET",
}),
m.numeric({
name: "energy_today",
cluster: "customClusterEwelink",
attribute: "energyToday",
description: "Electricity consumption for the day",
unit: "kWh",
scale: 1000,
access: "STATE_GET",
}),
m.numeric({
name: "energy_month",
cluster: "customClusterEwelink",
attribute: "energyMonth",
description: "Electricity consumption for the month",
unit: "kWh",
scale: 1000,
access: "STATE_GET",
}),
sonoffExtend.inchingControlSet(),
m.binary({
name: "outlet_control_protect",
cluster: "customClusterEwelink",
attribute: "outlet_control_protect",
description: "Outlet overload protection Settings",
valueOff: [false, 0],
valueOn: [true, 1],
}),
sonoffExtend.overloadProtection(4000, 17),
],
ota: true,
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff", "customClusterEwelink"]);
await reporting.onOff(endpoint, { min: 1, max: 1800, change: 0 });
await endpoint.read("customClusterEwelink", ["acCurrentCurrentValue", "acCurrentVoltageValue", "acCurrentPowerValue", 0x7003, "outlet_control_protect"], defaultResponseOptions);
await endpoint.configureReporting("customClusterEwelink", [
{ attribute: "energyMonth", minimumReportInterval: 60, maximumReportInterval: 3600, reportableChange: 50 },
{ attribute: "energyYesterday", minimumReportInterval: 60, maximumReportInterval: 3600, reportableChange: 50 },
{ attribute: "energyToday", minimumReportInterval: 60, maximumReportInterval: 3600, reportableChange: 50 },
]);
},
},
{
zigbeeModel: ["SWV"],
model: "SWV",
vendor: "SONOFF",
description: "Zigbee smart water valve",
fromZigbee: [fz.flow],
exposes: [e.numeric("flow", ea.STATE).withDescription("Current water flow").withUnit("m³/h")],
extend: [
m.battery(),
m.onOff({
powerOnBehavior: false,
skipDuplicateTransaction: true,
configureReporting: true,
}),
sonoffExtend.addCustomClusterEwelink(),
m.enumLookup({
name: "current_device_status",
lookup: { normal_state: 0, water_shortage: 1, water_leakage: 2, "water_shortage & water_leakage": 3 },
cluster: "customClusterEwelink",
attribute: { ID: 0x500c, type: 0x20 },
description: "The water valve is in normal state, water shortage or water leakage",
access: "STATE_GET",
}),
m.binary({
name: "auto_close_when_water_shortage",
cluster: "customClusterEwelink",
attribute: "lackWaterCloseValveTimeout",
description: "Automatically shut down the water valve after the water shortage exceeds 30 minutes. Requires firmware version 1.0.4 or later!",
valueOff: ["DISABLE", 0],
valueOn: ["ENABLE", 30],
}),
sonoffExtend.cyclicTimedIrrigation(),
sonoffExtend.cyclicQuantitativeIrrigation(),
],
ota: true,
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genPowerCfg", "genOnOff"]);
await reporting.bind(endpoint, coordinatorEndpoint, ["msFlowMeasurement"]);
await reporting.onOff(endpoint, { min: 1, max: 1800, change: 0 });
await endpoint.read("customClusterEwelink", [0x500c, 0x5011]);
},
},
{
zigbeeModel: ["ZBMicro"],
model: "ZBMicro",
vendor: "SONOFF",
description: "Zigbee USB repeater plug",
extend: [
m.onOff(),
sonoffExtend.addCustomClusterEwelink(),
m.binary({
name: "rf_turbo_mode",
cluster: "customClusterEwelink",
attribute: "radioPowerWithManuCode",
zigbeeCommandOptions: manufacturerOptions,
description: "Enable/disable Radio power turbo mode",
valueOff: [false, 0x09],
valueOn: [true, 0x14],
}),
sonoffExtend.inchingControlSet(),
],
ota: true,
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff"]);
await reporting.onOff(endpoint, { min: 1, max: 1800, change: 0 });
await endpoint.read("customClusterEwelink", ["radioPowerWithManuCode"], manufacturerOptions);
},
},
{
zigbeeModel: ["ZBMINIR2"],
model: "ZBMINIR2",
vendor: "SONOFF",
description: "Zigbee smart switch",
exposes: [],
extend: [
m.commandsOnOff({ commands: ["toggle"] }),
m.onOff({ configureReporting: false }),
sonoffExtend.addCustomClusterEwelink(),
m.binary({
name: "network_indicator",
cluster: "customClusterEwelink",
attribute: "networkLed",
description: "Network indicator Settings, turn off/turn on the online network indicator.",
entityCategory: "config",
valueOff: [false, 0],
valueOn: [true, 1],
}),
m.binary({
name: "turbo_mode",
cluster: "customClusterEwelink",
attribute: "radioPower",
description: "Enable/disable Radio power turbo mode",
entityCategory: "config",
valueOff: [false, 0x09],
valueOn: [true, 0x14],
}),
m.binary({
name: "delayed_power_on_state",
cluster: "customClusterEwelink",
attribute: "delayedPowerOnState",
description: "Delayed Power-on State",
entityCategory: "config",
valueOff: [false, 0],
valueOn: [true, 1],
}),
m.numeric({
name: "delayed_power_on_time",
cluster: "customClusterEwelink",
attribute: "delayedPowerOnTime",
description: "Delayed Power-on time",
entityCategory: "config",
valueMin: 0.5,
valueMax: 3599.5,
valueStep: 0.5,
unit: "seconds",
scale: 2,
}),
m.binary({
name: "detach_relay_mode",
cluster: "customClusterEwelink",
attribute: "detachRelayMode",
description: "Enable/Disable detach relay mode",
entityCategory: "config",
valueOff: [false, 0],
valueOn: [true, 1],
}),
sonoffExtend.externalSwitchTriggerMode({ entityCategory: "config" }),
sonoffExtend.inchingControlSet({ entityCategory: "config" }),
],
ota: true,
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff", "customClusterEwelink"]);
await reporting.onOff(endpoint, { min: 1, max: 1800, change: 0 });
await endpoint.read("customClusterEwelink", ["radioPower", 0x0001, 0x0014, 0x0015, 0x0016, 0x0017], defaultResponseOptions);
},
},
{
zigbeeModel: ["ZBM5-1C-120"],
model: "ZBM5-1C-120",
vendor: "SONOFF",
description: "Zigbee Smart one-channel wall switch (type 120).",
ota: true,
extend: [
m.commandsOnOff({ commands: ["toggle"] }),
m.onOff(),
sonoffExtend.addCustomClusterEwelink(),
m.enumLookup({
name: "device_work_mode",
lookup: { "Zigbee end device": 0, "Zigbee router": 1 },
cluster: "customClusterEwelink",
attribute: "deviceWorkMode",
description: "The device runs as a Zigbee End device or Zigbee router.",
access: "STATE_GET",
}),
m.binary({
name: "network_indicator",
cluster: "customClusterEwelink",
attribute: "networkLed",
description: "Network indicator settings, turn off/on the blue online status network indicator.",
valueOff: [false, 0],
valueOn: [true, 1],
}),
sonoffExtend.detachRelayModeControl(1),
],
configure: async (device, coordinatorEndpoint) => {
const endpoint1 = device.getEndpoint(1);
await reporting.bind(endpoint1, coordinatorEndpoint, ["genOnOff", "customClusterEwelink"]);
await reporting.onOff(endpoint1, { min: 1, max: 1800, change: 0 });
await endpoint1.read("genOnOff", [0x0000, 0x4003], defaultResponseOptions);
await endpoint1.read("customClusterEwelink", [0x0010, 0x0018, 0x0019], defaultResponseOptions);
},
},
{
zigbeeModel: ["ZBM5-2C-120"],
model: "ZBM5-2C-120",
vendor: "SONOFF",
description: "Zigbee Smart two-channel wall switch (type 120).",
exposes: [],
ota: true,
extend: [
m.deviceEndpoints({ endpoints: { l1: 1, l2: 2 } }),
m.commandsOnOff({ commands: ["toggle"], endpointNames: ["l1", "l2"] }),
m.onOff({ endpointNames: ["l1", "l2"] }),
sonoffExtend.addCustomClusterEwelink(),
m.enumLookup({
name: "device_work_mode",
lookup: { "Zigbee end device": 0, "Zigbee router": 1 },
cluster: "customClusterEwelink",
attribute: "deviceWorkMode",
description: "The device runs as a Zigbee End device or Zigbee router.",
access: "STATE_GET",
}),
m.binary({
name: "network_indicator",
cluster: "customClusterEwelink",
attribute: "networkLed",
description: "Network indicator settings, turn off/on the blue online status network indicator.",
valueOff: [false, 0],
valueOn: [true, 1],
}),
sonoffExtend.detachRelayModeControl(2),
],
configure: async (device, coordinatorEndpoint) => {
const endpoint1 = device.getEndpoint(1);
await reporting.bind(endpoint1, coordinatorEndpoint, ["genOnOff", "customClusterEwelink"]);
await reporting.onOff(endpoint1, { min: 1, max: 1800, change: 0 });
await endpoint1.read("genOnOff", [0x0000, 0x4003], defaultResponseOptions);
await endpoint1.read("customClusterEwelink", [0x0010, 0x0018, 0x0019], defaultResponseOptions);
const endpoint2 = device.getEndpoint(2);
await reporting.bind(endpoint2, coordinatorEndpoint, ["genOnOff"]);
await reporting.onOff(endpoint2, { min: 1, max: 1805, change: 0 });
await endpoint2.read("genOnOff", [0x0000, 0x4003], defaultResponseOptions);
},
},
{
zigbeeModel: ["ZBM5-3C-120"],
model: "ZBM5-3C-120",
vendor: "SONOFF",
description: "Zigbee Smart three-channel wall switch (type 120).",
exposes: [],
ota: true,
extend: [
m.deviceEndpoints({ endpoints: { l1: 1, l2: 2, l3: 3 } }),
m.commandsOnOff({ commands: ["toggle"], endpointNames: ["l1", "l2", "l3"] }),
m.onOff({ endpointNames: ["l1", "l2", "l3"] }),
sonoffExtend.addCustomClusterEwelink(),
m.enumLookup({
name: "device_work_mode",
lookup: { "Zigbee end device": 0, "Zigbee router": 1 },
cluster: "customClusterEwelink",
attribute: "deviceWorkMode",
description: "The device runs as a Zigbee End device or Zigbee router.",
access: "STATE_GET",
}),
m.binary({
name: "network_indicator",
cluster: "customClusterEwelink",
attribute: "networkLed",
description: "Network indicator settings, turn off/on the blue online status network indicator.",
valueOff: [false, 0],
valueOn: [true, 1],
}),
sonoffExtend.detachRelayModeControl(3),
],
configure: async (device, coordinatorEndpoint) => {
const endpoint1 = device.getEndpoint(1);
await reporting.bind(endpoint1, coordinatorEndpoint, ["genOnOff", "customClusterEwelink"]);
await reporting.onOff(endpoint1, { min: 1, max: 1800, change: 0 });
await endpoint1.read("genOnOff", [0x0000, 0x4003], defaultResponseOptions);
await endpoint1.read("customClusterEwelink", [0x0010, 0x0018, 0x0019], defaultResponseOptions);
const endpoint2 = device.getEndpoint(2);
await reporting.bind(endpoint2, coordinatorEndpoint, ["genOnOff"]);
await reporting.onOff(endpoint2, { min: 1, max: 1805, change: 0 });
await endpoint2.read("genOnOff", [0x0000, 0x4003], defaultResponseOptions);
const endpoint3 = device.getEndpoint(3);
await reporting.bind(endpoint3, coordinatorEndpoint, ["genOnOff"]);
await reporting.onOff(endpoint3, { min: 1, max: 1810, change: 0 });
await endpoint3.read("genOnOff", [0x0000, 0x4003], defaultResponseOptions);
},
},
];
//# sourceMappingURL=sonoff.js.map