"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.definitions = void 0;
const zigbee_herdsman_1 = require("zigbee-herdsman");
const fz = __importStar(require("../converters/fromZigbee"));
const exposes = __importStar(require("../lib/exposes"));
const logger_1 = require("../lib/logger");
const m = __importStar(require("../lib/modernExtend"));
const utils = __importStar(require("../lib/utils"));
const utils_1 = require("../lib/utils");
const e = exposes.presets;
const ea = exposes.access;
const SHELLY_ENDPOINT_ID = 239;
const SHELLY_OPTIONS = { profileId: zigbee_herdsman_1.ZSpec.CUSTOM_SHELLY_PROFILE_ID };
const NS = "zhc:shelly";
const HA_ELECTRICAL_MEASUREMENT_CLUSTER_ID = 0x0b04;
const HA_ELECTRICAL_MEASUREMENT_POWER_FACTOR_ATTR_ID = 0x0510;
/**
 * Get or initialize WS90 meta storage on device
 */
function getWS90Meta(device) {
    if (!device.meta.ws90) {
        device.meta.ws90 = {};
    }
    return device.meta.ws90;
}
/**
 * Calculate dew point using Magnus formula
 */
function calculateDewPoint(T, Rh) {
    if (T === undefined || Rh === undefined || Rh <= 0)
        return null;
    const a = 17.27;
    const b = 237.7;
    const alpha = (a * T) / (b + T) + Math.log(Rh / 100);
    return Math.round(((b * alpha) / (a - alpha)) * 10) / 10;
}
/**
 * Calculate humidex (Canadian heat index)
 */
function calculateHumidex(T, Rh) {
    if (T === undefined || Rh === undefined)
        return null;
    const dewPoint = calculateDewPoint(T, Rh);
    if (dewPoint === null)
        return null;
    const ee = 6.11 * Math.exp(5417.753 * (1 / 273.15 - 1 / (273.15 + dewPoint)));
    return Math.round((T + 0.5555 * (ee - 10)) * 10) / 10;
}
/**
 * Calculate wind chill (formula valid for T <= 10°C and wind >= 4.8 km/h)
 */
function calculateWindChill(T, windMs) {
    if (T === undefined || windMs === undefined)
        return null;
    const windKmh = windMs * 3.6;
    if (T > 10 || windKmh < 4.8)
        return Math.round(T * 10) / 10;
    const wc = 13.12 + 0.6215 * T - 11.37 * windKmh ** 0.16 + 0.3965 * T * windKmh ** 0.16;
    return Math.round(wc * 10) / 10;
}
/**
 * Calculate heat stress percentage using sigmoid curve
 */
function calculateHeatStress(T, Rh, lux, windMs, precipitation) {
    if (T === undefined)
        return null;
    const solar = (lux || 0) / 100;
    const base = T + solar / 100 + (Rh || 0) / 10;
    const cooled = base - (windMs || 0) / 2;
    const adjusted = cooled - ((precipitation || 0) > 0 ? 3 : 0);
    const scaled = (adjusted - 18) / (42 - 18);
    const sigmoid = 1 / (1 + Math.E ** (-4 * (scaled - 0.5)));
    return Math.max(Math.round(sigmoid * 100), 0);
}
/**
 * Calculate apparent temperature (wind chill when cold, humidex when warm)
 */
function calculateApparentTemperature(T, Rh, windMs) {
    if (T === undefined)
        return null;
    const windChill = calculateWindChill(T, windMs);
    const humidex = calculateHumidex(T, Rh);
    if (windChill !== null && windChill < T)
        return windChill;
    if (humidex !== null && humidex > T)
        return humidex;
    return Math.round(T * 10) / 10;
}
/**
 * Calculate rain rate from precipitation changes (mm/h)
 */
function calculateRainRate(meta, precipitation) {
    if (precipitation === undefined)
        return null;
    const now = Date.now();
    const history = meta.precipHistory;
    if (!history) {
        meta.precipHistory = { value: precipitation, time: now };
        return 0;
    }
    const timeDeltaMs = now - history.time;
    const precipDelta = precipitation - history.value;
    if (timeDeltaMs < 60000)
        return null;
    if (precipDelta < 0)
        return 0;
    meta.precipHistory = { value: precipitation, time: now };
    const timeDeltaHours = timeDeltaMs / (1000 * 60 * 60);
    const rate = precipDelta / timeDeltaHours;
    return Math.min(Math.round(rate * 10) / 10, 300);
}
/**
 * Calculate pressure trend (hPa/hour)
 */
function calculatePressureTrend(meta, pressure) {
    if (pressure === undefined)
        return null;
    const now = Date.now();
    const history = meta.pressureHistory;
    if (!history) {
        meta.pressureHistory = { value: pressure, time: now };
        return 0;
    }
    const timeDeltaMs = now - history.time;
    const pressureDelta = pressure - history.value;
    if (timeDeltaMs < 1800000)
        return null;
    meta.pressureHistory = { value: pressure, time: now };
    const timeDeltaHours = timeDeltaMs / (1000 * 60 * 60);
    const rate = pressureDelta / timeDeltaHours;
    return Math.round(rate * 10) / 10;
}
/**
 * Determine weather condition based on sensor data
 */
function calculateWeatherCondition(state) {
    const { temperature, illuminance, rain_status, wind_speed, rain_rate, pressure, pressure_trend } = state;
    if (illuminance === undefined)
        return null;
    const isRaining = rain_status === true && rain_rate !== undefined && rain_rate > 0;
    const isPouring = isRaining && rain_rate > 10;
    const isWindy = wind_speed !== undefined && wind_speed > 10;
    const isNight = illuminance < 10;
    const isLowPressure = pressure !== undefined && pressure < 1000;
    const isPressureFalling = pressure_trend !== undefined && pressure_trend < -2;
    const isHail = isRaining &&
        rain_rate > 5 &&
        illuminance < 5000 &&
        wind_speed !== undefined &&
        wind_speed > 5 &&
        (isLowPressure || isPressureFalling);
    const isSnowing = isRaining && temperature !== undefined && temperature < 1 && !isHail;
    if (isHail)
        return "hail";
    if (isSnowing)
        return "snowy";
    if (isPouring)
        return "pouring";
    if (isRaining)
        return "rainy";
    if (isNight) {
        return isWindy ? "windy" : "clear-night";
    }
    if (illuminance > 40000) {
        return isWindy ? "windy" : "sunny";
    }
    if (illuminance > 10000) {
        return isWindy ? "windy-variant" : "partlycloudy";
    }
    return "cloudy";
}
/**
 * Update calculated values whenever we get new sensor data (uses device.meta for persistence)
 */
