"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.develcoModernExtend = void 0;
const zigbee_herdsman_1 = require("zigbee-herdsman");
const exposes_1 = require("./exposes");
const modernExtend_1 = require("./modernExtend");
const utils_1 = require("./utils");
const manufacturerOptions = { manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.DEVELCO };
exports.develcoModernExtend = {
    addCustomClusterManuSpecificDevelcoGenBasic: () => (0, modernExtend_1.deviceAddCustomCluster)("genBasic", {
        name: "genBasic",
        ID: zigbee_herdsman_1.Zcl.Clusters.genBasic.ID,
        attributes: {
            develcoPrimarySwVersion: {
                name: "develcoPrimarySwVersion",
                ID: 0x8000,
                type: zigbee_herdsman_1.Zcl.DataType.OCTET_STR,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.DEVELCO,
                write: true,
            },
            develcoPrimaryHwVersion: {
                name: "develcoPrimaryHwVersion",
                ID: 0x8020,
                type: zigbee_herdsman_1.Zcl.DataType.OCTET_STR,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.DEVELCO,
                write: true,
            },
            develcoLedControl: {
                name: "develcoLedControl",
                ID: 0x8100,
                type: zigbee_herdsman_1.Zcl.DataType.BITMAP8,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.DEVELCO,
                write: true,
            },
            develcoTxPower: {
                name: "develcoTxPower",
                ID: 0x8101,
                type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.DEVELCO,
                write: true,
                max: 0xff,
            },
        },
        commands: {},
        commandsResponse: {},
    }),
    addCustomClusterManuSpecificDevelcoIasZone: () => (0, modernExtend_1.deviceAddCustomCluster)("ssIasZone", {
        name: "ssIasZone",
        ID: zigbee_herdsman_1.Zcl.Clusters.ssIasZone.ID,
        attributes: {
            develcoZoneStatusInterval: {
                name: "develcoZoneStatusInterval",
                ID: 0x8000,
                type: zigbee_herdsman_1.Zcl.DataType.UINT16,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.DEVELCO,
                write: true,
                max: 0xffff,
            },
            develcoAlarmOffDelay: {
                name: "develcoAlarmOffDelay",
                ID: 0x8001,
                type: zigbee_herdsman_1.Zcl.DataType.UINT16,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.DEVELCO,
                write: true,
                max: 0xffff,
            },
        },
        commands: {},
        commandsResponse: {},
    }),
    addCustomClusterManuSpecificDevelcoAirQuality: () => (0, modernExtend_1.deviceAddCustomCluster)("manuSpecificDevelcoAirQuality", {
        name: "manuSpecificDevelcoAirQuality",
        ID: 0xfc03,
        manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.DEVELCO,
        attributes: {
            measuredValue: { name: "measuredValue", ID: 0x0000, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff },
            minMeasuredValue: { name: "minMeasuredValue", ID: 0x0001, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff },
            maxMeasuredValue: { name: "maxMeasuredValue", ID: 0x0002, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff },
            resolution: { name: "resolution", ID: 0x0003, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff },
        },
        commands: {},
        commandsResponse: {},
    }),
    addCustomDevelcoSeMeteringCluster: () => (0, modernExtend_1.deviceAddCustomCluster)("seMetering", {
        name: "seMetering",
        ID: zigbee_herdsman_1.Zcl.Clusters.seMetering.ID,
        attributes: {
            develcoPulseConfiguration: {
                name: "develcoPulseConfiguration",
                ID: 0x0300,
                type: zigbee_herdsman_1.Zcl.DataType.UINT16,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.DEVELCO,
                write: true,
                max: 0xffff,
            },
            develcoCurrentSummation: {
                name: "develcoCurrentSummation",
                ID: 0x0301,
                type: zigbee_herdsman_1.Zcl.DataType.UINT48,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.DEVELCO,
                write: true,
                max: 0xffffffffffff,
            },
            develcoInterfaceMode: {
                name: "develcoInterfaceMode",
                ID: 0x0302,
                type: zigbee_herdsman_1.Zcl.DataType.ENUM16,
                manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.DEVELCO,
                write: true,
                max: 0xffff,
            },
        },
        commands: {},
        commandsResponse: {},
    }),
    readGenBasicPrimaryVersions: () => {
        /*
         * Develco (and there B2C brand Frient) do not use swBuildId
         *  The versions are stored in develcoPrimarySwVersion and develcoPrimaryHwVersion, we read them during configure.
         */
        const configure = [
            async (device, coordinatorEndpoint, definition) => {
                for (const ep of device.endpoints) {
                    if (ep.supportsInputCluster("genBasic")) {
                        try {
                            const data = await ep.read("genBasic", ["develcoPrimarySwVersion", "develcoPrimaryHwVersion"], manufacturerOptions);
                            if (data.develcoPrimarySwVersion !== undefined) {
                                device.softwareBuildID = data.develcoPrimarySwVersion.join(".");
                            }
                            if (data.develcoPrimaryHwVersion !== undefined) {
                                device.hardwareVersion = Number.parseInt(data.develcoPrimaryHwVersion.join(""), 10);
                            }
                            device.save();
                        }
                        catch {
                            /* catch timeouts of sleeping devices */
                        }
                        break;
                    }
                }
            },
        ];
        return { configure, isModernExtend: true };
    },
    voc: (args) => (0, modernExtend_1.numeric)({
        name: "voc",
        cluster: "manuSpecificDevelcoAirQuality",
        attribute: "measuredValue",
        reporting: { min: "1_MINUTE", max: "1_HOUR", change: 10 },
        description: "Measured VOC value",
        // from Sensirion_Gas_Sensors_SGP3x_TVOC_Concept.pdf
        // "The mean molar mass of this mixture is 110 g/mol and hence,
        // 1 ppb TVOC corresponds to 4.5 μg/m3."
        scale: (value, type) => {
            if (type === "from") {
                return value * 4.5;
            }
            return value;
        },
        unit: "µg/m³",
        access: "STATE_GET",
        ...args,
    }),
    airQuality: () => {
        // NOTE: do not setup reporting, this is handled by the voc() modernExtend
        const clusterName = "manuSpecificDevelcoAirQuality";
        const attributeName = "measuredValue";
        const propertyName = "air_quality";
        const access = exposes_1.access.STATE;
        const expose = exposes_1.presets
            .enum("air_quality", access, ["excellent", "good", "moderate", "poor", "unhealthy", "out_of_range", "unknown"])
            .withDescription("Measured air quality");
        const fromZigbee = [
            {
                cluster: clusterName,
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    if (msg.data[attributeName] !== undefined) {
                        const vocPpb = Number.parseFloat(msg.data[attributeName]);
                        // from aqszb-110-technical-manual-air-quality-sensor-04-08-20.pdf page 6, section 2.2 voc
                        // this contains a ppb to level mapping table.
                        // biome-ignore lint/suspicious/noImplicitAnyLet: ignored using `--suppress`
                        let airQuality;
                        if (vocPpb <= 65) {
                            airQuality = "excellent";
                        }
                        else if (vocPpb <= 220) {
                            airQuality = "good";
                        }
                        else if (vocPpb <= 660) {
                            airQuality = "moderate";
                        }
                        else if (vocPpb <= 2200) {
                            airQuality = "poor";
                        }
                        else if (vocPpb <= 5500) {
                            airQuality = "unhealthy";
                        }
                        else if (vocPpb > 5500) {
                            airQuality = "out_of_range";
                        }
                        else {
                            airQuality = "unknown";
                        }
                        return { [propertyName]: airQuality };
                    }
                },
            },
        ];
        return { exposes: [expose], fromZigbee, isModernExtend: true };
    },
    batteryLowAA: () => {
        /*
         * Per the technical documentation for AQSZB-110:
         * To detect low battery the system can monitor the "BatteryVoltage" by setting up a reporting interval of every 12 hour.
         * When a voltage of 2.5V is measured the battery should be replaced.
         * Low batt LED indication–RED LED will blink twice every 60 second.
         *
         * Similar notes found in other 2x AA powered Develco devices like HMSZB-110 and MOSZB-140
         */
        const clusterName = "genPowerCfg";
        const attributeName = "batteryVoltage";
        const propertyName = "battery_low";
        const expose = exposes_1.presets.battery_low();
        const fromZigbee = [
            {
                cluster: clusterName,
                type: ["attributeReport", "readResponse"],
                convert: (model, msg, publish, options, meta) => {
                    if (msg.data[attributeName] !== undefined && msg.data[attributeName] < 255) {
                        const voltage = msg.data[attributeName];
                        return { [propertyName]: voltage <= 25 };
                    }
                },
            },
        ];
        return { exposes: [expose], fromZigbee, isModernExtend: true };
    },
    temperature: (args) => (0, modernExtend_1.temperature)({
        ...args,
    }),
    deviceTemperature: (args) => (0, modernExtend_1.deviceTemperature)({
        reporting: { min: "5_MINUTES", max: "1_HOUR", change: 2 }, // Device temperature reports with 2 degree change
        ...args,
    }),
    currentSummation: (args) => (0, modernExtend_1.numeric)({
        name: "current_summation",
        cluster: "seMetering",
        attribute: "develcoCurrentSummation",
        description: "Current summation value sent to the display. e.g. 570 = 0,570 kWh",
        access: "SET",
        valueMin: 0,
        valueMax: 268435455,
        ...args,
    }),
    pulseConfiguration: (args) => (0, modernExtend_1.numeric)({
        name: "pulse_configuration",
        cluster: "seMetering",
        attribute: "develcoPulseConfiguration",
        description: "Pulses per kwh. Default 1000 imp/kWh. Range 0 to 65535",
        access: "ALL",
        valueMin: 0,
        valueMax: 65535,
        ...args,
    }),
    ledControl: () => {
        const expose = exposes_1.presets
            .composite("led_control", "led_control", exposes_1.access.ALL)
            .withFeature(exposes_1.presets
            .binary("indicate_faults", exposes_1.access.ALL, true, false)
            .withDescription("Enable/disable LED indication for faults (e.g., lost connection to gateway)"))
            .withFeature(exposes_1.presets.binary("indicate_mains_power", exposes_1.access.ALL, true, false).withDescription("Enable/disable green LED indication for mains power status"));
        const fromZigbee = [
            {
                cluster: "genBasic",
                type: ["attributeReport", "readResponse"],
                convert: (_model, msg, _publish, _options, _meta) => {
                    if (Object.hasOwn(msg.data, "develcoLedControl")) {
                        const ledControl = msg.data.develcoLedControl;
                        return {
                            led_control: {
                                indicate_faults: (ledControl & 1) > 0,
                                indicate_mains_power: (ledControl & 2) > 0,
                            },
                        };
                    }
                },
            },
        ];
        const toZigbee = [
            {
                key: ["led_control"],
                convertSet: async (entity, _key, value, meta) => {
                    // biome-ignore lint/style/useNamingConvention: zigbee2mqtt uses snake_case for exposed attributes
                    const currentState = meta.state.led_control || {
                        indicate_faults: false,
                        indicate_mains_power: false,
                    };
                    // biome-ignore lint/style/useNamingConvention: zigbee2mqtt uses snake_case for exposed attributes
                    const newState = { ...currentState, ...value };
                    let bitmap = 0;
                    if (newState.indicate_faults)
                        bitmap |= 1;
                    if (newState.indicate_mains_power)
                        bitmap |= 2;
                    await entity.write("genBasic", { develcoLedControl: bitmap }, manufacturerOptions);
                    return { state: { led_control: newState } };
                },
                convertGet: async (entity, _key, _meta) => {
                    await entity.read("genBasic", ["develcoLedControl"], manufacturerOptions);
                },
            },
        ];
        const configure = [
            async (device, _coordinatorEndpoint, _definition) => {
                for (const ep of device.endpoints) {
                    if (ep.supportsInputCluster("genBasic")) {
                        try {
                            await ep.read("genBasic", ["develcoLedControl"], manufacturerOptions);
                        }
                        catch {
                            /* catch timeouts of sleeping devices */
                        }
                        break;
                    }
                }
            },
        ];
        return { exposes: [expose], fromZigbee, toZigbee, configure, isModernExtend: true };
    },
    txPower: () => {
        const expose = exposes_1.presets
            .enum("tx_power", exposes_1.access.ALL, ["CE", "FCC"])
            .withDescription("TX power mode for regulatory compliance (CE or FCC). Requires device rejoin to apply.");
        const fromZigbee = [
            {
                cluster: "genBasic",
                type: ["attributeReport", "readResponse"],
                convert: (_model, msg, _publish, _options, _meta) => {
                    if (Object.hasOwn(msg.data, "develcoTxPower")) {
                        return { tx_power: msg.data.develcoTxPower === 0 ? "CE" : "FCC" };
                    }
                },
            },
        ];
        const toZigbee = [
            {
                key: ["tx_power"],
                convertSet: async (entity, _key, value, _meta) => {
                    const numericValue = value === "CE" ? 0 : 1;
                    await entity.write("genBasic", { develcoTxPower: numericValue }, manufacturerOptions);
                    return { state: { tx_power: value } };
                },
                convertGet: async (entity, _key, _meta) => {
                    await entity.read("genBasic", ["develcoTxPower"], manufacturerOptions);
                },
            },
        ];
        const configure = [
            async (device, _coordinatorEndpoint, _definition) => {
                for (const ep of device.endpoints) {
                    if (ep.supportsInputCluster("genBasic")) {
                        try {
                            await ep.read("genBasic", ["develcoTxPower"], manufacturerOptions);
                        }
                        catch {
                            /* catch timeouts of sleeping devices */
                        }
                        break;
                    }
                }
            },
        ];
        return { exposes: [expose], fromZigbee, toZigbee, configure, isModernExtend: true };
    },
    zoneStatusInterval: () => {
        const expose = exposes_1.presets
            .numeric("zone_status_interval", exposes_1.access.ALL)
            .withUnit("s")
            .withValueMin(0)
            .withValueMax(65535)
            .withDescription("Heartbeat interval in seconds. Controls the periodic interval between ZoneStatusChange commands (default 300s)");
        const fromZigbee = [
            {
                cluster: "ssIasZone",
                type: ["attributeReport", "readResponse"],
                convert: (_model, msg, _publish, _options, _meta) => {
                    if (Object.hasOwn(msg.data, "develcoZoneStatusInterval")) {
                        return { zone_status_interval: msg.data.develcoZoneStatusInterval };
                    }
                },
            },
        ];
        const toZigbee = [
            {
                key: ["zone_status_interval"],
                convertSet: async (entity, _key, value, _meta) => {
                    await entity.write("ssIasZone", { develcoZoneStatusInterval: value }, manufacturerOptions);
                    return { state: { zone_status_interval: value } };
                },
                convertGet: async (entity, _key, _meta) => {
                    await entity.read("ssIasZone", ["develcoZoneStatusInterval"], manufacturerOptions);
                },
            },
        ];
        const configure = [
            async (device, _coordinatorEndpoint, _definition) => {
                for (const ep of device.endpoints) {
                    if (ep.supportsInputCluster("ssIasZone")) {
                        try {
                            await ep.read("ssIasZone", ["develcoZoneStatusInterval"], manufacturerOptions);
                        }
                        catch {
                            /* catch timeouts of sleeping devices */
                        }
                        break;
                    }
                }
            },
        ];
        return { exposes: [expose], fromZigbee, toZigbee, configure, isModernExtend: true };
    },
    acConnected: () => {
        const expose = exposes_1.presets
            .binary("ac_connected", exposes_1.access.STATE, true, false)
            .withDescription("Indicates whether the device is connected to AC mains power")
            .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) {
                        // Bit 7 = 1 means "mains power lost", so ac_connected = false
                        // Bit 7 = 0 means "mains power ok", so ac_connected = true
                        return { ac_connected: (zoneStatus & (1 << 7)) === 0 };
                    }
                },
            },
        ];
        const configure = [
            async (device, _coordinatorEndpoint, _definition) => {
                for (const ep of device.endpoints) {
                    if (ep.supportsInputCluster("ssIasZone")) {
                        try {
                            await ep.read("ssIasZone", ["zoneStatus"]);
                        }
                        catch {
                            /* catch timeouts of sleeping devices */
                        }
                        break;
                    }
                }
            },
        ];
        return { exposes: [expose], fromZigbee, configure, isModernExtend: true };
    },
    customPulseTrigger: (options) => {
        const endpointNames = options?.endpointNames || [];
        const durationExpose = exposes_1.presets
            .numeric("duration", exposes_1.access.STATE_SET)
            .withLabel("Pulse duration")
            .withUnit("s")
            .withValueMin(0.0)
            .withValueMax(3600)
            .withDescription("Duration of the pulse.");
        const triggerExpose = exposes_1.presets
            .enum("trigger", exposes_1.access.SET, ["press"])
            .withDescription("Trigger a timed pulse. The length of the pulse is defined by 'Pulse duration'. If the 'Pulse duration' is undefined a default value of 1s will be used.");
        const exposes = [...(0, utils_1.exposeEndpoints)(durationExpose, endpointNames), ...(0, utils_1.exposeEndpoints)(triggerExpose, endpointNames)];
        const toZigbee = [
            {
                key: ["trigger", "duration"],
                convertSet: async (entity, key, value, meta) => {
                    if (key === "duration") {
                        const val = value === null ? 1.0 : Number(value);
                        return { state: { [key]: val } };
                    }
                    if (key === "trigger" && value === "press") {
                        const epSuffix = meta.endpoint_name ? `_${meta.endpoint_name}` : "";
                        const stateLookupKey = `duration${epSuffix}`;
                        const seconds = meta.state[stateLookupKey] !== undefined ? Number(meta.state[stateLookupKey]) : 1.0;
                        const deciseconds = Math.round(seconds * 10);
                        await entity.command("genOnOff", "onWithTimedOff", {
                            ctrlbits: 0,
                            ontime: deciseconds,
                            offwaittime: 0,
                        }, meta.options);
                        return { state: { [key]: null } };
                    }
                },
            },
        ];
        return {
            isModernExtend: true,
            exposes,
            toZigbee,
        };
    },
};
//# sourceMappingURL=develco.js.map