UNPKG

zigbee-herdsman-converters

Version:

Collection of device converters to be used with zigbee-herdsman

1,090 lines (1,089 loc) 93.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.definitions = void 0; 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 logger_1 = require("../lib/logger"); const m = __importStar(require("../lib/modernExtend")); const reporting = __importStar(require("../lib/reporting")); const globalStore = __importStar(require("../lib/store")); const utils = __importStar(require("../lib/utils")); const e = exposes.presets; const ea = exposes.access; const NS = "zhc:bosch"; const manufacturerOptions = { manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH }; const sirenVolume = { low: 0x01, medium: 0x02, high: 0x03, }; const sirenLight = { only_light: 0x00, only_siren: 0x01, siren_and_light: 0x02, }; const outdoorSirenState = { ON: 0x07, OFF: 0x00, }; const sirenPowerSupply = { solar_panel: 0x01, ac_power_supply: 0x02, dc_power_supply: 0x03, }; // Universal Switch II const buttonMap = { config_led_top_left_press: 0x10, config_led_top_right_press: 0x11, config_led_bottom_left_press: 0x12, config_led_bottom_right_press: 0x13, config_led_top_left_longpress: 0x20, config_led_top_right_longpress: 0x21, config_led_bottom_left_longpress: 0x22, config_led_bottom_right_longpress: 0x23, }; // Universal Switch II const labelShortPress = `Specifies LED color (rgb) and pattern on short press as hex string. 0-2: RGB value (e.g. ffffff = white) 3: Light position (01=top, 02=bottom, 00=full) 4-7: Durations for sequence fade-in -> on -> fade-out -> off (e.g. 01020102) 8: Number of Repetitions (01=1 to ff=255) Example: ff1493000104010001`; // Universal Switch II const labelLongPress = `Specifies LED color (rgb) and pattern on long press as hex string. 0-2: RGB value (e.g. ffffff = white) 3: Light position (01=top, 02=bottom, 00=full) 4-7: Durations for sequence fade-in -> on -> fade-out -> off (e.g. 01020102) 8: Number of Repetitions (01=1 to ff=255) Example: ff4200000502050001`; // Universal Switch II const labelConfirmation = `Specifies LED color (rgb) and pattern of the confirmation response as hex string. 0-2: RGB value (e.g. ffffff = white) 3: Light position (01=top, 02=bottom, 00=full) 4-7: Durations for sequence fade-in -> on -> fade-out -> off (e.g. 01020102) 8: Number of Repetitions (01=1 to ff=255) Example: 30ff00000102010001`; const boschExtend = { hvacThermostatCluster: () => m.deviceAddCustomCluster("hvacThermostat", { ID: zigbee_herdsman_1.Zcl.Clusters.hvacThermostat.ID, attributes: { operatingMode: { ID: 0x4007, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, }, heatingDemand: { ID: 0x4020, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, }, valveAdaptStatus: { ID: 0x4022, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, }, remoteTemperature: { ID: 0x4040, type: zigbee_herdsman_1.Zcl.DataType.INT16, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, }, windowDetection: { ID: 0x4042, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, }, boostHeating: { ID: 0x4043, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, }, }, commands: { calibrateValve: { ID: 0x41, parameters: [], }, }, commandsResponse: {}, }), hvacUserInterfaceCfgCluster: () => m.deviceAddCustomCluster("hvacUserInterfaceCfg", { ID: zigbee_herdsman_1.Zcl.Clusters.hvacUserInterfaceCfg.ID, attributes: { displayOrientation: { ID: 0x400b, type: zigbee_herdsman_1.Zcl.DataType.UINT8, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, }, displayedTemperature: { ID: 0x4039, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, }, displayOntime: { ID: 0x403a, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, }, displayBrightness: { ID: 0x403b, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, }, }, commands: {}, commandsResponse: {}, }), operatingMode: () => m.enumLookup({ name: "operating_mode", cluster: "hvacThermostat", attribute: "operatingMode", reporting: { min: "10_SECONDS", max: "MAX", change: null }, description: "Bosch-specific operating mode (overrides system mode)", lookup: { schedule: 0x00, manual: 0x01, pause: 0x05 }, zigbeeCommandOptions: manufacturerOptions, }), windowDetection: () => m.binary({ name: "window_detection", cluster: "hvacThermostat", attribute: "windowDetection", description: "Enable/disable window open (Lo.) mode", valueOn: ["ON", 0x01], valueOff: ["OFF", 0x00], zigbeeCommandOptions: manufacturerOptions, }), boostHeating: () => m.binary({ name: "boost_heating", cluster: "hvacThermostat", attribute: "boostHeating", reporting: { min: "10_SECONDS", max: "MAX", change: null, attribute: "boostHeating" }, description: "Activate boost heating (5 min. on TRV)", valueOn: ["ON", 0x01], valueOff: ["OFF", 0x00], zigbeeCommandOptions: manufacturerOptions, }), childLock: () => m.binary({ name: "child_lock", cluster: "hvacUserInterfaceCfg", attribute: "keypadLockout", description: "Enables/disables physical input on the device", valueOn: ["LOCK", 0x01], valueOff: ["UNLOCK", 0x00], }), displayOntime: () => m.numeric({ name: "display_ontime", cluster: "hvacUserInterfaceCfg", attribute: "displayOntime", description: "Sets the display on-time", valueMin: 5, valueMax: 30, unit: "s", zigbeeCommandOptions: manufacturerOptions, }), displayBrightness: () => m.numeric({ name: "display_brightness", cluster: "hvacUserInterfaceCfg", attribute: "displayBrightness", description: "Sets brightness of the display", valueMin: 0, valueMax: 10, zigbeeCommandOptions: manufacturerOptions, }), valveAdaptProcess: () => { const adaptationStatus = { none: 0x00, ready_to_calibrate: 0x01, calibration_in_progress: 0x02, error: 0x03, success: 0x04, }; const exposes = [ e .binary("valve_adapt_process", ea.ALL, true, false) .withLabel("Trigger adaptation process") .withDescription('Trigger the valve adaptation process. Only possible when adaptation status is "ready_to_calibrate" or "error".') .withCategory("config"), ]; const fromZigbee = [ { cluster: "hvacThermostat", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data.valveAdaptStatus !== undefined) { if (msg.data.valveAdaptStatus === adaptationStatus.calibration_in_progress) { result.valve_adapt_process = true; } else { result.valve_adapt_process = false; } } return result; }, }, ]; const toZigbee = [ { key: ["valve_adapt_process"], convertSet: async (entity, key, value, meta) => { if (value === true) { const adaptStatus = utils.getFromLookup(meta.state.valve_adapt_status, adaptationStatus); switch (adaptStatus) { case adaptationStatus.ready_to_calibrate: case adaptationStatus.error: await entity.command("hvacThermostat", "calibrateValve", {}, manufacturerOptions); break; default: throw new Error("Valve adaptation process not possible right now."); } } return { state: { valve_adapt_process: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("hvacThermostat", ["valveAdaptStatus"], manufacturerOptions); }, }, ]; return { exposes, fromZigbee, toZigbee, isModernExtend: true, }; }, heatingDemand: () => { const fromZigbee = [ { cluster: "hvacThermostat", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data.heatingDemand !== undefined) { const demand = msg.data.heatingDemand; result.pi_heating_demand = demand; result.running_state = demand > 0 ? "heat" : "idle"; } return result; }, }, ]; const toZigbee = [ { key: ["pi_heating_demand"], convertSet: async (entity, key, value, meta) => { if (key === "pi_heating_demand") { let demand = utils.toNumber(value, key); demand = utils.numberWithinRange(demand, 0, 100); await entity.write("hvacThermostat", { heatingDemand: demand }, manufacturerOptions); return { state: { pi_heating_demand: demand } }; } }, convertGet: async (entity, key, meta) => { await entity.read("hvacThermostat", ["heatingDemand"], manufacturerOptions); }, }, { key: ["running_state"], convertGet: async (entity, key, meta) => { await entity.read("hvacThermostat", ["heatingDemand"], manufacturerOptions); }, }, ]; return { fromZigbee, toZigbee, isModernExtend: true, }; }, ignoreDst: () => { const fromZigbee = [ { cluster: "genTime", type: "read", convert: async (model, msg, publish, options, meta) => { if (msg.data.includes("dstStart", "dstEnd", "dstShift")) { const response = { dstStart: { attribute: 0x0003, status: zigbee_herdsman_1.Zcl.Status.SUCCESS, value: 0x00 }, dstEnd: { attribute: 0x0004, status: zigbee_herdsman_1.Zcl.Status.SUCCESS, value: 0x00 }, dstShift: { attribute: 0x0005, status: zigbee_herdsman_1.Zcl.Status.SUCCESS, value: 0x00 }, }; await msg.endpoint.readResponse(msg.cluster, msg.meta.zclTransactionSequenceNumber, response); } }, }, ]; return { fromZigbee, isModernExtend: true, }; }, doorWindowContact: (hasVibrationSensor) => { const exposes = [ e.binary("contact", ea.STATE, false, true).withDescription("Indicates whether the device is opened or closed"), e .enum("action", ea.STATE, ["none", "single", "long"]) .withDescription("Triggered action (e.g. a button click)") .withCategory("diagnostic"), ]; if (hasVibrationSensor) { exposes.push(e.binary("vibration", ea.STATE, true, false).withDescription("Indicates whether the device detected vibration")); } const fromZigbee = [ { cluster: "ssIasZone", type: ["commandStatusChangeNotification", "attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { if (msg.data.zoneStatus !== undefined || msg.data.zonestatus !== undefined) { const zoneStatus = msg.type === "commandStatusChangeNotification" ? msg.data.zonestatus : msg.data.zoneStatus; const lookup = { 0: "none", 1: "single", 2: "long" }; const result = { contact: !((zoneStatus & 1) > 0), vibration: (zoneStatus & (1 << 1)) > 0, tamper: (zoneStatus & (1 << 2)) > 0, battery_low: (zoneStatus & (1 << 3)) > 0, supervision_reports: (zoneStatus & (1 << 4)) > 0, restore_reports: (zoneStatus & (1 << 5)) > 0, trouble: (zoneStatus & (1 << 6)) > 0, ac_status: (zoneStatus & (1 << 7)) > 0, test: (zoneStatus & (1 << 8)) > 0, battery_defect: (zoneStatus & (1 << 9)) > 0, action: lookup[(zoneStatus >> 11) & 3], }; // biome-ignore lint/performance/noDelete: ignored using `--suppress` if (result.action === "none") delete result.action; return result; } }, }, ]; return { exposes, fromZigbee, isModernExtend: true, }; }, smokeAlarm: () => { const smokeAlarm = { OFF: 0x0000, ON: 0x3c00, // 15360 or 46080 works }; const burglarAlarm = { OFF: 0x0001, ON: 0xb401, // 46081 }; const exposes = [ e.binary("smoke", ea.STATE, true, false).withDescription("Indicates whether the device detected smoke"), e .binary("test", ea.STATE, true, false) .withDescription("Indicates whether the device is currently performing a test") .withCategory("diagnostic"), e.binary("alarm_smoke", ea.ALL, true, false).withDescription("Toggle the smoke alarm siren").withCategory("config"), e.binary("alarm_burglar", ea.ALL, true, false).withDescription("Toggle the burglar alarm siren").withCategory("config"), ]; const fromZigbee = [ { cluster: "ssIasZone", type: ["commandStatusChangeNotification", "attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { if (msg.data.zoneStatus !== undefined || msg.data.zonestatus !== undefined) { const zoneStatus = msg.type === "commandStatusChangeNotification" ? msg.data.zonestatus : msg.data.zoneStatus; return { smoke: (zoneStatus & 1) > 0, alarm_smoke: (zoneStatus & (1 << 1)) > 0, battery_low: (zoneStatus & (1 << 3)) > 0, supervision_reports: (zoneStatus & (1 << 4)) > 0, restore_reports: (zoneStatus & (1 << 5)) > 0, alarm_burglar: (zoneStatus & (1 << 7)) > 0, test: (zoneStatus & (1 << 8)) > 0, alarm_silenced: (zoneStatus & (1 << 11)) > 0, }; } }, }, ]; const toZigbee = [ { key: ["alarm_smoke", "alarm_burglar"], convertSet: async (entity, key, value, meta) => { if (key === "alarm_smoke") { let transformedValue = "OFF"; if (value === true) { transformedValue = "ON"; } const index = utils.getFromLookup(transformedValue, smokeAlarm); await entity.command("ssIasZone", "boschSmokeAlarmSiren", { data: index }, manufacturerOptions); return { state: { alarm_smoke: value } }; } if (key === "alarm_burglar") { let transformedValue = "OFF"; if (value === true) { transformedValue = "ON"; } const index = utils.getFromLookup(transformedValue, burglarAlarm); await entity.command("ssIasZone", "boschSmokeAlarmSiren", { data: index }, manufacturerOptions); return { state: { alarm_burglar: value } }; } }, convertGet: async (entity, key, meta) => { switch (key) { case "alarm_smoke": case "alarm_burglar": case "zone_status": await entity.read("ssIasZone", ["zoneStatus"]); break; default: throw new Error(`Unhandled key boschExtend.smokeAlarm.toZigbee.convertGet ${key}`); } }, }, ]; return { exposes, fromZigbee, toZigbee, isModernExtend: true, }; }, broadcastAlarm: () => { const sirenState = { smoke_off: 0x0000, smoke_on: 0x3c00, burglar_off: 0x0001, burglar_on: 0xb401, }; const exposes = [ e .enum("broadcast_alarm", ea.SET, Object.keys(sirenState)) .withDescription("Set siren state of all BSD-2 via broadcast") .withCategory("config"), ]; const toZigbee = [ { key: ["broadcast_alarm"], convertSet: async (entity, key, value, meta) => { if (key === "broadcast_alarm") { const index = utils.getFromLookup(value, sirenState); utils.assertEndpoint(entity); await entity.zclCommandBroadcast(255, zigbee_herdsman_1.ZSpec.BroadcastAddress.SLEEPY, zigbee_herdsman_1.Zcl.Clusters.ssIasZone.ID, "boschSmokeAlarmSiren", { data: index }, manufacturerOptions); return; } }, }, ]; return { exposes, toZigbee, isModernExtend: true, }; }, twinguard: () => { const smokeSensitivity = { low: 0x03, medium: 0x02, high: 0x01, }; const sirenState = { stop: 0x00, pre_alarm: 0x01, fire: 0x02, burglar: 0x03, }; const stateOffOn = { OFF: 0x00, ON: 0x01, }; const exposes = [ e.binary("smoke", ea.STATE, true, false).withDescription("Indicates whether the device detected smoke"), e .numeric("temperature", ea.STATE) .withValueMin(0) .withValueMax(65) .withValueStep(0.1) .withUnit("°C") .withDescription("Measured temperature value"), e .numeric("humidity", ea.STATE) .withValueMin(0) .withValueMax(100) .withValueStep(0.1) .withUnit("%") .withDescription("Measured relative humidity"), e .numeric("voc", ea.STATE) .withValueMin(0) .withValueMax(50000) .withValueStep(1) .withLabel("VOC") .withUnit("µg/m³") .withDescription("Measured VOC value"), e .numeric("co2", ea.STATE) .withValueMin(400) .withValueMax(2400) .withValueStep(1) .withLabel("CO2") .withUnit("ppm") .withDescription("The measured CO2 (carbon dioxide) value"), e.numeric("aqi", ea.STATE).withValueMin(0).withValueMax(500).withValueStep(1).withLabel("AQI").withDescription("Air Quality Index"), e.illuminance(), e .numeric("battery", ea.STATE) .withUnit("%") .withValueMin(0) .withValueMax(100) .withDescription("Remaining battery in %") .withCategory("diagnostic"), e.text("siren_state", ea.STATE).withDescription("Siren state").withCategory("diagnostic"), e.enum("alarm", ea.ALL, Object.keys(sirenState)).withDescription("Alarm mode for siren"), e.binary("self_test", ea.ALL, true, false).withDescription("Initiate self-test").withCategory("config"), e.enum("sensitivity", ea.ALL, Object.keys(smokeSensitivity)).withDescription("Sensitivity of the smoke detector").withCategory("config"), e.binary("pre_alarm", ea.ALL, "ON", "OFF").withDescription("Enable/disable pre-alarm").withCategory("config"), e.binary("heartbeat", ea.ALL, "ON", "OFF").withDescription("Enable/disable heartbeat (blue LED)").withCategory("config"), ]; const fromZigbee = [ { cluster: "twinguardSmokeDetector", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data.sensitivity !== undefined) { result.sensitivity = Object.keys(smokeSensitivity)[msg.data.sensitivity]; } return result; }, }, { cluster: "twinguardMeasurements", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data.humidity !== undefined) { const humidity = utils.toNumber(msg.data.humidity) / 100.0; if (utils.isInRange(0, 100, humidity)) { result.humidity = humidity; } } if (msg.data.airpurity !== undefined) { const iaq = utils.toNumber(msg.data.airpurity); result.aqi = iaq; let factorVoc = 6; let factorCo2 = 2; if (iaq >= 51 && iaq <= 100) { factorVoc = 10; factorCo2 = 4; } else if (iaq >= 101 && iaq <= 150) { factorVoc = 20; factorCo2 = 4; } else if (iaq >= 151 && iaq <= 200) { factorVoc = 50; factorCo2 = 4; } else if (iaq >= 201 && iaq <= 250) { factorVoc = 100; factorCo2 = 4; } else if (iaq >= 251) { factorVoc = 100; factorCo2 = 4; } result.voc = iaq * factorVoc; result.co2 = iaq * factorCo2 + 400; } if (msg.data.temperature !== undefined) { result.temperature = utils.toNumber(msg.data.temperature) / 100.0; } if (msg.data.illuminance !== undefined) { result.illuminance = utils.precisionRound(msg.data.illuminance / 2, 2); } if (msg.data.battery !== undefined) { result.battery = utils.precisionRound(msg.data.battery / 2, 2); } return result; }, }, { cluster: "twinguardOptions", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data.pre_alarm !== undefined) { result.pre_alarm = Object.keys(stateOffOn)[msg.data.pre_alarm]; } return result; }, }, { cluster: "twinguardSetup", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data.heartbeat !== undefined) { result.heartbeat = Object.keys(stateOffOn)[msg.data.heartbeat]; } return result; }, }, { cluster: "twinguardAlarm", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; const lookup = { 2097184: "clear", 18874400: "self_test", 35651616: "burglar", 2097282: "pre_alarm", 2097281: "fire", 2097216: "silenced", }; if (msg.data.alarm_status !== undefined) { result.self_test = (msg.data.alarm_status & (1 << 24)) > 0; result.smoke = (msg.data.alarm_status & (1 << 7)) > 0; result.siren_state = lookup[msg.data.alarm_status]; } return result; }, }, { cluster: "genAlarms", type: ["commandAlarm", "readResponse"], convert: async (model, msg, publish, options, meta) => { const result = {}; const lookup = { 16: "fire", 17: "pre_alarm", 20: "clear", 22: "silenced", }; result.siren_state = lookup[msg.data.alarmcode]; if (msg.data.alarmcode === 0x10 || msg.data.alarmcode === 0x11) { await msg.endpoint.commandResponse("genAlarms", "alarm", { alarmcode: msg.data.alarmcode, clusterid: 0xe000 }, { direction: 1 }); } return result; }, }, ]; const toZigbee = [ { key: ["sensitivity", "pre_alarm", "self_test", "alarm", "heartbeat"], convertSet: async (entity, key, value, meta) => { if (key === "sensitivity") { const index = utils.getFromLookup(value, smokeSensitivity); await entity.write("twinguardSmokeDetector", { sensitivity: index }, manufacturerOptions); return { state: { sensitivity: value } }; } if (key === "pre_alarm") { const index = utils.getFromLookup(value, stateOffOn); await entity.write("twinguardOptions", { pre_alarm: index }, manufacturerOptions); return { state: { pre_alarm: value } }; } if (key === "heartbeat") { const endpoint = meta.device.getEndpoint(12); const index = utils.getFromLookup(value, stateOffOn); await endpoint.write("twinguardSetup", { heartbeat: index }, manufacturerOptions); return { state: { heartbeat: value } }; } if (key === "self_test") { if (value) { await entity.command("twinguardSmokeDetector", "initiateTestMode", manufacturerOptions); } } if (key === "alarm") { const endpoint = meta.device.getEndpoint(12); const index = utils.getFromLookup(value, sirenState); utils.assertEndpoint(entity); if (index === 0x00) { await entity.commandResponse("genAlarms", "alarm", { alarmcode: 0x16, clusterid: 0xe000 }, { direction: 1 }); await entity.commandResponse("genAlarms", "alarm", { alarmcode: 0x14, clusterid: 0xe000 }, { direction: 1 }); await endpoint.command("twinguardAlarm", "burglarAlarm", { data: 0x00 }, manufacturerOptions); } else if (index === 0x01) { await entity.commandResponse("genAlarms", "alarm", { alarmcode: 0x11, clusterid: 0xe000 }, { direction: 1 }); return { state: { siren_state: "pre_alarm" } }; } else if (index === 0x02) { await entity.commandResponse("genAlarms", "alarm", { alarmcode: 0x10, clusterid: 0xe000 }, { direction: 1 }); return { state: { siren_state: "fire" } }; } else if (index === 0x03) { await endpoint.command("twinguardAlarm", "burglarAlarm", { data: 0x01 }, manufacturerOptions); } } }, convertGet: async (entity, key, meta) => { switch (key) { case "sensitivity": await entity.read("twinguardSmokeDetector", ["sensitivity"], manufacturerOptions); break; case "pre_alarm": await entity.read("twinguardOptions", ["pre_alarm"], manufacturerOptions); break; case "heartbeat": await meta.device.getEndpoint(12).read("twinguardSetup", ["heartbeat"], manufacturerOptions); break; case "alarm": case "self_test": await meta.device.getEndpoint(12).read("twinguardAlarm", ["alarm_status"], manufacturerOptions); break; default: throw new Error(`Unhandled key boschExtend.twinguard.toZigbee.convertGet ${key}`); } }, }, ]; return { exposes, fromZigbee, toZigbee, isModernExtend: true, }; }, bmct: () => { const stateDeviceMode = { light: 0x04, shutter: 0x01, disabled: 0x00, }; const stateMotor = { stopped: 0x00, opening: 0x01, closing: 0x02, }; const stateSwitchType = { button: 0x01, button_key_change: 0x02, rocker_switch: 0x03, rocker_switch_key_change: 0x04, }; const stateOffOn = { OFF: 0x00, ON: 0x01, }; const fromZigbee = [ fz.on_off, fz.power_on_behavior, fz.cover_position_tilt, { cluster: "boschSpecific", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; const data = msg.data; if (data.deviceMode !== undefined) { result.device_mode = Object.keys(stateDeviceMode).find((key) => stateDeviceMode[key] === msg.data.deviceMode); const deviceMode = msg.data.deviceMode; if (deviceMode !== meta.device.meta.deviceMode) { meta.device.meta.deviceMode = deviceMode; meta.deviceExposesChanged(); } } if (data.switchType !== undefined) { result.switch_type = Object.keys(stateSwitchType).find((key) => stateSwitchType[key] === msg.data.switchType); } if (data.calibrationOpeningTime !== undefined) { result.calibration_opening_time = msg.data.calibrationOpeningTime / 10; } if (data.calibrationClosingTime !== undefined) { result.calibration_closing_time = msg.data.calibrationClosingTime / 10; } if (data.calibrationButtonHoldTime !== undefined) { result.calibration_button_hold_time = msg.data.calibrationButtonHoldTime / 10; } if (data.calibrationMotorStartDelay !== undefined) { result.calibration_motor_start_delay = msg.data.calibrationMotorStartDelay / 10; } if (data.childLock !== undefined) { const property = utils.postfixWithEndpointName("child_lock", msg, model, meta); result[property] = msg.data.childLock === 1 ? "ON" : "OFF"; } if (data.motorState !== undefined) { result.motor_state = Object.keys(stateMotor).find((key) => stateMotor[key] === msg.data.motorState); } return result; }, }, ]; const toZigbee = [ tz.power_on_behavior, tz.cover_position_tilt, { key: ["device_mode", "switch_type", "child_lock", "state", "on_time", "off_wait_time"], convertSet: async (entity, key, value, meta) => { if (key === "state") { if ("ID" in entity && entity.ID === 1) { await tz.cover_state.convertSet(entity, key, value, meta); } else { await tz.on_off.convertSet(entity, key, value, meta); } } if (key === "on_time" || key === "on_wait_time") { if ("ID" in entity && entity.ID !== 1) { await tz.on_off.convertSet(entity, key, value, meta); } } if (key === "device_mode") { const index = utils.getFromLookup(value, stateDeviceMode); await entity.write("boschSpecific", { deviceMode: index }); await entity.read("boschSpecific", ["deviceMode"]); return { state: { device_mode: value } }; } if (key === "switch_type") { const index = utils.getFromLookup(value, stateSwitchType); await entity.write("boschSpecific", { switchType: index }); return { state: { switch_type: value } }; } if (key === "child_lock") { const index = utils.getFromLookup(value, stateOffOn); await entity.write("boschSpecific", { childLock: index }); return { state: { child_lock: value } }; } }, convertGet: async (entity, key, meta) => { switch (key) { case "state": case "on_time": case "off_wait_time": if ("ID" in entity && entity.ID !== 1) { await entity.read("genOnOff", ["onOff"]); } break; case "device_mode": await entity.read("boschSpecific", ["deviceMode"]); break; case "switch_type": await entity.read("boschSpecific", ["switchType"]); break; case "child_lock": await entity.read("boschSpecific", ["childLock"]); break; default: throw new Error(`Unhandled key boschExtend.bmct.toZigbee.convertGet ${key}`); } }, }, { key: ["calibration_closing_time", "calibration_opening_time", "calibration_button_hold_time", "calibration_motor_start_delay"], convertSet: async (entity, key, value, meta) => { if (key === "calibration_opening_time") { const number = utils.toNumber(value, "calibration_opening_time"); const index = number * 10; await entity.write("boschSpecific", { calibrationOpeningTime: index }); return { state: { calibration_opening_time: number } }; } if (key === "calibration_closing_time") { const number = utils.toNumber(value, "calibration_closing_time"); const index = number * 10; await entity.write("boschSpecific", { calibrationClosingTime: index }); return { state: { calibration_closing_time: number } }; } if (key === "calibration_button_hold_time") { const number = utils.toNumber(value, "calibration_button_hold_time"); const index = number * 10; await entity.write("boschSpecific", { calibrationButtonHoldTime: index }); return { state: { calibration_button_hold_time: number } }; } if (key === "calibration_motor_start_delay") { const number = utils.toNumber(value, "calibration_motor_start_delay"); const index = number * 10; await entity.write("boschSpecific", { calibrationMotorStartDelay: index }); return { state: { calibration_motor_start_delay: number } }; } }, convertGet: async (entity, key, meta) => { switch (key) { case "calibration_opening_time": await entity.read("boschSpecific", ["calibrationOpeningTime"]); break; case "calibration_closing_time": await entity.read("boschSpecific", ["calibrationClosingTime"]); break; case "calibration_button_hold_time": await entity.read("boschSpecific", ["calibrationButtonHoldTime"]); break; case "calibration_motor_start_delay": await entity.read("boschSpecific", ["calibrationMotorStartDelay"]); break; default: throw new Error(`Unhandled key boschExtend.bmct.toZigbee.convertGet ${key}`); } }, }, ]; return { fromZigbee, toZigbee, isModernExtend: true, }; }, }; const tzLocal = { rbshoszbeu: { key: ["light_delay", "siren_delay", "light_duration", "siren_duration", "siren_volume", "alarm_state", "power_source", "siren_and_light"], convertSet: async (entity, key, value, meta) => { if (key === "light_delay") { const index = value; await entity.write(0x0502, { 40964: { value: index, type: 0x21 } }, manufacturerOptions); return { state: { light_delay: value } }; } if (key === "siren_delay") { const index = value; await entity.write(0x0502, { 40963: { value: index, type: 0x21 } }, manufacturerOptions); return { state: { siren_delay: value } }; } if (key === "light_duration") { const index = value; await entity.write(0x0502, { 40965: { value: index, type: 0x20 } }, manufacturerOptions); return { state: { light_duration: value } }; } if (key === "siren_duration") { const index = value; await entity.write(0x0502, { 40960: { value: index, type: 0x20 } }, manufacturerOptions); return { state: { siren_duration: value } }; } if (key === "siren_and_light") { const index = utils.getFromLookup(value, sirenLight); await entity.write(0x0502, { 40961: { value: index, type: 0x20 } }, manufacturerOptions); return { state: { siren_and_light: value } }; } if (key === "siren_volume") { const index = utils.getFromLookup(value, sirenVolume); await entity.write(0x0502, { 40962: { value: index, type: 0x20 } }, manufacturerOptions); return { state: { siren_volume: value } }; } if (key === "power_source") { const index = utils.getFromLookup(value, sirenPowerSupply); await entity.write(0x0001, { 40962: { value: index, type: 0x20 } }, manufacturerOptions); return { state: { power_source: value } }; } if (key === "alarm_state") { const endpoint = meta.device.getEndpoint(1); const index = utils.getFromLookup(value, outdoorSirenState); if (index === 0) { await endpoint.command(0x0502, 0xf0, { data: 0 }, manufacturerOptions); return { state: { alarm_state: value } }; } await endpoint.command(0x0502, 0xf0, { data: 7 }, manufacturerOptions); return { state: { alarm_state: value } }; } }, convertGet: async (entity, key, meta) => { switch (key) { case "light_delay": await entity.read(0x0502, [0xa004], manufacturerOptions); break; case "siren_delay": await entity.read(0x0502, [0xa003], manufacturerOptions); break; case "light_duration": await entity.read(0x0502, [0xa005], manufacturerOptions); break; case "siren_duration": await entity.read(0x0502, [0xa000], manufacturerOptions); break; case "siren_and_light": await entity.read(0x0502, [0xa001], manufacturerOptions); break; case "siren_volume": await entity.read(0x0502, [0xa002], manufacturerOptions); break; case "alarm_state": await entity.read(0x0502, [0xf0], manufacturerOptions); break; default: // Unknown key throw new Error(`Unhandled key toZigbee.rbshoszbeu.convertGet ${key}`); } }, }, bhius_config: { key: Object.keys(buttonMap), convertGet: async (entity, key, meta) => { if (buttonMap[key] === undefined) { throw new Error(`Unknown key ${key}`); } await entity.read("boschSpecific", [buttonMap[key]], manufacturerOptions); }, convertSet: async (entity, key, value, meta) => { if (buttonMap[key] === undefined) { return; } const buffer = Buffer.from(value, "hex"); if (buffer.length !== 9) throw new Error(`Invalid configuration length: ${buffer.length} (should be 9)`); const payload = {}; payload[buttonMap[key]] = { value: buffer, type: 65 }; await entity.write("boschSpecific", payload, manufacturerOptions); const result = {}; result[key] = value; return { state: result }; }, }, }; const fzLocal = { bhius_button_press: { cluster: "boschSpecific", type: "raw", options: [e.text("led_response", ea.ALL).withLabel("LED config (confirmation response)").withDescription(labelConfirmation)], convert: async (model, msg, publish, options, meta) => { const sequenceNumber = msg.data.readUInt8(3); const buttonId = msg.data.readUInt8(4); const longPress = msg.data.readUInt8(5); const duration = msg.data.readUInt16LE(6); // biome-ignore lint/suspicious/noImplicitAnyLet: ignored using `--suppress` let buffer; if (options.led_response != null) { buffer = Buffer.from(options.led_response, "hex"); if (buffer.length !== 9) { logger_1.logger.error(`Invalid length of led_response: ${buffer.length} (should be 9)`, NS); buffer = Buffer.from("30ff00000102010001", "hex"); } } else { buffer = Buffer.from("30ff00000102010001", "hex"); } if (utils.hasAlreadyProcessedMessage(msg, model, sequenceNumber)) return; const buttons = { 0: "top_left", 1: "top_right", 2: "bottom_left", 3: "bottom_right" }; let command = ""; if (buttonId in buttons) { if (longPress && duration > 0) { if (globalStore.hasValue(msg.endpoint, buttons[buttonId])) return;