UNPKG

zigbee-herdsman-converters

Version:

Collection of device converters to be used with zigbee-herdsman

2,041 lines 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;
                    globalStore.putValue(msg.endpoint, buttons[buttonId], duration);
                    command = "longpress";
                }
                else {
                    globalStore.clearValue(msg.endpoint, buttons[buttonId]);
                    command = longPress ? "longpress_release" : "release";
                    msg.endpoint.command("boschSpecific", "confirmButtonPressed", { data: buffer }, { sendPolicy: "immediate" }).catch((error) => { });
                }
                return { action: `button_${buttons[buttonId]}_${command}` };
            }
            logger_1.logger.error(`Received message with unknown command ID ${buttonId}. Data: 0x${msg.data.toString("hex")}`, NS);
        },
    },
    bhius_config: {
        cluster: "boschSpecific",
        type: ["attributeReport", "readResponse"],
        convert: async (model, msg, publish, options, meta) => {
            const result = {};
            for (const id of Object.values(buttonMap)) {
                if (msg.data[id] !== undefined) {
                    result[Object.keys(buttonMap).find((key) => buttonMap[key] === id)] = msg.data[id].toString("hex");
                }
            }
            return result;
        },
    },
};
exports.definitions = [
    {
        zigbeeModel: ["RBSH-OS-ZB-EU"],
        model: "BSIR-EZ",
        vendor: "Bosch",
        description: "Outdoor siren",
        fromZigbee: [fz.battery, fz.power_source],
        toZigbee: [tzLocal.rbshoszbeu, tz.warning],
        meta: { battery: { voltageToPercentage: { min: 2500, max: 4200 } } },
        configure: async (device, coordinatorEndpoint) => {
            const endpoint = device.getEndpoint(1);
            await reporting.bind(endpoint, coordinatorEndpoint, ["genPowerCfg", "ssIasZone", "ssIasWd", "genBasic"]);
            await reporting.batteryVoltage(endpoint);
            await endpoint.read(0x0502, [0xa000, 0xa001, 0xa002, 0xa003, 0xa004, 0xa005], manufacturerOptions);
            if (endpoint.binds.some((b) => b.cluster.name === "genPollCtrl")) {
                await endpoint.unbind("genPollCtrl", coordinatorEndpoint);
            }
        },
        exposes: [
            e.binary("alarm_state", ea.ALL, "ON", "OFF").withDescription("Alarm turn ON/OFF"),
            e
                .numeric("light_delay", ea.ALL)
                .withValueMin(0)
                .withValueMax(30)
                .withValueStep(1)
                .withUnit("s")
                .withDescription("Flashing light delay")
                .withUnit("s"),
            e
                .numeric("siren_delay", ea.ALL)
                .withValueMin(0)
                .withValueMax(30)
                .withValueStep(1)
                .withUnit("s")
                .withDescription("Siren alarm delay")
                .withUnit("s"),
            e
                .numeric("siren_duration", ea.ALL)
                .withValueMin(1)
                .withValueMax(15)
                .withValueStep(1)
                .withUnit("m")
                .withDescription("Duration of the alarm siren")
                .withUnit("m"),
            e
                .numeric("light_duration", ea.ALL)
                .withValueMin(1)
                .withValueMax(15)
                .withValueStep(1)
                .withUnit("m")
                .withDescription("Duration of the alarm light")
                .withUnit("m"),
            e.enum("siren_volume", ea.ALL, Object.keys(sirenVolume)).withDescription("Volume of the alarm"),
            e.enum("siren_and_light", ea.ALL, Object.keys(sirenLight)).withDescription("Siren and Light behaviour during alarm "),
            e.enum("power_source", ea.ALL, Object.keys(sirenPowerSupply)).withDescription("Siren power source"),
            e
                .warning()
                .removeFeature("strobe_level")
                .removeFeature("strobe")
                .removeFeature("strobe_duty_cycle")
                .removeFeature("level")
                .removeFeature("duration"),
            e.test(),
            e.battery(),
            e.battery_voltage(),
            e.binary("ac_status", ea.STATE, true, false).withDescription("Is the device plugged in"),
        ],
        extend: [
            m.iasZoneAlarm({ zoneType: "alarm", zoneAttributes: ["alarm_1", "tamper", "battery_low"] }),
            m.deviceAddCustomCluster("ssIasZone", {
                ID: zigbee_herdsman_1.Zcl.Clusters.ssIasZone.ID,
                attributes: {},
                commands: {
                    boschTestTamper: {
                        ID: 0xf3,
                        parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.DataType.UINT8 }],
                    },
                },
                commandsResponse: {},
            }),
            m.deviceAddCustomCluster("ssIasWd", {
                ID: zigbee_herdsman_1.Zcl.Clusters.ssIasWd.ID,
                attributes: {},
                commands: {
                    boschOutdoorSiren: {
                        ID: 240,
                        parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.DataType.UINT8 }],
                    },
                },
                commandsResponse: {},
            }),
            m.quirkCheckinInterval(0),
        ],
    },
    {
        zigbeeModel: ["RBSH-WS-ZB-EU"],
        model: "BWA-1",
        vendor: "Bosch",
        description: "Smart water alarm",
        extend: [
            m.deviceAddCustomCluster("boschSpecific", {
                ID: 0xfcac,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
                attributes: {
                    alarmOnMotion: {
                        ID: 0x0003,
                        type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN,
                    },
                },
                commands: {},
                commandsResponse: {},
            }),
            m.iasZoneAlarm({
                zoneType: "water_leak",
                zoneAttributes: ["alarm_1", "tamper"],
            }),
            m.battery({
                percentage: true,
                lowStatus: true,
            }),
            m.binary({
                name: "alarm_on_motion",
                cluster: "boschSpecific",
                attribute: "alarmOnMotion",
                description: "Toggle audible alarm on motion",
                valueOn: ["ON", 0x01],
                valueOff: ["OFF", 0x00],
                zigbeeCommandOptions: manufacturerOptions,
                entityCategory: "config",
            }),
            m.bindCluster({
                cluster: "genPollCtrl",
                clusterType: "input",
            }),
        ],
        configure: async (device, coordinatorEndpoint) => {
            const endpoint = device.getEndpoint(1);
            await endpoint.read("genPowerCfg", ["batteryPercentageRemaining"]);
            await endpoint.read("ssIasZone", ["zoneStatus"]);
            await endpoint.read("boschSpecific", ["alarmOnMotion"], manufacturerOptions);
        },
    },
    {
        zigbeeModel: ["RBSH-SD-ZB-EU"],
        model: "BSD-2",
        vendor: "Bosch",
        description: "Smoke alarm II",
        extend: [
            m.deviceAddCustomCluster("ssIasZone", {
                ID: zigbee_herdsman_1.Zcl.Clusters.ssIasZone.ID,
                attributes: {},
                commands: {
                    boschSmokeAlarmSiren: {
                        ID: 0x80,
                        parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.DataType.UINT16 }],
                    },
                },
                commandsResponse: {},
            }),
            boschExtend.smokeAlarm(),
            m.battery({
                percentage: true,
                lowStatus: false,
            }),
            m.enumLookup({
                name: "sensitivity",
                cluster: "ssIasZone",
                attribute: "currentZoneSensitivityLevel",
                description: "Sensitivity of the smoke detector",
                lookup: {
                    low: 0x00,
                    medium: 0x01,
                    high: 0x02,
                },
                entityCategory: "config",
            }),
            boschExtend.broadcastAlarm(),
            m.bindCluster({
                cluster: "genPollCtrl",
                clusterType: "input",
            }),
        ],
        configure: async (device, coordinatorEndpoint) => {
            const endpoint = device.getEndpoint(1);
            await endpoint.read("genPowerCfg", ["batteryPercentageRemaining"]);
            await endpoint.read("ssIasZone", ["zoneStatus"]);
            await endpoint.read("ssIasZone", ["currentZoneSensitivityLevel"]);
        },
    },
    {
        zigbeeModel: [
            "RFDL-ZB",
            "RFDL-ZB-EU",
            "RFDL-ZB-H",
            "RFDL-ZB-K",
            "RFDL-ZB-CHI",
            "RFDL-ZB-MS",
            "RFDL-ZB-ES",
            "RFPR-ZB",
            "RFPR-ZB-EU",
            "RFPR-ZB-CHI",
            "RFPR-ZB-ES",
            "RFPR-ZB-MS",
        ],
        model: "RADION TriTech ZB",
        vendor: "Bosch",
        description: "Wireless motion detector",
        fromZigbee: [fz.temperature, fz.battery, fz.ias_occupancy_alarm_1],
        toZigbee: [],
        meta: { battery: { voltageToPercentage: { min: 2500, max: 3000 } } },
        configure: async (device, coordinatorEndpoint) => {
            const endpoint = device.getEndpoint(1);
            await reporting.bind(endpoint, coordinatorEndpoint, ["msTemperatureMeasurement", "genPowerCfg"]);
            await reporting.temperature(endpoint);
            await reporting.batteryVoltage(endpoint);
        },
        exposes: [e.temperature(), e.battery(), e.occupancy(), e.battery_low(), e.tamper()],
        extend: [m.illuminance()],
    },
    {
        zigbeeModel: ["ISW-ZPR1-WP13"],
        model: "ISW-ZPR1-WP13",
        vendor: "Bosch",
        description: "Motion sensor",
        fromZigbee: [fz.temperature, fz.battery, fz.ias_occupancy_alarm_1, fz.ignore_iaszone_report],
        toZigbee: [],
        meta: { battery: { voltageToPercentage: { min: 2500, max: 3000 } } },
        configure: async (device, coordinatorEndpoint) => {
            const endpoint = device.getEndpoint(5);
            await reporting.bind(endpoint, coordinatorEndpoint, ["msTemperatureMeasurement", "genPowerCfg"]);
            await reporting.temperature(endpoint);
            await reporting.batteryVoltage(endpoint);
        },
        exposes: [e.temperature(), e.battery(), e.occupancy(), e.battery_low(), e.tamper()],
    },
    {
        zigbeeModel: ["RBSH-TRV0-ZB-EU", "RBSH-TRV1-ZB-EU"],
        model: "BTH-RA",
        vendor: "Bosch",
        description: "Radiator thermostat II",
        meta: {
            overrideHaDiscoveryPayload: (payload) => {
                // Override climate discovery
                // https://github.com/Koenkk/zigbee2mqtt/pull/23075#issue-2355829475
                if (payload.mode_command_topic?.endsWith("/system_mode")) {
                    payload.mode_command_topic = payload.mode_command_topic.substring(0, payload.mode_command_topic.lastIndexOf("/system_mode"));
                    payload.mode_command_template =
                        "{% set values = " +
                            `{ 'auto':'schedule','heat':'manual','off':'pause'} %}` +
                            `{"operating_mode": "{{ values[value] if value in values.keys() else 'pause' }}"}`;
                    payload.mode_state_template =
                        "{% set values = " +
                            `{'schedule':'auto','manual':'heat','pause':'off'} %}` +
                            `{% set value = value_json.operating_mode %}{{ values[value] if value in values.keys() else 'off' }}`;
                    payload.modes = ["off", "heat", "auto"];
                }
            },
        },
        exposes: [
            e
                .climate()
                .withLocalTemperature(ea.STATE_GET, "Temperature used by the heating algorithm. " +
                "This is the temperature measured on the device (by default) or the remote temperature (if set within the last 30 min).")
                .withLocalTemperatureCalibration(-5, 5, 0.1)
                .withSetpoint("occupied_heating_setpoint", 5, 30, 0.5)
                .withSystemMode(["heat"])
                .withRunningState(["idle", "heat"], ea.STATE_GET),
            e.pi_heating_demand().withAccess(ea.ALL),
        ],
        fromZigbee: [fz.thermostat],
        toZigbee: [
            tz.thermostat_system_mode,
            tz.thermostat_occupied_heating_setpoint,
            tz.thermostat_local_temperature_calibration,
            tz.thermostat_local_temperature,
            tz.thermostat_keypad_lockout,
        ],
        extend: [
            boschExtend.hvacThermostatCluster(),
            boschExtend.hvacUserInterfaceCfgCluster(),
            m.battery({
                percentage: true,
                lowStatus: false,
            }),
            boschExtend.operatingMode(),
            boschExtend.windowDetection(),
            boschExtend.boostHeating(),
            m.numeric({
                name: "remote_temperature",
                cluster: "hvacThermostat",
                attribute: "remoteTemperature",
                description: "Input for remote temperature sensor. Required at least every 30 min. to prevent fallback to internal sensor!",
                valueMin: 0.0,
                valueMax: 35.0,
                valueStep: 0.01,
                unit: "°C",
                scale: 100,
                zigbeeCommandOptions: manufacturerOptions,
            }),
            m.enumLookup({
                name: "setpoint_change_source",
                cluster: "hvacThermostat",
                attribute: "setpointChangeSource",
                reporting: { min: "10_SECONDS", max: "MAX", change: null },
                description: "Source of the current setpoint temperature",
                lookup: { manual: 0x00, schedule: 0x01, externally: 0x02 },
                access: "STATE_GET",
            }),
            boschExtend.childLock(),
            boschExtend.displayOntime(),
            boschExtend.displayBrightness(),
            m.enumLookup({
                name: "display_orientation",
                cluster: "hvacUserInterfaceCfg",
                attribute: "displayOrientation",
                description: "Sets orientation of the display",
                lookup: { normal: 0x00, flipped: 0x01 },
                zigbeeCommandOptions: manufacturerOptions,
            }),
            m.enumLookup({
                name: "displayed_temperature",
                cluster: "hvacUserInterfaceCfg",
                attribute: "displayedTemperature",
                description: "Temperature displayed on the TRV",
                lookup: { target: 0x00, measured: 0x01 },
                zigbeeCommandOptions: manufacturerOptions,
            }),
            m.enumLookup({
                name: "valve_adapt_status",
                cluster: "hvacThermostat",
                attribute: "valveAdaptStatus",
                reporting: { min: "10_SECONDS", max: "MAX", change: null },
                description: "Specifies the current status of the valve adaptation",
                lookup: {
                    none: 0x00,
                    ready_to_calibrate: 0x01,
                    calibration_in_progress: 0x02,
                    error: 0x03,
                    success: 0x04,
                },
                zigbeeCommandOptions: manufacturerOptions,
                access: "STATE_GET",
            }),
            boschExtend.valveAdaptProcess(),
            boschExtend.heatingDemand(),
            boschExtend.ignoreDst(),
            m.bindCluster({
                cluster: "genPollCtrl",
                clusterType: "input",
            }),
        ],
        ota: true,
        configure: async (device, coordinatorEndpoint) => {
            const endpoint = device.getEndpoint(1);
            await reporting.bind(endpoint, coordinatorEndpoint, ["hvacThermostat", "hvacUserInterfaceCfg"]);
            await reporting.thermostatTemperature(endpoint);
            await reporting.thermostatOccupiedHeatingSetpoint(endpoint, {
                min: constants.repInterval.SECONDS_10,
                max: constants.repInterval.HOUR,
                change: 50,
            });
            await reporting.thermostatKeypadLockMode(endpoint);
            await endpoint.configureReporting("hvacThermostat", [
                {
                    attribute: "heatingDemand",
                    minimumReportInterval: constants.repInterval.SECONDS_10,
                    maximumReportInterval: constants.repInterval.MAX,
                    reportableChange: null,
                },
            ], manufacturerOptions);
            await endpoint.read("genPowerCfg", ["batteryPercentageRemaining"]);
            await endpoint.read("hvacThermostat", ["localTemperatureCalibration", "setpointChangeSource"]);
            await endpoint.read("hvacThermostat", ["operatingMode", "heatingDemand", "valveAdaptStatus", "remoteTemperature", "windowDetection", "boostHeating"], manufacturerOptions);
            await endpoint.read("hvacUserInterfaceCfg", ["keypadLockout"]);
            await endpoint.read("hvacUserInterfaceCfg", ["displayOrientation", "displayedTemperature", "displayOntime", "displayBrightness"], manufacturerOptions);
        },
    },
    {
        zigbeeModel: ["RBSH-RTH0-BAT-ZB-EU"],
        model: "BTH-RM",
        vendor: "Bosch",
        description: "Room thermostat II (Battery model)",
        exposes: [
            e
                .climate()
                .withLocalTemperature()
                .withSetpoint("occupied_heating_setpoint", 4.5, 30, 0.5)
                .withSetpoint("occupied_cooling_setpoint", 4.5, 30, 0.5)
                .withLocalTemperatureCalibration(-5, 5, 0.1)
                .withSystemMode(["off", "heat", "cool"])
                .withRunningState(["idle", "heat", "cool"]),
        ],
        fromZigbee: [fz.thermostat, fz.hvac_user_interface],
        toZigbee: [
            tz.thermostat_system_mode,
            tz.thermostat_running_state,
            tz.thermostat_occupied_heating_setpoint,
            tz.thermostat_occupied_cooling_setpoint,
            tz.thermostat_programming_operation_mode, // NOTE: Only 0x0 & 0x1 supported
            tz.thermostat_local_temperature_calibration,
            tz.thermostat_local_temperature,
            tz.thermostat_temperature_setpoint_hold,
            tz.thermostat_temperature_display_mode,
        ],
        extend: [
            boschExtend.hvacThermostatCluster(),
            boschExtend.hvacUserInterfaceCfgCluster(),
            m.battery({
                voltageToPercentage: { min: 4400, max: 6400 },
                percentage: true,
                voltage: true,
                lowStatus: false,
                voltageReporting: true,
                percentageReporting: false,
            }),
            m.humidity(),
            boschExtend.operatingMode(),
            boschExtend.windowDetection(),
            boschExtend.boostHeating(),
            boschExtend.childLock(),
            boschExtend.displayOntime(),
            boschExtend.displayBrightness(),
            m.bindCluster({
                cluster: "genPollCtrl",
                clusterType: "input",
            }),
        ],
        ota: true,
        configure: async (device, coordinatorEndpoint) => {
            const endpoint = device.getEndpoint(1);
            await reporting.bind(endpoint, coordinatorEndpoint, ["hvacThermostat", "hvacUserInterfaceCfg"]);
            await reporting.thermostatSystemMode(endpoint);
            await reporting.thermostatRunningState(endpoint);
            await reporting.thermostatTemperature(endpoint);
            await reporting.thermostatOccupiedHeatingSetpoint(endpoint, {
                min: constants.repInterval.SECONDS_10,
                max: constants.repInterval.HOUR,
                change: 50,
            });
            await reporting.thermostatOccupiedCoolingSetpoint(endpoint, {
                min: constants.repInterval.SECONDS_10,
                max: constants.repInterval.HOUR,
                change: 50,
            });
            await reporting.thermostatKeypadLockMode(endpoint);
            await endpoint.read("genPowerCfg", ["batteryVoltage"]);
            await endpoint.read("hvacThermostat", ["localTemperatureCalibration"]);
            await endpoint.read("hvacThermostat", ["operatingMode", "windowDetection", "boostHeating"], manufacturerOptions);
            await endpoint.read("hvacUserInterfaceCfg", ["keypadLockout"]);
            await endpoint.read("hvacUserInterfaceCfg", ["displayOntime", "displayBrightness"], manufacturerOptions);
        },
    },
    {
        zigbeeModel: ["RBSH-RTH0-ZB-EU"],
        model: "BTH-RM230Z",
        vendor: "Bosch",
        description: "Room thermostat II 230V",
        exposes: [
            e
                .climate()
                .withLocalTemperature()
                .withSetpoint("occupied_heating_setpoint", 4.5, 30, 0.5)
                .withSetpoint("occupied_cooling_setpoint", 4.5, 30, 0.5)
                .withLocalTemperatureCalibration(-5, 5, 0.1)
                .withSystemMode(["off", "heat", "cool"])
                .withRunningState(["idle", "heat", "cool"]),
        ],
        fromZigbee: [fz.thermostat, fz.hvac_user_interface],
        toZigbee: [
            tz.thermostat_system_mode,
            tz.thermostat_running_state,
            tz.thermostat_occupied_heating_setpoint,
            tz.thermostat_occupied_cooling_setpoint,
            tz.thermostat_programming_operation_mode, // NOTE: Only 0x0 & 0x1 supported
            tz.thermostat_local_temperature_calibration,
            tz.thermostat_local_temperature,
            tz.thermostat_temperature_setpoint_hold,
            tz.thermostat_temperature_display_mode,
        ],
        extend: [
            boschExtend.hvacThermostatCluster(),
            boschExtend.hvacUserInterfaceCfgCluster(),
            m.humidity(),
            boschExtend.operatingMode(),
            boschExtend.windowDetection(),
            boschExtend.boostHeating(),
            boschExtend.childLock(),
            boschExtend.displayOntime(),
            boschExtend.displayBrightness(),
        ],
        ota: true,
        configure: async (device, coordinatorEndpoint) => {
            const endpoint = device.getEndpoint(1);
            await reporting.bind(endpoint, coordinatorEndpoint, ["hvacThermostat", "hvacUserInterfaceCfg"]);
            await reporting.thermostatSystemMode(endpoint);
            await reporting.thermostatRunningState(endpoint);
            await reporting.thermostatTemperature(endpoint);
            await reporting.thermostatOccupiedHeatingSetpoint(endpoint, {
                min: constants.repInterval.SECONDS_10,
                max: constants.repInterval.HOUR,
                change: 50,
            });
            await reporting.thermostatOccupiedCoolingSetpoint(endpoint, {
                min: constants.repInterval.SECONDS_10,
                max: constants.repInterval.HOUR,
                change: 50,
            });
            await reporting.thermostatKeypadLockMode(endpoint);
            await endpoint.read("hvacThermostat", ["localTemperatureCalibration"]);
            await endpoint.read("hvacThermostat", ["operatingMode", "windowDetection", "boostHeating"], manufacturerOptions);
            await endpoint.read("hvacUserInterfaceCfg", ["keypadLockout"]);
            await endpoint.read("hvacUserInterfaceCfg", ["displayOntime", "displayBrightness"], manufacturerOptions);
        },
    },
    {
        zigbeeModel: ["Champion"],
        model: "8750001213",
        vendor: "Bosch",
        description: "Twinguard",
        extend: [
            m.deviceAddCustomCluster("twinguardSmokeDetector", {
                ID: 0xe000,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
                attributes: {
                    sensitivity: { ID: 0x4003, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
                },
                commands: {
                    initiateTestMode: {
                        ID: 0x00,
                        parameters: [],
                    },
                },
                commandsResponse: {},
            }),
            m.deviceAddCustomCluster("twinguardMeasurements", {
                ID: 0xe002,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
                attributes: {
                    humidity: { ID: 0x4000, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
                    unknown1: { ID: 0x4001, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
                    unknown2: { ID: 0x4002, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
                    airpurity: { ID: 0x4003, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
                    temperature: { ID: 0x4004, type: zigbee_herdsman_1.Zcl.DataType.INT16 },
                    illuminance: { ID: 0x4005, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
                    battery: { ID: 0x4006, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
                    unknown3: { ID: 0x4007, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
                    unknown4: { ID: 0x4008, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
                    pressure: { ID: 0x4009, type: zigbee_herdsman_1.Zcl.DataType.UINT16 }, // Not yet confirmed
                    unknown6: { ID: 0x400a, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
                    unknown7: { ID: 0x400b, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
                    unknown8: { ID: 0x400c, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
                },
                commands: {},
                commandsResponse: {},
            }),
            m.deviceAddCustomCluster("twinguardOptions", {
                ID: 0xe004,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
                attributes: {
                    unknown1: { ID: 0x4000, type: zigbee_herdsman_1.Zcl.DataType.BITMAP8 }, // 0,1 ??? read during pairing
                    pre_alarm: { ID: 0x4001, type: zigbee_herdsman_1.Zcl.DataType.BITMAP8 }, // 0,1 on/off
                },
                commands: {},
                commandsResponse: {},
            }),
            m.deviceAddCustomCluster("twinguardSetup", {
                ID: 0xe006,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
                attributes: {
                    unknown1: { ID: 0x5003, type: zigbee_herdsman_1.Zcl.DataType.INT8 }, // perhaps signal strength? -7?
                    unknown2: { ID: 0x5004, type: zigbee_herdsman_1.Zcl.DataType.UINT8 }, // ????
                    heartbeat: { ID: 0x5005, type: zigbee_herdsman_1.Zcl.DataType.BITMAP8 }, // 0
                },
                commands: {
                    pairingCompleted: {
                        ID: 0x01,
                        parameters: [],
                    },
                },
                commandsResponse: {},
            }),
            m.deviceAddCustomCluster("twinguardAlarm", {
                ID: 0xe007,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
                attributes: {
                    alarm_status: { ID: 0x5000, type: zigbee_herdsman_1.Zcl.DataType.BITMAP32 },
                },
                commands: {
                    burglarAlarm: {
                        ID: 0x01,
                        parameters: [
                            { name: "data", type: zigbee_herdsman_1.Zcl.DataType.UINT8 }, // data:1 trips the siren data:0 should stop the siren
                        ],
                    },
                },
                commandsResponse: {},
            }),
            boschExtend.twinguard(),
        ],
        configure: async (device, coordinatorEndpoint) => {
            await reporting.bind(device.getEndpoint(7), coordinatorEndpoint, ["genPollCtrl"]);
            await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genAlarms", "twinguardSmokeDetector", "twinguardOptions"]);
            await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ["twinguardMeasurements"]);
            await reporting.bind(device.getEndpoint(12), coordinatorEndpoint, ["twinguardSetup", "twinguardAlarm"]);
            await device.getEndpoint(1).read("twinguardOptions", ["unknown1"], manufacturerOptions); // Needed for pairing
            await device.getEndpoint(12).command("twinguardSetup", "pairingCompleted", manufacturerOptions); // Needed for pairing
            await device.getEndpoint(1).write("twinguardSmokeDetector", { sensitivity: 0x0002 }, manufacturerOptions); // Setting defaults
            await device.getEndpoint(1).write("twinguardOptions", { pre_alarm: 0x01 }, manufacturerOptions); // Setting defaults
            await device.getEndpoint(12).write("twinguardSetup", { heartbeat: 0x01 }, manufacturerOptions); // Setting defaults
            await device.getEndpoint(1).read("twinguardSmokeDetector", ["sensitivity"], manufacturerOptions);
            await device.getEndpoint(1).read("twinguardOptions", ["pre_alarm"], manufacturerOptions);
            await device.getEndpoint(12).read("twinguardSetup", ["heartbeat"], manufacturerOptions);
        },
    },
    {
        zigbeeModel: ["RFPR-ZB-SH-EU"],
        model: "RFPR-ZB-SH-EU",
        vendor: "Bosch",
        description: "Wireless motion detector",
        fromZigbee: [fz.temperature, fz.battery, fz.ias_occupancy_alarm_1],
        toZigbee: [],
        meta: { battery: { voltageToPercentage: { min: 2500, max: 3000 } } },
        configure: async (device, coordinatorEndpoint) => {
            const endpoint = device.getEndpoint(1);
            await reporting.bind(endpoint, coordinatorEndpoint, ["msTemperatureMeasurement", "genPowerCfg"]);
            await reporting.temperature(endpoint);
            await reporting.batteryVoltage(endpoint);
        },
        exposes: [e.temperature(), e.battery(), e.occupancy(), e.battery_low(), e.tamper()],
    },
    {
        zigbeeModel: ["RBSH-SP-ZB-EU", "RBSH-SP-ZB-FR", "RBSH-SP-ZB-GB"],
        model: "BSP-FZ2",
        vendor: "Bosch",
        description: "Plug compact EU",
        extend: [m.onOff(), m.electricityMeter({ voltage: false, current: false })],
        ota: true,
        whiteLabel: [
            { vendor: "Bosch", model: "BSP-EZ2", description: "Plug compact FR", fingerprint: [{ modelID: "RBSH-SP-ZB-FR" }] },
            { vendor: "Bosch", model: "BSP-GZ2", description: "Plug compact UK", fingerprint: [{ modelID: "RBSH-SP-ZB-GB" }] },
        ],
    },
    {
        zigbeeModel: ["RBSH-SWD-ZB", "RBSH-SWD2-ZB"],
        model: "BSEN-C2",
        vendor: "Bosch",
        description: "Door/window contact II",
        extend: [
            boschExtend.doorWindowContact(false),
            m.battery({
                percentage: true,
                lowStatus: true,
            }),
            m.bindCluster({
                cluster: "genPollCtrl",
                clusterType: "input",
            }),
        ],
        configure: async (device, coordinatorEndpoint) => {
            const endpoint = device.getEndpoint(1);
            await endpoint.read("genPowerCfg", ["batteryPercentageRemaining"]);
            await endpoint.read("ssIasZone", ["zoneStatus"]);
        },
    },
    {
        zigbeeModel: ["RBSH-SWDV-ZB"],
        model: "BSEN-CV",
        vendor: "Bosch",
        description: "Door/window contact II plus",
        extend: [
            boschExtend.doorWindowContact(true),
            m.battery({
                percentage: true,
                lowStatus: true,
            }),
            m.bindCluster({
                cluster: "genPollCtrl",
                clusterType: "input",
            }),
        ],
        configure: async (device, coordinatorEndpoint) => {
            const endpoint = device.getEndpoint(1);
            await endpoint.read("genPowerCfg", ["batteryPercentageRemaining"]);
            await endpoint.read("ssIasZone", ["zoneStatus"]);
        },
    },
    {
        zigbeeModel: ["RBSH-MMD-ZB-EU"],
        model: "BMCT-DZ",
        vendor: "Bosch",
        description: "Phase-cut dimmer",
        extend: [m.identify(), m.light({ configureReporting: true, effect: false })],
    },
    {
        zigbeeModel: ["RBSH-MMR-ZB-EU"],
        model: "BMCT-RZ",
        vendor: "Bosch",
        description: "Relay, potential free",
        extend: [m.onOff({ powerOnBehavior: false })],
    },
    {
        zigbeeModel: ["RBSH-MMS-ZB-EU"],
        model: "BMCT-SLZ",
        vendor: "Bosch",
        description: "Light/shutter control unit II",
        extend: [
            m.deviceEndpoints({ endpoints: { left: 2, right: 3 } }),
            m.deviceAddCustomCluster("boschSpecific", {
                ID: 0xfca0,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
                attributes: {
                    deviceMode: { ID: 0x0000, type: zigbee_herdsman_1.Zcl.DataType.ENUM8 },
                    switchType: { ID: 0x0001, type: zigbee_herdsman_1.Zcl.DataType.ENUM8 },
                    calibrationOpeningTime: { ID: 0x0002, type: zigbee_herdsman_1.Zcl.DataType.UINT32 },
                    calibrationClosingTime: { ID: 0x0003, type: zigbee_herdsman_1.Zcl.DataType.UINT32 },
                    calibrationButtonHoldTime: { ID: 0x0005, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
                    childLock: { ID: 0x0008, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN },
                    calibrationMotorStartDelay: { ID: 0x000f, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
                    motorState: { ID: 0x0013, type: zigbee_herdsman_1.Zcl.DataType.ENUM8 },
                },
                commands: {},
                commandsResponse: {},
            }),
            boschExtend.bmct(),
        ],
        ota: true,
        configure: async (device, coordinatorEndpoint) => {
            const endpoint1 = device.getEndpoint(1);
            await reporting.bind(endpoint1, coordinatorEndpoint, ["genIdentify", "closuresWindowCovering", "boschSpecific"]);
            await reporting.currentPositionLiftPercentage(endpoint1);
            await endpoint1.read("boschSpecific", [
                "deviceMode",
                "switchType",
                "motorState",
                "childLock",
                "calibrationOpeningTime",
                "calibrationClosingTime",
                "calibrationButtonHoldTime",
                "calibrationMotorStartDelay",
            ]);
            const endpoint2 = device.getEndpoint(2);
            await endpoint2.read("boschSpecific", ["childLock"]);
            await reporting.bind(endpoint2, coordinatorEndpoint, ["genIdentify", "genOnOff"]);
            await reporting.onOff(endpoint2);
            const endpoint3 = device.getEndpoint(3);
            await endpoint3.read("boschSpecific", ["childLock"]);
            await reporting.bind(endpoint3, coordinatorEndpoint, ["genIdentify", "genOnOff"]);
            await reporting.onOff(endpoint3);
        },
        exposes: (device, options) => {
            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 commonExposes = [
                e.enum("switch_type", ea.ALL, Object.keys(stateSwitchType)).withDescription("Module controlled by a rocker switch or a button"),
            ];
            const lightExposes = [
                e.switch().withEndpoint("left"),
                e.switch().withEndpoint("right"),
                e.power_on_behavior().withEndpoint("left"),
                e.power_on_behavior().withEndpoint("right"),
                e.binary("child_lock", ea.ALL, "ON", "OFF").withEndpoint("left").withDescription("Enable/Disable child lock"),
                e.binary("child_lock", ea.ALL, "ON", "OFF").withEndpoint("right").withDescription("Enable/Disable child lock"),
            ];
            const coverExposes = [
                e.cover_position(),
                e.enum("motor_state", ea.STATE, Object.keys(stateMotor)).withDescription("Current shutter motor state"),
                e.binary("child_lock", ea.ALL, "ON", "OFF").withDescription("Enable/Disable child lock"),
                e
                    .numeric("calibration_closing_time", ea.ALL)
                    .withUnit("s")
                    .withDescription("Calibrate shutter closing time")
                    .withValueMin(1)
                    .withValueMax(90)
                    .withValueStep(0.1),
                e
                    .numeric("calibration_opening_time", ea.ALL)
                    .withUnit("s")
                    .withDescription("Calibrate shutter opening time")
                    .withValueMin(1)
                    .withValueMax(90)
                    .withValueStep(0.1),
                e
                    .numeric("calibration_button_hold_time", ea.ALL)
                    .withUnit("s")
                    .withDescription("Time to hold for long press")
                    .withValueMin(0.1)
                    .withValueMax(2)
                    .withValueStep(0.1),
                e
                    .numeric("calibration_motor_start_delay", ea.ALL)
                    .withUnit("s")
                    .withDescription("Delay between command and motor start")
                    .withValueMin(0)
                    .withValueMax(20)
                    .withValueStep(0.1),
            ];
            if (device) {
                const deviceModeKey = device.getEndpoint(1).getClusterAttributeValue("boschSpecific", "deviceMode");
                const deviceMode = Object.keys(stateDeviceMode).find((key) => stateDeviceMode[key] === deviceModeKey);
                if (deviceMode === "light") {
                    return [...commonExposes, ...lightExposes];
                }
                if (deviceMode === "shutter") {
                    return [...commonExposes, ...coverExposes];
                }
            }
            return [e.enum("device_mode", ea.ALL, Object.keys(stateDeviceMode)).withDescription("Device mode")];
        },
    },
    {
        zigbeeModel: ["RBSH-US4BTN-ZB-EU"],
        model: "BHI-US",
        vendor: "Bosch",
        description: "Universal Switch II",
        fromZigbee: [fzLocal.bhius_button_press, fzLocal.bhius_config, fz.battery],
        toZigbee: [tzLocal.bhius_config],
        exposes: [
            e.battery_low(),
            e.battery_voltage(),
            e
                .text("config_led_top_left_press", ea.ALL)
                .withLabel("LED config (top left short press)")
                .withDescription(labelShortPress)
                .withCategory("config"),
            e
                .text("config_led_top_right_press", ea.ALL)
                .withLabel("LED config (top right short press)")
                .withDescription(labelShortPress)
                .withCategory("config"),
            e
                .text("config_led_bottom_left_press", ea.ALL)
                .withLabel("LED config (bottom left short press)")
                .withDescription(labelShortPress)
                .withCategory("config"),
            e
                .text("config_led_bottom_right_press", ea.ALL)
                .withLabel("LED config (bottom right short press)")
                .withDescription(labelShortPress)
                .withCategory("config"),
            e
                .text("config_led_top_left_longpress", ea.ALL)
                .withLabel("LED config (top left long press)")
                .withDescription(labelLongPress)
                .withCategory("config"),
            e
                .text("config_led_top_right_longpress", ea.ALL)
                .withLabel("LED config (top right long press)")
                .withDescription(labelLongPress)
                .withCategory("config"),
            e
                .text("config_led_bottom_left_longpress", ea.ALL)
                .withLabel("LED config (bottom left long press)")
                .withDescription(labelLongPress)
                .withCategory("config"),
            e
                .text("config_led_bottom_right_longpress", ea.ALL)
                .withLabel("LED config (bottom right long press)")
                .withDescription(labelLongPress)
                .withCategory("config"),
            e.action([
                "button_top_left_release",
                "button_top_right_release",
                "button_bottom_left_release",
                "button_bottom_right_release",
                "button_top_left_longpress",
                "button_top_right_longpress",
                "button_bottom_left_longpress",
                "button_bottom_right_longpress",
                "button_top_left_longpress_release",
                "button_top_right_longpress_release",
                "button_bottom_left_longpress_release",
                "button_bottom_right_longpress_release",
            ]),
        ],
        extend: [
            m.deviceAddCustomCluster("boschSpecific", {
                ID: 0xfca1,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
                attributes: {},
                commands: {
                    confirmButtonPressed: {
                        ID: 0x0010,
                        parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.BuffaloZclDataType.BUFFER }],
                    },
                    pairingCompleted: {
                        ID: 0x0012,
                        parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.BuffaloZclDataType.BUFFER }],
                    },
                },
                commandsResponse: {},
            }),
        ],
        configure: async (device, coordinatorEndpoint) => {
            const endpoint = device.getEndpoint(1);
            // Read default LED configuration
            await endpoint
                .read("boschSpecific", [0x0010, 0x0011, 0x0012, 0x0013], { ...manufacturerOptions, sendPolicy: "immediate" })
                .catch((error) => { });
            await endpoint
                .read("boschSpecific", [0x0020, 0x0021, 0x0022, 0x0023], { ...manufacturerOptions, sendPolicy: "immediate" })
                .catch((error) => { });
            // We also have to read this one. Value reads 0x0f, looks like a bitmap
            await endpoint.read("boschSpecific", [0x0024], { ...manufacturerOptions, sendPolicy: "immediate" });
            await endpoint.command("boschSpecific", "pairingCompleted", { data: Buffer.from([0x00]) }, { sendPolicy: "immediate" });
            await reporting.bind(endpoint, coordinatorEndpoint, ["genPowerCfg", "genBasic", "boschSpecific"]);
            await reporting.batteryPercentageRemaining(endpoint);
            await reporting.batteryVoltage(endpoint);
        },
    },
];
//# sourceMappingURL=bosch.js.map