zigbee-herdsman-converters
Version:
Collection of device converters to be used with zigbee-herdsman
722 lines • 33 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.modernExtend = exports.ewelinkModernExtend = exports.ewelinkFromZigbee = exports.ewelinkToZigbee = void 0;
const exposes_1 = require("./exposes");
const modernExtend_1 = require("./modernExtend");
const utils = __importStar(require("./utils"));
const e = exposes_1.presets;
const ea = exposes_1.access;
// ====================== Type Or Interface ==============================
// ======================= Utils =========================================
const findKeyByValue = (object, value) => {
const entry = Object.entries(object).find(([key, val]) => val === value);
return entry ? entry[0] : undefined;
};
// ======================= Custom TZ =====================================
exports.ewelinkToZigbee = {
motor_direction: {
key: ["motor_direction"],
convertSet: async (entity, key, value, meta) => {
// biome-ignore lint/suspicious/noImplicitAnyLet: ignored using `--suppress`
let windowCoveringMode;
if (value === "forward") {
windowCoveringMode = 0x00;
}
else if (value === "reverse") {
windowCoveringMode = 0x01;
}
await entity.write("closuresWindowCovering", { windowCoveringMode }, utils.getOptions(meta.mapped, entity));
return { state: { motor_direction: value } };
},
},
};
// ======================= Custom FZ =====================================
exports.ewelinkFromZigbee = {
motor_direction: {
cluster: "closuresWindowCovering",
type: ["attributeReport", "readResponse"],
options: [exposes_1.options.invert_cover()],
convert: (model, msg, publish, options, meta) => {
const result = {};
if (typeof msg.data === "object" && Object.hasOwn(msg.data, "windowCoveringMode")) {
result.motor_direction = (msg.data.windowCoveringMode & (1 << 0)) > 0 === true ? "reverse" : "forward";
}
return result;
},
},
};
// ======================= Custom Extend =================================
function privateMotorClbByPosition(clusterName, writeCommand) {
const protocol = {
dooya: {
supportModel: ["CK-MG22-Z310EE07DOOYA-01(7015)", "MYDY25Z-1", "CK-MG22-JLDJ-01(7015)"],
mapping: {
open: 0x01,
close: 0x02,
other: 0x03,
clear: 0x10,
},
updateMotorClbCommand: {
privateCmd: 0x01,
subCmd: 0x09,
},
updatedMotorClbCommand: {
privateCmd: 0x01,
subCmd: 0x09,
},
deleteMotorClbCommand: {
privateCmd: 0x01,
subCmd: 0x0b,
},
deletedMotorClbCommand: {
privateCmd: 0x01,
subCmd: 0x0b,
},
},
raex: {
supportModel: ["MYRX25Z-1"],
mapping: {
open: 0x01,
close: 0x02,
clear: 0x01,
},
updateMotorClbCommand: {
privateCmd: 0x22,
dataLength: 0x02,
subCmd: 0x71,
},
deleteMotorClbCommand: {
privateCmd: 0x22,
dataLength: 0x02,
subCmd: 0x76,
},
},
ak: {
supportModel: ["AM25B-1-25-ES-E-Z", "ZM25-EAZ", "AM25C-1-25-ES-E-Z"],
mapping: {
open: 0x00,
close: 0x01,
clear: 0x02,
},
// AK Protocol setting the limit and clearing the limit use the same command.
updateMotorClbCommand: {
cmdType: 0x00,
privateCmd: 0x68,
dataType: 0x04,
dataLength: [0x00, 0x01],
},
updatedMotorClbCommand: {
cmdType: 0x01,
privateCmd: 0x68,
dataType: 0x04,
},
},
};
const exposes = [];
exposes.push(e.enum("motor_clb_position", ea.SET, ["open", "close", "other", "clear"]).withDescription("Motor Calibration By Position"));
exposes.push(e.text("motor_clb_position_result", ea.STATE).withDescription("Motor Calibration Result"));
const fromZigbee = [
{
cluster: clusterName,
type: ["raw"],
convert: (model, msg, publish, options, meta) => {
if (msg.type === "raw" && msg.data instanceof Buffer) {
// Raex Protocol,updated Report only through 'motor_info'.
if (protocol.dooya.supportModel.includes(model.model)) {
const bufferObj = msg.data.subarray(3, msg.data.length).toJSON(); // The first five bytes belong to the ZCL frame header, which are not of interest here; only the payload is extracted.
const payload = bufferObj.data;
const { privateCmd: updatedPrivateCmd, subCmd: updatedPrivateSubCmd } = protocol.dooya.updatedMotorClbCommand;
const { privateCmd: deletedPrivateCmd, subCmd: deletedPrivateSubCmd } = protocol.dooya.deletedMotorClbCommand;
if (payload[0] === updatedPrivateCmd && payload[1] === updatedPrivateSubCmd) {
const entities = Object.entries(protocol.dooya.mapping);
const motor_clb_position_result = {};
for (const entity of entities) {
if (entity[1] === payload[2]) {
motor_clb_position_result[entity[0]] = "calibrated";
break;
}
}
return {
motor_clb_position_result,
};
}
if (payload[0] === deletedPrivateCmd && payload[1] === deletedPrivateSubCmd) {
if (payload[2] === protocol.dooya.mapping.clear) {
const motor_clb_position_result = {
open: "uncalibrated",
close: "uncalibrated",
other: "uncalibrated",
};
return {
motor_clb_position_result,
};
}
}
}
else if (protocol.ak.supportModel.includes(model.model)) {
const bufferObj = msg.data.subarray(3, msg.data.length).toJSON(); // The first five bytes belong to the ZCL frame header, which are not of interest here; only the payload is extracted.
const payload = bufferObj.data;
const { cmdType, privateCmd, dataType } = protocol.ak.updatedMotorClbCommand;
if (payload[0] === cmdType && payload[2] === privateCmd && payload[3] === dataType) {
if (payload[6] === protocol.ak.mapping.clear) {
const motor_clb_position_result = {
open: "uncalibrated",
close: "uncalibrated",
other: "uncalibrated",
};
return {
motor_clb_position_result,
};
}
const entities = Object.entries(protocol.ak.mapping);
const motor_clb_position_result = {};
for (const entity of entities) {
if (entity[1] === payload[6]) {
motor_clb_position_result[entity[0]] = "calibrated";
break;
}
}
return {
motor_clb_position_result,
};
}
}
}
},
},
];
const toZigbee = [
{
key: ["motor_clb_position"],
convertSet: async (entity, key, value, meta) => {
utils.assertEndpoint(entity);
const device = entity.getDevice();
const modelID = device.modelID;
if (protocol.dooya.supportModel.includes(modelID)) {
const { deleteMotorClbCommand, updateMotorClbCommand, mapping } = protocol.dooya;
// Dooya Protocol
const payloadValue = [];
if (value === "clear") {
// Clear limit position
payloadValue[0] = deleteMotorClbCommand.privateCmd;
payloadValue[1] = deleteMotorClbCommand.subCmd;
payloadValue[2] = mapping[value];
}
else if (["open", "close", "other"].includes(value)) {
// Set limit position
payloadValue[0] = updateMotorClbCommand.privateCmd;
payloadValue[1] = updateMotorClbCommand.subCmd;
payloadValue[2] = mapping[value];
}
await entity.command(clusterName, writeCommand, { data: payloadValue });
}
else if (protocol.raex.supportModel.includes(modelID)) {
const { deleteMotorClbCommand, updateMotorClbCommand, mapping } = protocol.raex;
// Raex Protocol
const payloadValue = [];
if (value === "clear") {
// Clear limit position
payloadValue[0] = deleteMotorClbCommand.privateCmd;
payloadValue[1] = deleteMotorClbCommand.dataLength;
payloadValue[2] = deleteMotorClbCommand.subCmd;
payloadValue[3] = mapping[value];
}
else if (["open", "close", "other"].includes(value)) {
// Set limit position
payloadValue[0] = updateMotorClbCommand.privateCmd;
payloadValue[1] = updateMotorClbCommand.dataLength;
payloadValue[2] = updateMotorClbCommand.subCmd;
payloadValue[3] = mapping[value];
}
await entity.command(clusterName, writeCommand, { data: payloadValue });
}
else if (protocol.ak.supportModel.includes(modelID)) {
// AK Protocol
const { updateMotorClbCommand, mapping } = protocol.ak;
const payloadValue = [];
payloadValue[0] = updateMotorClbCommand.cmdType;
payloadValue[1] = 0x00;
payloadValue[2] = updateMotorClbCommand.privateCmd;
payloadValue[3] = updateMotorClbCommand.dataType;
payloadValue.push(...updateMotorClbCommand.dataLength);
payloadValue[6] = mapping[value];
await entity.command(clusterName, writeCommand, { data: payloadValue });
}
return { state: { [key]: value } };
},
},
];
return { exposes, toZigbee, fromZigbee, isModernExtend: true };
}
function privateMotorMode(clusterName, writeCommand) {
const mode = ["inching", "continuou"];
const protocol = {
dooya: {
supportModel: ["CK-MG22-Z310EE07DOOYA-01(7015)", "MYDY25Z-1", "CK-MG22-JLDJ-01(7015)"],
mapping: {
continuou: 0x20,
inching: 0x30,
},
updateMotorModeCommand: {
privateCmd: 0x01,
subCmd: 0x10,
},
updatedMotorModeCommand: {
privateCmd: 0x01,
subCmd: 0x10,
},
},
raex: {
supportModel: ["MYRX25Z-1"],
mapping: {
continuou: 0x01,
inching: 0x02,
},
updateMotorModeCommand: {
privateCmd: 0x11,
dataLength: 0x02,
subCmd: 0x54,
},
},
ak: {
supportModel: ["AM25B-1-25-ES-E-Z", "ZM25-EAZ", "AM25C-1-25-ES-E-Z"],
mapping: {
continuou: 0x00,
inching: 0x01,
},
updateMotorModeCommand: {
cmdType: 0x00,
privateCmd: 0x67,
dataType: 0x04,
dataLength: [0x00, 0x01],
},
updatedMotorModeCommand: {
cmdType: 0x01,
privateCmd: 0x67,
dataType: 0x04,
},
},
};
const expose = e.enum("motor_mode", ea.STATE_SET, mode).withDescription("Motor Mode");
const fromZigbee = [
{
cluster: clusterName,
type: ["raw"],
convert: (model, msg, publish, options, meta) => {
if (msg.type === "raw" && msg.data instanceof Buffer) {
if (protocol.dooya.supportModel.includes(model.model)) {
const bufferObj = msg.data.subarray(3, msg.data.length).toJSON();
const payload = bufferObj.data;
const { privateCmd, subCmd } = protocol.dooya.updatedMotorModeCommand;
if (payload[0] === privateCmd && payload[1] === subCmd) {
const entities = Object.entries(protocol.dooya.mapping);
// biome-ignore lint/suspicious/noImplicitAnyLet: ignored using `--suppress`
let motorMode;
for (const entity of entities) {
if (entity[1] === payload[2]) {
motorMode = entity[0];
break;
}
}
return {
motor_mode: motorMode,
};
}
}
else if (protocol.ak.supportModel.includes(model.model)) {
const bufferObj = msg.data.subarray(3, msg.data.length).toJSON();
const payload = bufferObj.data;
const { cmdType, privateCmd, dataType } = protocol.ak.updatedMotorModeCommand;
if (payload[0] === cmdType && payload[2] === privateCmd && payload[3] === dataType) {
const entities = Object.entries(protocol.ak.mapping);
// biome-ignore lint/suspicious/noImplicitAnyLet: ignored using `--suppress`
let motorMode;
for (const entity of entities) {
if (entity[1] === payload[6]) {
motorMode = entity[0];
break;
}
}
return {
motor_mode: motorMode,
};
}
}
}
},
},
];
const toZigbee = [
{
key: ["motor_mode"],
convertSet: async (entity, key, value, meta) => {
utils.assertEndpoint(entity);
const device = entity.getDevice();
const modelID = device.modelID;
if (protocol.dooya.supportModel.includes(modelID)) {
// Dooya Protocol
const payloadValue = [];
const { updateMotorModeCommand, mapping } = protocol.dooya;
payloadValue[0] = updateMotorModeCommand.privateCmd;
payloadValue[1] = updateMotorModeCommand.subCmd;
payloadValue[2] = mapping[value];
await entity.command(clusterName, writeCommand, { data: payloadValue });
}
else if (protocol.raex.supportModel.includes(modelID)) {
// Raex Protocol
const payloadValue = [];
const { updateMotorModeCommand, mapping } = protocol.raex;
payloadValue[0] = updateMotorModeCommand.privateCmd;
payloadValue[1] = updateMotorModeCommand.dataLength;
payloadValue[2] = updateMotorModeCommand.subCmd;
payloadValue[3] = mapping[value];
await entity.command(clusterName, writeCommand, { data: payloadValue });
}
else if (protocol.ak.supportModel.includes(modelID)) {
// AK Protocol
const payloadValue = [];
const { updateMotorModeCommand, mapping } = protocol.ak;
payloadValue[0] = updateMotorModeCommand.cmdType;
payloadValue[1] = 0x00;
payloadValue[2] = updateMotorModeCommand.privateCmd;
payloadValue[3] = updateMotorModeCommand.dataType;
payloadValue.push(...updateMotorModeCommand.dataLength);
payloadValue[6] = mapping[value];
await entity.command(clusterName, writeCommand, { data: payloadValue });
}
return { state: { [key]: value } };
},
},
];
return { exposes: [expose], toZigbee, fromZigbee, isModernExtend: true };
}
function privateReportMotorInfo(clusterName) {
const protocol = {
dooya: {
supportModel: ["CK-MG22-Z310EE07DOOYA-01(7015)", "MYDY25Z-1", "CK-MG22-JLDJ-01(7015)", "Grandekor Smart Curtain Grandekor"],
mapping: {
status: {
open: 0x01,
close: 0x02,
stop: 0x03,
},
itinerary: {
none: 0x00,
all: 0x01,
hasOpen: 0x02,
hasClose: 0x03,
hasThird: 0x04,
},
speed: {
none: 0x00,
T1: 0x01,
T2: 0x02,
T3: 0x03,
T4: 0x04,
T5: 0x05,
T6: 0x06,
T7: 0x07,
T8: 0x08,
T9: 0x09,
T10: 0x0a,
T11: 0x0b,
T12: 0x0c,
T13: 0x0d,
T14: 0x0e,
},
motorDirection: {
forward: 0x01,
reverse: 0x02,
},
motorMode: {
continuou: 0x01,
inching: 0x02,
},
},
updatedMotorInfoCommand: {
privateCmd: 0x03,
subCmd: 0x01,
},
},
raex: {
supportModel: ["MYRX25Z-1"],
mapping: {
status: {
open: "01",
close: "10",
stop: "00",
},
itinerary: {
none: 0x00,
hasOpen: 0x01,
hasClose: 0x02,
all: 0x03,
},
speed: {
none: 0x00,
T1: 0x01,
T2: 0x02,
T3: 0x03,
},
motorDirection: {
forward: "0",
reverse: "1",
},
motorMode: {
continuou: "0",
inching: "1",
},
},
updatedMotorInfoCommand: {
privateCmd: 0xa1,
subCmd: 0x0c,
},
},
};
const expose = e.text("motor_info", ea.STATE).withDescription("Motor Updated Info");
const fromZigbee = [
{
cluster: clusterName,
type: ["raw"],
convert: (model, msg, publish, options, meta) => {
if (msg.type === "raw" && msg.data instanceof Buffer) {
if (protocol.dooya.supportModel.includes(model.model)) {
// Dooya Protocol
const bufferObj = msg.data.subarray(3, msg.data.length).toJSON();
const payload = bufferObj.data;
const dooyaProtocol = protocol.dooya;
const { privateCmd, subCmd } = protocol.dooya.updatedMotorInfoCommand;
if (payload[0] === privateCmd && payload[1] === subCmd) {
const motor_status = findKeyByValue(dooyaProtocol.mapping.status, payload[2]);
const motor_percentage = payload[3];
const motor_angle = payload[4];
const motor_itinerary = findKeyByValue(dooyaProtocol.mapping.itinerary, payload[5]);
const motor_speed = payload[6];
const motor_direction = findKeyByValue(dooyaProtocol.mapping.motorDirection, payload[7]);
const motor_mode = findKeyByValue(dooyaProtocol.mapping.motorMode, payload[8]);
const battery = payload[9];
return {
[expose.property]: {
motor_status,
motor_percentage,
motor_angle,
motor_itinerary,
motor_speed,
motor_direction,
motor_mode,
battery,
},
};
}
}
else if (protocol.raex.supportModel.includes(model.model)) {
// Raex Protocol
const bufferObj = msg.data.subarray(3, msg.data.length).toJSON();
const payload = bufferObj.data;
const raexProtocol = protocol.raex;
const { privateCmd, subCmd } = raexProtocol.updatedMotorInfoCommand;
if (payload[0] === privateCmd && payload[1] === subCmd) {
const motor_status_binary = payload[2].toString(2).padStart(8, "0").slice(6, 8);
const motor_status = findKeyByValue(raexProtocol.mapping.status, motor_status_binary);
const motor_percentage = payload[3]; // 255 indicates that the motor cannot find the percentage.
const motor_angle = payload[4]; // 255 indicates that the motor cannot find the angle
const battery = payload[5];
const motor_itinerary = findKeyByValue(raexProtocol.mapping.itinerary, payload[11]);
const motor_speed = payload[9];
const motor_direction_binary = payload[8].toString(2).padStart(8, "0").slice(6, 7);
const motor_direction = findKeyByValue(raexProtocol.mapping.motorDirection, motor_direction_binary);
const motor_mode_binary = payload[8].toString(2).padStart(8, "0").slice(5, 6);
const motor_mode = findKeyByValue(raexProtocol.mapping.motorMode, motor_mode_binary);
return {
[expose.property]: {
motor_status,
motor_percentage,
motor_angle,
motor_itinerary,
motor_speed,
motor_direction,
motor_mode,
battery,
},
};
}
}
}
},
},
];
return { exposes: [expose], fromZigbee, isModernExtend: true };
}
function privateMotorSpeed(clusterName, writeCommand, minSpeed, maxSpeed) {
const protocol = {
dooya: {
supportModel: ["CK-MG22-Z310EE07DOOYA-01(7015)", "MYDY25Z-1", "CK-MG22-JLDJ-01(7015)", "Grandekor Smart Curtain Grandekor"],
updateMotorSpeedCommand: {
privateCmd: 0x01,
subCmd: 0xd1,
},
updatedMotorSpeedCommand: {
privateCmd: 0x01,
subCmd: 0xd1,
},
updatedMaxMotorSpeedCommand: {
privateCmd: 0x02,
subCmd: 0x0e,
},
},
raex: {
supportModel: ["MYRX25Z-1"],
updateMotorSpeedCommand: {
privateCmd: 0x11,
dataLength: 0x02,
subCmd: 0x53,
},
},
};
const exposes = [];
exposes.push(e.numeric("motor_speed", ea.STATE_SET).withDescription("Set the motor speed").withValueMin(minSpeed).withValueMax(maxSpeed));
exposes.push(e.numeric("supported_max_motor_speed", ea.STATE).withDescription("Supported max motor speed"));
const fromZigbee = [
{
cluster: clusterName,
type: ["raw"],
convert: (model, msg, publish, options, meta) => {
if (msg.type === "raw" && msg.data instanceof Buffer) {
if (protocol.dooya.supportModel.includes(model.model)) {
const bufferObj = msg.data.subarray(3, msg.data.length).toJSON();
const payload = bufferObj.data;
const { updatedMotorSpeedCommand, updatedMaxMotorSpeedCommand } = protocol.dooya;
if (payload[0] === updatedMotorSpeedCommand.privateCmd && payload[1] === updatedMotorSpeedCommand.subCmd) {
return {
motor_speed: payload[2], // If the gear position is 255, it means the device does not support speed adjustment.
};
}
if (payload[0] === updatedMaxMotorSpeedCommand.privateCmd && payload[1] === updatedMaxMotorSpeedCommand.subCmd) {
const supportedMax = payload[2];
if (supportedMax === 0 || supportedMax === undefined) {
return {
supported_max_motor_speed: 0,
};
}
return { supported_max_motor_speed: supportedMax };
}
}
}
},
},
];
const toZigbee = [
{
key: ["motor_speed"],
convertSet: async (entity, key, value, meta) => {
utils.assertEndpoint(entity);
const device = entity.getDevice();
const modelID = device.modelID;
if (protocol.dooya.supportModel.includes(modelID)) {
const payloadValue = [];
const { updateMotorSpeedCommand } = protocol.dooya;
payloadValue[0] = updateMotorSpeedCommand.privateCmd;
payloadValue[1] = updateMotorSpeedCommand.subCmd;
payloadValue[2] = value;
await entity.command(clusterName, writeCommand, { data: payloadValue });
}
else if (protocol.raex.supportModel.includes(modelID)) {
const payloadValue = [];
const { updateMotorSpeedCommand } = protocol.raex;
payloadValue[0] = updateMotorSpeedCommand.privateCmd;
payloadValue[1] = updateMotorSpeedCommand.dataLength;
payloadValue[2] = updateMotorSpeedCommand.subCmd;
payloadValue[3] = value;
await entity.command(clusterName, writeCommand, { data: payloadValue });
}
return { state: { [key]: value } };
},
},
];
return { exposes, toZigbee, fromZigbee, isModernExtend: true };
}
exports.ewelinkModernExtend = {
ewelinkAction: () => {
const exposes = [exposes_1.presets.action(["single", "double", "long"])];
const fromZigbee = [
{
cluster: "genOnOff",
type: ["commandOn", "commandOff", "commandToggle"],
convert: (model, msg, publish, options, meta) => {
const lookup = { commandToggle: "single", commandOn: "double", commandOff: "long" };
return { action: lookup[msg.type] };
},
},
];
const configure = [(0, modernExtend_1.setupConfigureForBinding)("genOnOff", "output")];
return { exposes, fromZigbee, configure, isModernExtend: true };
},
ewelinkBattery: () => {
// 3600/7200 prevents disconnect
// https://github.com/Koenkk/zigbee2mqtt/issues/13600#issuecomment-1283827935
return (0, modernExtend_1.battery)({
voltage: true,
voltageReporting: true,
percentageReportingConfig: { min: 3600, max: 7200, change: 2 },
voltageReportingConfig: { min: 3600, max: 7200, change: 100 },
});
},
ewelinkMotorReverse: () => {
const exposes = [e.enum("motor_direction", ea.STATE_SET, ["forward", "reverse"]).withDescription("Set the motor direction")];
const toZigbee = [exports.ewelinkToZigbee.motor_direction];
const fromZigbee = [exports.ewelinkFromZigbee.motor_direction];
return {
exposes,
fromZigbee,
toZigbee,
isModernExtend: true,
};
},
ewelinkMotorClbByPosition: (clusterName, writeCommand) => {
return privateMotorClbByPosition(clusterName, writeCommand);
},
ewelinkMotorMode: (clusterName, writeCommand) => {
return privateMotorMode(clusterName, writeCommand);
},
ewelinkReportMotorInfo: (clusterName) => {
return privateReportMotorInfo(clusterName);
},
ewelinkMotorSpeed: (clusterName, writeCommand, min, max) => {
return privateMotorSpeed(clusterName, writeCommand, min, max);
},
};
exports.modernExtend = exports.ewelinkModernExtend;
//# sourceMappingURL=ewelink.js.map