zigbee-herdsman-converters
Version:
Collection of device converters to be used with zigbee-herdsman
870 lines • 39.2 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.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