UNPKG

zigbee-herdsman-converters

Version:

Collection of device converters to be used with zigbee-herdsman

936 lines 154 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; }; })(); 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);