zigbee-herdsman-converters
Version:
Collection of device converters to be used with zigbee-herdsman
936 lines • 154 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;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.definitions = void 0;
const node_assert_1 = __importDefault(require("node:assert"));
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 heiman_1 = require("../lib/heiman");
const m = __importStar(require("../lib/modernExtend"));
const reporting = __importStar(require("../lib/reporting"));
const globalStore = __importStar(require("../lib/store"));
const tuya = __importStar(require("../lib/tuya"));
const utils = __importStar(require("../lib/utils"));
const e = exposes.presets;
const ea = exposes.access;
const defaultResponseOptions = { disableDefaultResponse: false };
const iasWarningMode = { stop: 0, burglar: 1, fire: 2, emergency: 3, police_panic: 4, fire_panic: 5, emergency_panic: 6 };
const heimanExtend = {
heimanClusterRadar: () => m.deviceAddCustomCluster("heimanClusterRadar", {
name: "heimanClusterRadar",
ID: 0xfc8b,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.HEIMAN_TECHNOLOGY_CO_LTD,
attributes: {
enableIndicator: { name: "enableIndicator", ID: 0xf001, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true }, // 0: off, 1: enable
sensitivity: { name: "sensitivity", ID: 0xf002, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true },
enableSubRegionIsolation: { name: "enableSubRegionIsolation", ID: 0xf006, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true },
installationMethod: { name: "installationMethod", ID: 0xf007, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true },
cellMountedTable: { name: "cellMountedTable", ID: 0xf008, type: zigbee_herdsman_1.Zcl.DataType.OCTET_STR, write: true }, // string
wallMountedTable: { name: "wallMountedTable", ID: 0xf009, type: zigbee_herdsman_1.Zcl.DataType.OCTET_STR, write: true }, // string
subRegionIsolationTable: { name: "subRegionIsolationTable", ID: 0xf00a, type: zigbee_herdsman_1.Zcl.DataType.OCTET_STR, write: true }, // string
},
commands: {},
commandsResponse: {},
}),
heimanClusterSpecial: () => m.deviceAddCustomCluster("heimanClusterSpecial", {
name: "heimanClusterSpecial",
ID: 0xfc90,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.HEIMAN_TECHNOLOGY_CO_LTD,
attributes: {
// Sensor 0x0000~0x0FFF
sensorPreheatingState: { name: "sensorPreheatingState", ID: 0x0000, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true },
sensorSelfCheckState: { name: "sensorSelfCheckState", ID: 0x0001, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true },
sensorFaultState: { name: "sensorFaultState", ID: 0x0002, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true },
sensorPollutionLevel: { name: "sensorPollutionLevel", ID: 0x0003, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true },
sensorSensitivityLevel: { name: "sensorSensitivityLevel", ID: 0x0004, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true },
sensorPrealarmThreshold: { name: "sensorPrealarmThreshold", ID: 0x0005, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true },
sensorLifeState: { name: "sensorLifeState", ID: 0x0006, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true },
sensorLifeTime: { name: "sensorLifeTime", ID: 0x0007, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true },
deviceMuteControl: { name: "deviceMuteControl", ID: 0x0008, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true },
deviceMuteState: { name: "deviceMuteState", ID: 0x0009, type: zigbee_herdsman_1.Zcl.DataType.BITMAP16, write: true },
deviceCascadeControlEnable: { name: "deviceCascadeControlEnable", ID: 0x000a, type: zigbee_herdsman_1.Zcl.DataType.BITMAP8, write: true },
deviceSoundToneType: { name: "deviceSoundToneType", ID: 0x000b, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true },
deviceSoundControl: { name: "deviceSoundControl", ID: 0x000c, type: zigbee_herdsman_1.Zcl.DataType.ARRAY, write: true },
deviceBlinkControl: { name: "deviceBlinkControl", ID: 0x000d, type: zigbee_herdsman_1.Zcl.DataType.ARRAY, write: true },
smokeAdValue: { name: "smokeAdValue", ID: 0x000e, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true },
smokeAlarmType: { name: "smokeAlarmType", ID: 0x000f, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true },
smokeWaterMistState: { name: "smokeWaterMistState", ID: 0x0010, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true },
smokeSensorData: { name: "smokeSensorData", ID: 0x0011, type: zigbee_herdsman_1.Zcl.DataType.ARRAY, write: true },
deviceCascadeState: { name: "deviceCascadeState", ID: 0x0012, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true },
sensorPrealarmState: { name: "sensorPrealarmState", ID: 0x0013, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true },
smokeConcentrationLevel: { name: "smokeConcentrationLevel", ID: 0x0016, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
smokeChamberContaminationLevel: { name: "smokeChamberContaminationLevel", ID: 0x0017, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
smokeConcentationUnit: { name: "smokeConcentationUnit", ID: 0x0018, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
emergencyStateControl: { name: "emergencyStateControl", ID: 0x001c, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true },
sensorSamplingPeriod: { name: "sensorSamplingPeriod", ID: 0x001d, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true },
occupanySensorWorkMode: { name: "occupanySensorWorkMode", ID: 0x001e, type: zigbee_herdsman_1.Zcl.DataType.BITMAP8, write: true },
occupiedToOccupiedInterval: { name: "occupiedToOccupiedInterval", ID: 0x001f, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true },
occupiedToOccupiedDuration: { name: "occupiedToOccupiedDuration", ID: 0x0020, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true },
radarConfigParamOfSection1: { name: "radarConfigParamOfSection1", ID: 0x0021, type: zigbee_herdsman_1.Zcl.DataType.OCTET_STR, write: true },
radarConfigParamOfSection2: { name: "radarConfigParamOfSection2", ID: 0x0022, type: zigbee_herdsman_1.Zcl.DataType.OCTET_STR, write: true },
radarConfigParamOfSection3: { name: "radarConfigParamOfSection3", ID: 0x0023, type: zigbee_herdsman_1.Zcl.DataType.OCTET_STR, write: true },
radarConfigParamOfSection4: { name: "radarConfigParamOfSection4", ID: 0x0024, type: zigbee_herdsman_1.Zcl.DataType.OCTET_STR, write: true },
radarMicroThresholdParam: { name: "radarMicroThresholdParam", ID: 0x0025, type: zigbee_herdsman_1.Zcl.DataType.OCTET_STR, write: true },
radarPersenceThresholdParam: { name: "radarPersenceThresholdParam", ID: 0x0026, type: zigbee_herdsman_1.Zcl.DataType.OCTET_STR, write: true },
radarLearningControl: { name: "radarLearningControl", ID: 0x0027, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true },
radarLearningState: { name: "radarLearningState", ID: 0x0028, type: zigbee_herdsman_1.Zcl.DataType.ENUM8 },
radarDetectionTargetRange: { name: "radarDetectionTargetRange", ID: 0x0029, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
radarDetectionMinRange: { name: "radarDetectionMinRange", ID: 0x002b, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true },
radarDetectionMaxRange: { name: "radarDetectionMaxRange", ID: 0x002c, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true },
// Light/Switch 0x1000~0x1FFF
indicatorLightControl: { name: "indicatorLightControl", ID: 0x1000, type: zigbee_herdsman_1.Zcl.DataType.BITMAP8, write: true },
indicatorLightNotDisturbStartTime: { name: "indicatorLightNotDisturbStartTime", ID: 0x1001, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true },
indicatorLightNotDisturbEndTime: { name: "indicatorLightNotDisturbEndTime", ID: 0x1002, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true },
indicatorLightNotDisturbEnable: { name: "indicatorLightNotDisturbEnable", ID: 0x1003, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true },
indicatorLightLevelControlOf1: { name: "indicatorLightLevelControlOf1", ID: 0x1004, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true },
indicatorLightLevelControlOf2: { name: "indicatorLightLevelControlOf2", ID: 0x1005, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true },
indicatorLightLevelControlOf3: { name: "indicatorLightLevelControlOf3", ID: 0x1006, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true },
interconnectable: { name: "interconnectable", ID: 0x1007, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
indicatorLightOnOff: { name: "indicatorLightOnOff", ID: 0x1008, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true },
remoteSelfTest: { name: "remoteSelfTest", ID: 0x1009, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
temperatureOffset: { name: "temperatureOffset", ID: 0x100d, type: zigbee_herdsman_1.Zcl.DataType.INT16, write: true },
switchType: { name: "switchType", ID: 0x1010, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true },
rebootedCount: { name: "rebootedCount", ID: 0x0019, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
rejoinedCount: { name: "rejoinedCount", ID: 0x001a, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
reportedPackages: { name: "reportedPackages", ID: 0x001b, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
illuminanceThreshold: { name: "illuminanceThreshold", ID: 0x002a, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true },
mcuSoftwareVersion: { name: "mcuSoftwareVersion", ID: 0x0030, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
// wifi classes
wifiSsid: { name: "wifiSsid", ID: 0x2000, type: zigbee_herdsman_1.Zcl.DataType.CHAR_STR, write: true },
wifiPassword: { name: "wifiPassword", ID: 0x2001, type: zigbee_herdsman_1.Zcl.DataType.CHAR_STR, write: true },
wifiSsidCandidate: { name: "wifiSsidCandidate", ID: 0x2002, type: zigbee_herdsman_1.Zcl.DataType.CHAR_STR, write: true },
wifiPasswordCandidate: { name: "wifiPasswordCandidate", ID: 0x2003, type: zigbee_herdsman_1.Zcl.DataType.CHAR_STR, write: true },
pictureQuantity: { name: "pictureQuantity", ID: 0x2004, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true },
pictureQuality: { name: "pictureQuality", ID: 0x2005, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true },
serverAddress: { name: "serverAddress", ID: 0x2006, type: zigbee_herdsman_1.Zcl.DataType.CHAR_STR },
serverAddressCandidae: { name: "serverAddressCandidae", ID: 0x2007, type: zigbee_herdsman_1.Zcl.DataType.CHAR_STR },
serverPort: { name: "serverPort", ID: 0x2008, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
authToken: { name: "authToken", ID: 0x2009, type: zigbee_herdsman_1.Zcl.DataType.CHAR_STR },
zoneCaptureMode: { name: "zoneCaptureMode", ID: 0x200a, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
sensorArmed: { name: "sensorArmed", ID: 0x200b, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true },
wifiStatus: { name: "wifiStatus", ID: 0x200c, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
wifiCandidateStatus: { name: "wifiCandidateStatus", ID: 0x200d, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
wifiRssi: { name: "wifiRssi", ID: 0x200e, type: zigbee_herdsman_1.Zcl.DataType.INT8, write: true },
serverStatus: { name: "serverStatus", ID: 0x200f, type: zigbee_herdsman_1.Zcl.DataType.INT8 },
serverCandidateStatus: { name: "serverCandidateStatus", ID: 0x2010, type: zigbee_herdsman_1.Zcl.DataType.INT8 },
cameraReady: { name: "cameraReady", ID: 0x2013, type: zigbee_herdsman_1.Zcl.DataType.INT8 },
flashLightBrightness: { name: "flashLightBrightness", ID: 0x2012, type: zigbee_herdsman_1.Zcl.DataType.INT8 },
cameraMode: { name: "cameraMode", ID: 0x2014, type: zigbee_herdsman_1.Zcl.DataType.INT8 },
},
commands: {
cameraActiveTrigger: {
ID: 0x01,
name: "cameraActiveTrigger",
parameters: [],
},
cameraTestTrigger: {
ID: 0x02,
name: "cameraTestTrigger",
parameters: [],
},
},
commandsResponse: {},
}),
heimanClusterIasZone: () => m.deviceAddCustomCluster("ssIasZone", {
name: "ssIasZone",
ID: zigbee_herdsman_1.Zcl.Clusters.ssIasZone.ID,
attributes: {},
commands: {
initiateTestMode: {
name: "initiateTestMode",
ID: 0x02,
parameters: [
{ name: "testModeDuration", type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
{ name: "sensitivityLevel", type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
],
},
},
commandsResponse: {},
}),
// ModernExtend define
heimanClusterRadarActiveIndicatorExtend: () => {
const clusterName = "heimanClusterRadar";
const exposes = utils.exposeEndpoints(e.binary("enable_indicator", ea.ALL, true, false).withDescription("active green indicator"));
const fromZigbee = [
{
cluster: clusterName,
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
if (msg.data.enableIndicator === undefined) {
return;
}
const state = !!msg.data["enableIndicator"];
return { enable_indicator: state };
},
},
];
const toZigbee = [
{
key: ["enable_indicator"],
convertGet: async (entity, key, meta) => {
await entity.read(clusterName, ["enableIndicator"], defaultResponseOptions);
},
convertSet: async (entity, key, value, meta) => {
// const state = (value as Record<string, unknown>) || {};
const state = value ? 1 : 0;
await entity.write(clusterName, { enableIndicator: state }, defaultResponseOptions);
},
},
];
return { exposes: exposes, fromZigbee, toZigbee, isModernExtend: true };
},
heimanClusterRadarSubRegionEnableExtend: () => {
const clusterName = "heimanClusterRadar";
const exposes = utils.exposeEndpoints(e.binary("enable_sub_region_isolation", ea.ALL, true, false).withDescription("active green indicator"));
const fromZigbee = [
{
cluster: clusterName,
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
if (msg.data.enableSubRegionIsolation === undefined) {
return;
}
const state = !!msg.data["enableSubRegionIsolation"];
return { enable_sub_region_isolation: state };
},
},
];
const toZigbee = [
{
key: ["enable_sub_region_isolation"],
convertGet: async (entity, key, meta) => {
await entity.read(clusterName, ["enableSubRegionIsolation"], defaultResponseOptions);
},
convertSet: async (entity, key, value, meta) => {
const state = value ? 1 : 0;
await entity.write(clusterName, { enableSubRegionIsolation: state }, defaultResponseOptions);
},
},
];
return {
exposes: exposes,
fromZigbee,
toZigbee,
isModernExtend: true,
};
},
heimanClusterRadarSensitivityExtend: () => {
const clusterName = "heimanClusterRadar";
const exposes = utils.exposeEndpoints(e
.numeric("sensitivity", ea.ALL)
.withUnit("%")
.withValueMin(0)
.withValueMax(100)
.withDescription("Sensitivity of the radar sensor in range of 0 ~ 100%"));
const fromZigbee = [
{
cluster: clusterName,
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
let attrData = null;
if (msg.data.sensitivity === undefined) {
return;
}
attrData = msg.data["sensitivity"];
return { sensitivity: attrData };
},
},
];
const toZigbee = [
{
key: ["sensitivity"],
convertGet: async (entity, key, meta) => {
await entity.read(clusterName, ["sensitivity"], defaultResponseOptions);
},
convertSet: async (entity, key, value, meta) => {
const state = Number(value);
await entity.write(clusterName, { sensitivity: state }, defaultResponseOptions);
},
},
];
return {
exposes: exposes,
fromZigbee,
toZigbee,
isModernExtend: true,
};
},
heimanClusterRadarCellMountedTableExtend: () => {
const clusterName = "heimanClusterRadar";
const exposes = utils.exposeEndpoints(e
.text("cell_mounted_table", ea.ALL)
.withDescription("Ceiling installation area coordinate table. Format: 'X1,X2,Y1,Y2,height'. Value range: -2000≤X1≤0, 0≤X2≤2000 -2500≤Y1≤0, 0≤Y2≤2500 2300≤height≤3000 Unit:mm"));
const fromZigbee = [
{
cluster: clusterName,
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
const value = msg.data["cellMountedTable"];
if (Buffer.isBuffer(value) && value.length >= 5) {
if (value.length !== 10) {
throw new Error(`Invalid cell_mounted_table data length: expected 10 bytes, got ${value.length}.`);
}
const coordinates = [
value.readInt16LE(0), // x1
value.readInt16LE(2), // y1
value.readInt16LE(4), // x2
value.readInt16LE(6), // y2
value.readInt16LE(8), // height
];
result.cell_mounted_table = coordinates.join(",");
}
return result;
},
},
];
const toZigbee = [
{
key: ["cell_mounted_table"],
convertGet: async (entity, key, meta) => {
await entity.read(clusterName, ["cellMountedTable"], defaultResponseOptions);
},
convertSet: async (entity, key, value, meta) => {
if (key === "cell_mounted_table" && value !== "") {
const coordinates = value.split(",").map((v) => Number.parseInt(v, 10));
if (coordinates.length !== 5) {
throw new Error("cell_mounted_table must be a string with 5 comma-separated values (e.g., '-2000,2000,-2500,2500,2300')");
}
// Range check
if (coordinates[0] < -2000 ||
coordinates[0] > 0 || // X1
coordinates[1] < 0 ||
coordinates[1] > 2000 || // X2
coordinates[2] < -2500 ||
coordinates[2] > 0 || // Y1
coordinates[3] < 0 ||
coordinates[3] > 2500 || // Y2
coordinates[4] < 2300 ||
coordinates[4] > 3000 // height
) {
throw new Error("Values out of range for Cell Mounted Table.");
}
const buffer = Buffer.alloc(10); // 10 bytes + 1 byte
buffer.writeInt16LE(coordinates[0], 0); // x1
buffer.writeInt16LE(coordinates[1], 2); // x2
buffer.writeInt16LE(coordinates[2], 4); // y1
buffer.writeInt16LE(coordinates[3], 6); // y2
buffer.writeInt16LE(coordinates[4], 8); // height
await entity.write(clusterName, { cellMountedTable: buffer }, defaultResponseOptions);
}
},
},
];
return {
exposes: exposes,
fromZigbee,
toZigbee,
isModernExtend: true,
};
},
heimanClusterRadarWallMountedTableExtend: () => {
const clusterName = "heimanClusterRadar";
const exposes = utils.exposeEndpoints(e
.text("wall_mounted_table", ea.ALL)
.withDescription("Wall-mounted installation area coordinate table. Format: 'X1,X2,Y2,height' Value range: -2000≤X1≤0, 0≤X2≤2000 200≤Y2≤4000 1500≤height≤1600 Unit:mm."));
const fromZigbee = [
{
cluster: clusterName,
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
const value = msg.data["wallMountedTable"];
if (Buffer.isBuffer(value) && value.length >= 5) {
if (value.length !== 8) {
throw new Error(`Invalid wall_mounted_table data length: expected 8 bytes, got ${value.length}.`);
}
const coordinates = [
value.readInt16LE(0), // x1
value.readInt16LE(2), // y1
value.readInt16LE(4), // x2
value.readInt16LE(6), // height
];
result.wall_mounted_table = coordinates.join(",");
}
return result;
},
},
];
const toZigbee = [
{
key: ["wall_mounted_table"],
convertGet: async (entity, key, meta) => {
await entity.read(clusterName, ["wallMountedTable"], defaultResponseOptions);
},
convertSet: async (entity, key, value, meta) => {
if (key === "wall_mounted_table" && value !== "") {
const coordinates = value.split(",").map((v) => Number.parseInt(v, 10));
if (coordinates.length !== 4) {
throw new Error("wall_mounted_table must be a string with 4 comma-separated values (e.g., '-2000,2000,4000,1600')");
}
if (coordinates[0] < -2000 ||
coordinates[0] > 0 || // X1
coordinates[1] < 0 ||
coordinates[1] > 2000 || // X2
coordinates[2] < 200 ||
coordinates[2] > 4000 || // Y2
coordinates[3] < 1500 ||
coordinates[3] > 1600 // height
) {
throw new Error("Values out of range for Wall Mounted Table.");
}
const buffer = Buffer.alloc(8); // 8 bytes + 1 byte
buffer.writeInt16LE(coordinates[0], 0); // x1
buffer.writeInt16LE(coordinates[1], 2); // x2
buffer.writeInt16LE(coordinates[2], 4); // y2
buffer.writeInt16LE(coordinates[3], 6); // height
await entity.write(clusterName, { wallMountedTable: buffer }, defaultResponseOptions);
}
},
},
];
return {
exposes: exposes,
fromZigbee,
toZigbee,
isModernExtend: true,
};
},
heimanClusterRadarSubRegionIsolationTableExtend: () => {
const clusterName = "heimanClusterRadar";
const exposes = utils.exposeEndpoints(e
.text("sub_region_isolation_table", ea.ALL)
.withDescription("Undetectable area coordinate table. Format: 'x1,x2,y1,y2,z1,z2'. Ranges: X1≤x1≤x2≤X2 When wall-mounted: 200≤y1≤y2≤Y2 0≤z1≤z2≤2300 Ceiling installation: Y1≤y1≤y2≤Y2 0≤z1≤z2≤height Unit:mm"));
const fromZigbee = [
{
cluster: clusterName,
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
const value = msg.data["subRegionIsolationTable"];
if (Buffer.isBuffer(value) && value.length >= 5) {
if (value.length !== 12) {
throw new Error(`Invalid sub_region_isolation_table data length: expected 12 bytes, got ${value.length}.`);
}
const coordinates = [
value.readInt16LE(0), // x1
value.readInt16LE(2), // y1
value.readInt16LE(4), // x2
value.readInt16LE(6), // y2
value.readInt16LE(8), // z1
value.readInt16LE(10), // z2
];
result.sub_region_isolation_table = coordinates.join(",");
}
return result;
},
},
];
const toZigbee = [
{
key: ["sub_region_isolation_table"],
convertGet: async (entity, key, meta) => {
await entity.read(clusterName, ["subRegionIsolationTable"], defaultResponseOptions);
},
convertSet: async (entity, key, value, meta) => {
if (key === "sub_region_isolation_table" && value !== "") {
const coordinates = value.split(",").map((v) => Number.parseInt(v, 10));
if (coordinates.length !== 6) {
throw new Error("sub_region_isolation_table must be a string with 6 comma-separated values (e.g., '-2000,2000,-2500,2500,2300,3000')");
}
if (coordinates[0] < -2000 ||
coordinates[0] > 2000 || // X1
coordinates[1] < -2000 ||
coordinates[1] > 2000 // X2
) {
throw new Error("Values out of range for Sub-Region Isolation Table.");
}
const buffer = Buffer.alloc(12); // 12 bytes + 1 byte
buffer.writeInt16LE(coordinates[0], 0); // x1
buffer.writeInt16LE(coordinates[1], 2); // x2
buffer.writeInt16LE(coordinates[2], 4); // y1
buffer.writeInt16LE(coordinates[3], 6); // y2
buffer.writeInt16LE(coordinates[4], 8); // z1
buffer.writeInt16LE(coordinates[5], 10); // z2
await entity.write(clusterName, { subRegionIsolationTable: buffer }, defaultResponseOptions);
}
},
},
];
return {
exposes: exposes,
fromZigbee,
toZigbee,
isModernExtend: true,
};
},
heimanClusterRadarSenseExtend: () => {
const clusterName = "msOccupancySensing";
const exposes = [
e.binary("occupancy", ea.STATE, true, false).withDescription("Indicates if someone is present"),
e.enum("sensor_status", ea.STATE, ["none", "activity", "unknown"]).withDescription("Sensor activity status"),
e.enum("fall_status", ea.STATE, ["normal", "fall_warning", "fall_alarm", "unknown"]).withDescription("Fall detection status"),
];
const fromZigbee = [
{
cluster: clusterName,
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
if (Object.hasOwn(msg.data, "occupancy")) {
const occupancy = msg.data.occupancy;
const bit0 = occupancy & 0x01; // Bit 0: Occupancy (0: no one, 1: someone)
const bit1to3 = (occupancy >> 1) & 0x07; // Bits 1-3: Sensor status
const bit4to5 = (occupancy >> 4) & 0x03; // Bits 4-5: Fall status
// Interprete bitmap
result.occupancy = bit0 === 1;
result.sensor_status = ["none", "activity"][bit1to3] || "unknown";
result.fall_status = ["normal", "fall_warning", "fall_alarm"][bit4to5] || "unknown";
}
return result;
},
},
];
return {
exposes: exposes,
fromZigbee,
isModernExtend: true,
};
},
heimanClusterLegacyIlluminanceExtend: () => {
const clusterName = "msIlluminanceMeasurement";
const exposes = utils.exposeEndpoints(e.numeric("ambient_light", ea.STATE_GET).withUnit("Lx").withDescription("ambient illuminance in lux"));
const fromZigbee = [
{
cluster: clusterName,
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
let attrData = null;
if (msg.data.measuredValue === undefined) {
return;
}
attrData = msg.data["measuredValue"];
return { ambient_light: attrData };
},
},
];
const toZigbee = [
{
key: ["ambient_light"],
convertGet: async (entity, key, meta) => {
await entity.read(clusterName, ["measuredValue"], defaultResponseOptions);
},
},
];
return {
exposes: exposes,
fromZigbee,
toZigbee,
isModernExtend: true,
};
},
heimanClusterSensorFaultState: () => {
const clusterName = "heimanClusterSpecial";
const faultStateBitMap = {
0: "fault", // bit0
1: "open_circuit_fault", // bit1
2: "short_circuit_fault", // bit2
3: "pollution_fault", // bit3
};
const exposes = utils.exposeEndpoints(e.text("fault_state", ea.STATE_GET).withDescription("Device fault status (normal or fault types)."));
const fromZigbee = [
{
cluster: clusterName,
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
let attrData = null;
let attrValue = 0;
if (msg.data.sensorFaultState === undefined) {
return;
}
attrData = msg.data["sensorFaultState"];
attrValue = Number(attrData);
const activeFaults = [];
for (const [bit, faultDesc] of Object.entries(faultStateBitMap)) {
const bitNum = Number(bit);
const isFault = (attrValue & (1 << bitNum)) !== 0;
if (isFault) {
activeFaults.push(faultDesc);
}
}
const faultResult = activeFaults.length > 0 ? activeFaults.join(" | ") : "normal";
return { fault_state: faultResult };
},
},
];
const toZigbee = [
{
key: ["fault_state"],
convertGet: async (entity, key, meta) => {
await entity.read(clusterName, ["sensorFaultState"], defaultResponseOptions);
},
},
];
return {
exposes: exposes,
fromZigbee,
toZigbee,
isModernExtend: true,
};
},
heimanClusterDeviceMuteState: () => {
const clusterName = "heimanClusterSpecial";
const muteStateBitMap = {
0: "muted", // bit0
1: "alarm_muted", // bit1
2: "fault_muted", // bit2
3: "low_battery_muted", // bit3
};
const exposes = utils.exposeEndpoints(e.text("muted", ea.STATE_GET).withDescription("Device mute status (normal or mute types)."));
const fromZigbee = [
{
cluster: clusterName,
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
let attrData = null;
let attrValue = 0;
if (msg.data.deviceMuteState === undefined) {
return;
}
attrData = msg.data["deviceMuteState"];
attrValue = Number(attrData);
const activeMutes = [];
for (const [bit, muteDesc] of Object.entries(muteStateBitMap)) {
const bitNum = Number(bit);
const isFault = (attrValue & (1 << bitNum)) !== 0;
if (isFault) {
activeMutes.push(muteDesc);
}
}
const muteResult = activeMutes.length > 0 ? activeMutes.join(" | ") : "normal";
return { muted: muteResult };
},
},
];
const toZigbee = [
{
key: ["muted"],
convertGet: async (entity, key, meta) => {
await entity.read(clusterName, ["deviceMuteState"], defaultResponseOptions);
},
},
];
return {
exposes: exposes,
fromZigbee,
toZigbee,
isModernExtend: true,
};
},
heimanClusterIndicatorLight: (componentName = "heartbeat_indicator") => {
const clusterName = "heimanClusterSpecial";
const exposes = utils.exposeEndpoints(e.binary(componentName, ea.ALL, true, false).withDescription("Enable/disable the indicator on product"));
const fromZigbee = [
{
cluster: clusterName,
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
if (msg.data.indicatorLightLevelControlOf1 === undefined) {
return;
}
const state = !!msg.data["indicatorLightLevelControlOf1"];
return { [componentName]: state };
},
},
];
const toZigbee = [
{
key: [componentName],
convertGet: async (entity, key, meta) => {
await entity.read(clusterName, ["indicatorLightLevelControlOf1"], defaultResponseOptions);
},
convertSet: async (entity, key, value, meta) => {
// const state = (value as Record<string, unknown>) || {};
const state = value ? 1 : 0;
await entity.write(clusterName, { indicatorLightLevelControlOf1: state }, defaultResponseOptions);
},
},
];
return {
exposes: exposes,
fromZigbee,
toZigbee,
isModernExtend: true,
};
},
heimanClusterSensorInterconnectable: () => {
const clusterName = "heimanClusterSpecial";
const exposes = utils.exposeEndpoints(e.binary("interconnectable", ea.STATE_GET, true, false).withDescription("used for interconnection automation."));
const fromZigbee = [
{
cluster: clusterName,
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
if (msg.data.interconnectable === undefined) {
return;
}
const state = !!msg.data.interconnectable;
return { interconnectable: state };
},
},
];
const toZigbee = [
{
key: ["interconnectable"],
convertGet: async (entity, key, meta) => {
await entity.read(clusterName, ["interconnectable"], defaultResponseOptions);
},
},
];
return {
exposes: exposes,
fromZigbee,
toZigbee,
isModernExtend: true,
};
},
iasZoneInitiateTestMode: () => {
const exposes = utils.exposeEndpoints(e.enum("trigger_selftest", ea.SET, ["test"]).withDescription("Trigger smoke alarm self-check test."));
const toZigbee = [
{
key: ["trigger_selftest"],
convertSet: async (entity, key, value, meta) => {
const testMode = {
testModeDuration: 0x01,
currentZoneSensitivityLevel: 0x01,
};
await entity.command("ssIasZone", "initTestMode", testMode);
return { state: { [key]: value } };
},
},
];
return {
exposes: exposes,
fromZigbee: [],
toZigbee,
isModernExtend: true,
};
},
iasWarningDeviceControl: (args) => {
const defaultModes = Object.keys(iasWarningMode);
const displayModes = args?.warningMode ?? defaultModes;
const invalidModes = displayModes.filter((m) => !defaultModes.includes(m));
if (invalidModes.length > 0) {
throw new Error(`Invalid alarm mode: ${invalidModes.join(", ")},
Legal values: ${defaultModes.join(", ")}`);
}
// biome-ignore lint/correctness/noUnusedVariables: In the future use.
const level = { low: 0, medium: 1, high: 2, very_high: 3 };
// biome-ignore lint/correctness/noUnusedVariables: In the future use.
const strobeLevel = { low: 0, medium: 1, high: 2, very_high: 3 };
const exposes = utils.exposeEndpoints(e
.composite("warning_control", "warning_control", ea.SET)
.withDescription("Make the device trigger an alarm.")
.withFeature(e.enum("mode", ea.SET, displayModes).withDescription("Mode of the warning (sound effect)"))
.withFeature(e.numeric("duration", ea.SET).withUnit("s").withDescription("Duration in seconds of the alarm")));
const toZigbee = [
{
key: ["warning_control"],
convertSet: async (entity, key, value, meta) => {
const warningModeKey = "mode";
const warningDurationKey = "duration";
const warningModeStr = value[warningModeKey];
const warningDurationValue = Number(value[warningDurationKey]);
const warningModeValue = iasWarningMode[warningModeStr];
if (warningModeValue === undefined) {
throw new Error(`Invalid warning mode: ${invalidModes.join(", ")}, Valid modes:${defaultModes.join(", ")}`);
}
if (Number.isNaN(warningDurationValue) || warningDurationValue < 0) {
throw new Error(`Invalid duration: ${warningDurationValue}. Must be a non-negative number.`);
}
const values = {
mode: warningModeValue,
level: warningModeValue ? 1 : 0,
strobe: false,
duration: warningDurationValue,
strobeDutyCycle: 0,
strobeLevel: 0,
};
if (Array.isArray(meta.mapped))
throw new Error("Not supported for groups");
const info = (values.mode << 4) + ((values.strobe ? 1 : 0) << 2) + values.level;
await entity.command("ssIasWd", "startWarning", {
startwarninginfo: info,
warningduration: values.duration,
strobedutycycle: values.strobeDutyCycle,
strobelevel: values.strobeLevel,
}, utils.getOptions(meta.mapped, entity));
},
},
];
return {
exposes: exposes,
fromZigbee: [],
toZigbee,
isModernExtend: true,
};
},
iasWarningDeviceMute: () => {
const exposes = utils.exposeEndpoints(e.enum("temporary_mute", ea.SET, ["mute"]).withDescription("temporarily mute smoke alarm but please ensure there is no real fire."));
const toZigbee = [
{
key: ["temporary_mute"],
convertSet: async (entity, key, value, meta) => {
const values = {
mode: 0,
level: 0,
strobe: false,
duration: 0,
strobeDutyCycle: 0,
strobeLevel: 0,
};
values.duration = 600;
if (Array.isArray(meta.mapped))
throw new Error("Not supported for groups");
const info = (values.mode << 4) + ((values.strobe ? 1 : 0) << 2) + values.level;
await entity.command("ssIasWd", "startWarning", {
startwarninginfo: info,
warningduration: values.duration,
strobedutycycle: values.strobeDutyCycle,
strobelevel: values.strobeLevel,
}, utils.getOptions(meta.mapped, entity));
},
},
];
return {
exposes: exposes,
fromZigbee: [],
toZigbee,
isModernExtend: true,
};
},
heimanClusterSensorMutable: () => {
const clusterName = "heimanClusterSpecial";
const exposes = utils.exposeEndpoints(e.binary("temporary_mute", ea.ALL, true, false).withDescription("temporarily mute smoke alarm but please ensure there is no real fire."));
const fromZigbee = [
{
cluster: clusterName,
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
if (msg.data.deviceMuteControl === undefined) {
return;
}
const state = !!msg.data["deviceMuteControl"];
return { temporary_mute: state };
},
},
];
const toZigbee = [
{
key: ["temporary_mute"],
convertGet: async (entity, key, meta) => {
await entity.read(clusterName, ["deviceMuteControl"], defaultResponseOptions);
},
convertSet: async (entity, key, value, meta) => {
// const state = (value as Record<string, unknown>) || {};
const state = value ? 1 : 0;
await entity.write(clusterName, { deviceMuteControl: state }, defaultResponseOptions);
},
},
];
return {
exposes: exposes,
fromZigbee,
toZigbee,
isModernExtend: true,
};
},
heimanClusterRadarConfigParam: () => {
const clusterName = "heimanClusterSpecial";
const exposes = utils.exposeEndpoints(e
.composite("radar_config", "radar_config", ea.ALL)
.withDescription("Radar module configuration parameters")
.withFeature(e.binary("enable", ea.ALL, true, false).withDescription("Enable radar (true=enable, false=disable)"))
.withFeature(e.enum("type", ea.ALL, ["micro", "move", "presence"]).withDescription("Radar detection type: micro/move/presence"))
.withFeature(e.numeric("capacity", ea.ALL).withDescription("Detection capacity"))
.withFeature(e.numeric("sensitivity", ea.ALL).withDescription("Detection sensitivity"))
.withFeature(e.numeric("range", ea.ALL).withUnit("cm").withDescription("Detection range"))
.withFeature(e.numeric("period", ea.ALL).withUnit("ms").withDescription("Detection period")));
const fromZigbee = [
{
cluster: clusterName,
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const data = msg.data.radarConfigParamOfSection1;
if (!data || !(data instanceof Buffer) || data.length < 11)
return;
let offset = 0;
const mode = data.readUInt8(offset++);
const capacity = data.readUInt8(offset++);
const sensitivity = data.readUInt8(offset++);
const range = data.readUInt16LE(offset);
offset += 2;
const period = data.readUInt16LE(offset);
offset += 2;
const enable = ((mode >> 7) & 0x01) === 1;
const typeValue = mode & 0x7f;
const typeMap = { 1: "micro", 2: "move", 3: "presence" };
const type = typeMap[typeValue] || "micro";
return {
radar_config: { enable, type, capacity, sensitivity, range, period },
};
},
},
];
const toZigbee = [
{
key: ["radar_config"],
convertGet: async (entity, key, meta) => {
await entity.read(clusterName, ["radarConfigParamOfSection1"], defaultResponseOptions);
},
convertSet: async (entity, key, value, meta) => {
const val = value;
const typeMap = { micro: 1, move: 2, presence: 3, external: 4 };
let mode = typeMap[val.type || "micro"] & 0x7f;
if (val.enable === true)
mode |= 0x80;
const data = [];
data.push(mode ?? 0);