"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.boschThermostatExtend = exports.boschSmartPlugExtend = exports.boschSmokeAlarmExtend = exports.boschWaterAlarmExtend = exports.boschBsenExtend = exports.boschDoorWindowContactExtend = exports.boschBsirExtend = exports.boschBmctExtend = exports.boschGeneralSensorDeviceExtend = exports.boschGeneralEnergyDeviceExtend = exports.boschGeneralExtend = exports.manufacturerOptions = void 0;
const zigbee_herdsman_1 = require("zigbee-herdsman");
const fz = __importStar(require("../converters/fromZigbee"));
const tz = __importStar(require("../converters/toZigbee"));
const exposes = __importStar(require("../lib/exposes"));
const m = __importStar(require("../lib/modernExtend"));
const constants_1 = require("./constants");
const logger_1 = require("./logger");
const reporting_1 = require("./reporting");
const globalStore = __importStar(require("./store"));
const utils = __importStar(require("./utils"));
const utils_1 = require("./utils");
const e = exposes.presets;
const ea = exposes.access;
const NS = "zhc:bosch";
exports.manufacturerOptions = {
    manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
    sendPolicy: "immediate",
};
//region Generally used Bosch functionality
exports.boschGeneralExtend = {
    /** Some devices now use a different name for some custom clusters than
     * originally used. This can lead to issues like those described in
     * https://github.com/Koenkk/zigbee2mqtt/issues/28806. To prevent that
     * we have to make sure that all attributes of the renamed cluster are
     * available when using "getEndpoint().getClusterAttributeValue()". */
    handleRenamedCustomCluster: (oldClusterName, newClusterName) => {
        const onEvent = [
            async (event) => {
                if (event.type !== "start") {
                    return;
                }
                const device = event.data.device;
                const renameAlreadyApplied = device.meta.renamedClusters?.includes(oldClusterName);
                if (!renameAlreadyApplied) {
                    logger_1.logger.debug(`Try to apply cluster rename from ${oldClusterName} to ${newClusterName} for device ${device.ieeeAddr}. Current meta state: ${JSON.stringify(device.meta)}`, NS);
                    const newClusterDefinition = device.customClusters[newClusterName];
                    const endpointsWithNewCluster = device.endpoints.filter((endpoint) => endpoint.clusters[newClusterName] !== undefined);
                    for (const endpointToRead in endpointsWithNewCluster) {
                        const endpoint = endpointsWithNewCluster[endpointToRead];
                        logger_1.logger.debug(`Attempt to read all attributes for cluster ${newClusterName} from endpoint ${endpoint.ID}`, NS);
                        for (const attributeToRead in newClusterDefinition.attributes) {
                            const attributeValueExistsInDatabase = endpoint.getClusterAttributeValue(newClusterName, newClusterDefinition.attributes[attributeToRead].ID) !== undefined;
                            if (!attributeValueExistsInDatabase) {
                                logger_1.logger.debug(`Attempt to read undefined attribute ${attributeToRead} in cluster ${newClusterName} from endpoint ${endpoint.ID}`, NS);
                                try {
                                    await endpoint.read(newClusterDefinition.ID, [newClusterDefinition.attributes[attributeToRead].ID]);
                                }
                                catch (exception) {
                                    logger_1.logger.debug(`Error during read attempt for attribute ${attributeToRead}. Probably unsupported attribute on this device or endpoint. Skipping... ${exception}`, NS);
                                }
                            }
                            else {
                                logger_1.logger.debug(`Value for attribute ${attributeToRead} in cluster ${newClusterName} from endpoint ${endpoint.ID} already in database. Skipping...`, NS);
                            }
                        }
                    }
                    logger_1.logger.debug(`All attributes are read, now calling deviceExposeChanged() for device ${device.ieeeAddr}`, NS);
                    event.data.deviceExposesChanged();
                    device.meta.renamedClusters =
                        device.meta.renamedClusters === undefined ? [oldClusterName] : [...device.meta.renamedClusters, oldClusterName];
                    logger_1.logger.debug(`Cluster rename from ${oldClusterName} to ${newClusterName} for device ${device.ieeeAddr} successfully applied. New meta state: ${JSON.stringify(device.meta)}`, NS);
                }
            },
        ];
        return {
            onEvent,
            isModernExtend: true,
        };
    },
    /** Some Bosch devices ask the coordinator for their ZCL version
     * during deviceAnnouncement. Without answer, these devices regularly
     * re-join the network. To avoid that, we have to make sure that a readRequest
     * for the zclVersion is always being answered. The answered zclVersion is
     * taken from the Bosch Smart Home Controller II.
     *
     * Exception: BTH-RM and BTH-RM230Z ask the coordinator at regular
     * intervals for their zclVersion (maybe availability check like Z2M does?)
     * and *not* during interview! To avoid code-duplication, we handle that
     * case here as well. */
    handleZclVersionReadRequest: () => {
        const onEvent = [
            (event) => {
                if (event.type === "start") {
                    event.data.device.customReadResponse = (frame, endpoint) => {
                        if (frame.isCluster("genBasic") && frame.payload.some((i) => i.attrId === 0x0000)) {
                            // XXX: we're replying to specific attribute, which could be incorrect (not based on the request attrIds)
                            endpoint.readResponse("genBasic", frame.header.transactionSequenceNumber, { zclVersion: 1 }).catch((e) => {
                                logger_1.logger.warning(`Custom zclVersion response failed for '${event.data.device.ieeeAddr}': ${e}`, NS);
                            });
                            return true;
                        }
                        return false;
                    };
                }
            },
        ];
        return {
            onEvent,
            isModernExtend: true,
        };
    },
    batteryWithPercentageAndLowStatus: (args) => m.battery({
        percentage: true,
        percentageReportingConfig: false,
        lowStatus: true,
        lowStatusReportingConfig: { min: "MIN", max: "MAX", change: null },
        ...args,
    }),
};
exports.boschGeneralEnergyDeviceExtend = {
    customMeteringCluster: () => m.deviceAddCustomCluster("seMetering", {
        name: "seMetering",
        ID: zigbee_herdsman_1.Zcl.Clusters.seMetering.ID,
        attributes: {},
        commands: {
            resetEnergyMeters: {
                name: "resetEnergyMeters",
                ID: 0x80,
                parameters: [],
            },
        },
        commandsResponse: {},
    }),
    resetEnergyMeters: () => {
        const exposes = [
            e
                .enum("reset_energy_meters", ea.SET, ["reset"])
                .withDescription("Triggers the reset of all energy meters on the device to 0 kWh")
                .withCategory("config"),
        ];
        const toZigbee = [
            {
                key: ["reset_energy_meters"],
                convertSet: async (entity, key, value, meta) => {
                    await entity.command("seMetering", "resetEnergyMeters", {}, exports.manufacturerOptions);
                },
            },
        ];
        return {
            exposes,
            toZigbee,
            isModernExtend: true,
        };
    },
    autoOff: (args) => {
        const { endpoint } = args ?? {};
        const offOnLookup = {
            OFF: 0x00,
            ON: 0x01,
        };
        const expose = (device, options) => {
            if (utils.isDummyDevice(device)) {
                return [];
            }
            if (device.modelID === "RBSH-MMR-ZB-EU") {
                const pulsedModeEnabled = device.getEndpoint(1).getClusterAttributeValue("boschEnergyDevice", "pulseLength") !== 0;
                if (pulsedModeEnabled) {
                    return [];
                }
            }
            const autoOffEnabledExpose = e
                .binary("auto_off_enabled", ea.ALL, utils.getFromLookupByValue(0x01, offOnLookup), utils.getFromLookupByValue(0x00, offOnLookup))
                .withLabel("Enable auto-off")
                .withDescription("Enable/disable the automatic turn-off feature")
                .withCategory("config");
            const autoOffTimeExpose = e
                .numeric("auto_off_time", ea.ALL)
                .withLabel("Auto-off time")
                .withDescription("Turn off the output after the specified amount of time. Only in action when the automatic turn-off is enabled.")
                .withUnit("min")
                .withValueMin(0)
                .withValueMax(720)
                .withValueStep(0.5)
                .withCategory("config");
            if (endpoint !== undefined) {
                autoOffEnabledExpose.withEndpoint(endpoint.toString());
                autoOffTimeExpose.withEndpoint(endpoint.toString());
            }
            return [autoOffEnabledExpose, autoOffTimeExpose];
        };
        const fromZigbee = [
            {
                cluster: "boschEnergyDevice",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const result = {};
                    const data = msg.data;
                    if (data.autoOffEnabled !== undefined) {
                        const property = utils.postfixWithEndpointName("auto_off_enabled", msg, model, meta);
                        result[property] = utils.getFromLookupByValue(data.autoOffEnabled, offOnLookup);
                    }
                    if (data.autoOffTime !== undefined) {
                        const property = utils.postfixWithEndpointName("auto_off_time", msg, model, meta);
                        result[property] = data.autoOffTime / 60;
                    }
                    return result;
                },
            },
        ];
        const toZigbee = [
            {
                key: ["auto_off_enabled", "auto_off_time"],
                convertSet: async (entity, key, value, meta) => {
                    if (key === "auto_off_enabled") {
                        const selectedState = utils.getFromLookup(value, offOnLookup);
                        await entity.write("boschEnergyDevice", {
                            autoOffEnabled: utils.toNumber(selectedState),
                        });
                        return { state: { auto_off_enabled: value } };
                    }
                    if (key === "auto_off_time") {
                        await entity.write("boschEnergyDevice", {
                            autoOffTime: (0, utils_1.toNumber)(value) * 60,
                        });
                        return { state: { auto_off_time: value } };
                    }
                },
                convertGet: async (entity, key, meta) => {
                    if (key === "auto_off_enabled") {
                        await entity.read("boschEnergyDevice", ["autoOffEnabled"]);
                    }
                    if (key === "auto_off_time") {
                        await entity.read("boschEnergyDevice", ["autoOffTime"]);
                    }
                },
            },
        ];
        const configure = [
            m.setupConfigureForBinding("boschEnergyDevice", "input", endpoint ? [endpoint.toString()] : null),
            m.setupConfigureForReading("boschEnergyDevice", ["autoOffEnabled", "autoOffTime"], endpoint ? [endpoint.toString()] : null),
        ];
        return {
            exposes: [expose],
            fromZigbee,
            toZigbee,
            configure,
            isModernExtend: true,
        };
    },
};
//endregion
//region Generally used Bosch functionality on sensor devices
exports.boschGeneralSensorDeviceExtend = {
    testMode: (args) => {
        const { testModeDescription, sensitivityLevelToUse, variableTimeoutSupported = false, defaultTimeout = 0, zoneStatusBit = 8 } = args;
        const testModeLookup = {
            ON: true,
            OFF: false,
        };
        const enableTestMode = async (endpoint, sensitivityLevelToUse, timeoutInSeconds) => {
            await endpoint.command("ssIasZone", "initTestMode", {
                testModeDuration: timeoutInSeconds,
                currentZoneSensitivityLevel: sensitivityLevelToUse,
            });
        };
        const disableTestMode = async (endpoint) => {
            await endpoint.command("ssIasZone", "initNormalOpMode", {});
        };
        const exposes = [
            e
                .binary("test_mode", ea.ALL, utils.getFromLookupByValue(true, testModeLookup), utils.getFromLookupByValue(false, testModeLookup))
                .withDescription(testModeDescription)
                .withCategory("config"),
        ];
        if (variableTimeoutSupported) {
            exposes.push(e
                .numeric("test_mode_timeout", ea.ALL)
                .withDescription(`Determines how long the test mode should be activated. The default length is ${defaultTimeout} seconds.`)
                .withValueMin(1)
                .withValueMax(255)
                .withUnit("seconds")
                .withCategory("config"));
        }
        const fromZigbee = [
            {
                cluster: "ssIasZone",
                type: ["commandStatusChangeNotification", "attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const zoneStatus = "zonestatus" in msg.data ? msg.data.zonestatus : msg.data.zoneStatus;
                    if (zoneStatus === undefined) {
                        return;
                    }
                    const result = {};
                    const testModeEnabled = (zoneStatus & (1 << zoneStatusBit)) > 0;
                    result.test_mode = utils.getFromLookupByValue(testModeEnabled, testModeLookup);
                    return result;
                },
            },
        ];
        const toZigbee = [
            {
                key: ["test_mode", "test_mode_timeout"],
                convertSet: async (entity, key, value, meta) => {
                    if (key === "test_mode") {
                        if (value === utils.getFromLookupByValue(true, testModeLookup)) {
                            let timeoutInSeconds;
                            const currentTimeout = meta.state.test_mode_timeout;
                            if (currentTimeout == null) {
                                timeoutInSeconds = defaultTimeout;
                                meta.publish({ test_mode_timeout: timeoutInSeconds });
                            }
                            else {
                                timeoutInSeconds = utils.toNumber(currentTimeout);
                            }
                            await enableTestMode(entity, sensitivityLevelToUse, timeoutInSeconds);
                        }
                        else {
                            await disableTestMode(entity);
                        }
                    }
                    if (key === "test_mode_timeout") {
                        return { state: { test_mode_timeout: value } };
                    }
                },
                convertGet: async (entity, key, meta) => {
                    if (key === "test_mode") {
                        await entity.read("ssIasZone", ["zoneStatus"]);
                    }
                    if (key === "test_mode_timeout" && meta.state.test_mode_timeout == null) {
                        meta.publish({ test_mode_timeout: defaultTimeout });
                    }
                },
            },
        ];
        const configure = [m.setupConfigureForBinding("ssIasZone", "input"), m.setupConfigureForReading("ssIasZone", ["zoneStatus"])];
        return {
            exposes,
            fromZigbee,
            toZigbee,
            configure,
            isModernExtend: true,
        };
    },
};
exports.boschBmctExtend = {
    switchMode: (args) => {
        const { endpoint, deviceModeLookup, switchModeLookup, switchTypeLookup } = args;
        const expose = (device, options) => {
            if (utils.isDummyDevice(device)) {
                return [];
            }
            const switchTypeKey = device.getEndpoint(1).getClusterAttributeValue("boschEnergyDevice", "switchType") ?? 0x00;
            const selectedSwitchType = utils.getFromLookupByValue(switchTypeKey, switchTypeLookup);
            if (selectedSwitchType === "none") {
                return [];
            }
            let supportedSwitchModes = Object.keys(switchModeLookup);
            if (device.modelID === "RBSH-MMS-ZB-EU") {
                const deviceModeKey = device.getEndpoint(1).getClusterAttributeValue("boschEnergyDevice", "deviceMode");
                const deviceMode = utils.getFromLookupByValue(deviceModeKey, deviceModeLookup);
                if (deviceMode === "light") {
                    if (selectedSwitchType.includes("rocker_switch")) {
                        supportedSwitchModes = supportedSwitchModes.filter((switchMode) => switchMode === "coupled" || switchMode === "decoupled");
                    }
                }
                if (deviceMode === "shutter") {
                    if (selectedSwitchType.includes("button")) {
                        supportedSwitchModes = supportedSwitchModes.filter((switchMode) => switchMode === "coupled" || switchMode === "only_long_press_decoupled");
                    }
                    else if (selectedSwitchType.includes("rocker_switch")) {
                        supportedSwitchModes = supportedSwitchModes.filter((switchMode) => switchMode === "coupled");
                    }
                }
            }
            const switchModeExpose = 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 may depend on the used switch type.")
                .withCategory("config");
            if (endpoint !== undefined) {
                switchModeExpose.withEndpoint(endpoint.toString());
            }
            return [switchModeExpose];
        };
        const fromZigbee = [
            {
                cluster: "boschEnergyDevice",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const result = {};
                    const data = msg.data;
                    if (data.switchMode !== undefined) {
                        const property = utils.postfixWithEndpointName("switch_mode", msg, model, meta);
                        result[property] = utils.getFromLookupByValue(data.switchMode, switchModeLookup);
                    }
                    return result;
                },
            },
        ];
        const toZigbee = [
            {
                key: ["switch_mode"],
                convertSet: async (entity, key, value, meta) => {
                    if (key === "switch_mode") {
                        const index = utils.getFromLookup(value, switchModeLookup);
                        await entity.write("boschEnergyDevice", { switchMode: index });
                        return { state: { switch_mode: value } };
                    }
                },
                convertGet: async (entity, key, meta) => {
                    if (key === "switch_mode") {
                        await entity.read("boschEnergyDevice", ["switchMode"]);
                    }
                },
            },
        ];
        const configure = [
            async (device, coordinatorEndpoint, definition) => {
                const desiredEndpoint = device.getEndpoint(endpoint ?? 1);
                await desiredEndpoint.read("boschEnergyDevice", ["switchMode"]);
            },
        ];
        return {
            exposes: [expose],
            fromZigbee,
            toZigbee,
            configure,
            isModernExtend: true,
        };
    },
    childLock: (args) => {
        const { endpoint } = args ?? {};
        const childLockLookup = {
            UNLOCKED: 0x00,
            LOCKED: 0x01,
        };
        const expose = (device, options) => {
            if (utils.isDummyDevice(device)) {
                return [];
            }
            const currentSwitchType = device.getEndpoint(1).getClusterAttributeValue("boschEnergyDevice", "switchType") ?? 0x00;
            if (currentSwitchType === 0) {
                return [];
            }
            const childLockExpose = e
                .binary("child_lock", ea.ALL, utils.getFromLookupByValue(0x01, childLockLookup), utils.getFromLookupByValue(0x00, childLockLookup))
                .withDescription("Enables/disables physical input on the switch")
                .withCategory("config");
            if (endpoint !== undefined) {
                childLockExpose.withEndpoint(endpoint.toString());
            }
            return [childLockExpose];
        };
        const fromZigbee = [
            {
                cluster: "boschEnergyDevice",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const result = {};
                    const data = msg.data;
                    if (data.childLock !== undefined) {
                        const property = utils.postfixWithEndpointName("child_lock", msg, model, meta);
                        result[property] = data.childLock;
                    }
                    return result;
                },
            },
        ];
        const toZigbee = [
            {
                key: ["child_lock"],
                convertSet: async (entity, key, value, meta) => {
                    if (key === "child_lock") {
                        const selectedMode = utils.getFromLookup(value, childLockLookup);
                        await entity.write("boschEnergyDevice", { childLock: selectedMode });
                        return { state: { child_lock: value } };
                    }
                },
                convertGet: async (entity, key, meta) => {
                    if (key === "child_lock") {
                        await entity.read("boschEnergyDevice", ["childLock"]);
                    }
                },
            },
        ];
        const configure = [
            async (device, coordinatorEndpoint, definition) => {
                const desiredEndpoint = device.getEndpoint(endpoint ?? 1);
                await desiredEndpoint.read("boschEnergyDevice", ["childLock"]);
            },
        ];
        return {
            exposes: [expose],
            fromZigbee,
            toZigbee,
            configure,
            isModernExtend: true,
        };
    },
    actuatorType: () => m.enumLookup({
        name: "actuator_type",
        cluster: "boschEnergyDevice",
        attribute: "actuatorType",
        description: "Select the appropriate actuator type so that the connected device can be controlled correctly.",
        lookup: {
            normally_closed: 0x00,
            normally_open: 0x01,
        },
        entityCategory: "config",
    }),
    dimmerType: () => m.enumLookup({
        name: "dimmer_type",
        cluster: "boschEnergyDevice",
        attribute: "dimmerType",
        description: "Select the appropriate dimmer type for your lamps. Make sure that you are only using dimmable lamps.",
        lookup: {
            leading_edge_phase_cut: 0x00,
            trailing_edge_phase_cut: 0x01,
        },
        entityCategory: "config",
    }),
    brightnessRange: () => {
        const expose = (device, options) => {
            if (utils.isDummyDevice(device)) {
                return [];
            }
            const minimumBrightnessExpose = e
                .numeric("minimum_brightness", ea.ALL)
                .withLabel("Raise minimum brightness")
                .withDescription("This raises the minimum brightness level of the connected light")
                .withValueMin(0)
                .withValueMax(255)
                .withCategory("config");
            const maximumBrightnessExpose = e
                .numeric("maximum_brightness", ea.ALL)
                .withLabel("Lower maximum brightness")
                .withDescription("This lowers the maximum brightness level of the connected light")
                .withValueMin(0)
                .withValueMax(255)
                .withCategory("config");
            return [minimumBrightnessExpose, maximumBrightnessExpose];
        };
        const fromZigbee = [
            {
                cluster: "boschEnergyDevice",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const result = {};
                    const data = msg.data;
                    if (data.minimumBrightness !== undefined) {
                        result.minimum_brightness = data.minimumBrightness;
                    }
                    if (data.maximumBrightness !== undefined) {
                        result.maximum_brightness = data.maximumBrightness;
                    }
                    return result;
                },
            },
        ];
        const toZigbee = [
            {
                key: ["minimum_brightness", "maximum_brightness"],
                convertSet: async (entity, key, value, meta) => {
                    if (key === "minimum_brightness") {
                        const newMinimumBrightness = (0, utils_1.toNumber)(value);
                        const currentState = await entity.read("boschEnergyDevice", ["maximumBrightness"]);
                        if (newMinimumBrightness >= currentState.maximumBrightness) {
                            throw new Error("The minimum brightness must be lower than the maximum brightness!");
                        }
                        await entity.write("boschEnergyDevice", {
                            minimumBrightness: newMinimumBrightness,
                        });
                        return { state: { minimum_brightness: value } };
                    }
                    if (key === "maximum_brightness") {
                        const newMaximumBrightness = (0, utils_1.toNumber)(value);
                        const currentState = await entity.read("boschEnergyDevice", ["minimumBrightness"]);
                        if (newMaximumBrightness <= currentState.minimumBrightness) {
                            throw new Error("The maximum brightness must be higher than the minimum brightness!");
                        }
                        await entity.write("boschEnergyDevice", {
                            maximumBrightness: newMaximumBrightness,
                        });
                        return { state: { maximum_brightness: value } };
                    }
                },
                convertGet: async (entity, key, meta) => {
                    if (key === "minimum_brightness") {
                        await entity.read("boschEnergyDevice", ["minimumBrightness"]);
                    }
                    if (key === "maximum_brightness") {
                        await entity.read("boschEnergyDevice", ["maximumBrightness"]);
                    }
                },
            },
        ];
        const configure = [
            async (device, coordinatorEndpoint, definition) => {
                const endpoint = device.getEndpoint(1);
                await endpoint.read("boschEnergyDevice", ["minimumBrightness", "maximumBrightness"]);
            },
        ];
        return {
            exposes: [expose],
            fromZigbee,
            toZigbee,
            configure,
            isModernExtend: true,
        };
    },
    rzDeviceModes: (args) => {
        const { deviceModesLookup } = args;
        const expose = (device, options) => {
            if (utils.isDummyDevice(device)) {
                return [];
            }
            const deviceModeExpose = e
                .enum("device_mode", ea.SET, Object.keys(deviceModesLookup))
                .withLabel("Device mode")
                .withDescription("Set the desired mode of the relay")
                .withCategory("config");
            return [deviceModeExpose];
        };
        const toZigbee = [
            {
                key: ["device_mode"],
                convertSet: async (entity, key, value, meta) => {
                    if (key === "device_mode") {
                        const deviceModeChanged = meta.state.device_mode !== value;
                        if (deviceModeChanged) {
                            const newPulseLength = value === utils.getFromLookupByValue(0x00, deviceModesLookup) ? 0 : 10;
                            const endpoint = meta.device.getEndpoint(1);
                            if (newPulseLength > 0) {
                                await endpoint.write("boschEnergyDevice", {
                                    switchType: 0x05,
                                    switchMode: 0x00,
                                    childLock: 0x00,
                                    autoOffEnabled: 0x00,
                                    autoOffTime: 0,
                                });
                                await endpoint.read("boschEnergyDevice", [
                                    "switchType",
                                    "switchMode",
                                    "childLock",
                                    "autoOffEnabled",
                                    "autoOffTime",
                                ]);
                            }
                            else {
                                await endpoint.write("boschEnergyDevice", {
                                    switchType: 0x00,
                                    switchMode: 0x00,
                                    childLock: 0x00,
                                });
                                await endpoint.read("boschEnergyDevice", [
                                    "switchType",
                                    "switchMode",
                                    "childLock",
                                ]);
                            }
                            await endpoint.write("boschEnergyDevice", {
                                pulseLength: newPulseLength,
                            });
                            await endpoint.read("boschEnergyDevice", ["pulseLength"]);
                        }
                        return { state: { device_mode: value } };
                    }
                },
            },
        ];
        return {
            exposes: [expose],
            toZigbee,
            isModernExtend: true,
        };
    },
    pulseLength: (args) => {
        const { updateDeviceMode, deviceModesLookup } = args;
        const expose = (device, options) => {
            if (utils.isDummyDevice(device)) {
                return [];
            }
            if (device.getEndpoint(1).getClusterAttributeValue("boschEnergyDevice", "pulseLength") === 0) {
                return [];
            }
            const pulseLengthExpose = e
                .numeric("pulse_length", ea.ALL)
                .withLabel("Pulse length")
                .withDescription("Set the desired pulse length for the relay in seconds.")
                .withUnit("s")
                .withValueStep(0.1)
                .withValueMin(0.5)
                .withValueMax(20)
                .withCategory("config");
            return [pulseLengthExpose];
        };
        const fromZigbee = [
            {
                cluster: "boschEnergyDevice",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const result = {};
                    const data = msg.data;
                    if (data.pulseLength !== undefined) {
                        const currentPulseLength = data.pulseLength / 10;
                        const oldPulseLength = meta.device.meta.pulseLength ?? 0;
                        const pulsedModeActivated = currentPulseLength > 0 && oldPulseLength === 0;
                        const pulsedModeDeactivated = currentPulseLength === 0 && oldPulseLength > 0;
                        if (pulsedModeActivated || pulsedModeDeactivated) {
                            meta.device.meta.pulseLength = currentPulseLength;
                            meta.deviceExposesChanged();
                        }
                        if (updateDeviceMode) {
                            result.device_mode =
                                currentPulseLength === 0
                                    ? utils.getFromLookupByValue(0x00, deviceModesLookup)
                                    : utils.getFromLookupByValue(0x01, deviceModesLookup);
                        }
                        result.pulse_length = currentPulseLength;
                    }
                    return result;
                },
            },
        ];
        const toZigbee = [
            {
                key: ["pulse_length"],
                convertSet: async (entity, key, value, meta) => {
                    if (key === "pulse_length") {
                        const selectedPulseLength = (0, utils_1.toNumber)(value) * 10;
                        await entity.write("boschEnergyDevice", {
                            pulseLength: selectedPulseLength,
                        });
                        return { state: { pulse_length: value } };
                    }
                },
                convertGet: async (entity, key, meta) => {
                    if (key === "pulse_length") {
                        await entity.read("boschEnergyDevice", ["pulseLength"]);
                    }
                },
            },
        ];
        const configure = [
            async (device, coordinatorEndpoint, definition) => {
                const endpoint = device.getEndpoint(1);
                await endpoint.read("boschEnergyDevice", ["pulseLength"]);
            },
        ];
        return {
            exposes: [expose],
            fromZigbee,
            toZigbee,
            configure,
            isModernExtend: true,
        };
    },
    switchType: (args) => {
        const { switchTypeLookup } = args;
        const expose = (device, options) => {
            if (utils.isDummyDevice(device)) {
                return [];
            }
            let supportedSwitchTypes = Object.keys(switchTypeLookup);
            if (device.modelID === "RBSH-MMR-ZB-EU") {
                const pulseModeActive = device.getEndpoint(1).getClusterAttributeValue("boschEnergyDevice", "pulseLength") > 0;
                if (pulseModeActive) {
                    supportedSwitchTypes = Object.keys(switchTypeLookup).filter((switchType) => switchType !== utils.getFromLookup("rocker_switch", switchTypeLookup));
                }
            }
            const switchTypeExpose = e
                .enum("switch_type", ea.ALL, supportedSwitchTypes)
                .withLabel("Connected switch type")
                .withDescription("Select which switch type is connected to the module. Please keep in mind that the available options may depend on the selected device mode.")
                .withCategory("config");
            return [switchTypeExpose];
        };
        const fromZigbee = [
            {
                cluster: "boschEnergyDevice",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const result = {};
                    const data = msg.data;
                    if (data.switchType !== undefined) {
                        const switchType = data.switchType;
                        result.switch_type = utils.getFromLookupByValue(switchType, switchTypeLookup);
                        if (switchType !== meta.device.meta.switchType) {
                            meta.device.meta.switchType = switchType;
                            meta.deviceExposesChanged();
                        }
                    }
                    return result;
                },
            },
        ];
        const toZigbee = [
            {
                key: ["switch_type"],
                convertSet: async (entity, key, value, meta) => {
                    if (key === "switch_type") {
                        const selectedSwitchType = utils.getFromLookup(value, switchTypeLookup);
                        if (meta.device.meta.switchType !== selectedSwitchType) {
                            const endpoints = meta.device.endpoints.filter((e) => e.supportsInputCluster("boschEnergyDevice"));
                            for (const endpoint of endpoints) {
                                await endpoint.write("boschEnergyDevice", {
                                    switchMode: 0x00,
                                    childLock: 0x00,
                                });
                                await endpoint.read("boschEnergyDevice", ["switchMode", "childLock"]);
                            }
                        }
                        await entity.write("boschEnergyDevice", {
                            switchType: selectedSwitchType,
                        });
                        await entity.read("boschEnergyDevice", ["switchType"]);
                        return { state: { switch_type: value } };
                    }
                },
                convertGet: async (entity, key, meta) => {
                    if (key === "switch_type") {
                        await entity.read("boschEnergyDevice", ["switchType"]);
                    }
                },
            },
        ];
        const configure = [
            async (device, coordinatorEndpoint, definition) => {
                const endpoint = device.getEndpoint(1);
                await endpoint.read("boschEnergyDevice", ["switchType"]);
            },
        ];
        return {
            exposes: [expose],
            fromZigbee,
            toZigbee,
            configure,
            isModernExtend: true,
        };
    },
    reportSwitchAction: (args) => {
        const { switchTypeLookup, hasDualSwitchInputs } = args;
        const expose = (device, options) => {
            const exposeList = [];
            if (utils.isDummyDevice(device)) {
                return exposeList;
            }
            const switchTypeKey = device.getEndpoint(1).getClusterAttributeValue("boschEnergyDevice", "switchType") ?? 0x00;
            const selectedSwitchType = utils.getFromLookupByValue(switchTypeKey, switchTypeLookup);
            let supportedActionTypes;
            if (selectedSwitchType.includes("button")) {
                if (hasDualSwitchInputs) {
                    supportedActionTypes = [
                        "press_released_left",
                        "press_released_right",
                        "hold_left",
                        "hold_right",
                        "hold_released_left",
                        "hold_released_right",
                    ];
                }
                else {
                    supportedActionTypes = ["press_released", "hold", "hold_released"];
                }
                exposeList.push(e.action(supportedActionTypes), e.action_duration());
            }
            else if (selectedSwitchType.includes("rocker_switch")) {
                if (hasDualSwitchInputs) {
                    supportedActionTypes = ["opened_left", "opened_right", "closed_left", "closed_right"];
                }
                else {
                    supportedActionTypes = ["opened", "closed"];
                }
                exposeList.push(e.action(supportedActionTypes));
            }
            return exposeList;
        };
        const fromZigbee = [
            {
                cluster: "boschEnergyDevice",
                type: ["raw"],
                convert: (model, msg, publish, options, meta) => {
                    const command = msg.data[4];
                    if (command !== 0x03 && command !== 0x04) {
                        return;
                    }
                    let state;
                    const status = msg.data[5];
                    const duration = msg.data[6] / 10;
                    switch (status) {
                        case 0:
                            state = "press_released";
                            break;
                        case 1:
                            state = duration !== 0 ? "hold" : "hold_released";
                            break;
                        case 2:
                            state = "closed";
                            break;
                        case 3:
                            state = "opened";
                            break;
                    }
                    let action;
                    if (hasDualSwitchInputs) {
                        const triggeredSide = command === 0x03 ? "left" : "right";
                        action = `${state}_${triggeredSide}`;
                    }
                    else {
                        action = state;
                    }
                    return { action: action, action_duration: duration };
                },
            },
        ];
        return {
            exposes: [expose],
            fromZigbee,
            isModernExtend: true,
        };
    },
    slzExtends: () => {
        const stateDeviceMode = {
            light: 0x04,
            shutter: 0x01,
            disabled: 0x00,
        };
        const stateMotor = {
            stopped: 0x00,
            opening: 0x01,
            closing: 0x02,
            unknownOne: 0x03,
            unknownTwo: 0x04,
        };
        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 stateOffOn = {
            OFF: 0x00,
            ON: 0x01,
        };
        const fromZigbee = [
            fz.on_off_force_multiendpoint,
            fz.power_on_behavior,
            fz.cover_position_tilt,
            {
                cluster: "boschEnergyDevice",
                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) {
                        const switchType = msg.data.switchType;
                        result.switch_type = Object.keys(stateSwitchType).find((key) => stateSwitchType[key] === switchType);
                        if (switchType !== meta.device.meta.switchType) {
                            meta.device.meta.switchType = switchType;
                            meta.deviceExposesChanged();
                        }
                    }
                    if (data.switchMode !== undefined) {
                        const property = utils.postfixWithEndpointName("switch_mode", msg, model, meta);
                        result[property] = Object.keys(stateSwitchMode).find((key) => stateSwitchMode[key] === msg.data.switchMode);
                    }
                    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);
                    }
                    if (data.autoOffEnabled !== undefined) {
                        const property = utils.postfixWithEndpointName("auto_off_enabled", msg, model, meta);
                        result[property] = msg.data.autoOffEnabled === 1 ? "ON" : "OFF";
                    }
                    if (data.autoOffTime !== undefined) {
                        const property = utils.postfixWithEndpointName("auto_off_time", msg, model, meta);
                        result[property] = msg.data.autoOffTime / 60;
                    }
                    return result;
                },
            },
        ];
        const toZigbee = [
            tz.power_on_behavior,
            tz.cover_position_tilt,
            {
                key: [
                    "device_mode",
                    "switch_type",
                    "switch_mode",
                    "child_lock",
                    "state",
                    "on_time",
                    "off_wait_time",
                    "auto_off_enabled",
                    "auto_off_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("boschEnergyDevice", { deviceMode: index });
                        await entity.read("boschEnergyDevice", ["deviceMode"]);
                        return { state: { device_mode: value } };
                    }
                    if (key === "switch_type") {
                        const applyDefaultForSwitchModeAndChildLock = async (endpoint) => {
                            const switchModeDefault = utils.getFromLookup("coupled", stateSwitchMode);
                            const childLockDefault = utils.getFromLookup("OFF", stateOffOn);
                            await endpoint.write("boschEnergyDevice", {
                                switchMode: switchModeDefault,
                                childLock: childLockDefault,
                            });
                            await endpoint.read("boschEnergyDevice", ["switchMode", "childLock"]);
                        };
                        const switchType = utils.getFromLookup(value, stateSwitchType);
                        await entity.write("boschEnergyDevice", { switchType: switchType });
                        await entity.read("boschEnergyDevice", ["switchType"]);
                        await applyDefaultForSwitchModeAndChildLock(entity);
                        const leftEndpoint = meta.device.getEndpoint(2);
                        await applyDefaultForSwitchModeAndChildLock(leftEndpoint);
                        const rightEndpoint = meta.device.getEndpoint(3);
                        await applyDefaultForSwitchModeAndChildLock(rightEndpoint);
                        return { state: { switch_type: value } };
                    }
                    if (key === "switch_mode") {
                        const index = utils.getFromLookup(value, stateSwitchMode);
                        await entity.write("boschEnergyDevice", { switchMode: index });
                        return { state: { switch_mode: value } };
                    }
                    if (key === "child_lock") {
                        const index = utils.getFromLookup(value, stateOffOn);
                        await entity.write("boschEnergyDevice", { childLock: index });
                        return { state: { child_lock: value } };
                    }
                    if (key === "auto_off_enabled") {
                        const index = utils.getFromLookup(value, stateOffOn);
                        await entity.write("boschEnergyDevice", { autoOffEnabled: index });
                        return { state: { auto_off_enabled: value } };
                    }
                    if (key === "auto_off_time" && typeof value === "number") {
                        await entity.write("boschEnergyDevice", { autoOffTime: value * 60 });
                        return { state: { auto_off_time: 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("boschEnergyDevice", ["deviceMode"]);
                            break;
                        case "switch_type":
                            await entity.read("boschEnergyDevice", ["switchType"]);
                            break;
                        case "switch_mode":
                            await entity.read("boschEnergyDevice", ["switchMode"]);
                            break;
                        case "child_lock":
                            await entity.read("boschEnergyDevice", ["childLock"]);
                            break;
                        case "auto_off_enabled":
                            await entity.read("boschEnergyDevice", ["autoOffEnabled"]);
                            break;
                        case "auto_off_time":
                            await entity.read("boschEnergyDevice", ["autoOffTime"]);
                            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("boschEnergyDevice", { 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("boschEnergyDevice", { 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("boschEnergyDevice", {
                            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("boschEnergyDevice", {
                            calibrationMotorStartDelay: index,
                        });
                        return { state: { calibration_motor_start_delay: number } };
                    }
                },
                convertGet: async (entity, key, meta) => {
                    switch (key) {
                        case "calibration_opening_time":
                            await entity.read("boschEnergyDevice", ["calibrationOpeningTime"]);
                            break;
                        case "calibration_closing_time":
                            await entity.read("boschEnergyDevice", ["calibrationClosingTime"]);
                            break;
                        case "calibration_button_hold_time":
                            await entity.read("boschEnergyDevice", ["calibrationButtonHoldTime"]);
                            break;
                        case "calibration_motor_start_delay":
                            await entity.read("boschEnergyDevice", ["calibrationMotorStartDelay"]);
                            break;
                        default:
                            throw new Error(`Unhandled key boschExtend.bmct.toZigbee.convertGet ${key}`);
                    }
                },
            },
        ];
        const reportActionExtend = exports.boschBmctExtend.reportSwitchAction({
            switchTypeLookup: stateSwitchType,
            hasDualSwitchInputs: true,
        });
        if (reportActionExtend.fromZigbee !== undefined) {
            fromZigbee.push(...reportActionExtend.fromZigbee);
        }
        return {
            fromZigbee,
            toZigbee,
            isModernExtend: true,
        };
    },
};
exports.boschBsirExtend = {
    customPowerCfgCluster: () => m.deviceAddCustomCluster("genPowerCfg", {
        name: "genPowerCfg",
        ID: zigbee_herdsman_1.Zcl.Clusters.genPowerCfg.ID,
        attributes: {
            solarPanelVoltage: {
                name: "solarPanelVoltage",
                ID: 0xa000,
                type: zigbee_herdsman_1.Zcl.DataType.UINT16,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xffff,
            },
            unknownAttribute: {
                name: "unknownAttribute",
                ID: 0xa001,
                type: zigbee_herdsman_1.Zcl.DataType.UINT8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            primaryPowerSource: {
                name: "primaryPowerSource",
                ID: 0xa002,
                type: zigbee_herdsman_1.Zcl.DataType.UINT8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
        },
        commands: {},
        commandsResponse: {},
    }),
    customIasZoneCluster: () => m.deviceAddCustomCluster("ssIasZone", {
        name: "ssIasZone",
        ID: zigbee_herdsman_1.Zcl.Clusters.ssIasZone.ID,
        attributes: {
            currentPowerSource: {
                name: "currentPowerSource",
                ID: 0xa001,
                type: zigbee_herdsman_1.Zcl.DataType.UINT8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
        },
        commands: {
            acknowledgeStatusChange: {
                name: "acknowledgeStatusChange",
                ID: 0xf3,
                parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.DataType.UINT8, max: 0xff }],
            },
        },
        commandsResponse: {},
    }),
    customIasWdCluster: () => m.deviceAddCustomCluster("ssIasWd", {
        name: "ssIasWd",
        ID: zigbee_herdsman_1.Zcl.Clusters.ssIasWd.ID,
        attributes: {
            sirenDuration: {
                name: "sirenDuration",
                ID: 0xa000,
                type: zigbee_herdsman_1.Zcl.DataType.UINT8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            alarmMode: {
                name: "alarmMode",
                ID: 0xa001,
                type: zigbee_herdsman_1.Zcl.DataType.UINT8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            sirenVolume: {
                name: "sirenVolume",
                ID: 0xa002,
                type: zigbee_herdsman_1.Zcl.DataType.UINT8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            sirenDelay: {
                name: "sirenDelay",
                ID: 0xa003,
                type: zigbee_herdsman_1.Zcl.DataType.UINT16,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xffff,
            },
            lightDelay: {
                name: "lightDelay",
                ID: 0xa004,
                type: zigbee_herdsman_1.Zcl.DataType.UINT16,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xffff,
            },
            lightDuration: {
                name: "lightDuration",
                ID: 0xa005,
                type: zigbee_herdsman_1.Zcl.DataType.UINT8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            deviceState: {
                name: "deviceState",
                ID: 0xa006,
                type: zigbee_herdsman_1.Zcl.DataType.UINT8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
        },
        commands: {
            alarmControl: {
                name: "alarmControl",
                ID: 0xf0,
                parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.DataType.UINT8, max: 0xff }],
            },
        },
        commandsResponse: {},
    }),
    alarmControl: () => {
        const exposes = [
            e
                .enum("trigger_alarm", ea.SET, ["trigger"])
                .withLabel("Trigger alarm")
                .withDescription("Trigger an alarm on the device")
                .withCategory("config"),
            e
                .enum("stop_alarm", ea.SET, ["stop"])
                .withLabel("Stop alarm")
                .withDescription("Stop an active alarm on the device. Please keep in mind that the alarm stops automatically after the configured duration for the light and siren is expired.")
                .withCategory("config"),
        ];
        const toZigbee = [
            {
                key: ["trigger_alarm", "stop_alarm"],
                convertSet: async (entity, key, value, meta) => {
                    if (key === "trigger_alarm") {
                        await entity.command("ssIasWd", "alarmControl", { data: 0x07 }, exports.manufacturerOptions);
                    }
                    if (key === "stop_alarm") {
                        await entity.command("ssIasWd", "alarmControl", { data: 0x00 }, exports.manufacturerOptions);
                    }
                },
            },
        ];
        const configure = [
            async (device, coordinatorEndpoint, definition) => {
                const endpoint = device.getEndpoint(1);
                await endpoint.bind("ssIasWd", coordinatorEndpoint);
            },
        ];
        return {
            exposes,
            toZigbee,
            configure,
            isModernExtend: true,
        };
    },
    deviceState: () => m.enumLookup({
        name: "device_state",
        cluster: "ssIasWd",
        attribute: "deviceState",
        description: "Current state of the siren and light. Please keep in mind that these activate after the specified delay time (except when using an external alarm trigger).",
        lookup: {
            siren_active_from_external_trigger: 0x05,
            light_active_from_external_trigger: 0x06,
            siren_and_light_active_from_external_trigger: 0x07,
            siren_active: 0x09,
            light_active: 0x0a,
            siren_and_light_active: 0x0b,
            idle: 0x00,
        },
        access: "STATE_GET",
    }),
    lightDelay: () => m.numeric({
        name: "light_delay",
        cluster: "ssIasWd",
        attribute: "lightDelay",
        description: "Delay of the light activation after an alarm is being triggered",
        valueMin: 0,
        valueMax: 180,
        valueStep: 1,
        unit: "sec",
        entityCategory: "config",
    }),
    sirenDelay: () => m.numeric({
        name: "siren_delay",
        cluster: "ssIasWd",
        attribute: "sirenDelay",
        description: "Delay of the siren activation after an alarm is being triggered",
        valueMin: 0,
        valueMax: 180,
        valueStep: 1,
        unit: "sec",
        entityCategory: "config",
    }),
    sirenDuration: () => m.numeric({
        name: "siren_duration",
        cluster: "ssIasWd",
        attribute: "sirenDuration",
        description: "Duration of the alarm siren",
        valueMin: 1,
        valueMax: 15,
        valueStep: 1,
        unit: "min",
        entityCategory: "config",
    }),
    lightDuration: () => m.numeric({
        name: "light_duration",
        cluster: "ssIasWd",
        attribute: "lightDuration",
        description: "Duration of the alarm light",
        valueMin: 1,
        valueMax: 15,
        valueStep: 1,
        unit: "min",
        entityCategory: "config",
    }),
    sirenVolume: () => m.enumLookup({
        name: "siren_volume",
        cluster: "ssIasWd",
        attribute: "sirenVolume",
        description: "Volume of the siren",
        lookup: {
            reduced: 0x01,
            medium: 0x02,
            loud: 0x03,
        },
        entityCategory: "config",
    }),
    alarmMode: () => m.enumLookup({
        name: "alarm_mode",
        cluster: "ssIasWd",
        attribute: "alarmMode",
        description: "Select if you only want a visual warning, an acoustic warning or both",
        lookup: {
            only_light: 0x00,
            only_siren: 0x01,
            siren_and_light: 0x02,
        },
        entityCategory: "config",
    }),
    primaryPowerSource: () => m.enumLookup({
        name: "primary_power_source",
        cluster: "genPowerCfg",
        attribute: "primaryPowerSource",
        description: "Select which power source you want to use. Note: The battery is always being used as backup source.",
        lookup: {
            solar_panel: 0x00,
            ac_power_supply: 0x01,
            dc_power_supply: 0x02,
        },
        reporting: { min: "MIN", max: "MAX", change: 1 },
        entityCategory: "config",
    }),
    iasZoneStatus: () => {
        const powerOutageLookup = {
            outage_detected: true,
            power_ok: false,
        };
        const exposes = [
            e
                .binary("external_trigger", ea.STATE, true, false)
                .withLabel("External trigger state")
                .withDescription("Indicates whether an external alarm via the 'TRIGGER_IN' connectors on the back of the device is being received. Please keep in mind that the device automatically activates/deactivates an alarm in that case."),
            e
                .binary("tamper", ea.STATE, true, false)
                .withLabel("Tamper state")
                .withDescription("Indicates whether the device is tampered")
                .withCategory("diagnostic"),
            e
                .binary("power_outage", ea.STATE, utils.getFromLookupByValue(true, powerOutageLookup), utils.getFromLookupByValue(false, powerOutageLookup))
                .withLabel("Power outage state")
                .withDescription("Indicates the configured primary power source experiences a power outage. This only works when using ac or dc power.")
                .withCategory("diagnostic"),
        ];
        const fromZigbee = [
            {
                cluster: "ssIasZone",
                type: ["commandStatusChangeNotification"],
                convert: (model, msg, publish, options, meta) => {
                    if (utils.hasAlreadyProcessedMessage(msg, model)) {
                        return;
                    }
                    const zoneStatus = msg.data.zonestatus;
                    const alarmOneStatus = (zoneStatus & 1) > 0;
                    const tamperStatus = (zoneStatus & (1 << 2)) > 0;
                    const alarmTwoStatus = (zoneStatus & (1 << 1)) > 0;
                    if (tamperStatus) {
                        meta.device
                            .getEndpoint(1)
                            .command("ssIasZone", "acknowledgeStatusChange", { data: 0x02 }, exports.manufacturerOptions)
                            .catch((e) => {
                            logger_1.logger.warning(`Acknowledgement of tamper status on device '${meta.device.ieeeAddr}' failed: ${e}`, NS);
                        });
                    }
                    if (alarmTwoStatus) {
                        meta.device
                            .getEndpoint(1)
                            .command("ssIasZone", "acknowledgeStatusChange", { data: 0x04 }, exports.manufacturerOptions)
                            .catch((e) => {
                            logger_1.logger.warning(`Acknowledgement of alarm 2 status on device '${meta.device.ieeeAddr}' failed: ${e}`, NS);
                        });
                    }
                    return {
                        external_trigger: alarmOneStatus,
                        tamper: tamperStatus,
                        power_outage: utils.getFromLookupByValue(alarmTwoStatus, powerOutageLookup),
                    };
                },
            },
        ];
        const configure = [
            async (device, coordinatorEndpoint, definition) => {
                const endpoint = device.getEndpoint(1);
                await endpoint.read("ssIasZone", ["zoneStatus"]);
            },
        ];
        return {
            exposes,
            fromZigbee,
            configure,
            isModernExtend: true,
        };
    },
    currentPowerSource: () => m.enumLookup({
        name: "current_power_source",
        cluster: "ssIasZone",
        attribute: "currentPowerSource",
        description: "Currently used power source for device operation",
        lookup: {
            battery: 0x00,
            solar_panel: 0x01,
            ac_power: 0x02,
            dc_power: 0x03,
        },
        reporting: { min: "MIN", max: "MAX", change: 1 },
        access: "STATE_GET",
        entityCategory: "diagnostic",
    }),
    solarPanelVoltage: () => {
        const exposes = [
            e
                .numeric("solar_panel_voltage", ea.STATE)
                .withDescription("Current voltage level received from the integrated solar panel")
                .withUnit("volt")
                .withCategory("diagnostic"),
        ];
        const fromZigbee = [
            {
                cluster: "genPowerCfg",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    if (utils.hasAlreadyProcessedMessage(msg, model)) {
                        return;
                    }
                    const data = msg.data;
                    const containsSolarPanelVoltage = data.solarPanelVoltage !== undefined;
                    if (containsSolarPanelVoltage) {
                        const currentSolarPanelVoltage = data.solarPanelVoltage / 10;
                        return { solar_panel_voltage: currentSolarPanelVoltage };
                    }
                },
            },
        ];
        const configure = [
            async (device, coordinatorEndpoint, definition) => {
                const endpoint = device.getEndpoint(1);
                const solarPanelVoltageReportingPayload = (0, reporting_1.payload)("solarPanelVoltage", constants_1.repInterval.MINUTES_5, constants_1.repInterval.MAX, 1);
                await endpoint.configureReporting("genPowerCfg", solarPanelVoltageReportingPayload);
            },
        ];
        return { exposes, fromZigbee, configure, isModernExtend: true };
    },
};
exports.boschDoorWindowContactExtend = {
    doorWindowContactCluster: () => m.deviceAddCustomCluster("boschDoorWindowContactCluster", {
        name: "boschDoorWindowContactCluster",
        ID: 0xfcad,
        attributes: {
            breakFunctionEnabled: {
                name: "breakFunctionEnabled",
                ID: 0x0000,
                type: zigbee_herdsman_1.Zcl.DataType.UINT8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            breakFunctionState: {
                name: "breakFunctionState",
                ID: 0x0001,
                type: zigbee_herdsman_1.Zcl.DataType.UINT8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            breakFunctionTimeout: {
                name: "breakFunctionTimeout",
                ID: 0x0002,
                type: zigbee_herdsman_1.Zcl.DataType.UINT8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            vibrationDetectionEnabled: {
                name: "vibrationDetectionEnabled",
                ID: 0x0004,
                type: zigbee_herdsman_1.Zcl.DataType.UINT8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            vibrationDetectionSensitivity: {
                name: "vibrationDetectionSensitivity",
                ID: 0x0005,
                type: zigbee_herdsman_1.Zcl.DataType.UINT8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            unknownOne: {
                name: "unknownOne",
                ID: 0x0007,
                type: zigbee_herdsman_1.Zcl.DataType.UINT8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            unknownTwo: {
                name: "unknownTwo",
                ID: 0x0008,
                type: zigbee_herdsman_1.Zcl.DataType.UINT16,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xffff,
            },
            unknownThree: {
                name: "unknownThree",
                ID: 0x0009,
                type: zigbee_herdsman_1.Zcl.DataType.UINT8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            unknownFour: {
                name: "unknownFour",
                ID: 0x000a,
                type: zigbee_herdsman_1.Zcl.DataType.UINT8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
        },
        commands: {},
        commandsResponse: {},
    }),
    reportContactState: () => m.iasZoneAlarm({
        zoneType: "contact",
        zoneAttributes: ["alarm_1"],
        description: "Indicates whether the device detected an open or closed door/window",
    }),
    reportButtonActions: (args) => {
        const { doublePressSupported } = args ?? { doublePressSupported: false };
        let buttonActionsLookup = {
            long_press: 0x02,
            single_press: 0x01,
            none: 0x00,
        };
        if (doublePressSupported) {
            buttonActionsLookup = { ...{ double_press: 0x08 }, ...buttonActionsLookup };
        }
        const exposes = [
            e
                .enum("action", ea.STATE, Object.keys(buttonActionsLookup))
                .withDescription("Indicates button presses on the device")
                .withCategory("diagnostic"),
        ];
        const fromZigbee = [
            {
                cluster: "ssIasZone",
                type: ["commandStatusChangeNotification", "attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const zoneStatus = "zonestatus" in msg.data ? msg.data.zonestatus : msg.data.zoneStatus;
                    if (zoneStatus !== undefined) {
                        const buttonPayload = zoneStatus >> 11;
                        const buttonState = utils.getFromLookupByValue(buttonPayload, buttonActionsLookup);
                        const result = {
                            action: buttonState,
                        };
                        return result;
                    }
                },
            },
        ];
        const configure = [m.setupConfigureForBinding("ssIasZone", "input"), m.setupConfigureForReading("ssIasZone", ["zoneStatus"])];
        return {
            exposes,
            fromZigbee,
            configure,
            isModernExtend: true,
        };
    },
    breakFunctionality: () => {
        const breakFunctionEnabledLookup = {
            ON: 0x01,
            OFF: 0x00,
        };
        const breakFunctionStatusLookup = {
            break_active: 0x01,
            idle: 0x00,
        };
        const exposes = [
            e
                .binary("break_function_enabled", ea.ALL, utils.getFromLookupByValue(0x01, breakFunctionEnabledLookup), utils.getFromLookupByValue(0x00, breakFunctionEnabledLookup))
                .withLabel("Break function")
                .withDescription("Activate the break function by pressing the operating button on the door/window contact twice. This means that the device temporarily stops reading the sensors.")
                .withCategory("config"),
            e
                .numeric("break_function_timeout", ea.ALL)
                .withLabel("Automatic time limit for breaks")
                .withDescription("Here you can define how long the break function is activated for the door/window contact. Once the time limit has expired, the break ends automatically. The LED on the device will flash orange as long as the break is activated when this setting is being used.")
                .withValueMin(1)
                .withValueMax(15)
                .withUnit("minutes")
                .withPreset("disable", null, "Disable automatic time limit")
                .withCategory("config"),
            e
                .enum("break_function_state", ea.STATE_GET, Object.keys(breakFunctionStatusLookup))
                .withLabel("Break function state")
                .withDescription("Indicates whether the device is in break mode or not"),
        ];
        const fromZigbee = [
            {
                cluster: "boschDoorWindowContactCluster",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const result = {};
                    const data = msg.data;
                    if (data.breakFunctionEnabled !== undefined) {
                        result.break_function_enabled = utils.getFromLookupByValue(data.breakFunctionEnabled, breakFunctionEnabledLookup);
                    }
                    if (data.breakFunctionTimeout !== undefined) {
                        result.break_function_timeout = data.breakFunctionTimeout === 0xff ? null : data.breakFunctionTimeout;
                    }
                    if (data.breakFunctionState !== undefined) {
                        result.break_function_state = utils.getFromLookupByValue(data.breakFunctionState, breakFunctionStatusLookup);
                    }
                    return result;
                },
            },
        ];
        const toZigbee = [
            {
                key: ["break_function_enabled", "break_function_timeout", "break_function_state"],
                convertSet: async (entity, key, value, meta) => {
                    if (key === "break_function_enabled") {
                        await entity.write("boschDoorWindowContactCluster", {
                            breakFunctionEnabled: utils.getFromLookup(value, breakFunctionEnabledLookup),
                        });
                        return { state: { break_function_enabled: value } };
                    }
                    if (key === "break_function_timeout") {
                        const index = value === null ? 0xff : utils.toNumber(value);
                        await entity.write("boschDoorWindowContactCluster", {
                            breakFunctionTimeout: index,
                        });
                        return { state: { break_function_timeout: value } };
                    }
                },
                convertGet: async (entity, key, meta) => {
                    if (key === "break_function_enabled") {
                        await entity.read("boschDoorWindowContactCluster", [
                            "breakFunctionEnabled",
                        ]);
                    }
                    if (key === "break_function_timeout") {
                        await entity.read("boschDoorWindowContactCluster", [
                            "breakFunctionTimeout",
                        ]);
                    }
                    if (key === "break_function_state") {
                        await entity.read("boschDoorWindowContactCluster", [
                            "breakFunctionState",
                        ]);
                    }
                },
            },
        ];
        const configure = [
            m.setupConfigureForReading("boschDoorWindowContactCluster", [
                "breakFunctionEnabled",
                "breakFunctionTimeout",
                "breakFunctionState",
            ]),
        ];
        return {
            exposes,
            fromZigbee,
            toZigbee,
            configure,
            isModernExtend: true,
        };
    },
    vibrationDetection: () => {
        const vibrationDetectionEnabledLookup = {
            ON: 0x01,
            OFF: 0x00,
        };
        const vibrationDetectionSensitivityLookup = {
            very_high: 0x05,
            high: 0x04,
            medium: 0x03,
            moderate: 0x02,
            low: 0x01,
        };
        const exposes = [
            e
                .binary("vibration_detection_enabled", ea.ALL, utils.getFromLookupByValue(0x01, vibrationDetectionEnabledLookup), utils.getFromLookupByValue(0x00, vibrationDetectionEnabledLookup))
                .withLabel("Vibration detection")
                .withDescription("Activate the vibration detection to detect vibrations at the window or door via the integrated sensor as well")
                .withCategory("config"),
            e
                .enum("vibration_detection_sensitivity", ea.ALL, Object.keys(vibrationDetectionSensitivityLookup))
                .withLabel("Vibration detection sensitivity")
                .withDescription("Set the sensitivity of the vibration detection sensor")
                .withCategory("config"),
            e.binary("vibration", ea.STATE_GET, true, false).withDescription("Indicates whether the device detected vibration"),
        ];
        const fromZigbee = [
            {
                cluster: "boschDoorWindowContactCluster",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const result = {};
                    const data = msg.data;
                    if (data.vibrationDetectionEnabled !== undefined) {
                        result.vibration_detection_enabled = utils.getFromLookupByValue(data.vibrationDetectionEnabled, vibrationDetectionEnabledLookup);
                    }
                    if (data.vibrationDetectionSensitivity !== undefined) {
                        result.vibration_detection_sensitivity = utils.getFromLookupByValue(data.vibrationDetectionSensitivity, vibrationDetectionSensitivityLookup);
                    }
                    return result;
                },
            },
            {
                cluster: "ssIasZone",
                type: ["commandStatusChangeNotification", "attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const zoneStatus = "zonestatus" in msg.data ? msg.data.zonestatus : msg.data.zoneStatus;
                    if (zoneStatus !== undefined) {
                        const alarm2Payload = (zoneStatus & (1 << 1)) > 0;
                        return {
                            vibration: alarm2Payload,
                        };
                    }
                },
            },
        ];
        const toZigbee = [
            {
                key: ["vibration_detection_enabled", "vibration_detection_sensitivity", "vibration"],
                convertSet: async (entity, key, value, meta) => {
                    if (key === "vibration_detection_enabled") {
                        await entity.write("boschDoorWindowContactCluster", {
                            vibrationDetectionEnabled: utils.getFromLookup(value, vibrationDetectionEnabledLookup),
                        });
                        return { state: { vibration_detection_enabled: value } };
                    }
                    if (key === "vibration_detection_sensitivity") {
                        await entity.write("boschDoorWindowContactCluster", {
                            vibrationDetectionSensitivity: utils.getFromLookup(value, vibrationDetectionSensitivityLookup),
                        });
                        return { state: { vibration_detection_sensitivity: value } };
                    }
                },
                convertGet: async (entity, key, meta) => {
                    if (key === "vibration_detection_enabled") {
                        await entity.read("boschDoorWindowContactCluster", [
                            "vibrationDetectionEnabled",
                        ]);
                    }
                    if (key === "vibration_detection_sensitivity") {
                        await entity.read("boschDoorWindowContactCluster", [
                            "vibrationDetectionSensitivity",
                        ]);
                    }
                    if (key === "vibration") {
                        await entity.read("ssIasZone", ["zoneStatus"]);
                    }
                },
            },
        ];
        const configure = [
            m.setupConfigureForBinding("ssIasZone", "input"),
            m.setupConfigureForReading("ssIasZone", ["zoneStatus"]),
            async (device, coordinatorEndpoint, definition) => {
                const endpoint = device.getEndpoint(1);
                // The write request is made when using the proprietary
                // Bosch Smart Home Controller II as of 19-09-2025. Looks like
                // the default value was too high, and they didn't want to
                // push a firmware update. We mimic it here to avoid complaints.
                if (device.meta.newDefaultSensitivityApplied === undefined) {
                    await endpoint.write("boschDoorWindowContactCluster", {
                        vibrationDetectionSensitivity: vibrationDetectionSensitivityLookup.medium,
                    });
                    device.meta.newDefaultSensitivityApplied = true;
                }
                // The write request is made when using the proprietary
                // Bosch Smart Home Controller II as of 19-09-2025. I have
                // no idea what it does, but we mimic it here in case it
                // fixes any issues.
                await endpoint.write("boschDoorWindowContactCluster", {
                    unknownOne: 0x00,
                });
            },
            m.setupConfigureForReading("boschDoorWindowContactCluster", [
                "vibrationDetectionEnabled",
                "vibrationDetectionSensitivity",
            ]),
        ];
        return {
            exposes,
            fromZigbee,
            toZigbee,
            configure,
            isModernExtend: true,
        };
    },
};
//endregion
//region Bosch BSEN-M device (Motion detector)
exports.boschBsenExtend = {
    battery: () => m.battery({
        percentage: false,
        percentageReporting: false,
        voltage: true,
        voltageReporting: true,
        voltageToPercentage: { min: 2500, max: 3000 },
        lowStatus: true,
        lowStatusReportingConfig: { min: "MIN", max: "MAX", change: null },
    }),
    testMode: () => exports.boschGeneralSensorDeviceExtend.testMode({
        testModeDescription: "Activates the test mode. In this mode, the device blinks on every detected motion " +
            "without any wait time in between to verify the installation. Please keep in mind " +
            "that it can take up to 45 seconds for the test mode to be activated.",
        sensitivityLevelToUse: 0x80,
    }),
    illuminance: () => m.illuminance({ reporting: { min: "1_SECOND", max: 600, change: 3522 } }),
    // The temperature sensor isn't used at all by Bosch on the BSEN-M.
    // Therefore, I decided to be a bit conservative with the reporting
    // intervals to not drain the battery too much.
    temperature: () => m.temperature({ reporting: { min: "5_MINUTES", max: "MAX", change: 100 } }),
    tamperAndOccupancyAlarm: () => {
        const exposes = [
            e
                .binary("tamper", ea.STATE, true, false)
                .withLabel("Tamper state")
                .withDescription("Indicates whether the device is tampered")
                .withCategory("diagnostic"),
            e
                .binary("occupancy", ea.STATE, true, false)
                .withLabel("Occupancy state")
                .withDescription("Indicates whether the device detected any motion in the surroundings"),
        ];
        const fromZigbee = [
            {
                cluster: "ssIasZone",
                type: ["commandStatusChangeNotification", "attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const zoneStatus = "zonestatus" in msg.data ? msg.data.zonestatus : msg.data.zoneStatus;
                    if (zoneStatus === undefined) {
                        return;
                    }
                    let payload = {};
                    const tamperStatus = (zoneStatus & (1 << 2)) > 0;
                    payload = { tamper: tamperStatus, ...payload };
                    const occupancyLockActive = meta.device.meta.occupancyLockTimeout ? meta.device.meta.occupancyLockTimeout > Date.now() : false;
                    if (!occupancyLockActive) {
                        const alarmOneStatus = (zoneStatus & 1) > 0;
                        payload = { occupancy: alarmOneStatus, ...payload };
                        const isChangeMessage = msg.type === "commandStatusChangeNotification";
                        const newOccupancyStatusDetected = alarmOneStatus === true;
                        if (isChangeMessage && newOccupancyStatusDetected) {
                            // After a detection, the device turns off the motion detection for 3 minutes.
                            // Unfortunately, the alarm is already turned off after 4 seconds for reasons
                            // only known to Bosch. Therefore, we have to manually defer the turn-off by
                            // 4 seconds + 3 minutes to avoid any confusion.
                            const timeoutDelay = 184 * 1000;
                            setTimeout(() => publish({ occupancy: false }), timeoutDelay);
                            meta.device.meta.occupancyLockTimeout = Date.now() + timeoutDelay;
                        }
                    }
                    return payload;
                },
            },
        ];
        const configure = [m.setupConfigureForBinding("ssIasZone", "input"), m.setupConfigureForReading("ssIasZone", ["zoneStatus"])];
        const onEvent = [
            async (event) => {
                if (event.type !== "start") {
                    return;
                }
                const occupancyLockTimeout = event.data.device.meta.occupancyLockTimeout;
                if (occupancyLockTimeout === undefined) {
                    return;
                }
                const currentTime = Date.now();
                const endpoint = event.data.device.getEndpoint(1);
                if (occupancyLockTimeout > currentTime) {
                    const timeoutDelay = occupancyLockTimeout - currentTime;
                    setTimeout(() => {
                        endpoint.read("ssIasZone", ["zoneStatus"]).catch((exception) => {
                            logger_1.logger.warning(`Error during reading the zoneStatus on device '${event.data.device.ieeeAddr}': ${exception}`, NS);
                        });
                    }, timeoutDelay);
                }
                else {
                    await endpoint.read("ssIasZone", ["zoneStatus"]);
                }
            },
        ];
        return {
            exposes,
            fromZigbee,
            configure,
            onEvent,
            isModernExtend: true,
        };
    },
    sensitivityLevel: () => {
        const sensitivityLevelLookup = {
            pet_immunity: 0xb8,
            sneak_by_guard: 0xb0,
            unknown: 0x00,
        };
        const exposes = [
            e
                .enum("sensitivity_level", ea.STATE_GET, Object.keys(sensitivityLevelLookup))
                .withDescription("Specifies the selected sensitivity level on the back of the device (either 'pet immunity' or 'sneak-by guard').")
                .withCategory("diagnostic"),
        ];
        const fromZigbee = [
            {
                cluster: "ssIasZone",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const result = {};
                    const data = msg.data;
                    if (data.currentZoneSensitivityLevel !== undefined) {
                        result.sensitivity_level = utils.getFromLookupByValue(data.currentZoneSensitivityLevel, sensitivityLevelLookup, "unknown");
                    }
                    return result;
                },
            },
        ];
        const toZigbee = [
            {
                key: ["sensitivity_level"],
                convertGet: async (entity, key, meta) => {
                    await entity.read("ssIasZone", ["currentZoneSensitivityLevel"]);
                },
            },
        ];
        const configure = [
            m.setupConfigureForBinding("ssIasZone", "input"),
            m.setupConfigureForReading("ssIasZone", ["numZoneSensitivityLevelsSupported", "currentZoneSensitivityLevel"]),
            async (device, coordinatorEndpoint, definition) => {
                const endpoint = device.getEndpoint(1);
                // The write request is made when using the proprietary
                // Bosch Smart Home Controller II as of 15-09-2025. Looks like
                // the default value was too low, and they didn't want to
                // push a firmware update. We mimic it here to avoid complaints.
                await endpoint.write("ssIasZone", { currentZoneSensitivityLevel: 176 });
            },
        ];
        return {
            exposes,
            fromZigbee,
            toZigbee,
            configure,
            isModernExtend: true,
        };
    },
    changedCheckinInterval: () => {
        const configure = [
            m.setupConfigureForReading("genPollCtrl", ["checkinInterval", "longPollInterval", "shortPollInterval"]),
            async (device, coordinatorEndpoint, definition) => {
                const endpoint = device.getEndpoint(1);
                // The write request is made when using the proprietary
                // Bosch Smart Home Controller II as of 15-09-2025.
                // The reason is unclear to me, but we mimic it here
                // to avoid possible complaints in case it fixed any issues.
                await endpoint.write("genPollCtrl", { checkinInterval: 2160 });
            },
        ];
        return {
            configure,
            isModernExtend: true,
        };
    },
};
exports.boschWaterAlarmExtend = {
    waterAlarmCluster: () => m.deviceAddCustomCluster("boschWaterAlarm", {
        name: "boschWaterAlarm",
        ID: 0xfcac,
        manufacturerCode: exports.manufacturerOptions.manufacturerCode,
        attributes: {
            alarmOnMotion: { name: "alarmOnMotion", ID: 0x0003, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN, write: true },
        },
        commands: {
            muteAlarmControl: { name: "muteAlarmControl", ID: 0x00, parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.DataType.UINT8, max: 0xff }] },
            muteAlarmControlResponse: {
                name: "muteAlarmControlResponse",
                ID: 0x01,
                parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.DataType.ENUM8, max: 0xff }],
            },
        },
        commandsResponse: {},
    }),
    changedSensitivityLevel: () => {
        const configure = [
            m.setupConfigureForBinding("ssIasZone", "input"),
            m.setupConfigureForReading("ssIasZone", ["numZoneSensitivityLevelsSupported", "currentZoneSensitivityLevel"]),
            async (device, coordinatorEndpoint, definition) => {
                const endpoint = device.getEndpoint(1);
                // The write request is made when using the proprietary
                // Bosch Smart Home Controller II as of 16-10-2025. Looks like
                // the default value was too high, and they didn't want to
                // push a firmware update. We mimic it here to avoid complaints.
                await endpoint.write("ssIasZone", { currentZoneSensitivityLevel: 5 });
            },
        ];
        return {
            configure,
            isModernExtend: true,
        };
    },
    waterAndTamperAlarm: () => m.iasZoneAlarm({
        zoneType: "water_leak",
        zoneAttributes: ["alarm_1", "tamper"],
    }),
    muteAlarmControl: () => {
        const muteAlarmControlLookup = {
            UNMUTED: false,
            MUTED: true,
        };
        const muteAlarmControlResponseLookup = {
            muted: 0x00,
            error: 0x01,
            no_change: 0x02,
            unmuted: 0x03,
        };
        const exposes = [
            e
                .binary("water_leak_alarm_control", ea.ALL, utils.getFromLookupByValue(true, muteAlarmControlLookup), utils.getFromLookupByValue(false, muteAlarmControlLookup))
                .withLabel("Mute water leak alarm")
                .withDescription("In case of an water leak, you can mute and unmute the audible alarm here"),
        ];
        const toZigbee = [
            {
                key: ["water_leak_alarm_control"],
                convertSet: async (entity, key, value, meta) => {
                    if (value === utils.getFromLookupByValue(false, muteAlarmControlLookup)) {
                        await entity.command("boschWaterAlarm", "muteAlarmControl", { data: 0x00 }, exports.manufacturerOptions);
                    }
                    else {
                        await entity.command("boschWaterAlarm", "muteAlarmControl", { data: 0x01 }, exports.manufacturerOptions);
                    }
                },
                convertGet: async (entity, key, meta) => {
                    await entity.read("ssIasZone", ["zoneStatus"]);
                },
            },
        ];
        const fromZigbee = [
            {
                cluster: "boschWaterAlarm",
                type: ["raw"],
                convert: (model, msg, publish, options, meta) => {
                    const command = msg.data[4];
                    if (command !== 0x01) {
                        return;
                    }
                    const muteAlarmControlResponse = msg.data[5];
                    switch (muteAlarmControlResponse) {
                        case muteAlarmControlResponseLookup.muted:
                            logger_1.logger.debug(`Alarm on device '${meta.device.ieeeAddr}' was muted`, NS);
                            break;
                        case muteAlarmControlResponseLookup.error:
                            logger_1.logger.error(`Alarm on device '${meta.device.ieeeAddr}' could not be muted right now (e.g., no active alarm)!`, NS);
                            break;
                        case muteAlarmControlResponseLookup.no_change:
                            logger_1.logger.debug(`Alarm on device '${meta.device.ieeeAddr}' is already in requested state`, NS);
                            break;
                        case muteAlarmControlResponseLookup.unmuted:
                            logger_1.logger.debug(`Alarm on device '${meta.device.ieeeAddr}' was unmuted`, NS);
                            break;
                    }
                },
            },
            {
                cluster: "ssIasZone",
                type: ["commandStatusChangeNotification", "attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const zoneStatus = "zonestatus" in msg.data ? msg.data.zonestatus : msg.data.zoneStatus;
                    if (zoneStatus === undefined) {
                        return;
                    }
                    const result = {};
                    const alarmMuted = (zoneStatus & (1 << 1)) > 0;
                    result.water_leak_alarm_control = utils.getFromLookupByValue(alarmMuted, muteAlarmControlLookup);
                    return result;
                },
            },
        ];
        const configure = [m.setupConfigureForBinding("ssIasZone", "input"), m.setupConfigureForReading("ssIasZone", ["zoneStatus"])];
        return {
            exposes,
            toZigbee,
            fromZigbee,
            configure,
            isModernExtend: true,
        };
    },
    alarmOnMotion: () => m.binary({
        name: "alarm_on_motion",
        cluster: "boschWaterAlarm",
        attribute: "alarmOnMotion",
        description: "If your water alarm is moved, an acoustic signal sounds",
        valueOn: ["ON", 0x01],
        valueOff: ["OFF", 0x00],
        entityCategory: "config",
    }),
    testMode: () => exports.boschGeneralSensorDeviceExtend.testMode({
        testModeDescription: "Activates the test mode. In this mode, the device acts like it would when " +
            "detecting any water to verify the installation. Please keep in mind " +
            "that it can take up to 10 seconds for the test mode to be activated.",
        sensitivityLevelToUse: 0x00,
        variableTimeoutSupported: true,
        defaultTimeout: 3,
    }),
};
exports.boschSmokeAlarmExtend = {
    customIasZoneCluster: () => m.deviceAddCustomCluster("ssIasZone", {
        name: "ssIasZone",
        ID: zigbee_herdsman_1.Zcl.Clusters.ssIasZone.ID,
        attributes: {
            unknownAttribute1: {
                name: "unknownAttribute1",
                ID: 0x8f01,
                type: zigbee_herdsman_1.Zcl.DataType.UINT8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            unknownAttribute2: {
                name: "unknownAttribute2",
                ID: 0x8f06,
                type: zigbee_herdsman_1.Zcl.DataType.UINT8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
        },
        commands: {
            alarmControl: {
                name: "alarmControl",
                ID: 0x80,
                parameters: [
                    { name: "alarmMode", type: zigbee_herdsman_1.Zcl.DataType.ENUM8, max: 0xff },
                    { name: "alarmTimeout", type: zigbee_herdsman_1.Zcl.DataType.UINT8, max: 0xff },
                ],
            },
        },
        commandsResponse: {},
    }),
    /** In previous implementations, the user was able to change the
     * sensitivity level of the smoke detector. That is not supported
     * when using the Bosch Smart Home Controller II. As the previous
     * creator assumed that Bosch follows the ZCL specification for
     * the sensitivity level (which isn't the case), this may result
     * in an unintentionally lowered sensitivity level. Therefore,
     * we set the manufacturer's default value here once to reverse
     * any previous modifications for safety reasons, as we talk
     * about a device that should save lives... */
    enforceDefaultSensitivityLevel: () => {
        const onEvent = [
            async (event) => {
                if (event.type !== "start") {
                    return;
                }
                const device = event.data.device;
                if (device.meta.enforceDefaultSensitivityLevelApplied !== true) {
                    const endpoint = device.getEndpoint(1);
                    await endpoint.write("ssIasZone", { currentZoneSensitivityLevel: 0x00 });
                    device.meta.enforceDefaultSensitivityLevelApplied = true;
                }
            },
        ];
        return {
            onEvent,
            isModernExtend: true,
        };
    },
    smokeAlarmAndButtonPushes: () => m.iasZoneAlarm({
        zoneType: "smoke",
        zoneAttributes: ["alarm_1"],
        manufacturerZoneAttributes: [
            {
                bit: 11,
                name: "smoke_alarm_silenced",
                valueOn: true,
                valueOff: false,
                description: "Indicates whether an smoke alarm was silenced on the device itself for 10 minutes. " +
                    "Please keep in mind that the smoke detection is being disabled during that " +
                    "time period as well.",
                entityCategory: "diagnostic",
            },
            {
                bit: 8,
                name: "button_pushed",
                valueOn: true,
                valueOff: false,
                description: "Indicates whether the button on the device is being pushed for at least " +
                    "3 seconds (e.g., to trigger a test alarm or silence a smoke alarm)",
                entityCategory: "diagnostic",
            },
        ],
    }),
    alarmControl: () => {
        const alarmModeLookup = {
            manual_smoke_alarm: 0x00,
            manual_burglar_alarm: 0x01,
        };
        const onOffLookup = {
            OFF: false,
            ON: true,
        };
        const defaultBroadcastAlarms = true;
        function setDefaultBroadcastAlarms(meta) {
            const newBroadcastStatus = utils.getFromLookupByValue(defaultBroadcastAlarms, onOffLookup);
            meta.publish({ broadcast_alarms: newBroadcastStatus });
        }
        async function sendAlarmControlMessage(endpoint, broadcastAlarm, alarmMode, timeoutInSeconds) {
            if (broadcastAlarm === true) {
                // Bosch sends broadcast messages two times with 4 seconds in between to
                // ensure all sleepy devices receive them. We mimic the same pattern here.
                for (let index = 0; index < 2; index++) {
                    await endpoint.zclCommandBroadcast(255, zigbee_herdsman_1.ZSpec.BroadcastAddress.SLEEPY, "ssIasZone", "alarmControl", { alarmMode: alarmMode, alarmTimeout: timeoutInSeconds }, exports.manufacturerOptions);
                    await (0, utils_1.sleep)(4000);
                }
            }
            else {
                await endpoint.command("ssIasZone", "alarmControl", { alarmMode: alarmMode, alarmTimeout: timeoutInSeconds }, exports.manufacturerOptions);
            }
        }
        const exposes = [
            e
                .binary("manual_smoke_alarm", ea.ALL, utils.getFromLookupByValue(true, onOffLookup), utils.getFromLookupByValue(false, onOffLookup))
                .withDescription("Indicates whether the smoke alarm siren is being manually activated on the device"),
            e
                .binary("manual_burglar_alarm", ea.ALL, utils.getFromLookupByValue(true, onOffLookup), utils.getFromLookupByValue(false, onOffLookup))
                .withDescription("Indicates whether the burglar alarm siren is being manually activated on the device"),
            e
                .binary("broadcast_alarms", ea.ALL, utils.getFromLookupByValue(true, onOffLookup), utils.getFromLookupByValue(false, onOffLookup))
                .withLabel("Broadcast alarms")
                .withDescription("Broadcast manual alarm state changes to all BSD-2 devices on the network. Please keep in mind " +
                "that a detected smoke alarm is not being transmitted automatically to other devices. " +
                "To achieve that, you must set up an automation, e.g., in Home Assistant.")
                .withCategory("config"),
        ];
        const fromZigbee = [
            {
                cluster: "ssIasZone",
                type: ["commandStatusChangeNotification", "attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const zoneStatus = "zonestatus" in msg.data ? msg.data.zonestatus : msg.data.zoneStatus;
                    if (zoneStatus === undefined) {
                        return;
                    }
                    const result = {};
                    const smokeAlarmEnabled = (zoneStatus & (1 << 1)) > 0;
                    result.manual_smoke_alarm = utils.getFromLookupByValue(smokeAlarmEnabled, onOffLookup);
                    const burglarAlarmEnabled = (zoneStatus & (1 << 7)) > 0;
                    result.manual_burglar_alarm = utils.getFromLookupByValue(burglarAlarmEnabled, onOffLookup);
                    return result;
                },
            },
        ];
        const toZigbee = [
            {
                key: ["manual_smoke_alarm", "manual_burglar_alarm", "broadcast_alarms"],
                convertSet: async (entity, key, value, meta) => {
                    if (key === "manual_smoke_alarm" || key === "manual_burglar_alarm") {
                        let broadcastAlarm;
                        try {
                            broadcastAlarm = utils.getFromLookup(meta.state.broadcast_alarms, onOffLookup);
                        }
                        catch {
                            setDefaultBroadcastAlarms(meta);
                            broadcastAlarm = defaultBroadcastAlarms;
                        }
                        const alarmMode = utils.getFromLookup(key, alarmModeLookup);
                        const enableAlarm = utils.getFromLookup(value, onOffLookup);
                        const timeoutInSeconds = enableAlarm ? 0xf0 : 0;
                        utils.assertEndpoint(entity);
                        await sendAlarmControlMessage(entity, broadcastAlarm, alarmMode, timeoutInSeconds);
                        clearTimeout(globalStore.getValue("boschSmokeAlarm", "alarmTimer"));
                        if (enableAlarm) {
                            const alarmTimer = setTimeout(async () => await sendAlarmControlMessage(entity, broadcastAlarm, alarmMode, timeoutInSeconds), (timeoutInSeconds - 60) * 1000);
                            globalStore.putValue("boschSmokeAlarm", "alarmTimer", alarmTimer);
                        }
                    }
                    if (key === "broadcast_alarms") {
                        return { state: { broadcast_alarms: value } };
                    }
                },
                convertGet: async (entity, key, meta) => {
                    if (key === "manual_smoke_alarm" || key === "manual_burglar_alarm") {
                        await entity.read("ssIasZone", ["zoneStatus"]);
                    }
                    if (key === "broadcast_alarms" && meta.state[key] === undefined) {
                        setDefaultBroadcastAlarms(meta);
                    }
                },
            },
        ];
        return {
            exposes,
            fromZigbee,
            toZigbee,
            isModernExtend: true,
        };
    },
    testMode: () => exports.boschGeneralSensorDeviceExtend.testMode({
        testModeDescription: "Check the function of the smoke alarm. Pay attention to the alarm sound " +
            "and the flashing of the alarm LED. Please keep in mind that it can take " +
            "up to 10 seconds for the test mode to be activated.",
        sensitivityLevelToUse: 0x00,
        variableTimeoutSupported: true,
        defaultTimeout: 5,
        zoneStatusBit: 10,
    }),
    battery: () => exports.boschGeneralExtend.batteryWithPercentageAndLowStatus({ percentageReportingConfig: { min: "MIN", max: "MAX", change: 1 } }),
};
exports.boschSmartPlugExtend = {
    smartPlugCluster: () => m.deviceAddCustomCluster("boschEnergyDevice", {
        name: "boschEnergyDevice",
        ID: 0xfca0,
        manufacturerCode: exports.manufacturerOptions.manufacturerCode,
        attributes: {
            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 },
            ledBrightness: { name: "ledBrightness", ID: 0x002c, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true, max: 0xff },
            energySavingModeEnabled: { name: "energySavingModeEnabled", ID: 0x002d, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN, write: true },
            energySavingModeThreshold: { name: "energySavingModeThreshold", ID: 0x002e, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff },
            energySavingModeTimer: { name: "energySavingModeTimer", ID: 0x002f, type: zigbee_herdsman_1.Zcl.DataType.UINT32, write: true, max: 0xffffffff },
        },
        commands: {},
        commandsResponse: {},
    }),
    onOff: () => m.onOff({ powerOnBehavior: true, configureReporting: true }),
    ledBrightness: () => m.numeric({
        name: "led_brightness",
        cluster: "boschEnergyDevice",
        attribute: "ledBrightness",
        label: "LED brightness",
        description: "Here you can adjust the LED brightness",
        valueMin: 0,
        valueMax: 100,
        valueStep: 1,
        unit: "%",
        entityCategory: "config",
    }),
    energySavingMode: () => {
        const energySavingModeEnabledLookup = {
            ON: 0x01,
            OFF: 0x00,
        };
        const exposes = [
            e
                .binary("energy_saving_mode_enabled", ea.ALL, utils.getFromLookupByValue(0x01, energySavingModeEnabledLookup), utils.getFromLookupByValue(0x00, energySavingModeEnabledLookup))
                .withLabel("Enable energy-saving mode")
                .withDescription("Here you can enable/disable the energy-saving mode")
                .withCategory("config"),
            e
                .numeric("energy_saving_mode_threshold", ea.ALL)
                .withLabel("Energy-saving threshold")
                .withDescription("Here you can set the threshold for the energy-saving mode. If the consumption falls below the set value (and the timer has been met), the smart plug will be turned off.")
                .withUnit("watt")
                .withValueMin(1)
                .withValueMax(50)
                .withValueStep(1)
                .withCategory("config"),
            e
                .numeric("energy_saving_mode_timer", ea.ALL)
                .withLabel("Energy-saving timer")
                .withDescription("Here you can set the time the threshold has to be met before the smart plug is turned off")
                .withUnit("seconds")
                .withValueMin(1)
                .withValueMax(1800)
                .withValueStep(1)
                .withCategory("config"),
        ];
        const fromZigbee = [
            {
                cluster: "boschEnergyDevice",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const result = {};
                    const data = msg.data;
                    if (data.energySavingModeEnabled !== undefined) {
                        result.energy_saving_mode_enabled = utils.getFromLookupByValue(data.energySavingModeEnabled, energySavingModeEnabledLookup);
                    }
                    if (data.energySavingModeThreshold !== undefined) {
                        result.energy_saving_mode_threshold = utils.toNumber(data.energySavingModeThreshold) / 10;
                    }
                    if (data.energySavingModeTimer !== undefined) {
                        result.energy_saving_mode_timer = utils.toNumber(data.energySavingModeTimer);
                    }
                    return result;
                },
            },
        ];
        const toZigbee = [
            {
                key: ["energy_saving_mode_enabled", "energy_saving_mode_threshold", "energy_saving_mode_timer"],
                convertSet: async (entity, key, value, meta) => {
                    if (key === "energy_saving_mode_enabled") {
                        await entity.write("boschEnergyDevice", {
                            energySavingModeEnabled: utils.getFromLookup(value, energySavingModeEnabledLookup),
                        });
                        return { state: { energy_saving_mode_enabled: value } };
                    }
                    if (key === "energy_saving_mode_threshold") {
                        await entity.write("boschEnergyDevice", {
                            energySavingModeThreshold: utils.toNumber(value) * 10,
                        });
                        return { state: { energy_saving_mode_threshold: value } };
                    }
                    if (key === "energy_saving_mode_timer") {
                        await entity.write("boschEnergyDevice", {
                            energySavingModeTimer: utils.toNumber(value),
                        });
                        return { state: { energy_saving_mode_timer: value } };
                    }
                },
                convertGet: async (entity, key, meta) => {
                    if (key === "energy_saving_mode_enabled") {
                        await entity.read("boschEnergyDevice", ["energySavingModeEnabled"]);
                    }
                    if (key === "energy_saving_mode_threshold") {
                        await entity.read("boschEnergyDevice", ["energySavingModeThreshold"]);
                    }
                    if (key === "energy_saving_mode_timer") {
                        await entity.read("boschEnergyDevice", ["energySavingModeTimer"]);
                    }
                },
            },
        ];
        const configure = [
            m.setupConfigureForBinding("boschEnergyDevice", "input"),
            m.setupConfigureForReading("boschEnergyDevice", [
                "energySavingModeEnabled",
                "energySavingModeThreshold",
                "energySavingModeTimer",
            ]),
        ];
        return {
            exposes,
            fromZigbee,
            toZigbee,
            configure,
            isModernExtend: true,
        };
    },
    electricityMeter: (args) => m.electricityMeter({
        voltage: false,
        current: false,
        power: { change: 1 },
        energy: { change: 1 },
        ...args,
    }),
};
const boschThermostatLookup = {
    systemModes: {
        heat: 0x04,
        cool: 0x03,
        off: 0x00,
    },
    raRunningStates: ["idle", "heat"],
    heaterType: {
        underfloor_heating: 0x00,
        radiator: 0x02,
        central_heating: 0x01,
        manual_control: 0x03,
    },
};
exports.boschThermostatExtend = {
    customThermostatCluster: () => m.deviceAddCustomCluster("hvacThermostat", {
        name: "hvacThermostat",
        ID: zigbee_herdsman_1.Zcl.Clusters.hvacThermostat.ID,
        attributes: {
            operatingMode: {
                name: "operatingMode",
                ID: 0x4007,
                type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            heatingDemand: {
                name: "heatingDemand",
                ID: 0x4020,
                type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            valveAdaptStatus: {
                name: "valveAdaptStatus",
                ID: 0x4022,
                type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            unknownAttribute0: {
                name: "unknownAttribute0",
                ID: 0x4025,
                type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            remoteTemperature: {
                name: "remoteTemperature",
                ID: 0x4040,
                type: zigbee_herdsman_1.Zcl.DataType.INT16,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                min: -32768,
            },
            unknownAttribute1: {
                name: "unknownAttribute1",
                ID: 0x4041,
                type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            windowOpenMode: {
                name: "windowOpenMode",
                ID: 0x4042,
                type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            boostHeating: {
                name: "boostHeating",
                ID: 0x4043,
                type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            cableSensorTemperature: {
                name: "cableSensorTemperature",
                ID: 0x4052,
                type: zigbee_herdsman_1.Zcl.DataType.INT16,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                min: -32768,
            },
            valveType: {
                name: "valveType",
                ID: 0x4060,
                type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            unknownAttribute2: {
                name: "unknownAttribute2",
                ID: 0x4061,
                type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            cableSensorMode: {
                name: "cableSensorMode",
                ID: 0x4062,
                type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            heaterType: {
                name: "heaterType",
                ID: 0x4063,
                type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            errorState: {
                name: "errorState",
                ID: 0x5000,
                type: zigbee_herdsman_1.Zcl.DataType.BITMAP8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
            },
            automaticValveAdapt: {
                name: "automaticValveAdapt",
                ID: 0x5010,
                type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
        },
        commands: {
            calibrateValve: { name: "calibrateValve", ID: 0x41, parameters: [] },
        },
        commandsResponse: {},
    }),
    customUserInterfaceCfgCluster: () => m.deviceAddCustomCluster("hvacUserInterfaceCfg", {
        name: "hvacUserInterfaceCfg",
        ID: zigbee_herdsman_1.Zcl.Clusters.hvacUserInterfaceCfg.ID,
        attributes: {
            displayOrientation: {
                name: "displayOrientation",
                ID: 0x400b,
                type: zigbee_herdsman_1.Zcl.DataType.UINT8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            activityLed: {
                name: "activityLed",
                ID: 0x4033,
                type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            displayedTemperature: {
                name: "displayedTemperature",
                ID: 0x4039,
                type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            displaySwitchOnDuration: {
                name: "displaySwitchOnDuration",
                ID: 0x403a,
                type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
            displayBrightness: {
                name: "displayBrightness",
                ID: 0x403b,
                type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
                manufacturerCode: exports.manufacturerOptions.manufacturerCode,
                write: true,
                max: 0xff,
            },
        },
        commands: {},
        commandsResponse: {},
    }),
    relayState: () => m.onOff({ description: "The state of the relay controlling the connected heating/cooling device", powerOnBehavior: false }),
    cableSensorMode: () => m.enumLookup({
        name: "cable_sensor_mode",
        cluster: "hvacThermostat",
        attribute: "cableSensorMode",
        description: 'Select a configuration for the sensor connection. If you select "with_regulation", ' +
            "the measured temperature on the cable sensor is used by the heating/cooling algorithm " +
            "instead of the local temperature.",
        lookup: { not_used: 0x00, cable_sensor_without_regulation: 0xb0, cable_sensor_with_regulation: 0xb1 },
        reporting: false,
        entityCategory: "config",
    }),
    cableSensorTemperature: () => m.numeric({
        name: "cable_sensor_temperature",
        cluster: "hvacThermostat",
        attribute: "cableSensorTemperature",
        description: "Measured temperature value on the cable sensor (if enabled)",
        unit: "°C",
        scale: 100,
        reporting: { min: 30, max: "MAX", change: 20 },
        access: "STATE_GET",
    }),
    heaterType: () => m.enumLookup({
        name: "heater_type",
        cluster: "hvacThermostat",
        attribute: "heaterType",
        description: "Select the connected heater type or 'manual_control' if you like to activate the relay manually when necessary",
        lookup: boschThermostatLookup.heaterType,
        reporting: false,
        entityCategory: "config",
    }),
    valveType: () => m.enumLookup({
        name: "valve_type",
        cluster: "hvacThermostat",
        attribute: "valveType",
        description: "Select the connected valve type",
        lookup: { normally_closed: 0x00, normally_open: 0x01 },
        reporting: false,
        entityCategory: "config",
    }),
    humidity: () => m.humidity({ reporting: { min: "1_MINUTE", max: "1_HOUR", change: 100 } }),
    operatingMode: (args) => m.enumLookup({
        name: "operating_mode",
        cluster: "hvacThermostat",
        attribute: "operatingMode",
        description: "Bosch-specific operating mode",
        lookup: { schedule: 0x00, manual: 0x01, pause: 0x05 },
        reporting: args?.enableReporting ? { min: "MIN", max: "MAX", change: null } : false,
        entityCategory: "config",
    }),
    windowOpenMode: (args) => m.binary({
        name: "window_detection",
        cluster: "hvacThermostat",
        attribute: "windowOpenMode",
        description: "Activates the window open mode, where the thermostat disables any heating/cooling " +
            "to prevent unnecessary energy consumption. Please keep in mind that the device " +
            "itself does not detect any open windows!",
        valueOn: ["ON", 0x01],
        valueOff: ["OFF", 0x00],
        reporting: args?.enableReporting ? { min: "MIN", max: "MAX", change: null } : false,
    }),
    childLock: () => m.binary({
        name: "child_lock",
        cluster: "hvacUserInterfaceCfg",
        attribute: "keypadLockout",
        description: "Enables/disables physical input on the thermostat",
        valueOn: ["LOCK", 0x01],
        valueOff: ["UNLOCK", 0x00],
        reporting: { min: "MIN", max: "MAX", change: null },
    }),
    displayBrightness: () => m.numeric({
        name: "display_brightness",
        cluster: "hvacUserInterfaceCfg",
        attribute: "displayBrightness",
        description: "Sets brightness of the display",
        valueMin: 0,
        valueMax: 100,
        valueStep: 10,
        unit: "%",
        scale: 0.1,
        reporting: false,
        entityCategory: "config",
    }),
    displaySwitchOnDuration: () => m.numeric({
        name: "display_switch_on_duration",
        cluster: "hvacUserInterfaceCfg",
        attribute: "displaySwitchOnDuration",
        label: "Display switch-on duration",
        description: "Sets the time before the display is automatically switched off",
        valueMin: 5,
        valueMax: 30,
        unit: "s",
        reporting: false,
        entityCategory: "config",
    }),
    displayOrientation: () => m.enumLookup({
        name: "display_orientation",
        cluster: "hvacUserInterfaceCfg",
        attribute: "displayOrientation",
        description: "You can rotate the display content by 180° here. This is recommended if your thermostat is fitted vertically, for instance.",
        lookup: { standard_arrangement: 0x00, rotated_by_180_degrees: 0x01 },
        reporting: false,
        entityCategory: "config",
    }),
    displayedTemperature: () => m.enumLookup({
        name: "displayed_temperature",
        cluster: "hvacUserInterfaceCfg",
        attribute: "displayedTemperature",
        description: "Select which temperature should be displayed on your radiator thermostat display",
        lookup: { set_temperature: 0x00, measured_temperature: 0x01 },
        reporting: false,
        entityCategory: "config",
    }),
    activityLedState: () => m.enumLookup({
        name: "activity_led",
        cluster: "hvacUserInterfaceCfg",
        attribute: "activityLed",
        label: "Activity LED state",
        description: "Determines the state of the little dot on the display next to the heating/cooling symbol",
        lookup: { off: 0x00, auto: 0x01, on: 0x02 },
        reporting: false,
        entityCategory: "config",
    }),
    remoteTemperature: () => m.numeric({
        name: "remote_temperature",
        cluster: "hvacThermostat",
        attribute: "remoteTemperature",
        description: "Input for remote temperature sensor. Required at least every 30 minutes to prevent fallback to the internal sensor!",
        valueMin: 0.0,
        valueMax: 35.0,
        valueStep: 0.2,
        unit: "°C",
        scale: 100,
        reporting: false,
        entityCategory: "config",
    }),
    setpointChangeSource: (args) => m.enumLookup({
        name: "setpoint_change_source",
        cluster: "hvacThermostat",
        attribute: "setpointChangeSource",
        description: "Source of the current setpoint temperature",
        lookup: { manual: 0x00, schedule: 0x01, externally: 0x02 },
        access: "STATE_GET",
        reporting: args?.enableReporting ? { min: "MIN", max: "MAX", change: null } : false,
        entityCategory: "diagnostic",
    }),
    customHeatingDemand: () => m.numeric({
        name: "pi_heating_demand",
        cluster: "hvacThermostat",
        attribute: "heatingDemand",
        label: "PI heating demand",
        description: "Position of the valve (= demanded heat) where 0% is fully closed and 100% is fully open",
        unit: "%",
        valueMin: 0,
        valueMax: 100,
        access: "ALL",
        reporting: { min: "MIN", max: "MAX", change: null },
    }),
    rmBattery: () => m.battery({
        percentage: true,
        percentageReporting: false,
        voltage: true,
        voltageReporting: true,
        voltageReportingConfig: false,
        voltageToPercentage: { min: 4400, max: 6400 },
        lowStatus: true,
        lowStatusReportingConfig: { min: "MIN", max: "MAX", change: null },
    }),
    rmThermostat: () => {
        const thermostat = m.thermostat({
            localTemperature: {
                configure: { reporting: { min: "1_MINUTE", max: "1_HOUR", change: 10 } },
            },
            localTemperatureCalibration: {
                values: { min: -5, max: 5, step: 0.1 },
                configure: { reporting: false },
            },
            setpoints: {
                values: {
                    occupiedHeatingSetpoint: { min: 5, max: 30, step: 0.5 },
                    occupiedCoolingSetpoint: { min: 5, max: 30, step: 0.5 },
                },
                configure: { reporting: { min: "10_SECONDS", max: "MAX", change: 50 } },
            },
            systemMode: {
                values: ["off", "heat", "cool"],
                configure: { reporting: { min: "MIN", max: "MAX", change: null } },
            },
            runningState: {
                values: ["idle", "heat", "cool"],
                configure: { reporting: { min: "MIN", max: "MAX", change: null } },
            },
            ctrlSeqeOfOper: {
                values: ["cooling_only", "heating_only"],
                configure: { reporting: { min: "MIN", max: "MAX", change: null } },
            },
        });
        const exposes = thermostat.exposes;
        return {
            exposes: exposes,
            fromZigbee: thermostat.fromZigbee,
            toZigbee: thermostat.toZigbee,
            onEvent: thermostat.onEvent,
            configure: thermostat.configure,
            isModernExtend: true,
        };
    },
    raThermostat: () => {
        // Native thermostat
        const thermostat = m.thermostat({
            localTemperature: {
                values: {
                    description: "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).",
                },
                configure: {
                    reporting: { min: "1_MINUTE", max: "1_HOUR", change: 10 },
                },
            },
            localTemperatureCalibration: {
                values: { min: -5, max: 5, step: 0.1 },
                configure: { reporting: false },
            },
            setpoints: {
                values: {
                    occupiedHeatingSetpoint: { min: 5, max: 30, step: 0.5 },
                },
                configure: {
                    reporting: { min: "10_SECONDS", max: "MAX", change: 50 },
                },
            },
            systemMode: {
                values: ["heat"],
                configure: {
                    reporting: false,
                },
            },
            runningState: {
                values: boschThermostatLookup.raRunningStates,
                toZigbee: {
                    skip: true,
                },
                configure: {
                    reporting: false,
                },
            },
            piHeatingDemand: {
                values: ea.ALL,
                toZigbee: {
                    skip: true,
                },
                configure: {
                    skip: true,
                },
            },
        });
        const exposes = thermostat.exposes;
        const fromZigbee = thermostat.fromZigbee;
        const toZigbee = thermostat.toZigbee;
        const onEvent = thermostat.onEvent;
        let configure = thermostat.configure;
        // Add converters for custom running state
        const runningState = exports.boschThermostatExtend.customRunningState();
        fromZigbee.push(...runningState.fromZigbee);
        toZigbee.push(...runningState.toZigbee);
        // Add converters and configure for custom heating demand
        const piHeatingDemand = exports.boschThermostatExtend.customHeatingDemand();
        fromZigbee.push(...piHeatingDemand.fromZigbee);
        toZigbee.push(...piHeatingDemand.toZigbee);
        configure = [...configure, ...piHeatingDemand.configure];
        return {
            exposes,
            fromZigbee,
            toZigbee,
            configure,
            onEvent,
            isModernExtend: true,
        };
    },
    customRunningState: () => {
        const fromZigbee = [
            {
                cluster: "hvacThermostat",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const result = {};
                    const data = msg.data;
                    if (data.heatingDemand !== undefined) {
                        result.running_state =
                            utils.toNumber(data.heatingDemand) > 0
                                ? boschThermostatLookup.raRunningStates[1]
                                : boschThermostatLookup.raRunningStates[0];
                    }
                    return result;
                },
            },
        ];
        const toZigbee = [
            {
                key: ["running_state"],
                convertGet: async (entity, key, meta) => {
                    await entity.read("hvacThermostat", ["heatingDemand"]);
                },
            },
        ];
        return {
            fromZigbee,
            toZigbee,
            isModernExtend: true,
        };
    },
    boostHeating: (args) => {
        const boostHeatingLookup = {
            OFF: 0x00,
            ON: 0x01,
        };
        const exposes = [
            e
                .binary("boost_heating", ea.ALL, utils.getFromLookupByValue(0x01, boostHeatingLookup), utils.getFromLookupByValue(0x00, boostHeatingLookup))
                .withLabel("Activate boost heating")
                .withDescription("Activate boost heating (opens TRV for 5 minutes)"),
        ];
        const fromZigbee = [
            {
                cluster: "hvacThermostat",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const result = {};
                    const data = msg.data;
                    if (data.boostHeating !== undefined) {
                        result.boost_heating = utils.getFromLookupByValue(data.boostHeating, boostHeatingLookup);
                    }
                    return result;
                },
            },
        ];
        const toZigbee = [
            {
                key: ["boost_heating"],
                convertSet: async (entity, key, value, meta) => {
                    const enableBoostHeating = value === utils.getFromLookupByValue(boostHeatingLookup.ON, boostHeatingLookup);
                    if (enableBoostHeating) {
                        const systemModeNotSetToHeat = "system_mode" in meta.state && meta.state.system_mode !== "heat";
                        if (systemModeNotSetToHeat) {
                            throw new Error("Boost heating is only possible when system mode is set to 'heat'!");
                        }
                        const heaterTypeNotSetToRadiator = "heater_type" in meta.state &&
                            meta.state.heater_type !==
                                utils.getFromLookupByValue(boschThermostatLookup.heaterType.radiator, boschThermostatLookup.heaterType);
                        if (heaterTypeNotSetToRadiator) {
                            throw new Error("Boost heating is only possible when heater type is set to 'radiator'!");
                        }
                    }
                    await entity.write("hvacThermostat", {
                        boostHeating: utils.toNumber(utils.getFromLookup(value, boostHeatingLookup)),
                    });
                    return { state: { boost_heating: value } };
                },
                convertGet: async (entity, key, meta) => {
                    await entity.read("hvacThermostat", ["boostHeating"]);
                },
            },
        ];
        const configure = [
            m.setupConfigureForReporting("hvacThermostat", "boostHeating", {
                config: args?.enableReporting ? { min: "MIN", max: "MAX", change: null } : false,
                access: ea.ALL,
            }),
        ];
        return {
            exposes,
            fromZigbee,
            toZigbee,
            configure,
            isModernExtend: true,
        };
    },
    errorState: (args) => {
        const exposes = [
            e
                .text("error_state", ea.STATE_GET)
                .withDescription("Indicates whether the device encounters any errors or not")
                .withCategory("diagnostic"),
        ];
        const fromZigbee = [
            {
                cluster: "hvacThermostat",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const result = {};
                    const data = msg.data;
                    if (data.errorState !== undefined) {
                        const receivedErrorState = data.errorState;
                        if (receivedErrorState === 0) {
                            result.error_state = "ok";
                        }
                        else {
                            result.error_state = "";
                            const bitmapLength = (receivedErrorState >>> 0).toString(2).length;
                            for (let errorNumber = 0; errorNumber < bitmapLength; errorNumber++) {
                                if ((receivedErrorState >> errorNumber) & 1) {
                                    if (String(result.error_state).length > 0) {
                                        result.error_state += " - ";
                                    }
                                    result.error_state += `E${String(errorNumber + 1).padStart(2, "0")}`;
                                }
                            }
                        }
                    }
                    return result;
                },
            },
        ];
        const toZigbee = [
            {
                key: ["error_state"],
                convertGet: async (entity, key, meta) => {
                    await entity.read("hvacThermostat", ["errorState"]);
                },
            },
        ];
        const configure = [
            m.setupConfigureForReporting("hvacThermostat", "errorState", {
                config: args?.enableReporting ? { min: "MIN", max: "MAX", change: null } : false,
                access: ea.STATE_GET,
            }),
        ];
        return {
            exposes,
            fromZigbee,
            toZigbee,
            configure,
            isModernExtend: true,
        };
    },
    valveAdaptation: () => {
        const valveAdaptStatusLookup = {
            none: 0x00,
            ready_to_calibrate: 0x01,
            calibration_in_progress: 0x02,
            error: 0x03,
            success: 0x04,
        };
        const triggerValveAdaptation = async (state, endpoint, throwError = true) => {
            let adaptStatus;
            try {
                adaptStatus = utils.getFromLookup(state.valve_adapt_status, valveAdaptStatusLookup);
            }
            catch {
                adaptStatus = valveAdaptStatusLookup.none;
            }
            switch (adaptStatus) {
                case valveAdaptStatusLookup.ready_to_calibrate:
                case valveAdaptStatusLookup.error:
                    await endpoint.command("hvacThermostat", "calibrateValve", {}, exports.manufacturerOptions);
                    break;
                default:
                    if (throwError) {
                        throw new Error("Valve adaptation process not possible right now!");
                    }
            }
        };
        const exposes = [
            e
                .enum("valve_adapt_status", ea.STATE_GET, Object.keys(valveAdaptStatusLookup))
                .withLabel("Valve adaptation status")
                .withDescription("Specifies the current status of the valve adaptation")
                .withCategory("diagnostic"),
            e
                .binary("automatic_valve_adapt", ea.STATE_GET, true, false)
                .withLabel("Automatic valve adaptation requested")
                .withDescription("Specifies if an automatic valve adaptation is being requested by the thermostat " +
                "(for example after a successful firmware upgrade). If this is the case, the " +
                "valve adaptation will be automatically started as soon as the adaptation status " +
                "is 'ready_to_calibrate' or 'error'.")
                .withCategory("diagnostic"),
            e
                .enum("valve_adapt_process", ea.SET, ["adapt"])
                .withLabel("Trigger adaptation process")
                .withDescription("Trigger the valve adaptation process. Only possible when the adaptation status is 'ready_to_calibrate' or 'error'.")
                .withCategory("config"),
        ];
        const fromZigbee = [
            {
                cluster: "hvacThermostat",
                type: ["attributeReport", "readResponse"],
                convert: async (model, msg, publish, options, meta) => {
                    const result = {};
                    const data = msg.data;
                    if (data.valveAdaptStatus !== undefined) {
                        result.valve_adapt_status = utils.getFromLookupByValue(data.valveAdaptStatus, valveAdaptStatusLookup);
                        const automaticValveAdapt = meta.state.automatic_valve_adapt ?? false;
                        if (automaticValveAdapt === true) {
                            await triggerValveAdaptation(meta.state, msg.endpoint, false);
                        }
                    }
                    if (data.automaticValveAdapt !== undefined) {
                        result.automatic_valve_adapt = !!data.automaticValveAdapt;
                    }
                    return result;
                },
            },
        ];
        const toZigbee = [
            {
                key: ["valve_adapt_status", "automatic_valve_adapt", "valve_adapt_process"],
                convertSet: async (entity, key, value, meta) => {
                    if (key === "valve_adapt_process") {
                        await triggerValveAdaptation(meta.state, entity);
                    }
                },
                convertGet: async (entity, key, meta) => {
                    if (key === "valve_adapt_status") {
                        await entity.read("hvacThermostat", ["valveAdaptStatus"]);
                    }
                    if (key === "automatic_valve_adapt") {
                        await entity.read("hvacThermostat", ["automaticValveAdapt"]);
                    }
                },
            },
        ];
        const configure = [
            m.setupConfigureForReporting("hvacThermostat", "valveAdaptStatus", {
                config: { min: "MIN", max: "MAX", change: null },
                access: ea.STATE_GET,
            }),
            m.setupConfigureForReading("hvacThermostat", ["automaticValveAdapt"]),
        ];
        return {
            exposes,
            fromZigbee,
            toZigbee,
            configure,
            isModernExtend: true,
        };
    },
};
//endregion
//# sourceMappingURL=bosch.js.map