zigbee-herdsman-converters
Version:
Collection of device converters to be used with zigbee-herdsman
2,330 lines • 112 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 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 e = exposes.presets;
const ea = exposes.access;
const NS = "zhc:sber";
const { tuyaMagicPacket, tuyaOnOffActionLegacy } = tuya.modernExtend;
const manufacturerOptions = { manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD };
const defaultResponseOptions = { disableDefaultResponse: false };
const sdevices = {
fz: {
emergency_shutoff_state: {
cluster: "manuSpecificSDevices",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
if (msg.data.emergencyShutoffState !== undefined) {
const stateBitmap = msg.data.emergencyShutoffState;
const emergencyOvervoltage = stateBitmap & 0x01;
const emergencyUndervoltage = (stateBitmap & 0x02) >> 1;
const emergencyOvercurrent = (stateBitmap & 0x04) >> 2;
const emergencyOverheat = (stateBitmap & 0x08) >> 3;
return {
emergency_overvoltage: !!emergencyOvervoltage,
emergency_undervoltage: !!emergencyUndervoltage,
emergency_overcurrent: !!emergencyOvercurrent,
emergency_overheat: !!emergencyOverheat,
};
}
},
},
emergency_shutoff_state_v2: {
cluster: "manuSpecificSDevices",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
if (msg.data.emergencyShutoffState !== undefined) {
const stateBitmap = msg.data.emergencyShutoffState;
const emergencyOvercurrent = (stateBitmap & 0x04) >> 2;
const emergencyOverheat = (stateBitmap & 0x08) >> 3;
const emergencyNoLoad = (stateBitmap & 0x10) >> 4;
return {
emergency_overcurrent: !!emergencyOvercurrent,
emergency_overheat: !!emergencyOverheat,
emergency_no_load: !!emergencyNoLoad,
};
}
},
},
led_indicator_settings: {
cluster: "manuSpecificSDevices",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
if (msg.data.ledIndicatorOnEnable !== undefined) {
result[utils.postfixWithEndpointName("led_indicator_on_enable", msg, model, meta)] = msg.data.ledIndicatorOnEnable ? "ON" : "OFF";
}
if (msg.data.ledIndicatorOnH !== undefined) {
result[utils.postfixWithEndpointName("led_indicator_on_h", msg, model, meta)] = msg.data.ledIndicatorOnH;
}
if (msg.data.ledIndicatorOnS !== undefined) {
result[utils.postfixWithEndpointName("led_indicator_on_s", msg, model, meta)] = msg.data.ledIndicatorOnS;
}
if (msg.data.ledIndicatorOnB !== undefined) {
result[utils.postfixWithEndpointName("led_indicator_on_b", msg, model, meta)] = msg.data.ledIndicatorOnB;
}
if (msg.data.ledIndicatorOffEnable !== undefined) {
result[utils.postfixWithEndpointName("led_indicator_off_enable", msg, model, meta)] = msg.data.ledIndicatorOffEnable
? "ON"
: "OFF";
}
if (msg.data.ledIndicatorOffH !== undefined) {
result[utils.postfixWithEndpointName("led_indicator_off_h", msg, model, meta)] = msg.data.ledIndicatorOffH;
}
if (msg.data.ledIndicatorOffS !== undefined) {
result[utils.postfixWithEndpointName("led_indicator_off_s", msg, model, meta)] = msg.data.ledIndicatorOffS;
}
if (msg.data.ledIndicatorOffB !== undefined) {
result[utils.postfixWithEndpointName("led_indicator_off_b", msg, model, meta)] = msg.data.ledIndicatorOffB;
}
return result;
},
},
multistate_input: {
cluster: "genMultistateInput",
type: ["attributeReport"],
convert: (model, msg, publish, options, meta) => {
const actionLookup = { 0: "hold", 1: "single", 2: "double" };
const value = msg.data.presentValue;
const action = actionLookup[value];
return { action: utils.postfixWithEndpointName(action, msg, model, meta) };
},
},
decouple_relay: {
cluster: "genOnOff",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const state = {};
if (msg.data.sdevicesRelayDecouple !== undefined) {
const relayDecoupleLookup = { 0: "control_relay", 1: "decoupled" };
state[utils.postfixWithEndpointName("relay_mode", msg, model, meta)] = utils.getFromLookup(msg.data.sdevicesRelayDecouple, relayDecoupleLookup);
}
return state;
},
},
allow_double_click: {
cluster: "manuSpecificSDevices",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
if (msg.data.buttonEnableMultiClick !== undefined) {
result[utils.postfixWithEndpointName("allow_double_click", msg, model, meta)] = msg.data.buttonEnableMultiClick ? "ON" : "OFF";
}
return result;
},
},
cover_position_tilt: {
cluster: "closuresWindowCovering",
type: ["attributeReport", "readResponse"],
options: [exposes.options.invert_cover()],
convert: (model, msg, publish, options, meta) => {
const result = {};
// Zigbee officially expects 'open' to be 0 and 'closed' to be 100 whereas
// HomeAssistant etc. work the other way round.
// For zigbee-herdsman-converters: open = 100, close = 0
const metaInvert = model.meta?.coverInverted;
const invert = metaInvert ? !options.invert_cover : options.invert_cover;
if (msg.data.currentPositionLiftPercentage !== undefined && msg.data.currentPositionLiftPercentage <= 100) {
const value = msg.data.currentPositionLiftPercentage;
result[utils.postfixWithEndpointName("position", msg, model, meta)] = invert ? value : 100 - value;
result[utils.postfixWithEndpointName("current_state", msg, model, meta)] = metaInvert
? value === 0
? "CLOSE"
: "OPEN"
: value === 100
? "CLOSE"
: "OPEN";
}
if (msg.data.windowCoveringMode !== undefined) {
result[utils.postfixWithEndpointName("cover_mode", msg, model, meta)] = {
reversed_cover: (msg.data.windowCoveringMode & (1 << 0)) > 0,
};
}
return result;
},
},
closures: {
cluster: "closuresWindowCovering",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
if (msg.data.sdevicesCalibrationTime !== undefined) {
result[utils.postfixWithEndpointName("calibration_time", msg, model, meta)] = msg.data.sdevicesCalibrationTime * 0.1;
}
if (msg.data.sdevicesButtonsMode !== undefined) {
result[utils.postfixWithEndpointName("buttons_mode", msg, model, meta)] = msg.data.sdevicesButtonsMode ? "inverted" : "normal";
}
if (msg.data.sdevicesMotorTimeout !== undefined) {
result[utils.postfixWithEndpointName("motor_timeout", msg, model, meta)] = msg.data.sdevicesMotorTimeout;
}
return result;
},
},
sensor_error: {
cluster: "hvacThermostat",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
if (msg.data.sdevicesSensorError !== undefined) {
const stateBitmap = msg.data.sdevicesSensorError;
const errorRemoteDisconnected = stateBitmap & 0x01;
const errorLocalDisconnected = (stateBitmap & 0x02) >> 1;
const errorShortCircuit = (stateBitmap & 0x04) >> 2;
return {
sensor_error_remote_disconnected: !!errorRemoteDisconnected,
sensor_error_local_disconnected: !!errorLocalDisconnected,
sensor_error_short_circuit: !!errorShortCircuit,
};
}
},
},
event_status: {
cluster: "hvacThermostat",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
if (msg.data.sdevicesEventStatus !== undefined) {
const stateBitmap = msg.data.sdevicesEventStatus;
const statusInefficient = stateBitmap & 0x01;
const statusAntifrost = (stateBitmap & 0x02) >> 1;
const statusInvalidTime = (stateBitmap & 0x08) >> 3;
return {
status_heat_inefficient: !!statusInefficient,
status_antifrost: !!statusAntifrost,
status_invalid_time: !!statusInvalidTime,
};
}
},
},
},
tz: {
custom_on_off: {
key: ["state"],
convertSet: async (entity, key, value, meta) => {
const state = utils.isString(meta.message.state) ? meta.message.state.toLowerCase() : null;
utils.validateValue(state, ["toggle", "off", "on"]);
await entity.command("genOnOff", state, {}, utils.getOptions(meta.mapped, entity));
if (state === "toggle") {
const currentState = meta.state[`state${meta.endpoint_name ? `_${meta.endpoint_name}` : ""}`];
return currentState ? { state: { state: currentState === "OFF" ? "ON" : "OFF" } } : {};
}
return { state: { state: state.toUpperCase() } };
},
convertGet: async (entity, key, meta) => {
// Allow reading only if endpoint is explicitly stated.
// Workaround to prevent reading from non-existing genOnOff cluster at EP3 during availability check.
if (meta.endpoint_name) {
await entity.read("genOnOff", ["onOff"]);
}
},
},
led_indicator_on_settings: {
key: ["led_indicator_on_enable", "led_indicator_on_h", "led_indicator_on_s", "led_indicator_on_b"],
convertSet: async (entity, key, value, meta) => {
utils.assertString(key);
const payload = {};
switch (key) {
case "led_indicator_on_enable":
utils.assertString(value);
payload.ledIndicatorOnEnable = value.toUpperCase() === "ON" ? 1 : 0;
break;
case "led_indicator_on_h":
payload.ledIndicatorOnH = value;
break;
case "led_indicator_on_s":
payload.ledIndicatorOnS = value;
break;
case "led_indicator_on_b":
payload.ledIndicatorOnB = value;
break;
}
await utils.determineEndpoint(entity, meta, "manuSpecificSDevices").write("manuSpecificSDevices", payload, defaultResponseOptions);
return { state: { [key]: value } };
},
convertGet: async (entity, key, meta) => {
await utils
.determineEndpoint(entity, meta, "manuSpecificSDevices")
.read("manuSpecificSDevices", ["ledIndicatorOnEnable", "ledIndicatorOnH", "ledIndicatorOnS", "ledIndicatorOnB"], defaultResponseOptions);
},
},
led_indicator_off_settings: {
key: ["led_indicator_off_enable", "led_indicator_off_h", "led_indicator_off_s", "led_indicator_off_b"],
convertSet: async (entity, key, value, meta) => {
utils.assertString(key);
const payload = {};
switch (key) {
case "led_indicator_off_enable":
utils.assertString(value);
payload.ledIndicatorOffEnable = value.toUpperCase() === "ON" ? 1 : 0;
break;
case "led_indicator_off_h":
payload.ledIndicatorOffH = value;
break;
case "led_indicator_off_s":
payload.ledIndicatorOffS = value;
break;
case "led_indicator_off_b":
payload.ledIndicatorOffB = value;
break;
}
await utils.determineEndpoint(entity, meta, "manuSpecificSDevices").write("manuSpecificSDevices", payload, defaultResponseOptions);
return { state: { [key]: value } };
},
convertGet: async (entity, key, meta) => {
await utils
.determineEndpoint(entity, meta, "manuSpecificSDevices")
.read("manuSpecificSDevices", ["ledIndicatorOffEnable", "ledIndicatorOffH", "ledIndicatorOffS", "ledIndicatorOffB"], defaultResponseOptions);
},
},
identify: {
key: ["identify"],
options: [
e
.numeric("identify_timeout", ea.SET)
.withDescription("Sets the duration of the identification procedure in seconds (i.e., how long the device would flash)." +
"The value ranges from 1 to 60 seconds (default: 30).")
.withValueMin(1)
.withValueMax(60),
],
convertSet: async (entity, key, value, meta) => {
const identifyTimeout = meta.options.identify_timeout ?? 30;
await entity.command("genIdentify", "identify", { identifytime: identifyTimeout }, utils.getOptions(meta.mapped, entity));
},
},
decouple_relay: {
key: ["relay_mode"],
convertSet: async (entity, key, value, meta) => {
if (typeof value !== "string") {
return;
}
const relayDecoupleLookup = { control_relay: 0, decoupled: 1 };
utils.assertEndpoint(entity);
await utils.enforceEndpoint(entity, key, meta).write("genOnOff", {
sdevicesRelayDecouple: utils.getFromLookup(value, relayDecoupleLookup),
}, manufacturerOptions);
return { state: { relay_mode: value.toLowerCase() } };
},
convertGet: async (entity, key, meta) => {
utils.assertEndpoint(entity);
await utils
.enforceEndpoint(entity, key, meta)
.read("genOnOff", ["sdevicesRelayDecouple"], manufacturerOptions);
},
},
allow_double_click: {
key: ["allow_double_click"],
convertSet: async (entity, key, value, meta) => {
if (typeof value !== "string") {
return;
}
const payload = {};
payload.buttonEnableMultiClick = value.toUpperCase() === "ON" ? 1 : 0;
await utils.determineEndpoint(entity, meta, "manuSpecificSDevices").write("manuSpecificSDevices", payload, defaultResponseOptions);
return { state: { [key]: value } };
},
convertGet: async (entity, key, meta) => {
await utils
.determineEndpoint(entity, meta, "manuSpecificSDevices")
.read("manuSpecificSDevices", ["buttonEnableMultiClick"], defaultResponseOptions);
},
},
cover_mode: {
key: ["cover_mode"],
convertSet: async (entity, key, value, meta) => {
utils.assertObject(value, key);
const old_value = meta.state.cover_mode_cover;
const combined_value = Object.assign(old_value, value);
const windowCoveringMode = old_value | ((combined_value.reversed_cover ? 1 : 0) << 0);
await entity.write("closuresWindowCovering", { windowCoveringMode }, utils.getOptions(meta.mapped, entity));
return { state: { cover_mode: combined_value } };
},
convertGet: async (entity, key, meta) => {
await entity.read("closuresWindowCovering", ["windowCoveringMode"]);
},
},
cover_state: {
key: ["current_state"],
convertSet: async (entity, key, value, meta) => {
const lookup = {
open: "upOpen",
close: "downClose",
stop: "stop",
on: "upOpen",
off: "downClose",
};
utils.assertString(value, key);
value = value.toLowerCase();
await entity.command("closuresWindowCovering", utils.getFromLookup(value, lookup), {}, utils.getOptions(meta.mapped, entity));
},
},
closures_custom: {
key: ["calibration_time", "buttons_mode", "motor_timeout"],
convertSet: async (entity, key, value, meta) => {
if (key === "calibration_time") {
utils.assertNumber(value);
const calibration = value * 10;
await entity.write("closuresWindowCovering", { sdevicesCalibrationTime: calibration }, manufacturerOptions);
return { state: { calibration_time: value } };
}
if (key === "buttons_mode") {
utils.assertString(value, key);
value = value.toLowerCase();
const lookup = { normal: 0, inverted: 1 };
await entity.write("closuresWindowCovering", { sdevicesButtonsMode: utils.getFromLookup(value, lookup) }, manufacturerOptions);
return { state: { buttons_mode: value } };
}
if (key === "motor_timeout") {
utils.assertNumber(value);
await entity.write("closuresWindowCovering", { sdevicesMotorTimeout: value }, manufacturerOptions);
return { state: { motor_timeout: value } };
}
},
convertGet: async (entity, key, meta) => {
switch (key) {
case "calibration_time":
await entity.read("closuresWindowCovering", ["sdevicesCalibrationTime"], manufacturerOptions);
break;
case "buttons_mode":
await entity.read("closuresWindowCovering", ["sdevicesButtonsMode"], manufacturerOptions);
break;
case "motor_timeout":
await entity.read("closuresWindowCovering", ["sdevicesMotorTimeout"], manufacturerOptions);
break;
}
},
},
thermostat_abs_min_heat_setpoint_limit: {
key: ["abs_min_heat_setpoint_limit"],
convertGet: async (entity, key, meta) => {
await entity.read("hvacThermostat", ["absMinHeatSetpointLimit"]);
},
},
thermostat_abs_max_heat_setpoint_limit: {
key: ["abs_max_heat_setpoint_limit"],
convertGet: async (entity, key, meta) => {
await entity.read("hvacThermostat", ["absMaxHeatSetpointLimit"]);
},
},
},
};
const sdevicesCustomCluster = {
name: "manuSpecificSDevices",
ID: 0xfccf,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
attributes: {
buttonEnableMultiClick: { name: "buttonEnableMultiClick", ID: 0x1002, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN, write: true },
childLock: { name: "childLock", ID: 0x1003, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN, write: true },
ledIndicatorOnEnable: { name: "ledIndicatorOnEnable", ID: 0x2001, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN, write: true },
ledIndicatorOnH: { name: "ledIndicatorOnH", ID: 0x2002, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff },
ledIndicatorOnS: { name: "ledIndicatorOnS", ID: 0x2003, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true, max: 0xff },
ledIndicatorOnB: { name: "ledIndicatorOnB", ID: 0x2004, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true, max: 0xff },
ledIndicatorOffEnable: { name: "ledIndicatorOffEnable", ID: 0x2005, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN, write: true },
ledIndicatorOffH: { name: "ledIndicatorOffH", ID: 0x2006, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff },
ledIndicatorOffS: { name: "ledIndicatorOffS", ID: 0x2007, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true, max: 0xff },
ledIndicatorOffB: { name: "ledIndicatorOffB", ID: 0x2008, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true, max: 0xff },
ledIndicationType: { name: "ledIndicationType", ID: 0x2009, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true, max: 0xff },
emergencyShutoffState: { name: "emergencyShutoffState", ID: 0x3001, type: zigbee_herdsman_1.Zcl.DataType.BITMAP16 },
emergencyShutoffRecovery: { name: "emergencyShutoffRecovery", ID: 0x3002, type: zigbee_herdsman_1.Zcl.DataType.BITMAP16, write: true, max: 0xffff },
upperVoltageThreshold: { name: "upperVoltageThreshold", ID: 0x3011, type: zigbee_herdsman_1.Zcl.DataType.UINT32, write: true, max: 0xffffffff },
lowerVoltageThreshold: { name: "lowerVoltageThreshold", ID: 0x3012, type: zigbee_herdsman_1.Zcl.DataType.UINT32, write: true, max: 0xffffffff },
upperCurrentThreshold: { name: "upperCurrentThreshold", ID: 0x3013, type: zigbee_herdsman_1.Zcl.DataType.UINT32, write: true, max: 0xffffffff },
upperTempThreshold: { name: "upperTempThreshold", ID: 0x3014, type: zigbee_herdsman_1.Zcl.DataType.INT16, write: true, min: -32768 },
rmsVoltageMv: { name: "rmsVoltageMv", ID: 0x4001, type: zigbee_herdsman_1.Zcl.DataType.UINT32 },
rmsCurrentMa: { name: "rmsCurrentMa", ID: 0x4002, type: zigbee_herdsman_1.Zcl.DataType.UINT32 },
activePowerMw: { name: "activePowerMw", ID: 0x4003, type: zigbee_herdsman_1.Zcl.DataType.INT32 },
powerProfile: { name: "powerProfile", ID: 0x4100, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true, max: 0xff },
neutralPresence: { name: "neutralPresence", ID: 0x4101, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true, max: 0xff },
rtcStatus: { name: "rtcStatus", ID: 0x5001, type: zigbee_herdsman_1.Zcl.DataType.BITMAP16 },
},
commands: {},
commandsResponse: {},
};
const sdevicesExtend = {
sdevicesCustomCluster: () => m.deviceAddCustomCluster("manuSpecificSDevices", sdevicesCustomCluster),
haDiagnosticCluster: () => m.deviceAddCustomCluster("haDiagnostic", {
name: "haDiagnostic",
ID: zigbee_herdsman_1.Zcl.Clusters.haDiagnostic.ID,
attributes: {
sdevicesUptimeS: {
name: "sdevicesUptimeS",
ID: 0x1001,
type: zigbee_herdsman_1.Zcl.DataType.UINT32,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
},
sdevicesButton1Clicks: {
name: "sdevicesButton1Clicks",
ID: 0x1002,
type: zigbee_herdsman_1.Zcl.DataType.UINT32,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
},
sdevicesButton2Clicks: {
name: "sdevicesButton2Clicks",
ID: 0x1003,
type: zigbee_herdsman_1.Zcl.DataType.UINT32,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
},
sdevicesButton3Clicks: {
name: "sdevicesButton3Clicks",
ID: 0x1004,
type: zigbee_herdsman_1.Zcl.DataType.UINT32,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
},
sdevicesRelay1Switches: {
name: "sdevicesRelay1Switches",
ID: 0x1005,
type: zigbee_herdsman_1.Zcl.DataType.UINT32,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
},
sdevicesRelay2Switches: {
name: "sdevicesRelay2Switches",
ID: 0x1006,
type: zigbee_herdsman_1.Zcl.DataType.UINT32,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
},
},
commands: {},
commandsResponse: {},
}),
closuresWindowCoveringCluster: () => m.deviceAddCustomCluster("closuresWindowCovering", {
name: "closuresWindowCovering",
ID: zigbee_herdsman_1.Zcl.Clusters.closuresWindowCovering.ID,
attributes: {
sdevicesCalibrationTime: {
name: "sdevicesCalibrationTime",
ID: 0x1001,
type: zigbee_herdsman_1.Zcl.DataType.UINT16,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
write: true,
max: 0xffff,
},
sdevicesButtonsMode: {
name: "sdevicesButtonsMode",
ID: 0x1002,
type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
write: true,
max: 0x01,
},
sdevicesMotorTimeout: {
name: "sdevicesMotorTimeout",
ID: 0x1003,
type: zigbee_herdsman_1.Zcl.DataType.UINT16,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
write: true,
max: 0xffff,
},
},
commands: {},
commandsResponse: {},
}),
hvacThermostatCluster: () => m.deviceAddCustomCluster("hvacThermostat", {
name: "hvacThermostat",
ID: zigbee_herdsman_1.Zcl.Clusters.hvacThermostat.ID,
attributes: {
sdevicesRemoteTemperature: {
name: "sdevicesRemoteTemperature",
ID: 0x4001,
type: zigbee_herdsman_1.Zcl.DataType.INT16,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
write: true,
min: -32768,
},
sdevicesRemoteTemperatureCalibration: {
name: "sdevicesRemoteTemperatureCalibration",
ID: 0x4002,
type: zigbee_herdsman_1.Zcl.DataType.INT8,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
write: true,
min: -128,
},
sdevicesSensorMode: {
name: "sdevicesSensorMode",
ID: 0x4003,
type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
write: true,
max: 0x02,
},
sdevicesHeatingHysteresis: {
name: "sdevicesHeatingHysteresis",
ID: 0x4019,
type: zigbee_herdsman_1.Zcl.DataType.INT8,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
write: true,
min: -128,
},
sdevicesMinLocalTemperatureLimit: {
name: "sdevicesMinLocalTemperatureLimit",
ID: 0x40f0,
type: zigbee_herdsman_1.Zcl.DataType.INT16,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
write: true,
min: -32768,
},
sdevicesMaxLocalTemperatureLimit: {
name: "sdevicesMaxLocalTemperatureLimit",
ID: 0x40f1,
type: zigbee_herdsman_1.Zcl.DataType.INT16,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
write: true,
min: -32768,
},
sdevicesOutputMode: {
name: "sdevicesOutputMode",
ID: 0x4100,
type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
write: true,
max: 0xff,
},
sdevicesLocalSensorType: {
name: "sdevicesLocalSensorType",
ID: 0x4101,
type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
write: true,
max: 0xff,
},
sdevicesSensorError: {
name: "sdevicesSensorError",
ID: 0x4102,
type: zigbee_herdsman_1.Zcl.DataType.BITMAP16,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
},
sdevicesEventStatus: {
name: "sdevicesEventStatus",
ID: 0x4103,
type: zigbee_herdsman_1.Zcl.DataType.BITMAP16,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
},
sdevicesRemoteSensorTimeout: {
name: "sdevicesRemoteSensorTimeout",
ID: 0x4203,
type: zigbee_herdsman_1.Zcl.DataType.UINT16,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
write: true,
max: 0xffff,
},
},
commands: {},
commandsResponse: {},
}),
hvacUserInterfaceCfgCluster: () => m.deviceAddCustomCluster("hvacUserInterfaceCfg", {
name: "hvacUserInterfaceCfg",
ID: zigbee_herdsman_1.Zcl.Clusters.hvacUserInterfaceCfg.ID,
attributes: {
sdevicesBrightnessOperationsMode: {
name: "sdevicesBrightnessOperationsMode",
ID: 0x2001,
type: zigbee_herdsman_1.Zcl.DataType.UINT16,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
write: true,
max: 0xffff,
},
sdevicesBrightnessSteadyMode: {
name: "sdevicesBrightnessSteadyMode",
ID: 0x2002,
type: zigbee_herdsman_1.Zcl.DataType.UINT16,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
write: true,
max: 0xffff,
},
},
commands: {},
commandsResponse: {},
}),
genOnOffCluster: () => m.deviceAddCustomCluster("genOnOff", {
name: "genOnOff",
ID: zigbee_herdsman_1.Zcl.Clusters.genOnOff.ID,
attributes: {
sdevicesRelayDecouple: {
name: "sdevicesRelayDecouple",
ID: 0x10dc,
type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SBERDEVICES_LTD,
write: true,
},
},
commands: {},
commandsResponse: {},
}),
deviceInfo: () => {
const toZigbee = [
{
key: ["serial_number"],
convertGet: async (entity, key, meta) => {
await entity.read("genBasic", ["serialNumber"]);
},
},
];
const fromZigbee = [
{
cluster: "genBasic",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
if (msg.data.serialNumber !== undefined) {
result.serial_number = msg.data.serialNumber.toString();
}
return result;
},
},
];
const exposes = [e.text("serial_number", ea.STATE_GET).withLabel("Serial Number").withCategory("diagnostic")];
return { exposes, fromZigbee, toZigbee, isModernExtend: true };
},
onOffRelayDecouple: (args) => m.enumLookup({
name: "relay_mode",
description: "Decoupled mode for button",
cluster: "genOnOff",
attribute: "sdevicesRelayDecouple",
lookup: { control_relay: 0, decoupled: 1 },
zigbeeCommandOptions: manufacturerOptions,
...args,
}),
ledIndicatorSettings: () => {
const fromZigbee = [
{
cluster: "manuSpecificSDevices",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
if (msg.data.ledIndicatorOnEnable !== undefined) {
result.led_indicator_on_enable = msg.data.ledIndicatorOnEnable ? "ON" : "OFF";
}
if (msg.data.ledIndicatorOnH !== undefined) {
result.led_indicator_on_h = msg.data.ledIndicatorOnH;
}
if (msg.data.ledIndicatorOnS !== undefined) {
result.led_indicator_on_s = msg.data.ledIndicatorOnS;
}
if (msg.data.ledIndicatorOnB !== undefined) {
result.led_indicator_on_b = msg.data.ledIndicatorOnB;
}
if (msg.data.ledIndicatorOffEnable !== undefined) {
result.led_indicator_off_enable = msg.data.ledIndicatorOffEnable ? "ON" : "OFF";
}
if (msg.data.ledIndicatorOffH !== undefined) {
result.led_indicator_off_h = msg.data.ledIndicatorOffH;
}
if (msg.data.ledIndicatorOffS !== undefined) {
result.led_indicator_off_s = msg.data.ledIndicatorOffS;
}
if (msg.data.ledIndicatorOffB !== undefined) {
result.led_indicator_off_b = msg.data.ledIndicatorOffB;
}
return result;
},
},
];
const toZigbee = [
{
key: ["led_indicator_on_enable", "led_indicator_on_h", "led_indicator_on_s", "led_indicator_on_b"],
convertSet: async (entity, key, value, meta) => {
const payload = {};
switch (key) {
case "led_indicator_on_enable":
utils.assertString(value);
payload.ledIndicatorOnEnable = value.toUpperCase() === "ON" ? 1 : 0;
break;
case "led_indicator_on_h":
payload.ledIndicatorOnH = value;
break;
case "led_indicator_on_s":
payload.ledIndicatorOnS = value;
break;
case "led_indicator_on_b":
payload.ledIndicatorOnB = value;
break;
}
await entity.write("manuSpecificSDevices", payload, defaultResponseOptions);
return { state: { [key]: value } };
},
convertGet: async (entity, key, meta) => {
await entity.read("manuSpecificSDevices", ["ledIndicatorOnEnable", "ledIndicatorOnH", "ledIndicatorOnS", "ledIndicatorOnB"], defaultResponseOptions);
},
},
{
key: ["led_indicator_off_enable", "led_indicator_off_h", "led_indicator_off_s", "led_indicator_off_b"],
convertSet: async (entity, key, value, meta) => {
const payload = {};
switch (key) {
case "led_indicator_off_enable":
utils.assertString(value);
payload.ledIndicatorOffEnable = value.toUpperCase() === "ON" ? 1 : 0;
break;
case "led_indicator_off_h":
payload.ledIndicatorOffH = value;
break;
case "led_indicator_off_s":
payload.ledIndicatorOffS = value;
break;
case "led_indicator_off_b":
payload.ledIndicatorOffB = value;
break;
}
await entity.write("manuSpecificSDevices", payload, defaultResponseOptions);
return { state: { [key]: value } };
},
convertGet: async (entity, key, meta) => {
await entity.read("manuSpecificSDevices", ["ledIndicatorOffEnable", "ledIndicatorOffH", "ledIndicatorOffS", "ledIndicatorOffB"], defaultResponseOptions);
},
},
];
const exposes = [
e
.binary("led_indicator_on_enable", ea.ALL, "ON", "OFF")
.withLabel("LED indication")
.withDescription("Is LED indicator enabled in ON state"),
e
.numeric("led_indicator_on_h", ea.ALL)
.withUnit("°")
.withValueMin(0)
.withValueMax(359)
.withLabel("Hue")
.withDescription("Hue of LED in ON state"),
e
.numeric("led_indicator_on_s", ea.ALL)
.withValueMin(0)
.withValueMax(0xfe)
.withLabel("Saturation")
.withDescription("Saturation of LED in ON state"),
e
.numeric("led_indicator_on_b", ea.ALL)
.withValueMin(1)
.withValueMax(0xfe)
.withLabel("Brightness")
.withDescription("Brightness of LED in ON state"),
e
.binary("led_indicator_off_enable", ea.ALL, "ON", "OFF")
.withLabel("LED indication")
.withDescription("Is LED indicator enabled in OFF state"),
e
.numeric("led_indicator_off_h", ea.ALL)
.withUnit("°")
.withValueMin(0)
.withValueMax(359)
.withLabel("Hue")
.withDescription("Hue of LED in OFF state"),
e
.numeric("led_indicator_off_s", ea.ALL)
.withValueMin(0)
.withValueMax(0xfe)
.withLabel("Saturation")
.withDescription("Saturation of LED in OFF state"),
e
.numeric("led_indicator_off_b", ea.ALL)
.withValueMin(1)
.withValueMax(0xfe)
.withLabel("Brightness")
.withDescription("Brightness of LED in OFF state"),
];
return { exposes, fromZigbee, toZigbee, isModernExtend: true };
},
electricityMeter: () => {
const exposes = [e.voltage().withAccess(ea.STATE_GET), e.current().withAccess(ea.STATE_GET), e.power().withAccess(ea.STATE_GET)];
const fromZigbee = [
{
cluster: "manuSpecificSDevices",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
if (utils.hasAlreadyProcessedMessage(msg, model))
return;
const lookup = [
{ key: "rmsVoltageMv", name: "voltage", multiplier: 0.001 },
{ key: "rmsCurrentMa", name: "current", multiplier: 0.001 },
{ key: "activePowerMw", name: "power", multiplier: 0.001 },
];
const payload = {};
for (const entry of lookup) {
if (msg.data[entry.key] !== undefined) {
const value = msg.data[entry.key] * entry.multiplier;
payload[entry.name] = value;
}
}
return payload;
},
},
];
const toZigbee = [
{
key: ["voltage"],
convertGet: async (entity, key, meta) => {
await entity.read("manuSpecificSDevices", ["rmsVoltageMv"]);
},
},
{
key: ["current"],
convertGet: async (entity, key, meta) => {
await entity.read("manuSpecificSDevices", ["rmsCurrentMa"]);
},
},
{
key: ["power"],
convertGet: async (entity, key, meta) => {
await entity.read("manuSpecificSDevices", ["activePowerMw"]);
},
},
];
return { exposes, fromZigbee, toZigbee, isModernExtend: true };
},
rtcStatus: () => {
const exposes = [
e
.list("rtc_status", ea.STATE_GET, e
.composite("rtc_status_list", "rtc_status_list", ea.STATE_GET)
.withFeature(e.binary("unavailable", ea.STATE_GET, true, false).withDescription("RTC hardware in unavailable"))
.withFeature(e.binary("data_not_vaild", ea.STATE_GET, true, false).withDescription("RTC data is not valid")))
.withDescription("List of active RTC warnings"),
];
const toZigbee = [
{
key: ["rtc_status"],
convertGet: async (entity, key, meta) => {
await entity.read("manuSpecificSDevices", ["rtcStatus"]);
},
},
];
const fromZigbee = [
{
cluster: "manuSpecificSDevices",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
if (msg.data.rtcStatus !== undefined) {
const stateBitmap = msg.data.rtcStatus;
const statusRtcUnavailable = stateBitmap & 0x01;
const statusRtcDataNotValid = (stateBitmap & 0x02) >> 1;
const rtc_status = {
unavailable: !!statusRtcUnavailable,
data_not_vaild: !!statusRtcDataNotValid,
};
return { rtc_status: rtc_status };
}
},
},
];
return { exposes, fromZigbee, toZigbee, isModernExtend: true };
},
thermostatSensorType: () => m.enumLookup({
name: "sensor_type",
description: "Resistance of NTC sensor, default is 10 kOhm",
cluster: "hvacThermostat",
attribute: "sdevicesLocalSensorType",
lookup: {
"4p7K": 0,
"6p8K": 1,
"10K": 2,
"12K": 3,
"15K": 4,
"33K": 5,
"47K": 6,
},
zigbeeCommandOptions: manufacturerOptions,
}),
thermostatWeeklySchedule: () => {
const exposes = e
.composite("schedule", "weekly_schedule", ea.ALL)
.withDescription("The heating schedule to use when the operation mode is set to 'schedule'. " +
"Up to 10 transitions can be defined per day, where a transition is expressed in the format 'HH:mm/temperature', each " +
"separated by a space. The valid temperature range is 1-50°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);
const schedule = [];
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] === "" ? [] : value[dayOfWeekName].split(" ").sort();
if (transitions.length > 10) {
throw new Error("Invalid schedule: days must have no more than 10 transitions");
}
const payload = {
dayofweek: 1 << Number(dayOfWeekBit),
numoftrans: transitions.length,
mode: 1 << 0, // heat only
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/25.5), found: ${transition}`);
}
const hour = Number.parseInt(matches[1], 10);
const mins = Number.parseInt(matches[2], 10);
const temp = Number.parseFloat(matches[3]);
if (temp < 1 || temp > 50) {
throw new Error(`Invalid schedule: temperature value must be between 1-50 (inclusive), found: ${temp}`);
}
payload.transitions.push({
transitionTime: hour * 60 + mins,
heatSetpoint: Math.round(temp * 100),
});
}
schedule.push(payload);
}
const reduced_schedule = schedule.reduce((a, b) => {
const index = a.findIndex((x) => JSON.stringify(x.transitions) === JSON.stringify(b.transitions));
if (index < 0) {
a.push(b);
}
else {
a[index].dayofweek = a[index].dayofweek | b.dayofweek;
}
return a;
}, []);
for (const payload of reduced_schedule) {
await entity.command("hvacThermostat", "setWeeklySchedule", payload, utils.getOptions(meta.mapped, entity));
}
for (let dayOfWeekBit = 0; dayOfWeekBit < 7; dayOfWeekBit++) {
const payload = {
daystoreturn: 1 << dayOfWeekBit,
modetoreturn: 1 << 0, // heat only
};
await entity.command("hvacThermostat", "getWeeklySchedule", payload, { disableDefaultResponse: true });
}
},
convertGet: async (entity, key, meta) => {
for (let dayOfWeekBit = 0; dayOfWeekBit < 7; dayOfWeekBit++) {
const payload = {
daystoreturn: 1 << dayOfWeekBit,
modetoreturn: 1 << 0, // heat only
};
await entity.command("hvacThermostat", "getWeeklySchedule", payload, { disableDefaultResponse: true });
}
},
},
];
return {
exposes: [exposes],
fromZigbee,
toZigbee,
isModernExtend: true,
};
},
childLock: () => m.binary({
name: "child_lock",
cluster: "manuSpecificSDevices",
attribute: "childLock",
description: "Enable child lock to prevent manual button operation",
valueOn: ["ON", 0x01],
valueOff: ["OFF", 0x00],
zigbeeCommandOptions: manufacturerOptions,
}),
deviceTemperature: () => m.numeric({
name: "device_temperature",
cluster: "genDeviceTempCfg",
attribute: "currentTemperature",
description: "Temperature of the device",
unit: "°C",
access: "STATE_GET",
entityCategory: "diagnostic",
}),
emergencyShutoffRecovery: () => m.enumLookup({
name: "emergency_shutoff_recovery",
cluster: "manuSpecificSDevices",
attribute: "emergencyShutoffRecovery",
description: "Condition of automatic recovery after emergency shutoff",
lookup: { disabled: 0, voltage_is_good: 1 },
zigbeeCommandOptions: manufacturerOptions,
}),
upperVoltageThreshold: () => m.numeric({
name: "upper_voltage_threshold",
cluster: "manuSpecificSDevices",
attribute: "upperVoltageThreshold",
description: "Upper voltage threshold",
valueMin: 230000,
valueMax: 260000,
valueStep: 1000,
unit: "mV",
zigbeeCommandOptions: manufacturerOptions,
}),
lowerVoltageThreshold: () => m.numeric({
name: "lower_voltage_threshold",
cluster: "manuSpecificSDevices",
attribute: "lowerVoltageThreshold",
description: "Lower voltage threshold (use with caution)",
valueMin: 100000,
valueMax: 230000,
valueStep: 1000,
unit: "mV",
zigbeeCommandOptions: manufacturerOptions,
}),
upperCurrentThreshold: () => m.numeric({
name: "upper_current_threshold",
cluster: "manuSpecificSDevices",
attribute: "upperCurrentThreshold",
description: "Upper current threshold",
valueMin: 100,
valueMax: 16000,
valueStep: 100,
unit: "mA",
zigbeeCommandOptions: manufacturerOptions,
}),
temperatureThreshold: () => m.numeric({
name: "temperature_threshold",
cluster: "manuSpecificSDevices",
attribute: "upperTempThreshold",
description: "Overtemperature threshold (use with caution)",
valueMin: -200,
valueMax: 200,
valueStep: 1,
unit: "°C",
zigbeeCommandOptions: manufacturerOptions,
}),
deviceCustomDiagnostic: (buttonsCount, relaysCount) => {
const extend = m.numeric({
name: "resets_count",
cluster: "haDiagnostic",
attribute: "numberOfResets",
description: "(telemetry) Number of resets",
entityCategory: "diagnostic",
access: "STATE_GET",
});
// Custom diagnostics attributes should not be configured automatically
extend.configure = [];
const uptime = m.numeric({
name: "uptime",
cluster: "haDiagnostic",
attribute: "sdevicesUptimeS",
description: "(telemetry) Device uptime, in seconds",
entityCategory: "diagnostic",
unit: "s",
access: "STATE_GET",
zigbeeCommandOptions: manufacturerOptions,
});
extend.fromZigbee.push(...uptime.fromZigbee);
extend.toZigbee.push(...uptime.toZigbee);
extend.exposes.push(...uptime.exposes);
for (let i = 1; i <= Math.min(buttonsCount, 3); i++) {
const attrNames = ["sdevicesButton1Clicks", "sdevicesButton2Clicks", "sdevicesButton3Clicks"];
const buttonClicks = m.numeric({
name: `button_clicks_count_${i}`,
cluster: "haDiagnostic",
attribute: attrNames[i - 1],
description: `(telemetry) Total clicks count of button ${i}`,
entityCategory: "diagnostic",
access: "STATE_GET",
zigbeeCommandOptions: manufacturerOptions,
});
extend.fromZigbee.push(...buttonClicks.fromZigbee);
extend.toZigbee.push(...buttonClicks.toZigbee);
extend.exposes.push(...buttonClicks.exposes);
}
for (let i = 1; i <= Math.min(relaysCount, 2); i++) {
const attrNames = ["sdevicesRelay1Switches", "sdevicesRelay2Switches"];
const relayToggles = m.numeric({
name: `relay_switches_count_${i}`,
cluster: "haDiagnostic",
attribute: attrNames[i - 1],
description: `(telemetry) Total toggle count of relay ${i}`,
entityCategory: "diagnostic",
access: "STATE_GET",
zigbeeCommandOptions: manufacturerOptions,
reporting: false,
});
extend.fromZigbee.push(...relayToggles.fromZigbee);
extend.toZigbee.push(...relayToggles.toZigbee);
extend.exposes.push(...relayToggles.exposes);
}
return extend;
},
ledIndicationType: () => m.enumLookup({
name: "led_indication_type",
description: "Type of LED indication (ignored in cover controller mode). Either continuous light, or short flashes",
cluster: "manuSpecificSDevices",
attribute: "ledIndicationType",
lookup: { continuous: 0, flashes: 1 },
zigbeeCommandOptions: manufacturerOptions,
}),
powerProfile: () => m.enumLookup({
name: "power_profile",
description: "Power Profile when neutral wire is absent: quick mode is for fastest device response; anti-flicker (af) modes have different polling intervals (in ms). Anti-flicker fallback mode is the safest one: device will fall back to it automatically if unstable behavior has been detected in more responsive mode",
cluster: "manuSpecificSDevices",
attribute: "powerProfile",
lookup: { quick: 0, af_250: 1, af_500: 2, af_750: 3, af_1000: 4, af_1250: 5, af_1750: 6, af_2000: 7, af_fallback: 254 },
zigbeeCommandOptions: manufacturerOptions,
}),
neutralPresence: () => m.binary({
name: "neutral_presence",
cluster: "manuSpecificSDevices",
attribute: "neutralPresence",
description: "Neutral wire presence status",
valueOn: ["YES", 0x01],
valueOff: ["NO", 0x00],
access: "STATE_GET",
zigbeeCommandOptions: manufacturerOptions,
}),
};
exports.definitions = [
{
fingerprint: tuya.fingerprint("SM0202", ["_TYZB01_2jzbhomb"]),
model: "SBDV-00029",
vendor: "Sber",
description: "Smart motion sensor",
extend: [
m.iasZoneAlarm({ zoneType: "occupancy", zoneAttributes: ["alarm_1", "tamper", "battery_low"], alarmTimeout: true }),
m.battery({ voltage: true, voltageReporting: true }),
],
},
{
fingerprint: tuya.fingerprint("TS0203", ["_TYZB01_epni2jgy"]),
model: "SBDV-00030",
vendor: "Sber",
description: "Smart opening sensor",
extend: [
m.ignoreClusterReport({ cluster: "genBasic" }),
m.iasZoneAlarm({ zoneType: "contact", zoneAttributes: ["alarm_1", "tamper", "battery_low"] }),
m.battery({ voltage: true, voltageReporting: true }),
],
},
{
fingerprint: tuya.fingerprint("TS0041A", ["_TYZB01_ub7urdza"]),
model: "SBDV-00032",
vendor: "Sber",
description: "Smart button",
extend: [
tuya.clusters.addTuyaGenOnOffCluster(),
tuyaMagicPacket(),
tuyaOnOffActionLegacy({ actions: ["single", "double", "hold"] }),
m.battery({ percentageReporting: false }),
/*
* reporting.batteryPercentageRemaining removed as it was causing devices to fall of the network
* every 1 hour, with light flashing when it happened, extremely short battery life, 2 presses for
* action to register: https://github.com/Koenkk/zigbee2mqtt/issues/8072
* Initially wrapped in a try catch: https://github.com/Koenkk/zigbee2mqtt/issues/6313
*/
],
},
{
fingerprint: tuya.fingerprint("TS0201", ["_TZ3000_zfirri2d"]),
model: "SBDV-00079",
vendor: "Sber",
description: "Smart temperature and humidity sensor",
extend: [m.temperature(), m.humidity(), m.battery({ voltage: true, voltageReporting: true })],
},
{
fingerprint: tuya.fingerprint("TS0207", ["_TZ3000_c8bqthpo"]),
model: "SBDV-00154",
vendor: "Sber",
description: "Smart water leak sensor",
extend: [
m.ignoreClusterReport({ cluster: "genBasic" }),
m.iasZoneAlarm({ zoneType: "water_leak", zoneAttributes: ["alarm_1", "battery_low"] }),
m.battery(),
],
},
{
fingerprint: [{ modelID: "SBDV-00196", manufacturerName: "SDevices" }],
model: "SBDV-00196",
vendor: "Sber",
description: "Smart Wall Switch (with neutral, single button)",
fromZigbee: [],
toZigbee: [],
extend: [
sdevicesExtend.sdevicesCustomCluster(),
sdevicesExtend.haDiagnosticCluster(),
sdevicesExtend.genOnOffCluster(),
m.binary({
name: "allow_double_click",
cluster: "manuSpecificSDevices",
attribute: "buttonEnableMultiClick",
description: "Allow detection of double clicks, may introduce delay in reaction when enabled",
valueOn: ["ON", 0x01],
valueOff: ["OFF", 0x00],
zigbeeCommandOptions: manufacturerOptions,
}),
sdevicesExtend.ledIndicatorSettings(),
m.identify(),
m.onOff({
powerOnBehavior: true,
configureReporting: false,
}),
sdevicesExtend.onOffRelayDecouple({
name: "relay_mode",
description: "Decoupled mode for button",
}),
m.actionEnumLookup({
cluster: "genMultistateInput",
attribute: "presentValue",
actionLookup: { hold: 0, single: 1, double: 2 },
}),
sdevicesExtend.childLock(),
sdevicesExtend.deviceInfo(),
sdevicesExtend.deviceCustomDiagnostic(2, 1),
],
ota: true,
configure: async (device, coordinatorEndpoint) => {
await device.getEndpoint(1).read("genOnOff", ["onOff", "startUpOnOff"]);
await device.getEndpoint(1).read("genBasic", ["serialNumber"]);
await device
.getEndpoint(1)
.read("manuSpecificSDevices", [
"ledIndicatorOnEnable",
"ledIndicatorOnH",
"ledIndicatorOnS",
"ledIndicatorOnB",
"ledIndicatorOffEnable",
"ledIndicatorOffH",
"ledIndicatorOffS",
"ledIndicatorOffB",
]);
await device.getEndpoint(1).read("manuSpecificSDevices", ["buttonEnableMultiClick"]);
try {
await device.getEndpoint(1).read("manuSpecificSDevices", ["childLock"]);
}
catch (error) {
if (error.message.includes("UNSUPPORTED_ATTRIBUTE")) {
// ignore: attribute is not supported in early versions
}
else {
logger_1.logger.warning(`Configure failed: ${error}`, NS);
}
}
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff", "genMultistateInput"]);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["haDiagnostic"]);
},
},
{
fingerprint: [{ modelID: "SBDV-00197", manufacturerName: "SDevices" }],
model: "SBDV-00197",
vendor: "Sber",
description: "Smart Wall Switch (optional neutral wire, single button)",
fromZigbee: [],
toZigbee: [],
extend: [
sdevicesExtend.sdevicesCustomCluster(),
sdevicesExtend.haDiagnosticCluster(),
sdevicesExtend.genOnOffCluster(),
m.binary({
name: "allow_double_click",
cluster: "manuSpecificSDevices",
attribute: "buttonEnableMultiClick",
description: "Allow detection of double clicks, may introduce delay in reaction when enabled",
valueOn: ["ON", 0x01],
valueOff: ["OFF", 0x00],
zigbeeCommandOptions: manufacturerOptions,
}),
sdevicesExtend.ledIndicatorSettings(),
m.identify(),
m.onOff({
powerOnBehavior: true,
configureReporting: false,
}),
sdevicesExtend.onOffRelayDecouple({
name: "relay_mode",
description: "Decoupled mode for button",
}),
m.actionEnumLookup({
cluster: "genMultistateInput",
attribute: "presentValue",
actionLookup: { hold: 0, single: 1, double: 2 },
}),
sdevicesExtend.childLock(),
sdevicesExtend.deviceInfo(),
sdevicesExtend.neutralPresence(),
sdevicesExtend.powerProfile(),
sdevicesExtend.ledIndicationType(),
sdevicesExtend.deviceCustomDiagnostic(2, 1),
],
ota: true,
configure: async (device, coordinatorEndpoint) => {
await device.getEndpoint(1).read("genOnOff", ["onOff", "startUpOnOff"]);
await device.getEndpoint(1).read("genBasic", ["serialNumber"]);
await device
.getEndpoint(1)
.read("manuSpecificSDevices", [
"ledIndicatorOnEnable",
"ledIndicatorOnH",
"ledIndicatorOnS",
"ledIndicatorOnB",
"ledIndicatorOffEnable",
"ledIndicatorOffH",
"ledIndicatorOffS",
"ledIndicatorOffB",
]);
await device.getEndpoint(1).read("manuSpecificSDevices", ["buttonEnableMultiClick"]);
await device.getEndpoint(1).read("manuSpecificSDevices", ["childLock"]);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff", "genMultistateInput"]);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["haDiagnostic"]);
},
},
{
fingerprint: [{ modelID: "SBDV-00199", manufacturerName: "SDevices" }],
model: "SBDV-00199",
vendor: "Sber",
description: "Smart Wall Switch (with neutral, two buttons)",
fromZigbee: [
fz.on_off,
fz.power_on_behavior,
sdevices.fz.cover_position_tilt,
sdevices.fz.multistate_input,
sdevices.fz.led_indicator_settings,
sdevices.fz.decouple_relay,
sdevices.fz.allow_double_click,
sdevices.fz.closures,
],
toZigbee: [
sdevices.tz.custom_on_off,
tz.power_on_behavior,
tz.cover_position_tilt,
sdevices.tz.cover_state,
sdevices.tz.cover_mode,
sdevices.tz.identify,
sdevices.tz.led_indicator_on_settings,
sdevices.tz.led_indicator_off_settings,
sdevices.tz.decouple_relay,
sdevices.tz.allow_double_click,
sdevices.tz.closures_custom,
],
exposes: (device, options) => {
const switchExposes = [];
if (!utils.isDummyDevice(device)) {
const coversEpName = "cover";
const coversExposes = [
e.enum("State", ea.STATE_SET, ["OPEN", "CLOSE", "STOP"]).withProperty("current_state").withEndpoint(coversEpName),
e
.numeric("position", ea.ALL)
.withValueMin(0)
.withValueMax(100)
.withDescription("Position of this cover, 100 is fully open if 'Invert cover' option is false (default)")
.withUnit("%")
.withEndpoint(coversEpName),
e
.composite("cover_mode", "cover_mode", ea.ALL)
.withFeature(e.binary("reversed", ea.ALL, true, false).withDescription("Reversal of the motor rotating direction"))
.withEndpoint(coversEpName),
e
.numeric("calibration_time", ea.ALL)
.withValueMin(0)
.withValueMax(3600)
.withValueStep(0.1)
.withDescription("Calibration time")
.withUnit("s")
.withEndpoint(coversEpName),
e
.numeric("motor_timeout", ea.ALL)
.withValueMin(0)
.withValueMax(3600)
.withDescription("Timeout for continuous motor action")
.withUnit("s")
.withEndpoint(coversEpName),
e.enum("Buttons mode", ea.ALL, ["normal", "inverted"]).withProperty("buttons_mode").withEndpoint(coversEpName),
e
.enum("identify", ea.SET, ["identify"])
.withLabel("Identify")
.withDescription("Initiate device identification")
.withCategory("config")
.withEndpoint(coversEpName),
e
.binary("led_indicator_off_enable", ea.ALL, "ON", "OFF")
.withLabel("LED indication")
.withDescription("Is LED indicator enabled")
.withEndpoint(coversEpName),
e
.numeric("led_indicator_off_h", ea.ALL)
.withUnit("°")
.withValueMin(0)
.withValueMax(359)
.withLabel("Hue")
.withDescription("Hue of LED indicator")
.withEndpoint(coversEpName),
e
.numeric("led_indicator_off_s", ea.ALL)
.withValueMin(0)
.withValueMax(0xfe)
.withLabel("Saturation")
.withDescription("Saturation of LED indicator")
.withEndpoint(coversEpName),
e
.numeric("led_indicator_off_b", ea.ALL)
.withValueMin(1)
.withValueMax(0xfe)
.withLabel("Brightness")
.withDescription("Brightness of LED indicator")
.withEndpoint(coversEpName),
];
if (device.meta.window_covering_enabled) {
return [...coversExposes];
}
}
const endpointsCount = 2;
switchExposes.push(e.action(["hold_switch_1", "hold_switch_2", "single_switch_1", "single_switch_2", "double_switch_1", "double_switch_2"]));
for (let i = 1; i <= endpointsCount; i++) {
const epName = `switch_${i}`;
const epPrefix = `(${i}) `;
switchExposes.push(e.switch().withEndpoint(epName));
switchExposes.push(e.power_on_behavior(["off", "on", "toggle", "previous"]).withLabel(`${epPrefix}Power-on behavior`).withEndpoint(epName));
switchExposes.push(e
.enum("relay_mode", ea.ALL, ["control_relay", "decoupled"])
.withLabel(`${epPrefix}Relay mode`)
.withDescription("Decoupled mode")
.withEndpoint(epName));
switchExposes.push(e
.binary("allow_double_click", ea.ALL, "ON", "OFF")
.withLabel(`${epPrefix}Allow double clicks`)
.withDescription("Allow detection of double clicks, may introduce delay in reaction when enabled")
.withEndpoint(epName));
switchExposes.push(e
.enum("identify", ea.SET, ["identify"])
.withLabel(`${epPrefix}Identify`)
.withDescription("Initiate device identification")
.withCategory("config")
.withEndpoint(epName));
switchExposes.push(e
.binary("led_indicator_on_enable", ea.ALL, "ON", "OFF")
.withLabel(`${epPrefix}LED indication`)
.withDescription("Is LED indicator enabled in ON state")
.withEndpoint(epName));
switchExposes.push(e
.numeric("led_indicator_on_h", ea.ALL)
.withUnit("°")
.withValueMin(0)
.withValueMax(359)
.withLabel(`${epPrefix}Hue`)
.withDescription("Hue of LED in ON state")
.withEndpoint(epName));
switchExposes.push(e
.numeric("led_indicator_on_s", ea.ALL)
.withValueMin(0)
.withValueMax(0xfe)
.withLabel(`${epPrefix}Saturation`)
.withDescription("Saturation of LED in ON state")
.withEndpoint(epName));
switchExposes.push(e
.numeric("led_indicator_on_b", ea.ALL)
.withValueMin(1)
.withValueMax(0xfe)
.withLabel(`${epPrefix}Brightness`)
.withDescription("Brightness of LED in ON state")
.withEndpoint(epName));
switchExposes.push(e
.binary("led_indicator_off_enable", ea.ALL, "ON", "OFF")
.withLabel(`${epPrefix}LED indication`)
.withDescription("Is LED indicator enabled in OFF state")
.withEndpoint(epName));
switchExposes.push(e
.numeric("led_indicator_off_h", ea.ALL)
.withUnit("°")
.withValueMin(0)
.withValueMax(359)
.withLabel(`${epPrefix}Hue`)
.withDescription("Hue of LED in OFF state")
.withEndpoint(epName));
switchExposes.push(e
.numeric("led_indicator_off_s", ea.ALL)
.withValueMin(0)
.withValueMax(0xfe)
.withLabel(`${epPrefix}Saturation`)
.withDescription("Saturation of LED in OFF state")
.withEndpoint(epName));
switchExposes.push(e
.numeric("led_indicator_off_b", ea.ALL)
.withValueMin(1)
.withValueMax(0xfe)
.withLabel(`${epPrefix}Brightness`)
.withDescription("Brightness of LED in OFF state")
.withEndpoint(epName));
}
return [...switchExposes];
},
extend: [
m.deviceEndpoints({ endpoints: { switch_1: 1, switch_2: 2, cover: 3 } }),
sdevicesExtend.sdevicesCustomCluster(),
sdevicesExtend.haDiagnosticCluster(),
sdevicesExtend.genOnOffCluster(),
sdevicesExtend.closuresWindowCoveringCluster(),
sdevicesExtend.childLock(),
sdevicesExtend.deviceInfo(),
sdevicesExtend.deviceCustomDiagnostic(3, 2),
],
ota: true,
configure: async (device, coordinatorEndpoint) => {
if (!device.customClusters.manuSpecificSDevices) {
device.addCustomCluster("manuSpecificSDevices", sdevicesCustomCluster);
}
const coveringEp = device.getEndpoint(3);
if (coveringEp != null) {
try {
const deviceCoverMode = await device.getEndpoint(3).read("genBasic", ["deviceEnabled"]);
device.meta.window_covering_enabled = !!deviceCoverMode.deviceEnabled;
device.save();
}
catch (error) {
if (error.message.includes("UNSUPPORTED_ATTRIBUTE")) {
device.meta.window_covering_enabled = false;
device.save();
}
else {
throw error;
}
}
}
else {
device.meta.window_covering_enabled = false;
device.save();
}
if (device.meta.window_covering_enabled) {
/* Device is in Window Covering mode */
await device.getEndpoint(3).read("genBasic", ["serialNumber"]);
await device.getEndpoint(3).read("closuresWindowCovering", ["currentPositionLiftPercentage", "windowCoveringMode"]);
await device
.getEndpoint(3)
.read("closuresWindowCovering", ["sdevicesCalibrationTime", "sdevicesButtonsMode", "sdevicesMotorTimeout"], manufacturerOptions);
await device
.getEndpoint(3)
.read("manuSpecificSDevices", ["ledIndicatorOffEnable", "ledIndicatorOffH", "ledIndicatorOffS", "ledIndicatorOffB"], manufacturerOptions);
await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ["closuresWindowCovering"]);
await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ["manuSpecificSDevices"]);
await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ["haDiagnostic"]);
try {
await device.getEndpoint(3).read("manuSpecificSDevices", ["childLock"]);
}
catch (error) {
if (error.message.includes("UNSUPPORTED_ATTRIBUTE")) {
// ignore: attribute is not supported in early versions
}
else {
logger_1.logger.warning(`Configure failed: ${error}`, NS);
}
}
}
else {
/* Device is in 2-button Switch mode */
await device.getEndpoint(1).read("genBasic", ["serialNumber"]);
await device.getEndpoint(1).read("genOnOff", ["onOff", "startUpOnOff"]);
await device.getEndpoint(2).read("genOnOff", ["onOff", "startUpOnOff"]);
await device.getEndpoint(1).read("genOnOff", ["sdevicesRelayDecouple"], manufacturerOptions);
await device.getEndpoint(2).read("genOnOff", ["sdevicesRelayDecouple"], manufacturerOptions);
await device
.getEndpoint(1)
.read("manuSpecificSDevices", [
"buttonEnableMultiClick",
"ledIndicatorOnEnable",
"ledIndicatorOnH",
"ledIndicatorOnS",
"ledIndicatorOnB",
"ledIndicatorOffEnable",
"ledIndicatorOffH",
"ledIndicatorOffS",
"ledIndicatorOffB",
], manufacturerOptions);
await device
.getEndpoint(2)
.read("manuSpecificSDevices", [
"buttonEnableMultiClick",
"ledIndicatorOnEnable",
"ledIndicatorOnH",
"ledIndicatorOnS",
"ledIndicatorOnB",
"ledIndicatorOffEnable",
"ledIndicatorOffH",
"ledIndicatorOffS",
"ledIndicatorOffB",
], manufacturerOptions);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff", "genMultistateInput"]);
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff", "genMultistateInput"]);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["manuSpecificSDevices"]);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["haDiagnostic"]);
try {
await device.getEndpoint(1).read("manuSpecificSDevices", ["childLock"]);
}
catch (error) {
if (error.message.includes("UNSUPPORTED_ATTRIBUTE")) {
// ignore: attribute is not supported in early versions
}
else {
logger_1.logger.warning(`Configure failed: ${error}`, NS);
}
}
}
device.save();
},
},
{
fingerprint: [{ modelID: "SBDV-00200", manufacturerName: "SDevices" }],
model: "SBDV-00200",
vendor: "Sber",
description: "Smart Wall Switch (optional neutral wire, two buttons)",
fromZigbee: [
fz.on_off,
fz.power_on_behavior,
sdevices.fz.cover_position_tilt,
sdevices.fz.multistate_input,
sdevices.fz.led_indicator_settings,
sdevices.fz.decouple_relay,
sdevices.fz.allow_double_click,
sdevices.fz.closures,
],
toZigbee: [
sdevices.tz.custom_on_off,
tz.power_on_behavior,
tz.cover_position_tilt,
sdevices.tz.cover_state,
sdevices.tz.cover_mode,
sdevices.tz.identify,
sdevices.tz.led_indicator_on_settings,
sdevices.tz.led_indicator_off_settings,
sdevices.tz.decouple_relay,
sdevices.tz.allow_double_click,
sdevices.tz.closures_custom,
],
exposes: (device, options) => {
const switchExposes = [];
const coversEpName = "cover";
const coversExposes = [
e.enum("state", ea.STATE_SET, ["OPEN", "CLOSE", "STOP"]).withProperty("current_state").withEndpoint(coversEpName),
e
.numeric("position", ea.ALL)
.withValueMin(0)
.withValueMax(100)
.withDescription("Position of this cover, 100 is fully open if 'Invert cover' option is false (default)")
.withUnit("%")
.withEndpoint(coversEpName),
e
.composite("cover_mode", "cover_mode", ea.ALL)
.withFeature(e.binary("reversed", ea.ALL, true, false).withDescription("Reversal of the motor rotating direction"))
.withEndpoint(coversEpName),
e
.numeric("calibration_time", ea.ALL)
.withValueMin(0)
.withValueMax(3600)
.withValueStep(0.1)
.withDescription("Calibration time")
.withUnit("s")
.withEndpoint(coversEpName),
e
.numeric("motor_timeout", ea.ALL)
.withValueMin(0)
.withValueMax(3600)
.withDescription("Timeout for continuous motor action")
.withUnit("s")
.withEndpoint(coversEpName),
e.enum("buttons_mode", ea.ALL, ["normal", "inverted"]).withProperty("buttons_mode").withEndpoint(coversEpName),
e
.enum("identify", ea.SET, ["identify"])
.withLabel("Identify")
.withDescription("Initiate device identification")
.withCategory("config")
.withEndpoint(coversEpName),
e
.binary("led_indicator_off_enable", ea.ALL, "ON", "OFF")
.withLabel("LED indication")
.withDescription("Is LED indicator enabled")
.withEndpoint(coversEpName),
e
.numeric("led_indicator_off_h", ea.ALL)
.withUnit("°")
.withValueMin(0)
.withValueMax(359)
.withLabel("Hue")
.withDescription("Hue of LED indicator")
.withEndpoint(coversEpName),
e
.numeric("led_indicator_off_s", ea.ALL)
.withValueMin(0)
.withValueMax(0xfe)
.withLabel("Saturation")
.withDescription("Saturation of LED indicator")
.withEndpoint(coversEpName),
e
.numeric("led_indicator_off_b", ea.ALL)
.withValueMin(1)
.withValueMax(0xfe)
.withLabel("Brightness")
.withDescription("Brightness of LED indicator")
.withEndpoint(coversEpName),
];
const endpointsCount = 2;
switchExposes.push(e.action(["hold_switch_1", "hold_switch_2", "single_switch_1", "single_switch_2", "double_switch_1", "double_switch_2"]));
for (let i = 1; i <= endpointsCount; i++) {
const epName = `switch_${i}`;
const epPrefix = `(${i}) `;
switchExposes.push(e.switch().withEndpoint(epName));
switchExposes.push(e.power_on_behavior(["off", "on", "toggle", "previous"]).withLabel(`${epPrefix}Power-on behavior`).withEndpoint(epName));
switchExposes.push(e
.enum("relay_mode", ea.ALL, ["control_relay", "decoupled"])
.withLabel(`${epPrefix}Relay mode`)
.withDescription("Decoupled mode")
.withEndpoint(epName));
switchExposes.push(e
.binary("allow_double_click", ea.ALL, "ON", "OFF")
.withLabel(`${epPrefix}Allow double clicks`)
.withDescription("Allow detection of double clicks, may introduce delay in reaction when enabled")
.withEndpoint(epName));
switchExposes.push(e
.enum("identify", ea.SET, ["identify"])
.withLabel(`${epPrefix}Identify`)
.withDescription("Initiate device identification")
.withCategory("config")
.withEndpoint(epName));
switchExposes.push(e
.binary("led_indicator_on_enable", ea.ALL, "ON", "OFF")
.withLabel(`${epPrefix}LED indication`)
.withDescription("Is LED indicator enabled in ON state")
.withEndpoint(epName));
switchExposes.push(e
.numeric("led_indicator_on_h", ea.ALL)
.withUnit("°")
.withValueMin(0)
.withValueMax(359)
.withLabel(`${epPrefix}Hue`)
.withDescription("Hue of LED in ON state")
.withEndpoint(epName));
switchExposes.push(e
.numeric("led_indicator_on_s", ea.ALL)
.withValueMin(0)
.withValueMax(0xfe)
.withLabel(`${epPrefix}Saturation`)
.withDescription("Saturation of LED in ON state")
.withEndpoint(epName));
switchExposes.push(e
.numeric("led_indicator_on_b", ea.ALL)
.withValueMin(1)
.withValueMax(0xfe)
.withLabel(`${epPrefix}Brightness`)
.withDescription("Brightness of LED in ON state")
.withEndpoint(epName));
switchExposes.push(e
.binary("led_indicator_off_enable", ea.ALL, "ON", "OFF")
.withLabel(`${epPrefix}LED indication`)
.withDescription("Is LED indicator enabled in OFF state")
.withEndpoint(epName));
switchExposes.push(e
.numeric("led_indicator_off_h", ea.ALL)
.withUnit("°")
.withValueMin(0)
.withValueMax(359)
.withLabel(`${epPrefix}Hue`)
.withDescription("Hue of LED in OFF state")
.withEndpoint(epName));
switchExposes.push(e
.numeric("led_indicator_off_s", ea.ALL)
.withValueMin(0)
.withValueMax(0xfe)
.withLabel(`${epPrefix}Saturation`)
.withDescription("Saturation of LED in OFF state")
.withEndpoint(epName));
switchExposes.push(e
.numeric("led_indicator_off_b", ea.ALL)
.withValueMin(1)
.withValueMax(0xfe)
.withLabel(`${epPrefix}Brightness`)
.withDescription("Brightness of LED in OFF state")
.withEndpoint(epName));
}
if (!utils.isDummyDevice(device)) {
return device.meta.window_covering_enabled ? [...coversExposes] : [...switchExposes];
}
return [...switchExposes, ...coversExposes];
},
extend: [
m.deviceEndpoints({ endpoints: { switch_1: 1, switch_2: 2, cover: 3 } }),
sdevicesExtend.sdevicesCustomCluster(),
sdevicesExtend.haDiagnosticCluster(),
sdevicesExtend.genOnOffCluster(),
sdevicesExtend.closuresWindowCoveringCluster(),
sdevicesExtend.childLock(),
sdevicesExtend.deviceInfo(),
sdevicesExtend.neutralPresence(),
sdevicesExtend.powerProfile(),
sdevicesExtend.ledIndicationType(),
sdevicesExtend.deviceCustomDiagnostic(3, 2),
],
ota: true,
configure: async (device, coordinatorEndpoint) => {
if (!device.customClusters.manuSpecificSDevices) {
device.addCustomCluster("manuSpecificSDevices", sdevicesCustomCluster);
}
const coveringEp = device.getEndpoint(3);
if (coveringEp != null) {
try {
const deviceCoverMode = await device.getEndpoint(3).read("genBasic", ["deviceEnabled"]);
device.meta.window_covering_enabled = !!deviceCoverMode.deviceEnabled;
device.save();
}
catch (error) {
if (error.message.includes("UNSUPPORTED_ATTRIBUTE")) {
device.meta.window_covering_enabled = false;
device.save();
}
else {
throw error;
}
}
}
else {
device.meta.window_covering_enabled = false;
device.save();
}
if (device.meta.window_covering_enabled) {
/* Device is in Window Covering mode */
await device.getEndpoint(3).read("genBasic", ["serialNumber"]);
await device.getEndpoint(3).read("closuresWindowCovering", ["currentPositionLiftPercentage", "windowCoveringMode"]);
await device
.getEndpoint(3)
.read("closuresWindowCovering", ["sdevicesCalibrationTime", "sdevicesButtonsMode", "sdevicesMotorTimeout"], manufacturerOptions);
await device
.getEndpoint(3)
.read("manuSpecificSDevices", ["ledIndicatorOffEnable", "ledIndicatorOffH", "ledIndicatorOffS", "ledIndicatorOffB"], manufacturerOptions);
await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ["closuresWindowCovering"]);
await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ["manuSpecificSDevices"]);
await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ["haDiagnostic"]);
await device.getEndpoint(3).read("manuSpecificSDevices", ["childLock"]);
}
else {
/* Device is in 2-button Switch mode */
await device.getEndpoint(1).read("genBasic", ["serialNumber"]);
await device.getEndpoint(1).read("genOnOff", ["onOff", "startUpOnOff"]);
await device.getEndpoint(2).read("genOnOff", ["onOff", "startUpOnOff"]);
await device.getEndpoint(1).read("genOnOff", ["sdevicesRelayDecouple"], manufacturerOptions);
await device.getEndpoint(2).read("genOnOff", ["sdevicesRelayDecouple"], manufacturerOptions);
await device
.getEndpoint(1)
.read("manuSpecificSDevices", [
"buttonEnableMultiClick",
"ledIndicatorOnEnable",
"ledIndicatorOnH",
"ledIndicatorOnS",
"ledIndicatorOnB",
"ledIndicatorOffEnable",
"ledIndicatorOffH",
"ledIndicatorOffS",
"ledIndicatorOffB",
], manufacturerOptions);
await device
.getEndpoint(2)
.read("manuSpecificSDevices", [
"buttonEnableMultiClick",
"ledIndicatorOnEnable",
"ledIndicatorOnH",
"ledIndicatorOnS",
"ledIndicatorOnB",
"ledIndicatorOffEnable",
"ledIndicatorOffH",
"ledIndicatorOffS",
"ledIndicatorOffB",
], manufacturerOptions);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff", "genMultistateInput"]);
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff", "genMultistateInput"]);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["manuSpecificSDevices"]);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["haDiagnostic"]);
await device.getEndpoint(1).read("manuSpecificSDevices", ["childLock"]);
}
device.save();
},
},
{
fingerprint: [{ modelID: "SBDV-00202", manufacturerName: "SDevices" }],
model: "SBDV-00202",
vendor: "Sber",
description: "Smart Wall Socket",
fromZigbee: [sdevices.fz.emergency_shutoff_state],
exposes: [
e.binary("emergency_overvoltage", ea.STATE, true, false).withDescription("Overvoltage alarm is triggered").withCategory("diagnostic"),
e.binary("emergency_undervoltage", ea.STATE, true, false).withDescription("Undervoltage alarm is triggered").withCategory("diagnostic"),
e.binary("emergency_overcurrent", ea.STATE, true, false).withDescription("Overcurrent alarm is triggered").withCategory("diagnostic"),
e.binary("emergency_overheat", ea.STATE, true, false).withDescription("Overheat alarm is triggered").withCategory("diagnostic"),
],
extend: [
sdevicesExtend.sdevicesCustomCluster(),
sdevicesExtend.haDiagnosticCluster(),
m.identify(),
sdevicesExtend.deviceTemperature(),
m.onOff({
powerOnBehavior: true,
configureReporting: false,
}),
sdevicesExtend.childLock(),
sdevicesExtend.electricityMeter(),
sdevicesExtend.ledIndicatorSettings(),
sdevicesExtend.emergencyShutoffRecovery(),
sdevicesExtend.upperVoltageThreshold(),
sdevicesExtend.lowerVoltageThreshold(),
sdevicesExtend.upperCurrentThreshold(),
sdevicesExtend.temperatureThreshold(),
sdevicesExtend.deviceInfo(),
sdevicesExtend.deviceCustomDiagnostic(1, 1),
],
ota: true,
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await endpoint.read("genBasic", ["serialNumber"]);
await endpoint.read("genOnOff", ["onOff", "startUpOnOff"]);
await endpoint.read("manuSpecificSDevices", [
"childLock",
"rmsVoltageMv",
"rmsCurrentMa",
"activePowerMw",
]);
await endpoint.read("manuSpecificSDevices", [
"ledIndicatorOnEnable",
"ledIndicatorOnH",
"ledIndicatorOnS",
"ledIndicatorOnB",
"ledIndicatorOffEnable",
"ledIndicatorOffH",
"ledIndicatorOffS",
"ledIndicatorOffB",
]);
await endpoint.read("manuSpecificSDevices", [
"upperVoltageThreshold",
"lowerVoltageThreshold",
"upperCurrentThreshold",
"upperTempThreshold",
]);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff", "genDeviceTempCfg", "manuSpecificSDevices"]);
await reporting.bind(endpoint, coordinatorEndpoint, ["haDiagnostic"]);
},
},
{
fingerprint: [{ modelID: "SBDV-00205", manufacturerName: "SDevices" }],
model: "SBDV-00205",
vendor: "Sber",
description: "Smart Thermostat",
fromZigbee: [
sdevices.fz.emergency_shutoff_state_v2,
sdevices.fz.sensor_error,
sdevices.fz.event_status,
fz.temperature,
fz.thermostat,
fz.hvac_user_interface,
],
toZigbee: [
tz.thermostat_local_temperature,
tz.thermostat_occupied_heating_setpoint,
tz.thermostat_local_temperature_calibration,
tz.thermostat_system_mode,
tz.thermostat_running_mode,
tz.thermostat_running_state,
tz.thermostat_keypad_lockout,
tz.thermostat_min_heat_setpoint_limit,
tz.thermostat_max_heat_setpoint_limit,
tz.thermostat_control_sequence_of_operation,
tz.thermostat_clear_weekly_schedule,
tz.thermostat_programming_operation_mode,
sdevices.tz.thermostat_abs_min_heat_setpoint_limit,
sdevices.tz.thermostat_abs_max_heat_setpoint_limit,
],
exposes: [
e.binary("emergency_overcurrent", ea.STATE, true, false).withDescription("Overcurrent alarm is triggered").withCategory("diagnostic"),
e.binary("emergency_overheat", ea.STATE, true, false).withDescription("Overheat alarm is triggered").withCategory("diagnostic"),
e.binary("emergency_no_load", ea.STATE, true, false).withDescription("Load disconnection alarm is triggered").withCategory("diagnostic"),
e
.binary("sensor_error_remote_disconnected", ea.STATE, true, false)
.withDescription("Wireless sensor disconnected")
.withCategory("diagnostic"),
e
.binary("sensor_error_local_disconnected", ea.STATE, true, false)
.withDescription("Wired sensor disconnected")
.withCategory("diagnostic"),
e.binary("sensor_error_short_circuit", ea.STATE, true, false).withDescription("Wired sensor short circuit").withCategory("diagnostic"),
e.binary("status_heat_inefficient", ea.STATE, true, false).withDescription("Heating is inefficient").withCategory("diagnostic"),
e.binary("status_antifrost", ea.STATE, true, false).withDescription("Antifrost mode").withCategory("diagnostic"),
e
.binary("status_invalid_time", ea.STATE, true, false)
.withDescription("Time information is not valid, schedule is not functioning")
.withCategory("diagnostic"),
e.enum("keypad_lockout", ea.ALL, ["unlock", "lock1"]).withDescription("Enables/disables physical input on the device"),
e
.climate()
.withSetpoint("occupied_heating_setpoint", 1, 50, 0.5)
.withLocalTemperature(ea.STATE_GET, "Current temperature value used by thermostat")
.withSystemMode(["heat", "off", "sleep"])
.withLocalTemperatureCalibration()
.withControlSequenceOfOperation(["heating_only"], ea.ALL)
.withRunningMode(["off", "heat"])
.withRunningState(["idle", "heat"], ea.STATE_GET),
e.enum("clear_weekly_schedule", ea.SET, ["Clear"]),
e
.programming_operation_mode(["setpoint", "schedule"])
.withDescription("Controls how programming affects the thermostat. Possible values: setpoint (only use specified setpoint), " +
"schedule (follow programmed setpoint schedule)"),
e.numeric("abs_min_heat_setpoint_limit", ea.STATE_GET).withUnit("°C").withDescription("Absolute min temperature allowed on the device"),
e.numeric("abs_max_heat_setpoint_limit", ea.STATE_GET).withUnit("°C").withDescription("Absolute max temperature allowed on the device"),
e.min_heat_setpoint_limit(1, 35, 0.5),
e.max_heat_setpoint_limit(5, 50, 0.5),
],
extend: [
sdevicesExtend.sdevicesCustomCluster(),
sdevicesExtend.haDiagnosticCluster(),
sdevicesExtend.hvacThermostatCluster(),
sdevicesExtend.hvacUserInterfaceCfgCluster(),
sdevicesExtend.thermostatWeeklySchedule(),
m.numeric({
name: "min_local_temperature_limit",
cluster: "hvacThermostat",
attribute: "sdevicesMinLocalTemperatureLimit",
description: "Min Local Temperature Threshold",
valueMin: 1.0,
valueMax: 35.0,
valueStep: 0.01,
scale: 100,
unit: "°C",
zigbeeCommandOptions: manufacturerOptions,
}),
m.numeric({
name: "max_local_temperature_limit",
cluster: "hvacThermostat",
attribute: "sdevicesMaxLocalTemperatureLimit",
description: "Max Local Temperature Threshold",
valueMin: 5.0,
valueMax: 50.0,
valueStep: 0.01,
scale: 100,
unit: "°C",
zigbeeCommandOptions: manufacturerOptions,
}),
m.numeric({
name: "heating_hysteresis",
cluster: "hvacThermostat",
attribute: "sdevicesHeatingHysteresis",
description: "Heating hysteresis",
valueMin: 1.0,
valueMax: 10.0,
valueStep: 0.1,
scale: 10,
unit: "°C",
zigbeeCommandOptions: manufacturerOptions,
}),
sdevicesExtend.deviceTemperature(),
m.temperature({
label: "Sensor Temperature (Wired)",
description: "Temperature from wired NTC sensor",
}),
sdevicesExtend.electricityMeter(),
sdevicesExtend.upperCurrentThreshold(),
sdevicesExtend.temperatureThreshold(),
sdevicesExtend.thermostatSensorType(),
m.enumLookup({
name: "sensor_mode",
description: "Sensor mode selects active temperature sensor: local (wired), remote (wireless), or combined logic which uses both sensors",
cluster: "hvacThermostat",
attribute: "sdevicesSensorMode",
lookup: {
local: 0,
remote: 1,
both: 2,
},
zigbeeCommandOptions: manufacturerOptions,
}),
m.numeric({
name: "remote_temperature",
cluster: "hvacThermostat",
attribute: "sdevicesRemoteTemperature",
description: "Remote Temperature",
valueMin: -273.15,
valueMax: 327.67,
valueStep: 0.01,
scale: 100,
unit: "°C",
zigbeeCommandOptions: manufacturerOptions,
}),
m.numeric({
name: "remote_temperature_calibration",
cluster: "hvacThermostat",
attribute: "sdevicesRemoteTemperatureCalibration",
description: "Remote Temperature Calibration",
valueMin: -12.8,
valueMax: 12.7,
valueStep: 0.1,
scale: 10,
unit: "°C",
zigbeeCommandOptions: manufacturerOptions,
}),
m.numeric({
name: "remote_sensor_timeout",
cluster: "hvacThermostat",
attribute: "sdevicesRemoteSensorTimeout",
valueMin: 1,
valueMax: 65535,
description: "Timeout of remote temperature sensor (in seconds)",
zigbeeCommandOptions: manufacturerOptions,
}),
m.enumLookup({
name: "output_mode",
description: "Output mode: normal or inverted logic of relay",
cluster: "hvacThermostat",
attribute: "sdevicesOutputMode",
lookup: {
normal: 0,
inverted: 1,
},
zigbeeCommandOptions: manufacturerOptions,
}),
m.numeric({
name: "brightness_operations_mode",
cluster: "hvacUserInterfaceCfg",
attribute: "sdevicesBrightnessOperationsMode",
valueMin: 0,
valueMax: 1000,
description: "Brightness (active mode)",
zigbeeCommandOptions: manufacturerOptions,
}),
m.numeric({
name: "brightness_steady_mode",
cluster: "hvacUserInterfaceCfg",
attribute: "sdevicesBrightnessSteadyMode",
valueMin: 0,
valueMax: 1000,
description: "Brightness (steady mode)",
zigbeeCommandOptions: manufacturerOptions,
}),
m.identify(),
sdevicesExtend.rtcStatus(),
sdevicesExtend.deviceInfo(),
sdevicesExtend.deviceCustomDiagnostic(3, 1),
],
ota: true,
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await endpoint.read("genBasic", ["serialNumber"]);
await endpoint.read("manuSpecificSDevices", [
"rmsVoltageMv",
"rmsCurrentMa",
"activePowerMw",
"emergencyShutoffState",
"upperCurrentThreshold",
"upperTempThreshold",
]);
await endpoint.read("genDeviceTempCfg", ["currentTemperature"]);
await endpoint.read("msTemperatureMeasurement", ["measuredValue"]);
await endpoint.read("hvacThermostat", [
"occupiedHeatingSetpoint",
"localTemp",
"localTemperatureCalibration",
"absMinHeatSetpointLimit",
"absMaxHeatSetpointLimit",
"minHeatSetpointLimit",
"maxHeatSetpointLimit",
"ctrlSeqeOfOper",
"systemMode",
"runningMode",
]);
await endpoint.read("hvacThermostat", ["startOfWeek", "numberOfWeeklyTrans", "numberOfDailyTrans", "programingOperMode", "runningState"]);
await endpoint.read("hvacThermostat", [
"sdevicesRemoteTemperature",
"sdevicesRemoteTemperatureCalibration",
"sdevicesHeatingHysteresis",
"sdevicesMinLocalTemperatureLimit",
"sdevicesMaxLocalTemperatureLimit",
"sdevicesSensorError",
"sdevicesEventStatus",
]);
await endpoint.read("hvacThermostat", ["sdevicesRemoteSensorTimeout"]);
await endpoint.read("hvacUserInterfaceCfg", ["keypadLockout"]);
await endpoint.read("hvacUserInterfaceCfg", [
"sdevicesBrightnessOperationsMode",
"sdevicesBrightnessSteadyMode",
]);
try {
await endpoint.read("manuSpecificSDevices", ["rtcStatus"]);
}
catch (error) {
if (error.message.includes("UNSUPPORTED_ATTRIBUTE")) {
// ignore: attribute is not supported in early versions of firmware
}
else {
logger_1.logger.warning(`Configure failed: ${error}`, NS);
}
}
for (let dayOfWeekBit = 0; dayOfWeekBit < 7; dayOfWeekBit++) {
const payload = {
daystoreturn: 1 << dayOfWeekBit,
modetoreturn: 1 << 0, // heat only
};
await endpoint.command("hvacThermostat", "getWeeklySchedule", payload, { disableDefaultResponse: true });
}
await reporting.bind(endpoint, coordinatorEndpoint, [
"genDeviceTempCfg",
"msTemperatureMeasurement",
"hvacThermostat",
"hvacUserInterfaceCfg",
"manuSpecificSDevices",
]);
await reporting.bind(endpoint, coordinatorEndpoint, ["haDiagnostic"]);
},
},
];
//# sourceMappingURL=sber.js.map