UNPKG

zigbee-herdsman-converters

Version:

Collection of device converters to be used with zigbee-herdsman

2,113 lines • 97.5 kB
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ias_sos_alarm_2 = exports.ias_carbon_monoxide_alarm_1_gas_alarm_2 = exports.ias_carbon_monoxide_alarm_1 = exports.ias_contact_alarm_1_report = exports.ias_contact_alarm_1 = exports.ias_smoke_alarm_1 = exports.ias_gas_alarm_2 = exports.ias_gas_alarm_1 = exports.ias_vibration_alarm_1_with_timeout = exports.ias_vibration_alarm_1 = exports.ias_water_leak_alarm_1_report = exports.ias_water_leak_alarm_1 = exports.ias_siren = exports.ias_no_alarm = exports.power_on_behavior = exports.on_off_skip_duplicate_transaction = exports.on_off_force_multiendpoint = exports.on_off = exports.gas_metering = exports.electrical_measurement = exports.metering = exports.meter_identification = exports.color_colortemp = exports.level_config = exports.brightness = exports.occupancy_timeout = exports.occupancy_with_timeout = exports.occupancy = exports.co2 = exports.pressure = exports.soil_moisture = exports.flow = exports.pm25 = exports.humidity = exports.device_temperature = exports.temperature = exports.battery = exports.linkquality_from_basic = exports.lock_user_status_response = exports.lock_pin_code_response = exports.lock_set_pin_code_response = exports.lock = exports.lock_programming_event_read_pincode = exports.lock_programming_event = exports.lock_operation_event = exports.hvac_user_interface = exports.thermostat_weekly_schedule = exports.thermostat = exports.fan_speed = exports.fan = void 0;
exports.ignore_onoff_report = exports.command_status_change_notification_action = exports.ewelink_action = exports.hw_version = exports.power_source = exports.ias_enroll = exports.checkin_presence = exports.lighting_ballast_configuration = exports.curtain_position_analog_output = exports.cover_state_via_onoff = exports.cover_position_via_brightness = exports.cover_position_tilt = exports.identify = exports.command_off_state = exports.command_on_state = exports.command_emergency = exports.command_move_to_hue = exports.command_move_to_saturation = exports.command_move_hue = exports.command_move_to_color = exports.command_move_to_color_temp = exports.command_color_loop_set = exports.command_step_saturation = exports.command_step_hue = exports.command_move_to_hue_and_saturation = exports.command_enhanced_move_to_hue_and_saturation = exports.command_step_color_temperature = exports.command_stop_move_step = exports.command_move_color_temperature = exports.command_stop = exports.command_step = exports.command_move = exports.command_move_to_level = exports.command_toggle = exports.command_off_with_effect = exports.command_off = exports.command_on = exports.command_cover_close = exports.command_cover_open = exports.command_cover_stop = exports.command_arm = exports.command_panic = exports.command_recall = exports.command_store = exports.ias_occupancy_alarm_1_with_timeout = exports.ias_occupancy_only_alarm_2 = exports.ias_alarm_only_alarm_1 = exports.ias_occupancy_alarm_2 = exports.ias_occupancy_alarm_1_report = exports.ias_occupancy_alarm_1 = void 0;
exports.ias_ace_occupancy_with_timeout = exports.command_arm_with_transaction = exports.ignore_electrical_measurement = exports.ignore_metering = exports.ignore_tuya_raw = exports.ignore_tuya_set_time = exports.ignore_haDiagnostic = exports.ignore_genLevelCtrl_report = exports.ignore_command_stop = exports.ignore_command_step = exports.ignore_command_off_with_effect = exports.ignore_command_off = exports.ignore_command_on = exports.ignore_genIdentify = exports.ignore_iasace_commandgetpanelstatus = exports.ignore_iaszone_report = exports.ignore_iaszone_statuschange = exports.ignore_iaszone_attreport = exports.ignore_thermostat_report = exports.ignore_closuresWindowCovering_report = exports.ignore_light_color_colortemp_report = exports.ignore_light_brightness_report = exports.ignore_power_report = exports.ignore_multistate_report = exports.ignore_analog_report = exports.ignore_pressure_report = exports.ignore_humidity_report = exports.ignore_temperature_report = exports.ignore_occupancy_report = exports.ignore_illuminance_report = void 0;
const libColor = __importStar(require("../lib/color"));
const constants = __importStar(require("../lib/constants"));
const exposes = __importStar(require("../lib/exposes"));
const logger_1 = require("../lib/logger");
const globalStore = __importStar(require("../lib/store"));
const utils = __importStar(require("../lib/utils"));
const utils_1 = require("../lib/utils");
const NS = "zhc:fz";
const defaultSimulatedBrightness = 255;
// #region Generic/recommended converters
exports.fan = {
    cluster: "hvacFanCtrl",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        if (msg.data.fanMode !== undefined) {
            const key = (0, utils_1.getKey)(constants.fanMode, msg.data.fanMode);
            return { fan_mode: key, fan_state: key === "off" ? "OFF" : "ON" };
        }
    },
};
exports.fan_speed = {
    cluster: "genLevelCtrl",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        if (msg.data.currentLevel !== undefined) {
            const property = (0, utils_1.postfixWithEndpointName)("speed", msg, model, meta);
            return { [property]: msg.data.currentLevel };
        }
    },
};
exports.thermostat = {
    cluster: "hvacThermostat",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        const result = {};
        const dontMapPIHeatingDemand = model.meta?.thermostat?.dontMapPIHeatingDemand;
        if (msg.data.localTemp !== undefined) {
            const value = (0, utils_1.precisionRound)(msg.data.localTemp, 2) / 100;
            if (value >= -273.15) {
                result[(0, utils_1.postfixWithEndpointName)("local_temperature", msg, model, meta)] = value;
            }
        }
        if (msg.data.localTemperatureCalibration !== undefined) {
            result[(0, utils_1.postfixWithEndpointName)("local_temperature_calibration", msg, model, meta)] =
                (0, utils_1.precisionRound)(msg.data.localTemperatureCalibration, 2) / 10;
        }
        if (msg.data.outdoorTemp !== undefined) {
            const value = (0, utils_1.precisionRound)(msg.data.outdoorTemp, 2) / 100;
            if (value >= -273.15) {
                result[(0, utils_1.postfixWithEndpointName)("outdoor_temperature", msg, model, meta)] = value;
            }
        }
        if (msg.data.occupancy !== undefined) {
            result[(0, utils_1.postfixWithEndpointName)("occupancy", msg, model, meta)] = msg.data.occupancy % 2 > 0;
        }
        if (msg.data.occupiedHeatingSetpoint !== undefined) {
            const value = (0, utils_1.precisionRound)(msg.data.occupiedHeatingSetpoint, 2) / 100;
            // Stelpro will return -325.65 when set to off, value is not realistic anyway
            if (value >= -273.15) {
                result[(0, utils_1.postfixWithEndpointName)("occupied_heating_setpoint", msg, model, meta)] = value;
            }
        }
        if (msg.data.unoccupiedHeatingSetpoint !== undefined) {
            result[(0, utils_1.postfixWithEndpointName)("unoccupied_heating_setpoint", msg, model, meta)] =
                (0, utils_1.precisionRound)(msg.data.unoccupiedHeatingSetpoint, 2) / 100;
        }
        if (msg.data.occupiedCoolingSetpoint !== undefined) {
            result[(0, utils_1.postfixWithEndpointName)("occupied_cooling_setpoint", msg, model, meta)] =
                (0, utils_1.precisionRound)(msg.data.occupiedCoolingSetpoint, 2) / 100;
        }
        if (msg.data.unoccupiedCoolingSetpoint !== undefined) {
            result[(0, utils_1.postfixWithEndpointName)("unoccupied_cooling_setpoint", msg, model, meta)] =
                (0, utils_1.precisionRound)(msg.data.unoccupiedCoolingSetpoint, 2) / 100;
        }
        if (msg.data.setpointChangeAmount !== undefined) {
            result[(0, utils_1.postfixWithEndpointName)("setpoint_change_amount", msg, model, meta)] = msg.data.setpointChangeAmount / 100;
        }
        if (msg.data.setpointChangeSource !== undefined) {
            result[(0, utils_1.postfixWithEndpointName)("setpoint_change_source", msg, model, meta)] = utils.getFromLookup(msg.data.setpointChangeSource, constants.thermostatSetpointChangeSource);
        }
        if (msg.data.setpointChangeSourceTimeStamp !== undefined) {
            const date = new Date(2000, 0, 1);
            date.setSeconds(msg.data.setpointChangeSourceTimeStamp);
            const value = (0, utils_1.toLocalISOString)(date);
            result[(0, utils_1.postfixWithEndpointName)("setpoint_change_source_timestamp", msg, model, meta)] = value;
        }
        if (msg.data.remoteSensing !== undefined) {
            const value = msg.data.remoteSensing;
            result[(0, utils_1.postfixWithEndpointName)("remote_sensing", msg, model, meta)] = {
                local_temperature: (value & 1) > 0 ? "remotely" : "internally",
                outdoor_temperature: (value & (1 << 1)) > 0 ? "remotely" : "internally",
                occupancy: (value & (1 << 2)) > 0 ? "remotely" : "internally",
            };
        }
        if (msg.data.ctrlSeqeOfOper !== undefined) {
            result[(0, utils_1.postfixWithEndpointName)("control_sequence_of_operation", msg, model, meta)] = utils.getFromLookup(msg.data.ctrlSeqeOfOper, constants.thermostatControlSequenceOfOperations);
        }
        if (msg.data.programingOperMode !== undefined) {
            result[(0, utils_1.postfixWithEndpointName)("programming_operation_mode", msg, model, meta)] = utils.getFromLookup(msg.data.programingOperMode, constants.thermostatProgrammingOperationModes);
        }
        if (msg.data.systemMode !== undefined) {
            result[(0, utils_1.postfixWithEndpointName)("system_mode", msg, model, meta)] = utils.getFromLookup(msg.data.systemMode, constants.thermostatSystemModes);
        }
        if (msg.data.runningMode !== undefined) {
            result[(0, utils_1.postfixWithEndpointName)("running_mode", msg, model, meta)] = utils.getFromLookup(msg.data.runningMode, constants.thermostatRunningMode);
        }
        if (msg.data.runningState !== undefined) {
            result[(0, utils_1.postfixWithEndpointName)("running_state", msg, model, meta)] = utils.getFromLookup(msg.data.runningState, constants.thermostatRunningStates);
        }
        if (msg.data.pIHeatingDemand !== undefined) {
            result[(0, utils_1.postfixWithEndpointName)("pi_heating_demand", msg, model, meta)] = (0, utils_1.mapNumberRange)(msg.data.pIHeatingDemand, 0, dontMapPIHeatingDemand ? 100 : 255, 0, 100);
        }
        if (msg.data.pICoolingDemand !== undefined) {
            // we assume the behavior is consistent for pIHeatingDemand + pICoolingDemand for the same vendor
            result[(0, utils_1.postfixWithEndpointName)("pi_cooling_demand", msg, model, meta)] = (0, utils_1.mapNumberRange)(msg.data.pICoolingDemand, 0, dontMapPIHeatingDemand ? 100 : 255, 0, 100);
        }
        if (msg.data.tempSetpointHold !== undefined) {
            result[(0, utils_1.postfixWithEndpointName)("temperature_setpoint_hold", msg, model, meta)] = msg.data.tempSetpointHold === 1;
        }
        if (msg.data.tempSetpointHoldDuration !== undefined) {
            result[(0, utils_1.postfixWithEndpointName)("temperature_setpoint_hold_duration", msg, model, meta)] = msg.data.tempSetpointHoldDuration;
        }
        if (msg.data.minHeatSetpointLimit !== undefined) {
            const value = (0, utils_1.precisionRound)(msg.data.minHeatSetpointLimit, 2) / 100;
            if (value >= -273.15) {
                result[(0, utils_1.postfixWithEndpointName)("min_heat_setpoint_limit", msg, model, meta)] = value;
            }
        }
        if (msg.data.maxHeatSetpointLimit !== undefined) {
            const value = (0, utils_1.precisionRound)(msg.data.maxHeatSetpointLimit, 2) / 100;
            if (value >= -273.15) {
                result[(0, utils_1.postfixWithEndpointName)("max_heat_setpoint_limit", msg, model, meta)] = value;
            }
        }
        if (msg.data.absMinHeatSetpointLimit !== undefined) {
            const value = (0, utils_1.precisionRound)(msg.data.absMinHeatSetpointLimit, 2) / 100;
            if (value >= -273.15) {
                result[(0, utils_1.postfixWithEndpointName)("abs_min_heat_setpoint_limit", msg, model, meta)] = value;
            }
        }
        if (msg.data.absMaxHeatSetpointLimit !== undefined) {
            const value = (0, utils_1.precisionRound)(msg.data.absMaxHeatSetpointLimit, 2) / 100;
            if (value >= -273.15) {
                result[(0, utils_1.postfixWithEndpointName)("abs_max_heat_setpoint_limit", msg, model, meta)] = value;
            }
        }
        if (msg.data.absMinCoolSetpointLimit !== undefined) {
            const value = (0, utils_1.precisionRound)(msg.data.absMinCoolSetpointLimit, 2) / 100;
            if (value >= -273.15) {
                result[(0, utils_1.postfixWithEndpointName)("abs_min_cool_setpoint_limit", msg, model, meta)] = value;
            }
        }
        if (msg.data.absMaxCoolSetpointLimit !== undefined) {
            const value = (0, utils_1.precisionRound)(msg.data.absMaxCoolSetpointLimit, 2) / 100;
            if (value >= -273.15) {
                result[(0, utils_1.postfixWithEndpointName)("abs_max_cool_setpoint_limit", msg, model, meta)] = value;
            }
        }
        if (msg.data.minSetpointDeadBand !== undefined) {
            const value = (0, utils_1.precisionRound)(msg.data.minSetpointDeadBand, 2) / 100;
            if (value >= -273.15) {
                result[(0, utils_1.postfixWithEndpointName)("min_setpoint_dead_band", msg, model, meta)] = value;
            }
        }
        if (msg.data.acLouverPosition !== undefined) {
            result[(0, utils_1.postfixWithEndpointName)("ac_louver_position", msg, model, meta)] = utils.getFromLookup(msg.data.acLouverPosition, constants.thermostatAcLouverPositions);
        }
        return result;
    },
};
exports.thermostat_weekly_schedule = {
    cluster: "hvacThermostat",
    type: ["commandGetWeeklyScheduleRsp"],
    convert: (model, msg, publish, options, meta) => {
        const days = [];
        for (let i = 0; i < 8; i++) {
            if ((msg.data.dayofweek & (1 << i)) > 0) {
                days.push(utils.getFromLookup(i, constants.thermostatDayOfWeek));
            }
        }
        const transitions = [];
        for (const transition of msg.data.transitions) {
            const entry = { time: transition.transitionTime };
            if (transition.heatSetpoint !== undefined) {
                entry.heating_setpoint = transition.heatSetpoint / 100;
            }
            if (transition.coolSetpoint !== undefined) {
                entry.cooling_setpoint = transition.coolSetpoint / 100;
            }
            transitions.push(entry);
        }
        return { [(0, utils_1.postfixWithEndpointName)("weekly_schedule", msg, model, meta)]: { days, transitions } };
    },
};
exports.hvac_user_interface = {
    cluster: "hvacUserInterfaceCfg",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        const result = {};
        if (msg.data.keypadLockout !== undefined) {
            result.keypad_lockout =
                constants.keypadLockoutMode[msg.data.keypadLockout] !== undefined
                    ? constants.keypadLockoutMode[msg.data.keypadLockout]
                    : msg.data.keypadLockout;
        }
        if (msg.data.tempDisplayMode !== undefined) {
            result.temperature_display_mode =
                constants.temperatureDisplayMode[msg.data.tempDisplayMode] !== undefined
                    ? constants.temperatureDisplayMode[msg.data.tempDisplayMode]
                    : msg.data.tempDisplayMode;
        }
        return result;
    },
};
exports.lock_operation_event = {
    cluster: "closuresDoorLock",
    type: "commandOperationEventNotification",
    convert: (model, msg, publish, options, meta) => {
        const lookup = {
            0: "unknown",
            1: "lock",
            2: "unlock",
            3: "lock_failure_invalid_pin_or_id",
            4: "lock_failure_invalid_schedule",
            5: "unlock_failure_invalid_pin_or_id",
            6: "unlock_failure_invalid_schedule",
            7: "one_touch_lock",
            8: "key_lock",
            9: "key_unlock",
            10: "auto_lock",
            11: "schedule_lock",
            12: "schedule_unlock",
            13: "manual_lock",
            14: "manual_unlock",
            15: "non_access_user_operational_event",
        };
        return {
            action: lookup[msg.data.opereventcode],
            action_user: msg.data.userid,
            action_source: msg.data.opereventsrc,
            action_source_name: constants.lockSourceName[msg.data.opereventsrc],
        };
    },
};
exports.lock_programming_event = {
    cluster: "closuresDoorLock",
    type: "commandProgrammingEventNotification",
    convert: (model, msg, publish, options, meta) => {
        const lookup = {
            0: "unknown",
            1: "master_code_changed",
            2: "pin_code_added",
            3: "pin_code_deleted",
            4: "pin_code_changed",
            5: "rfid_code_added",
            6: "rfid_code_deleted",
        };
        return {
            action: lookup[msg.data.programeventcode],
            action_user: msg.data.userid,
            action_source: msg.data.programeventsrc,
            action_source_name: constants.lockSourceName[msg.data.programeventsrc],
        };
    },
};
exports.lock_programming_event_read_pincode = {
    cluster: "closuresDoorLock",
    type: "commandProgrammingEventNotification",
    convert: (model, msg, publish, options, meta) => {
        if (msg.data.userid !== undefined &&
            (msg.data.programeventsrc === undefined || constants.lockSourceName[msg.data.programeventsrc] !== "rf")) {
            msg.endpoint
                .command("closuresDoorLock", "getPinCode", { userid: msg.data.userid }, {})
                .catch((error) => logger_1.logger.error(`Failed to read pincode of '${msg.device.ieeeAddr}' (${error})`, NS));
        }
    },
};
exports.lock = {
    cluster: "closuresDoorLock",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        const result = {};
        if (msg.data.lockState !== undefined) {
            result[(0, utils_1.postfixWithEndpointName)("state", msg, model, meta)] = msg.data.lockState === 1 ? "LOCK" : "UNLOCK";
            const lookup = ["not_fully_locked", "locked", "unlocked"];
            result[(0, utils_1.postfixWithEndpointName)("lock_state", msg, model, meta)] = lookup[msg.data.lockState];
        }
        if (msg.data.autoRelockTime !== undefined) {
            result[(0, utils_1.postfixWithEndpointName)("auto_relock_time", msg, model, meta)] = msg.data.autoRelockTime;
        }
        if (msg.data.soundVolume !== undefined) {
            result[(0, utils_1.postfixWithEndpointName)("sound_volume", msg, model, meta)] = constants.lockSoundVolume[msg.data.soundVolume];
        }
        if (msg.data.doorState !== undefined) {
            const lookup = {
                0: "open",
                1: "closed",
                2: "error_jammed",
                3: "error_forced_open",
                4: "error_unspecified",
                255: "undefined",
            };
            result[(0, utils_1.postfixWithEndpointName)("door_state", msg, model, meta)] = lookup[msg.data.doorState];
        }
        return result;
    },
};
exports.lock_set_pin_code_response = {
    cluster: "closuresDoorLock",
    type: ["commandSetPinCodeRsp", "commandClearPinCodeRsp"],
    convert: (model, msg, publish, options, meta) => {
        const result = {};
        if (msg.data.status === 0) {
            if (msg.type === "commandSetPinCodeRsp") {
                result.last_successful_pincode_save = Date.now();
            }
            if (msg.type === "commandClearPinCodeRsp") {
                result.last_successful_pincode_clear = Date.now();
            }
        }
        if (Object.keys(result).length > 0) {
            return result;
        }
    },
};
exports.lock_pin_code_response = {
    cluster: "closuresDoorLock",
    type: ["commandGetPinCodeRsp"],
    options: [exposes.options.expose_pin()],
    convert: (model, msg, publish, options, meta) => {
        const { data } = msg;
        let status = constants.lockUserStatus[data.userstatus];
        if (status === undefined) {
            status = `not_supported_${data.userstatus}`;
        }
        const userId = data.userid.toString();
        const result = { users: {} };
        result.users[userId] = { status: status };
        if (options?.expose_pin && data.pincodevalue) {
            result.users[userId].pin_code = data.pincodevalue.toString();
        }
        return result;
    },
};
exports.lock_user_status_response = {
    cluster: "closuresDoorLock",
    type: ["commandGetUserStatusRsp"],
    options: [exposes.options.expose_pin()],
    convert: (model, msg, publish, options, meta) => {
        const { data } = msg;
        let status = constants.lockUserStatus[data.userstatus];
        if (status === undefined) {
            status = `not_supported_${data.userstatus}`;
        }
        const userId = data.userid.toString();
        const result = { users: {} };
        result.users[userId] = { status: status };
        return result;
    },
};
exports.linkquality_from_basic = {
    cluster: "genBasic",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        return { linkquality: msg.linkquality };
    },
};
exports.battery = {
    cluster: "genPowerCfg",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        const payload = {};
        // If voltageToPercentage is specified, it means we do not trust the percentage
        // returned by the device and are instead calculating it ourselves.
        if (model.meta?.battery?.voltageToPercentage == null &&
            msg.data.batteryPercentageRemaining !== undefined &&
            msg.data.batteryPercentageRemaining < 255) {
            // Some devices do not comply to the ZCL and report a
            // batteryPercentageRemaining of 100 when the battery is full (should be 200).
            const dontDividePercentage = model.meta?.battery?.dontDividePercentage;
            let percentage = msg.data.batteryPercentageRemaining;
            percentage = dontDividePercentage ? percentage : percentage / 2;
            payload.battery = (0, utils_1.precisionRound)(percentage, 2);
        }
        if (msg.data.batteryVoltage !== undefined && msg.data.batteryVoltage < 255) {
            // Deprecated: voltage is = mV now but should be V
            payload.voltage = msg.data.batteryVoltage * 100;
            if (model.meta?.battery?.voltageToPercentage) {
                payload.battery = (0, utils_1.batteryVoltageToPercentage)(payload.voltage, model.meta.battery.voltageToPercentage);
            }
        }
        if (msg.data.batteryAlarmState !== undefined) {
            const battery1Low = (msg.data.batteryAlarmState & (1 << 0) ||
                msg.data.batteryAlarmState & (1 << 1) ||
                msg.data.batteryAlarmState & (1 << 2) ||
                msg.data.batteryAlarmState & (1 << 3)) > 0;
            const battery2Low = (msg.data.batteryAlarmState & (1 << 10) ||
                msg.data.batteryAlarmState & (1 << 11) ||
                msg.data.batteryAlarmState & (1 << 12) ||
                msg.data.batteryAlarmState & (1 << 13)) > 0;
            const battery3Low = (msg.data.batteryAlarmState & (1 << 20) ||
                msg.data.batteryAlarmState & (1 << 21) ||
                msg.data.batteryAlarmState & (1 << 22) ||
                msg.data.batteryAlarmState & (1 << 23)) > 0;
            payload.battery_low = battery1Low || battery2Low || battery3Low;
        }
        return payload;
    },
};
exports.temperature = {
    cluster: "msTemperatureMeasurement",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        if (msg.data.measuredValue !== undefined) {
            const temperature = msg.data.measuredValue / 100.0;
            const property = (0, utils_1.postfixWithEndpointName)("temperature", msg, model, meta);
            return { [property]: temperature };
        }
    },
};
exports.device_temperature = {
    cluster: "genDeviceTempCfg",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        if (msg.data.currentTemperature !== undefined) {
            const value = msg.data.currentTemperature;
            return { device_temperature: value };
        }
    },
};
exports.humidity = {
    cluster: "msRelativeHumidity",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        const humidity = msg.data.measuredValue / 100.0;
        const property = (0, utils_1.postfixWithEndpointName)("humidity", msg, model, meta);
        // https://github.com/Koenkk/zigbee2mqtt/issues/798
        // Sometimes the sensor publishes non-realistic vales, it should only publish message
        // in the 0 - 100 range, don't produce messages beyond these values.
        if (humidity >= 0 && humidity <= 100) {
            return { [property]: humidity };
        }
    },
};
exports.pm25 = {
    cluster: "pm25Measurement",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        if (msg.data.measuredValue !== undefined) {
            return { pm25: msg.data.measuredValue };
        }
    },
};
exports.flow = {
    cluster: "msFlowMeasurement",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        const flow = msg.data.measuredValue / 10.0;
        const property = (0, utils_1.postfixWithEndpointName)("flow", msg, model, meta);
        if (msg.data.measuredValue !== undefined) {
            return { [property]: flow };
        }
    },
};
exports.soil_moisture = {
    cluster: "msSoilMoisture",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        const soilMoisture = msg.data.measuredValue / 100.0;
        return { soil_moisture: soilMoisture };
    },
};
exports.pressure = {
    cluster: "msPressureMeasurement",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        let pressure = 0;
        if (msg.data.scaledValue !== undefined) {
            const scale = msg.endpoint.getClusterAttributeValue("msPressureMeasurement", "scale");
            pressure = msg.data.scaledValue / 10 ** scale / 100.0; // convert to hPa
        }
        else {
            pressure = msg.data.measuredValue;
        }
        return { pressure };
    },
};
exports.co2 = {
    cluster: "msCO2",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        return { co2: Math.floor(msg.data.measuredValue * 1000000) };
    },
};
exports.occupancy = {
    // This is for occupancy sensor that send motion start AND stop messages
    cluster: "msOccupancySensing",
    type: ["attributeReport", "readResponse"],
    options: [exposes.options.no_occupancy_since_false()],
    convert: (model, msg, publish, options, meta) => {
        if (msg.data.occupancy !== undefined) {
            const payload = { occupancy: msg.data.occupancy % 2 > 0 };
            utils.noOccupancySince(msg.endpoint, options, publish, payload.occupancy ? "stop" : "start");
            return payload;
        }
    },
};
exports.occupancy_with_timeout = {
    // This is for occupancy sensor that only send a message when motion detected,
    // but do not send a motion stop.
    // Therefore we need to publish the no_motion detected by ourselves.
    cluster: "msOccupancySensing",
    type: ["attributeReport", "readResponse"],
    options: [exposes.options.occupancy_timeout(), exposes.options.no_occupancy_since_true()],
    convert: (model, msg, publish, options, meta) => {
        if (msg.data.occupancy !== 1) {
            // In case of 0 no occupancy is reported.
            // https://github.com/Koenkk/zigbee2mqtt/issues/467
            return;
        }
        // The occupancy sensor only sends a message when motion detected.
        // Therefore we need to publish the no_motion detected by ourselves.
        const timeout = options?.occupancy_timeout != null ? Number(options.occupancy_timeout) : 90;
        // Stop existing timers because motion is detected and set a new one.
        clearTimeout(globalStore.getValue(msg.endpoint, "occupancy_timer", null));
        if (timeout !== 0) {
            const timer = setTimeout(() => {
                publish({ occupancy: false });
            }, timeout * 1000).unref();
            globalStore.putValue(msg.endpoint, "occupancy_timer", timer);
        }
        const payload = { occupancy: true };
        utils.noOccupancySince(msg.endpoint, options, publish, "start");
        return payload;
    },
};
exports.occupancy_timeout = {
    cluster: "msOccupancySensing",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        if (msg.data.pirOToUDelay !== undefined) {
            return { occupancy_timeout: msg.data.pirOToUDelay };
        }
    },
};
exports.brightness = {
    cluster: "genLevelCtrl",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        if (msg.data.currentLevel !== undefined) {
            const property = (0, utils_1.postfixWithEndpointName)("brightness", msg, model, meta);
            return { [property]: msg.data.currentLevel };
        }
    },
};
exports.level_config = {
    cluster: "genLevelCtrl",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        const level_config = (0, utils_1.postfixWithEndpointName)("level_config", msg, model, meta);
        const result = {};
        result[level_config] = {};
        // onOffTransitionTime - range 0x0000 to 0xffff - optional
        if (msg.data.onOffTransitionTime !== undefined && msg.data.onOffTransitionTime !== undefined) {
            result[level_config].on_off_transition_time = Number(msg.data.onOffTransitionTime) / 10;
        }
        // onTransitionTime - range 0x0000 to 0xffff - optional
        //                    0xffff = use onOffTransitionTime
        if (msg.data.onTransitionTime !== undefined && msg.data.onTransitionTime !== undefined) {
            if (Number(msg.data.onTransitionTime) === 65535) {
                result[level_config].on_transition_time = "disabled";
            }
            else
                result[level_config].on_transition_time = Number(msg.data.onTransitionTime) / 10;
        }
        // offTransitionTime - range 0x0000 to 0xffff - optional
        //                    0xffff = use onOffTransitionTime
        if (msg.data.offTransitionTime !== undefined && msg.data.offTransitionTime !== undefined) {
            if (Number(msg.data.offTransitionTime) === 65535) {
                result[level_config].off_transition_time = "disabled";
            }
            else
                result[level_config].off_transition_time = Number(msg.data.offTransitionTime) / 10;
        }
        // startUpCurrentLevel - range 0x00 to 0xff - optional
        //                       0x00 = return to minimum supported level
        //                       0xff - return to previous previous
        if (msg.data.startUpCurrentLevel !== undefined && msg.data.startUpCurrentLevel !== undefined) {
            result[level_config].current_level_startup = Number(msg.data.startUpCurrentLevel);
            if (result[level_config].current_level_startup === 255) {
                result[level_config].current_level_startup = "previous";
            }
            if (result[level_config].current_level_startup === 0) {
                result[level_config].current_level_startup = "minimum";
            }
        }
        // onLevel - range 0x00 to 0xff - optional
        //           Any value outside of MinLevel to MaxLevel, including 0xff and 0x00, is interpreted as "previous".
        if (msg.data.onLevel !== undefined && msg.data.onLevel !== undefined) {
            result[level_config].on_level = Number(msg.data.onLevel);
            if (result[level_config].on_level === 255) {
                result[level_config].on_level = "previous";
            }
        }
        // options - 8-bit map
        //   bit 0: ExecuteIfOff - when 0, Move commands are ignored if the device is off;
        //          when 1, CurrentLevel can be changed while the device is off.
        //   bit 1: CoupleColorTempToLevel - when 1, changes to level also change color temperature.
        //          (What this means is not defined, but it's most likely to be "dim to warm".)
        if (msg.data.options !== undefined) {
            result[level_config].execute_if_off = !!(Number(msg.data.options) & 1);
        }
        if (Object.keys(result[level_config]).length > 0) {
            return result;
        }
    },
};
exports.color_colortemp = {
    cluster: "lightingColorCtrl",
    type: ["attributeReport", "readResponse"],
    options: [exposes.options.color_sync()],
    convert: (model, msg, publish, options, meta) => {
        const result = {};
        if (msg.data.colorTemperature !== undefined) {
            const color_temp = (0, utils_1.postfixWithEndpointName)("color_temp", msg, model, meta);
            result[color_temp] = msg.data.colorTemperature;
        }
        if (msg.data.startUpColorTemperature !== undefined) {
            const color_temp_startup = (0, utils_1.postfixWithEndpointName)("color_temp_startup", msg, model, meta);
            result[color_temp_startup] = msg.data.startUpColorTemperature;
        }
        if (msg.data.colorMode !== undefined) {
            const color_mode = (0, utils_1.postfixWithEndpointName)("color_mode", msg, model, meta);
            result[color_mode] =
                constants.colorModeLookup[msg.data.colorMode] !== undefined ? constants.colorModeLookup[msg.data.colorMode] : msg.data.colorMode;
        }
        if (msg.data.currentX !== undefined ||
            msg.data.currentY !== undefined ||
            msg.data.currentSaturation !== undefined ||
            msg.data.currentHue !== undefined ||
            msg.data.enhancedCurrentHue !== undefined) {
            const color = (0, utils_1.postfixWithEndpointName)("color", msg, model, meta);
            result[color] = {};
            if (msg.data.currentX !== undefined) {
                result[color].x = (0, utils_1.mapNumberRange)(msg.data.currentX, 0, 65535, 0, 1, 4);
            }
            if (msg.data.currentY !== undefined) {
                result[color].y = (0, utils_1.mapNumberRange)(msg.data.currentY, 0, 65535, 0, 1, 4);
            }
            if (msg.data.currentSaturation !== undefined) {
                result[color].saturation = (0, utils_1.mapNumberRange)(msg.data.currentSaturation, 0, 254, 0, 100);
            }
            if (msg.data.currentHue !== undefined) {
                result[color].hue = (0, utils_1.mapNumberRange)(msg.data.currentHue, 0, 254, 0, 360, 0);
            }
            if (msg.data.enhancedCurrentHue !== undefined) {
                result[color].hue = (0, utils_1.mapNumberRange)(msg.data.enhancedCurrentHue, 0, 65535, 0, 360, 1);
            }
        }
        if (msg.data.options !== undefined) {
            /*
             * Bit | Value & Summary
             * --------------------------
             * 0   | 0: Do not execute command if the On/Off cluster, OnOff attribute is 0x00 (FALSE)
             *     | 1: Execute command if the On/Off cluster, OnOff attribute is 0x00 (FALSE)
             */
            const color_options = (0, utils_1.postfixWithEndpointName)("color_options", msg, model, meta);
            result[color_options] = { execute_if_off: (msg.data.options & (1 << 0)) > 0 };
        }
        // Use postfixWithEndpointName with an empty value to get just the postfix that
        // needs to be added to the result key.
        const epPostfix = (0, utils_1.postfixWithEndpointName)("", msg, model, meta);
        // handle color property sync
        // NOTE: this should the last thing we do, as we need to have processed all attributes,
        //       we use assign here so we do not lose other attributes.
        return Object.assign(result, libColor.syncColorState(result, meta.state, msg.endpoint, options, epPostfix));
    },
};
exports.meter_identification = {
    cluster: "seMeterIdentification",
    type: ["readResponse"],
    convert: (model, msg, publish, options, meta) => {
        const result = {};
        const elements = [/* 0x000A*/ "softwareRevision", /* 0x000D*/ "availablePower", /* 0x000E*/ "powerThreshold"];
        for (const at of elements) {
            const atSnake = at
                .split(/(?=[A-Z])/)
                .join("_")
                .toLowerCase();
            if (msg.data[at]) {
                result[atSnake] = msg.data[at];
            }
        }
        return result;
    },
};
exports.metering = {
    /**
     * When using this converter also add the following to the configure method of the device:
     * await readMeteringPowerConverterAttributes(endpoint);
     */
    cluster: "seMetering",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        if (utils.hasAlreadyProcessedMessage(msg, model))
            return;
        const payload = {};
        const multiplier = msg.endpoint.getClusterAttributeValue("seMetering", "multiplier");
        const divisor = msg.endpoint.getClusterAttributeValue("seMetering", "divisor");
        const factor = multiplier && divisor ? multiplier / divisor : null;
        if (msg.data.instantaneousDemand !== undefined) {
            let power = msg.data.instantaneousDemand;
            if (factor != null) {
                power = power * factor * 1000; // kWh to Watt
            }
            const property = (0, utils_1.postfixWithEndpointName)("power", msg, model, meta);
            payload[property] = power;
        }
        if (msg.data.currentSummDelivered !== undefined) {
            const value = msg.data.currentSummDelivered;
            const property = (0, utils_1.postfixWithEndpointName)("energy", msg, model, meta);
            payload[property] = value * (factor ?? 1);
        }
        if (msg.data.currentSummReceived !== undefined) {
            const value = msg.data.currentSummReceived;
            const property = (0, utils_1.postfixWithEndpointName)("produced_energy", msg, model, meta);
            payload[property] = value * (factor ?? 1);
        }
        // Support for tariff-based energy measurements (e.g., P1/OBIS smart meters)
        if (msg.data.currentTier1SummDelivered !== undefined) {
            const value = msg.data.currentTier1SummDelivered;
            const property = (0, utils_1.postfixWithEndpointName)("energy_tier_1", msg, model, meta);
            payload[property] = value * (factor ?? 1);
        }
        if (msg.data.currentTier2SummDelivered !== undefined) {
            const value = msg.data.currentTier2SummDelivered;
            const property = (0, utils_1.postfixWithEndpointName)("energy_tier_2", msg, model, meta);
            payload[property] = value * (factor ?? 1);
        }
        if (msg.data.currentTier3SummDelivered !== undefined) {
            const value = msg.data.currentTier3SummDelivered;
            const property = (0, utils_1.postfixWithEndpointName)("energy_tier_3", msg, model, meta);
            payload[property] = value * (factor ?? 1);
        }
        if (msg.data.currentTier4SummDelivered !== undefined) {
            const value = msg.data.currentTier4SummDelivered;
            const property = (0, utils_1.postfixWithEndpointName)("energy_tier_4", msg, model, meta);
            payload[property] = value * (factor ?? 1);
        }
        if (msg.data.currentTier1SummReceived !== undefined) {
            const value = msg.data.currentTier1SummReceived;
            const property = (0, utils_1.postfixWithEndpointName)("produced_energy_tier_1", msg, model, meta);
            payload[property] = value * (factor ?? 1);
        }
        if (msg.data.currentTier2SummReceived !== undefined) {
            const value = msg.data.currentTier2SummReceived;
            const property = (0, utils_1.postfixWithEndpointName)("produced_energy_tier_2", msg, model, meta);
            payload[property] = value * (factor ?? 1);
        }
        return payload;
    },
};
exports.electrical_measurement = {
    /**
     * When using this converter also add the following to the configure method of the device:
     * await readEletricalMeasurementConverterAttributes(endpoint);
     */
    cluster: "haElectricalMeasurement",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        if (utils.hasAlreadyProcessedMessage(msg, model))
            return;
        const getFactor = (key) => {
            const multiplier = msg.endpoint.getClusterAttributeValue("haElectricalMeasurement", `${key}Multiplier`);
            const divisor = msg.endpoint.getClusterAttributeValue("haElectricalMeasurement", `${key}Divisor`);
            const factor = multiplier && divisor ? multiplier / divisor : 1;
            return factor;
        };
        const lookup = [
            { key: "activePower", name: "power", factor: "acPower" },
            { key: "activePowerPhB", name: "power_phase_b", factor: "acPower" },
            { key: "activePowerPhC", name: "power_phase_c", factor: "acPower" },
            { key: "apparentPower", name: "power_apparent", factor: "acPower" },
            { key: "apparentPowerPhB", name: "power_apparent_phase_b", factor: "acPower" },
            { key: "apparentPowerPhC", name: "power_apparent_phase_c", factor: "acPower" },
            { key: "reactivePower", name: "power_reactive", factor: "acPower" },
            { key: "reactivePowerPhB", name: "power_reactive_phase_b", factor: "acPower" },
            { key: "reactivePowerPhC", name: "power_reactive_phase_c", factor: "acPower" },
            { key: "rmsCurrent", name: "current", factor: "acCurrent" },
            { key: "rmsCurrentPhB", name: "current_phase_b", factor: "acCurrent" },
            { key: "rmsCurrentPhC", name: "current_phase_c", factor: "acCurrent" },
            { key: "neutralCurrent", name: "current_neutral", factor: "acCurrent" },
            { key: "rmsVoltage", name: "voltage", factor: "acVoltage" },
            { key: "rmsVoltagePhB", name: "voltage_phase_b", factor: "acVoltage" },
            { key: "rmsVoltagePhC", name: "voltage_phase_c", factor: "acVoltage" },
            { key: "acFrequency", name: "ac_frequency", factor: "acFrequency" },
            { key: "dcPower", name: "power", factor: "dcPower" },
            { key: "dcCurrent", name: "current", factor: "dcCurrent" },
            { key: "dcVoltage", name: "voltage", factor: "dcVoltage" },
        ];
        const payload = {};
        for (const entry of lookup) {
            if (msg.data[entry.key] !== undefined) {
                const factor = getFactor(entry.factor);
                const property = (0, utils_1.postfixWithEndpointName)(entry.name, msg, model, meta);
                const value = msg.data[entry.key] * factor;
                payload[property] = value;
            }
        }
        if (msg.data.powerFactor !== undefined) {
            const property = (0, utils_1.postfixWithEndpointName)("power_factor", msg, model, meta);
            payload[property] = (0, utils_1.precisionRound)(msg.data.powerFactor / 100, 2);
        }
        if (msg.data.powerFactorPhB !== undefined) {
            const property = (0, utils_1.postfixWithEndpointName)("power_factor_phase_b", msg, model, meta);
            payload[property] = (0, utils_1.precisionRound)(msg.data.powerFactorPhB / 100, 2);
        }
        if (msg.data.powerFactorPhC !== undefined) {
            const property = (0, utils_1.postfixWithEndpointName)("power_factor_phase_c", msg, model, meta);
            payload[property] = (0, utils_1.precisionRound)(msg.data.powerFactorPhC / 100, 2);
        }
        return payload;
    },
};
exports.gas_metering = {
    cluster: "seMetering",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        if (utils.hasAlreadyProcessedMessage(msg, model))
            return;
        const payload = {};
        const multiplier = msg.endpoint.getClusterAttributeValue("seMetering", "multiplier");
        const divisor = msg.endpoint.getClusterAttributeValue("seMetering", "divisor");
        const factor = multiplier && divisor ? multiplier / divisor : null;
        if (msg.data.instantaneousDemand !== undefined) {
            const power = msg.data.instantaneousDemand;
            const property = utils.postfixWithEndpointName("volume_flow_rate", msg, model, meta);
            payload[property] = utils.precisionRound(power * (factor ?? 1), 2);
        }
        if (msg.data.currentSummDelivered !== undefined) {
            const value = msg.data.currentSummDelivered;
            const property = utils.postfixWithEndpointName("gas", msg, model, meta);
            payload[property] = utils.precisionRound(value * (factor ?? 1), 2);
        }
        if (msg.data.status !== undefined) {
            const value = msg.data.status;
            const property = utils.postfixWithEndpointName("status", msg, model, meta);
            payload[property] = value;
        }
        if (msg.data.extendedStatus !== undefined) {
            const value = msg.data.extendedStatus;
            const property = utils.postfixWithEndpointName("extended_status", msg, model, meta);
            payload[property] = value;
        }
        return payload;
    },
};
exports.on_off = {
    cluster: "genOnOff",
    type: ["attributeReport", "readResponse"],
    options: [exposes.options.state_action()],
    convert: (model, msg, publish, options, meta) => {
        if (msg.data.onOff !== undefined) {
            const payload = {};
            const property = (0, utils_1.postfixWithEndpointName)("state", msg, model, meta);
            const state = msg.data.onOff === 1 ? "ON" : "OFF";
            payload[property] = state;
            if (options?.state_action) {
                payload.action = (0, utils_1.postfixWithEndpointName)(state.toLowerCase(), msg, model, meta);
            }
            return payload;
        }
    },
};
exports.on_off_force_multiendpoint = {
    cluster: "genOnOff",
    type: ["attributeReport", "readResponse"],
    options: [exposes.options.state_action()],
    convert: (model, msg, publish, options, meta) => {
        // This converted is need instead of `fz.on_off` when no meta: {multiEndpoint: true} can be defined for this device
        // but it is needed for the `state`. E.g. when a switch has 3 channels (state_l1, state_l2, state_l3) but
        // has combined power measurements (power, energy))
        if (msg.data.onOff !== undefined) {
            const payload = {};
            const endpointName = model.endpoint !== undefined ? utils.getKey(model.endpoint(meta.device), msg.endpoint.ID) : msg.endpoint.ID;
            const state = msg.data.onOff === 1 ? "ON" : "OFF";
            payload[`state_${endpointName}`] = state;
            if (options?.state_action) {
                payload.action = `${state.toLowerCase()}_${endpointName}`;
            }
            return payload;
        }
    },
};
exports.on_off_skip_duplicate_transaction = {
    cluster: "genOnOff",
    type: ["attributeReport", "readResponse"],
    options: [exposes.options.state_action()],
    convert: (model, msg, publish, options, meta) => {
        // Device sends multiple messages with the same transactionSequenceNumber,
        // prevent that multiple messages get send.
        // https://github.com/Koenkk/zigbee2mqtt/issues/3687
        if (msg.data.onOff !== undefined && !(0, utils_1.hasAlreadyProcessedMessage)(msg, model)) {
            const payload = {};
            const property = (0, utils_1.postfixWithEndpointName)("state", msg, model, meta);
            const state = msg.data.onOff === 1 ? "ON" : "OFF";
            payload[property] = state;
            if (options?.state_action) {
                payload.action = (0, utils_1.postfixWithEndpointName)(state.toLowerCase(), msg, model, meta);
            }
            return payload;
        }
    },
};
exports.power_on_behavior = {
    cluster: "genOnOff",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        const lookup = { 0: "off", 1: "on", 2: "toggle", 255: "previous" };
        if (msg.data.startUpOnOff !== undefined) {
            const property = (0, utils_1.postfixWithEndpointName)("power_on_behavior", msg, model, meta);
            return { [property]: lookup[msg.data.startUpOnOff] };
        }
    },
};
exports.ias_no_alarm = {
    cluster: "ssIasZone",
    type: ["attributeReport", "commandStatusChangeNotification"],
    convert: (model, msg, publish, options, meta) => {
        const zoneStatus = "zonestatus" in msg.data ? msg.data.zonestatus : msg.data.zoneStatus;
        if (zoneStatus !== undefined) {
            return {
                tamper: (zoneStatus & (1 << 2)) > 0,
                battery_low: (zoneStatus & (1 << 3)) > 0,
            };
        }
    },
};
exports.ias_siren = {
    cluster: "ssIasZone",
    type: "commandStatusChangeNotification",
    convert: (model, msg, publish, options, meta) => {
        const zoneStatus = msg.data.zonestatus;
        return {
            alarm: (zoneStatus & 1) > 0,
            tamper: (zoneStatus & (1 << 2)) > 0,
            battery_low: (zoneStatus & (1 << 3)) > 0,
            supervision_reports: (zoneStatus & (1 << 4)) > 0,
            restore_reports: (zoneStatus & (1 << 5)) > 0,
            ac_status: (zoneStatus & (1 << 7)) > 0,
            test: (zoneStatus & (1 << 8)) > 0,
        };
    },
};
exports.ias_water_leak_alarm_1 = {
    cluster: "ssIasZone",
    type: "commandStatusChangeNotification",
    convert: (model, msg, publish, options, meta) => {
        const zoneStatus = msg.data.zonestatus;
        return {
            water_leak: (zoneStatus & 1) > 0,
            tamper: (zoneStatus & (1 << 2)) > 0,
            battery_low: (zoneStatus & (1 << 3)) > 0,
        };
    },
};
exports.ias_water_leak_alarm_1_report = {
    cluster: "ssIasZone",
    type: "attributeReport",
    convert: (model, msg, publish, options, meta) => {
        const zoneStatus = msg.data.zoneStatus;
        if (zoneStatus !== undefined) {
            return {
                water_leak: (zoneStatus & 1) > 0,
                tamper: (zoneStatus & (1 << 2)) > 0,
                battery_low: (zoneStatus & (1 << 3)) > 0,
            };
        }
    },
};
exports.ias_vibration_alarm_1 = {
    cluster: "ssIasZone",
    type: "commandStatusChangeNotification",
    convert: (model, msg, publish, options, meta) => {
        const zoneStatus = msg.data.zonestatus;
        return {
            vibration: (zoneStatus & 1) > 0,
            tamper: (zoneStatus & (1 << 2)) > 0,
            battery_low: (zoneStatus & (1 << 3)) > 0,
        };
    },
};
exports.ias_vibration_alarm_1_with_timeout = {
    cluster: "ssIasZone",
    type: "commandStatusChangeNotification",
    options: [exposes.options.vibration_timeout()],
    convert: (model, msg, publish, options, meta) => {
        const zoneStatus = msg.data.zonestatus;
        const timeout = options?.vibration_timeout != null ? Number(options.vibration_timeout) : 90;
        // Stop existing timers because vibration is detected and set a new one.
        globalStore.getValue(msg.endpoint, "timers", []).forEach((t) => {
            clearTimeout(t);
        });
        globalStore.putValue(msg.endpoint, "timers", []);
        if (timeout !== 0) {
            const timer = setTimeout(() => {
                publish({ vibration: false });
            }, timeout * 1000).unref();
            globalStore.getValue(msg.endpoint, "timers").push(timer);
        }
        return {
            vibration: (zoneStatus & 1) > 0,
            tamper: (zoneStatus & (1 << 2)) > 0,
            battery_low: (zoneStatus & (1 << 3)) > 0,
        };
    },
};
exports.ias_gas_alarm_1 = {
    cluster: "ssIasZone",
    type: "commandStatusChangeNotification",
    convert: (model, msg, publish, options, meta) => {
        const zoneStatus = msg.data.zonestatus;
        return {
            gas: (zoneStatus & 1) > 0,
            tamper: (zoneStatus & (1 << 2)) > 0,
            battery_low: (zoneStatus & (1 << 3)) > 0,
        };
    },
};
exports.ias_gas_alarm_2 = {
    cluster: "ssIasZone",
    type: "commandStatusChangeNotification",
    convert: (model, msg, publish, options, meta) => {
        const zoneStatus = msg.data.zonestatus;
        return {
            gas: (zoneStatus & (1 << 1)) > 0,
            tamper: (zoneStatus & (1 << 2)) > 0,
            battery_low: (zoneStatus & (1 << 3)) > 0,
        };
    },
};
exports.ias_smoke_alarm_1 = {
    cluster: "ssIasZone",
    type: ["commandStatusChangeNotification", "attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        const zoneStatus = "zonestatus" in msg.data ? msg.data.zonestatus : msg.data.zoneStatus;
        return {
            smoke: (zoneStatus & 1) > 0,
            tamper: (zoneStatus & (1 << 2)) > 0,
            battery_low: (zoneStatus & (1 << 3)) > 0,
            supervision_reports: (zoneStatus & (1 << 4)) > 0,
            restore_reports: (zoneStatus & (1 << 5)) > 0,
            trouble: (zoneStatus & (1 << 6)) > 0,
            ac_status: (zoneStatus & (1 << 7)) > 0,
            test: (zoneStatus & (1 << 8)) > 0,
            battery_defect: (zoneStatus & (1 << 9)) > 0,
        };
    },
};
exports.ias_contact_alarm_1 = {
    cluster: "ssIasZone",
    type: "commandStatusChangeNotification",
    convert: (model, msg, publish, options, meta) => {
        const zoneStatus = msg.data.zonestatus;
        const contactProperty = (0, utils_1.postfixWithEndpointName)("contact", msg, model, meta);
        const tamperProperty = (0, utils_1.postfixWithEndpointName)("tamper", msg, model, meta);
        const batteryLowProperty = (0, utils_1.postfixWithEndpointName)("battery_low", msg, model, meta);
        return {
            [contactProperty]: !((zoneStatus & 1) > 0),
            [tamperProperty]: (zoneStatus & (1 << 2)) > 0,
            [batteryLowProperty]: (zoneStatus & (1 << 3)) > 0,
        };
    },
};
exports.ias_contact_alarm_1_report = {
    cluster: "ssIasZone",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        const zoneStatus = msg.data.zoneStatus;
        if (zoneStatus !== undefined) {
            return {
                contact: !((zoneStatus & 1) > 0),
                tamper: (zoneStatus & (1 << 2)) > 0,
                battery_low: (zoneStatus & (1 << 3)) > 0,
            };
        }
    },
};
exports.ias_carbon_monoxide_alarm_1 = {
    cluster: "ssIasZone",
    type: "commandStatusChangeNotification",
    convert: (model, msg, publish, options, meta) => {
        const zoneStatus = msg.data.zonestatus;
        return {
            carbon_monoxide: (zoneStatus & 1) > 0,
            tamper: (zoneStatus & (1 << 2)) > 0,
            battery_low: (zoneStatus & (1 << 3)) > 0,
        };
    },
};
exports.ias_carbon_monoxide_alarm_1_gas_alarm_2 = {
    cluster: "ssIasZone",
    type: "commandStatusChangeNotification",
    convert: (model, msg, publish, options, meta) => {
        const { zonestatus } = msg.data;
        return {
            carbon_monoxide: (zonestatus & 1) > 0,
            gas: (zonestatus & (1 << 1)) > 0,
            tamper: (zonestatus & (1 << 2)) > 0,
            battery_low: (zonestatus & (1 << 3)) > 0,
            trouble: (zonestatus & (1 << 6)) > 0,
            ac_connected: !((zonestatus & (1 << 7)) > 0),
            test: (zonestatus & (1 << 8)) > 0,
            battery_defect: (zonestatus & (1 << 9)) > 0,
        };
    },
};
exports.ias_sos_alarm_2 = {
    cluster: "ssIasZone",
    type: "commandStatusChangeNotification",
    convert: (model, msg, publish, options, meta) => {
        const zoneStatus = msg.data.zonestatus;
        return {
            sos: (zoneStatus & (1 << 1)) > 0,
            tamper: (zoneStatus & (1 << 2)) > 0,
            battery_low: (zoneStatus & (1 << 3)) > 0,
        };
    },
};
exports.ias_occupancy_alarm_1 = {
    cluster: "ssIasZone",
    type: "commandStatusChangeNotification",
    convert: (model, msg, publish, options, meta) => {
        const zoneStatus = msg.data.zonestatus;
        return {
            occupancy: (zoneStatus & 1) > 0,
            tamper: (zoneStatus & (1 << 2)) > 0,
            battery_low: (zoneStatus & (1 << 3)) > 0,
        };
    },
};
exports.ias_occupancy_alarm_1_report = {
    cluster: "ssIasZone",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        const zoneStatus = msg.data.zoneStatus;
        if (zoneStatus !== undefined) {
            return {
                occupancy: (zoneStatus & 1) > 0,
                tamper: (zoneStatus & (1 << 2)) > 0,
                battery_low: (zoneStatus & (1 << 3)) > 0,
            };
        }
    },
};
exports.ias_occupancy_alarm_2 = {
    cluster: "ssIasZone",
    type: "commandStatusChangeNotification",
    convert: (model, msg, publish, options, meta) => {
        const zoneStatus = msg.data.zonestatus;
        return {
            occupancy: (zoneStatus & (1 << 1)) > 0,
            tamper: (zoneStatus & (1 << 2)) > 0,
            battery_low: (zoneStatus & (1 << 3)) > 0,
        };
    },
};
exports.ias_alarm_only_alarm_1 = {
    cluster: "ssIasZone",
    type: "attributeReport",
    convert: (model, msg, publish, options, meta) => {
        const zoneStatus = msg.data.zoneStatus;
        return {
            alarm: (zoneStatus & 1) > 0,
        };
    },
};
exports.ias_occupancy_only_alarm_2 = {
    cluster: "ssIasZone",
    type: "commandStatusChangeNotification",
    convert: (model, msg, publish, options, meta) => {
        const zoneStatus = msg.data.zonestatus;
        return {
            occupancy: (zoneStatus & (1 << 1)) > 0,
        };
    },
};
exports.ias_occupancy_alarm_1_with_timeout = {
    cluster: "ssIasZone",
    type: "commandStatusChangeNotification",
    options: [exposes.options.occupancy_timeout()],
    convert: (model, msg, publish, options, meta) => {
        const zoneStatus = msg.data.zonestatus;
        const timeout = options?.occupancy_timeout != null ? Number(options.occupancy_timeout) : 90;
        clearTimeout(globalStore.getValue(msg.endpoint, "timer"));
        if (timeout !== 0) {
            const timer = setTimeout(() => publish({ occupancy: false }), timeout * 1000).unref();
            globalStore.putValue(msg.endpoint, "timer", timer);
        }
        return {
            occupancy: (zoneStatus & 1) > 0,
            tamper: (zoneStatus & (1 << 2)) > 0,
            battery_low: (zoneStatus & (1 << 3)) > 0,
        };
    },
};
exports.command_store = {
    cluster: "genScenes",
    type: "commandStore",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const payload = { action: (0, utils_1.postfixWithEndpointName)(`store_${msg.data.sceneid}`, msg, model, meta) };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_recall = {
    cluster: "genScenes",
    type: "commandRecall",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const payload = { action: (0, utils_1.postfixWithEndpointName)(`recall_${msg.data.sceneid}`, msg, model, meta) };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_panic = {
    cluster: "ssIasAce",
    type: "commandPanic",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const payload = { action: (0, utils_1.postfixWithEndpointName)("panic", msg, model, meta) };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_arm = {
    cluster: "ssIasAce",
    type: "commandArm",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const payload = {
            action: (0, utils_1.postfixWithEndpointName)(constants.armMode[msg.data.armmode], msg, model, meta),
            action_code: msg.data.code,
            action_zone: msg.data.zoneid,
        };
        if (msg.groupID)
            payload.action_group = msg.groupID;
        return payload;
    },
};
exports.command_cover_stop = {
    cluster: "closuresWindowCovering",
    type: "commandStop",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const payload = { action: (0, utils_1.postfixWithEndpointName)("stop", msg, model, meta) };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_cover_open = {
    cluster: "closuresWindowCovering",
    type: "commandUpOpen",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const payload = { action: (0, utils_1.postfixWithEndpointName)("open", msg, model, meta) };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_cover_close = {
    cluster: "closuresWindowCovering",
    type: "commandDownClose",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const payload = { action: (0, utils_1.postfixWithEndpointName)("close", msg, model, meta) };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_on = {
    cluster: "genOnOff",
    type: "commandOn",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const payload = { action: (0, utils_1.postfixWithEndpointName)("on", msg, model, meta) };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_off = {
    cluster: "genOnOff",
    type: "commandOff",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const payload = { action: (0, utils_1.postfixWithEndpointName)("off", msg, model, meta) };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_off_with_effect = {
    cluster: "genOnOff",
    type: "commandOffWithEffect",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const payload = { action: (0, utils_1.postfixWithEndpointName)("off", msg, model, meta) };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_toggle = {
    cluster: "genOnOff",
    type: "commandToggle",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const payload = { action: (0, utils_1.postfixWithEndpointName)("toggle", msg, model, meta) };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_move_to_level = {
    cluster: "genLevelCtrl",
    type: ["commandMoveToLevel", "commandMoveToLevelWithOnOff"],
    options: [exposes.options.simulated_brightness()],
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const payload = {
            action: (0, utils_1.postfixWithEndpointName)("brightness_move_to_level", msg, model, meta),
            action_level: msg.data.level,
            action_transition_time: msg.data.transtime / 10,
        };
        (0, utils_1.addActionGroup)(payload, msg, model);
        if (options.simulated_brightness) {
            const currentBrightness = globalStore.getValue(msg.endpoint, "simulated_brightness_brightness", defaultSimulatedBrightness);
            globalStore.putValue(msg.endpoint, "simulated_brightness_brightness", msg.data.level);
            const property = (0, utils_1.postfixWithEndpointName)("brightness", msg, model, meta);
            payload[property] = msg.data.level;
            const deltaProperty = (0, utils_1.postfixWithEndpointName)("action_brightness_delta", msg, model, meta);
            payload[deltaProperty] = msg.data.level - currentBrightness;
        }
        return payload;
    },
};
exports.command_move = {
    cluster: "genLevelCtrl",
    type: ["commandMove", "commandMoveWithOnOff"],
    options: [exposes.options.simulated_brightness()],
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const direction = msg.data.movemode === 1 ? "down" : "up";
        const action = (0, utils_1.postfixWithEndpointName)(`brightness_move_${direction}`, msg, model, meta);
        const payload = { action, action_rate: msg.data.rate };
        (0, utils_1.addActionGroup)(payload, msg, model);
        if (options.simulated_brightness) {
            const opts = options.simulated_brightness;
            const deltaOpts = typeof opts === "object" && opts.delta != null ? opts.delta : 20;
            const intervalOpts = typeof opts === "object" && opts.interval != null ? opts.interval : 200;
            globalStore.putValue(msg.endpoint, "simulated_brightness_direction", direction);
            if (globalStore.getValue(msg.endpoint, "simulated_brightness_timer") === undefined) {
                const timer = setInterval(() => {
                    let brightness = globalStore.getValue(msg.endpoint, "simulated_brightness_brightness", defaultSimulatedBrightness);
                    const delta = globalStore.getValue(msg.endpoint, "simulated_brightness_direction") === "up" ? deltaOpts : -1 * deltaOpts;
                    brightness += delta;
                    brightness = (0, utils_1.numberWithinRange)(brightness, 0, 255);
                    globalStore.putValue(msg.endpoint, "simulated_brightness_brightness", brightness);
                    const property = (0, utils_1.postfixWithEndpointName)("brightness", msg, model, meta);
                    const deltaProperty = (0, utils_1.postfixWithEndpointName)("action_brightness_delta", msg, model, meta);
                    publish({ [property]: brightness, [deltaProperty]: delta });
                }, intervalOpts).unref();
                globalStore.putValue(msg.endpoint, "simulated_brightness_timer", timer);
            }
        }
        return payload;
    },
};
exports.command_step = {
    cluster: "genLevelCtrl",
    type: ["commandStep", "commandStepWithOnOff"],
    options: [exposes.options.simulated_brightness()],
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const direction = msg.data.stepmode === 1 ? "down" : "up";
        const payload = {
            action: (0, utils_1.postfixWithEndpointName)(`brightness_step_${direction}`, msg, model, meta),
            action_step_size: msg.data.stepsize,
            action_transition_time: msg.data.transtime / 10,
        };
        (0, utils_1.addActionGroup)(payload, msg, model);
        if (options.simulated_brightness) {
            let brightness = globalStore.getValue(msg.endpoint, "simulated_brightness_brightness", defaultSimulatedBrightness);
            const delta = direction === "up" ? msg.data.stepsize : -1 * msg.data.stepsize;
            brightness += delta;
            brightness = (0, utils_1.numberWithinRange)(brightness, 0, 255);
            globalStore.putValue(msg.endpoint, "simulated_brightness_brightness", brightness);
            const property = (0, utils_1.postfixWithEndpointName)("brightness", msg, model, meta);
            payload[property] = brightness;
            const deltaProperty = (0, utils_1.postfixWithEndpointName)("action_brightness_delta", msg, model, meta);
            payload[deltaProperty] = delta;
        }
        return payload;
    },
};
exports.command_stop = {
    cluster: "genLevelCtrl",
    type: ["commandStop", "commandStopWithOnOff"],
    options: [exposes.options.simulated_brightness()],
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        if (options.simulated_brightness) {
            clearInterval(globalStore.getValue(msg.endpoint, "simulated_brightness_timer"));
            globalStore.clearValue(msg.endpoint, "simulated_brightness_timer");
        }
        const payload = { action: (0, utils_1.postfixWithEndpointName)("brightness_stop", msg, model, meta) };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_move_color_temperature = {
    cluster: "lightingColorCtrl",
    type: ["commandMoveColorTemp"],
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const direction = utils.getFromLookup(msg.data.movemode, { 0: "stop", 1: "up", 3: "down" });
        const action = (0, utils_1.postfixWithEndpointName)(`color_temperature_move_${direction}`, msg, model, meta);
        const payload = { action, action_rate: msg.data.rate, action_minimum: msg.data.minimum, action_maximum: msg.data.maximum };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_stop_move_step = {
    cluster: "lightingColorCtrl",
    type: "commandStopMoveStep",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const payload = { action: (0, utils_1.postfixWithEndpointName)("stop_move_step", msg, model, meta) };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_step_color_temperature = {
    cluster: "lightingColorCtrl",
    type: "commandStepColorTemp",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const direction = msg.data.stepmode === 1 ? "up" : "down";
        const payload = {
            action: (0, utils_1.postfixWithEndpointName)(`color_temperature_step_${direction}`, msg, model, meta),
            action_step_size: msg.data.stepsize,
            action_color_temperature_delta: direction === "up" ? msg.data.stepsize : -1 * msg.data.stepsize,
        };
        if (msg.data.transtime !== undefined) {
            payload.action_transition_time = msg.data.transtime / 10;
        }
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_enhanced_move_to_hue_and_saturation = {
    cluster: "lightingColorCtrl",
    type: "commandEnhancedMoveToHueAndSaturation",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const payload = {
            action: (0, utils_1.postfixWithEndpointName)("enhanced_move_to_hue_and_saturation", msg, model, meta),
            action_enhanced_hue: msg.data.enhancehue,
            action_hue: (0, utils_1.mapNumberRange)(msg.data.enhancehue, 0, 65535, 0, 360, 1),
            action_saturation: msg.data.saturation,
            action_transition_time: msg.data.transtime,
        };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_move_to_hue_and_saturation = {
    cluster: "lightingColorCtrl",
    type: "commandMoveToHueAndSaturation",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const payload = {
            action: (0, utils_1.postfixWithEndpointName)("move_to_hue_and_saturation", msg, model, meta),
            action_hue: msg.data.hue,
            action_saturation: msg.data.saturation,
            action_transition_time: msg.data.transtime,
        };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_step_hue = {
    cluster: "lightingColorCtrl",
    type: ["commandStepHue"],
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const direction = msg.data.stepmode === 1 ? "up" : "down";
        const payload = {
            action: (0, utils_1.postfixWithEndpointName)(`color_hue_step_${direction}`, msg, model, meta),
            action_step_size: msg.data.stepsize,
            action_transition_time: msg.data.transtime / 10,
        };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_step_saturation = {
    cluster: "lightingColorCtrl",
    type: ["commandStepSaturation"],
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const direction = msg.data.stepmode === 1 ? "up" : "down";
        const payload = {
            action: (0, utils_1.postfixWithEndpointName)(`color_saturation_step_${direction}`, msg, model, meta),
            action_step_size: msg.data.stepsize,
            action_transition_time: msg.data.transtime / 10,
        };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_color_loop_set = {
    cluster: "lightingColorCtrl",
    type: "commandColorLoopSet",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const updateFlags = msg.data.updateflags;
        const actionLookup = {
            0: "deactivate",
            1: "activate_from_color_loop_start_enhanced_hue",
            2: "activate_from_enhanced_current_hue",
        };
        const payload = {
            action: (0, utils_1.postfixWithEndpointName)("color_loop_set", msg, model, meta),
            action_update_flags: {
                action: (updateFlags & (1 << 0)) > 0,
                direction: (updateFlags & (1 << 1)) > 0,
                time: (updateFlags & (1 << 2)) > 0,
                start_hue: (updateFlags & (1 << 3)) > 0,
            },
            action_action: actionLookup[msg.data.action],
            action_direction: msg.data.direction === 0 ? "decrement" : "increment",
            action_time: msg.data.time,
            action_start_hue: msg.data.starthue,
        };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_move_to_color_temp = {
    cluster: "lightingColorCtrl",
    type: "commandMoveToColorTemp",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const payload = {
            action: (0, utils_1.postfixWithEndpointName)("color_temperature_move", msg, model, meta),
            action_color_temperature: msg.data.colortemp,
            action_transition_time: msg.data.transtime,
        };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_move_to_color = {
    cluster: "lightingColorCtrl",
    type: "commandMoveToColor",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const payload = {
            action: (0, utils_1.postfixWithEndpointName)("color_move", msg, model, meta),
            action_color: {
                x: (0, utils_1.precisionRound)(msg.data.colorx / 65535, 3),
                y: (0, utils_1.precisionRound)(msg.data.colory / 65535, 3),
            },
            action_transition_time: msg.data.transtime,
        };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_move_hue = {
    cluster: "lightingColorCtrl",
    type: "commandMoveHue",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const movestop = msg.data.movemode === 1 ? "move" : msg.data.movemode === 3 ? "down" : "stop";
        const action = (0, utils_1.postfixWithEndpointName)(`hue_${movestop}`, msg, model, meta);
        const payload = { action, action_rate: msg.data.rate };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_move_to_saturation = {
    cluster: "lightingColorCtrl",
    type: "commandMoveToSaturation",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const payload = {
            action: (0, utils_1.postfixWithEndpointName)("move_to_saturation", msg, model, meta),
            action_saturation: msg.data.saturation,
            action_transition_time: msg.data.transtime,
        };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_move_to_hue = {
    cluster: "lightingColorCtrl",
    type: "commandMoveToHue",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const payload = {
            action: (0, utils_1.postfixWithEndpointName)("move_to_hue", msg, model, meta),
            action_hue: msg.data.hue,
            action_transition_time: msg.data.transtime / 10,
            action_direction: msg.data.direction === 0 ? "decrement" : "increment",
        };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_emergency = {
    cluster: "ssIasAce",
    type: "commandEmergency",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const payload = { action: (0, utils_1.postfixWithEndpointName)("emergency", msg, model, meta) };
        (0, utils_1.addActionGroup)(payload, msg, model);
        return payload;
    },
};
exports.command_on_state = {
    cluster: "genOnOff",
    type: "commandOn",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const property = (0, utils_1.postfixWithEndpointName)("state", msg, model, meta);
        return { [property]: "ON" };
    },
};
exports.command_off_state = {
    cluster: "genOnOff",
    type: "commandOff",
    convert: (model, msg, publish, options, meta) => {
        if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
            return;
        const property = (0, utils_1.postfixWithEndpointName)("state", msg, model, meta);
        return { [property]: "OFF" };
    },
};
exports.identify = {
    cluster: "genIdentify",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        return { action: (0, utils_1.postfixWithEndpointName)("identify", msg, model, meta) };
    },
};
exports.cover_position_tilt = {
    cluster: "closuresWindowCovering",
    type: ["attributeReport", "readResponse"],
    options: [exposes.options.invert_cover()],
    convert: (model, msg, publish, options, meta) => {
        const result = {};
        // Zigbee officially expects 'open' to be 0 and 'closed' to be 100 whereas
        // HomeAssistant etc. work the other way round.
        // For zigbee-herdsman-converters: open = 100, close = 0
        // ubisys J1 will report 255 if lift or tilt positions are not known, so skip that.
        const metaInvert = model.meta?.coverInverted;
        const invert = metaInvert ? !options.invert_cover : options.invert_cover;
        const coverStateFromTilt = model.meta?.coverStateFromTilt;
        if (msg.data.currentPositionLiftPercentage !== undefined && msg.data.currentPositionLiftPercentage <= 100) {
            const value = msg.data.currentPositionLiftPercentage;
            result[(0, utils_1.postfixWithEndpointName)("position", msg, model, meta)] = invert ? value : 100 - value;
            if (!coverStateFromTilt) {
                result[(0, utils_1.postfixWithEndpointName)("state", msg, model, meta)] = invert
                    ? value === 0
                        ? "CLOSE"
                        : "OPEN"
                    : value === 100
                        ? "CLOSE"
                        : "OPEN";
            }
        }
        if (msg.data.currentPositionTiltPercentage !== undefined && msg.data.currentPositionTiltPercentage <= 100) {
            const value = msg.data.currentPositionTiltPercentage;
            result[(0, utils_1.postfixWithEndpointName)("tilt", msg, model, meta)] = invert ? value : 100 - value;
            if (coverStateFromTilt) {
                result[(0, utils_1.postfixWithEndpointName)("state", msg, model, meta)] = invert
                    ? value === 100
                        ? "OPEN"
                        : "CLOSE"
                    : value === 0
                        ? "OPEN"
                        : "CLOSE";
            }
        }
        if (msg.data.windowCoveringMode !== undefined) {
            result[(0, utils_1.postfixWithEndpointName)("cover_mode", msg, model, meta)] = {
                reversed: (msg.data.windowCoveringMode & (1 << 0)) > 0,
                calibration: (msg.data.windowCoveringMode & (1 << 1)) > 0,
                maintenance: (msg.data.windowCoveringMode & (1 << 2)) > 0,
                led: (msg.data.windowCoveringMode & (1 << 3)) > 0,
            };
        }
        return result;
    },
};
exports.cover_position_via_brightness = {
    cluster: "genLevelCtrl",
    type: ["attributeReport", "readResponse"],
    options: [exposes.options.invert_cover()],
    convert: (model, msg, publish, options, meta) => {
        const currentLevel = Number(msg.data.currentLevel);
        let position = (0, utils_1.mapNumberRange)(currentLevel, 0, 255, 0, 100);
        position = options.invert_cover ? 100 - position : position;
        const state = options.invert_cover ? (position > 0 ? "CLOSE" : "OPEN") : position > 0 ? "OPEN" : "CLOSE";
        return { state: state, position: position };
    },
};
exports.cover_state_via_onoff = {
    cluster: "genOnOff",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        if (msg.data.onOff !== undefined) {
            return { state: msg.data.onOff === 1 ? "OPEN" : "CLOSE" };
        }
    },
};
exports.curtain_position_analog_output = {
    cluster: "genAnalogOutput",
    type: ["attributeReport", "readResponse"],
    options: [exposes.options.invert_cover()],
    convert: (model, msg, publish, options, meta) => {
        let position = (0, utils_1.precisionRound)(msg.data.presentValue, 2);
        position = options.invert_cover ? 100 - position : position;
        return { position };
    },
};
exports.lighting_ballast_configuration = {
    cluster: "lightingBallastCfg",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        const result = {};
        if (msg.data.ballastStatus !== undefined) {
            const ballastStatus = msg.data.ballastStatus;
            result.ballast_status_non_operational = !!(ballastStatus & 1);
            result.ballast_status_lamp_failure = !!(ballastStatus & 2);
        }
        if (msg.data.minLevel !== undefined) {
            result.ballast_minimum_level = msg.data.minLevel;
        }
        if (msg.data.maxLevel !== undefined) {
            result.ballast_maximum_level = msg.data.maxLevel;
        }
        if (msg.data.powerOnLevel !== undefined) {
            result.ballast_power_on_level = msg.data.powerOnLevel;
        }
        if (msg.data.powerOnFadeTime !== undefined) {
            result.ballast_power_on_fade_time = msg.data.powerOnFadeTime;
        }
        if (msg.data.intrinsicBallastFactor !== undefined) {
            result.ballast_intrinsic_ballast_factor = msg.data.intrinsicBallastFactor;
        }
        if (msg.data.ballastFactorAdjustment !== undefined) {
            result.ballast_ballast_factor_adjustment = msg.data.ballastFactorAdjustment;
        }
        if (msg.data.lampQuantity !== undefined) {
            result.ballast_lamp_quantity = msg.data.lampQuantity;
        }
        if (msg.data.lampType !== undefined) {
            result.ballast_lamp_type = msg.data.lampType;
        }
        if (msg.data.lampManufacturer !== undefined) {
            result.ballast_lamp_manufacturer = msg.data.lampManufacturer;
        }
        if (msg.data.lampRatedHours !== undefined) {
            result.ballast_lamp_rated_hours = msg.data.lampRatedHours;
        }
        if (msg.data.lampBurnHours !== undefined) {
            result.ballast_lamp_burn_hours = msg.data.lampBurnHours;
        }
        if (msg.data.lampAlarmMode !== undefined) {
            const lampAlarmMode = msg.data.lampAlarmMode;
            result.ballast_lamp_alarm_lamp_burn_hours = !!(lampAlarmMode & 1);
        }
        if (msg.data.lampBurnHoursTripPoint !== undefined) {
            result.ballast_lamp_burn_hours_trip_point = msg.data.lampBurnHoursTripPoint;
        }
        return result;
    },
};
exports.checkin_presence = {
    cluster: "genPollCtrl",
    type: ["commandCheckin"],
    options: [exposes.options.presence_timeout()],
    convert: (model, msg, publish, options, meta) => {
        const useOptionsTimeout = options?.presence_timeout != null;
        const timeout = useOptionsTimeout ? Number(options.presence_timeout) : 100; // 100 seconds by default
        // Stop existing timer because presence is detected and set a new one.
        clearTimeout(globalStore.getValue(msg.endpoint, "timer"));
        const timer = setTimeout(() => publish({ presence: false }), timeout * 1000).unref();
        globalStore.putValue(msg.endpoint, "timer", timer);
        return { presence: true };
    },
};
exports.ias_enroll = {
    cluster: "ssIasZone",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        const zoneState = msg.data.zoneState;
        const iasCieAddr = msg.data.iasCieAddr;
        const zoneId = msg.data.zoneId;
        return {
            enrolled: zoneState !== 0,
            ias_cie_address: iasCieAddr,
            zone_id: zoneId,
        };
    },
};
exports.power_source = {
    cluster: "genBasic",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        const payload = {};
        if (msg.data.powerSource !== undefined) {
            const value = msg.data.powerSource;
            const lookup = {
                0: "unknown",
                1: "mains_single_phase",
                2: "mains_three_phase",
                3: "battery",
                4: "dc_source",
                5: "emergency_mains_constantly_powered",
                6: "emergency_mains_and_transfer_switch",
            };
            payload.power_source = lookup[value];
            if (["R7051"].includes(model.model)) {
                payload.ac_connected = value === 2;
            }
            else if (["ZNCLBL01LM"].includes(model.model)) {
                payload.charging = value === 4;
            }
        }
        return payload;
    },
};
exports.hw_version = {
    cluster: "genBasic",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => {
        const result = {};
        if (msg.data.hwVersion !== undefined)
            result.hw_version = msg.data.hwVersion;
        return result;
    },
};
// #endregion
// #region Non-generic converters
exports.ewelink_action = {
    cluster: "genOnOff",
    type: ["commandOn", "commandOff", "commandToggle"],
    convert: (model, msg, publish, options, meta) => {
        const lookup = { commandToggle: "single", commandOn: "double", commandOff: "long" };
        return { action: lookup[msg.type] };
    },
};
exports.command_status_change_notification_action = {
    cluster: "ssIasZone",
    type: "commandStatusChangeNotification",
    convert: (model, msg, publish, options, meta) => {
        const lookup = { 0: "off", 1: "single", 2: "double", 3: "hold" };
        return { action: lookup[msg.data.zonestatus] };
    },
};
// #endregion
// #region Ignore converters (these message dont need parsing).
exports.ignore_onoff_report = {
    cluster: "genOnOff",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_illuminance_report = {
    cluster: "msIlluminanceMeasurement",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_occupancy_report = {
    cluster: "msOccupancySensing",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_temperature_report = {
    cluster: "msTemperatureMeasurement",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_humidity_report = {
    cluster: "msRelativeHumidity",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_pressure_report = {
    cluster: "msPressureMeasurement",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_analog_report = {
    cluster: "genAnalogInput",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_multistate_report = {
    cluster: "genMultistateInput",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_power_report = {
    cluster: "genPowerCfg",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_light_brightness_report = {
    cluster: "genLevelCtrl",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_light_color_colortemp_report = {
    cluster: "lightingColorCtrl",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => { },
};
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
exports.ignore_closuresWindowCovering_report = {
    cluster: "closuresWindowCovering",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_thermostat_report = {
    cluster: "hvacThermostat",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_iaszone_attreport = {
    cluster: "ssIasZone",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_iaszone_statuschange = {
    cluster: "ssIasZone",
    type: "commandStatusChangeNotification",
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_iaszone_report = {
    cluster: "ssIasZone",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_iasace_commandgetpanelstatus = {
    cluster: "ssIasAce",
    type: ["commandGetPanelStatus"],
    convert: (model, msg, publish, options, meta) => { },
};
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
exports.ignore_genIdentify = {
    cluster: "genIdentify",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_command_on = {
    cluster: "genOnOff",
    type: "commandOn",
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_command_off = {
    cluster: "genOnOff",
    type: "commandOff",
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_command_off_with_effect = {
    cluster: "genOnOff",
    type: "commandOffWithEffect",
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_command_step = {
    cluster: "genLevelCtrl",
    type: "commandStep",
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_command_stop = {
    cluster: "genLevelCtrl",
    type: "commandStop",
    convert: (model, msg, publish, options, meta) => { },
};
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
exports.ignore_genLevelCtrl_report = {
    cluster: "genLevelCtrl",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => { },
};
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
exports.ignore_haDiagnostic = {
    cluster: "haDiagnostic",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_tuya_set_time = {
    cluster: "manuSpecificTuya",
    type: ["commandMcuSyncTime"],
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_tuya_raw = {
    cluster: "manuSpecificTuya",
    type: ["raw"],
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_metering = {
    cluster: "seMetering",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => { },
};
exports.ignore_electrical_measurement = {
    cluster: "haElectricalMeasurement",
    type: ["attributeReport", "readResponse"],
    convert: (model, msg, publish, options, meta) => { },
};
// #endregion
exports.command_arm_with_transaction = {
    cluster: "ssIasAce",
    type: "commandArm",
    convert: (model, msg, publish, options, meta) => {
        const payload = exports.command_arm.convert(model, msg, publish, options, meta);
        if (!payload)
            return;
        payload.action_transaction = msg.meta.zclTransactionSequenceNumber;
        return payload;
    },
};
exports.ias_ace_occupancy_with_timeout = {
    cluster: "ssIasAce",
    type: "commandGetPanelStatus",
    options: [exposes.options.occupancy_timeout()],
    convert: (model, msg, publish, options, meta) => {
        const newMsg = { ...msg, type: "attributeReport", data: { occupancy: 1 } };
        return exports.occupancy_with_timeout.convert(model, newMsg, publish, options, meta);
    },
};
//# sourceMappingURL=fromZigbee.js.map