function updateWS90CalculatedValues(device, payload) {
    const meta = getWS90Meta(device);
    if (!meta.state)
        meta.state = {};
    Object.assign(meta.state, payload);
    const state = meta.state;
    const result = {};
    const temp = state.temperature;
    const humidity = state.humidity;
    const windSpeed = state.wind_speed;
    const lux = state.illuminance;
    const precip = state.precipitation;
    const pressure = state.pressure;
    if (temp !== undefined && humidity !== undefined) {
        const dewPoint = calculateDewPoint(temp, humidity);
        if (dewPoint !== null)
            result.dew_point = dewPoint;
        const humidex = calculateHumidex(temp, humidity);
        if (humidex !== null)
            result.humidex = humidex;
        const heatStress = calculateHeatStress(temp, humidity, lux, windSpeed, precip);
        if (heatStress !== null)
            result.heat_stress = heatStress;
    }
    if (temp !== undefined && windSpeed !== undefined) {
        const windChill = calculateWindChill(temp, windSpeed);
        if (windChill !== null)
            result.wind_chill = windChill;
    }
    if (temp !== undefined) {
        const apparent = calculateApparentTemperature(temp, humidity, windSpeed);
        if (apparent !== null)
            result.apparent_temperature = apparent;
    }
    if (pressure !== undefined) {
        const trend = calculatePressureTrend(meta, pressure);
        if (trend !== null) {
            result.pressure_trend = trend;
            state.pressure_trend = trend;
        }
        else if (typeof state.pressure_trend === "number") {
            result.pressure_trend = state.pressure_trend;
        }
    }
    const condition = calculateWeatherCondition(state);
    if (condition !== null)
        result.weather_condition = condition;
    // Save device meta to persist across restarts
    device.save();
    return result;
}
// =============================================================================
// Shelly Modern Extend
// =============================================================================
const shellyModernExtend = {
    shellyPowerFactorInt16Fix() {
        // Shelly Gen4 devices report haElectricalMeasurement.powerFactor (0x0510) as INT16 (0x29)
        // while zigbee-herdsman defines it as INT8 (0x28). This breaks configureReporting (INVALID_DATA_TYPE).
        return m.deviceAddCustomCluster("haElectricalMeasurement", {
            name: "haElectricalMeasurement",
            ID: HA_ELECTRICAL_MEASUREMENT_CLUSTER_ID,
            attributes: {
                powerFactor: { name: "powerFactor", ID: HA_ELECTRICAL_MEASUREMENT_POWER_FACTOR_ATTR_ID, type: zigbee_herdsman_1.Zcl.DataType.INT16 },
            },
            commands: {},
            commandsResponse: {},
        });
    },
    shellyCustomClusters() {
        return [
            m.deviceAddCustomCluster("shellyRPCCluster", {
                name: "shellyRPCCluster",
                ID: 0xfc01,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SHELLY,
                attributes: {
                    data: { name: "data", ID: 0x0000, type: zigbee_herdsman_1.Zcl.DataType.CHAR_STR, write: true },
                    txCtl: { name: "txCtl", ID: 0x0001, type: zigbee_herdsman_1.Zcl.DataType.UINT32, write: true, max: 0xffffffff },
                    rxCtl: { name: "rxCtl", ID: 0x0002, type: zigbee_herdsman_1.Zcl.DataType.UINT32, write: true, max: 0xffffffff },
                },
                commands: {},
                commandsResponse: {},
            }),
            m.deviceAddCustomCluster("shellyWiFiSetupCluster", {
                name: "shellyWiFiSetupCluster",
                ID: 0xfc02,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SHELLY,
                attributes: {
                    status: { name: "status", ID: 0x0000, type: zigbee_herdsman_1.Zcl.DataType.CHAR_STR, write: true },
                    ip: { name: "ip", ID: 0x0001, type: zigbee_herdsman_1.Zcl.DataType.CHAR_STR, write: true },
                    actionCode: { name: "actionCode", ID: 0x0002, type: zigbee_herdsman_1.Zcl.DataType.UINT8, write: true, max: 0xff },
                    dhcp: { name: "dhcp", ID: 0x0003, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN, write: true },
                    enabled: { name: "enabled", ID: 0x0004, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN, write: true },
                    ssid: { name: "ssid", ID: 0x0005, type: zigbee_herdsman_1.Zcl.DataType.CHAR_STR, write: true },
                    password: { name: "password", ID: 0x0006, type: zigbee_herdsman_1.Zcl.DataType.CHAR_STR, write: true },
                    staticIp: { name: "staticIp", ID: 0x0007, type: zigbee_herdsman_1.Zcl.DataType.CHAR_STR, write: true },
                    netMask: { name: "netMask", ID: 0x0008, type: zigbee_herdsman_1.Zcl.DataType.CHAR_STR, write: true },
                    gateway: { name: "gateway", ID: 0x0009, type: zigbee_herdsman_1.Zcl.DataType.CHAR_STR, write: true },
                    nameServer: { name: "nameServer", ID: 0x000a, type: zigbee_herdsman_1.Zcl.DataType.CHAR_STR, write: true },
                },
                commands: {},
                commandsResponse: {},
            }),
        ];
    },
    shellyRPCSetup(features = []) {
        // Set helper variables
        const shellyRPCBugFixed = false; // For firmware 20250819-150402/ga0def2d
        const featureDev = features.includes("Dev");
        const featurePowerstripUI = features.includes("PowerstripUI");
        const featurePowerstripPowerOnBehavior = features.includes("PowerstripPowerOnBehavior");
        const featureTwoPMInputMode = features.includes("2PMInputMode");
        const featureOnePMInputMode = features.includes("1PMInputMode");
        // Generic helper functions
        const validateTime = (value) => {
            const hhmmRegex = /^([01][0-9]|2[0-3]):[0-5][0-9]$/;
            if (value === undefined || !value.match(hhmmRegex)) {
                throw new Error(`Invalid time "${value}"`);
            }
        };
        // RPC helper functions
        let rpcSending = false;
        const rpcSendRaw = async (endpoint, message) => {
            // Since RPC messages require multiple writes to complete, we have to make sure
            // we're not interleaving them accidentally. This is good enough for now, at least
            // until the RPC receive firmware bug is fixed by Shelly.
            while (rpcSending) {
                await (0, utils_1.sleep)(200);
            }
            try {
                rpcSending = true;
                const splitBytes = 40;
                logger_1.logger.debug(">>> shellyRPC write TxCtl", NS);
                const txCtl = message.length;
                await endpoint.write("shellyRPCCluster", { txCtl: txCtl }, SHELLY_OPTIONS);
                logger_1.logger.debug(`>>> TxCtl: ${txCtl}`, NS);
                logger_1.logger.debug(">>> shellyRPC write Data", NS);
                let dataToSend = message;
                while (dataToSend.length > 0) {
                    const data = dataToSend.substring(0, splitBytes);
                    dataToSend = dataToSend.substring(splitBytes);
                    await endpoint.write("shellyRPCCluster", { data: data }, SHELLY_OPTIONS);
                    logger_1.logger.debug(`>>> Data: ${data}`, NS);
                }
            }
            finally {
                rpcSending = false;
            }
        };
        const rpcSend = async (endpoint, method, params = undefined) => {
            const command = {
                id: 1, // We can't read replies anyway so don't care for now
                method: method,
                params: params,
            };
            return await rpcSendRaw(endpoint, JSON.stringify(command));
        };
        const rpcReceive = async (endpoint, key) => {
            logger_1.logger.debug(`||| shellyRPC rpcReceive(${key})`, NS);
            if (key === "rpc_rxctl") {
                logger_1.logger.debug(">>> shellyRPC read RxCtl", NS);
                const result = await endpoint.read("shellyRPCCluster", ["rxCtl"], SHELLY_OPTIONS);
                logger_1.logger.debug(`<<< RxCtl: ${JSON.stringify(result)}`, NS);
            }
            else if (key === "rpc_data") {
                logger_1.logger.debug(">>> shellyRPC read Data", NS);
                const result = await endpoint.read("shellyRPCCluster", ["data"], { ...SHELLY_OPTIONS, timeout: 1000 });
                logger_1.logger.debug(`<<< Data: ${JSON.stringify(result)}`, NS);
            }
        };
        // Features for exposes
        const featurePercentage = (name, label) => {
            return e.numeric(name, ea.STATE_SET).withValueMin(0).withValueMax(100).withValueStep(1).withLabel(label).withUnit("%");
        };
        const featureButtonEnabled = (id) => {
            return e.binary(`switch_${id}`, ea.STATE_SET, "momentary", "detached").withLabel(`Endpoint: ${id + 1}`);
        };
        const exposes = [];
        const exposesDev = [
            e
                .text("rpc_tx", ea.STATE_SET)
                .withLabel("TX Data")
                .withDescription("See https://shelly-api-docs.shelly.cloud/gen2/Devices/Gen4/ShellyPowerStripG4"),
            e.text("rpc_rxctl", ea.STATE_GET).withLabel("RxCtl").withDescription("RX bytes available").withCategory("diagnostic"),
            e.text("rpc_data", ea.STATE_GET).withLabel("Data").withDescription("RX Data").withCategory("diagnostic"),
        ];
        const exposesPowerstripUI = [
            e
                .enum("led_mode", ea.STATE_SET, ["off", "switch", "power"])
                .withLabel("LED Mode")
                .withDescription("Controls the behaviour of the LED rings around the sockets")
                .withCategory("config"),
            e
                .composite("led_colors", "led_colors", ea.ALL)
                .withFeature(featurePercentage("on_r", "Red (on)"))
                .withFeature(featurePercentage("on_g", "Green (on)"))
                .withFeature(featurePercentage("on_b", "Blue (on)"))
                .withFeature(featurePercentage("on_brightness", "Brightness (on)"))
                .withFeature(featurePercentage("off_r", "Red (off)"))
                .withFeature(featurePercentage("off_g", "Green (off)"))
                .withFeature(featurePercentage("off_b", "Blue (off)"))
                .withFeature(featurePercentage("off_brightness", "Brightness (off)"))
                .withLabel("LED colors in 'switch' mode")
                .withCategory("config"),
            featurePercentage("led_power_brightness", "LED brightness in 'power' mode").withCategory("config"),
            e
                .composite("led_night_mode", "led_night_mode", ea.ALL)
                .withFeature(e.binary("enable", ea.STATE_SET, true, false))
                .withFeature(featurePercentage("brightness", "Brightness"))
                .withFeature(e.text("from", ea.STATE_SET).withLabel("Active from").withDescription("hh:mm"))
                .withFeature(e.text("until", ea.STATE_SET).withLabel("Active until").withDescription("hh:mm"))
                .withLabel("LED night mode")
                .withDescription("Adjust LED brightness during night time")
                .withCategory("config"),
            e
                .composite("buttons_enabled", "buttons_enabled", ea.ALL)
                .withFeature(featureButtonEnabled(0))
                .withFeature(featureButtonEnabled(1))
                .withFeature(featureButtonEnabled(2))
                .withFeature(featureButtonEnabled(3))
                .withLabel("Buttons enabled")
                .withCategory("config"),
        ];
        const fromZigbee = [
            {
                cluster: "shellyRPCCluster",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const state = {};
                    // Diagnostic data
                    if (msg.data.rxCtl !== undefined) {
                        state.rpc_rxctl = msg.data.rxCtl;
                        state.rpc_data = "";
                    }
                    if (msg.data.data !== undefined) {
                        const accumulated = (meta.state.rpc_data ?? "") + msg.data.data;
                        state.rpc_data = accumulated;
                        const expectedLen = meta.state.rpc_rxctl;
                        if (expectedLen > 0 && accumulated.length >= expectedLen) {
                            try {
                                const response = JSON.parse(accumulated);
                                if (response.result?.in_mode !== undefined) {
                                    const epName = response.result.id === 0 ? "sw1" : "sw2";
                                    state[`switch_mode_${epName}`] = response.result.in_mode;
                                }
                            }
                            catch { }
                        }
                    }
                    return state;
                },
            },
        ];
        const toZigbee = [];
        const configure = [];
        const toZigbeeDev = [
            {
                key: ["rpc_rxctl", "rpc_data"],
                convertGet: async (entity, key, meta) => {
                    const ep = (0, utils_1.determineEndpoint)(entity, meta, "shellyRPCCluster");
                    await rpcReceive(ep, key);
                },
            },
            {
                key: ["rpc_tx"],
                convertSet: async (entity, key, value, meta) => {
                    logger_1.logger.debug(`>>> toZigbee.convertSet(${key}): ${value}`, NS);
                    const ep = (0, utils_1.determineEndpoint)(entity, meta, "shellyRPCCluster");
                    await rpcSendRaw(ep, value);
                    await rpcReceive(ep, "rpc_rxctl");
                    if (shellyRPCBugFixed) {
                        await rpcReceive(ep, "rpc_data");
                    }
                    else {
                        return { state: { rpc_data: "[Refresh for response]" } };
                    }
                },
            },
        ];
        const toZigbeePowerstripUI = [
            {
                key: ["led_mode"],
                convertSet: async (entity, key, value, meta) => {
                    const ep = (0, utils_1.determineEndpoint)(entity, meta, "shellyRPCCluster");
                    await rpcSend(ep, "POWERSTRIP_UI.SetConfig", {
                        config: {
                            leds: {
                                mode: value,
                            },
                        },
                    });
                },
            },
            {
                key: ["led_colors"],
                convertSet: async (entity, key, value, meta) => {
                    (0, utils_1.assertObject)(value);
                    const ep = (0, utils_1.determineEndpoint)(entity, meta, "shellyRPCCluster");
                    await rpcSend(ep, "POWERSTRIP_UI.SetConfig", {
                        config: {
                            leds: {
                                colors: {
                                    "switch:0": {
                                        on: {
                                            rgb: [value.on_r ?? 0, value.on_g ?? 0, value.on_b ?? 0],
                                            brightness: value.on_brightness ?? 0,
                                        },
                                        off: {
                                            rgb: [value.off_r ?? 0, value.off_g ?? 0, value.off_b ?? 0],
                                            brightness: value.off_brightness ?? 0,
                                        },
                                    },
                                },
                            },
                        },
                    });
                },
            },
            {
                key: ["led_power_brightness"],
                convertSet: async (entity, key, value, meta) => {
                    const ep = (0, utils_1.determineEndpoint)(entity, meta, "shellyRPCCluster");
                    await rpcSend(ep, "POWERSTRIP_UI.SetConfig", {
                        config: {
                            leds: {
                                colors: {
                                    power: {
                                        brightness: value ?? 0,
                                    },
                                },
                            },
                        },
                    });
                },
            },
            {
                key: ["led_night_mode"],
                convertSet: async (entity, key, value, meta) => {
                    (0, utils_1.assertObject)(value);
                    validateTime(value.from);
                    validateTime(value.until);
                    const ep = (0, utils_1.determineEndpoint)(entity, meta, "shellyRPCCluster");
                    await rpcSend(ep, "POWERSTRIP_UI.SetConfig", {
                        config: {
                            leds: {
                                night_mode: {
                                    enable: value.enable,
                                    brightness: value.brightness,
                                    active_between: [value.from, value.until],
                                },
                            },
                        },
                    });
                },
            },
            {
                key: ["buttons_enabled"],
                convertSet: async (entity, key, value, meta) => {
                    (0, utils_1.assertObject)(value);
                    const ep = (0, utils_1.determineEndpoint)(entity, meta, "shellyRPCCluster");
                    await rpcSend(ep, "POWERSTRIP_UI.SetConfig", {
                        config: {
                            controls: {
                                "switch:0": {
                                    in_mode: value.switch_0,
                                },
                                "switch:1": {
                                    in_mode: value.switch_1,
                                },
                                "switch:2": {
                                    in_mode: value.switch_2,
                                },
                                "switch:3": {
                                    in_mode: value.switch_3,
                                },
                            },
                        },
                    });
                },
            },
        ];
        if (featureDev) {
            exposes.push(...exposesDev);
            toZigbee.push(...toZigbeeDev);
        }
        if (featurePowerstripUI) {
            exposes.push(...exposesPowerstripUI);
            toZigbee.push(...toZigbeePowerstripUI);
        }
        if (featurePowerstripPowerOnBehavior) {
            const powerOnBehaviorValues = ["off", "on", "previous", "match_input"];
            for (const channel of [1, 2, 3, 4]) {
                exposes.push(e
                    .enum("power_on_behavior", ea.STATE_SET, powerOnBehaviorValues)
                    .withDescription("Behavior of the socket after a power outage. 'previous' restores the last known state.")
                    .withCategory("config")
                    .withEndpoint(String(channel)));
            }
            toZigbee.push({
                key: ["power_on_behavior"],
                convertSet: async (entity, key, value, meta) => {
                    utils.assertString(value, key);
                    utils.assertString(meta.endpoint_name, "endpoint_name");
                    utils.assertEndpoint(entity);
                    const switchId = Number(meta.endpoint_name) - 1;
                    // shellyRPCCluster lives on a dedicated endpoint (239), but this expose is per-channel.
                    // determineEndpoint() would return the per-channel endpoint when endpoint_name is set,
                    // so we explicitly resolve the RPC endpoint via the device.
                    const ep = entity.getDevice().getEndpoint(SHELLY_ENDPOINT_ID);
                    if (!ep)
                        throw new Error(`Shelly RPC endpoint ${SHELLY_ENDPOINT_ID} not found`);
                    const shellyValue = value === "previous" ? "restore_last" : value;
                    await rpcSend(ep, "Switch.SetConfig", { id: switchId, config: { initial_state: shellyValue } });
                    return { state: { power_on_behavior: value } };
                },
            });
        }
        if (featureTwoPMInputMode) {
            const inModeValues = ["follow", "flip", "detached", "cycle", "activation"];
            exposes.push((device, _options) => {
                if (utils.isDummyDevice(device) || !device.getEndpoint(SHELLY_ENDPOINT_ID))
                    return [];
                return [
                    e.enum("switch_mode", ea.ALL, inModeValues).withDescription("Switch input mode").withCategory("config").withEndpoint("sw1"),
                    e.enum("switch_mode", ea.ALL, inModeValues).withDescription("Switch input mode").withCategory("config").withEndpoint("sw2"),
                ];
            });
            toZigbee.push({
                key: ["switch_mode"],
                convertSet: async (entity, key, value, meta) => {
                    const switchId = meta.endpoint_name === "sw1" ? 0 : 1;
                    const ep = (0, utils_1.determineEndpoint)(entity, meta, "shellyRPCCluster");
                    await rpcSend(ep, "Switch.SetConfig", { id: switchId, config: { in_mode: value } });
                    return { state: { switch_mode: value } };
                },
                convertGet: async (entity, key, meta) => {
                    const switchId = meta.endpoint_name === "sw1" ? 0 : 1;
                    const ep = (0, utils_1.determineEndpoint)(entity, meta, "shellyRPCCluster");
                    await rpcSend(ep, "Switch.GetConfig", { id: switchId });
                    await rpcReceive(ep, "rpc_rxctl");
                },
            });
            configure.push(async (device) => {
                const ep = device.getEndpoint(SHELLY_ENDPOINT_ID);
                if (!ep)
                    return;
                try {
                    for (const id of [0, 1]) {
                        await rpcSend(ep, "Switch.GetConfig", { id });
                        await rpcReceive(ep, "rpc_rxctl");
                    }
                }
                catch (e) {
                    logger_1.logger.warning(`Failed to read switch_mode during configure, use get to retry: ${e}`, NS);
                }
            });
        }
        if (featureOnePMInputMode) {
            const inModeValues = ["follow", "flip", "detached", "cycle", "activation"];
            exposes.push((device, _options) => {
                if (utils.isDummyDevice(device) || !device.getEndpoint(SHELLY_ENDPOINT_ID))
                    return [];
                return [e.enum("switch_mode", ea.ALL, inModeValues).withDescription("Switch input mode").withCategory("config").withEndpoint("sw1")];
            });
            toZigbee.push({
                key: ["switch_mode"],
                convertSet: async (entity, key, value, meta) => {
                    const ep = (0, utils_1.determineEndpoint)(entity, meta, "shellyRPCCluster");
                    await rpcSend(ep, "Switch.SetConfig", { id: 0, config: { in_mode: value } });
                    return { state: { switch_mode: value } };
                },
                convertGet: async (entity, key, meta) => {
                    const ep = (0, utils_1.determineEndpoint)(entity, meta, "shellyRPCCluster");
                    await rpcSend(ep, "Switch.GetConfig", { id: 0 });
                    await rpcReceive(ep, "rpc_rxctl");
                },
            });
            configure.push(async (device) => {
                const ep = device.getEndpoint(SHELLY_ENDPOINT_ID);
                if (!ep)
                    return;
                try {
                    await rpcSend(ep, "Switch.GetConfig", { id: 0 });
                    await rpcReceive(ep, "rpc_rxctl");
                }
                catch (e) {
                    logger_1.logger.warning(`Failed to read switch_mode during configure, use get to retry: ${e}`, NS);
                }
            });
        }
        return { exposes, fromZigbee, toZigbee, configure, isModernExtend: true };
    },
    shellyWiFiSetup() {
        // biome-ignore lint/suspicious/noExplicitAny: generic
        const refresh = async (endpoint) => {
            await endpoint.write("shellyWiFiSetupCluster", { actionCode: 0 }, SHELLY_OPTIONS);
            await endpoint.read("shellyWiFiSetupCluster", ["status", "ip", "enabled", "dhcp", "ssid"], SHELLY_OPTIONS);
            await endpoint.read("shellyWiFiSetupCluster", ["staticIp", "netMask"], SHELLY_OPTIONS);
            await endpoint.read("shellyWiFiSetupCluster", ["gateway", "nameServer"], SHELLY_OPTIONS);
        };
        const exposes = [
            e.text("wifi_status", ea.STATE_GET).withLabel("Wi-Fi status").withDescription("Current connection status").withCategory("diagnostic"),
            e
                .text("ip_address", ea.STATE_GET)
                .withLabel("IP address")
                .withDescription("IP address currently assigned to the device")
                .withCategory("diagnostic"),
            e
                .binary("dhcp_enabled", ea.STATE_GET, true, false)
                .withLabel("DHCP enabled")
                .withDescription("Indicates whether DHCP is used to automatically assign network settings")
                .withCategory("diagnostic"),
            e
                .composite("wifi_config", "wifi_config", ea.ALL)
                .withFeature(e.binary("enabled", ea.STATE_SET, true, false).withLabel("Wi-Fi enabled").withDescription("Enable/disable Wi-Fi connectivity"))
                .withFeature(e.text("ssid", ea.STATE_SET).withLabel("Network").withDescription("Name (SSID) of the Wi-Fi network to connect to"))
                .withFeature(e.text("password", ea.SET).withLabel("Password").withDescription("Password for the selected Wi-Fi network"))
                .withFeature(e
                .text("static_ip", ea.STATE_SET)
                .withLabel("IPv4 address")
                .withDescription("Manually assigned IP address (used when DHCP is disabled)"))
                .withFeature(e.text("net_mask", ea.STATE_SET).withLabel("Network mask").withDescription("Subnet mask for the static IP configuration"))
                .withFeature(e.text("gateway", ea.STATE_SET).withLabel("Gateway").withDescription("Default gateway address for static IP configuration"))
                .withFeature(e.text("name_server", ea.STATE_SET).withLabel("DNS").withDescription("Name server address for static IP configuration"))
                .withLabel("Wi-Fi Configuration")
                .withCategory("config"),
        ];
        // biome-ignore lint/suspicious/noExplicitAny: generic
        const fromZigbee = [
            {
                cluster: "shellyWiFiSetupCluster",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const wifi_config = {};
                    const state = { wifi_config };
                    // Diagnostic data
                    if (msg.data.status !== undefined)
                        state.wifi_status = msg.data.status;
                    if (msg.data.ip !== undefined)
                        state.ip_address = msg.data.ip;
                    if (msg.data.dhcp !== undefined)
                        state.dhcp_enabled = msg.data.dhcp === 1;
                    // Wi-Fi config
                    if (msg.data.enabled !== undefined)
                        wifi_config.enabled = msg.data.enabled === 1;
                    if (msg.data.ssid !== undefined)
                        wifi_config.ssid = msg.data.ssid;
                    if (msg.data.staticIp !== undefined)
                        wifi_config.static_ip = msg.data.staticIp;
                    if (msg.data.netMask !== undefined)
                        wifi_config.net_mask = msg.data.netMask;
                    if (msg.data.gateway !== undefined)
                        wifi_config.gateway = msg.data.gateway;
                    if (msg.data.nameServer !== undefined)
                        wifi_config.name_server = msg.data.nameServer;
                    // Cleanup empty keys
                    for (const key in wifi_config) {
                        if (wifi_config[key] === "") {
                            wifi_config[key] = undefined;
                        }
                    }
                    return state;
                },
            },
        ];
        const toZigbee = [
            {
                key: ["wifi_status", "ip_address", "dhcp_enabled"],
                convertGet: async (entity, key, meta) => {
                    const ep = (0, utils_1.determineEndpoint)(entity, meta, "shellyWiFiSetupCluster");
                    await refresh(ep);
                },
            },
            {
                key: ["wifi_config"],
                convertGet: async (entity, key, meta) => {
                    const ep = (0, utils_1.determineEndpoint)(entity, meta, "shellyWiFiSetupCluster");
                    await refresh(ep);
                },
                convertSet: async (entity, key, value, meta) => {
                    (0, utils_1.assertObject)(value);
                    const ep = (0, utils_1.determineEndpoint)(entity, meta, "shellyWiFiSetupCluster");
                    const attr1 = {
                        enabled: value.enabled === true,
                        ssid: value.ssid || "",
                    };
                    await ep.write("shellyWiFiSetupCluster", attr1, SHELLY_OPTIONS);
                    const attr2 = {
                        password: value.password || "",
                    };
                    await ep.write("shellyWiFiSetupCluster", attr2, SHELLY_OPTIONS);
                    const attr3 = {
                        staticIp: value.static_ip || "",
                        netMask: value.net_mask || "",
                    };
                    await ep.write("shellyWiFiSetupCluster", attr3, SHELLY_OPTIONS);
                    const attr4 = {
                        gateway: value.gateway || "",
                        nameServer: value.name_server || "",
                    };
                    await ep.write("shellyWiFiSetupCluster", attr4, SHELLY_OPTIONS);
                    const attr5 = {
                        actionCode: 1,
                    };
                    await ep.write("shellyWiFiSetupCluster", attr5, SHELLY_OPTIONS);
                    return {
                        state: {
                            wifi_config: {
                                enabled: attr1.enabled,
                                ssid: attr1.ssid === "" ? undefined : attr1.ssid,
                                static_ip: attr3.staticIp === "" ? undefined : attr3.staticIp,
                                net_mask: attr3.netMask === "" ? undefined : attr3.netMask,
                                gateway: attr4.gateway === "" ? undefined : attr4.gateway,
                                name_server: attr4.nameServer === "" ? undefined : attr4.nameServer,
                            },
                        },
                    };
                },
            },
        ];
        const configure = [
            async (device, coordinatorEndpoint, definition) => {
                const ep = device.getEndpoint(SHELLY_ENDPOINT_ID);
                await refresh(ep);
            },
        ];
        return { exposes, fromZigbee, toZigbee, configure, isModernExtend: true };
    },
    ws90CalculatedValues() {
        const exposes = [
            // Calculated values only
            e.numeric("dew_point", ea.STATE).withUnit("°C").withDescription("Calculated dew point temperature"),
            e.numeric("wind_chill", ea.STATE).withUnit("°C").withDescription("Calculated wind chill temperature"),
            e.numeric("humidex", ea.STATE).withUnit("°C").withDescription("Calculated humidex (feels-like for warm conditions)"),
            e.numeric("apparent_temperature", ea.STATE).withUnit("°C").withDescription("Calculated apparent temperature"),
            e.numeric("heat_stress", ea.STATE).withUnit("%").withDescription("Calculated heat stress percentage (0-100%)"),
            e.numeric("rain_rate", ea.STATE).withUnit("mm/h").withDescription("Calculated rainfall rate"),
            e.numeric("pressure_trend", ea.STATE).withUnit("hPa/h").withDescription("Pressure change rate (negative = falling)"),
            e.text("weather_condition", ea.STATE).withDescription("Weather condition (sunny, rainy, snowy, cloudy, etc.)"),
        ];
        // biome-ignore lint/suspicious/noExplicitAny: custom clusters not in type registry
        const fromZigbee = [
            {
                cluster: "msTemperatureMeasurement",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    if (msg.data.measuredValue !== undefined) {
                        const temperature = msg.data.measuredValue / 100;
                        const calculated = updateWS90CalculatedValues(msg.device, { temperature });
                        return calculated; // Only calculated values; m.temperature() handles base temperature
                    }
                },
            },
            {
                cluster: "msRelativeHumidity",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    if (msg.data.measuredValue !== undefined) {
                        const humidity = msg.data.measuredValue / 100;
                        const calculated = updateWS90CalculatedValues(msg.device, { humidity });
                        return calculated; // Only calculated values; m.humidity() handles base humidity
                    }
                },
            },
            {
                cluster: "msPressureMeasurement",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    if (msg.data.measuredValue !== undefined) {
                        const pressure = msg.data.measuredValue / 10;
                        const calculated = updateWS90CalculatedValues(msg.device, { pressure });
                        return calculated; // Only calculated values; m.pressure() handles base pressure
                    }
                },
            },
            {
                cluster: "msIlluminanceMeasurement",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    if (msg.data.measuredValue !== undefined) {
                        const measuredValue = msg.data.measuredValue;
                        const illuminance = measuredValue > 0 ? Math.round(10 ** ((measuredValue - 1) / 10000)) : 0;
                        const calculated = updateWS90CalculatedValues(msg.device, { illuminance });
                        return calculated; // Only calculated values; m.illuminance() handles base illuminance
                    }
                },
            },
            {
                cluster: "shellyWS90UV",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const data = msg.data;
                    if (data.uvIndex !== undefined) {
                        const uv_index = data.uvIndex / 10;
                        const calculated = updateWS90CalculatedValues(msg.device, { uv_index });
                        return calculated;
                    }
                },
            },
            {
                cluster: "shellyWS90Wind",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const data = msg.data;
                    const payload = {};
                    if (data.windSpeed !== undefined)
                        payload.wind_speed = data.windSpeed / 10;
                    if (data.windDirection !== undefined)
                        payload.wind_direction = data.windDirection / 10;
                    if (data.gustSpeed !== undefined)
                        payload.gust_speed = data.gustSpeed / 10;
                    const calculated = updateWS90CalculatedValues(msg.device, payload);
                    return calculated;
                },
            },
            {
                cluster: "shellyWS90Rain",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const data = msg.data;
                    const payload = {};
                    if (data.rainStatus !== undefined)
                        payload.rain_status = Boolean(data.rainStatus);
                    if (data.precipitation !== undefined) {
                        payload.precipitation = data.precipitation / 10;
                    }
                    // Calculate rain_rate (it's a calculated value, not a base sensor value)
                    const ws90Meta = getWS90Meta(msg.device);
                    const rainRate = calculateRainRate(ws90Meta, payload.precipitation);
                    const rain_rate = rainRate !== null ? rainRate : 0;
                    // Update state with precipitation and rain_rate
                    const stateUpdate = { ...payload, rain_rate };
                    const calculated = updateWS90CalculatedValues(msg.device, stateUpdate);
                    // Include rain_rate in calculated values
                    calculated.rain_rate = rain_rate;
                    msg.device.save();
                    return calculated; // Only calculated values; m.binary()/m.numeric() handle base rain values
                },
            },
        ];
        return { exposes, fromZigbee, isModernExtend: true };
    },
    shellyLightLevel(args) {
        const reporting = args?.reporting ?? { min: "1_MINUTE", max: 900, change: 0 };
        return [
            m.deviceAddCustomCluster("shellyLightLevel", {
                name: "shellyLightLevel",
                ID: 0xfc21,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SHELLY,
                attributes: {
                    lightLevel: { name: "lightLevel", ID: 0x0000, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
                    darkThreshold: { name: "darkThreshold", ID: 0x0001, type: zigbee_herdsman_1.Zcl.DataType.UINT24, write: true },
                    brightThreshold: { name: "brightThreshold", ID: 0x0002, type: zigbee_herdsman_1.Zcl.DataType.UINT24, write: true },
                },
                commands: {},
                commandsResponse: {},
            }),
            m.enumLookup({
                name: "light_level",
                cluster: "shellyLightLevel",
                attribute: "lightLevel",
                lookup: { dark: 0, twilight: 1, bright: 2 },
                description: "Coarse light level",
                reporting,
                access: "STATE_GET",
            }),
            m.numeric({
                name: "dark_threshold",
                cluster: "shellyLightLevel",
                attribute: "darkThreshold",
                valueMin: 0,
                valueMax: 65535,
                reporting: false,
                description: "Lux threshold below which light level is dark",
                unit: "lx",
                access: "ALL",
            }),
            m.numeric({
                name: "bright_threshold",
                cluster: "shellyLightLevel",
                attribute: "brightThreshold",
                valueMin: 0,
                valueMax: 65535,
                reporting: false,
                description: "Lux threshold above which light level is bright",
                unit: "lx",
                access: "ALL",
            }),
        ];
    },
};
// =============================================================================
// Local From Zigbee Converters
// =============================================================================
const handlePosition = e
    .enum("handle_position", ea.STATE, ["closed", "tilted", "open"])
    .withDescription("Handle position: closed, tilted (partly open), or open");
