UNPKG

zigbee-herdsman-converters

Version:

Collection of device converters to be used with zigbee-herdsman

1,408 lines • 73.2 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 bosch_1 = require("../lib/bosch");
const constants_1 = 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 reporting_1 = 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";
// 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 boschBmctRzSettings = {
    deviceModes: {
        switch: 0x00,
        pulsed: 0x01,
    },
    switchTypes: {
        button: 0x05,
        rocker_switch: 0x07,
        none: 0x00,
    },
    switchModes: {
        coupled: 0x00,
        decoupled: 0x01,
    },
    hasDualSwitchInputs: false,
};
const boschBmctDzSettings = {
    switchTypes: {
        button: 0x05,
        none: 0x00,
    },
    switchModes: {
        coupled: 0x00,
        decoupled: 0x01,
    },
    hasDualSwitchInputs: false,
};
const boschExtend = {
    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, "ssIasZone", "boschSmokeAlarmSiren", { data: index }, bosch_1.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("Temperature"),
            e.numeric("humidity", ea.STATE).withValueMin(0).withValueMax(100).withValueStep(0.1).withUnit("%").withDescription("Relative humidity"),
            e
                .numeric("eco2", ea.STATE)
                .withValueMin(500)
                .withValueMax(5500)
                .withValueStep(1)
                .withLabel("eCOâ‚‚")
                .withUnit("ppm")
                .withDescription("TVOC-derived COâ‚‚-equivalent"),
            e.numeric("aqi", ea.STATE).withValueMin(0).withValueMax(500).withValueStep(1).withLabel("IAQ").withDescription("Index for Air Quality"),
            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;
                        const factorCo2 = 10;
                        result.eco2 = iaq * factorCo2 + 500;
                    }
                    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",
                    };
                    if ("alarmcode" in msg.data) {
                        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 }, bosch_1.manufacturerOptions);
                        return { state: { sensitivity: value } };
                    }
                    if (key === "pre_alarm") {
                        const index = utils.getFromLookup(value, stateOffOn);
                        await entity.write("twinguardOptions", { pre_alarm: index }, bosch_1.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 }, bosch_1.manufacturerOptions);
                        return { state: { heartbeat: value } };
                    }
                    if (key === "self_test") {
                        if (value) {
                            await entity.command("twinguardSmokeDetector", "initiateTestMode", {}, bosch_1.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 }, bosch_1.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 }, bosch_1.manufacturerOptions);
                        }
                    }
                },
                convertGet: async (entity, key, meta) => {
                    switch (key) {
                        case "sensitivity":
                            await entity.read("twinguardSmokeDetector", ["sensitivity"], bosch_1.manufacturerOptions);
                            break;
                        case "pre_alarm":
                            await entity.read("twinguardOptions", ["pre_alarm"], bosch_1.manufacturerOptions);
                            break;
                        case "heartbeat":
                            await meta.device
                                .getEndpoint(12)
                                .read("twinguardSetup", ["heartbeat"], bosch_1.manufacturerOptions);
                            break;
                        case "alarm":
                        case "self_test":
                            await meta.device
                                .getEndpoint(12)
                                .read("twinguardAlarm", ["alarm_status"], bosch_1.manufacturerOptions);
                            break;
                        default:
                            throw new Error(`Unhandled key boschExtend.twinguard.toZigbee.convertGet ${key}`);
                    }
                },
            },
        ];
        return {
            exposes,
            fromZigbee,
            toZigbee,
            isModernExtend: true,
        };
    },
};
const tzLocal = {
    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]], bosch_1.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 = {
                [buttonMap[key]]: { value: buffer, type: 65 },
            };
            await entity.write("boschSpecific", payload, bosch_1.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: (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: (model, msg, publish, options, meta) => {
            const result = {};
            for (const id of Object.values(buttonMap)) {
                if (msg.data[id] !== undefined) {
                    // TODO: type is assumed "Buffer" since using `toString("hex")`
                    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",
        extend: [
            bosch_1.boschBsirExtend.customPowerCfgCluster(),
            bosch_1.boschBsirExtend.customIasZoneCluster(),
            bosch_1.boschBsirExtend.customIasWdCluster(),
            bosch_1.boschBsirExtend.deviceState(),
            bosch_1.boschBsirExtend.alarmControl(),
            bosch_1.boschBsirExtend.iasZoneStatus(),
            bosch_1.boschBsirExtend.alarmMode(),
            bosch_1.boschBsirExtend.sirenVolume(),
            bosch_1.boschBsirExtend.sirenDuration(),
            bosch_1.boschBsirExtend.lightDuration(),
            bosch_1.boschBsirExtend.sirenDelay(),
            bosch_1.boschBsirExtend.lightDelay(),
            bosch_1.boschBsirExtend.primaryPowerSource(),
            bosch_1.boschBsirExtend.currentPowerSource(),
            bosch_1.boschBsirExtend.solarPanelVoltage(),
            bosch_1.boschGeneralExtend.batteryWithPercentageAndLowStatus({
                percentageReportingConfig: { min: "MIN", max: "MAX", change: 1 },
            }),
        ],
        ota: true,
    },
    {
        zigbeeModel: ["RBSH-WS-ZB-EU"],
        model: "BSEN-W",
        vendor: "Bosch",
        description: "Water alarm (formerly known as BWA-1)",
        extend: [
            bosch_1.boschWaterAlarmExtend.changedSensitivityLevel(),
            bosch_1.boschWaterAlarmExtend.waterAlarmCluster(),
            bosch_1.boschGeneralExtend.handleRenamedCustomCluster("boschSpecific", "boschWaterAlarm"),
            bosch_1.boschWaterAlarmExtend.waterAndTamperAlarm(),
            bosch_1.boschWaterAlarmExtend.muteAlarmControl(),
            bosch_1.boschWaterAlarmExtend.alarmOnMotion(),
            bosch_1.boschWaterAlarmExtend.testMode(),
            bosch_1.boschGeneralExtend.batteryWithPercentageAndLowStatus(),
        ],
        ota: true,
    },
    {
        zigbeeModel: ["RBSH-SD-ZB-EU"],
        model: "BSD-2",
        vendor: "Bosch",
        description: "Smoke alarm II",
        extend: [
            bosch_1.boschSmokeAlarmExtend.enforceDefaultSensitivityLevel(),
            bosch_1.boschSmokeAlarmExtend.customIasZoneCluster(),
            bosch_1.boschSmokeAlarmExtend.smokeAlarmAndButtonPushes(),
            bosch_1.boschSmokeAlarmExtend.alarmControl(),
            bosch_1.boschSmokeAlarmExtend.testMode(),
            bosch_1.boschSmokeAlarmExtend.battery(),
        ],
    },
    {
        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"];
                }
            },
        },
        extend: [
            bosch_1.boschThermostatExtend.customThermostatCluster(),
            bosch_1.boschThermostatExtend.customUserInterfaceCfgCluster(),
            bosch_1.boschThermostatExtend.raThermostat(),
            bosch_1.boschThermostatExtend.setpointChangeSource({ enableReporting: true }),
            bosch_1.boschThermostatExtend.operatingMode({ enableReporting: true }),
            bosch_1.boschThermostatExtend.windowOpenMode({ enableReporting: true }),
            bosch_1.boschThermostatExtend.boostHeating({ enableReporting: true }),
            bosch_1.boschThermostatExtend.remoteTemperature(),
            bosch_1.boschThermostatExtend.childLock(),
            bosch_1.boschThermostatExtend.displayBrightness(),
            bosch_1.boschThermostatExtend.displaySwitchOnDuration(),
            bosch_1.boschThermostatExtend.displayOrientation(),
            bosch_1.boschThermostatExtend.displayedTemperature(),
            bosch_1.boschThermostatExtend.valveAdaptation(),
            bosch_1.boschThermostatExtend.errorState({ enableReporting: true }),
            bosch_1.boschGeneralExtend.batteryWithPercentageAndLowStatus(),
        ],
        ota: true,
    },
    {
        zigbeeModel: ["RBSH-RTH0-BAT-ZB-EU"],
        model: "BTH-RM",
        vendor: "Bosch",
        description: "Room thermostat II",
        meta: {
            overrideHaDiscoveryPayload: (payload) => {
                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','cool':'manual','off':'pause'} %}` +
                            `{% if value == "heat" or value == "cool" %}` +
                            `{"operating_mode": "manual", "system_mode": "{{ value }}"}` +
                            "{% else %}" +
                            `{"operating_mode": "{{ values[value] if value in values.keys() else 'pause' }}"}` +
                            "{% endif %}";
                    payload.mode_state_template =
                        "{% set values = " +
                            `{'schedule':'auto','manual':'heat','pause':'off'} %}` +
                            "{% set value = value_json.operating_mode %}" +
                            `{% if value == "manual" %}` +
                            "{{ value_json.system_mode }}" +
                            "{% else %}" +
                            `{{ values[value] if value in values.keys() else 'off' }}` +
                            "{% endif %}";
                    payload.modes = ["off", "heat", "cool", "auto"];
                }
            },
        },
        extend: [
            bosch_1.boschGeneralExtend.handleZclVersionReadRequest(),
            bosch_1.boschThermostatExtend.customThermostatCluster(),
            bosch_1.boschThermostatExtend.customUserInterfaceCfgCluster(),
            bosch_1.boschThermostatExtend.operatingMode({ enableReporting: true }),
            bosch_1.boschThermostatExtend.rmThermostat(),
            bosch_1.boschThermostatExtend.setpointChangeSource({ enableReporting: true }),
            bosch_1.boschThermostatExtend.humidity(),
            bosch_1.boschThermostatExtend.cableSensorMode(),
            bosch_1.boschThermostatExtend.cableSensorTemperature(),
            bosch_1.boschThermostatExtend.windowOpenMode(),
            bosch_1.boschThermostatExtend.boostHeating(),
            bosch_1.boschThermostatExtend.childLock(),
            bosch_1.boschThermostatExtend.displayBrightness(),
            bosch_1.boschThermostatExtend.displaySwitchOnDuration(),
            bosch_1.boschThermostatExtend.activityLedState(),
            bosch_1.boschThermostatExtend.errorState({ enableReporting: true }),
            bosch_1.boschThermostatExtend.rmBattery(),
        ],
        ota: true,
    },
    {
        zigbeeModel: ["RBSH-RTH0-ZB-EU"],
        model: "BTH-RM230Z",
        vendor: "Bosch",
        description: "Room thermostat II 230V",
        options: [exposes.options.homeassistant_climate_modes()],
        meta: {
            overrideHaDiscoveryPayload: (payload, options) => {
                if (payload.mode_command_topic?.endsWith("/system_mode")) {
                    const climateModes = options?.homeassistant_climate_modes;
                    const activeModes = climateModes === "cool" ? ["cool"] : climateModes === "heat_cool" ? ["heat", "cool"] : ["heat"];
                    const fallbackMode = activeModes[0];
                    const activeModesTemplate = `[${activeModes.map((mode) => `'${mode}'`).join(",")}]`;
                    payload.mode_command_topic = payload.mode_command_topic.substring(0, payload.mode_command_topic.lastIndexOf("/system_mode"));
                    payload.mode_command_template =
                        `{% set active_modes = ${activeModesTemplate} %}` +
                            `{% set values = {'auto':'schedule','off':'pause'} %}` +
                            "{% if value in active_modes %}" +
                            `{"operating_mode": "manual", "system_mode": "{{ value }}"}` +
                            "{% else %}" +
                            `{"operating_mode": "{{ values[value] if value in values.keys() else 'pause' }}"}` +
                            "{% endif %}";
                    payload.mode_state_template =
                        `{% set active_modes = ${activeModesTemplate} %}` +
                            `{% set fallback_mode = '${fallbackMode}' %}` +
                            "{% set values = {'schedule':'auto','pause':'off'} %}" +
                            "{% set value = value_json.operating_mode %}" +
                            "{% set mode = value_json.system_mode %}" +
                            "{% if value == 'manual' %}" +
                            "{{ mode if mode in active_modes else fallback_mode }}" +
                            "{% else %}" +
                            `{{ values[value] if value in values.keys() else 'off' }}` +
                            "{% endif %}";
                    payload.modes = ["off", ...activeModes, "auto"];
                }
            },
        },
        extend: [
            bosch_1.boschGeneralExtend.handleZclVersionReadRequest(),
            bosch_1.boschThermostatExtend.customThermostatCluster(),
            bosch_1.boschThermostatExtend.customUserInterfaceCfgCluster(),
            bosch_1.boschThermostatExtend.relayState(),
            bosch_1.boschThermostatExtend.operatingMode({ enableReporting: true }),
            bosch_1.boschThermostatExtend.rmThermostat({ weeklySchedule: true }),
            bosch_1.boschThermostatExtend.setpointChangeSource({ enableReporting: true }),
            bosch_1.boschThermostatExtend.humidity(),
            bosch_1.boschThermostatExtend.heaterType(),
            bosch_1.boschThermostatExtend.valveType(),
            bosch_1.boschThermostatExtend.cableSensorMode(),
            bosch_1.boschThermostatExtend.cableSensorTemperature(),
            bosch_1.boschThermostatExtend.windowOpenMode(),
            bosch_1.boschThermostatExtend.boostHeating(),
            bosch_1.boschThermostatExtend.childLock(),
            bosch_1.boschThermostatExtend.displayBrightness(),
            bosch_1.boschThermostatExtend.displaySwitchOnDuration(),
            bosch_1.boschThermostatExtend.activityLedState(),
            bosch_1.boschThermostatExtend.errorState({ enableReporting: true }),
        ],
        ota: true,
    },
    {
        zigbeeModel: ["Champion"],
        model: "8750001213",
        vendor: "Bosch",
        description: "Twinguard",
        extend: [
            m.deviceAddCustomCluster("twinguardSmokeDetector", {
                name: "twinguardSmokeDetector",
                ID: 0xe000,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
                attributes: {
                    sensitivity: { name: "sensitivity", ID: 0x4003, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff },
                },
                commands: {
                    initiateTestMode: {
                        name: "initiateTestMode",
                        ID: 0x00,
                        parameters: [],
                    },
                },
                commandsResponse: {},
            }),
            m.deviceAddCustomCluster("twinguardMeasurements", {
                name: "twinguardMeasurements",
                ID: 0xe002,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
                attributes: {
                    humidity: { name: "humidity", ID: 0x4000, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff },
                    unknown1: { name: "unknown1", ID: 0x4001, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff },
                    unknown2: { name: "unknown2", ID: 0x4002, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff },
                    airpurity: { name: "airpurity", ID: 0x4003, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff },
                    temperature: { name: "temperature", ID: 0x4004, type: zigbee_herdsman_1.Zcl.DataType.INT16, write: true, min: -32768 },
                    illuminance: { name: "illuminance", ID: 0x4005, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff },
                    battery: { name: "battery", ID: 0x4006, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff },
                    unknown3: { name: "unknown3", ID: 0x4007, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff },
                    unknown4: { name: "unknown4", ID: 0x4008, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff },
                    pressure: { name: "pressure", ID: 0x4009, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff }, // Not yet confirmed
                    unknown6: { name: "unknown6", ID: 0x400a, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff },
                    unknown7: { name: "unknown7", ID: 0x400b, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff },
                    unknown8: { name: "unknown8", ID: 0x400c, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff },
                },
                commands: {},
                commandsResponse: {},
            }),
            m.deviceAddCustomCluster("twinguardOptions", {
                name: "twinguardOptions",
                ID: 0xe004,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
                attributes: {
                    unknown1: { name: "unknown1", ID: 0x4000, type: zigbee_herdsman_1.Zcl.DataType.BITMAP8, write: true }, // 0,1 ??? read during pairing
                    pre_alarm: { name: "pre_alarm", ID: 0x4001, type: zigbee_herdsman_1.Zcl.DataType.BITMAP8, write: true }, // 0,1 on/off
                },
                commands: {},
                commandsResponse: {},
            }),
            m.deviceAddCustomCluster("twinguardSetup", {
                name: "twinguardSetup",
                ID: 0xe006,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
                attributes: {
                    unknown1: { name: "unknown1", ID: 0x5003, type: zigbee_herdsman_1.Zcl.DataType.INT8, write: true, min: -128 }, // perhaps signal strength? -7?
                    unknown2: { name: "unknown2", ID: 0x5004, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true, max: 0xff }, // ????
                    heartbeat: { name: "heartbeat", ID: 0x5005, type: zigbee_herdsman_1.Zcl.DataType.BITMAP8, write: true }, // 0
                },
                commands: {
                    pairingCompleted: {
                        name: "pairingCompleted",
                        ID: 0x01,
                        parameters: [],
                    },
                },
                commandsResponse: {},
            }),
            m.deviceAddCustomCluster("twinguardAlarm", {
                name: "twinguardAlarm",
                ID: 0xe007,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
                attributes: {
                    alarm_status: { name: "alarm_status", ID: 0x5000, type: zigbee_herdsman_1.Zcl.DataType.BITMAP32, write: true },
                },
                commands: {
                    burglarAlarm: {
                        name: "burglarAlarm",
                        ID: 0x01,
                        parameters: [
                            { name: "data", type: zigbee_herdsman_1.Zcl.DataType.UINT8, max: 0xff }, // 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"], bosch_1.manufacturerOptions); // Needed for pairing
            await device
                .getEndpoint(12)
                .command("twinguardSetup", "pairingCompleted", {}, bosch_1.manufacturerOptions); // Needed for pairing
            await device
                .getEndpoint(1)
                .write("twinguardSmokeDetector", { sensitivity: 0x0002 }, bosch_1.manufacturerOptions); // Setting defaults
            await device.getEndpoint(1).write("twinguardOptions", { pre_alarm: 0x01 }, bosch_1.manufacturerOptions); // Setting defaults
            await device.getEndpoint(12).write("twinguardSetup", { heartbeat: 0x01 }, bosch_1.manufacturerOptions); // Setting defaults
            await device
                .getEndpoint(1)
                .read("twinguardSmokeDetector", ["sensitivity"], bosch_1.manufacturerOptions);
            await device.getEndpoint(1).read("twinguardOptions", ["pre_alarm"], bosch_1.manufacturerOptions);
            await device.getEndpoint(12).read("twinguardSetup", ["heartbeat"], bosch_1.manufacturerOptions);
        },
    },
    {
        zigbeeModel: ["RFPR-ZB-SH-EU"],
        model: "BSEN-M",
        vendor: "Bosch",
        description: "Motion detector",
        extend: [
            bosch_1.boschBsenExtend.changedCheckinInterval(),
            bosch_1.boschBsenExtend.tamperAndOccupancyAlarm(),
            bosch_1.boschBsenExtend.battery(),
            bosch_1.boschBsenExtend.sensitivityLevel(),
            bosch_1.boschBsenExtend.testMode(),
            bosch_1.boschBsenExtend.illuminance(),
            bosch_1.boschBsenExtend.temperature(),
        ],
    },
    {
        zigbeeModel: ["RBSH-SP-ZB-EU", "RBSH-SP-ZB-FR", "RBSH-SP-ZB-GB"],
        model: "BSP-FZ2",
        vendor: "Bosch",
        description: "Smart plug compact (type F plug)",
        extend: [
            bosch_1.boschGeneralEnergyDeviceExtend.customMeteringCluster(),
            bosch_1.boschSmartPlugExtend.smartPlugCluster(),
            bosch_1.boschGeneralExtend.handleRenamedCustomCluster("boschSpecific", "boschEnergyDevice"),
            bosch_1.boschSmartPlugExtend.onOff(),
            bosch_1.boschGeneralEnergyDeviceExtend.autoOff(),
            bosch_1.boschSmartPlugExtend.electricityMeter(),
            bosch_1.boschGeneralEnergyDeviceExtend.resetEnergyMeters(),
        ],
        version: "0.0.1",
        ota: true,
        whiteLabel: [
            { vendor: "Bosch", model: "BSP-EZ2", description: "Smart plug compact (type E plug)", fingerprint: [{ modelID: "RBSH-SP-ZB-FR" }] },
            { vendor: "Bosch", model: "BSP-GZ2", description: "Smart plug compact (type G plug)", fingerprint: [{ modelID: "RBSH-SP-ZB-GB" }] },
        ],
    },
    {
        zigbeeModel: ["RBSH-SP2-ZB-EU"],
        model: "BSP-FD",
        vendor: "Bosch",
        description: "Smart plug compact [+M]",
        extend: [
            bosch_1.boschGeneralExtend.handleZclVersionReadRequest(),
            bosch_1.boschGeneralEnergyDeviceExtend.customMeteringCluster(),
            bosch_1.boschSmartPlugExtend.smartPlugCluster(),
            bosch_1.boschSmartPlugExtend.onOff(),
            bosch_1.boschGeneralEnergyDeviceExtend.autoOff(),
            bosch_1.boschSmartPlugExtend.ledBrightness(),
            bosch_1.boschSmartPlugExtend.energySavingMode(),
            bosch_1.boschSmartPlugExtend.electricityMeter({ producedEnergy: true }),
            bosch_1.boschGeneralEnergyDeviceExtend.resetEnergyMeters(),
        ],
    },
    {
        zigbeeModel: ["RBSH-SWD-ZB"],
        model: "BSEN-C2",
        vendor: "Bosch",
        description: "Door/window contact II",
        extend: [
            bosch_1.boschDoorWindowContactExtend.doorWindowContactCluster(),
            bosch_1.boschGeneralExtend.handleRenamedCustomCluster("boschSpecific", "boschDoorWindowContactCluster"),
            bosch_1.boschDoorWindowContactExtend.reportContactState(),
            bosch_1.boschDoorWindowContactExtend.reportButtonActions(),
            bosch_1.boschDoorWindowContactExtend.breakFunctionality(),
            bosch_1.boschGeneralExtend.batteryWithPercentageAndLowStatus(),
        ],
        ota: true,
    },
    {
        zigbeeModel: ["RBSH-SWDV-ZB"],
        model: "BSEN-CV",
        vendor: "Bosch",
        description: "Door/window contact II plus",
        extend: [
            bosch_1.boschDoorWindowContactExtend.doorWindowContactCluster(),
            bosch_1.boschGeneralExtend.handleRenamedCustomCluster("boschSpecific", "boschDoorWindowContactCluster"),
            bosch_1.boschDoorWindowContactExtend.reportContactState(),
            bosch_1.boschDoorWindowContactExtend.reportButtonActions(),
            bosch_1.boschDoorWindowContactExtend.vibrationDetection(),
            bosch_1.boschDoorWindowContactExtend.breakFunctionality(),
            bosch_1.boschGeneralExtend.batteryWithPercentageAndLowStatus(),
        ],
    },
    {
        zigbeeModel: ["RBSH-SWD2-ZB"],
        model: "BSEN-C2D",
        vendor: "Bosch",
        description: "Door/window contact II [+M]",
        extend: [
            bosch_1.boschDoorWindowContactExtend.doorWindowContactCluster(),
            bosch_1.boschGeneralExtend.handleRenamedCustomCluster("boschSpecific", "boschDoorWindowContactCluster"),
            bosch_1.boschDoorWindowContactExtend.reportContactState(),
            bosch_1.boschDoorWindowContactExtend.reportButtonActions({ doublePressSupported: true }),
            bosch_1.boschDoorWindowContactExtend.breakFunctionality(),
            bosch_1.boschGeneralExtend.batteryWithPercentageAndLowStatus(),
        ],
    },
    {
        zigbeeModel: ["RBSH-MMD-ZB-EU"],
        model: "BMCT-DZ",
        vendor: "Bosch",
        description: "Phase-cut dimmer",
        extend: [
            bosch_1.boschGeneralExtend.handleZclVersionReadRequest(),
            m.deviceAddCustomCluster("boschEnergyDevice", {
                name: "boschEnergyDevice",
                ID: 0xfca0,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
                attributes: {
                    switchType: { name: "switchType", ID: 0x0001, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true, max: 0xff },
                    childLock: { name: "childLock", ID: 0x0008, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN, write: true },
                    dimmerType: { name: "dimmerType", ID: 0x0022, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true, max: 0xff },
                    minimumBrightness: { name: "minimumBrightness", ID: 0x0025, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true, max: 0xff },
                    maximumBrightness: { name: "maximumBrightness", ID: 0x0026, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true, max: 0xff },
                    switchMode: { name: "switchMode", ID: 0x0031, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN, write: true },
                },
                commands: {},
                commandsResponse: {},
            }),
            bosch_1.boschGeneralExtend.handleRenamedCustomCluster("boschSpecific", "boschEnergyDevice"),
            m.light({
                configureReporting: true,
                levelConfig: { features: ["on_level", "current_level_startup"] },
                powerOnBehavior: true,
                effect: false,
            }),
            bosch_1.boschBmctExtend.switchType({
                switchTypeLookup: boschBmctDzSettings.switchTypes,
            }),
            bosch_1.boschBmctExtend.reportSwitchAction({
                switchTypeLookup: boschBmctDzSettings.switchTypes,
                hasDualSwitchInputs: boschBmctDzSettings.hasDualSwitchInputs,
            }),
            bosch_1.boschBmctExtend.switchMode({
                switchModeLookup: boschBmctDzSettings.switchModes,
                switchTypeLookup: boschBmctDzSettings.switchTypes,
            }),
            bosch_1.boschBmctExtend.childLock(),
            bosch_1.boschBmctExtend.brightnessRange(),
            bosch_1.boschBmctExtend.dimmerType(),
        ],
        ota: true,
    },
    {
        zigbeeModel: ["RBSH-MMR-ZB-EU"],
        model: "BMCT-RZ",
        vendor: "Bosch",
        description: "Relay (potential free)",
        extend: [
            bosch_1.boschGeneralExtend.handleZclVersionReadRequest(),
            m.deviceAddCustomCluster("boschEnergyDevice", {
                name: "boschEnergyDevice",
                ID: 0xfca0,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
                attributes: {
                    switchType: { name: "switchType", ID: 0x0001, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true, max: 0xff },
                    childLock: { name: "childLock", ID: 0x0008, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN, write: true },
                    pulseLength: { name: "pulseLength", ID: 0x0024, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff },
                    switchMode: { name: "switchMode", ID: 0x0031, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN, write: true },
                    actuatorType: { name: "actuatorType", ID: 0x0034, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true, max: 0xff },
                },
                commands: {},
                commandsResponse: {},
            }),
            bosch_1.boschGeneralExtend.handleRenamedCustomCluster("boschSpecific", "boschEnergyDevice"),
            bosch_1.boschBmctExtend.rzDeviceModes({
                deviceModesLookup: boschBmctRzSettings.deviceModes,
            }),
            m.onOff({ powerOnBehavior: false }),
            bosch_1.boschBmctExtend.switchType({
                switchTypeLookup: boschBmctRzSettings.switchTypes,
            }),
            bosch_1.boschBmctExtend.reportSwitchAction({
                switchTypeLookup: boschBmctRzSettings.switchTypes,
                hasDualSwitchInputs: boschBmctRzSettings.hasDualSwitchInputs,
            }),
            bosch_1.boschBmctExtend.switchMode({
                switchModeLookup: boschBmctRzSettings.switchModes,
                switchTypeLookup: boschBmctRzSettings.switchTypes,
            }),
            bosch_1.boschBmctExtend.childLock(),
            bosch_1.boschGeneralEnergyDeviceExtend.autoOff(),
            bosch_1.boschBmctExtend.pulseLength({
                updateDeviceMode: true,
                deviceModesLookup: boschBmctRzSettings.deviceModes,
            }),
            bosch_1.boschBmctExtend.actuatorType(),
        ],
        ota: true,
    },
    {
        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.electricityMeter({
                voltage: false,
                current: false,
                power: { change: 1 },
                energy: { change: 1 },
            }),
            m.deviceAddCustomCluster("boschEnergyDevice", {
                name: "boschEnergyDevice",
                ID: 0xfca0,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
                attributes: {
                    deviceMode: { name: "deviceMode", ID: 0x0000, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true, max: 0xff },
                    switchType: { name: "switchType", ID: 0x0001, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true, max: 0xff },
                    switchMode: { name: "switchMode", ID: 0x0031, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true, max: 0xff },
                    calibrationOpeningTime: { name: "calibrationOpeningTime", ID: 0x0002, type: zigbee_herdsman_1.Zcl.DataType.UINT32, write: true, max: 0xffffffff },
                    calibrationClosingTime: { name: "calibrationClosingTime", ID: 0x0003, type: zigbee_herdsman_1.Zcl.DataType.UINT32, write: true, max: 0xffffffff },
                    // 0x0005 isn't used at all when using the Bosch SHC as of 30-06-2025.
                    // As I don't have any shutters, I can't run all calibration steps
                    // successfully. So, keep any comments regarding these
                    // attributes with caution.
                    calibrationButtonHoldTime: { name: "calibrationButtonHoldTime", ID: 0x0005, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true, max: 0xff },
                    autoOffEnabled: { name: "autoOffEnabled", ID: 0x0006, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN, write: true },
                    autoOffTime: { name: "autoOffTime", ID: 0x0007, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff },
                    childLock: { name: "childLock", ID: 0x0008, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN, write: true },
                    // 0x000f is only being set when using the automatic calibration.
                    // It's being set to 0 then before sending the calibration
                    // command. Additionally, when changing
                    // the calibrationOpeningTime or calibrationClosingTime in the
                    // Bosch app, it's also being set to 0.
                    // I couldn't find any way to set 0x000f manually in the Bosch app.
                    calibrationMotorStartDelay: { name: "calibrationMotorStartDelay", ID: 0x000f, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true, max: 0xff },
                    calibrationMotorReverseDirection: { name: "calibrationMotorReverseDirection", ID: 0x0032, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN, write: true },
                    motorState: { name: "motorState", ID: 0x0013, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true, max: 0xff },
                    // unknownAttributeOne is always being configured as reporting
                    // attribute on endpoint 1 when using the Bosch SHC.
                    // Can't tell what this attribute does (always received
                    // 0x00 as answer on manual lookup).
                    unknownAttributeOne: { name: "unknownAttributeOne", ID: 0x0004, type: zigbee_herdsman_1.Zcl.DataType.BITMAP8, write: true },
                    // Attribute is being set to 255 when deactivating the automatic
                    // detection of the motor end position by the Bosch SHC. After
                    // activating the automatic end position detection it's being set
                    // to 0 by the Bosch SHC. Apart from that, there's no way to manually
                    // change the value.
                    calibrationMotorEndPosition: { name: "calibrationMotorEndPosition", ID: 0x0021, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true, max: 0xff },
                    // 0x0033 is used when setting the motor start delay manually
                    // using the Bosch SHC as of 30-06-2025.
                    // If the user wants to automatically detect the delay during
                    // calibration, it's being set to 0 over the Bosch app.
                    calibrationNewMotorStartDelay: {
                        name: "calibrationNewMotorStartDelay",
                        ID: 0x0033,
                        type: zigbee_herdsman_1.Zcl.DataType.UINT16,
                        write: true,
                        max: 0xffff,
                    },
                    // 0x0010 and 0x0011 is being set simultaneously with the same value
                    // when changing the delay for the rotation of the slats on venetian
                    // blinds. Maybe one attribute for each direction?
                    // It's also being configured as reporting attribute when using
                    // venetian blinds.
                    slatRotationDurationOne: { name: "slatRotationDurationOne", ID: 0x0010, type: zigbee_herdsman_1.Zcl.DataType.UINT32, write: true, max: 0xffffffff },
                    slatRotationDurationTwo: { name: "slatRotationDurationTwo", ID: 0x0011, type: zigbee_herdsman_1.Zcl.DataType.UINT32, write: true, max: 0xffffffff },
                    // 0x002a is only being used when doing an automatic calibration
                    // with the Bosch specific startAutomaticMotorCalibration command.
                    // It's being set to true before starting the calibration process.
                    // This happens regardless of the shutter type. I didn't capture
                    // any packages where this attribute is being actively set to false.
                    // Maybe this activates some "full calibration" flag which is being
                    // set to false by the device itself afterward?
                    unknownAttributeTwo: { name: "unknownAttributeTwo", ID: 0x002a, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN, write: true },
                },
                commands: {
                    // Command being sent by the Bosch SHC when starting an
                    // automatic shutter calibration.
                    startAutomaticMotorCalibration: { name: "startAutomaticMotorCalibration", ID: 0x00, parameters: [] },
                },
                commandsResponse: {},
            }),
            bosch_1.boschGeneralExtend.handleRenamedCustomCluster("boschSpecific", "boschEnergyDevice"),
            bosch_1.boschGeneralExtend.handleZclVersionReadRequest(),
            bosch_1.boschBmctExtend.slzExtends(),
            bosch_1.boschGeneralEnergyDeviceExtend.customMeteringCluster(),
            bosch_1.boschGeneralEnergyDeviceExtend.resetEnergyMeters(),
        ],
        ota: true,
        configure: async (device, coordinatorEndpoint) => {
            const lightConfiguration = async () => {
                const endpoint1 = device.getEndpoint(1);
                await reporting.bind(endpoint1, coordinatorEndpoint, ["genIdentify"]);
                await endpoint1.read("boschEnergyDevice", ["switchType"]);
                const endpoint2 = device.getEndpoint(2);
                await reporting.bind(endpoint2, coordinatorEndpoint, ["genIdentify", "genOnOff", "boschEnergyDevice"]);
                await reporting.onOff(endpoint2);
                await endpoint2.read("genOnOff", ["onOff", "startUpOnOff"]);
                await endpoint2.read("boschEnergyDevice", [
                    "switchMode",
                    "childLock",
                    "autoOffEnabled",
                    "autoOffTime",
                ]);
                const endpoint3 = device.getEndpoint(3);
                await reporting.bind(endpoint3, coordinatorEndpoint, ["genIdentify", "genOnOff", "boschEnergyDevice"]);
                await reporting.onOff(endpoint3);
                await endpoint3.read("genOnOff", ["onOff", "startUpOnOff"]);
                await endpoint3.read("boschEnergyDevice", [
                    "switchMode",
                    "childLock",
                    "autoOffEnabled",
                    "autoOffTime",
                ]);
            };
            const shutterConfiguration = async () => {
                const endpoint1 = device.getEndpoint(1);
                await reporting.bind(endpoint1, coordinatorEndpoint, ["genIdentify", "closuresWindowCovering", "boschEnergyDevice"]);
                await reporting.currentPositionLiftPercentage(endpoint1);
                await endpoint1.read("closuresWindowCovering", ["currentPositionLiftPercentage"]);
                const payloadMotorState = (0, reporting_1.payload)("motorState", 0, constants_1.repInterval.MAX, 0);
                await endpoint1.configureReporting("boschEnergyDevice", payloadMotorState);
                await endpoint1.read("boschEnergyDevice", [
                    "switchType",
                    "switchMode",
                    "motorState",
                    "calibrationOpeningTime",
                    "calibrationClosingTime",
                    "calibrationButtonHoldTime",
                    "calibrationMotorStartDelay",
                    "childLock",
                ]);
            };
            const endpoint1 = device.getEndpoint(1);
            await endpoint1.read("boschEnergyDevice", ["deviceMode"]);
            await lightConfiguration();
            await shutterConfiguration();
        },
        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,
                none: 0x00,
            };
            const stateSwitchMode = {
                coupled: 0x00,
                decoupled: 0x01,
                only_short_press_decoupled: 0x02,
                only_long_press_decoupled: 0x03,
            };
            const commonExposes = (switchType) => {
                const exposeList = [];
                exposeList.push(e.enum("switch_type", ea.ALL, Object.keys(stateSwitchType)).withDescription("Module controlled by a rocker switch or a button"));
                if (switchType !== "none") {
                    let supportedActionTypes;
                    switch (switchType) {
                        case "button":
                        case "button_key_change":
                            supportedActionTypes = [
                                "press_released_left",
                                "press_released_right",
                                "hold_left",
                                "hold_right",
                                "hold_released_left",
                                "hold_released_right",
                            ];
                            break;
                        case "rocker_switch":
                        case "rocker_switch_key_change":
                            supportedActionTypes = ["opened_left", "opened_right", "closed_left", "closed_right"];
                            break;
                    }
                    exposeList.push(e.action(supportedActionTypes), e.action_duration());
                }
                return exposeList;
            };
            const lightExposes = (endpoint, switchType) => {
                const exposeList = [];
                exposeList.push(e.switch().withEndpoint(endpoint), e.power_on_behavior().withEndpoint(endpoint), e
                    .binary("auto_off_enabled", ea.ALL, "ON", "OFF")
                    .withEndpoint(endpoint)
                    .withDescription("Enable/Disable the automatic turn-off feature"), e
                    .numeric("auto_off_time", ea.ALL)
                    .withValueMin(0)
                    .withValueMax(720)
                    .withValueStep(1)
                    .withUnit("min")
                    .withDescription("Turn off the output after the specified amount of time. Only in action when the automatic turn-off is enabled.")
                    .withEndpoint(endpoint));
                if (switchType !== "none") {
                    let supportedSwitchModes;
                    switch (switchType) {
                        case "button":
                        case "button_key_change":
                            supportedSwitchModes = Object.keys(stateSwitchMode);
                            break;
                        case "rocker_switch":
                        case "rocker_switch_key_change":
                            supportedSwitchModes = Object.keys(stateSwitchMode).filter((switchMode) => switchMode === "coupled" || switchMode === "decoupled");
                            break;
                    }
                    exposeList.push(e
                        .enum("switch_mode", ea.ALL, supportedSwitchModes)
                        .withEndpoint(endpoint)
                        .withDescription("Decouple the switch from the corresponding output to use it for other purposes. Please keep in mind that the available options depend on the used switch type."), e.binary("child_lock", ea.ALL, "ON", "OFF").withEndpoint(endpoint).withDescription("Enable/Disable child lock"));
                }
                return exposeList;
            };
            const coverExposes = (switchType) => {
                const exposeList = [];
                exposeList.push(e.cover_position(), e.enum("motor_state", ea.STATE, Object.keys(stateMotor)).withDescription("Current shutter motor state"), 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 (switchType !== "none") {
                    let supportedSwitchModes;
                    switch (switchType) {
                        case "button":
                        case "button_key_change":
                            supportedSwitchModes = Object.keys(stateSwitchMode).filter((switchMode) => switchMode === "coupled" || switchMode === "only_long_press_decoupled");
                            break;
                        case "rocker_switch":
                        case "rocker_switch_key_change":
                            supportedSwitchModes = Object.keys(stateSwitchMode).filter((switchMode) => switchMode === "coupled");
                            break;
                    }
                    exposeList.push(e
                        .enum("switch_mode", ea.ALL, supportedSwitchModes)
                        .withDescription("Decouple the switch from the corresponding output to use it for other purposes. Please keep in mind that the available options depend on the used switch type."), e.binary("child_lock", ea.ALL, "ON", "OFF").withDescription("Enable/Disable child lock"));
                }
                return exposeList;
            };
            if (!utils.isDummyDevice(device)) {
                const deviceModeKey = device.getEndpoint(1).getClusterAttributeValue("boschEnergyDevice", "deviceMode");
                const deviceMode = Object.keys(stateDeviceMode).find((key) => stateDeviceMode[key] === deviceModeKey);
                const switchTypeKey = device.getEndpoint(1).getClusterAttributeValue("boschEnergyDevice", "switchType");
                const switchType = Object.keys(stateSwitchType).find((key) => stateSwitchType[key] === switchTypeKey);
                if (deviceMode === "light") {
                    return [...commonExposes(switchType), ...lightExposes("left", switchType), ...lightExposes("right", switchType)];
                }
                if (deviceMode === "shutter") {
                    return [...commonExposes(switchType), ...coverExposes(switchType)];
                }
            }
            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", {
                name: "boschSpecific",
                ID: 0xfca1,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
                attributes: {},
                commands: {
                    confirmButtonPressed: {
                        name: "confirmButtonPressed",
                        ID: 0x0010,
                        parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.BuffaloZclDataType.BUFFER }],
                    },
                    pairingCompleted: {
                        name: "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], { ...bosch_1.manufacturerOptions, sendPolicy: "immediate" })
                .catch((error) => { });
            await endpoint
                .read("boschSpecific", [0x0020, 0x0021, 0x0022, 0x0023], { ...bosch_1.manufacturerOptions, sendPolicy: "immediate" })
                .catch((error) => { });
            // We also have to read this one. Value reads 0x0f, looks like a bitmap
            await endpoint.read("boschSpecific", [0x0024], { ...bosch_1.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