UNPKG

zigbee-herdsman-converters

Version:

Collection of device converters to be used with zigbee-herdsman

870 lines • 39.2 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.extend = exports.tz = void 0; const constants = __importStar(require("./constants")); const constants_1 = require("./constants"); const exposes = __importStar(require("./exposes")); const logger_1 = require("./logger"); const reporting = __importStar(require("./reporting")); const reporting_1 = require("./reporting"); const globalStore = __importStar(require("./store")); const utils = __importStar(require("./utils")); const utils_1 = require("./utils"); const e = exposes.presets; const ea = exposes.access; const NS = "zhc:sunricher"; const sunricherManufacturerCode = 0x1224; const tz = { setModel: { key: ["model"], convertSet: async (entity, key, value, meta) => { await entity.write("genBasic", { modelId: value }); return { state: { model: value } }; }, }, }; exports.tz = tz; const extend = { configureReadModelID: () => { const configure = [ async (device, coordinatorEndpoint, definition) => { // https://github.com/Koenkk/zigbee-herdsman-converters/issues/3016#issuecomment-1027726604 const endpoint = device.endpoints[0]; const oldModel = device.modelID; const newModel = (await endpoint.read("genBasic", ["modelId"])).modelId; if (oldModel !== newModel) { logger_1.logger.info(`Detected Sunricher device mode change, from '${oldModel}' to '${newModel}'. Triggering re-interview.`, NS); await device.interview(); return; } }, ]; return { configure, isModernExtend: true }; }, externalSwitchType: () => { const attribute = 0x8803; const data_type = 0x20; const value_map = { 0: "push_button", 1: "normal_on_off", 2: "three_way", }; const value_lookup = { push_button: 0, normal_on_off: 1, three_way: 2, }; const fromZigbee = [ { cluster: "genBasic", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { if (Object.prototype.hasOwnProperty.call(msg.data, attribute)) { const value = msg.data[attribute]; return { external_switch_type: value_map[value] || "unknown", external_switch_type_numeric: value, }; } return undefined; }, }, ]; const toZigbee = [ { key: ["external_switch_type"], convertSet: async (entity, key, value, meta) => { utils.assertString(value); const numericValue = value_lookup[value] ?? Number.parseInt(value, 10); await entity.write("genBasic", { [attribute]: { value: numericValue, type: data_type } }, { manufacturerCode: sunricherManufacturerCode }); return { state: { external_switch_type: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("genBasic", [attribute], { manufacturerCode: sunricherManufacturerCode, }); }, }, ]; const exposes = [ e.enum("external_switch_type", ea.ALL, ["push_button", "normal_on_off", "three_way"]).withLabel("External switch type"), ]; const configure = [ async (device, coordinatorEndpoint, definition) => { const endpoint = device.getEndpoint(1); try { await endpoint.read("genBasic", [attribute], { manufacturerCode: sunricherManufacturerCode, }); } catch (error) { console.warn(`Failed to read external switch type attribute: ${error}`); } }, ]; return { fromZigbee, toZigbee, exposes, configure, isModernExtend: true, }; }, minimumPWM: () => { const attribute = 0x7809; const data_type = 0x20; const fromZigbee = [ { cluster: "genBasic", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { if (Object.prototype.hasOwnProperty.call(msg.data, attribute)) { console.log("from ", msg.data[attribute]); const value = Math.round(msg.data[attribute] / 5.1); return { minimum_pwm: value, }; } return undefined; }, }, ]; const toZigbee = [ { key: ["minimum_pwm"], convertSet: async (entity, key, value, meta) => { console.log("to ", value); const numValue = typeof value === "string" ? Number.parseInt(value) : value; utils.assertNumber(numValue); const zgValue = Math.round(numValue * 5.1); await entity.write("genBasic", { [attribute]: { value: zgValue, type: data_type } }, { manufacturerCode: sunricherManufacturerCode }); return { state: { minimum_pwm: numValue } }; }, convertGet: async (entity, key, meta) => { await entity.read("genBasic", [attribute], { manufacturerCode: sunricherManufacturerCode, }); }, }, ]; const exposes = [ e .numeric("minimum_pwm", ea.ALL) .withLabel("Minimum PWM") .withDescription("Power off the device and wait for 3 seconds before reconnecting to apply the settings.") .withValueMin(0) .withValueMax(50) .withUnit("%") .withValueStep(1), ]; const configure = [ async (device, coordinatorEndpoint, definition) => { const endpoint = device.getEndpoint(1); try { await endpoint.read("genBasic", [attribute], { manufacturerCode: sunricherManufacturerCode, }); } catch (error) { console.warn(`Failed to read external switch type attribute: ${error}`); } }, ]; return { fromZigbee, toZigbee, exposes, configure, isModernExtend: true, }; }, SRZG9002KR12Pro: () => { const cluster = 0xff03; const fromZigbee = [ { cluster: 0xff03, type: ["raw"], convert: (model, msg, publish, options, meta) => { const bytes = [...msg.data]; const messageType = bytes[3]; let action = "unknown"; if (messageType === 0x01) { const pressTypeMask = bytes[6]; const pressTypeLookup = { 1: "short_press", 2: "double_press", 3: "hold", 4: "hold_released", }; action = pressTypeLookup[pressTypeMask] || "unknown"; const buttonMask = (bytes[4] << 8) | bytes[5]; const specialButtonMap = { 9: "knob", 11: "k9", 12: "k10", 15: "k11", 16: "k12", }; const actionButtons = []; for (let i = 0; i < 16; i++) { if ((buttonMask >> i) & 1) { const button = i + 1; actionButtons.push(specialButtonMap[button] ?? `k${button}`); } } return { action, action_buttons: actionButtons }; } if (messageType === 0x03) { const directionMask = bytes[4]; const actionSpeed = bytes[6]; const directionMap = { 1: "clockwise", 2: "anti_clockwise", }; const direction = directionMap[directionMask] || "unknown"; action = `${direction}_rotation`; return { action, action_speed: actionSpeed }; } return { action }; }, }, ]; const exposes = [ e.action(["short_press", "double_press", "hold", "hold_released", "clockwise_rotation", "anti_clockwise_rotation"]), ]; const configure = [ async (device, coordinatorEndpoint, definition) => { const endpoint = device.getEndpoint(1); await endpoint.bind(cluster, coordinatorEndpoint); }, ]; return { fromZigbee, exposes, configure, isModernExtend: true, }; }, SRZG2836D5Pro: () => { const cluster = 0xff03; const fromZigbee = [ { cluster: 0xff03, type: ["raw"], convert: (model, msg, publish, options, meta) => { const bytes = [...msg.data]; const messageType = bytes[3]; let action = "unknown"; if (messageType === 0x01) { const pressTypeMask = bytes[6]; const pressTypeLookup = { 1: "short_press", 2: "double_press", 3: "hold", 4: "hold_released", }; action = pressTypeLookup[pressTypeMask] || "unknown"; const buttonMask = bytes[5]; const specialButtonLookup = { 1: "top_left", 2: "top_right", 3: "bottom_left", 4: "bottom_right", 5: "center", }; const actionButtons = []; for (let i = 0; i < 5; i++) { if ((buttonMask >> i) & 1) { const button = i + 1; actionButtons.push(specialButtonLookup[button] || `unknown_${button}`); } } return { action, action_buttons: actionButtons }; } if (messageType === 0x03) { const directionMask = bytes[4]; const actionSpeed = bytes[6]; const isStop = bytes[5] === 0x02; const directionMap = { 1: "clockwise", 2: "anti_clockwise", }; const direction = isStop ? "stop" : directionMap[directionMask] || "unknown"; action = `${direction}_rotation`; return { action, action_speed: actionSpeed }; } return { action }; }, }, ]; const exposes = [ e.action(["short_press", "double_press", "hold", "hold_released", "clockwise_rotation", "anti_clockwise_rotation", "stop_rotation"]), ]; const configure = [ async (device, coordinatorEndpoint, definition) => { const endpoint = device.getEndpoint(1); await endpoint.bind(cluster, coordinatorEndpoint); }, ]; return { fromZigbee, exposes, configure, isModernExtend: true, }; }, SRZG9002K16Pro: () => { const cluster = 0xff03; const fromZigbee = [ { cluster, type: ["raw"], convert: (model, msg, publish, options, meta) => { const bytes = [...msg.data]; const messageType = bytes[3]; let action = "unknown"; if (messageType === 0x01) { const pressTypeMask = bytes[6]; const pressTypeLookup = { 1: "short_press", 2: "double_press", 3: "hold", 4: "hold_released", }; action = pressTypeLookup[pressTypeMask] || "unknown"; const buttonMask = (bytes[4] << 8) | bytes[5]; const getButtonNumber = (input) => { const row = Math.floor((input - 1) / 4); const col = (input - 1) % 4; return col * 4 + row + 1; }; const actionButtons = []; for (let i = 0; i < 16; i++) { if ((buttonMask >> i) & 1) { const button = i + 1; actionButtons.push(`k${getButtonNumber(button)}`); } } return { action, action_buttons: actionButtons }; } return { action }; }, }, ]; const exposes = [e.action(["short_press", "double_press", "hold", "hold_released"])]; const configure = [ async (device, coordinatorEndpoint, definition) => { const endpoint = device.getEndpoint(1); await endpoint.bind(cluster, coordinatorEndpoint); }, ]; return { fromZigbee, exposes, configure, isModernExtend: true, }; }, indicatorLight() { const cluster = 0xfc8b; const attribute = 0xf001; const data_type = 0x20; const manufacturerCode = 0x120b; const exposes = [ e.enum("indicator_light", ea.ALL, ["on", "off"]).withDescription("Enable/disable the LED indicator").withCategory("config"), ]; const fromZigbee = [ { cluster, type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { if (!Object.prototype.hasOwnProperty.call(msg.data, attribute)) return; const indicatorLight = msg.data[attribute]; const firstBit = indicatorLight & 0x01; return { indicator_light: firstBit === 1 ? "on" : "off" }; }, }, ]; const toZigbee = [ { key: ["indicator_light"], convertSet: async (entity, key, value, meta) => { const attributeRead = await entity.read(cluster, [attribute]); if (attributeRead === undefined) return; // @ts-expect-error ignore const currentValue = attributeRead[attribute]; const newValue = value === "on" ? currentValue | 0x01 : currentValue & ~0x01; await entity.write(cluster, { [attribute]: { value: newValue, type: data_type } }, { manufacturerCode }); return { state: { indicator_light: value } }; }, convertGet: async (entity, key, meta) => { await entity.read(cluster, [attribute], { manufacturerCode }); }, }, ]; const configure = [ async (device, coordinatorEndpoint, definition) => { const endpoint = device.getEndpoint(1); await endpoint.bind(cluster, coordinatorEndpoint); await endpoint.read(cluster, [attribute], { manufacturerCode }); }, ]; return { exposes, configure, fromZigbee, toZigbee, isModernExtend: true, }; }, thermostatWeeklySchedule: () => { const exposes = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"].map((day) => e .text(`schedule_${day}`, ea.ALL) .withDescription(`Schedule for ${day.charAt(0).toUpperCase() + day.slice(1)}, example: "06:00/21.0 12:00/21.0 18:00/21.0 22:00/16.0"`) .withCategory("config")); 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 hours = Math.floor(t.transitionTime / 60); const minutes = t.transitionTime % 60; return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}/${t.heatSetpoint / 100}`; }) .sort() .join(" "); return { ...meta.state.weekly_schedule, [`schedule_${day}`]: transitions, }; }, }, ]; const toZigbee = [ { key: [ "schedule_sunday", "schedule_monday", "schedule_tuesday", "schedule_wednesday", "schedule_thursday", "schedule_friday", "schedule_saturday", ], convertSet: async (entity, key, value, meta) => { const transitionRegex = /^(0[0-9]|1[0-9]|2[0-3]):([0-5][0-9])\/(\d+(\.\d+)?)$/; const dayOfWeekName = key.replace("schedule_", ""); utils.assertString(value, dayOfWeekName); const dayKey = utils.getKey(constants.thermostatDayOfWeek, dayOfWeekName.toLowerCase(), null); if (!dayKey) throw new Error(`Invalid schedule: invalid day name, found: ${dayOfWeekName}`); const transitions = value.split(" ").sort(); if (transitions.length !== 4) { throw new Error("Invalid schedule: days must have exactly 4 transitions"); } const payload = { dayofweek: 1 << Number(dayKey), numoftrans: transitions.length, mode: 1 << 0, transitions: transitions.map((transition) => { const matches = transition.match(transitionRegex); if (!matches) { throw new Error(`Invalid schedule: transitions must be in format HH:mm/temperature (e.g. 12:00/15.5), found: ${transition}`); } const [, hours, minutes, temp] = matches; const temperature = Number.parseFloat(temp); if (temperature < 4 || temperature > 35) { throw new Error(`Invalid schedule: temperature value must be between 4-35 (inclusive), found: ${temperature}`); } return { transitionTime: Number.parseInt(hours) * 60 + Number.parseInt(minutes), heatSetpoint: Math.round(temperature * 100), }; }), }; await entity.command("hvacThermostat", "setWeeklySchedule", payload, utils.getOptions(meta.mapped, entity)); }, convertGet: async (entity, key, meta) => { const dayOfWeekName = key.replace("schedule_", ""); const dayKey = utils.getKey(constants.thermostatDayOfWeek, dayOfWeekName.toLowerCase(), null); await entity.command("hvacThermostat", "getWeeklySchedule", { daystoreturn: dayKey !== null ? 1 << Number(dayKey) : 0xff, modetoreturn: 1, }, utils.getOptions(meta.mapped, entity)); }, }, ]; const configure = [ async (device, coordinatorEndpoint, definition) => { const endpoint = device.getEndpoint(1); await endpoint.command("hvacThermostat", "getWeeklySchedule", { daystoreturn: 0xff, modetoreturn: 1, }); }, ]; return { exposes, fromZigbee, toZigbee, configure, isModernExtend: true }; }, thermostatChildLock: () => { const exposes = [e.binary("child_lock", ea.ALL, "LOCK", "UNLOCK").withDescription("Enables/disables physical input on the device")]; const fromZigbee = [ { cluster: "hvacUserInterfaceCfg", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { if (Object.hasOwn(msg.data, "keypadLockout")) { return { child_lock: msg.data.keypadLockout === 0 ? "UNLOCK" : "LOCK", }; } return {}; }, }, ]; const toZigbee = [ { key: ["child_lock"], convertSet: async (entity, key, value, meta) => { const keypadLockout = Number(value === "LOCK"); await entity.write("hvacUserInterfaceCfg", { keypadLockout }); return { state: { child_lock: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("hvacUserInterfaceCfg", ["keypadLockout"]); }, }, ]; const configure = [ async (device, coordinatorEndpoint, definition) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ["hvacUserInterfaceCfg"]); await endpoint.read("hvacUserInterfaceCfg", ["keypadLockout"]); await reporting.thermostatKeypadLockMode(endpoint); }, ]; return { exposes, fromZigbee, toZigbee, configure, isModernExtend: true }; }, thermostatPreset: () => { const systemModeLookup = { 0: "off", 1: "auto", 3: "cool", 4: "manual", 5: "emergency_heating", 6: "precooling", 7: "fan_only", 8: "dry", 9: "sleep", }; const awayOrBoostModeLookup = { 0: "normal", 1: "away", 2: "forced" }; const fromZigbee = [ { cluster: "hvacThermostat", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { if (!Object.hasOwn(msg.data, "systemMode") && !Object.hasOwn(msg.data, "awayOrBoostMode")) return; const systemMode = msg.data.systemMode ?? globalStore.getValue(msg.device, "systemMode"); const awayOrBoostMode = msg.data.awayOrBoostMode ?? globalStore.getValue(msg.device, "awayOrBoostMode"); globalStore.putValue(msg.device, "systemMode", systemMode); globalStore.putValue(msg.device, "awayOrBoostMode", awayOrBoostMode); const result = {}; if (awayOrBoostMode !== undefined && awayOrBoostMode !== 0) { result.preset = utils.getFromLookup(awayOrBoostMode, awayOrBoostModeLookup); result.away_or_boost_mode = utils.getFromLookup(awayOrBoostMode, awayOrBoostModeLookup); if (systemMode !== undefined) { result.system_mode = constants.thermostatSystemModes[systemMode]; } } else if (systemMode !== undefined) { result.preset = utils.getFromLookup(systemMode, systemModeLookup); result.system_mode = constants.thermostatSystemModes[systemMode]; if (awayOrBoostMode !== undefined) { result.away_or_boost_mode = utils.getFromLookup(awayOrBoostMode, awayOrBoostModeLookup); } } return result; }, }, ]; const toZigbee = [ { key: ["preset"], convertSet: async (entity, key, value, meta) => { if (value === "away" || value === "forced") { const awayOrBoostMode = value === "away" ? 1 : 2; globalStore.putValue(entity, "awayOrBoostMode", awayOrBoostMode); if (value === "away") { await entity.read("hvacThermostat", ["unoccupiedHeatingSetpoint"]); } await entity.write("hvacThermostat", { awayOrBoostMode }); return { state: { preset: value, away_or_boost_mode: value } }; } globalStore.putValue(entity, "awayOrBoostMode", 0); const systemMode = utils.getKey(systemModeLookup, value, undefined, Number); await entity.write("hvacThermostat", { systemMode }); if (typeof systemMode === "number") { return { state: { // @ts-expect-error ignore preset: systemModeLookup[systemMode], system_mode: constants.thermostatSystemModes[systemMode], }, }; } }, }, { key: ["system_mode"], convertSet: async (entity, key, value, meta) => { const systemMode = utils.getKey(constants.thermostatSystemModes, value, undefined, Number); if (systemMode === undefined || typeof systemMode !== "number") { throw new Error(`Invalid system mode: ${value}`); } await entity.write("hvacThermostat", { systemMode }); return { state: { // @ts-expect-error ignore preset: systemModeLookup[systemMode], system_mode: constants.thermostatSystemModes[systemMode], }, }; }, convertGet: async (entity, key, meta) => { await entity.read("hvacThermostat", ["systemMode"]); }, }, ]; const configure = [ async (device, coordinatorEndpoint, definition) => { const endpoint = device.getEndpoint(1); await endpoint.read("hvacThermostat", ["systemMode"]); await endpoint.read("hvacThermostat", ["awayOrBoostMode"]); await reporting.bind(endpoint, coordinatorEndpoint, ["hvacThermostat"]); await reporting.thermostatSystemMode(endpoint); await endpoint.configureReporting("hvacThermostat", (0, reporting_1.payload)("awayOrBoostMode", 10, constants_1.repInterval.HOUR, null)); }, ]; return { fromZigbee, toZigbee, configure, isModernExtend: true }; }, thermostatCurrentHeatingSetpoint: () => { const getAwayOrBoostMode = async (entity) => { let result = globalStore.getValue(entity, "awayOrBoostMode"); if (result === undefined) { const attributeRead = await entity.read("hvacThermostat", ["awayOrBoostMode"]); // @ts-expect-error ignore result = attributeRead.awayOrBoostMode; globalStore.putValue(entity, "awayOrBoostMode", result); } return result; }; const fromZigbee = [ { cluster: "hvacThermostat", type: ["attributeReport", "readResponse"], convert: async (model, msg, publish, options, meta) => { const hasHeatingSetpoints = Object.hasOwn(msg.data, "occupiedHeatingSetpoint") || Object.hasOwn(msg.data, "unoccupiedHeatingSetpoint"); if (!hasHeatingSetpoints) return; const processSetpoint = (value) => { if (value === undefined) return undefined; return (0, utils_1.precisionRound)(value, 2) / 100; }; const occupiedSetpoint = processSetpoint(msg.data.occupiedHeatingSetpoint); const unoccupiedSetpoint = processSetpoint(msg.data.unoccupiedHeatingSetpoint); const awayOrBoostMode = msg.data.awayOrBoostMode ?? (await getAwayOrBoostMode(msg.device.getEndpoint(1))); const result = {}; if (awayOrBoostMode === 1 && unoccupiedSetpoint !== undefined) { result.current_heating_setpoint = unoccupiedSetpoint; } else if (occupiedSetpoint !== undefined) { result.current_heating_setpoint = occupiedSetpoint; } return result; }, }, ]; const toZigbee = [ { key: ["current_heating_setpoint"], convertSet: async (entity, key, value, meta) => { utils.assertNumber(value, key); const awayOrBoostMode = await getAwayOrBoostMode(entity); let convertedValue; if (meta.options.thermostat_unit === "fahrenheit") { convertedValue = Math.round(utils.normalizeCelsiusVersionOfFahrenheit(value) * 100); } else { convertedValue = Number((Math.round(Number((value * 2).toFixed(1))) / 2).toFixed(1)) * 100; } const attribute = awayOrBoostMode === 1 ? "unoccupiedHeatingSetpoint" : "occupiedHeatingSetpoint"; await entity.write("hvacThermostat", { [attribute]: convertedValue }); return { state: { current_heating_setpoint: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("hvacThermostat", ["occupiedHeatingSetpoint", "unoccupiedHeatingSetpoint"]); }, }, ]; const configure = [ async (device, coordinatorEndpoint, definition) => { const endpoint = device.getEndpoint(1); await endpoint.read("hvacThermostat", ["occupiedHeatingSetpoint", "unoccupiedHeatingSetpoint"]); await reporting.bind(endpoint, coordinatorEndpoint, ["hvacThermostat"]); await reporting.thermostatOccupiedHeatingSetpoint(endpoint); await reporting.thermostatUnoccupiedHeatingSetpoint(endpoint); }, ]; return { fromZigbee, toZigbee, configure, isModernExtend: true }; }, SRZG2856Pro: () => { const fromZigbee = [ { cluster: "sunricherRemote", type: ["commandPress"], convert: (model, msg, publish, options, meta) => { let action = "unknown"; if (msg.data.messageType === 0x01) { const pressTypeLookup = { 1: "short_press", 2: "double_press", 3: "hold", 4: "hold_released", }; action = pressTypeLookup[msg.data.pressType] || "unknown"; const buttonMask = (msg.data.button2 << 8) | msg.data.button1; const actionButtons = []; for (let i = 0; i < 16; i++) { if ((buttonMask >> i) & 1) { const button = i + 1; actionButtons.push(`k${button}`); } } return { action, action_buttons: actionButtons }; } return { action }; }, }, ]; const exposes = [e.action(["short_press", "double_press", "hold", "hold_released"])]; const configure = [ async (device, coordinatorEndpoint, definition) => { const endpoint = device.getEndpoint(1); await endpoint.bind("sunricherRemote", coordinatorEndpoint); }, ]; return { fromZigbee, exposes, configure, isModernExtend: true, }; }, motorControl: () => { const fromZigbee = [ { cluster: "closuresWindowCovering", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; if (Object.hasOwn(msg.data, 0x0017)) { const value = msg.data[0x0017]; result.motor_direction_reversed = (value & 0x01) > 0; result.calibration_mode = (value & 0x02) > 0; } return result; }, }, ]; const toZigbee = [ { key: ["motor_direction_reversed", "calibration_mode"], convertSet: async (entity, key, value, meta) => { // First read current value to preserve other bits const current = await entity.read("closuresWindowCovering", [0x0017]); let currentValue = current?.[0x0017] || 0; if (key === "motor_direction_reversed") { if (value) { currentValue |= 0x01; } else { currentValue &= ~0x01; } } else if (key === "calibration_mode") { if (value) { currentValue |= 0x02; } else { currentValue &= ~0x02; } } await entity.write("closuresWindowCovering", { [0x0017]: { value: currentValue, type: 0x18 }, // BITMAP8 }); return { state: { [key]: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("closuresWindowCovering", [0x0017]); }, }, ]; const exposes = [ e .binary("motor_direction_reversed", ea.ALL, true, false) .withDescription("Reverse motor direction (if motor runs in the wrong direction after installation, use this and recalibration is required)") .withCategory("config"), e .binary("calibration_mode", ea.ALL, true, false) .withDescription("Trigger curtain calibration (motor will learn travel limits automatically)") .withCategory("config"), ]; return { fromZigbee, toZigbee, exposes, isModernExtend: true, }; }, }; exports.extend = extend; //# sourceMappingURL=sunricher.js.map