zigbee-herdsman-converters
Version:
Collection of device converters to be used with zigbee-herdsman
1,016 lines (1,015 loc) • 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) {