const fzLocal = {
    one_button_events: {
        cluster: "genOnOff",
        type: ["commandToggle"],
        convert: (model, msg, publish, options, meta) => {
            const event = utils.getFromLookup(msg.endpoint.ID, { 1: "single", 2: "double", 3: "triple" });
            return { action: event };
        },
    },
    one_button_scene_events: {
        cluster: "genScenes",
        type: ["commandRecall"],
        convert: (model, msg, publish, options, meta) => {
            const event = utils.getFromLookup(`${msg.endpoint.ID}`, { "1": "single_long", "2": "double_long", "3": "triple_long" });
            return { action: event };
        },
    },
    four_buttons_single_events: {
        cluster: "genOnOff",
        type: ["commandOn", "commandOff", "commandToggle"],
        convert: (model, msg, publish, options, meta) => {
            const event = utils.getFromLookup(`${msg.endpoint.ID}_${msg.type}`, {
                "1_commandOn": "1_single",
                "1_commandOff": "2_single",
                "2_commandOn": "3_single",
                "2_commandOff": "4_single",
                "1_commandToggle": "1_single",
                "2_commandToggle": "2_single",
                "3_commandToggle": "3_single",
                "4_commandToggle": "4_single",
            });
            return { action: event };
        },
    },
    four_buttons_hold_events: {
        cluster: "genLevelCtrl",
        type: ["commandStep"],
        convert: (model, msg, publish, options, meta) => {
            const event = utils.getFromLookup(`${msg.endpoint.ID}_${msg.data.stepmode}`, {
                "1_0": "1_hold",
                "1_1": "2_hold",
                "2_0": "3_hold",
                "2_1": "4_hold",
            });
            return { action: event };
        },
    },
    four_buttons_scene_events: {
        cluster: "genScenes",
        type: ["commandRecall"],
        convert: (model, msg, publish, options, meta) => {
            const event = utils.getFromLookup(`${msg.endpoint.ID}_${msg.data.sceneid}`, {
                "1_1": "1_double",
                "2_1": "2_double",
                "3_1": "3_double",
                "4_1": "4_double",
                "1_2": "1_triple",
                "2_2": "2_triple",
                "3_2": "3_triple",
                "4_2": "4_triple",
                "1_11": "1_single_long",
                "2_11": "2_single_long",
                "3_11": "3_single_long",
                "4_11": "4_single_long",
                "1_12": "1_double_long",
                "2_12": "2_double_long",
                "3_12": "3_double_long",
                "4_12": "4_double_long",
                "1_13": "1_triple_long",
                "2_13": "2_triple_long",
                "3_13": "3_triple_long",
                "4_13": "4_triple_long",
            });
            return { action: event };
        },
    },
    two_switch_inputs_events: {
        cluster: "genOnOff",
        type: ["commandOn", "commandOff", "commandToggle"],
        convert: (model, msg, publish, options, meta) => {
            const event = utils.getFromLookup(`${msg.endpoint.ID}_${msg.type}`, {
                "3_commandOn": "input_1_on",
                "3_commandOff": "input_1_off",
                "3_commandToggle": "input_1_toggle",
                "4_commandOn": "input_2_on",
                "4_commandOff": "input_2_off",
                "4_commandToggle": "input_2_toggle",
            });
            return { action: event };
        },
    },
    two_switch_inputs_scene_events: {
        cluster: "genScenes",
        type: ["commandRecall"],
        convert: (model, msg, publish, options, meta) => {
            const event = utils.getFromLookup(`${msg.endpoint.ID}_${msg.data.sceneid}`, {
                "3_5": "input_1_toggle",
                "4_5": "input_2_toggle",
                "3_11": "input_1_hold",
                "4_11": "input_2_hold",
            });
            return { action: event };
        },
    },
    one_switch_input_events: {
        cluster: "genOnOff",
        type: ["commandOn", "commandOff", "commandToggle"],
        convert: (model, msg, publish, options, meta) => {
            if (msg.endpoint.ID !== 2)
                return {};
            const event = utils.getFromLookup(msg.type, {
                commandOn: "input_1_on",
                commandOff: "input_1_off",
                commandToggle: "input_1_toggle",
            });
            return { action: event };
        },
    },
    one_switch_input_scene_events: {
        cluster: "genScenes",
        type: ["commandRecall"],
        convert: (model, msg, publish, options, meta) => {
            if (msg.endpoint.ID !== 2)
                return {};
            const event = utils.getFromLookup(`${msg.data.sceneid}`, { "5": "input_1_toggle", "11": "input_1_hold" });
            return { action: event };
        },
    },
    switch_input_type: {
        cluster: "genOnOffSwitchCfg",
        type: ["attributeReport", "readResponse"],
        convert: (model, msg, publish, options, meta) => {
            if (!Object.hasOwn(msg.data, "switchType"))
                return {};
            const epName = utils.getFromLookup(msg.endpoint.ID, { 2: "sw1", 3: "sw1", 4: "sw2" });
            if (!epName)
                return {};
            return { [`switch_type_${epName}`]: utils.getFromLookup(msg.data.switchType, { 0: "toggle", 1: "momentary" }) };
        },
    },
    blu_door_window: {
        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 alarm1 = (zoneStatus & 1) > 0;
            const alarm2 = (zoneStatus & (1 << 1)) > 0;
            // Zone type 0x0016 (door/window handle) per ZigBee spec Table 8-7:
            //   alarm1=0, alarm2=0 -> closed
            //   alarm1=1, alarm2=0 -> tilted (partly open)
            //   alarm1=1, alarm2=1 -> open
            let position;
            if (!alarm1 && !alarm2)
                position = "closed";
            else if (alarm1 && !alarm2)
                position = "tilted";
            else
                position = "open";
            return {
                contact: position === "closed",
                handle_position: position,
                battery_low: (zoneStatus & (1 << 3)) > 0,
            };
        },
    },
};
const tzLocal = {
    switch_input_type: {
        key: ["switch_type"],
        convertSet: async (entity, key, value, meta) => {
            const lookup = { toggle: 0, momentary: 1 };
            const ep = (0, utils_1.determineEndpoint)(entity, meta, "genOnOffSwitchCfg");
            await ep.write("genOnOffSwitchCfg", { switchType: utils.getFromLookup(value, lookup) });
            return { state: { switch_type: value } };
        },
        convertGet: async (entity, key, meta) => {
            const ep = (0, utils_1.determineEndpoint)(entity, meta, "genOnOffSwitchCfg");
            await ep.read("genOnOffSwitchCfg", ["switchType"]);
        },
    },
};
// =============================================================================
// Device Definitions
// =============================================================================
exports.definitions = [
    {
        zigbeeModel: ["Mini1", "1 Mini"],
        model: "S4SW-001X8EU",
        vendor: "Shelly",
        description: "1 Mini Gen 4",
        ota: true,
        fromZigbee: [fzLocal.one_switch_input_events, fzLocal.one_switch_input_scene_events, fzLocal.switch_input_type],
        toZigbee: [tzLocal.switch_input_type],
        exposes: [
            e.action(["input_1_on", "input_1_off", "input_1_toggle", "input_1_hold"]),
            e.enum("switch_type", ea.ALL, ["toggle", "momentary"]).withDescription("Switch input type").withCategory("config").withEndpoint("sw1"),
        ],
        extend: [
            m.deviceEndpoints({ endpoints: { sw1: 2 } }),
            m.onOff({ powerOnBehavior: false }),
            ...shellyModernExtend.shellyCustomClusters(),
            shellyModernExtend.shellyRPCSetup(["1PMInputMode"]),
            shellyModernExtend.shellyWiFiSetup(),
        ],
        configure: async (device, coordinatorEndpoint) => {
            const ep = device.getEndpoint(2);
            if (ep) {
                await ep.bind("genOnOff", coordinatorEndpoint);
                await ep.bind("genScenes", coordinatorEndpoint);
                await ep.read("genOnOffSwitchCfg", ["switchType"]);
            }
        },
    },
    {
        fingerprint: [{ modelID: "1", manufacturerName: "Shelly" }],
        model: "S4SW-001X16EU",
        vendor: "Shelly",
        description: "1 Gen 4",
        ota: true,
        fromZigbee: [fzLocal.one_switch_input_events, fzLocal.one_switch_input_scene_events, fzLocal.switch_input_type],
        toZigbee: [tzLocal.switch_input_type],
        exposes: [
            e.action(["input_1_on", "input_1_off", "input_1_toggle", "input_1_hold"]),
            e.enum("switch_type", ea.ALL, ["toggle", "momentary"]).withDescription("Switch input type").withCategory("config").withEndpoint("sw1"),
        ],
        extend: [
            m.deviceEndpoints({ endpoints: { sw1: 2 } }),
            m.onOff({ powerOnBehavior: false }),
            ...shellyModernExtend.shellyCustomClusters(),
            shellyModernExtend.shellyRPCSetup(["1PMInputMode"]),
            shellyModernExtend.shellyWiFiSetup(),
        ],
        configure: async (device, coordinatorEndpoint) => {
            const ep = device.getEndpoint(2);
            if (ep) {
                await ep.bind("genOnOff", coordinatorEndpoint);
                await ep.bind("genScenes", coordinatorEndpoint);
                await ep.read("genOnOffSwitchCfg", ["switchType"]);
            }
        },
    },
    {
        zigbeeModel: ["Mini1PM", "1PM Mini"],
        model: "S4SW-001P8EU",
        vendor: "Shelly",
        description: "1PM Mini Gen 4",
        ota: true,
        fromZigbee: [fzLocal.one_switch_input_events, fzLocal.one_switch_input_scene_events, fzLocal.switch_input_type],
        toZigbee: [tzLocal.switch_input_type],
        exposes: [
            e.action(["input_1_on", "input_1_off", "input_1_toggle", "input_1_hold"]),
            e.enum("switch_type", ea.ALL, ["toggle", "momentary"]).withDescription("Switch input type").withCategory("config").withEndpoint("sw1"),
        ],
        extend: [
            m.deviceEndpoints({ endpoints: { sw1: 2 } }),
            m.onOff({ powerOnBehavior: false }),
            m.electricityMeter({ producedEnergy: true, acFrequency: true }),
            shellyModernExtend.shellyPowerFactorInt16Fix(),
            ...shellyModernExtend.shellyCustomClusters(),
            shellyModernExtend.shellyRPCSetup(["1PMInputMode"]),
            shellyModernExtend.shellyWiFiSetup(),
        ],
        configure: async (device, coordinatorEndpoint) => {
            const ep = device.getEndpoint(2);
            if (ep) {
                await ep.bind("genOnOff", coordinatorEndpoint);
                await ep.bind("genScenes", coordinatorEndpoint);
                await ep.read("genOnOffSwitchCfg", ["switchType"]);
            }
        },
    },
    {
        zigbeeModel: ["1PM"],
        model: "S4SW-001P16EU",
        vendor: "Shelly",
        description: "1PM Gen 4",
        ota: true,
        fromZigbee: [fzLocal.one_switch_input_events, fzLocal.one_switch_input_scene_events, fzLocal.switch_input_type],
        toZigbee: [tzLocal.switch_input_type],
        exposes: [
            e.action(["input_1_on", "input_1_off", "input_1_toggle", "input_1_hold"]),
            e.enum("switch_type", ea.ALL, ["toggle", "momentary"]).withDescription("Switch input type").withCategory("config").withEndpoint("sw1"),
        ],
        extend: [
            m.deviceEndpoints({ endpoints: { sw1: 2 } }),
            m.onOff({ powerOnBehavior: false }),
            m.electricityMeter({ producedEnergy: true, acFrequency: true }),
            shellyModernExtend.shellyPowerFactorInt16Fix(),
            ...shellyModernExtend.shellyCustomClusters(),
            shellyModernExtend.shellyRPCSetup(["1PMInputMode"]),
            shellyModernExtend.shellyWiFiSetup(),
        ],
        configure: async (device, coordinatorEndpoint) => {
            const ep = device.getEndpoint(2);
            if (ep) {
                await ep.bind("genOnOff", coordinatorEndpoint);
                await ep.bind("genScenes", coordinatorEndpoint);
                await ep.read("genOnOffSwitchCfg", ["switchType"]);
            }
        },
    },
    {
        zigbeeModel: ["EM Mini"],
        model: "S4EM-001PXCEU16",
        vendor: "Shelly",
        description: "EM Mini Gen4",
        extend: [
            m.electricityMeter({ producedEnergy: true, acFrequency: true }),
            shellyModernExtend.shellyPowerFactorInt16Fix(),
            ...shellyModernExtend.shellyCustomClusters(),
            shellyModernExtend.shellyWiFiSetup(),
        ],
    },
    {
        fingerprint: [{ modelID: "EM", manufacturerName: "Shelly" }],
        model: "S4EM-002CXCEU",
        vendor: "Shelly",
        description: "EM Gen4",
        extend: [
            m.deviceEndpoints({ endpoints: { "1": 1, "2": 2, "3": 3 } }),
            m.onOff({ powerOnBehavior: false, endpointNames: ["1"] }),
            m.electricityMeter({
                endpointNames: ["2", "3"],
                producedEnergy: true,
                acFrequency: true,
            }),
            m.forcePowerSource({ powerSource: "Mains (single phase)" }),
            shellyModernExtend.shellyPowerFactorInt16Fix(),
            ...shellyModernExtend.shellyCustomClusters(),
            shellyModernExtend.shellyWiFiSetup(),
        ],
    },
    {
        fingerprint: [
            {
                type: "Router",
                manufacturerName: "Shelly",
                modelID: "2PM",
                endpoints: [
                    { ID: 1, profileID: 260, deviceID: 514, inputClusters: [0, 3, 4, 5, 258], outputClusters: [] },
                    { ID: 239, profileID: 49153, deviceID: 8193, inputClusters: [64513, 64514], outputClusters: [] },
                    { ID: 242, profileID: 41440, deviceID: 97, inputClusters: [], outputClusters: [33] },
                ],
            },
            {
                type: "Router",
                manufacturerName: "Shelly",
                modelID: "2PM",
                endpoints: [
                    { ID: 1, profileID: 260, deviceID: 514, inputClusters: [0, 3, 4, 5, 258], outputClusters: [25] },
                    { ID: 2, inputClusters: [7], outputClusters: [3, 4, 5, 6] },
                    { ID: 3, inputClusters: [7], outputClusters: [3, 4, 5, 6] },
                    { ID: 4, inputClusters: [], outputClusters: [3, 4, 6, 8, 258] },
                    { ID: 239, profileID: 49153, deviceID: 8193, inputClusters: [64513, 64514], outputClusters: [] },
                    { ID: 242, profileID: 41440, deviceID: 97, inputClusters: [], outputClusters: [33] },
                ],
            },
        ],
        model: "S4SW-002P16EU-COVER",
        vendor: "Shelly",
        description: "2PM Gen4 (Cover mode)",
        ota: true,
        fromZigbee: [fzLocal.two_switch_inputs_events, fzLocal.two_switch_inputs_scene_events, fzLocal.switch_input_type],
        toZigbee: [tzLocal.switch_input_type],
        exposes: [
            e.action(["input_1_on", "input_1_off", "input_1_toggle", "input_1_hold", "input_2_on", "input_2_off", "input_2_toggle", "input_2_hold"]),
            e.enum("switch_type", ea.ALL, ["toggle", "momentary"]).withDescription("Switch input type").withCategory("config").withEndpoint("sw1"),
            e.enum("switch_type", ea.ALL, ["toggle", "momentary"]).withDescription("Switch input type").withCategory("config").withEndpoint("sw2"),
        ],
        extend: [
            m.deviceEndpoints({ endpoints: { sw1: 2, sw2: 3 } }),
            m.windowCovering({ controls: ["lift", "tilt"] }),
            ...shellyModernExtend.shellyCustomClusters(),
            shellyModernExtend.shellyRPCSetup(["2PMInputMode"]),
            shellyModernExtend.shellyWiFiSetup(),
        ],
        configure: async (device, coordinatorEndpoint) => {
            for (const epID of [2, 3]) {
                const ep = device.getEndpoint(epID);
                if (ep) {
                    await ep.bind("genOnOff", coordinatorEndpoint);
                    await ep.bind("genScenes", coordinatorEndpoint);
                    await ep.read("genOnOffSwitchCfg", ["switchType"]);
                }
            }
        },
    },
    {
        fingerprint: [
            {
                type: "Router",
                manufacturerName: "Shelly",
                modelID: "2PM",
                endpoints: [
                    { ID: 1, profileID: 260, deviceID: 266, inputClusters: [0, 3, 4, 5, 6, 2820, 1794], outputClusters: [] },
                    { ID: 2, profileID: 260, deviceID: 266, inputClusters: [4, 5, 6, 2820, 1794], outputClusters: [] },
                    { ID: 239, profileID: 49153, deviceID: 8193, inputClusters: [64513, 64514], outputClusters: [] },
                    { ID: 242, profileID: 41440, deviceID: 97, inputClusters: [], outputClusters: [33] },
                ],
            },
            {
                type: "Router",
                manufacturerName: "Shelly",
                modelID: "2PM",
                endpoints: [
                    { ID: 1, profileID: 260, deviceID: 266, inputClusters: [0, 3, 4, 5, 6, 2820, 1794], outputClusters: [25] },
                    { ID: 2, profileID: 260, deviceID: 266, inputClusters: [4, 5, 6, 2820, 1794], outputClusters: [] },
                    { ID: 3, inputClusters: [7], outputClusters: [3, 4, 5, 6] },
                    { ID: 4, inputClusters: [7], outputClusters: [3, 4, 5, 6] },
                    { ID: 5, inputClusters: [], outputClusters: [3, 4, 6, 8, 258] },
                    { ID: 239, profileID: 49153, deviceID: 8193, inputClusters: [64513, 64514], outputClusters: [] },
                    { ID: 242, profileID: 41440, deviceID: 97, inputClusters: [], outputClusters: [33] },
                ],
            },
        ],
        model: "S4SW-002P16EU-SWITCH",
        vendor: "Shelly",
        description: "2PM Gen4 (Switch mode)",
        ota: true,
        fromZigbee: [fzLocal.two_switch_inputs_events, fzLocal.two_switch_inputs_scene_events, fzLocal.switch_input_type],
        toZigbee: [tzLocal.switch_input_type],
        exposes: [
            e.action(["input_1_on", "input_1_off", "input_1_toggle", "input_1_hold", "input_2_on", "input_2_off", "input_2_toggle", "input_2_hold"]),
            e.enum("switch_type", ea.ALL, ["toggle", "momentary"]).withDescription("Switch input type").withCategory("config").withEndpoint("sw1"),
            e.enum("switch_type", ea.ALL, ["toggle", "momentary"]).withDescription("Switch input type").withCategory("config").withEndpoint("sw2"),
        ],
        extend: [
            m.deviceEndpoints({ endpoints: { l1: 1, l2: 2, sw1: 3, sw2: 4 } }),
            m.onOff({ powerOnBehavior: false, endpointNames: ["l1", "l2"] }),
            m.electricityMeter({ producedEnergy: true, acFrequency: true, endpointNames: ["l1", "l2"] }),
            shellyModernExtend.shellyPowerFactorInt16Fix(),
            ...shellyModernExtend.shellyCustomClusters(),
            shellyModernExtend.shellyRPCSetup(["2PMInputMode"]),
            shellyModernExtend.shellyWiFiSetup(),
        ],
        configure: async (device, coordinatorEndpoint) => {
            for (const epID of [3, 4]) {
                const ep = device.getEndpoint(epID);
                if (ep) {
                    await ep.bind("genOnOff", coordinatorEndpoint);
                    await ep.bind("genScenes", coordinatorEndpoint);
                    await ep.read("genOnOffSwitchCfg", ["switchType"]);
                }
            }
        },
    },
    {
        fingerprint: [{ modelID: "Plug US", manufacturerName: "Shelly" }],
        model: "S4PL-00116US",
        vendor: "Shelly",
        description: "Plug US Gen4",
        extend: [
            m.onOff({ powerOnBehavior: false }),
            m.electricityMeter(),
            shellyModernExtend.shellyPowerFactorInt16Fix(),
            ...shellyModernExtend.shellyCustomClusters(),
            shellyModernExtend.shellyWiFiSetup(),
        ],
    },
    {
        fingerprint: [{ modelID: "Power Strip", manufacturerName: "Shelly" }],
        model: "S4PL-00416EU",
        vendor: "Shelly",
        description: "Power strip 4 Gen4",
        version: "0.0.1",
        extend: [
            m.deviceEndpoints({ endpoints: { "1": 1, "2": 2, "3": 3, "4": 4 } }),
            m.onOff({ powerOnBehavior: false, endpointNames: ["1", "2", "3", "4"] }),
            m.electricityMeter({
                endpointNames: ["1", "2", "3", "4"],
                // Reduce reporting to prevent crashes
                // https://github.com/Koenkk/zigbee2mqtt/issues/31183
                acFrequency: { change: 125 },
                current: { change: 60 },
                voltage: { change: 625 },
                power: { change: 6 },
                energy: { change: 125000 },
            }),
            shellyModernExtend.shellyPowerFactorInt16Fix(),
            ...shellyModernExtend.shellyCustomClusters(),
            shellyModernExtend.shellyRPCSetup(["PowerstripUI", "PowerstripPowerOnBehavior"]),
            shellyModernExtend.shellyWiFiSetup(),
        ],
    },
    {
        fingerprint: [{ modelID: "Flood", manufacturerName: "Shelly" }],
        model: "S4SN-0071A",
        vendor: "Shelly",
        description: "Flood Gen 4",
        extend: [
            m.battery({ percentageReportingConfig: false }),
            m.iasZoneAlarm({ zoneType: "water_leak", zoneAttributes: ["alarm_1", "tamper", "battery_low", "trouble"] }),
            ...shellyModernExtend.shellyCustomClusters(),
            shellyModernExtend.shellyWiFiSetup(),
        ],
    },
    {
        fingerprint: [{ modelID: "Ecowitt WS90", manufacturerName: "Shelly" }],
        model: "WS90",
        vendor: "Shelly",
        description: "Weather station",
        extend: [
            m.battery(),
            m.illuminance(),
            m.temperature(),
            m.pressure(),
            m.humidity(),
            m.deviceAddCustomCluster("shellyWS90Wind", {
                name: "shellyWS90Wind",
                ID: 0xfc01,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SHELLY,
                attributes: {
                    windSpeed: { name: "windSpeed", ID: 0x0000, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
                    windDirection: { name: "windDirection", ID: 0x0004, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
                    gustSpeed: { name: "gustSpeed", ID: 0x0007, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
                },
                commands: {},
                commandsResponse: {},
            }),
            m.numeric({
                name: "wind_speed",
                cluster: "shellyWS90Wind",
                attribute: "windSpeed",
                valueMin: 0,
                valueMax: 140,
                reporting: { min: "10_SECONDS", max: "1_HOUR", change: 1 },
                description: "Wind speed in m/s",
                scale: 10,
                unit: "m/s",
                access: "STATE_GET",
            }),
            m.numeric({
                name: "wind_direction",
                cluster: "shellyWS90Wind",
                attribute: "windDirection",
                valueMin: 0,
                valueMax: 360,
                reporting: { min: "10_SECONDS", max: "1_HOUR", change: 1 },
                description: "Wind direction in degrees",
                scale: 10,
                unit: "°",
                access: "STATE_GET",
            }),
            m.numeric({
                name: "gust_speed",
                cluster: "shellyWS90Wind",
                attribute: "gustSpeed",
                valueMin: 0,
                valueMax: 140,
                reporting: { min: "10_SECONDS", max: "1_HOUR", change: 1 },
                description: "Gust speed in m/s",
                scale: 10,
                unit: "m/s",
                access: "STATE_GET",
            }),
            m.deviceAddCustomCluster("shellyWS90UV", {
                name: "shellyWS90UV",
                ID: 0xfc02,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SHELLY,
                attributes: {
                    uvIndex: { name: "uvIndex", ID: 0x0000, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
                },
                commands: {},
                commandsResponse: {},
            }),
            m.numeric({
                name: "uv_index",
                cluster: "shellyWS90UV",
                attribute: "uvIndex",
                valueMin: 0,
                valueMax: 11,
                reporting: { min: "10_SECONDS", max: "1_HOUR", change: 1 },
                description: "UV index",
                scale: 10,
                access: "STATE_GET",
            }),
            m.deviceAddCustomCluster("shellyWS90Rain", {
                name: "shellyWS90Rain",
                ID: 0xfc03,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SHELLY,
                attributes: {
                    rainStatus: { name: "rainStatus", ID: 0x0000, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN },
                    precipitation: { name: "precipitation", ID: 0x0001, type: zigbee_herdsman_1.Zcl.DataType.UINT24 },
                },
                commands: {},
                commandsResponse: {},
            }),
            m.binary({
                name: "rain_status",
                cluster: "shellyWS90Rain",
                attribute: "rainStatus",
                valueOn: [true, 1],
                valueOff: [false, 0],
                reporting: { min: "10_SECONDS", max: "1_HOUR", change: 1 },
                description: "Rain status",
                access: "STATE_GET",
            }),
            m.numeric({
                name: "precipitation",
                cluster: "shellyWS90Rain",
                attribute: "precipitation",
                valueMin: 0,
                valueMax: 100000,
                reporting: { min: "10_SECONDS", max: "1_HOUR", change: 1 },
                description: "Precipitation",
                unit: "mm",
                scale: 10,
                access: "STATE_GET",
            }),
            // Calculated values (added by PR #11437)
            shellyModernExtend.ws90CalculatedValues(),
        ],
    },
    {
        fingerprint: [
            { modelID: "Dimmer", manufacturerName: "Shelly" },
            { modelID: "Dimmer US", manufacturerName: "Shelly" },
        ],
        model: "S4DM-0A101WWL",
        vendor: "Shelly",
        description: "Dimmer Gen4",
        extend: [
            m.light({ configureReporting: true }),
            m.electricityMeter(),
            shellyModernExtend.shellyPowerFactorInt16Fix(),
            ...shellyModernExtend.shellyCustomClusters(),
            shellyModernExtend.shellyWiFiSetup(),
        ],
    },
    {
        fingerprint: [{ modelID: "BLU H&T ZB", manufacturerName: "Shelly" }],
        model: "SBHT-203C",
        vendor: "Shelly",
        description: "Humidity & temperature sensor",
        extend: [m.battery(), m.temperature(), m.humidity()],
    },
    {
        fingerprint: [{ modelID: "BLU H&T Display ZB", manufacturerName: "Shelly" }],
        model: "SBHT-103C",
        vendor: "Shelly",
        description: "BLU H&T display Zigbee",
        extend: [m.battery(), m.temperature(), m.humidity(), ...shellyModernExtend.shellyLightLevel()],
    },
    {
        fingerprint: [{ modelID: "BLU Remote Control ZB", manufacturerName: "Shelly" }],
        model: "SBRC-005B-B",
        vendor: "Shelly",
        description: "BLU Remote Control ZB",
        exposes: [
            e.action(["on", "off", "brightness_step_up", "brightness_step_down"]),
            e.numeric("action_group", ea.STATE).withDescription("Group ID associated with the action command."),
            e.numeric("action_step_size", ea.STATE).withDescription("Step size value used for brightness step actions."),
            e.numeric("action_transition_time", ea.STATE).withDescription("Transition time in seconds for the action."),
        ],
        extend: [
            m.battery(),
            m.commandsOnOff({ commands: ["on", "off"] }),
            m.commandsLevelCtrl({ commands: ["brightness_step_up", "brightness_step_down"] }),
            m.identify(),
        ],
    },
    {
        fingerprint: [{ modelID: "BLU Button Tough 1 ZB", manufacturerName: "Shelly" }],
        model: "SBBT-102C",
        vendor: "Shelly",
        description: "BLU Button Tough 1 ZB",
        fromZigbee: [fzLocal.one_button_events, fzLocal.one_button_scene_events],
        exposes: [e.action(["single", "double", "triple", "single_long", "double_long", "triple_long"])],
        extend: [m.battery(), m.deviceEndpoints({ endpoints: { "1": 1, "2": 2, "3": 3 } }), m.identify()],
        version: "0.0.2",
        configure: async (device, coordinatorEndpoint, definition) => {
            for (const endpoint of device.endpoints) {
                await endpoint.bind("genOnOff", coordinatorEndpoint);
                await endpoint.bind("genScenes", coordinatorEndpoint);
            }
        },
    },
    {
        zigbeeModel: ["BLU RC Button 4 ZB", "BLU Wall Switch 4 ZB", "BLU Wall Switch 4 ZB DK"],
        model: "SBBT-104CUS",
        vendor: "Shelly",
        description: "BLU RC Button 4 ZB",
        whiteLabel: [
            { vendor: "Shelly", model: "SBBT-004CEU", fingerprint: [{ modelID: "BLU Wall Switch 4 ZB" }], description: "BLU Wall Switch 4 ZB" },
            { vendor: "Shelly", model: "SBBT-104CEU", fingerprint: [{ modelID: "BLU Wall Switch 4 ZB DK" }], description: "BLU Wall Switch 4 ZB DK" },
        ],
        fromZigbee: [fzLocal.four_buttons_single_events, fzLocal.four_buttons_hold_events, fzLocal.four_buttons_scene_events],
        exposes: [
            e.action([
                "1_single",
                "2_single",
                "3_single",
                "4_single",
                "1_double",
                "2_double",
                "3_double",
                "4_double",
                "1_triple",
                "2_triple",
                "3_triple",
                "4_triple",
                "1_single_long",
                "2_single_long",
                "3_single_long",
                "4_single_long",
                "1_double_long",
                "2_double_long",
                "3_double_long",
                "4_double_long",
                "1_triple_long",
                "2_triple_long",
                "3_triple_long",
                "4_triple_long",
                "1_hold",
                "2_hold",
                "3_hold",
                "4_hold",
            ]),
        ],
        extend: [m.battery(), m.deviceEndpoints({ endpoints: { "1": 1, "2": 2, "3": 3, "4": 4 } }), m.identify()],
        version: "0.0.2",
        configure: async (device, coordinatorEndpoint, definition) => {
            for (const endpoint of device.endpoints) {
                await endpoint.bind("genOnOff", coordinatorEndpoint);
                await endpoint.bind("genLevelCtrl", coordinatorEndpoint);
                await endpoint.bind("genScenes", coordinatorEndpoint);
            }
        },
    },
    {
        zigbeeModel: ["BLU TRV"],
        model: "SBTR-001AEU",
        vendor: "Shelly",
        description: "Thermostatic radiator valve",
        fromZigbee: [
            fz.thermostat,
            {
                cluster: "hvacThermostat",
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    const result = {};
                    if (msg.data.alarmMask !== undefined) {
                        const alarmMask = msg.data.alarmMask;
                        result.calibration_ok = !((alarmMask & (1 << 2)) > 0);
                    }
                    return result;
                },
            },
        ],
        toZigbee: [
            {
                key: ["calibrate"],
                convertSet: async (entity, key, value, meta) => {
                    await entity.command("shellyTRVManualMode", "calibrate", {}, { manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SHELLY });
                },
            },
        ],
        exposes: [
            e.binary("calibration_ok", ea.STATE, true, false).withDescription("Calibration OK").withCategory("diagnostic"),
            e.enum("calibrate", ea.SET, ["trigger"]).withDescription("Trigger valve calibration").withCategory("config"),
        ],
        extend: [
            m.battery(),
            m.thermostat({
                localTemperatureCalibration: { values: { min: -10, max: 10, step: 0.1 } },
                setpoints: {
                    values: {
                        occupiedHeatingSetpoint: { min: 4, max: 30, step: 0.1 },
                        unoccupiedHeatingSetpoint: { min: 4, max: 30, step: 0.1 },
                    },
                },
                setpointsLimit: {
                    minHeatSetpointLimit: { min: 4, max: 30, step: 0.1 },
                    maxHeatSetpointLimit: { min: 4, max: 30, step: 0.1 },
                },
                systemMode: { values: ["off", "auto", "heat"] },
                piHeatingDemand: { values: true },
            }),
            m.deviceAddCustomCluster("shellyTRVManualMode", {
                name: "shellyTRVManualMode",
                ID: 0xfc24,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SHELLY,
                attributes: {
                    manualMode: { name: "manualMode", ID: 0x0000, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
                    position: { name: "position", ID: 0x0001, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
                },
                commands: {
                    calibrate: { name: "calibrate", ID: 0x0000, parameters: [] },
                },
                commandsResponse: {},
            }),
            m.binary({
                name: "manual_mode",
                cluster: "shellyTRVManualMode",
                attribute: { ID: 0x0000, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
                valueOn: [true, 1],
                valueOff: [false, 0],
                description: "Manual mode (0 = auto, 1 = manual)",
                access: "ALL",
            }),
            m.numeric({
                name: "valve_position",
                cluster: "shellyTRVManualMode",
                attribute: { ID: 0x0001, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
                valueMin: 0,
                valueMax: 100,
                reporting: { min: "10_SECONDS", max: "1_HOUR", change: 1 },
                description: "Valve position (0-100%)",
                unit: "%",
                access: "ALL",
            }),
            m.identify(),
        ],
    },
    {
        fingerprint: [{ modelID: "Presence", manufacturerName: "Shelly" }],
        model: "S4SN-0U61X",
        vendor: "Shelly",
        description: "Presence Gen4 Zigbee",
        extend: [
            m.deviceEndpoints({ endpoints: { "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "10": 10 } }),
            m.occupancy({ reporting: false, endpointNames: ["1"] }),
            m.occupancy({ reporting: false, endpointNames: ["2"] }),
            m.occupancy({ reporting: false, endpointNames: ["3"] }),
            m.occupancy({ reporting: false, endpointNames: ["4"] }),
            m.occupancy({ reporting: false, endpointNames: ["5"] }),
            m.occupancy({ reporting: false, endpointNames: ["6"] }),
            m.occupancy({ reporting: false, endpointNames: ["7"] }),
            m.occupancy({ reporting: false, endpointNames: ["8"] }),
            m.occupancy({ reporting: false, endpointNames: ["9"] }),
            m.occupancy({ reporting: false, endpointNames: ["10"] }),
            ...shellyModernExtend.shellyLightLevel(),
            m.identify(),
            ...shellyModernExtend.shellyCustomClusters(),
            shellyModernExtend.shellyWiFiSetup(),
        ],
    },
    {
        fingerprint: [{ modelID: "BLU DoorWindow ZB", manufacturerName: "Shelly" }],
        model: "SBDW-103C",
        vendor: "Shelly",
        description: "BLU DoorWindow ZB",
        fromZigbee: [fzLocal.blu_door_window],
        toZigbee: [],
        exposes: [e.contact(), handlePosition, e.battery_low()],
        extend: [m.battery(), ...shellyModernExtend.shellyLightLevel(), m.identify()],
        configure: async (device, coordinatorEndpoint) => {
            const endpoint = device.getEndpoint(1);
            if (endpoint) {
                await endpoint.read("ssIasZone", ["zoneStatus"]);
            }
        },
    },
    {
        fingerprint: [{ modelID: "BLU Motion ZB", manufacturerName: "Shelly" }],
        model: "SBMO-103Z",
        vendor: "Shelly",
        description: "BLU Motion ZB",
        extend: [
            m.iasZoneAlarm({ zoneType: "occupancy", zoneAttributes: ["alarm_1", "battery_low"] }),
            m.battery(),
            ...shellyModernExtend.shellyLightLevel(),
            m.enumLookup({
                name: "motion_sensitivity",
                cluster: "ssIasZone",
                attribute: "currentZoneSensitivityLevel",
                lookup: { low: 1, medium: 2, high: 3 },
                description: "Motion sensor sensitivity",
                access: "ALL",
                reporting: false,
                entityCategory: "config",
            }),
            m.identify(),
        ],
        configure: async (device, coordinatorEndpoint) => {
            const endpoint = device.getEndpoint(1);
            if (endpoint) {
                await endpoint.read("ssIasZone", ["currentZoneSensitivityLevel"]);
            }
        },
    },
];
//# sourceMappingURL=shelly.js.map