UNPKG

zigbee-herdsman-converters

Version:

Collection of device converters to be used with zigbee-herdsman

1,016 lines (1,015 loc) 112 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.definitions = void 0; const zigbee_herdsman_1 = require("zigbee-herdsman"); const fz = __importStar(require("../converters/fromZigbee")); const tz = __importStar(require("../converters/toZigbee")); const constants = __importStar(require("../lib/constants")); const 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) {