UNPKG

zigbee-herdsman-converters

Version:

Collection of device converters to be used with zigbee-herdsman

6,893 lines 317 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;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ZMLookups = exports.tuyaHPSCheckingResult = exports.toZigbee = exports.tz = exports.thermostatSystemModes4 = exports.thermostatSystemModes3 = exports.thermostatSystemModes2 = exports.thermostatSystemModes = exports.thermostatPresets = exports.msLookups = exports.moesSwitch = exports.giexWaterValve = exports.fromZigbee = exports.fz = exports.dataPoints = void 0;
const node_fs_1 = __importDefault(require("node:fs"));
const fromZigbee_1 = require("../converters/fromZigbee");
const constants = __importStar(require("./constants"));
const exposes = __importStar(require("./exposes"));
const light = __importStar(require("./light"));
const logger_1 = require("./logger");
const globalStore = __importStar(require("./store"));
const utils = __importStar(require("./utils"));
const dataTypes = {
    raw: 0, // [ bytes ]
    bool: 1, // [0/1]
    value: 2, // [ 4 byte value ]
    string: 3, // [ N byte string ]
    enum: 4, // [ 0-255 ]
    bitmap: 5, // [ 1,2,4 bytes ] as bits
};
// biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
const convertMultiByteNumberPayloadToSingleDecimalNumber = (chunks) => {
    // Destructuring "chunks" is needed because it's a Buffer
    // and we need a simple array.
    let value = 0;
    for (let i = 0; i < chunks.length; i++) {
        value = value << 8;
        value += chunks[i];
    }
    return value;
};
// biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
function getDataValue(dpValue) {
    let dataString = "";
    switch (dpValue.datatype) {
        case dataTypes.raw:
            return dpValue.data;
        case dataTypes.bool:
            return dpValue.data[0] === 1;
        case dataTypes.value:
            return convertMultiByteNumberPayloadToSingleDecimalNumber(dpValue.data);
        case dataTypes.string:
            // Don't use .map here, doesn't work: https://github.com/Koenkk/zigbee-herdsman-converters/pull/1799/files#r530377091
            for (let i = 0; i < dpValue.data.length; ++i) {
                dataString += String.fromCharCode(dpValue.data[i]);
            }
            return dataString;
        case dataTypes.enum:
            return dpValue.data[0];
        case dataTypes.bitmap:
            return convertMultiByteNumberPayloadToSingleDecimalNumber(dpValue.data);
    }
}
// biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
function getTypeName(dpValue) {
    const entry = Object.entries(dataTypes).find(([typeName, typeId]) => typeId === dpValue.datatype);
    return entry ? entry[0] : "unknown";
}
// biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
function logUnexpectedDataPoint(where, msg, dpValue, meta) {
    logger_1.logger.debug(`Received unexpected Tuya DataPoint #${dpValue.dp} from ${meta.device.ieeeAddr} with raw data '${JSON.stringify(dpValue)}': \
        type='${msg.type}', datatype='${getTypeName(dpValue)}', value='${getDataValue(dpValue)}', known DP# usage: \
        ${JSON.stringify(getDataPointNames(dpValue))}`, `zhc:${where}`);
}
// biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
function logUnexpectedDataType(where, msg, dpValue, meta, expectedDataType) {
    logger_1.logger.debug(`Received Tuya DataPoint #${dpValue.dp} with unexpected datatype from ${meta.device.ieeeAddr} with raw data \
        '${JSON.stringify(dpValue)}': type='${msg.type}', datatype='${getTypeName(dpValue)}' (instead of '${expectedDataType}'), \
        value='${getDataValue(dpValue)}', known DP# usage: ${JSON.stringify(getDataPointNames(dpValue))}`, `zhc:${where}`);
}
// biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
function getDataPointNames(dpValue) {
    const entries = Object.entries(dataPoints).filter(([dpName, dpId]) => dpId === dpValue.dp);
    return entries.map(([dpName, dpId]) => dpName);
}
const coverStateOverride = {
    // Contains all covers which differentiate from the default enum states
    // Use manufacturerName to identify device!
    // https://github.com/Koenkk/zigbee2mqtt/issues/5596#issuecomment-759408189
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    _TZE200_rddyvrci: { close: 1, open: 2, stop: 0 },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    _TZE200_wmcdj3aq: { close: 0, open: 2, stop: 1 },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    _TZE200_cowvfni3: { close: 0, open: 2, stop: 1 },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    _TYST11_cowvfni3: { close: 0, open: 2, stop: 1 },
};
// Gets an array containing which enums have to be used in order for the correct close/open/stop commands to be sent
function getCoverStateEnums(manufacturerName) {
    if (manufacturerName in coverStateOverride) {
        return coverStateOverride[manufacturerName];
    }
    return { close: 2, open: 0, stop: 1 }; // defaults
}
function convertDecimalValueTo4ByteHexArray(value) {
    const hexValue = Number(value).toString(16).padStart(8, "0");
    const chunk1 = hexValue.substring(0, 2);
    const chunk2 = hexValue.substring(2, 4);
    const chunk3 = hexValue.substring(4, 6);
    const chunk4 = hexValue.substring(6);
    return Buffer.from([chunk1, chunk2, chunk3, chunk4].map((hexVal) => Number.parseInt(hexVal, 16)));
}
let gSec;
async function sendDataPoints(entity, dpValues, cmd = "dataRequest", seq = undefined) {
    if (seq === undefined) {
        if (gSec === undefined) {
            gSec = 0;
        }
        else {
            gSec++;
            gSec %= 0xffff;
        }
        seq = gSec;
    }
    await entity.command("manuSpecificTuya", cmd, {
        seq,
        dpValues,
    }, { disableDefaultResponse: true });
    return seq;
}
function convertStringToHexArray(value) {
    const asciiKeys = [];
    for (let i = 0; i < value.length; i++) {
        asciiKeys.push(value[i].charCodeAt(0));
    }
    return asciiKeys;
}
function dpValueFromIntValue(dp, value) {
    return { dp, datatype: dataTypes.value, data: convertDecimalValueTo4ByteHexArray(value) };
}
function dpValueFromBool(dp, value) {
    return { dp, datatype: dataTypes.bool, data: Buffer.from([value ? 1 : 0]) };
}
function dpValueFromEnum(dp, value) {
    return { dp, datatype: dataTypes.enum, data: Buffer.from([value]) };
}
function dpValueFromStringBuffer(dp, stringBuffer) {
    return { dp, datatype: dataTypes.string, data: stringBuffer };
}
function dpValueFromRaw(dp, rawBuffer) {
    return { dp, datatype: dataTypes.raw, data: rawBuffer };
}
function dpValueFromBitmap(dp, bitmapBuffer) {
    return { dp, datatype: dataTypes.bitmap, data: bitmapBuffer };
}
// Return `seq` - transaction ID for handling concrete response
async function sendDataPoint(entity, dpValue, cmd, seq = undefined) {
    return await sendDataPoints(entity, [dpValue], cmd, seq);
}
async function sendDataPointValue(entity, dp, value, cmd, seq = undefined) {
    return await sendDataPoints(entity, [dpValueFromIntValue(dp, value)], cmd, seq);
}
async function sendDataPointBool(entity, dp, value, cmd, seq = undefined) {
    return await sendDataPoints(entity, [dpValueFromBool(dp, value)], cmd, seq);
}
async function sendDataPointEnum(entity, dp, value, cmd, seq = undefined) {
    return await sendDataPoints(entity, [dpValueFromEnum(dp, value)], cmd, seq);
}
async function sendDataPointRaw(entity, dp, value, cmd, seq = undefined) {
    return await sendDataPoints(entity, [dpValueFromRaw(dp, value)], cmd, seq);
}
async function sendDataPointBitmap(entity, dp, value, cmd, seq = undefined) {
    return await sendDataPoints(entity, [dpValueFromBitmap(dp, value)], cmd, seq);
}
async function sendDataPointStringBuffer(entity, dp, value, cmd, seq = undefined) {
    return await sendDataPoints(entity, [dpValueFromStringBuffer(dp, value)], cmd, seq);
}
// biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
function convertRawToCycleTimer(value) {
    let timernr = 0;
    let starttime = "00:00";
    let endtime = "00:00";
    let irrigationDuration = 0;
    let pauseDuration = 0;
    let weekdays = "once";
    let timeractive = 0;
    if (value.length > 11) {
        timernr = value[1];
        timeractive = value[2];
        if (value[3] > 0) {
            weekdays =
                (value[3] & 0x01 ? "Su" : "") +
                    (value[3] & 0x02 ? "Mo" : "") +
                    (value[3] & 0x04 ? "Tu" : "") +
                    (value[3] & 0x08 ? "We" : "") +
                    (value[3] & 0x10 ? "Th" : "") +
                    (value[3] & 0x20 ? "Fr" : "") +
                    (value[3] & 0x40 ? "Sa" : "");
        }
        else {
            weekdays = "once";
        }
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        let minsincemidnight = value[4] * 256 + value[5];
        // @ts-expect-error ignore
        starttime = `${String(Number.parseInt(minsincemidnight / 60, 10)).padStart(2, "0")}:${String(minsincemidnight % 60).padStart(2, "0")}`;
        minsincemidnight = value[6] * 256 + value[7];
        // @ts-expect-error ignore
        endtime = `${String(Number.parseInt(minsincemidnight / 60, 10)).padStart(2, "0")}:${String(minsincemidnight % 60).padStart(2, "0")}`;
        irrigationDuration = value[8] * 256 + value[9];
        pauseDuration = value[10] * 256 + value[11];
    }
    return {
        timernr: timernr,
        starttime: starttime,
        endtime: endtime,
        irrigationDuration: irrigationDuration,
        pauseDuration: pauseDuration,
        weekdays: weekdays,
        active: timeractive,
    };
}
// biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
function logDataPoint(where, msg, dpValue, meta) {
    logger_1.logger.info(`Received Tuya DataPoint #${dpValue.dp} from ${meta.device.ieeeAddr} with raw data '${JSON.stringify(dpValue)}': \
        type='${msg.type}', datatype='${getTypeName(dpValue)}', value='${getDataValue(dpValue)}', known DP# usage: \
        ${JSON.stringify(getDataPointNames(dpValue))}`, `zhc:${where}`);
}
const thermostatSystemModes2 = {
    0: "auto",
    1: "cool",
    2: "heat",
    3: "dry",
    4: "fan",
};
exports.thermostatSystemModes2 = thermostatSystemModes2;
const thermostatSystemModes3 = {
    0: "auto",
    1: "heat",
    2: "off",
};
exports.thermostatSystemModes3 = thermostatSystemModes3;
const thermostatSystemModes4 = {
    0: "off",
    1: "auto",
    2: "heat",
};
exports.thermostatSystemModes4 = thermostatSystemModes4;
const thermostatWeekFormat = {
    0: "5+2",
    1: "6+1",
    2: "7",
};
const thermostatForceMode = {
    0: "normal",
    1: "open",
    2: "close",
};
const thermostatPresets = {
    0: "away",
    1: "schedule",
    2: "manual",
    3: "comfort",
    4: "eco",
    5: "boost",
    6: "complex",
};
exports.thermostatPresets = thermostatPresets;
const thermostatScheduleMode = {
    1: "single", // One schedule for all days
    2: "weekday/weekend", // Weekdays(2-5) and Holidays(6-1)
    3: "weekday/sat/sun", // Weekdays(2-6), Saturday(7), Sunday(1)
    4: "7day", // 7 day schedule
};
const silvercrestModes = {
    white: 0,
    color: 1,
    effect: 2,
};
const silvercrestEffects = {
    steady: "00",
    snow: "01",
    rainbow: "02",
    snake: "03",
    twinkle: "04",
    firework: "05",
    horizontal_flag: "06",
    waves: "07",
    updown: "08",
    vintage: "09",
    fading: "0a",
    collide: "0b",
    strobe: "0c",
    sparkles: "0d",
    carnaval: "0e",
    glow: "0f",
};
const fanModes = {
    0: "low",
    1: "medium",
    2: "high",
    3: "auto",
};
// Motion sensor lookups
const msLookups = {
    OSensitivity: {
        0: "sensitive",
        1: "normal",
        2: "cautious",
    },
    VSensitivity: {
        0: "speed_priority",
        1: "normal_priority",
        2: "accuracy_priority",
    },
    Mode: {
        0: "general_model",
        1: "temporaty_stay",
        2: "basic_detection",
        3: "sensor_test",
    },
};
exports.msLookups = msLookups;
const tvThermostatMode = {
    0: "off",
    1: "heat",
    2: "auto",
};
const tvThermostatPreset = {
    0: "auto",
    1: "manual",
    2: "holiday",
    3: "holiday",
};
// Zemismart ZM_AM02 Roller Shade Converter
const ZMLookups = {
    AM02Mode: {
        0: "morning",
        1: "night",
    },
    AM02Control: {
        0: "open",
        1: "stop",
        2: "close",
        3: "continue",
    },
    AM02Direction: {
        0: "forward",
        1: "back",
    },
    AM02WorkState: {
        0: "opening",
        1: "closing",
    },
    AM02Border: {
        0: "up",
        1: "down",
        2: "down_delete",
    },
    AM02Situation: {
        0: "fully_open",
        1: "fully_close",
    },
    AM02MotorWorkingMode: {
        0: "continuous",
        1: "intermittently",
    },
};
exports.ZMLookups = ZMLookups;
const moesSwitch = {
    powerOnBehavior: {
        0: "off",
        1: "on",
        2: "previous",
    },
    indicateLight: {
        0: "off",
        1: "switch",
        2: "position",
        3: "freeze",
    },
};
exports.moesSwitch = moesSwitch;
const tuyaHPSCheckingResult = {
    0: "checking",
    1: "check_success",
    2: "check_failure",
    3: "others",
    4: "comm_fault",
    5: "radar_fault",
};
exports.tuyaHPSCheckingResult = tuyaHPSCheckingResult;
function convertWeekdaysTo1ByteHexArray(weekdays) {
    let nr = 0;
    if (weekdays === "once") {
        return nr;
    }
    if (weekdays.includes("Mo")) {
        nr |= 0x40;
    }
    if (weekdays.includes("Tu")) {
        nr |= 0x20;
    }
    if (weekdays.includes("We")) {
        nr |= 0x10;
    }
    if (weekdays.includes("Th")) {
        nr |= 0x08;
    }
    if (weekdays.includes("Fr")) {
        nr |= 0x04;
    }
    if (weekdays.includes("Sa")) {
        nr |= 0x02;
    }
    if (weekdays.includes("Su")) {
        nr |= 0x01;
    }
    return [nr];
}
// biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
function convertRawToTimer(value) {
    let timernr = 0;
    let starttime = "00:00";
    let duration = 0;
    let weekdays = "once";
    let timeractive = "";
    if (value.length > 12) {
        timernr = value[1];
        const minsincemidnight = value[2] * 256 + value[3];
        // @ts-expect-error ignore
        starttime = `${String(Number.parseInt(minsincemidnight / 60, 10)).padStart(2, "0")}:${String(minsincemidnight % 60).padStart(2, "0")}`;
        duration = value[4] * 256 + value[5];
        if (value[6] > 0) {
            weekdays =
                (value[6] & 0x01 ? "Su" : "") +
                    (value[6] & 0x02 ? "Mo" : "") +
                    (value[6] & 0x04 ? "Tu" : "") +
                    (value[6] & 0x08 ? "We" : "") +
                    (value[6] & 0x10 ? "Th" : "") +
                    (value[6] & 0x20 ? "Fr" : "") +
                    (value[6] & 0x40 ? "Sa" : "");
        }
        else {
            weekdays = "once";
        }
        timeractive = value[8];
    }
    return { timernr: timernr, time: starttime, duration: duration, weekdays: weekdays, active: timeractive };
}
function logUnexpectedDataValue(where, msg, 
// biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
dpValue, meta, 
// biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
valueKind, 
// biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
expectedMinValue = null, 
// biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
expectedMaxValue = null) {
    if (expectedMinValue === null) {
        if (expectedMaxValue === null) {
            logger_1.logger.debug(`Received Tuya DataPoint #${dpValue.dp} with invalid value ${getDataValue(dpValue)} for ${valueKind} \
                from ${meta.device.ieeeAddr}`, `zhc:${where}`);
        }
        else {
            logger_1.logger.debug(`Received Tuya DataPoint #${dpValue.dp} with invalid value ${getDataValue(dpValue)} for ${valueKind} \
                from ${meta.device.ieeeAddr} which is higher than the expected maximum of ${expectedMaxValue}`, `zhc:${where}`);
        }
    }
    else {
        if (expectedMaxValue === null) {
            logger_1.logger.debug(`Received Tuya DataPoint #${dpValue.dp} with invalid value ${getDataValue(dpValue)} for ${valueKind} \
                from ${meta.device.ieeeAddr} which is lower than the expected minimum of ${expectedMinValue}`, `zhc:${where}`);
        }
        else {
            logger_1.logger.debug(`Received Tuya DataPoint #${dpValue.dp} with invalid value ${getDataValue(dpValue)} for ${valueKind} \
                from ${meta.device.ieeeAddr} which is outside the expected range from ${expectedMinValue} to ${expectedMaxValue}`, `zhc:${where}`);
        }
    }
}
// Contains all covers which need their position inverted by default
// Default is 100 = open, 0 = closed; Devices listed here will use 0 = open, 100 = closed instead
// Use manufacturerName to identify device!
// Don't invert _TZE200_cowvfni3: https://github.com/Koenkk/zigbee2mqtt/issues/6043
const coverPositionInvert = [
    "_TZE200_wmcdj3aq",
    "_TZE200_nogaemzt",
    "_TZE200_xuzcvlku",
    "_TZE200_xaabybja",
    "_TZE200_rmymn92d",
    "_TZE200_gubdgai2",
    "_TZE200_r0jdjrvi",
];
// Gets a boolean indicating whether the cover by this manufacturerName needs reversed positions
function isCoverInverted(manufacturerName) {
    // Return true if cover is listed in coverPositionInvert
    // Return false by default, not inverted
    return coverPositionInvert.includes(manufacturerName);
}
// biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
function convertDecimalValueTo2ByteHexArray(value) {
    const hexValue = Number(value).toString(16).padStart(4, "0");
    const chunk1 = hexValue.substr(0, 2);
    const chunk2 = hexValue.substr(2);
    return [chunk1, chunk2].map((hexVal) => Number.parseInt(hexVal, 16));
}
function convertTimeTo2ByteHexArray(time) {
    const timeArray = time.split(":");
    if (timeArray.length !== 2) {
        throw new Error("Time format incorrect");
    }
    const timeHour = Number.parseInt(timeArray[0], 10);
    const timeMinute = Number.parseInt(timeArray[1], 10);
    if (timeHour > 23 || timeMinute > 59) {
        throw new Error("Time incorrect");
    }
    return convertDecimalValueTo2ByteHexArray(timeHour * 60 + timeMinute);
}
const dataPoints = {
    wateringTimer: {
        valve_state_auto_shutdown: 2,
        water_flow: 3,
        shutdown_timer: 11,
        remaining_watering_time: 101,
        valve_state: 102,
        last_watering_duration: 107,
        battery: 110,
    },
    // Common data points
    // Below data points are usually shared between devices
    state: 1,
    heatingSetpoint: 2,
    coverPosition: 2,
    dimmerLevel: 3,
    dimmerMinLevel: 3,
    localTemp: 3,
    coverArrived: 3,
    occupancy: 3,
    mode: 4,
    fanMode: 5,
    dimmerMaxLevel: 5,
    motorDirection: 5,
    config: 5,
    childLock: 7,
    coverChange: 7,
    runningState: 14,
    valveDetection: 20,
    battery: 21,
    tempCalibration: 44,
    // Data points above 100 are usually custom function data points
    waterLeak: 101,
    minTemp: 102,
    maxTemp: 103,
    windowDetection: 104,
    boostTime: 105,
    coverSpeed: 105,
    forceMode: 106,
    comfortTemp: 107,
    ecoTemp: 108,
    valvePos: 109,
    batteryLow: 110,
    weekFormat: 111,
    scheduleWorkday: 112,
    scheduleHoliday: 113,
    awayTemp: 114,
    windowOpen: 115,
    autoLock: 116,
    awayDays: 117,
    // Manufacturer specific
    // Earda
    eardaDimmerLevel: 2,
    // Siterwell Thermostat
    siterwellWindowDetection: 18,
    // Moes Thermostat
    moesHold: 2,
    moesScheduleEnable: 3,
    moesHeatingSetpoint: 16,
    moesMaxTempLimit: 18,
    moesMaxTemp: 19,
    moesDeadZoneTemp: 20,
    moesLocalTemp: 24,
    moesMinTempLimit: 26,
    moesTempCalibration: 27,
    moesValve: 36,
    moesChildLock: 40,
    moesSensor: 43,
    moesSchedule: 101,
    etopErrorStatus: 13,
    // MoesS Thermostat
    moesSsystemMode: 1,
    moesSheatingSetpoint: 2,
    moesSlocalTemp: 3,
    moesSboostHeating: 4,
    moesSboostHeatingCountdown: 5,
    moesSreset: 7,
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    moesSwindowDetectionFunktion_A2: 8,
    moesSwindowDetection: 9,
    moesSchildLock: 13,
    moesSbattery: 14,
    moesSschedule: 101,
    moesSvalvePosition: 104,
    moesSboostHeatingCountdownTimeSet: 103,
    moesScompensationTempSet: 105,
    moesSecoMode: 106,
    moesSecoModeTempSet: 107,
    moesSmaxTempSet: 108,
    moesSminTempSet: 109,
    moesCoverCalibration: 3,
    moesCoverBacklight: 7,
    moesCoverMotorReversal: 8,
    // Neo T&H
    neoOccupancy: 101,
    neoPowerType: 101,
    neoMelody: 102,
    neoDuration: 103,
    neoTamper: 103,
    neoAlarm: 104,
    neoTemp: 105,
    neoTempScale: 106,
    neoHumidity: 106,
    neoMinTemp: 107,
    neoMaxTemp: 108,
    neoMinHumidity: 109,
    neoMaxHumidity: 110,
    neoUnknown2: 112,
    neoTempAlarm: 113,
    neoTempHumidityAlarm: 113,
    neoHumidityAlarm: 114,
    neoUnknown3: 115,
    neoVolume: 116,
    // Neo AlarmOnly
    neoAOBattPerc: 15,
    neoAOMelody: 21,
    neoAODuration: 7,
    neoAOAlarm: 13,
    neoAOVolume: 5,
    // Saswell TRV
    saswellHeating: 3,
    saswellWindowDetection: 8,
    saswellFrostDetection: 10,
    saswellTempCalibration: 27,
    saswellChildLock: 40,
    saswellState: 101,
    saswellLocalTemp: 102,
    saswellHeatingSetpoint: 103,
    saswellValvePos: 104,
    saswellBatteryLow: 105,
    saswellAwayMode: 106,
    saswellScheduleMode: 107,
    saswellScheduleEnable: 108,
    saswellScheduleSet: 109,
    saswellSetpointHistoryDay: 110,
    saswellTimeSync: 111,
    saswellSetpointHistoryWeek: 112,
    saswellSetpointHistoryMonth: 113,
    saswellSetpointHistoryYear: 114,
    saswellLocalHistoryDay: 115,
    saswellLocalHistoryWeek: 116,
    saswellLocalHistoryMonth: 117,
    saswellLocalHistoryYear: 118,
    saswellMotorHistoryDay: 119,
    saswellMotorHistoryWeek: 120,
    saswellMotorHistoryMonth: 121,
    saswellMotorHistoryYear: 122,
    saswellScheduleSunday: 123,
    saswellScheduleMonday: 124,
    saswellScheduleTuesday: 125,
    saswellScheduleWednesday: 126,
    saswellScheduleThursday: 127,
    saswellScheduleFriday: 128,
    saswellScheduleSaturday: 129,
    saswellAntiScaling: 130,
    // HY thermostat
    hyHeating: 102,
    hyExternalTemp: 103,
    hyAwayDays: 104,
    hyAwayTemp: 105,
    hyMaxTempProtection: 106,
    hyMinTempProtection: 107,
    hyTempCalibration: 109,
    hyHysteresis: 110,
    hyProtectionHysteresis: 111,
    hyProtectionMaxTemp: 112,
    hyProtectionMinTemp: 113,
    hyMaxTemp: 114,
    hyMinTemp: 115,
    hySensor: 116,
    hyPowerOnBehavior: 117,
    hyWeekFormat: 118,
    hyWorkdaySchedule1: 119,
    hyWorkdaySchedule2: 120,
    hyHolidaySchedule1: 121,
    hyHolidaySchedule2: 122,
    hyState: 125,
    hyHeatingSetpoint: 126,
    hyLocalTemp: 127,
    hyMode: 128,
    hyChildLock: 129,
    hyAlarm: 130,
    // Silvercrest
    silvercrestChangeMode: 2,
    silvercrestSetBrightness: 3,
    silvercrestSetColorTemp: 4,
    silvercrestSetColor: 5,
    silvercrestSetEffect: 6,
    // Fantem
    fantemPowerSupplyMode: 101,
    fantemReportingTime: 102,
    fantemExtSwitchType: 103,
    fantemTempCalibration: 104,
    fantemHumidityCalibration: 105,
    fantemLoadDetectionMode: 105,
    fantemLuxCalibration: 106,
    fantemExtSwitchStatus: 106,
    fantemTemp: 107,
    fantemHumidity: 108,
    fantemMotionEnable: 109,
    fantemControlMode: 109,
    fantemBattery: 110,
    fantemLedEnable: 111,
    fantemReportingEnable: 112,
    fantemLoadType: 112,
    fantemLoadDimmable: 113,
    // Woox
    wooxSwitch: 102,
    wooxBattery: 14,
    wooxSmokeTest: 8,
    // Woox thermostat
    wooxDormancy: 108, // ???
    wooxRefresh: 120, //  ???
    wooxControlTemperature: 119, // map auto and manual temperature setpoint.
    wooxManualTemperatureSetpoint: 16, //RW
    wooxAutomaticTemperatureSetpoint: 105, //RW
    wooxMode: 2, //RW
    wooxLocalTemperature: 24, //R
    wooxTemperatureCalibration: 104, //RW
    wooxWindowStatus: 107, //R open,close
    wooxWindowTemperature: 116, //RW
    wooxWindowTime: 117, //RW
    wooxChildLock: 30, //RW
    wooxBatteryCapacity: 34, //R
    wooxEnergySavingTemperature: 102, //RW
    wooxComfortTemperature: 101, //RW
    wooxHolidayModeSettings: 103, //RW
    wooxProgrammingMonday: 109, //RW
    wooxProgrammingTuesday: 110, //RW
    wooxProgrammingWednesday: 111, //RW
    wooxProgrammingThursday: 112, //RW
    wooxProgrammingFriday: 113, //RW
    wooxProgrammingSaturday: 114, //RW
    wooxProgrammingSunday: 115, //RW
    wooxBoostHeating: 106, //RW
    wooxFaultAlarm: 45, // R
    wooxBoostHeatingCountdown: 118, //R
    // FrankEver
    frankEverTimer: 9,
    frankEverTreshold: 101,
    // Dinrail power meter switch
    dinrailPowerMeterTotalEnergy: 17,
    dinrailPowerMeterCurrent: 18,
    dinrailPowerMeterPower: 19,
    dinrailPowerMeterVoltage: 20,
    dinrailPowerMeterTotalEnergy2: 101,
    dinrailPowerMeterPower2: 103,
    // tuya smart air box
    tuyaSabCO2: 2,
    tuyaSabTemp: 18,
    tuyaSabHumidity: 19,
    tuyaSabVOC: 21,
    tuyaSabFormaldehyd: 22,
    // tuya Smart Air House Keeper, Multifunctionale air quality detector.
    // CO2, Temp, Humidity, VOC and Formaldehyd same as Smart Air Box
    tuyaSahkMP25: 2,
    tuyaSahkCO2: 22,
    tuyaSahkFormaldehyd: 20,
    // Tuya CO (carbon monoxide) smart air box
    tuyaSabCOalarm: 1,
    tuyaSabCO: 2,
    // Moes MS-105 Dimmer
    moes105DimmerState1: 1,
    moes105DimmerLevel1: 2,
    moes105DimmerState2: 7,
    moes105DimmerLevel2: 8,
    // Tuya Radar Sensor
    trsPresenceState: 1,
    trsSensitivity: 2,
    trsMotionState: 102,
    trsIlluminanceLux: 103,
    trsDetectionData: 104,
    trsScene: 112,
    trsMotionDirection: 114,
    trsMotionSpeed: 115,
    // Tuya Radar Sensor with fall function
    trsfPresenceState: 1,
    trsfSensitivity: 2,
    trsfMotionState: 102,
    trsfIlluminanceLux: 103,
    trsfTumbleSwitch: 105,
    trsfTumbleAlarmTime: 106,
    trsfScene: 112,
    trsfMotionDirection: 114,
    trsfMotionSpeed: 115,
    trsfFallDownStatus: 116,
    trsfStaticDwellAlarm: 117,
    trsfFallSensitivity: 118,
    // Human Presence Sensor AIR
    msVSensitivity: 101,
    msOSensitivity: 102,
    msVacancyDelay: 103,
    msMode: 104,
    msVacantConfirmTime: 105,
    msReferenceLuminance: 106,
    msLightOnLuminancePrefer: 107,
    msLightOffLuminancePrefer: 108,
    msLuminanceLevel: 109,
    msLedStatus: 110,
    // TV01 Moes Thermostat
    tvMode: 2,
    tvWindowDetection: 8,
    tvFrostDetection: 10,
    tvHeatingSetpoint: 16,
    tvLocalTemp: 24,
    tvTempCalibration: 27,
    tvWorkingDay: 31,
    tvHolidayTemp: 32,
    tvBattery: 35,
    tvChildLock: 40,
    tvErrorStatus: 45,
    tvHolidayMode: 46,
    tvBoostTime: 101,
    tvOpenWindowTemp: 102,
    tvComfortTemp: 104,
    tvEcoTemp: 105,
    tvWeekSchedule: 106,
    tvHeatingStop: 107,
    tvMondaySchedule: 108,
    tvWednesdaySchedule: 109,
    tvFridaySchedule: 110,
    tvSundaySchedule: 111,
    tvTuesdaySchedule: 112,
    tvThursdaySchedule: 113,
    tvSaturdaySchedule: 114,
    tvBoostMode: 115,
    // HOCH / WDYK DIN Rail
    hochCountdownTimer: 9,
    hochFaultCode: 26,
    hochRelayStatus: 27,
    hochChildLock: 29,
    hochVoltage: 101,
    hochCurrent: 102,
    hochActivePower: 103,
    hochLeakageCurrent: 104,
    hochTemperature: 105,
    hochRemainingEnergy: 106,
    hochRechargeEnergy: 107,
    hochCostParameters: 108,
    hochLeakageParameters: 109,
    hochVoltageThreshold: 110,
    hochCurrentThreshold: 111,
    hochTemperatureThreshold: 112,
    hochTotalActivePower: 113,
    hochEquipmentNumberType: 114,
    hochClearEnergy: 115,
    hochLocking: 116,
    hochTotalReverseActivePower: 117,
    hochHistoricalVoltage: 118,
    hochHistoricalCurrent: 119,
    // NOUS SMart LCD Temperature and Humidity Sensor E6
    nousTemperature: 1,
    nousHumidity: 2,
    nousBattery: 4,
    nousTempUnitConvert: 9,
    nousMaxTemp: 10,
    nousMinTemp: 11,
    nousMaxHumi: 12,
    nousMinHumi: 13,
    nousTempAlarm: 14,
    nousHumiAlarm: 15,
    nousHumiSensitivity: 20,
    nousTempSensitivity: 19,
    nousTempReportInterval: 17,
    nousHumiReportInterval: 18,
    // TUYA Temperature and Humidity Sensor
    tthTemperature: 1,
    tthHumidity: 2,
    tthBatteryLevel: 3,
    tthBattery: 4,
    // TUYA / HUMIDITY/ILLUMINANCE/TEMPERATURE SENSOR
    thitBatteryPercentage: 3,
    thitIlluminanceLux: 7,
    tIlluminanceLux: 2,
    thitHumidity: 9,
    thitTemperature: 8,
    // TUYA SMART VIBRATION SENSOR
    tuyaVibration: 10,
    // TUYA WLS-100z Water Leak Sensor
    wlsWaterLeak: 1,
    wlsBatteryPercentage: 4,
    // Evanell
    evanellMode: 2,
    evanellHeatingSetpoint: 4,
    evanellLocalTemp: 5,
    evanellBattery: 6,
    evanellChildLock: 8,
    // ZMAM02 Zemismart RF Courtain Converter
    AM02Control: 1,
    AM02PercentControl: 2,
    AM02PercentState: 3,
    AM02Mode: 4,
    AM02Direction: 5,
    AM02WorkState: 7,
    AM02CountdownLeft: 9,
    AM02TimeTotal: 10,
    AM02SituationSet: 11,
    AM02Fault: 12,
    AM02Border: 16,
    AM02MotorWorkingMode: 20,
    AM02AddRemoter: 101,
    // Matsee Tuya Garage Door Opener
    garageDoorTrigger: 1,
    garageDoorContact: 3,
    garageDoorStatus: 12,
    // Moes switch with optional neutral
    moesSwitchPowerOnBehavior: 14,
    moesSwitchIndicateLight: 15,
    // X5H thermostat
    x5hState: 1,
    x5hMode: 2,
    x5hWorkingStatus: 3,
    x5hSound: 7,
    x5hFrostProtection: 10,
    x5hSetTemp: 16,
    x5hSetTempCeiling: 19,
    x5hCurrentTemp: 24,
    x5hTempCorrection: 27,
    x5hWeeklyProcedure: 30,
    x5hWorkingDaySetting: 31,
    x5hFactoryReset: 39,
    x5hChildLock: 40,
    x5hSensorSelection: 43,
    x5hFaultAlarm: 45,
    x5hTempDiff: 101,
    x5hProtectionTempLimit: 102,
    x5hOutputReverse: 103,
    x5hBackplaneBrightness: 104,
    // Connected thermostat
    connecteState: 1,
    connecteMode: 2,
    connecteHeatingSetpoint: 16,
    connecteLocalTemp: 24,
    connecteTempCalibration: 28,
    connecteChildLock: 30,
    connecteTempFloor: 101,
    connecteSensorType: 102,
    connecteHysteresis: 103,
    connecteRunningState: 104,
    connecteTempProgram: 105,
    connecteOpenWindow: 106,
    connecteMaxProtectTemp: 107,
    // Tuya Smart Human Presence Sensor
    tshpsPresenceState: 1,
    tshpscSensitivity: 2,
    tshpsMinimumRange: 3,
    tshpsMaximumRange: 4,
    tshpsTargetDistance: 9,
    tshpsDetectionDelay: 101,
    tshpsFadingTime: 102,
    tshpsIlluminanceLux: 104,
    tshpsCLI: 103, // not recognize
    tshpsSelfTest: 6, // not recognize
    // Tuya Luminance Motion sensor
    lmsState: 1,
    lmsBattery: 4,
    lmsSensitivity: 9,
    lmsKeepTime: 10,
    lmsIlluminance: 12,
    // Alecto SMART-SMOKE10
    alectoSmokeState: 1,
    alectoSmokeValue: 2,
    alectoSelfChecking: 8,
    alectoCheckingResult: 9,
    alectoSmokeTest: 11,
    alectoLifecycle: 12,
    alectoBatteryState: 14,
    alectoBatteryPercentage: 15,
    alectoSilence: 16,
    // BAC-002-ALZB - Moes like thermostat with Fan control
    bacFanMode: 28,
    // Human Presence Sensor Zigbee Radiowave Tuya
    HPSZInductionState: 1,
    HPSZPresenceTime: 101,
    HPSZLeavingTime: 102,
    HPSZLEDState: 103,
    giexWaterValve: {
        battery: 108,
        currentTemperature: 106,
        cycleIrrigationInterval: 105,
        cycleIrrigationNumTimes: 103,
        irrigationEndTime: 102,
        irrigationStartTime: 101,
        irrigationTarget: 104,
        lastIrrigationDuration: 114,
        mode: 1,
        state: 2,
        waterConsumed: 111,
    },
    zsHeatingSetpoint: 16,
    zsChildLock: 40,
    zsTempCalibration: 104,
    zsLocalTemp: 24,
    zsBatteryVoltage: 35,
    zsComfortTemp: 101,
    zsEcoTemp: 102,
    zsHeatingSetpointAuto: 105,
    zsOpenwindowTemp: 116,
    zsOpenwindowTime: 117,
    zsErrorStatus: 45,
    zsMode: 2,
    zsAwaySetting: 103,
    zsBinaryOne: 106,
    zsBinaryTwo: 107,
    zsScheduleMonday: 109,
    zsScheduleTuesday: 110,
    zsScheduleWednesday: 111,
    zsScheduleThursday: 112,
    zsScheduleFriday: 113,
    zsScheduleSaturday: 114,
    zsScheduleSunday: 115,
};
exports.dataPoints = dataPoints;
// biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
function firstDpValue(msg, meta, converterName) {
    const dpValues = msg.data.dpValues;
    for (let index = 1; index < dpValues.length; index++) {
        logger_1.logger.debug(`Additional DP #${dpValues[index].dp} with data ${JSON.stringify(dpValues[index])} will be ignored! \
                Use a for loop in the fromZigbee converter (see \
                https://www.zigbee2mqtt.io/advanced/support-new-devices/02_support_new_tuya_devices.html)`, `zhc:${converterName}`);
    }
    return dpValues[0];
}
// biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
function getMetaValue(entity, definition, key, groupStrategy = "first") {
    if (entity.constructor.name === "Group" && entity.members.length > 0) {
        const values = [];
        for (const memberMeta of definition) {
            if (memberMeta.meta && memberMeta.meta[key] !== undefined) {
                if (groupStrategy === "first") {
                    return memberMeta.meta[key];
                }
                values.push(memberMeta.meta[key]);
            }
            else {
                values.push(undefined);
            }
        }
        if (groupStrategy === "allEqual" && new Set(values).size === 1) {
            return values[0];
        }
    }
    else if (definition && definition.meta && definition.meta[key] !== undefined) {
        return definition.meta[key];
    }
    return undefined;
}
const SAFETY_MIN_SECS = 10;
const CAPACITY = "capacity";
const DURATION = "duration";
const OFF = "OFF";
const ON = "ON";
// biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
const toLocalTime = (time, timezone) => {
    if (time === "--:--:--") {
        return time;
    }
    const local = new Date(`2000-01-01T${time}.000${timezone}`); // Using 1970 instead produces edge cases
    return local.toTimeString().split(" ").shift();
};
const giexFzModelConverters = {
    QT06_1: {
        // _TZE200_sh1btabb timezone is GMT+8
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        time: (value) => toLocalTime(value, "+08:00"),
    },
};
const giexTzModelConverters = {
    QT06_2: {
        // _TZE200_a7sghmms irrigation time should not be less than 10 secs as per GiEX advice
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        irrigationTarget: (value, mode) => (value > 0 && value < SAFETY_MIN_SECS && mode === DURATION ? SAFETY_MIN_SECS : value),
    },
};
const giexWaterValve = {
    battery: "battery",
    currentTemperature: "current_temperature",
    cycleIrrigationInterval: "cycle_irrigation_interval",
    cycleIrrigationNumTimes: "cycle_irrigation_num_times",
    irrigationEndTime: "irrigation_end_time",
    irrigationStartTime: "irrigation_start_time",
    irrigationTarget: "irrigation_target",
    lastIrrigationDuration: "last_irrigation_duration",
    mode: "mode",
    state: "state",
    waterConsumed: "water_consumed",
};
exports.giexWaterValve = giexWaterValve;
const fromZigbee = {
    TS0222: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const result = {};
            for (const dpValue of msg.data.dpValues) {
                const dp = dpValue.dp;
                const value = getDataValue(dpValue);
                switch (dp) {
                    case 2:
                        result.illuminance = value;
                        break;
                    case 4:
                        result.battery = value;
                        break;
                    default:
                        logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:ts0222");
                }
            }
            return result;
        },
    },
    watering_timer: {
        cluster: "manuSpecificTuya",
        type: ["commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const result = {};
            for (const dpValue of msg.data.dpValues) {
                const dp = dpValue.dp; // First we get the data point ID
                const value = getDataValue(dpValue); // This function will take care of converting the data to proper JS type
                switch (dp) {
                    case dataPoints.wateringTimer.water_flow: {
                        result.water_flow = value;
                        break;
                    }
                    case dataPoints.wateringTimer.remaining_watering_time: {
                        result.remaining_watering_time = value;
                        break;
                    }
                    case dataPoints.wateringTimer.last_watering_duration: {
                        result.last_watering_duration = value;
                        break;
                    }
                    case dataPoints.wateringTimer.valve_state: {
                        result.valve_state = value;
                        break;
                    }
                    case dataPoints.wateringTimer.shutdown_timer: {
                        result.shutdown_timer = value;
                        break;
                    }
                    case dataPoints.wateringTimer.valve_state_auto_shutdown: {
                        result.valve_state_auto_shutdown = value;
                        result.valve_state = value;
                        break;
                    }
                    case dataPoints.wateringTimer.battery: {
                        result.battery = value;
                        break;
                    }
                    default: {
                        logger_1.logger.debug(`>>> UNKNOWN DP #${dp} with data "${JSON.stringify(dpValue)}"`, "zhc:legacy:fz:watering_timer");
                    }
                }
            }
            return result;
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    ZM35HQ_battery: {
        cluster: "manuSpecificTuya",
        type: ["commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "ZM35HQ");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            if (dp === 4)
                return { battery: value };
            logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:zm35hq");
        },
    },
    ZMRM02: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "ZMRM02");
            if (dpValue.dp === 10) {
                return { battery: getDataValue(dpValue) };
            }
            const button = dpValue.dp;
            const actionValue = getDataValue(dpValue);
            const lookup = { 0: "single", 1: "double", 2: "hold" };
            const action = lookup[actionValue];
            return { action: `button_${button}_${action}` };
        },
    },
    SA12IZL: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const result = {};
            for (const dpValue of msg.data.dpValues) {
                const dp = dpValue.dp;
                const value = getDataValue(dpValue);
                switch (dp) {
                    case dataPoints.state:
                        result.smoke = value === 0;
                        break;
                    case 15:
                        result.battery = value;
                        break;
                    case 16:
                        result.silence_siren = value;
                        break;
                    case 20: {
                        const alarm = { 0: true, 1: false };
                        result.alarm = alarm[value];
                        break;
                    }
                    default:
                        logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:sa12izl");
                }
            }
            return result;
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    R7049_status: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const result = {};
            for (const dpValue of msg.data.dpValues) {
                const dp = dpValue.dp; // First we get the data point ID
                const value = getDataValue(dpValue); // This function will take care of converting the data to proper JS type
                switch (dp) {
                    case 1:
                        result.smoke = Boolean(!value);
                        break;
                    case 8:
                        result.test_alarm = value;
                        break;
                    case 9: {
                        const testAlarmResult = { 0: "checking", 1: "check_success", 2: "check_failure", 3: "others" };
                        result.test_alarm_result = testAlarmResult[value];
                        break;
                    }
                    case 11:
                        result.fault_alarm = Boolean(value);
                        break;
                    case 14: {
                        const batteryLevels = { 0: "low", 1: "middle", 2: "high" };
                        result.battery_level = batteryLevels[value];
                        result.battery_low = value === 0;
                        break;
                    }
                    case 16:
                        result.silence_siren = value;
                        break;
                    case 20: {
                        const alarm = { 0: true, 1: false };
                        result.alarm = alarm[value];
                        break;
                    }
                    default:
                        logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:r7049_status");
                }
            }
            return result;
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    woox_R7060: {
        cluster: "manuSpecificTuya",
        type: ["commandActiveStatusReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "woox_R7060");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            switch (dp) {
                case dataPoints.wooxSwitch:
                    return { state: value === 2 ? "OFF" : "ON" };
                case 101:
                    return { battery: value };
                default:
                    logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:woox_r7060");
            }
        },
    },
    woox_thermostat: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const result = {};
            for (const dpValue of msg.data.dpValues) {
                const dp = dpValue.dp;
                const value = getDataValue(dpValue);
                switch (dp) {
                    case dataPoints.wooxMode:
                        if (value === 0) {
                            result.system_mode = "auto";
                            result.away_mode = "OFF";
                        }
                        else if (value === 1) {
                            result.system_mode = "heat";
                            result.away_mode = "OFF";
                        }
                        else if (value === 2) {
                            result.away_mode = "ON";
                            result.system_mode = "auto";
                        }
                        else {
                            result.away_mode = "OFF";
                            result.system_mode = "off";
                        }
                        break;
                    case dataPoints.wooxManualTemperatureSetpoint:
                        result.current_heating_setpoint = Number.parseFloat((value / 2).toFixed(1));
                        result.manual_heating_setpoint = Number.parseFloat((value / 2).toFixed(1));
                        break;
                    case dataPoints.wooxAutomaticTemperatureSetpoint:
                        result.current_heating_setpoint = Number.parseFloat((value / 2).toFixed(1));
                        result.auto_heating_setpoint = Number.parseFloat((value / 2).toFixed(1));
                        break;
                    case dataPoints.wooxLocalTemperature:
                        result.local_temperature = Number.parseFloat((value / 10).toFixed(1));
                        break;
                    case dataPoints.wooxTemperatureCalibration:
                        result.local_temperature_calibration = Number.parseFloat((value / 10).toFixed(1));
                        break;
                    case dataPoints.wooxWindowStatus:
                        result.window_detection = value[0] ? "OPEN" : "CLOSED";
                        break;
                    case dataPoints.wooxWindowTemperature:
                        result.window_detection_temperature = Number.parseFloat((value / 2).toFixed(1));
                        break;
                    case dataPoints.wooxWindowTime:
                        result.window_detection_time = value;
                        break;
                    case dataPoints.wooxChildLock:
                        result.child_lock = value ? "LOCK" : "UNLOCK";
                        break;
                    case dataPoints.wooxBatteryCapacity:
                        result.battery = value;
                        result.battery_low = value < 30 ? 1 : 0;
                        break;
                    case dataPoints.wooxBoostHeatingCountdown:
                        result.boost_time = value;
                        break;
                    case dataPoints.wooxEnergySavingTemperature:
                        result.eco_temperature = Number.parseFloat((value / 2).toFixed(1));
                        break;
                    case dataPoints.wooxComfortTemperature:
                        result.comfort_temperature = Number.parseFloat((value / 2).toFixed(1));
                        break;
                    case dataPoints.wooxBoostHeating:
                        result.boost_heating = value ? "ON" : "OFF";
                        break;
                    case dataPoints.wooxFaultAlarm:
                        result.error_status = value;
                        break;
                    case dataPoints.wooxProgrammingMonday:
                        result.monday_schedule = value.join();
                        break;
                    case dataPoints.wooxProgrammingTuesday:
                        result.tuesday_schedule = value.join();
                        break;
                    case dataPoints.wooxProgrammingWednesday:
                        result.wednesday_schedule = value.join();
                        break;
                    case dataPoints.wooxProgrammingThursday:
                        result.thursday_schedule = value.join();
                        break;
                    case dataPoints.wooxProgrammingFriday:
                        result.friday_schedule = value.join();
                        break;
                    case dataPoints.wooxProgrammingSaturday:
                        result.saturday_schedule = value.join();
                        break;
                    case dataPoints.wooxProgrammingSunday:
                        result.sunday_schedule = value.join();
                        break;
                    case dataPoints.wooxHolidayModeSettings:
                        result.holidays_schedule = value.join();
                        break;
                }
                logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue.data)}`, "zhc:legacy:fz:woox_thermostat");
            }
            return result;
        },
    },
    hpsz: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "hpsz");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            let result = null;
            switch (dp) {
                case dataPoints.HPSZInductionState:
                    result = { presence: value === 1 };
                    break;
                case dataPoints.HPSZPresenceTime:
                    result = { duration_of_attendance: value };
                    break;
                case dataPoints.HPSZLeavingTime:
                    result = { duration_of_absence: value };
                    break;
                case dataPoints.HPSZLEDState:
                    result = { led_state: value };
                    break;
                default:
                    logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:hpsz");
            }
            return result;
        },
    },
    zb_sm_cover: {
        cluster: "manuSpecificTuya",
        type: ["commandDataReport", "commandDataResponse"],
        convert: (model, msg, publish, options, meta) => {
            const result = {};
            for (const dpValue of msg.data.dpValues) {
                const dp = dpValue.dp;
                const value = getDataValue(dpValue);
                switch (dp) {
                    case dataPoints.coverPosition: // Started moving to position (triggered from Zigbee)
                    case dataPoints.coverArrived: {
                        // Arrived at position
                        const invert = meta.state ? !meta.state.invert_cover : false;
                        const position = invert ? 100 - (value & 0xff) : value & 0xff;
                        if (position > 0 && position <= 100) {
                            result.position = position;
                            result.state = "OPEN";
                        }
                        else if (position === 0) {
                            // Report fully closed
                            result.position = position;
                            result.state = "CLOSE";
                        }
                        break;
                    }
                    case 1: // report state
                        // @ts-expect-error ignore
                        result.state = { 0: "OPEN", 1: "STOP", 2: "CLOSE" }[value];
                        break;
                    case dataPoints.motorDirection: // reverse direction
                        result.reverse_direction = value === 1;
                        break;
                    case 10: // cycle time
                        result.cycle_time = value;
                        break;
                    case 101: // model
                        // @ts-expect-error ignore
                        result.motor_type = {
                            0: "",
                            1: "AM0/6-28R-Sm",
                            2: "AM0/10-19R-Sm",
                            3: "AM1/10-13R-Sm",
                            4: "AM1/20-13R-Sm",
                            5: "AM1/30-13R-Sm",
                        }[value];
                        break;
                    case 102: // cycles
                        result.cycle_count = value;
                        break;
                    case 103: // set or clear bottom limit
                        // @ts-expect-error ignore
                        result.bottom_limit = { 0: "SET", 1: "CLEAR" }[value];
                        break;
                    case 104: // set or clear top limit
                        // @ts-expect-error ignore
                        result.top_limit = { 0: "SET", 1: "CLEAR" }[value];
                        break;
                    case 109: // active power
                        result.active_power = value;
                        break;
                    case 115: // favorite_position
                        result.favorite_position = value !== 101 ? value : null;
                        break;
                    case 116: // report confirmation
                        break;
                    case 121: // running state
                        // @ts-expect-error ignore
                        result.motor_state = { 0: "opening", 1: "stopped", 2: "closing" }[value];
                        result.running = value !== 1;
                        break;
                    default: // Unknown code
                        logger_1.logger.debug(`Unhandled DP #${dp} for ${meta.device.manufacturerName}: ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:zb_sm_cover");
                }
            }
            return result;
        },
    },
    x5h_thermostat: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "x5h_thermostat");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            switch (dp) {
                case dataPoints.x5hState: {
                    return { system_mode: value ? "heat" : "off" };
                }
                case dataPoints.x5hWorkingStatus: {
                    return { running_state: value ? "heat" : "idle" };
                }
                case dataPoints.x5hSound: {
                    return { sound: value ? "ON" : "OFF" };
                }
                case dataPoints.x5hFrostProtection: {
                    return { frost_protection: value ? "ON" : "OFF" };
                }
                case dataPoints.x5hWorkingDaySetting: {
                    return { week: thermostatWeekFormat[value] };
                }
                case dataPoints.x5hFactoryReset: {
                    if (value) {
                        clearTimeout(globalStore.getValue(msg.endpoint, "factoryResetTimer"));
                        const timer = setTimeout(() => publish({ factory_reset: "OFF" }), 60 * 1000).unref();
                        globalStore.putValue(msg.endpoint, "factoryResetTimer", timer);
                        logger_1.logger.info("The thermostat is resetting now. It will be available in 1 minute.", "zhc:legacy:fz:x5h_thermostat");
                    }
                    return { factory_reset: value ? "ON" : "OFF" };
                }
                case dataPoints.x5hTempDiff: {
                    return { deadzone_temperature: Number.parseFloat((value / 10).toFixed(1)) };
                }
                case dataPoints.x5hProtectionTempLimit: {
                    return { heating_temp_limit: value };
                }
                case dataPoints.x5hBackplaneBrightness: {
                    const lookup = { 0: "off", 1: "low", 2: "medium", 3: "high" };
                    if (value >= 0 && value <= 3) {
                        globalStore.putValue(msg.endpoint, "brightnessState", value);
                        return { brightness_state: lookup[value] };
                    }
                    // Sometimes, for example on thermostat restart, it sends message like:
                    // {"dpValues":[{"data":{"data":[90],"type":"Buffer"},"datatype":4,"dp":104}
                    // It doesn't represent any brightness value and brightness remains the previous value
                    const lastValue = globalStore.getValue(msg.endpoint, "brightnessState") || 1;
                    return { brightness_state: lookup[lastValue] };
                }
                case dataPoints.x5hWeeklyProcedure: {
                    const periods = [];
                    const periodSize = 4;
                    const periodsNumber = 8;
                    for (let i = 0; i < periodsNumber; i++) {
                        const hours = value[i * periodSize];
                        const minutes = value[i * periodSize + 1];
                        const tempHexArray = [value[i * periodSize + 2], value[i * periodSize + 3]];
                        const tempRaw = Buffer.from(tempHexArray).readUIntBE(0, tempHexArray.length);
                        const strHours = hours.toString().padStart(2, "0");
                        const strMinutes = minutes.toString().padStart(2, "0");
                        const temp = Number.parseFloat((tempRaw / 10).toFixed(1));
                        periods.push(`${strHours}:${strMinutes}/${temp}`);
                    }
                    const schedule = periods.join(" ");
                    return { schedule };
                }
                case dataPoints.x5hChildLock: {
                    return { child_lock: value ? "LOCK" : "UNLOCK" };
                }
                case dataPoints.x5hSetTemp: {
                    const setpoint = Number.parseFloat((value / 10).toFixed(1));
                    globalStore.putValue(msg.endpoint, "currentHeatingSetpoint", setpoint);
                    return { current_heating_setpoint: setpoint };
                }
                case dataPoints.x5hSetTempCeiling: {
                    return { upper_temp: value };
                }
                case dataPoints.x5hCurrentTemp: {
                    const temperature = value & (1 << 15) ? value - (1 << 16) + 1 : value;
                    return { local_temperature: Number.parseFloat((temperature / 10).toFixed(1)) };
                }
                case dataPoints.x5hTempCorrection: {
                    return { local_temperature_calibration: Number.parseFloat((value / 10).toFixed(1)) };
                }
                case dataPoints.x5hMode: {
                    const lookup = { 0: "manual", 1: "program" };
                    return { preset: lookup[value] };
                }
                case dataPoints.x5hSensorSelection: {
                    const lookup = { 0: "internal", 1: "external", 2: "both" };
                    return { sensor: lookup[value] };
                }
                case dataPoints.x5hOutputReverse: {
                    return { output_reverse: value };
                }
                default: {
                    logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:x5h_thermostat");
                }
            }
        },
    },
    zs_thermostat: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "zs_thermostat");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            const ret = {};
            const daysMap = { 1: "monday", 2: "tuesday", 3: "wednesday", 4: "thursday", 5: "friday", 6: "saturday", 7: "sunday" };
            const day = daysMap[value[0]];
            switch (dp) {
                case dataPoints.zsChildLock:
                    return { child_lock: value ? "LOCK" : "UNLOCK" };
                case dataPoints.zsHeatingSetpoint:
                    if (value === 0)
                        ret.system_mode = "off";
                    if (value === 60) {
                        ret.system_mode = "heat";
                        ret.preset = "boost";
                    }
                    ret.current_heating_setpoint = (value / 2).toFixed(1);
                    if (value > 0 && value < 60)
                        globalStore.putValue(msg.endpoint, "current_heating_setpoint", ret.current_heating_setpoint);
                    return ret;
                case dataPoints.zsHeatingSetpointAuto:
                    return { current_heating_setpoint_auto: (value / 2).toFixed(1) };
                case dataPoints.zsOpenwindowTemp:
                    return { detectwindow_temperature: (value / 2).toFixed(1) };
                case dataPoints.zsOpenwindowTime:
                    return { detectwindow_timeminute: value };
                case dataPoints.zsLocalTemp:
                    return { local_temperature: (value / 10).toFixed(1) };
                case dataPoints.zsBatteryVoltage:
                    return { voltage: Math.round(value * 10) };
                case dataPoints.zsTempCalibration:
                    return { local_temperature_calibration: value > 55 ? ((value - 0x100000000) / 10).toFixed(1) : (value / 10).toFixed(1) };
                case dataPoints.zsBinaryOne:
                    return { binary_one: value ? "ON" : "OFF" };
                case dataPoints.zsBinaryTwo:
                    return { binary_two: value ? "ON" : "OFF" };
                case dataPoints.zsComfortTemp:
                    return { comfort_temperature: (value / 2).toFixed(1) };
                case dataPoints.zsEcoTemp:
                    return { eco_temperature: (value / 2).toFixed(1) };
                // case dataPoints.zsAwayTemp:
                //     return {away_preset_temperature: (value / 2).toFixed(1)};
                case dataPoints.zsMode:
                    switch (value) {
                        case 1: // manual
                            return { system_mode: "heat", away_mode: "OFF", preset: "manual" };
                        case 2: // away
                            return { system_mode: "auto", away_mode: "ON", preset: "holiday" };
                        case 0: // auto
                            return { system_mode: "auto", away_mode: "OFF", preset: "schedule" };
                        default:
                            logger_1.logger.warning(`Preset ${value} is not recognized.`, "zhc:legacy:fz:zs_thermostat");
                            break;
                    }
                    break;
                case dataPoints.zsScheduleMonday:
                case dataPoints.zsScheduleTuesday:
                case dataPoints.zsScheduleWednesday:
                case dataPoints.zsScheduleThursday:
                case dataPoints.zsScheduleFriday:
                case dataPoints.zsScheduleSaturday:
                case dataPoints.zsScheduleSunday:
                    for (let i = 1; i <= 9; i++) {
                        const tempId = (i - 1) * 2 + 1;
                        const timeId = (i - 1) * 2 + 2;
                        ret[`${day}_temp_${i}`] = (value[tempId] / 2).toFixed(1);
                        if (i !== 9) {
                            ret[`${day}_hour_${i}`] = Math.floor(value[timeId] / 4)
                                .toString()
                                .padStart(2, "0");
                            ret[`${day}_minute_${i}`] = ((value[timeId] % 4) * 15).toString().padStart(2, "0");
                        }
                    }
                    return ret;
                case dataPoints.zsAwaySetting:
                    ret.away_preset_year = value[0];
                    ret.away_preset_month = value[1];
                    ret.away_preset_day = value[2];
                    ret.away_preset_hour = value[3];
                    ret.away_preset_minute = value[4];
                    ret.away_preset_temperature = (value[5] / 2).toFixed(1);
                    ret.away_preset_days = (value[6] << 8) + value[7];
                    return ret;
                default:
                    logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:zs_thermostat");
            }
        },
    },
    giexWaterValve: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            // @ts-expect-error ignore
            const modelConverters = giexFzModelConverters[model.model] || {};
            for (const dpValue of msg.data.dpValues) {
                const value = getDataValue(dpValue);
                const { dp } = dpValue;
                switch (dp) {
                    case dataPoints.giexWaterValve.state:
                        return { [giexWaterValve.state]: value ? ON : OFF };
                    case dataPoints.giexWaterValve.mode:
                        return { [giexWaterValve.mode]: value ? CAPACITY : DURATION };
                    case dataPoints.giexWaterValve.irrigationTarget:
                        return { [giexWaterValve.irrigationTarget]: value };
                    case dataPoints.giexWaterValve.cycleIrrigationNumTimes:
                        return { [giexWaterValve.cycleIrrigationNumTimes]: value };
                    case dataPoints.giexWaterValve.cycleIrrigationInterval:
                        return { [giexWaterValve.cycleIrrigationInterval]: value };
                    case dataPoints.giexWaterValve.waterConsumed:
                        return { [giexWaterValve.waterConsumed]: value };
                    case dataPoints.giexWaterValve.irrigationStartTime:
                        return { [giexWaterValve.irrigationStartTime]: modelConverters.time?.(value) || value };
                    case dataPoints.giexWaterValve.irrigationEndTime:
                        return { [giexWaterValve.irrigationEndTime]: modelConverters.time?.(value) || value };
                    case dataPoints.giexWaterValve.lastIrrigationDuration:
                        return { [giexWaterValve.lastIrrigationDuration]: value.split(",").shift() }; // Remove meaningless ,0 suffix
                    case dataPoints.giexWaterValve.battery:
                        return { [giexWaterValve.battery]: value };
                    case dataPoints.giexWaterValve.currentTemperature:
                        return; // Do Nothing - value ignored because it isn't a valid temperature reading (misdocumented and usage unclear)
                    default: // Unknown data point warning
                        logger_1.logger.debug(`Unrecognized DP #${dp} with VALUE = ${value}`, "legacy:fz:giex_water_valve");
                }
            }
        },
    },
    tuya_alecto_smoke: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "tuya_alecto_smoke");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            switch (dp) {
                case dataPoints.alectoSmokeState:
                    // @ts-expect-error ignore
                    return { smoke_state: { 0: "alarm", 1: "normal" }[value] };
                case dataPoints.alectoSmokeValue:
                    return { smoke_value: value };
                case dataPoints.alectoSelfChecking:
                    return { self_checking: value };
                case dataPoints.alectoCheckingResult:
                    // @ts-expect-error ignore
                    return { checking_result: { 0: "checking", 1: "check_success", 2: "check_failure", 3: "others" }[value] };
                case dataPoints.alectoSmokeTest:
                    return { smoke_test: value };
                case dataPoints.alectoLifecycle:
                    return { lifecycle: value };
                case dataPoints.alectoBatteryPercentage:
                    return { battery: value };
                case dataPoints.alectoBatteryState:
                    // @ts-expect-error ignore
                    return { battery_state: { 0: "low", 1: "middle", 2: "high" }[value] };
                case dataPoints.alectoSilence:
                    return { silence: value };
                default:
                    logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(msg.data)}`, "zhc:legacy:fz:tuya_alecto_smoke");
            }
        },
    },
    blitzwolf_occupancy_with_timeout: {
        cluster: "manuSpecificTuya",
        type: "commandDataResponse",
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "blitzwolf_occupancy_with_timeout");
            const newMsg = { ...msg, type: "attributeReport", data: { occupancy: dpValue.dp === dataPoints.occupancy ? 1 : 0 } };
            return fromZigbee_1.occupancy_with_timeout.convert(model, newMsg, publish, options, meta);
        },
    },
    moes_thermostat: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "moes_thermostat");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            const stateLookup = { "0": "cool", "1": "heat", "2": "fan_only" };
            // biome-ignore lint/suspicious/noImplicitAnyLet: ignored using `--suppress`
            let temperature;
            /* See tuyaThermostat above for message structure comment */
            switch (dp) {
                case dataPoints.moesSchedule:
                    return {
                        program: {
                            weekdays_p1_hour: value[0],
                            weekdays_p1_minute: value[1],
                            weekdays_p1_temperature: value[2] / 2,
                            weekdays_p2_hour: value[3],
                            weekdays_p2_minute: value[4],
                            weekdays_p2_temperature: value[5] / 2,
                            weekdays_p3_hour: value[6],
                            weekdays_p3_minute: value[7],
                            weekdays_p3_temperature: value[8] / 2,
                            weekdays_p4_hour: value[9],
                            weekdays_p4_minute: value[10],
                            weekdays_p4_temperature: value[11] / 2,
                            saturday_p1_hour: value[12],
                            saturday_p1_minute: value[13],
                            saturday_p1_temperature: value[14] / 2,
                            saturday_p2_hour: value[15],
                            saturday_p2_minute: value[16],
                            saturday_p2_temperature: value[17] / 2,
                            saturday_p3_hour: value[18],
                            saturday_p3_minute: value[19],
                            saturday_p3_temperature: value[20] / 2,
                            saturday_p4_hour: value[21],
                            saturday_p4_minute: value[22],
                            saturday_p4_temperature: value[23] / 2,
                            sunday_p1_hour: value[24],
                            sunday_p1_minute: value[25],
                            sunday_p1_temperature: value[26] / 2,
                            sunday_p2_hour: value[27],
                            sunday_p2_minute: value[28],
                            sunday_p2_temperature: value[29] / 2,
                            sunday_p3_hour: value[30],
                            sunday_p3_minute: value[31],
                            sunday_p3_temperature: value[32] / 2,
                            sunday_p4_hour: value[33],
                            sunday_p4_minute: value[34],
                            sunday_p4_temperature: value[35] / 2,
                        },
                    };
                case dataPoints.state: // Thermostat on standby = OFF, running = ON
                    if (model.model === "BAC-002-ALZB") {
                        if (!value) {
                            return { system_mode: "off" };
                        }
                        return;
                    }
                    return { system_mode: value ? "heat" : "off" };
                case dataPoints.tvMode:
                    if (model.model === "BAC-002-ALZB") {
                        return { system_mode: stateLookup[value] };
                    }
                    return { preset_mode: value ? "program" : "hold", preset: value ? "program" : "hold" };
                case dataPoints.moesChildLock:
                    return { child_lock: value ? "LOCK" : "UNLOCK" };
                case dataPoints.moesHeatingSetpoint:
                    if (["_TZE200_5toc8efa", "_TZE204_5toc8efa"].includes(meta.device.manufacturerName)) {
                        return { current_heating_setpoint: value / 10 };
                    }
                    return { current_heating_setpoint: value };
                case dataPoints.moesMinTempLimit:
                    if (["_TZE200_5toc8efa", "_TZE204_5toc8efa"].includes(meta.device.manufacturerName)) {
                        return { min_temperature_limit: value / 10 };
                    }
                    return { min_temperature_limit: value };
                case dataPoints.moesMaxTempLimit:
                    if (["_TZE200_5toc8efa", "_TZE204_5toc8efa"].includes(meta.device.manufacturerName)) {
                        return { max_temperature_limit: value / 10 };
                    }
                    return { max_temperature_limit: value };
                case dataPoints.moesMaxTemp:
                    if (["_TZE200_5toc8efa", "_TZE204_5toc8efa"].includes(meta.device.manufacturerName)) {
                        return { max_temperature: value / 10 };
                    }
                    return { max_temperature: value };
                case dataPoints.moesDeadZoneTemp:
                    if (["_TZE200_5toc8efa", "_TZE204_5toc8efa"].includes(meta.device.manufacturerName)) {
                        return { deadzone_temperature: value / 10 };
                    }
                    return { deadzone_temperature: value };
                case dataPoints.moesLocalTemp:
                    if (["_TZE200_5toc8efa", "_TZE204_5toc8efa"].includes(meta.device.manufacturerName)) {
                        temperature = value / 10;
                    }
                    else {
                        temperature = value & (1 << 15) ? value - (1 << 16) + 1 : value;
                        if (!["_TZE200_ztvwu4nk", "_TZE200_ye5jkfsb", "_TZE284_ye5jkfsb"].includes(meta.device.manufacturerName)) {
                            // https://github.com/Koenkk/zigbee2mqtt/issues/11980
                            temperature = temperature / 10;
                        }
                    }
                    temperature = Number.parseFloat(temperature.toFixed(1));
                    if (temperature < 100) {
                        return { local_temperature: Number.parseFloat(temperature.toFixed(1)) };
                    }
                    break;
                case dataPoints.moesTempCalibration:
                    temperature = value;
                    // for negative values produce complimentary hex (equivalent to negative values)
                    if (temperature > 4000)
                        temperature = temperature - 4096;
                    return { local_temperature_calibration: temperature };
                case dataPoints.moesScheduleEnable: // state is inverted, preset_mode is deprecated
                    return { preset_mode: value ? "hold" : "program", preset: value ? "hold" : "program" };
                case dataPoints.moesValve:
                    return { heat: value ? "OFF" : "ON", running_state: value ? "idle" : model.model === "BAC-002-ALZB" ? "cool" : "heat" };
                case dataPoints.moesSensor:
                    switch (value) {
                        case 0:
                            return { sensor: "IN" };
                        case 1:
                            return { sensor: "AL" };
                        case 2:
                            return { sensor: "OU" };
                        default:
                            return { sensor: "not_supported" };
                    }
                case dataPoints.bacFanMode:
                    return { fan_mode: fanModes[value] };
                default: // DataPoint 17 is unknown
                    logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:moes_bht_022");
            }
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    moesS_thermostat: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "moesS_thermostat");
            const dp = dpValue.dp; // First we get the data point ID
            const value = getDataValue(dpValue);
            const presetLookup = { 0: "programming", 1: "manual", 2: "temporary_manual", 3: "holiday" };
            switch (dp) {
                case dataPoints.moesSsystemMode:
                    // @ts-expect-error ignore
                    return { preset: presetLookup[value], system_mode: "heat" };
                case dataPoints.moesSheatingSetpoint:
                    return { current_heating_setpoint: value };
                case dataPoints.moesSlocalTemp:
                    return { local_temperature: value / 10 };
                case dataPoints.moesSboostHeating:
                    return { boost_heating: value ? "ON" : "OFF" };
                case dataPoints.moesSboostHeatingCountdown:
                    return { boost_heating_countdown: value };
                case dataPoints.moesSreset:
                    return { running_state: value ? "idle" : "heat", valve_state: value ? "CLOSED" : "OPEN" };
                case dataPoints.moesSwindowDetectionFunktion_A2:
                    return { window_detection: value ? "ON" : "OFF" };
                case dataPoints.moesSwindowDetection:
                    return { window: value ? "CLOSED" : "OPEN" };
                case dataPoints.moesSchildLock:
                    return { child_lock: value ? "LOCK" : "UNLOCK" };
                case dataPoints.moesSbattery:
                    return { battery: value };
                case dataPoints.moesSboostHeatingCountdownTimeSet:
                    return { boost_heating_countdown_time_set: value };
                case dataPoints.moesSvalvePosition:
                    return { position: value };
                case dataPoints.moesScompensationTempSet:
                    return {
                        local_temperature_calibration: value,
                        // local_temperature is now stale: the valve does not report the re-calibrated value until an actual temperature change
                        // so update local_temperature by subtracting the old calibration and adding the new one
                        ...(meta && meta.state && meta.state.local_temperature != null && meta.state.local_temperature_calibration != null
                            ? // @ts-expect-error ignore
                                { local_temperature: meta.state.local_temperature + (value - meta.state.local_temperature_calibration) }
                            : {}),
                    };
                case dataPoints.moesSecoMode:
                    return { eco_mode: value ? "ON" : "OFF" };
                case dataPoints.moesSecoModeTempSet:
                    return { eco_temperature: value };
                case dataPoints.moesSmaxTempSet:
                    return { max_temperature: value };
                case dataPoints.moesSminTempSet:
                    return { min_temperature: value };
                case dataPoints.moesSschedule: {
                    const items = [];
                    const pMode = [];
                    for (let i = 0; i < 12; i++) {
                        const item = { h: value[i * 3], m: value[i * 3 + 1], temp: value[i * 3 + 2] / 2 };
                        items[i] = item;
                        pMode[i] = `${item.h.toString().padStart(2, "0")}:${item.m.toString().padStart(2, "0")}/${item.temp}°C`;
                    }
                    return { programming_mode: pMode.join("  ") };
                }
                default:
                    logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:moes_s_thermostat");
            }
        },
    },
    tuya_air_quality: {
        cluster: "manuSpecificTuya",
        type: ["commandDataReport", "commandDataResponse"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "tuya_air_quality");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            switch (dp) {
                case dataPoints.tuyaSabTemp:
                    return { temperature: (value > 0x2000 ? value - 0xffff : value) / 10 };
                case dataPoints.tuyaSabHumidity:
                    return { humidity: value / 10 };
                // DP22: Smart Air Box: Formaldehyd, Smart Air Housekeeper: co2
                case dataPoints.tuyaSabFormaldehyd:
                    if (["_TZE200_dwcarsat", "_TZE200_ryfmq5rl", "_TZE200_mja3fuja", "_TZE204_dwcarsat"].includes(meta.device.manufacturerName)) {
                        return { co2: value };
                    }
                    return { formaldehyd: value };
                // DP2: Smart Air Box: co2, Smart Air Housekeeper: MP25
                case dataPoints.tuyaSabCO2:
                    if (["_TZE200_dwcarsat", "_TZE204_dwcarsat"].includes(meta.device.manufacturerName)) {
                        // Ignore: https://github.com/Koenkk/zigbee2mqtt/issues/11033#issuecomment-1109808552
                        // There are a lot of "strange" big values, so if the value is bigger than the range of the sensor, discard
                        // According to the manual of the device, the valid range is 0-1000 ug/m3
                        if (value > 1000)
                            return;
                        return { pm25: value };
                    }
                    if (meta.device.manufacturerName === "_TZE200_ryfmq5rl") {
                        return { formaldehyd: value / 100 };
                    }
                    if (meta.device.manufacturerName === "_TZE200_mja3fuja") {
                        return { formaldehyd: value };
                    }
                    return { co2: value };
                case dataPoints.tuyaSabVOC:
                    if (meta.device.manufacturerName === "_TZE200_ryfmq5rl") {
                        return { voc: value / 10 };
                    }
                    return { voc: value };
                case dataPoints.tuyaSahkFormaldehyd:
                    return { formaldehyd: value };
                default:
                    logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:tuya_air_quality");
            }
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    tuya_CO: {
        cluster: "manuSpecificTuya",
        type: ["commandDataReport", "commandDataResponse"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "tuya_CO");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            switch (dp) {
                case dataPoints.tuyaSabCO:
                    return { co: value / 100 };
                case dataPoints.tuyaSabCOalarm:
                    return { carbon_monoxide: value ? "OFF" : "ON" };
                default:
                    logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:tuya_co");
            }
        },
    },
    connecte_thermostat: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "connecte_thermostat");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            switch (dp) {
                case dataPoints.connecteState:
                    return { state: value ? "ON" : "OFF" };
                case dataPoints.connecteMode:
                    // biome-ignore lint/nursery/useExhaustiveSwitchCases: legacy
                    switch (value) {
                        case 0: // manual
                            return { system_mode: "heat", away_mode: "OFF" };
                        case 1: // home (auto)
                            return { system_mode: "auto", away_mode: "OFF" };
                        case 2: // away (auto)
                            return { system_mode: "auto", away_mode: "ON" };
                    }
                    break;
                case dataPoints.connecteHeatingSetpoint:
                    return { current_heating_setpoint: value };
                case dataPoints.connecteLocalTemp:
                    return { local_temperature: value };
                case dataPoints.connecteTempCalibration:
                    return { local_temperature_calibration: value };
                case dataPoints.connecteChildLock:
                    return { child_lock: value ? "LOCK" : "UNLOCK" };
                case dataPoints.connecteTempFloor:
                    return { external_temperature: value };
                case dataPoints.connecteSensorType:
                    // @ts-expect-error ignore
                    return { sensor: { 0: "internal", 1: "external", 2: "both" }[value] };
                case dataPoints.connecteHysteresis:
                    return { hysteresis: value };
                case dataPoints.connecteRunningState:
                    return { running_state: value ? "heat" : "idle" };
                case dataPoints.connecteTempProgram:
                    break;
                case dataPoints.connecteOpenWindow:
                    return { window_detection: value ? "ON" : "OFF" };
                case dataPoints.connecteMaxProtectTemp:
                    return { max_temperature_protection: value };
                default:
                    logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:connecte_thermostat");
            }
        },
    },
    saswell_thermostat: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "saswell_thermostat");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            switch (dp) {
                case dataPoints.saswellHeating:
                    // heating status 1 - heating
                    return { heating: value ? "ON" : "OFF", running_state: value ? "heat" : "idle" };
                case dataPoints.saswellWindowDetection:
                    return { window_detection: value ? "ON" : "OFF" };
                case dataPoints.saswellFrostDetection:
                    return { frost_detection: value ? "ON" : "OFF" };
                case dataPoints.saswellTempCalibration:
                    return { local_temperature_calibration: value > 6 ? 0xffffffff - value : value };
                case dataPoints.saswellChildLock:
                    return { child_lock: value ? "LOCK" : "UNLOCK" };
                case dataPoints.saswellState:
                    return { system_mode: value ? "heat" : "off" };
                case dataPoints.saswellLocalTemp:
                    return { local_temperature: Number.parseFloat((value / 10).toFixed(1)) };
                case dataPoints.saswellHeatingSetpoint:
                    return { current_heating_setpoint: Number.parseFloat((value / 10).toFixed(1)) };
                case dataPoints.saswellValvePos:
                    // single value 1-100%
                    break;
                case dataPoints.saswellBatteryLow:
                    return { battery_low: !!value };
                case dataPoints.saswellAwayMode:
                    if (value) {
                        return { away_mode: "ON", preset_mode: "away" };
                    }
                    return { away_mode: "OFF", preset_mode: "none" };
                case dataPoints.saswellScheduleMode:
                    if (thermostatScheduleMode[value] !== undefined) {
                        return { schedule_mode: thermostatScheduleMode[value] };
                    }
                    logger_1.logger.warning(`Unknown schedule mode ${value}`, "zhc:legacy:fz:saswell_thermostat");
                    break;
                case dataPoints.saswellScheduleEnable:
                    if (value) {
                        return { system_mode: "auto" };
                    }
                    break;
                case dataPoints.saswellScheduleSet:
                    // Never seen being reported, but put here to prevent warnings
                    break;
                case dataPoints.saswellSetpointHistoryDay:
                    // 24 values - 1 value for each hour
                    break;
                case dataPoints.saswellTimeSync:
                    // uint8: year - 2000
                    // uint8: month (1-12)
                    // uint8: day (1-21)
                    // uint8: hour (0-23)
                    // uint8: minute (0-59)
                    break;
                case dataPoints.saswellSetpointHistoryWeek:
                    // 7 values - 1 value for each day
                    break;
                case dataPoints.saswellSetpointHistoryMonth:
                    // 31 values - 1 value for each day
                    break;
                case dataPoints.saswellSetpointHistoryYear:
                    // 12 values - 1 value for each month
                    break;
                case dataPoints.saswellLocalHistoryDay:
                    // 24 values - 1 value for each hour
                    break;
                case dataPoints.saswellLocalHistoryWeek:
                    // 7 values - 1 value for each day
                    break;
                case dataPoints.saswellLocalHistoryMonth:
                    // 31 values - 1 value for each day
                    break;
                case dataPoints.saswellLocalHistoryYear:
                    // 12 values - 1 value for each month
                    break;
                case dataPoints.saswellMotorHistoryDay:
                    // 24 values - 1 value for each hour
                    break;
                case dataPoints.saswellMotorHistoryWeek:
                    // 7 values - 1 value for each day
                    break;
                case dataPoints.saswellMotorHistoryMonth:
                    // 31 values - 1 value for each day
                    break;
                case dataPoints.saswellMotorHistoryYear:
                    // 12 values - 1 value for each month
                    break;
                case dataPoints.saswellScheduleSunday:
                case dataPoints.saswellScheduleMonday:
                case dataPoints.saswellScheduleTuesday:
                case dataPoints.saswellScheduleWednesday:
                case dataPoints.saswellScheduleThursday:
                case dataPoints.saswellScheduleFriday:
                case dataPoints.saswellScheduleSaturday:
                    // Handled by tuya_thermostat_weekly_schedule
                    // Schedule for each day
                    // [
                    //     uint8: schedule mode - see above,
                    //     uint16: time (60 * hour + minute)
                    //     uint16: temperature * 10
                    //     uint16: time (60 * hour + minute)
                    //     uint16: temperature * 10
                    //     uint16: time (60 * hour + minute)
                    //     uint16: temperature * 10
                    //     uint16: time (60 * hour + minute)
                    //     uint16: temperature * 10
                    // ]
                    break;
                case dataPoints.saswellAntiScaling:
                    return { anti_scaling: value ? "ON" : "OFF" };
                default:
                    logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:saswell_thermostat");
            }
        },
    },
    evanell_thermostat: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const result = {};
            for (const dpValue of msg.data.dpValues) {
                const value = getDataValue(dpValue);
                switch (dpValue.dp) {
                    case dataPoints.evanellChildLock:
                        result.child_lock = value ? "LOCK" : "UNLOCK";
                        break;
                    case dataPoints.evanellBattery:
                        result.battery = value;
                        break;
                    case dataPoints.evanellHeatingSetpoint:
                        result.current_heating_setpoint = value / 10;
                        break;
                    case dataPoints.evanellLocalTemp:
                        result.local_temperature = value / 10;
                        break;
                    case dataPoints.evanellMode:
                        switch (value) {
                            case 0: // manual
                                result.system_mode = "auto";
                                break;
                            case 2: // away
                                result.system_mode = "heat";
                                break;
                            case 3: // auto
                                result.system_mode = "off";
                                break;
                            default:
                                logger_1.logger.warning(`Mode ${value} is not recognized.`, "zhc:legacy:fz:evanell_thermostat");
                                break;
                        }
                        break;
                    default:
                        logger_1.logger.debug(`Unrecognized DP #${dpValue.dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:evanell_thermostat");
                }
            }
            return result;
        },
    },
    etop_thermostat: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "etop_thermostat");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            if (dp >= 101 && dp <= 107)
                return; // handled by tuya_thermostat_weekly_schedule
            switch (dp) {
                case dataPoints.state: // on/off
                    return !value ? { system_mode: "off" } : {};
                case dataPoints.etopErrorStatus:
                    return {
                        high_temperature: (value & (1 << 0)) > 0 ? "ON" : "OFF",
                        low_temperature: (value & (1 << 1)) > 0 ? "ON" : "OFF",
                        internal_sensor_error: (value & (1 << 2)) > 0 ? "ON" : "OFF",
                        external_sensor_error: (value & (1 << 3)) > 0 ? "ON" : "OFF",
                        battery_low: (value & (1 << 4)) > 0,
                        device_offline: (value & (1 << 5)) > 0 ? "ON" : "OFF",
                    };
                case dataPoints.childLock:
                    return { child_lock: value ? "LOCK" : "UNLOCK" };
                case dataPoints.heatingSetpoint:
                    return { current_heating_setpoint: (value / 10).toFixed(1) };
                case dataPoints.localTemp:
                    return { local_temperature: (value / 10).toFixed(1) };
                case dataPoints.mode:
                    switch (value) {
                        case 0: // manual
                            return { system_mode: "heat", away_mode: "OFF", preset: "none" };
                        case 1: // away
                            return { system_mode: "heat", away_mode: "ON", preset: "away" };
                        case 2: // auto
                            return { system_mode: "auto", away_mode: "OFF", preset: "none" };
                        default:
                            logger_1.logger.warning(`Preset ${value} is not recognized.`, "zhc:legacy:fz:etop_thermostat");
                            break;
                    }
                    break;
                case dataPoints.runningState:
                    return { running_state: value ? "heat" : "idle" };
                default:
                    logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:etop_thermostat");
            }
        },
    },
    tuya_thermostat: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "tuya_thermostat");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            switch (dp) {
                case dataPoints.windowOpen:
                    return { window_open: value };
                case dataPoints.windowDetection:
                    return {
                        window_detection: value[0] ? "ON" : "OFF",
                        window_detection_params: {
                            temperature: value[1],
                            minutes: value[2],
                        },
                    };
                case dataPoints.scheduleWorkday: // set schedule for workdays/holidays [6,0,20,8,0,15,11,30,15,12,30,15,17,30,20,22,0,15]
                case dataPoints.scheduleHoliday: {
                    // 6:00 - 20*, 8:00 - 15*, 11:30 - 15*, 12:30 - 15*, 17:30 - 20*, 22:00 - 15*
                    // Top bits in hours have special meaning
                    // 6: Current schedule indicator
                    const items = [];
                    const programmingMode = [];
                    for (let i = 0; i < 6; i++) {
                        const item = { hour: value[i * 3] & 0x3f, minute: value[i * 3 + 1], temperature: value[i * 3 + 2] };
                        if (value[i * 3] & 0x40) {
                            item.current = true;
                        }
                        items[i] = item;
                        programmingMode[i] =
                            `${item.hour.toString().padStart(2, "0")}:${item.minute.toString().padStart(2, "0")}/${item.temperature}°C`;
                    }
                    if (dp === dataPoints.scheduleWorkday) {
                        return { workdays: items, workdays_schedule: programmingMode.join(" ") };
                    }
                    return { holidays: items, holidays_schedule: programmingMode.join(" ") };
                }
                case dataPoints.childLock:
                    return { child_lock: value ? "LOCK" : "UNLOCK" };
                case dataPoints.siterwellWindowDetection:
                    return { window_detection: value ? "ON" : "OFF" };
                case dataPoints.valveDetection:
                    return { valve_detection: value ? "ON" : "OFF" };
                case dataPoints.autoLock: // 0x7401 auto lock mode
                    return { auto_lock: value ? "AUTO" : "MANUAL" };
                case dataPoints.heatingSetpoint:
                    return { current_heating_setpoint: Number.parseFloat((value / 10).toFixed(1)) };
                case dataPoints.localTemp:
                    return { local_temperature: Number.parseFloat((value / 10).toFixed(1)) };
                case dataPoints.tempCalibration:
                    return { local_temperature_calibration: Number.parseFloat((value / 10).toFixed(1)) };
                case dataPoints.battery: // 0x1502 MCU reporting battery status
                    return { battery: value };
                case dataPoints.batteryLow:
                    return { battery_low: value };
                case dataPoints.minTemp:
                    return { min_temperature: value };
                case dataPoints.maxTemp:
                    return { max_temperature: value };
                case dataPoints.boostTime: // 0x6902 boost time
                    return { boost_time: value };
                case dataPoints.comfortTemp:
                    return { comfort_temperature: value };
                case dataPoints.ecoTemp:
                    return { eco_temperature: value };
                case dataPoints.valvePos:
                    return { position: value, running_state: value ? "heat" : "idle" };
                case dataPoints.awayTemp:
                    return { away_preset_temperature: value };
                case dataPoints.awayDays:
                    return { away_preset_days: value };
                case dataPoints.mode: {
                    const ret = {};
                    const presetOk = getMetaValue(msg.endpoint, model, "tuyaThermostatPreset")[value] !== undefined;
                    if (presetOk) {
                        ret.preset = getMetaValue(msg.endpoint, model, "tuyaThermostatPreset")[value];
                        ret.away_mode = ret.preset === "away" ? "ON" : "OFF"; // Away is special HA mode
                        const presetToSystemMode = utils.getMetaValue(msg.endpoint, model, "tuyaThermostatPresetToSystemMode", null, {});
                        if (value in presetToSystemMode) {
                            // @ts-expect-error ignore
                            ret.system_mode = presetToSystemMode[value];
                        }
                    }
                    else {
                        logger_1.logger.warning(`TRV preset ${value} is not recognized.`, "zhc:legacy:fz:tuya_thermostat");
                        return;
                    }
                    return ret;
                }
                // fan mode 0 - low , 1 - medium , 2 - high , 3 - auto ( tested on 6dfgetq TUYA zigbee module )
                case dataPoints.fanMode:
                    return { fan_mode: fanModes[value] };
                case dataPoints.forceMode: // force mode 0 - normal, 1 - open, 2 - close
                    return { system_mode: thermostatSystemModes3[value], force: thermostatForceMode[value] };
                case dataPoints.weekFormat: // Week select 0 - 5 days, 1 - 6 days, 2 - 7 days
                    return { week: thermostatWeekFormat[value] };
                default: // The purpose of the dps 17 & 19 is still unknown
                    logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:tuya_thermostat");
            }
        },
    },
    tuya_dimmer: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "tuya_dimmer");
            const value = getDataValue(dpValue);
            if (dpValue.dp === dataPoints.state) {
                return { state: value ? "ON" : "OFF" };
            }
            if (meta.device.manufacturerName === "_TZE200_swaamsoy") {
                // https://github.com/Koenkk/zigbee-herdsman-converters/pull/3004
                if (dpValue.dp === 2) {
                    if (value < 10) {
                        logUnexpectedDataValue("tuya_dimmer", msg, dpValue, meta, "brightness", 10, 1000);
                    }
                    return { brightness: utils.mapNumberRange(value, 10, 1000, 0, 254) };
                }
            }
            else if (["_TZE200_3p5ydos3", "_TZE200_9i9dt8is", "_TZE200_dfxkcots", "_TZE200_w4cryh2i"].includes(meta.device.manufacturerName)) {
                if (dpValue.dp === dataPoints.eardaDimmerLevel) {
                    return { brightness: utils.mapNumberRange(value, 0, 1000, 0, 254) };
                }
                if (dpValue.dp === dataPoints.dimmerMinLevel) {
                    return { min_brightness: utils.mapNumberRange(value, 0, 1000, 1, 255) };
                }
                if (dpValue.dp === dataPoints.dimmerMaxLevel) {
                    return { max_brightness: utils.mapNumberRange(value, 0, 1000, 1, 255) };
                }
                logUnexpectedDataPoint("tuya_dimmer", msg, dpValue, meta);
            }
            else {
                if (dpValue.dp !== dataPoints.dimmerLevel) {
                    logUnexpectedDataPoint("tuya_dimmer", msg, dpValue, meta);
                }
                if (dpValue.datatype !== dataTypes.value) {
                    logUnexpectedDataType("tuya_dimmer", msg, dpValue, meta);
                }
                else {
                    if (value < 10) {
                        logUnexpectedDataValue("tuya_dimmer", msg, dpValue, meta, "brightness", 10, 1000);
                    }
                    return { brightness: utils.mapNumberRange(value, 10, 1000, 0, 254), level: value };
                }
            }
        },
    },
    tuya_motion_sensor: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "tuya_motion_sensor");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            let result = null;
            switch (dp) {
                case dataPoints.state:
                    // @ts-expect-error ignore
                    result = { occupancy: { 1: true, 0: false }[value] };
                    break;
                case dataPoints.msReferenceLuminance:
                    result = { reference_luminance: value };
                    break;
                case dataPoints.msOSensitivity:
                    result = { o_sensitivity: msLookups.OSensitivity[value] };
                    break;
                case dataPoints.msVSensitivity:
                    result = { v_sensitivity: msLookups.VSensitivity[value] };
                    break;
                case dataPoints.msLedStatus:
                    // @ts-expect-error ignore
                    result = { led_status: { 1: "OFF", 0: "ON" }[value] };
                    break;
                case dataPoints.msVacancyDelay:
                    result = { vacancy_delay: value };
                    break;
                case dataPoints.msLightOnLuminancePrefer:
                    result = { light_on_luminance_prefer: value };
                    break;
                case dataPoints.msLightOffLuminancePrefer:
                    result = { light_off_luminance_prefer: value };
                    break;
                case dataPoints.msMode:
                    result = { mode: msLookups.Mode[value] };
                    break;
                case dataPoints.msVacantConfirmTime:
                    result = { vacant_confirm_time: value };
                    break;
                case dataPoints.msLuminanceLevel:
                    result = { luminance_level: value };
                    break;
                default:
                    logger_1.logger.debug(`Unrecognized DP ${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:tuya_motion_sensor");
            }
            return result;
        },
    },
    tuya_smart_vibration_sensor: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const result = {};
            for (const dpValue of msg.data.dpValues) {
                const value = getDataValue(dpValue);
                switch (dpValue.dp) {
                    case dataPoints.state:
                        result.contact = !value;
                        break;
                    case dataPoints.thitBatteryPercentage:
                        result.battery = value;
                        break;
                    case dataPoints.tuyaVibration:
                        result.vibration = Boolean(value);
                        break;
                    default:
                        logger_1.logger.debug(`Unrecognized DP #${dpValue.dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:tuya_smart_vibration_sensor");
                }
            }
            return result;
        },
    },
    matsee_garage_door_opener: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const result = {};
            for (const dpValue of msg.data.dpValues) {
                const value = getDataValue(dpValue);
                switch (dpValue.dp) {
                    case dataPoints.garageDoorTrigger:
                        result.action = "trigger";
                        break;
                    case dataPoints.garageDoorContact:
                        result.garage_door_contact = Boolean(!value);
                        break;
                    case dataPoints.garageDoorStatus:
                        // This reports a garage door status (open, closed), but it is very naive and misleading
                        break;
                    default:
                        logger_1.logger.debug(`Unrecognized DP #${dpValue.dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:matsee_garage_door_opener");
                }
            }
            return result;
        },
    },
    moes_thermostat_tv: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport", "raw"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "moes_thermostat_tv");
            const dp = dpValue.dp;
            let value = getDataValue(dpValue);
            let result = null;
            switch (dp) {
                case dataPoints.tvMode:
                    switch (value) {
                        case 1: // manual
                            result = { system_mode: "heat", preset: "manual" };
                            break;
                        case 2: // holiday
                            result = { system_mode: "heat", preset: "holiday" };
                            break;
                        case 0: // auto
                            result = { system_mode: "auto", preset: "schedule" };
                            break;
                        default:
                            logger_1.logger.warning(`Preset ${value} is not recognized.`, "zhc:legacy:fz:moes_thermostat_tv");
                            break;
                    }
                    break;
                case dataPoints.tvWindowDetection:
                    // @ts-expect-error ignore
                    result = { window_detection: { 1: true, 0: false }[value] };
                    break;
                case dataPoints.tvFrostDetection:
                    // @ts-expect-error ignore
                    result = { frost_detection: { 1: true, 0: false }[value] };
                    break;
                case dataPoints.tvHeatingSetpoint:
                    result = { current_heating_setpoint: (value / 10).toFixed(1) };
                    break;
                case dataPoints.tvLocalTemp:
                    result = { local_temperature: (value / 10).toFixed(1) };
                    break;
                case dataPoints.tvTempCalibration:
                    value = value > 0x7fffffff ? 0xffffffff - value : value;
                    result = { local_temperature_calibration: (value / 10).toFixed(1) };
                    break;
                case dataPoints.tvHolidayTemp:
                    result = { holiday_temperature: (value / 10).toFixed(1) };
                    break;
                case dataPoints.tvBattery:
                    result = { battery: value };
                    break;
                case dataPoints.tvChildLock:
                    // @ts-expect-error ignore
                    result = { child_lock: { 1: "LOCK", 0: "UNLOCK" }[value] };
                    break;
                case dataPoints.tvErrorStatus:
                    result = { error: value };
                    break;
                case dataPoints.tvHolidayMode:
                    result = { holiday_mode: value };
                    break;
                // case dataPoints.tvBoostMode:
                //     result = {boost_mode: {1: false, 0: true}[value]};
                //     break;
                case dataPoints.tvBoostTime:
                    result = { boost_heating_countdown: value };
                    break;
                case dataPoints.tvOpenWindowTemp:
                    result = { open_window_temperature: (value / 10).toFixed(1) };
                    break;
                case dataPoints.tvComfortTemp:
                    result = { comfort_temperature: (value / 10).toFixed(1) };
                    break;
                case dataPoints.tvEcoTemp:
                    result = { eco_temperature: (value / 10).toFixed(1) };
                    break;
                case dataPoints.tvHeatingStop:
                    if (value === 1) {
                        result = { system_mode: "off", heating_stop: true };
                    }
                    else {
                        result = { heating_stop: false };
                    }
                    break;
                default:
                    logger_1.logger.debug(`Unrecognized DP ${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:moes_thermostat_tv");
            }
            return result;
        },
    },
    hoch_din: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "hoch_din");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            const result = {};
            logger_1.logger.debug(`dp=[${dp}], datatype=[${dpValue.datatype}], value=[${value}]`, "zhc:legacy:fz::hoch_din");
            if (dp === dataPoints.state) {
                result.state = value ? "ON" : "OFF";
                if (value) {
                    result.trip = "clear";
                }
            }
            if (dp === dataPoints.hochChildLock) {
                result.child_lock = value ? "ON" : "OFF";
            }
            if (dp === dataPoints.hochVoltage) {
                result.voltage = (value[1] | (value[0] << 8)) / 10;
            }
            if (dp === dataPoints.hochHistoricalVoltage) {
                result.voltage_rms = (value[1] | (value[0] << 8)) / 10;
            }
            if (dp === dataPoints.hochCurrent) {
                result.current = (value[2] | (value[1] << 8)) / 1000;
            }
            if (dp === dataPoints.hochHistoricalCurrent) {
                result.current_average = (value[2] | (value[1] << 8)) / 1000;
            }
            if (dp === dataPoints.hochActivePower) {
                result.power = (value[2] | (value[1] << 8)) / 10;
                if (value.length > 3) {
                    result.power_l1 = (value[5] | (value[4] << 8)) / 10;
                }
                if (value.length > 6) {
                    result.power_l2 = (value[8] | (value[7] << 8)) / 10;
                }
                if (value.length > 9) {
                    result.power_l3 = (value[11] | (value[10] << 8)) / 10;
                }
            }
            if (dp === dataPoints.hochTotalActivePower) {
                result.energy_consumed = value / 100;
                result.energy = result.energy_consumed;
            }
            if (dp === dataPoints.hochLocking) {
                result.trip = value ? "trip" : "clear";
            }
            if (dp === dataPoints.hochCountdownTimer) {
                result.countdown_timer = value;
            }
            if (dp === dataPoints.hochTemperature) {
                result.temperature = value;
            }
            if (dp === dataPoints.hochRelayStatus) {
                const lookup = {
                    0: "off",
                    1: "on",
                    2: "previous",
                };
                result.power_on_behavior = lookup[value];
            }
            if (dp === dataPoints.hochFaultCode) {
                const lookup = {
                    0: "clear",
                    1: "over voltage threshold",
                    2: "under voltage threshold",
                    4: "over current threshold",
                    8: "over temperature threshold",
                    10: "over leakage current threshold",
                    16: "trip test",
                    128: "safety lock",
                };
                result.alarm = lookup[value];
            }
            if (dp === dataPoints.hochEquipmentNumberType) {
                result.meter_number = value.trim();
            }
            if (dp === dataPoints.hochVoltageThreshold) {
                result.over_voltage_threshold = (value[1] | (value[0] << 8)) / 10;
                result.over_voltage_trip = value[2] ? "ON" : "OFF";
                result.over_voltage_alarm = value[3] ? "ON" : "OFF";
                result.under_voltage_threshold = (value[5] | (value[4] << 8)) / 10;
                result.under_voltage_trip = value[6] ? "ON" : "OFF";
                result.under_voltage_alarm = value[7] ? "ON" : "OFF";
            }
            if (dp === dataPoints.hochCurrentThreshold) {
                let overCurrentValue = 0;
                for (let i = 0; i < 3; i++) {
                    overCurrentValue = overCurrentValue << 8;
                    overCurrentValue += value[i];
                }
                result.over_current_threshold = overCurrentValue / 1000;
                result.over_current_trip = value[3] ? "ON" : "OFF";
                result.over_current_alarm = value[4] ? "ON" : "OFF";
            }
            if (dp === dataPoints.hochTemperatureThreshold) {
                result.over_temperature_threshold = value[0] > 127 ? (value[0] - 128) * -1 : value[0];
                result.over_temperature_trip = value[1] ? "ON" : "OFF";
                result.over_temperature_alarm = value[2] ? "ON" : "OFF";
            }
            if (dp === dataPoints.hochLeakageParameters) {
                result.self_test_auto_days = value[0];
                result.self_test_auto_hours = value[1];
                result.self_test_auto = value[2] ? "ON" : "OFF";
                result.over_leakage_current_threshold = value[4] | (value[3] << 8);
                result.over_leakage_current_trip = value[5] ? "ON" : "OFF";
                result.over_leakage_current_alarm = value[6] ? "ON" : "OFF";
                result.self_test = value[7] ? "test" : "clear";
            }
            return result;
        },
    },
    tuya_light_wz5: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const separateWhite = model.meta?.separateWhite;
            const result = {};
            for (const dpValue of msg.data.dpValues.values()) {
                const dp = dpValue.dp;
                const value = getDataValue(dpValue);
                if (dp === dataPoints.state) {
                    result.state = value ? "ON" : "OFF";
                }
                else if (dp === dataPoints.silvercrestSetBrightness) {
                    const brightness = utils.mapNumberRange(value, 0, 1000, 0, 255);
                    if (separateWhite) {
                        result.white_brightness = brightness;
                    }
                    else {
                        result.brightness = brightness;
                    }
                }
                else if (dp === dataPoints.silvercrestSetColor) {
                    const h = Number.parseInt(value.substring(0, 4), 16);
                    const s = Number.parseInt(value.substring(4, 8), 16);
                    const b = Number.parseInt(value.substring(8, 12), 16);
                    result.color_mode = "hs";
                    result.color = { hue: h, saturation: utils.mapNumberRange(s, 0, 1000, 0, 100) };
                    result.brightness = utils.mapNumberRange(b, 0, 1000, 0, 255);
                }
                else if (dp === dataPoints.silvercrestSetColorTemp) {
                    const [colorTempMin, colorTempMax] = [250, 454];
                    result.color_temp = utils.mapNumberRange(value, 0, 1000, colorTempMax, colorTempMin);
                }
            }
            return result;
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    ZMAM02_cover: {
        cluster: "manuSpecificTuya",
        type: ["commandDataReport", "commandDataResponse"],
        options: [exposes.options.invert_cover()],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "ZMAM02_cover");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            switch (dp) {
                case dataPoints.coverPosition: // Started moving to position (triggered from Zigbee)
                case dataPoints.coverArrived: {
                    // Arrived at position
                    const running = dp !== dataPoints.coverArrived;
                    const invert = isCoverInverted(meta.device.manufacturerName) ? !options.invert_cover : options.invert_cover;
                    const position = invert ? 100 - (value & 0xff) : value & 0xff;
                    if (position > 0 && position <= 100) {
                        return { running, position, state: "OPEN" };
                    }
                    if (position === 0) {
                        // Report fully closed
                        return { running, position, state: "CLOSE" };
                    }
                    return { running }; // Not calibrated yet, no position is available
                }
                case dataPoints.coverSpeed: // Cover is reporting its current speed setting
                    return { motor_speed: value };
                case dataPoints.state: // Ignore the cover state, it's not reliable between different covers!
                case dataPoints.coverChange: // Ignore manual cover change, it's not reliable between different covers!
                    break;
                case dataPoints.config: // Returned by configuration set; ignore
                    break;
                case dataPoints.AM02MotorWorkingMode:
                    switch (value) {
                        case 0: // continuous 1
                            return { motor_working_mode: "continuous" };
                        case 1: // intermittently
                            return { motor_working_mode: "intermittently" };
                        default:
                            logger_1.logger.warning(`Mode ${value} is not recognized.`, "zhc:legacy:fz:zmam02_cover");
                            break;
                    }
                    break;
                case dataPoints.AM02Border:
                    switch (value) {
                        case 0: // up
                            return { border: "up" };
                        case 1: // down
                            return { border: "down" };
                        case 2: // down_delete
                            return { border: "down_delete" };
                        default:
                            logger_1.logger.warning(`Mode ${value} is not recognized.`, "zhc:legacy:fz:zmam02_cover");
                            break;
                    }
                    break;
                case dataPoints.AM02Direction:
                    switch (value) {
                        case 0:
                            return { motor_direction: "forward" };
                        case 1:
                            return { motor_direction: "back" };
                        default:
                            logger_1.logger.warning(`Mode ${value} is not recognized.`, "zhc:legacy:fz:zmam02_cover");
                            break;
                    }
                    break;
                case dataPoints.AM02Mode:
                    switch (value) {
                        case 0: // morning
                            return { mode: "morning" };
                        case 1: // night
                            return { mode: "night" };
                        default:
                            logger_1.logger.warning(`Mode ${value} is not recognized.`, "zhc:legacy:fz:zmam02_cover");
                            break;
                    }
                    break;
                case dataPoints.AM02AddRemoter: // DP 101: Ignore until need is defined
                case dataPoints.AM02TimeTotal: // DP 10: Ignore until need is defined
                    break;
                default: // Unknown code
                    logger_1.logger.debug(`Unrecognized DP #${dp} ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:zmam02_cover");
            }
        },
    },
    tm081: {
        cluster: "manuSpecificTuya",
        type: ["commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "tm0801");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            if (dp === 1)
                return { contact: value !== true };
            if (dp === 2)
                return { battery: value };
            logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:tm081");
        },
    },
    tuya_remote: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const result = {};
            const clickMapping = { 0: "single", 1: "double", 2: "hold" };
            const buttonMapping = { 1: "1", 2: "2", 3: "3", 4: "4", 5: "5", 6: "6" };
            for (const dpValue of msg.data.dpValues) {
                const value = getDataValue(dpValue);
                // battery DP
                if (dpValue.dp === 10) {
                    result.battery = value;
                }
                else {
                    result.action = `${buttonMapping[dpValue.dp]}_${clickMapping[value]}`;
                }
            }
            return result;
        },
    },
    tuya_smart_human_presense_sensor: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "tuya_smart_human_presense_sensor");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            let result = null;
            switch (dp) {
                case dataPoints.tshpsPresenceState:
                    // @ts-expect-error ignore
                    result = { presence: { 0: false, 1: true }[value] };
                    break;
                case dataPoints.tshpscSensitivity:
                    result = { radar_sensitivity: value };
                    break;
                case dataPoints.tshpsMinimumRange:
                    result = { minimum_range: value / 100 };
                    break;
                case dataPoints.tshpsMaximumRange:
                    result = { maximum_range: value / 100 };
                    break;
                case dataPoints.tshpsTargetDistance:
                    result = { target_distance: value / 100 };
                    break;
                case dataPoints.tshpsDetectionDelay:
                    result = { detection_delay: value / 10 };
                    break;
                case dataPoints.tshpsFadingTime:
                    result = { fading_time: value / 10 };
                    break;
                case dataPoints.tshpsIlluminanceLux:
                    result = { illuminance: value };
                    break;
                case dataPoints.tshpsCLI: // not recognize
                    result = { cli: value };
                    break;
                case dataPoints.tshpsSelfTest:
                    result = { self_test: tuyaHPSCheckingResult[value] };
                    break;
                default:
                    logger_1.logger.debug(`Unrecognized DP ${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:tuya_smart_human_presense_sensor");
            }
            return result;
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    ZG204ZL_lms: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const result = {};
            for (const dpValue of msg.data.dpValues) {
                const dp = dpValue.dp;
                const value = getDataValue(dpValue);
                switch (dp) {
                    case dataPoints.lmsState:
                        result.occupancy = value === 0;
                        break;
                    case dataPoints.lmsBattery:
                        result.battery = value;
                        break;
                    case dataPoints.lmsSensitivity:
                        // @ts-expect-error ignore
                        result.sensitivity = { "0": "low", "1": "medium", "2": "high" }[value];
                        break;
                    case dataPoints.lmsKeepTime:
                        // @ts-expect-error ignore
                        result.keep_time = { "0": "10", "1": "30", "2": "60", "3": "120" }[value];
                        break;
                    case dataPoints.lmsIlluminance:
                        result.illuminance = value;
                        break;
                    default:
                        logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:zg204zl_lms");
                }
            }
            return result;
        },
    },
    moes_cover: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        options: [exposes.options.invert_cover()],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "moes_cover");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            let result = null;
            let fixedValue = null;
            switch (dp) {
                case dataPoints.coverPosition: {
                    // https://github.com/Koenkk/zigbee-herdsman-converters/pull/11408
                    if (value >= 100 && value <= 127) {
                        fixedValue = 100;
                    }
                    else if (value > 127 || value < 0) {
                        fixedValue = 0;
                    }
                    else {
                        fixedValue = value;
                    }
                    const invert = !isCoverInverted(meta.device.manufacturerName) ? !options.invert_cover : options.invert_cover;
                    const position = invert ? 100 - fixedValue : fixedValue;
                    result = { position: position };
                    break;
                }
                case dataPoints.state:
                    // @ts-expect-error ignore
                    result = { state: { 0: "OPEN", 1: "STOP", 2: "CLOSE" }[value], running: { 0: true, 1: false, 2: true }[value] };
                    break;
                case dataPoints.moesCoverBacklight:
                    result = { backlight: value ? "ON" : "OFF" };
                    break;
                case dataPoints.moesCoverCalibration:
                    // @ts-expect-error ignore
                    result = { calibration: { 0: "ON", 1: "OFF" }[value] };
                    break;
                case dataPoints.moesCoverMotorReversal:
                    // @ts-expect-error ignore
                    result = { motor_reversal: { 0: "OFF", 1: "ON" }[value] };
                    break;
                default:
                    logger_1.logger.debug(`Unrecognized DP ${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:moes_cover");
            }
            return result;
        },
    },
    tuya_temperature_humidity_sensor: {
        cluster: "manuSpecificTuya",
        type: ["commandDataReport", "commandDataResponse"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "tuya_temperature_humidity_sensor");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            switch (dp) {
                case dataPoints.tthTemperature:
                    return { temperature: (value > 0x2000 ? value - 0xffff : value) / 10 };
                case dataPoints.tthHumidity:
                    return { humidity: value / (["_TZE200_bjawzodf", "_TZE200_zl1kmjqx"].includes(meta.device.manufacturerName) ? 10 : 1) };
                case dataPoints.tthBatteryLevel:
                    return {
                        // @ts-expect-error ignore
                        battery_level: { 0: "low", 1: "middle", 2: "high" }[value],
                        battery_low: value === 0,
                    };
                case dataPoints.tthBattery:
                    return { battery: value };
                default:
                    logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:tuya_temperature_humidity_sensor");
            }
        },
    },
    nous_lcd_temperature_humidity_sensor: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const result = {};
            for (const dpValue of msg.data.dpValues) {
                const dp = dpValue.dp;
                const value = getDataValue(dpValue);
                switch (dp) {
                    case dataPoints.nousTemperature:
                        result.temperature = value / 10;
                        break;
                    case dataPoints.nousHumidity:
                        result.humidity = value;
                        break;
                    case dataPoints.nousBattery:
                        result.battery = value;
                        break;
                    case dataPoints.nousTempUnitConvert:
                        // @ts-expect-error ignore
                        result.temperature_unit_convert = { 0: "celsius", 1: "fahrenheit" }[value];
                        break;
                    case dataPoints.nousMaxTemp:
                        result.max_temperature = value / 10;
                        break;
                    case dataPoints.nousMinTemp:
                        result.min_temperature = value / 10;
                        break;
                    case dataPoints.nousMaxHumi:
                        result.max_humidity = value;
                        break;
                    case dataPoints.nousMinHumi:
                        result.min_humidity = value;
                        break;
                    case dataPoints.nousTempAlarm:
                        // @ts-expect-error ignore
                        result.temperature_alarm = { 0: "lower_alarm", 1: "upper_alarm", 2: "canceled" }[value];
                        break;
                    case dataPoints.nousHumiAlarm:
                        // @ts-expect-error ignore
                        result.humidity_alarm = { 0: "lower_alarm", 1: "upper_alarm", 2: "canceled" }[value];
                        break;
                    case dataPoints.nousTempSensitivity:
                        result.temperature_sensitivity = value / 10;
                        break;
                    case dataPoints.nousHumiSensitivity:
                        result.humidity_sensitivity = value;
                        break;
                    case dataPoints.nousTempReportInterval:
                        result.temperature_report_interval = value;
                        break;
                    case dataPoints.nousHumiReportInterval:
                        result.humidity_report_interval = value;
                        break;
                    default:
                        logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:nous_lcd_temperature_humidity_sensor");
                }
            }
            return result;
        },
    },
    tuya_illuminance_temperature_humidity_sensor: {
        cluster: "manuSpecificTuya",
        type: ["commandDataReport", "commandDataResponse"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "tuya_illuminance_temperature_humidity_sensor");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            switch (dp) {
                case dataPoints.thitTemperature:
                    return { temperature: value / 10 };
                case dataPoints.thitHumidity:
                    return { humidity: value };
                case dataPoints.thitBatteryPercentage:
                    return { battery: value };
                case dataPoints.thitIlluminanceLux:
                    return { illuminance: value };
                default:
                    logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:tuya_illuminance_temperature_humidity_sensor");
            }
        },
    },
    tuya_illuminance_sensor: {
        cluster: "manuSpecificTuya",
        type: ["commandDataReport", "commandDataResponse"],
        convert: (() => {
            const brightnessState = {
                0: "low",
                1: "middle",
                2: "high",
                3: "strong",
            };
            return (model, msg, publish, options, meta) => {
                const dpValue = firstDpValue(msg, meta, "tuya_illuminance_sensor");
                const dp = dpValue.dp;
                const value = getDataValue(dpValue);
                switch (dp) {
                    case dataPoints.state:
                        return { brightness_state: brightnessState[value] };
                    case dataPoints.tIlluminanceLux:
                        return { illuminance: value };
                    default:
                        logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:tuya_illuminance_sensor");
                }
            };
        })(),
    },
    hy_thermostat: {
        cluster: "manuSpecificTuya",
        type: ["commandDataReport", "commandDataResponse"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "hy_thermostat");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            switch (dp) {
                case dataPoints.hyWorkdaySchedule1: // schedule for workdays [5,9,12,8,0,15,10,0,15]
                    return {
                        workdays: [
                            { hour: value[0], minute: value[1], temperature: value[2] },
                            { hour: value[3], minute: value[4], temperature: value[5] },
                            { hour: value[6], minute: value[7], temperature: value[8] },
                        ],
                        range: "am",
                    };
                case dataPoints.hyWorkdaySchedule2: // schedule for workdays [15,0,25,145,2,17,22,50,14]
                    return {
                        workdays: [
                            { hour: value[0], minute: value[1], temperature: value[2] },
                            { hour: value[3], minute: value[4], temperature: value[5] },
                            { hour: value[6], minute: value[7], temperature: value[8] },
                        ],
                        range: "pm",
                    };
                case dataPoints.hyHolidaySchedule1: // schedule for holidays [5,5,20,8,4,13,11,30,15]
                    return {
                        holidays: [
                            { hour: value[0], minute: value[1], temperature: value[2] },
                            { hour: value[3], minute: value[4], temperature: value[5] },
                            { hour: value[6], minute: value[7], temperature: value[8] },
                        ],
                        range: "am",
                    };
                case dataPoints.hyHolidaySchedule2: // schedule for holidays [13,30,15,17,0,15,22,0,15]
                    return {
                        holidays: [
                            { hour: value[0], minute: value[1], temperature: value[2] },
                            { hour: value[3], minute: value[4], temperature: value[5] },
                            { hour: value[6], minute: value[7], temperature: value[8] },
                        ],
                        range: "pm",
                    };
                case dataPoints.hyHeating: // heating
                    return { heating: value ? "ON" : "OFF" };
                case dataPoints.hyMaxTempProtection: // max temperature protection
                    return { max_temperature_protection: value ? "ON" : "OFF" };
                case dataPoints.hyMinTempProtection: // min temperature protection
                    return { min_temperature_protection: value ? "ON" : "OFF" };
                case dataPoints.hyState: // 0x017D work state
                    return { state: value ? "ON" : "OFF" };
                case dataPoints.hyChildLock: // 0x0181 Changed child lock status
                    return { child_lock: value ? "LOCK" : "UNLOCK" };
                case dataPoints.hyExternalTemp: // external sensor temperature
                    return { external_temperature: (value / 10).toFixed(1) };
                case dataPoints.hyAwayDays: // away preset days
                    return { away_preset_days: value };
                case dataPoints.hyAwayTemp: // away preset temperature
                    return { away_preset_temperature: value };
                case dataPoints.hyTempCalibration: // 0x026D Temperature correction
                    return { local_temperature_calibration: (value / 10).toFixed(1) };
                case dataPoints.hyHysteresis: // 0x026E Temperature hysteresis
                    return { hysteresis: (value / 10).toFixed(1) };
                case dataPoints.hyProtectionHysteresis: // 0x026F Temperature protection hysteresis
                    return { hysteresis_for_protection: value };
                case dataPoints.hyProtectionMaxTemp: // 0x027A max temperature for protection
                    return { max_temperature_for_protection: value };
                case dataPoints.hyProtectionMinTemp: // 0x027B min temperature for protection
                    return { min_temperature_for_protection: value };
                case dataPoints.hyMaxTemp: // 0x027C max temperature limit
                    return { max_temperature: value };
                case dataPoints.hyMinTemp: // 0x027D min temperature limit
                    return { min_temperature: value };
                case dataPoints.hyHeatingSetpoint: // 0x027E Changed target temperature
                    return { current_heating_setpoint: (value / 10).toFixed(1) };
                case dataPoints.hyLocalTemp: // 0x027F MCU reporting room temperature
                    return { local_temperature: (value / 10).toFixed(1) };
                case dataPoints.hySensor: // Sensor type
                    // @ts-expect-error ignore
                    return { sensor_type: { 0: "internal", 1: "external", 2: "both" }[value] };
                case dataPoints.hyPowerOnBehavior: // 0x0475 State after power on
                    // @ts-expect-error ignore
                    return { power_on_behavior: { 0: "restore", 1: "off", 2: "on" }[value] };
                case dataPoints.hyWeekFormat: // 0x0476 Week select 0 - 5 days, 1 - 6 days, 2 - 7 days
                    return { week: thermostatWeekFormat[value] };
                case dataPoints.hyMode: // 0x0480 mode
                    // @ts-expect-error ignore
                    return { system_mode: { 0: "manual", 1: "auto", 2: "away" }[value] };
                case dataPoints.hyAlarm: // [16] [0]
                    return { alarm: value > 0 };
                default: // The purpose of the codes 17 & 19 are still unknown
                    logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:hy_thermostat");
            }
        },
    },
    neo_nas_pd07: {
        cluster: "manuSpecificTuya",
        type: ["commandDataReport", "commandDataResponse"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "neo_nas_pd07");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            switch (dp) {
                case dataPoints.neoOccupancy:
                    return { occupancy: value > 0 };
                case 102:
                    return {
                        // @ts-expect-error ignore
                        power_type: { 0: "battery_full", 1: "battery_high", 2: "battery_medium", 3: "battery_low", 4: "usb" }[value],
                        battery_low: value === 3,
                    };
                case dataPoints.neoTamper:
                    return { tamper: value > 0 };
                case 104:
                    return { temperature: value / 10 };
                case 105:
                    return { humidity: value };
                case dataPoints.neoMinTemp:
                    return { temperature_min: value };
                case dataPoints.neoMaxTemp:
                    return { temperature_max: value };
                case dataPoints.neoMinHumidity:
                    return { humidity_min: value };
                case dataPoints.neoMaxHumidity:
                    return { humidity_max: value };
                case dataPoints.neoTempScale:
                    return { temperature_scale: value ? "°C" : "°F" };
                case 111:
                    return { unknown_111: value ? "ON" : "OFF" };
                case 112:
                    return { unknown_112: value ? "ON" : "OFF" };
                case dataPoints.neoTempHumidityAlarm:
                    // @ts-expect-error ignore
                    return { alarm: { 0: "over_temperature", 1: "over_humidity", 2: "below_min_temperature", 3: "below_min_humdity", 4: "off" }[value] };
                default: // Unknown code
                    logger_1.logger.debug(`Unrecognized DP #${dp}: ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:neo_nas_pd07");
            }
        },
    },
    neo_t_h_alarm: {
        cluster: "manuSpecificTuya",
        type: ["commandDataReport", "commandDataResponse"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "neo_t_h_alarm");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            switch (dp) {
                case dataPoints.neoAlarm:
                    return { alarm: value };
                case dataPoints.neoUnknown2: // 0x0170 [0]
                    break;
                case dataPoints.neoTempAlarm:
                    return { temperature_alarm: value };
                case dataPoints.neoHumidityAlarm: // 0x0172 [0]/[1] Disable/Enable alarm by humidity
                    return { humidity_alarm: value };
                case dataPoints.neoDuration: // 0x0267 [0,0,0,10] duration alarm in second
                    return { duration: value };
                case dataPoints.neoTemp: // 0x0269 [0,0,0,240] temperature
                    return { temperature: value / 10 };
                case dataPoints.neoHumidity: // 0x026A [0,0,0,36] humidity
                    return { humidity: value };
                case dataPoints.neoMinTemp: // 0x026B [0,0,0,18] min alarm temperature
                    return { temperature_min: value };
                case dataPoints.neoMaxTemp: // 0x026C [0,0,0,27] max alarm temperature
                    return { temperature_max: value };
                case dataPoints.neoMinHumidity: // 0x026D [0,0,0,45] min alarm humidity
                    return { humidity_min: value };
                case dataPoints.neoMaxHumidity: // 0x026E [0,0,0,80] max alarm humidity
                    return { humidity_max: value };
                case dataPoints.neoPowerType: // 0x0465 [4]
                    return {
                        // @ts-expect-error ignore
                        power_type: { 0: "battery_full", 1: "battery_high", 2: "battery_medium", 3: "battery_low", 4: "usb" }[value],
                        battery_low: value === 3,
                    };
                case dataPoints.neoMelody: // 0x0466 [5] Melody
                    return { melody: value };
                case dataPoints.neoUnknown3: // 0x0473 [0]
                    break;
                case dataPoints.neoVolume: // 0x0474 [0]/[1]/[2] Volume 0-max, 2-low
                    // @ts-expect-error ignore
                    return { volume: { 2: "low", 1: "medium", 0: "high" }[value] };
                default: // Unknown code
                    logger_1.logger.debug(`Unrecognized DP #${dp}: ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:neo_t_h_alarm");
            }
        },
    },
    neo_alarm: {
        cluster: "manuSpecificTuya",
        type: ["commandDataReport", "commandDataResponse"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "neo_alarm");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            switch (dp) {
                case dataPoints.neoAOAlarm: // 0x13 [TRUE,FALSE]
                    return { alarm: value };
                case dataPoints.neoAODuration: // 0x7 [0,0,0,10] duration alarm in second
                    return { duration: value };
                case dataPoints.neoAOBattPerc: // 0x15 [0,0,0,100] battery percentage
                    return { battpercentage: value };
                case dataPoints.neoAOMelody: // 0x21 [5] Melody
                    return { melody: value };
                case dataPoints.neoAOVolume: // 0x5 [0]/[1]/[2] Volume 0-low, 2-max
                    // @ts-expect-error ignore
                    return { volume: { 0: "low", 1: "medium", 2: "high" }[value] };
                default: // Unknown code
                    logger_1.logger.debug(`Unrecognized DP #${dp}: ${JSON.stringify(msg.data)}`, "zhc:legacy:fz:neo_alarm");
            }
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    ZB006X_settings: {
        cluster: "manuSpecificTuya",
        type: ["commandActiveStatusReport", "commandActiveStatusReportAlt"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "ZB006X_settings");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            switch (dp) {
                case dataPoints.fantemPowerSupplyMode:
                    // @ts-expect-error ignore
                    return { power_supply_mode: { 0: "unknown", 1: "no_neutral", 2: "with_neutral" }[value] };
                case dataPoints.fantemExtSwitchType:
                    // @ts-expect-error ignore
                    return { switch_type: { 0: "unknown", 1: "toggle", 2: "momentary", 3: "rotary", 4: "auto_config" }[value] };
                case dataPoints.fantemLoadDetectionMode:
                    // @ts-expect-error ignore
                    return { load_detection_mode: { 0: "none", 1: "first_power_on", 2: "every_power_on" }[value] };
                case dataPoints.fantemExtSwitchStatus:
                    return { switch_status: value };
                case dataPoints.fantemControlMode:
                    // @ts-expect-error ignore
                    return { control_mode: { 0: "ext_switch", 1: "remote", 2: "both" }[value] };
                case 111:
                    // Value 0 is received after each device power-on. No idea what it means.
                    return;
                case dataPoints.fantemLoadType:
                    // Not sure if 0 is 'resistive' and 2 is 'resistive_inductive'.
                    // If you see 'unknown', pls. check with Tuya gateway and app and update with label shown in Tuya app.
                    // @ts-expect-error ignore
                    return { load_type: { 0: "unknown", 1: "resistive_capacitive", 2: "unknown", 3: "detecting" }[value] };
                case dataPoints.fantemLoadDimmable:
                    // @ts-expect-error ignore
                    return { load_dimmable: { 0: "unknown", 1: "dimmable", 2: "not_dimmable" }[value] };
                default:
                    logger_1.logger.debug(`Unrecognized DP|Value [${dp}|${value}][${JSON.stringify(dpValue)}]`, "zhc:legacy:fz:zb006x_settings");
            }
        },
    },
    tuya_cover: {
        cluster: "manuSpecificTuya",
        type: ["commandDataReport", "commandDataResponse"],
        options: [exposes.options.invert_cover()],
        convert: (model, msg, publish, options, meta) => {
            // Protocol description
            // https://github.com/Koenkk/zigbee-herdsman-converters/issues/1159#issuecomment-614659802
            const result = {};
            // Iterate through dpValues in case of some zigbee models returning multiple dp values in one message
            // For example: [TS0601, _TZE200_3ylew7b4]
            for (const dpValue of msg.data.dpValues) {
                const dp = dpValue.dp;
                const value = getDataValue(dpValue);
                switch (dp) {
                    case dataPoints.coverPosition: // Started moving to position (triggered from Zigbee)
                    case dataPoints.coverArrived:
                        {
                            // Arrived at position
                            const invert = isCoverInverted(meta.device.manufacturerName) ? !options.invert_cover : options.invert_cover;
                            const position = invert ? 100 - (value & 0xff) : value & 0xff;
                            const running = dp !== dataPoints.coverArrived;
                            // Not all covers report coverArrived, so set running to false if device doesn't report position
                            // for a few seconds
                            clearTimeout(globalStore.getValue(msg.endpoint, "running_timer"));
                            if (running) {
                                const timer = setTimeout(() => publish({ running: false }), 3 * 1000).unref();
                                globalStore.putValue(msg.endpoint, "running_timer", timer);
                            }
                            if (position > 0 && position <= 100) {
                                result.running = running;
                                result.position = position;
                                result.state = "OPEN";
                            }
                            else if (position === 0) {
                                // Report fully closed
                                result.running = running;
                                result.position = position;
                                result.state = "CLOSE";
                            }
                            else {
                                result.running = running; // Not calibrated yet, no position is available
                            }
                        }
                        break;
                    case dataPoints.coverSpeed: // Cover is reporting its current speed setting
                        result.motor_speed = value;
                        break;
                    case dataPoints.state: // Ignore the cover state, it's not reliable between different covers!
                        break;
                    case dataPoints.coverChange: // Ignore manual cover change, it's not reliable between different covers!
                        break;
                    case dataPoints.config: // Returned by configuration set; ignore
                        break;
                    default: // Unknown code
                        logger_1.logger.debug(`Unrecognized DP #${dp}: ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:tuya_cover");
                }
            }
            return result;
        },
    },
    moes_switch: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "moes_switch");
            const dp = dpValue.dp;
            // tuya_switch datapoints
            if (dp >= 1 && dp <= 4) {
                return null;
            }
            const value = getDataValue(dpValue);
            switch (dp) {
                case dataPoints.moesSwitchPowerOnBehavior:
                    return { power_on_behavior: moesSwitch.powerOnBehavior[value] };
                case dataPoints.moesSwitchIndicateLight:
                    return { indicate_light: moesSwitch.indicateLight[value] };
                default:
                    logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:moes_switch");
                    break;
            }
        },
    },
    tuya_water_leak: {
        cluster: "manuSpecificTuya",
        type: "commandDataReport",
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "tuya_water_leak");
            if (dpValue.dp === dataPoints.waterLeak) {
                return { water_leak: getDataValue(dpValue) };
            }
        },
    },
    wls100z_water_leak: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const result = {};
            for (const dpValue of msg.data.dpValues) {
                const value = getDataValue(dpValue);
                switch (dpValue.dp) {
                    case dataPoints.wlsWaterLeak:
                        result.water_leak = value < 1;
                        break;
                    case dataPoints.wlsBatteryPercentage:
                        result.battery = value;
                        break;
                    default:
                        logger_1.logger.debug(`Unrecognized DP #${dpValue.dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:wls100z_water_leak");
                }
            }
            return result;
        },
    },
    silvercrest_smart_led_string: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "silvercrest_smart_led_string");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            const result = {};
            if (dp === dataPoints.silvercrestChangeMode) {
                if (value !== silvercrestModes.effect) {
                    result.effect = null;
                }
            }
            if (dp === dataPoints.silvercrestSetBrightness) {
                result.brightness = utils.mapNumberRange(value, 0, 1000, 0, 255);
            }
            else if (dp === dataPoints.silvercrestSetColor) {
                const h = Number.parseInt(value.substring(0, 4), 16);
                const s = Number.parseInt(value.substring(4, 8), 16);
                const b = Number.parseInt(value.substring(8, 12), 16);
                result.color_mode = "hs";
                result.color = { b: utils.mapNumberRange(b, 0, 1000, 0, 255), h, s: utils.mapNumberRange(s, 0, 1000, 0, 100) };
                result.brightness = result.color.b;
            }
            else if (dp === dataPoints.silvercrestSetEffect) {
                result.effect = {
                    effect: utils.getKey(silvercrestEffects, value.substring(0, 2), "", String),
                    speed: utils.mapNumberRange(Number.parseInt(value.substring(2, 4), 10), 0, 64, 0, 100),
                    colors: [],
                };
                const colorsString = value.substring(4);
                // Colors are 6 characters.
                const n = Math.floor(colorsString.length / 6);
                // The incoming message can contain anywhere between 0 to 6 colors.
                // In the following loop we're extracting every color the led
                // string gives us.
                for (let i = 0; i < n; ++i) {
                    const part = colorsString.substring(i * 6, (i + 1) * 6);
                    const r = part[0] + part[1];
                    const g = part[2] + part[3];
                    const b = part[4] + part[5];
                    result.effect.colors.push({
                        r: Number.parseInt(r, 16),
                        g: Number.parseInt(g, 16),
                        b: Number.parseInt(b, 16),
                    });
                }
            }
            return result;
        },
    },
    frankever_valve: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport", "commandActiveStatusReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "frankever_valve");
            const value = getDataValue(dpValue);
            const dp = dpValue.dp;
            switch (dp) {
                case dataPoints.state: {
                    return { state: value ? "ON" : "OFF" };
                }
                case dataPoints.frankEverTreshold: {
                    return { threshold: value };
                }
                case dataPoints.frankEverTimer: {
                    return { timer: value / 60 };
                }
                default: {
                    logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:frankever_valve");
                }
            }
        },
    },
    tuya_woox_smoke: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "tuya_woox_smoke");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            switch (dp) {
                case dataPoints.wooxBattery:
                    return { battery_low: value === 0 };
                case dataPoints.state:
                    return { smoke: value === 0 };
                case dataPoints.wooxSmokeTest:
                    return { smoke: value };
                default:
                    logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:tuya_smoke");
            }
        },
    },
    tuya_switch: {
        cluster: "manuSpecificTuya",
        type: ["commandDataReport", "commandDataResponse", "commandActiveStatusReport"],
        convert: (model, msg, publish, options, meta) => {
            const multiEndpoint = model.meta?.multiEndpoint;
            const dpValue = firstDpValue(msg, meta, "tuya_switch");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            const state = value ? "ON" : "OFF";
            if (multiEndpoint) {
                const lookup = { 1: "l1", 2: "l2", 3: "l3", 4: "l4", 5: "l5", 6: "l6" };
                const endpoint = lookup[dp];
                if (endpoint in model.endpoint(msg.device)) {
                    return { [`state_${endpoint}`]: state };
                }
            }
            else if (dp === dataPoints.state) {
                return { state: state };
            }
            return null;
        },
    },
    tuya_dinrail_switch: {
        cluster: "manuSpecificTuya",
        type: ["commandDataReport", "commandDataResponse", "commandActiveStatusReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "tuya_dinrail_switch");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            const state = value ? "ON" : "OFF";
            switch (dp) {
                case dataPoints.state: // DPID that we added to common
                    return { state: state };
                case dataPoints.dinrailPowerMeterTotalEnergy:
                    return { energy: value / 100 };
                case dataPoints.dinrailPowerMeterCurrent:
                    return { current: value / 1000 };
                case dataPoints.dinrailPowerMeterPower:
                    return { power: value / 10 };
                case dataPoints.dinrailPowerMeterVoltage:
                    return { voltage: value / 10 };
                default:
                    logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:tuya_dinrail_switch");
            }
            return null;
        },
    },
    ZVG1: {
        cluster: "manuSpecificTuya",
        type: "commandDataResponse",
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "ZVG1");
            const value = getDataValue(dpValue);
            const dp = dpValue.dp;
            switch (dp) {
                case dataPoints.state: {
                    return { state: value ? "ON" : "OFF" };
                }
                case 5: {
                    // Assume value is reported in fl. oz., converter to litres
                    return { water_consumed: (value / 33.8140226).toFixed(2) };
                }
                case 7: {
                    return { battery: value };
                }
                case 10: {
                    let data = "disabled";
                    if (value === 1) {
                        data = "24h";
                    }
                    else if (value === 2) {
                        data = "48h";
                    }
                    else if (value === 3) {
                        data = "72h";
                    }
                    return { weather_delay: data };
                }
                case 11: {
                    // value reported in seconds
                    return { timer_time_left: value / 60 };
                }
                case 12: {
                    if (value === 0)
                        return { timer_state: "disabled" };
                    if (value === 1)
                        return { timer_state: "active" };
                    return { timer_state: "enabled" };
                }
                case 15: {
                    // value reported in seconds
                    return { last_valve_open_duration: value / 60 };
                }
                case 16: {
                    const tresult = {
                        cycle_timer_1: "",
                        cycle_timer_2: "",
                        cycle_timer_3: "",
                        cycle_timer_4: "",
                    };
                    for (let index = 0; index < 40; index += 12) {
                        const timer = convertRawToCycleTimer(value.slice(index));
                        if (timer.irrigationDuration > 0) {
                            tresult[`cycle_timer_${index / 13 + 1}`] =
                                `${timer.starttime} / ${timer.endtime} / ${timer.irrigationDuration} / ${timer.pauseDuration} / ${timer.weekdays} / ${timer.active}`;
                        }
                    }
                    return tresult;
                }
                case 17: {
                    const tresult = {
                        normal_schedule_timer_1: "",
                        normal_schedule_timer_2: "",
                        normal_schedule_timer_3: "",
                        normal_schedule_timer_4: "",
                    };
                    for (let index = 0; index < 40; index += 13) {
                        const timer = convertRawToTimer(value.slice(index));
                        if (timer.duration > 0) {
                            tresult[`normal_schedule_timer_${index / 13 + 1}`] =
                                `${timer.time} / ${timer.duration} / ${timer.weekdays} / ${timer.active}`;
                        }
                    }
                    return tresult;
                }
                default: {
                    logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:rtx_zvg1_valve");
                }
            }
        },
    },
    ZB003X: {
        cluster: "manuSpecificTuya",
        type: ["commandActiveStatusReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "ZB003X");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            switch (dp) {
                case dataPoints.fantemTemp:
                    return { temperature: value / 10 };
                case dataPoints.fantemHumidity:
                    return { humidity: value };
                case dataPoints.fantemBattery:
                    // second battery level, first battery is reported by fz.battery
                    return { battery2: value };
                case dataPoints.fantemReportingTime:
                    return { reporting_time: value };
                case dataPoints.fantemTempCalibration:
                    return {
                        temperature_calibration: ((value > 0x7fffffff ? 0xffffffff - value : value) / 10).toFixed(1),
                    };
                case dataPoints.fantemHumidityCalibration:
                    return { humidity_calibration: value > 0x7fffffff ? 0xffffffff - value : value };
                case dataPoints.fantemLuxCalibration:
                    return { illuminance_calibration: value > 0x7fffffff ? 0xffffffff - value : value };
                case dataPoints.fantemMotionEnable:
                    return { pir_enable: value };
                case dataPoints.fantemLedEnable:
                    return { led_enable: !value };
                case dataPoints.fantemReportingEnable:
                    return { reporting_enable: value };
                default:
                    logger_1.logger.debug(`Unrecognized DP #${dp}: ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:zb003x");
            }
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    ZB003X_attr: {
        cluster: "ssIasZone",
        type: ["attributeReport", "readResponse"],
        convert: (model, msg, publish, options, meta) => {
            const data = msg.data;
            const senslookup = { 0: "low", 1: "medium", 2: "high" };
            const keeptimelookup = { 0: 0, 1: 30, 2: 60, 3: 120, 4: 240, 5: 480 };
            if (data && data.currentZoneSensitivityLevel !== undefined) {
                const value = data.currentZoneSensitivityLevel;
                return { sensitivity: senslookup[value] };
            }
            if (data && data["61441"] !== undefined) {
                const value = data["61441"];
                return { keep_time: keeptimelookup[value] };
            }
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    ZB003X_occupancy: {
        cluster: "ssIasZone",
        type: "commandStatusChangeNotification",
        convert: (model, msg, publish, options, meta) => {
            const zoneStatus = msg.data.zonestatus;
            return { occupancy: (zoneStatus & 1) > 0, tamper: (zoneStatus & 4) > 0 };
        },
    },
    tuya_thermostat_weekly_schedule_2: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "tuya_thermostat_weekly_schedule");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            const thermostatMeta = getMetaValue(msg.endpoint, model, "thermostat");
            const firstDayDpId = thermostatMeta.weeklyScheduleFirstDayDpId;
            const maxTransitions = thermostatMeta.weeklyScheduleMaxTransitions;
            let dataOffset = 0;
            let conversion = "generic";
            // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
            function dataToTransitions(data, maxTransitions, offset) {
                // Later it is possible to move converter to meta or to other place outside if other type of converter
                // will be needed for other device. Currently this converter is based on ETOP HT-08 thermostat.
                // see also toZigbee.tuya_thermostat_weekly_schedule()
                // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
                function dataToTransition(data, index) {
                    return {
                        time: (data[index + 0] << 8) + data[index + 1],
                        heating_setpoint: (Number.parseFloat((data[index + 2] << 8) + data[index + 3]) / 10.0).toFixed(1),
                    };
                }
                const result = [];
                for (let i = 0; i < maxTransitions; i++) {
                    result.push(dataToTransition(data, i * 4 + offset));
                }
                return result;
            }
            if (thermostatMeta.weeklyScheduleConversion !== undefined) {
                conversion = thermostatMeta.weeklyScheduleConversion;
            }
            if (conversion === "saswell") {
                // Saswell has scheduling mode in the first byte
                dataOffset = 1;
            }
            if (dp >= firstDayDpId && dp < firstDayDpId + 7) {
                const dayOfWeek = dp - firstDayDpId + 1;
                return {
                    // Same as in hvacThermostat:getWeeklyScheduleRsp hvacThermostat:setWeeklySchedule cluster format
                    weekly_schedule: {
                        days: [utils.getFromLookup(dayOfWeek, constants.thermostatDayOfWeek)],
                        transitions: dataToTransitions(value, maxTransitions, dataOffset),
                    },
                };
            }
        },
    },
    tuya_data_point_dump: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport", "commandActiveStatusReport", "commandActiveStatusReportAlt"],
        convert: (model, msg, publish, options, meta) => {
            // Don't use in production!
            // Used in: https://www.zigbee2mqtt.io/how_tos/how_to_support_new_tuya_devices.html
            const getHex = (value) => {
                let hex = value.toString(16);
                if (hex.length < 2) {
                    hex = `0${hex}`;
                }
                return hex;
            };
            const now = Date.now().toString();
            let dataStr = "";
            for (const [i, dpValue] of msg.data.dpValues.entries()) {
                logDataPoint("tuya_data_point_dump", msg, dpValue, meta);
                dataStr += `${now} ${meta.device.ieeeAddr} ${getHex(msg.data.seq)} ${getHex(i)} ${getHex(dpValue.dp)} ${getHex(dpValue.datatype)}`;
                // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
                dpValue.data.forEach((elem) => {
                    dataStr += ` ${getHex(elem)}`;
                });
                dataStr += "\n";
            }
            node_fs_1.default.appendFile("data/tuya.dump.txt", dataStr, (err) => {
                if (err)
                    throw err;
            });
        },
    },
    javis_microwave_sensor: {
        cluster: "manuSpecificTuya",
        type: ["commandDataReport", "commandDataResponse"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "javis_microwave_sensor");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            const lookup = {
                0: "no_motion",
                1: "big_motion",
                2: "minor_motion",
                3: "breathing",
                4: "abnormal_state",
                5: "initializing",
                6: "initialization_completed",
            };
            switch (dp) {
                case 1:
                    return {
                        states: lookup[value],
                        occupancy: !!(0 < value && value < 5),
                    };
                case 2:
                    return {
                        sensitivity: value,
                    };
                case 101:
                    return {
                        illuminance: value,
                    };
                case 102:
                    if (meta.device.manufacturerName === "_TZE200_kagkgk0i") {
                        return {
                            illuminance_calibration: value,
                        };
                    }
                    return {
                        keep_time: value,
                    };
                case 103:
                    return {
                        led_enable: value === 1,
                    };
                case 104:
                    return { illuminance: value };
                case 105:
                    return {
                        illuminance_calibration: value,
                    };
                case 106:
                    if (meta.device.manufacturerName === "_TZE200_kagkgk0i") {
                        return {
                            keep_time: value,
                        };
                    }
                    break;
                case 107:
                    if (meta.device.manufacturerName === "_TZE200_kagkgk0i") {
                        return {
                            led_enable: value === 1,
                        };
                    }
                    break;
                default:
                    logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:javis_microwave_sensor");
            }
        },
    },
    SLUXZB: {
        cluster: "manuSpecificTuya",
        type: ["commandDataResponse", "commandDataReport"],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = firstDpValue(msg, meta, "SLUXZB");
            const dp = dpValue.dp;
            const value = getDataValue(dpValue);
            const brightnesStateLookup = { "0": "low", "1": "middle", "2": "high" };
            switch (dp) {
                case 2:
                    return { illuminance: value };
                case 4:
                    return { battery: value };
                case 1:
                    return { brightness_level: brightnesStateLookup[value] };
                default:
                    logger_1.logger.debug(`Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`, "zhc:legacy:fz:s_lux_zb");
            }
        },
    },
};
exports.fz = fromZigbee;
exports.fromZigbee = fromZigbee;
const toZigbee1 = {
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    SA12IZL_silence_siren: {
        key: ["silence_siren"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, 16, value);
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    SA12IZL_alarm: {
        key: ["alarm"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointEnum(entity, 20, value ? 0 : 1);
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    R7049_silenceSiren: {
        key: ["silence_siren"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, 16, value);
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    R7049_testAlarm: {
        key: ["test_alarm"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, 8, value);
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    R7049_alarm: {
        key: ["alarm"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointEnum(entity, 20, value ? 0 : 1);
        },
    },
    valve_state: {
        key: ["valve_state"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointValue(entity, dataPoints.wateringTimer.valve_state, value);
        },
    },
    shutdown_timer: {
        key: ["shutdown_timer"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointValue(entity, dataPoints.wateringTimer.shutdown_timer, value);
        },
    },
    valve_state_auto_shutdown: {
        key: ["valve_state_auto_shutdown"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointValue(entity, dataPoints.wateringTimer.valve_state_auto_shutdown, value);
        },
    },
    hpsz: {
        key: ["led_state"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, dataPoints.HPSZLEDState, value);
        },
    },
    tuya_cover_control: {
        key: ["state", "position"],
        options: [exposes.options.invert_cover()],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            // Protocol description
            // https://github.com/Koenkk/zigbee-herdsman-converters/issues/1159#issuecomment-614659802
            if (key === "position") {
                if (value >= 0 && value <= 100) {
                    const invert = isCoverInverted(meta.device.manufacturerName) ? !meta.options.invert_cover : meta.options.invert_cover;
                    value = invert ? 100 - value : value;
                    await sendDataPointValue(entity, dataPoints.coverPosition, value);
                }
                else {
                    throw new Error("Tuya_cover_control: Curtain motor position is out of range");
                }
            }
            else if (key === "state") {
                const stateEnums = getCoverStateEnums(meta.device.manufacturerName);
                logger_1.logger.debug(`Using state enums for ${meta.device.manufacturerName}: ${JSON.stringify(stateEnums)}`, "zhc:legacy:tz:tuya_cover_control");
                value = value.toLowerCase();
                switch (value) {
                    case "close":
                        await sendDataPointEnum(entity, dataPoints.state, stateEnums.close);
                        break;
                    case "open":
                        await sendDataPointEnum(entity, dataPoints.state, stateEnums.open);
                        break;
                    case "stop":
                        await sendDataPointEnum(entity, dataPoints.state, stateEnums.stop);
                        break;
                    default:
                        throw new Error("Tuya_cover_control: Invalid command received");
                }
            }
        },
    },
};
const toZigbee2 = {
    zb_sm_cover: {
        key: ["state", "position", "reverse_direction", "top_limit", "bottom_limit", "favorite_position", "goto_positon", "report"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            switch (key) {
                case "position": {
                    const invert = meta.state ? !meta.state.invert_cover : false;
                    value = invert ? 100 - value : value;
                    if (value >= 0 && value <= 100) {
                        await sendDataPointValue(entity, dataPoints.coverPosition, value);
                    }
                    else {
                        throw new Error("Tuya_cover_control: Curtain motor position is out of range");
                    }
                    break;
                }
                case "state": {
                    const stateEnums = getCoverStateEnums(meta.device.manufacturerName);
                    logger_1.logger.debug(`Using state enums for ${meta.device.manufacturerName}: ${JSON.stringify(stateEnums)}`, "zhc:legacy:tz:zb_sm_cover");
                    value = value.toLowerCase();
                    switch (value) {
                        case "close":
                            await sendDataPointEnum(entity, dataPoints.state, stateEnums.close);
                            break;
                        case "open":
                            await sendDataPointEnum(entity, dataPoints.state, stateEnums.open);
                            break;
                        case "stop":
                            await sendDataPointEnum(entity, dataPoints.state, stateEnums.stop);
                            break;
                        default:
                            throw new Error("Tuya_cover_control: Invalid command received");
                    }
                    break;
                }
                case "reverse_direction": {
                    logger_1.logger.info(`Motor direction ${value ? "reverse" : "forward"}`, "zhc:legacy:tz:zb_sm_cover");
                    await sendDataPointEnum(entity, dataPoints.motorDirection, value ? 1 : 0);
                    break;
                }
                case "top_limit": {
                    // @ts-expect-error ignore
                    await sendDataPointEnum(entity, 104, { SET: 0, CLEAR: 1 }[value]);
                    break;
                }
                case "bottom_limit": {
                    // @ts-expect-error ignore
                    await sendDataPointEnum(entity, 103, { SET: 0, CLEAR: 1 }[value]);
                    break;
                }
                case "favorite_position": {
                    await sendDataPointValue(entity, 115, value);
                    break;
                }
                case "goto_positon": {
                    if (value === "FAVORITE") {
                        value = meta.state ? meta.state.favorite_position : null;
                    }
                    else {
                        value = Number.parseInt(value, 10);
                    }
                    return await toZigbee1.tuya_cover_control.convertSet(entity, "position", value, meta);
                }
                case "report": {
                    await sendDataPointBool(entity, 116, 0);
                    break;
                }
            }
        },
    },
    x5h_thermostat: {
        key: [
            "system_mode",
            "current_heating_setpoint",
            "sensor",
            "brightness_state",
            "sound",
            "frost_protection",
            "week",
            "factory_reset",
            "local_temperature_calibration",
            "heating_temp_limit",
            "deadzone_temperature",
            "upper_temp",
            "preset",
            "child_lock",
            "schedule",
        ],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            switch (key) {
                case "system_mode":
                    await sendDataPointBool(entity, dataPoints.x5hState, value === "heat");
                    break;
                case "preset": {
                    value = value.toLowerCase();
                    const lookup = { manual: 0, program: 1 };
                    utils.validateValue(value, Object.keys(lookup));
                    value = lookup[value];
                    await sendDataPointEnum(entity, dataPoints.x5hMode, value);
                    break;
                }
                case "upper_temp":
                    if (value >= 35 && value <= 95) {
                        await sendDataPointValue(entity, dataPoints.x5hSetTempCeiling, value);
                        const setpoint = globalStore.getValue(entity, "currentHeatingSetpoint", 20);
                        const setpointRaw = Math.round(setpoint * 10);
                        await utils.sleep(500);
                        await sendDataPointValue(entity, dataPoints.x5hSetTemp, setpointRaw);
                    }
                    else {
                        throw new Error("Supported values are in range [35, 95]");
                    }
                    break;
                case "deadzone_temperature":
                    if (value >= 0.5 && value <= 9.5) {
                        value = Math.round(value * 10);
                        await sendDataPointValue(entity, dataPoints.x5hTempDiff, value);
                    }
                    else {
                        throw new Error("Supported values are in range [0.5, 9.5]");
                    }
                    break;
                case "heating_temp_limit":
                    if (value >= 5 && value <= 60) {
                        await sendDataPointValue(entity, dataPoints.x5hProtectionTempLimit, value);
                    }
                    else {
                        throw new Error("Supported values are in range [5, 60]");
                    }
                    break;
                case "local_temperature_calibration":
                    if (value >= -9.9 && value <= 9.9) {
                        value = Math.round(value * 10);
                        if (value < 0) {
                            value = 0xffffffff + value + 1;
                        }
                        await sendDataPointValue(entity, dataPoints.x5hTempCorrection, value);
                    }
                    else {
                        throw new Error("Supported values are in range [-9.9, 9.9]");
                    }
                    break;
                case "factory_reset":
                    await sendDataPointBool(entity, dataPoints.x5hFactoryReset, value === "ON");
                    break;
                case "week":
                    await sendDataPointEnum(entity, dataPoints.x5hWorkingDaySetting, utils.getKey(thermostatWeekFormat, value, value, Number));
                    break;
                case "frost_protection":
                    await sendDataPointBool(entity, dataPoints.x5hFrostProtection, value === "ON");
                    break;
                case "sound":
                    await sendDataPointBool(entity, dataPoints.x5hSound, value === "ON");
                    break;
                case "brightness_state": {
                    value = value.toLowerCase();
                    const lookup = { off: 0, low: 1, medium: 2, high: 3 };
                    utils.validateValue(value, Object.keys(lookup));
                    value = lookup[value];
                    await sendDataPointEnum(entity, dataPoints.x5hBackplaneBrightness, value);
                    break;
                }
                case "sensor": {
                    value = value.toLowerCase();
                    const lookup = { internal: 0, external: 1, both: 2 };
                    utils.validateValue(value, Object.keys(lookup));
                    value = lookup[value];
                    await sendDataPointEnum(entity, dataPoints.x5hSensorSelection, value);
                    break;
                }
                case "current_heating_setpoint":
                    if (value >= 5 && value <= 60) {
                        value = Math.round(value * 10);
                        await sendDataPointValue(entity, dataPoints.x5hSetTemp, value);
                    }
                    else {
                        throw new Error(`Unsupported value: ${value}`);
                    }
                    break;
                case "child_lock":
                    await sendDataPointBool(entity, dataPoints.x5hChildLock, value === "LOCK");
                    break;
                case "schedule": {
                    const periods = value.split(" ");
                    const periodsNumber = 8;
                    const payload = [];
                    for (let i = 0; i < periodsNumber; i++) {
                        const timeTemp = periods[i].split("/");
                        const hm = timeTemp[0].split(":", 2);
                        const h = Number.parseInt(hm[0], 10);
                        const m = Number.parseInt(hm[1], 10);
                        const temp = Number.parseFloat(timeTemp[1]);
                        if (h < 0 || h >= 24 || m < 0 || m >= 60 || temp < 5 || temp > 60) {
                            throw new Error(`Invalid hour, minute or temperature of: ${periods[i]}`);
                        }
                        const tempHexArray = convertDecimalValueTo2ByteHexArray(Math.round(temp * 10));
                        // 1 byte for hour, 1 byte for minutes, 2 bytes for temperature
                        payload.push(h, m, ...tempHexArray);
                    }
                    await sendDataPointRaw(entity, dataPoints.x5hWeeklyProcedure, Buffer.from(payload));
                    break;
                }
                default:
                    break;
            }
        },
    },
    zs_thermostat_child_lock: {
        key: ["child_lock"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, dataPoints.zsChildLock, value === "LOCK");
        },
    },
    zs_thermostat_binary_one: {
        key: ["binary_one"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, dataPoints.zsBinaryOne, value === "ON");
        },
    },
    zs_thermostat_binary_two: {
        key: ["binary_two"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, dataPoints.zsBinaryTwo, value === "ON");
        },
    },
    zs_thermostat_current_heating_setpoint: {
        key: ["current_heating_setpoint"],
        convertSet: async (entity, key, value, meta) => {
            utils.assertNumber(value);
            let temp = Math.round(value * 2);
            if (temp <= 0)
                temp = 1;
            if (temp >= 60)
                temp = 59;
            await sendDataPointValue(entity, dataPoints.zsHeatingSetpoint, temp);
        },
    },
    zs_thermostat_current_heating_setpoint_auto: {
        key: ["current_heating_setpoint_auto"],
        convertSet: async (entity, key, value, meta) => {
            utils.assertNumber(value);
            let temp = Math.round(value * 2);
            if (temp <= 0)
                temp = 1;
            if (temp >= 60)
                temp = 59;
            await sendDataPointValue(entity, dataPoints.zsHeatingSetpointAuto, temp);
        },
    },
    zs_thermostat_comfort_temp: {
        key: ["comfort_temperature"],
        convertSet: async (entity, key, value, meta) => {
            utils.assertNumber(value);
            logger_1.logger.debug(JSON.stringify(entity), "zhc:legacy:tz:zs_thermostat_comfort_temp");
            const temp = Math.round(value * 2);
            await sendDataPointValue(entity, dataPoints.zsComfortTemp, temp);
        },
    },
    zs_thermostat_openwindow_temp: {
        key: ["detectwindow_temperature"],
        convertSet: async (entity, key, value, meta) => {
            utils.assertNumber(value);
            let temp = Math.round(value * 2);
            if (temp <= 0)
                temp = 1;
            if (temp >= 60)
                temp = 59;
            await sendDataPointValue(entity, dataPoints.zsOpenwindowTemp, temp);
        },
    },
    zs_thermostat_openwindow_time: {
        key: ["detectwindow_timeminute"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointValue(entity, dataPoints.zsOpenwindowTime, value);
        },
    },
    zs_thermostat_eco_temp: {
        key: ["eco_temperature"],
        convertSet: async (entity, key, value, meta) => {
            utils.assertNumber(value);
            const temp = Math.round(value * 2);
            await sendDataPointValue(entity, dataPoints.zsEcoTemp, temp);
        },
    },
    zs_thermostat_preset_mode: {
        key: ["preset"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            const lookup = { schedule: 0, manual: 1, holiday: 2 };
            if (value === "boost") {
                await sendDataPointEnum(entity, dataPoints.zsMode, lookup.manual);
                await sendDataPointValue(entity, dataPoints.zsHeatingSetpoint, 60);
            }
            else {
                await sendDataPointEnum(entity, dataPoints.zsMode, lookup[value]);
                if (value === "manual") {
                    const temp = globalStore.getValue(entity, "current_heating_setpoint");
                    await sendDataPointValue(entity, dataPoints.zsHeatingSetpoint, temp ? Math.round(temp * 2) : 43);
                }
            }
        },
    },
    zs_thermostat_system_mode: {
        key: ["system_mode"],
        convertSet: async (entity, key, value, meta) => {
            if (value === "off") {
                await sendDataPointEnum(entity, dataPoints.zsMode, 1);
                await sendDataPointValue(entity, dataPoints.zsHeatingSetpoint, 0);
            }
            else if (value === "auto") {
                await sendDataPointEnum(entity, dataPoints.zsMode, 0);
            }
            else if (value === "heat") {
                // manual
                const temp = globalStore.getValue(entity, "current_heating_setpoint");
                await sendDataPointEnum(entity, dataPoints.zsMode, 1);
                await sendDataPointValue(entity, dataPoints.zsHeatingSetpoint, temp ? Math.round(temp * 2) : 43);
            }
        },
    },
    zs_thermostat_local_temperature_calibration: {
        key: ["local_temperature_calibration"],
        convertSet: async (entity, key, value, meta) => {
            utils.assertNumber(value);
            let calValue = value;
            if (calValue > 0)
                calValue = calValue * 10;
            if (calValue < 0)
                calValue = calValue * 10 + 0x100000000;
            await sendDataPointValue(entity, dataPoints.zsTempCalibration, calValue);
        },
    },
    zs_thermostat_away_setting: {
        key: ["away_setting"],
        convertSet: async (entity, key, value, meta) => {
            // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
            const result = [];
            const daysInMonth = new Date(2000 + result[0], result[1], 0).getDate();
            for (const attrName of [
                "away_preset_year",
                "away_preset_month",
                "away_preset_day",
                "away_preset_hour",
                "away_preset_minute",
                "away_preset_temperature",
                "away_preset_days",
            ]) {
                let v = 0;
                if (value[attrName] != null) {
                    v = value[attrName];
                }
                else if (meta.state[attrName] !== undefined) {
                    // @ts-expect-error ignore
                    v = meta.state[attrName];
                }
                switch (attrName) {
                    case "away_preset_year":
                        if (v < 17 || v > 99)
                            v = 17;
                        result.push(Math.round(v));
                        break;
                    case "away_preset_month":
                        if (v < 1 || v > 12)
                            v = 1;
                        result.push(Math.round(v));
                        break;
                    case "away_preset_day":
                        if (v < 1) {
                            v = 1;
                        }
                        else if (v > daysInMonth) {
                            v = daysInMonth;
                        }
                        result.push(Math.round(v));
                        break;
                    case "away_preset_hour":
                        if (v < 0 || v > 23)
                            v = 0;
                        result.push(Math.round(v));
                        break;
                    case "away_preset_minute":
                        if (v < 0 || v > 59)
                            v = 0;
                        result.push(Math.round(v));
                        break;
                    case "away_preset_temperature":
                        if (v < 0.5 || v > 29.5)
                            v = 17;
                        result.push(Math.round(v * 2));
                        break;
                    case "away_preset_days":
                        if (v < 1 || v > 9999)
                            v = 1;
                        result.push((v & 0xff00) >> 8);
                        result.push(v & 0x00ff);
                        break;
                }
            }
            await sendDataPointRaw(entity, dataPoints.zsAwaySetting, result);
        },
    },
    zs_thermostat_local_schedule: {
        key: ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            const daysMap = { monday: 1, tuesday: 2, wednesday: 3, thursday: 4, friday: 5, saturday: 6, sunday: 7 };
            const day = daysMap[key];
            const results = [];
            results.push(day);
            for (let i = 1; i <= 9; i++) {
                // temperature
                const attrName = `${key}_temp_${i}`;
                let v = 17;
                if (value[attrName] !== undefined) {
                    v = value[attrName];
                }
                else if (meta.state[attrName] !== undefined) {
                    // @ts-expect-error ignore
                    v = meta.state[attrName];
                }
                if (v < 0.5 || v > 29.5)
                    v = 17;
                results.push(Math.round(v * 2));
                if (i !== 9) {
                    // hour
                    let attrName = `${key}_hour_${i}`;
                    let h = 0;
                    if (value[attrName] !== undefined) {
                        h = value[attrName];
                    }
                    else if (meta.state[attrName] !== undefined) {
                        // @ts-expect-error ignore
                        h = meta.state[attrName];
                    }
                    // minute
                    attrName = `${key}_minute_${i}`;
                    let m = 0;
                    if (value[attrName] !== undefined) {
                        m = value[attrName];
                    }
                    else if (meta.state[attrName] !== undefined) {
                        // @ts-expect-error ignore
                        m = meta.state[attrName];
                    }
                    let rt = h * 4 + m / 15;
                    if (rt < 1) {
                        rt = 1;
                    }
                    else if (rt > 96) {
                        rt = 96;
                    }
                    results.push(Math.round(rt));
                }
            }
            if (value > 0)
                value = value * 10;
            if (value < 0)
                value = value * 10 + 0x100000000;
            await sendDataPointRaw(entity, 109 + day - 1, Buffer.from(results));
        },
    },
    giexWaterValve: {
        key: [
            giexWaterValve.mode,
            giexWaterValve.irrigationTarget,
            giexWaterValve.state,
            giexWaterValve.cycleIrrigationNumTimes,
            giexWaterValve.cycleIrrigationInterval,
        ],
        convertSet: async (entity, key, value, meta) => {
            if (Array.isArray(meta.mapped))
                throw new Error("Not supported for groups");
            const modelConverters = giexTzModelConverters[meta.mapped?.model] || {};
            switch (key) {
                case giexWaterValve.state:
                    await sendDataPointBool(entity, dataPoints.giexWaterValve.state, value === ON);
                    break;
                case giexWaterValve.mode:
                    await sendDataPointBool(entity, dataPoints.giexWaterValve.mode, value === CAPACITY);
                    return { state: { [giexWaterValve.mode]: value } };
                case giexWaterValve.irrigationTarget: {
                    const mode = meta.state?.[giexWaterValve.mode];
                    const sanitizedValue = modelConverters.irrigationTarget?.(value, mode) || value;
                    await sendDataPointValue(entity, dataPoints.giexWaterValve.irrigationTarget, sanitizedValue);
                    return { state: { [giexWaterValve.irrigationTarget]: sanitizedValue } };
                }
                case giexWaterValve.cycleIrrigationNumTimes:
                    await sendDataPointValue(entity, dataPoints.giexWaterValve.cycleIrrigationNumTimes, value);
                    return { state: { [giexWaterValve.cycleIrrigationNumTimes]: value } };
                case giexWaterValve.cycleIrrigationInterval:
                    await sendDataPointValue(entity, dataPoints.giexWaterValve.cycleIrrigationInterval, value);
                    return { state: { [giexWaterValve.cycleIrrigationInterval]: value } };
                default: // Unknown key warning
                    logger_1.logger.warning(`Unhandled KEY ${key}`, "zhc:legacy:tz:giex_water_Valve");
            }
        },
    },
    tuya_alecto_smoke: {
        key: ["self_checking", "silence"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            switch (key) {
                case "self_checking":
                    await sendDataPointBool(entity, dataPoints.alectoSelfChecking, value);
                    break;
                case "silence":
                    await sendDataPointBool(entity, dataPoints.alectoSilence, value);
                    break;
                default: // Unknown key
                    throw new Error(`zigbee-herdsman-converters:tuya_alecto_smoke: Unhandled key ${key}`);
            }
        },
    },
    matsee_garage_door_opener: {
        key: ["trigger"],
        convertSet: async (entity, key, value, meta) => {
            const state = meta.message.trigger != null ? meta.message.trigger : true;
            // @ts-expect-error ignore
            await sendDataPointBool(entity, dataPoints.garageDoorTrigger, state);
            return { state: { trigger: state } };
        },
    },
    connecte_thermostat: {
        key: [
            "child_lock",
            "current_heating_setpoint",
            "local_temperature_calibration",
            "max_temperature_protection",
            "window_detection",
            "hysteresis",
            "state",
            "away_mode",
            "sensor",
            "system_mode",
        ],
        convertSet: async (entity, key, value, meta) => {
            switch (key) {
                case "state":
                    await sendDataPointBool(entity, dataPoints.connecteState, value === "ON");
                    break;
                case "child_lock":
                    await sendDataPointBool(entity, dataPoints.connecteChildLock, value === "LOCK");
                    break;
                case "local_temperature_calibration":
                    // @ts-expect-error ignore
                    if (value < 0)
                        value = 0xffffffff + value + 1;
                    await sendDataPointValue(entity, dataPoints.connecteTempCalibration, value);
                    break;
                case "hysteresis":
                    // value = Math.round(value * 10);
                    await sendDataPointValue(entity, dataPoints.connecteHysteresis, value);
                    break;
                case "max_temperature_protection":
                    // @ts-expect-error ignore
                    await sendDataPointValue(entity, dataPoints.connecteMaxProtectTemp, Math.round(value));
                    break;
                case "current_heating_setpoint":
                    await sendDataPointValue(entity, dataPoints.connecteHeatingSetpoint, value);
                    break;
                case "sensor":
                    await sendDataPointEnum(entity, dataPoints.connecteSensorType, 
                    // @ts-expect-error ignore
                    { internal: 0, external: 1, both: 2 }[value]);
                    break;
                case "system_mode":
                    switch (value) {
                        case "heat":
                            await sendDataPointEnum(entity, dataPoints.connecteMode, 0 /* manual */);
                            break;
                        case "auto":
                            await sendDataPointEnum(entity, dataPoints.connecteMode, 1 /* auto */);
                            break;
                    }
                    break;
                case "away_mode":
                    switch (value) {
                        case "ON":
                            await sendDataPointEnum(entity, dataPoints.connecteMode, 2 /* auto */);
                            break;
                        case "OFF":
                            await sendDataPointEnum(entity, dataPoints.connecteMode, 0 /* manual */);
                            break;
                    }
                    break;
                case "window_detection":
                    await sendDataPointBool(entity, dataPoints.connecteOpenWindow, value === "ON");
                    break;
                default: // Unknown key
                    throw new Error(`Unhandled key toZigbee.connecte_thermostat ${key}`);
            }
        },
    },
    moes_thermostat_child_lock: {
        key: ["child_lock"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, dataPoints.moesChildLock, value === "LOCK");
        },
    },
    moes_thermostat_current_heating_setpoint: {
        key: ["current_heating_setpoint"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            if (["_TZE200_5toc8efa", "_TZE204_5toc8efa"].includes(meta.device.manufacturerName)) {
                await sendDataPointValue(entity, dataPoints.moesHeatingSetpoint, value * 10);
            }
            else {
                await sendDataPointValue(entity, dataPoints.moesHeatingSetpoint, value);
            }
        },
    },
    moes_thermostat_deadzone_temperature: {
        key: ["deadzone_temperature"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            if (["_TZE200_5toc8efa", "_TZE204_5toc8efa"].includes(meta.device.manufacturerName)) {
                await sendDataPointValue(entity, dataPoints.moesDeadZoneTemp, value * 10);
            }
            else {
                await sendDataPointValue(entity, dataPoints.moesDeadZoneTemp, value);
            }
        },
    },
    moes_thermostat_calibration: {
        key: ["local_temperature_calibration"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            if (value < 0)
                value = 4096 + value;
            await sendDataPointValue(entity, dataPoints.moesTempCalibration, value);
        },
    },
    moes_thermostat_min_temperature_limit: {
        key: ["min_temperature_limit"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            if (["_TZE200_5toc8efa", "_TZE204_5toc8efa"].includes(meta.device.manufacturerName)) {
                await sendDataPointValue(entity, dataPoints.moesMinTempLimit, value * 10);
            }
            else {
                await sendDataPointValue(entity, dataPoints.moesMinTempLimit, value);
            }
        },
    },
    moes_thermostat_max_temperature_limit: {
        key: ["max_temperature_limit"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            if (["_TZE200_5toc8efa", "_TZE204_5toc8efa"].includes(meta.device.manufacturerName)) {
                await sendDataPointValue(entity, dataPoints.moesMaxTempLimit, value * 10);
            }
            else {
                await sendDataPointValue(entity, dataPoints.moesMaxTempLimit, value);
            }
        },
    },
    moes_thermostat_mode: {
        key: ["preset"],
        convertSet: async (entity, key, value, meta) => {
            const hold = value === "hold" ? 0 : 1;
            const schedule = value === "program" ? 0 : 1;
            await sendDataPointEnum(entity, dataPoints.moesHold, hold);
            await sendDataPointEnum(entity, dataPoints.moesScheduleEnable, schedule);
        },
    },
    moes_thermostat_mode2: {
        key: ["system_mode"],
        convertSet: async (entity, key, value, meta) => {
            // const stateLookup: KeyValueAny = {'0': 'cool', '1': 'heat', '2': 'fan_only'};
            switch (value) {
                case "off":
                    await sendDataPointBool(entity, dataPoints.moesSsystemMode, 0);
                    break;
                case "cool":
                    // turn on
                    await sendDataPointBool(entity, dataPoints.moesSsystemMode, 1);
                    await sendDataPointEnum(entity, dataPoints.tvMode, 0);
                    break;
                case "heat":
                    // turn on
                    await sendDataPointBool(entity, dataPoints.moesSsystemMode, 1);
                    await sendDataPointEnum(entity, dataPoints.tvMode, 1);
                    break;
                case "fan_only":
                    // turn on
                    await sendDataPointBool(entity, dataPoints.moesSsystemMode, 1);
                    await sendDataPointEnum(entity, dataPoints.tvMode, 2);
                    // await sendDataPointEnum(entity, dataPoints.moesScheduleEnable, 0);
                    break;
            }
        },
    },
    moes_thermostat_standby: {
        key: ["system_mode"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, dataPoints.state, value === "heat");
        },
    },
    moes_thermostat_program_schedule: {
        key: ["program"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            if (!meta.state.program) {
                logger_1.logger.warning("Existing program state not set.", "zhc:legacy:tz:moes_bht_002");
                return;
            }
            /* Merge modified value into existing state and send all over in one go */
            const newProgram = {
                // @ts-expect-error ignore
                ...meta.state.program,
                ...value,
            };
            const payload = [
                Math.floor(newProgram.weekdays_p1_hour),
                Math.floor(newProgram.weekdays_p1_minute),
                Math.round(newProgram.weekdays_p1_temperature * 2),
                Math.floor(newProgram.weekdays_p2_hour),
                Math.floor(newProgram.weekdays_p2_minute),
                Math.round(newProgram.weekdays_p2_temperature * 2),
                Math.floor(newProgram.weekdays_p3_hour),
                Math.floor(newProgram.weekdays_p3_minute),
                Math.round(newProgram.weekdays_p3_temperature * 2),
                Math.floor(newProgram.weekdays_p4_hour),
                Math.floor(newProgram.weekdays_p4_minute),
                Math.round(newProgram.weekdays_p4_temperature * 2),
                Math.floor(newProgram.saturday_p1_hour),
                Math.floor(newProgram.saturday_p1_minute),
                Math.round(newProgram.saturday_p1_temperature * 2),
                Math.floor(newProgram.saturday_p2_hour),
                Math.floor(newProgram.saturday_p2_minute),
                Math.round(newProgram.saturday_p2_temperature * 2),
                Math.floor(newProgram.saturday_p3_hour),
                Math.floor(newProgram.saturday_p3_minute),
                Math.round(newProgram.saturday_p3_temperature * 2),
                Math.floor(newProgram.saturday_p4_hour),
                Math.floor(newProgram.saturday_p4_minute),
                Math.round(newProgram.saturday_p4_temperature * 2),
                Math.floor(newProgram.sunday_p1_hour),
                Math.floor(newProgram.sunday_p1_minute),
                Math.round(newProgram.sunday_p1_temperature * 2),
                Math.floor(newProgram.sunday_p2_hour),
                Math.floor(newProgram.sunday_p2_minute),
                Math.round(newProgram.sunday_p2_temperature * 2),
                Math.floor(newProgram.sunday_p3_hour),
                Math.floor(newProgram.sunday_p3_minute),
                Math.round(newProgram.sunday_p3_temperature * 2),
                Math.floor(newProgram.sunday_p4_hour),
                Math.floor(newProgram.sunday_p4_minute),
                Math.round(newProgram.sunday_p4_temperature * 2),
            ];
            await sendDataPointRaw(entity, dataPoints.moesSchedule, Buffer.from(payload));
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    moesS_thermostat_system_mode: {
        key: ["system_mode"],
        convertSet: (entity, key, value, meta) => {
            return { state: { system_mode: "heat" } };
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    moesS_thermostat_preset: {
        key: ["preset"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            const lookup = { programming: 0, manual: 1, temporary_manual: 2, holiday: 3 };
            await sendDataPointEnum(entity, dataPoints.moesSsystemMode, lookup[value]);
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    moesS_thermostat_current_heating_setpoint: {
        key: ["current_heating_setpoint"],
        convertSet: async (entity, key, value, meta) => {
            utils.assertNumber(value);
            const temp = Math.round(value);
            await sendDataPointValue(entity, dataPoints.moesSheatingSetpoint, temp);
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    moesS_thermostat_boost_heating: {
        key: ["boost_heating"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, dataPoints.moesSboostHeating, value === "ON");
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    moesS_thermostat_window_detection: {
        key: ["window_detection"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, dataPoints.moesSwindowDetectionFunktion_A2, value === "ON");
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    moesS_thermostat_child_lock: {
        key: ["child_lock"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, dataPoints.moesSchildLock, value === "LOCK");
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    moesS_thermostat_boostHeatingCountdownTimeSet: {
        key: ["boost_heating_countdown_time_set"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointValue(entity, dataPoints.moesSboostHeatingCountdownTimeSet, value);
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    moesS_thermostat_temperature_calibration: {
        key: ["local_temperature_calibration"],
        convertSet: async (entity, key, value, meta) => {
            utils.assertNumber(value);
            let temp = Math.round(value * 1);
            if (temp < 0) {
                temp = 0xffffffff + temp + 1;
            }
            await sendDataPointValue(entity, dataPoints.moesScompensationTempSet, temp);
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    moesS_thermostat_moesSecoMode: {
        key: ["eco_mode"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, dataPoints.moesSecoMode, value === "ON");
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    moesS_thermostat_eco_temperature: {
        key: ["eco_temperature"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            const temp = Math.round(value);
            await sendDataPointValue(entity, dataPoints.moesSecoModeTempSet, temp);
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    moesS_thermostat_max_temperature: {
        key: ["max_temperature"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            const temp = Math.round(value);
            await sendDataPointValue(entity, dataPoints.moesSmaxTempSet, temp);
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    moesS_thermostat_min_temperature: {
        key: ["min_temperature"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            const temp = Math.round(value);
            await sendDataPointValue(entity, dataPoints.moesSminTempSet, temp);
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    moesS_thermostat_schedule_programming: {
        key: ["programming_mode"],
        convertSet: async (entity, key, value, meta) => {
            utils.assertString(value);
            const payload = [];
            const items = value.split("  ");
            for (let i = 0; i < 12; i++) {
                const hourTemperature = items[i].split("/");
                const hourMinute = hourTemperature[0].split(":", 2);
                const h = Number.parseInt(hourMinute[0], 10);
                const m = Number.parseInt(hourMinute[1], 10);
                const temp = Number.parseInt(hourTemperature[1], 10);
                if (h < 0 || h >= 24 || m < 0 || m >= 60 || temp < 5 || temp >= 35) {
                    throw new Error(`Invalid hour, minute or temperature of:${items[i]}`);
                }
                payload[i * 3] = h;
                payload[i * 3 + 1] = m;
                payload[i * 3 + 2] = temp * 2;
            }
            await sendDataPointRaw(entity, dataPoints.moesSschedule, Buffer.from(payload));
        },
    },
    hgkg_thermostat_standby: {
        key: ["system_mode"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, dataPoints.state, value === "cool");
        },
    },
    moes_switch: {
        key: ["power_on_behavior", "indicate_light"],
        convertSet: async (entity, key, value, meta) => {
            switch (key) {
                case "power_on_behavior":
                    await sendDataPointEnum(entity, dataPoints.moesSwitchPowerOnBehavior, 
                    // @ts-expect-error ignore
                    utils.getKey(moesSwitch.powerOnBehavior, value));
                    break;
                case "indicate_light":
                    await sendDataPointEnum(entity, dataPoints.moesSwitchIndicateLight, 
                    // @ts-expect-error ignore
                    utils.getKey(moesSwitch.indicateLight, value));
                    break;
                default:
                    logger_1.logger.warning(`Unhandled Key ${key}`, "zhc:legacy:tz:moes_switch");
                    break;
            }
        },
    },
    moes_thermostat_sensor: {
        key: ["sensor"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            if (typeof value === "string") {
                value = value.toLowerCase();
                const lookup = { in: 0, al: 1, ou: 2 };
                utils.validateValue(value, Object.keys(lookup));
                value = lookup[value];
            }
            if (typeof value === "number" && value >= 0 && value <= 2) {
                await sendDataPointEnum(entity, dataPoints.moesSensor, value);
            }
            else {
                throw new Error(`Unsupported value: ${value}`);
            }
        },
    },
    tuya_dimmer_state: {
        key: ["state"],
        convertSet: async (entity, key, value, meta) => {
            // Always use same transid as tuya_dimmer_level (https://github.com/Koenkk/zigbee2mqtt/issues/6366)
            await sendDataPointBool(entity, dataPoints.state, value === "ON", "dataRequest", 1);
        },
    },
    tuya_dimmer_level: {
        key: ["brightness_min", "min_brightness", "max_brightness", "brightness", "brightness_percent", "level"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            // upscale to 1000
            // biome-ignore lint/suspicious/noImplicitAnyLet: ignored using `--suppress`
            let newValue;
            let dp = dataPoints.dimmerLevel;
            if (["_TZE200_3p5ydos3", "_TZE200_9i9dt8is", "_TZE200_dfxkcots", "_TZE200_w4cryh2i"].includes(meta.device.manufacturerName)) {
                dp = dataPoints.eardaDimmerLevel;
            }
            if (key === "brightness_min") {
                if (value >= 0 && value <= 100) {
                    newValue = utils.mapNumberRange(value, 0, 100, 0, 1000);
                    dp = dataPoints.dimmerLevel;
                }
                else {
                    throw new Error("Dimmer brightness_min is out of range 0..100");
                }
            }
            else if (key === "min_brightness") {
                if (value >= 1 && value <= 255) {
                    newValue = utils.mapNumberRange(value, 1, 255, 0, 1000);
                    dp = dataPoints.dimmerMinLevel;
                }
                else {
                    throw new Error("Dimmer min_brightness is out of range 1..255");
                }
            }
            else if (key === "max_brightness") {
                if (value >= 1 && value <= 255) {
                    newValue = utils.mapNumberRange(value, 1, 255, 0, 1000);
                    dp = dataPoints.dimmerMaxLevel;
                }
                else {
                    throw new Error("Dimmer min_brightness is out of range 1..255");
                }
            }
            else if (key === "level") {
                if (value >= 0 && value <= 1000) {
                    newValue = Math.round(Number(value));
                }
                else {
                    throw new Error("Dimmer level is out of range 0..1000");
                }
            }
            else if (key === "brightness_percent") {
                if (value >= 0 && value <= 100) {
                    newValue = utils.mapNumberRange(value, 0, 100, 0, 1000);
                }
                else {
                    throw new Error("Dimmer brightness_percent is out of range 0..100");
                }
            }
            else {
                // brightness
                if (value >= 0 && value <= 254) {
                    newValue = utils.mapNumberRange(value, 0, 254, 0, 1000);
                }
                else {
                    throw new Error("Dimmer brightness is out of range 0..254");
                }
            }
            // Always use same transid as tuya_dimmer_state (https://github.com/Koenkk/zigbee2mqtt/issues/6366)
            await sendDataPointValue(entity, dp, newValue, "dataRequest", 1);
        },
    },
    tuya_switch_state: {
        key: ["state"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            const lookup = { l1: 1, l2: 2, l3: 3, l4: 4, l5: 5, l6: 6 };
            const multiEndpoint = utils.getMetaValue(entity, meta.mapped, "multiEndpoint", "allEqual", false);
            const keyid = multiEndpoint ? lookup[meta.endpoint_name] : 1;
            await sendDataPointBool(entity, keyid, value === "ON");
            return { state: { state: value.toUpperCase() } };
        },
    },
    frankever_threshold: {
        key: ["threshold"],
        convertSet: async (entity, key, value, meta) => {
            utils.assertNumber(value);
            // input to multiple of 10 with max value of 100
            const thresh = Math.abs(Math.min(10 * Math.floor(value / 10), 100));
            await sendDataPointValue(entity, dataPoints.frankEverTreshold, thresh, "dataRequest", 1);
            return { state: { threshold: value } };
        },
    },
    frankever_timer: {
        key: ["timer"],
        convertSet: async (entity, key, value, meta) => {
            utils.assertNumber(value);
            // input in minutes with maximum of 600 minutes (equals 10 hours)
            const timer = 60 * Math.abs(Math.min(value, 600));
            // sendTuyaDataPoint* functions take care of converting the data to proper format
            await sendDataPointValue(entity, dataPoints.frankEverTimer, timer, "dataRequest", 1);
            return { state: { timer: value } };
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    ZVG1_timer: {
        key: ["timer"],
        convertSet: async (entity, key, value, meta) => {
            utils.assertNumber(value);
            // input in minutes with maximum of 600 minutes (equals 10 hours)
            const timer = 60 * Math.abs(Math.min(value, 600));
            // sendTuyaDataPoint* functions take care of converting the data to proper format
            await sendDataPointValue(entity, 11, timer, "dataRequest", 1);
            return { state: { timer: value } };
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    ZVG1_weather_delay: {
        key: ["weather_delay"],
        convertSet: async (entity, key, value, meta) => {
            utils.assertString(value);
            const lookup = { disabled: 0, "24h": 1, "48h": 2, "72h": 3 };
            await sendDataPointEnum(entity, 10, lookup[value]);
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    ZVG1_cycle_timer: {
        key: ["cycle_timer_1", "cycle_timer_2", "cycle_timer_3", "cycle_timer_4"],
        convertSet: async (entity, key, value, meta) => {
            utils.assertString(value);
            let data = [0];
            const footer = [0x64];
            if (value === "") {
                // delete
                data.push(0x04);
                data.push(Number.parseInt(key.substr(-1), 10));
                await sendDataPointRaw(entity, 16, Buffer.from(data));
                const ret = { state: {} };
                ret.state[key] = value;
                return ret;
            }
            if ((meta.state[key] !== undefined && meta.state[key] === "") || meta.state[key] === undefined) {
                data.push(0x03);
            }
            else {
                data.push(0x02);
                data.push(Number.parseInt(key.substr(-1), 10));
            }
            const tarray = value.replace(/ /g, "").split("/");
            if (tarray.length < 4) {
                throw new Error("Please check the format of the timer string");
            }
            if (tarray.length < 5) {
                tarray.push("MoTuWeThFrSaSu");
            }
            if (tarray.length < 6) {
                tarray.push("1");
            }
            const starttime = tarray[0];
            const endtime = tarray[1];
            const irrigationDuration = tarray[2];
            const pauseDuration = tarray[3];
            const weekdays = tarray[4];
            const active = Number.parseInt(tarray[5], 10);
            if (!(active === 0 || active === 1)) {
                throw new Error("Active value only 0 or 1 allowed");
            }
            data.push(active);
            const weekdaysPart = convertWeekdaysTo1ByteHexArray(weekdays);
            data = data.concat(weekdaysPart);
            data = data.concat(convertTimeTo2ByteHexArray(starttime));
            data = data.concat(convertTimeTo2ByteHexArray(endtime));
            data = data.concat(convertDecimalValueTo2ByteHexArray(irrigationDuration));
            data = data.concat(convertDecimalValueTo2ByteHexArray(pauseDuration));
            data = data.concat(footer);
            await sendDataPointRaw(entity, 16, Buffer.from(data));
            const ret = { state: {} };
            ret.state[key] = value;
            return ret;
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    ZVG1_normal_schedule_timer: {
        key: ["normal_schedule_timer_1", "normal_schedule_timer_2", "normal_schedule_timer_3", "normal_schedule_timer_4"],
        convertSet: async (entity, key, value, meta) => {
            utils.assertString(value);
            let data = [0];
            const footer = [0x07, 0xe6, 0x08, 0x01, 0x01];
            if (value === "") {
                // delete
                data.push(0x04);
                data.push(Number.parseInt(key.substr(-1), 10));
                await sendDataPointRaw(entity, 17, Buffer.from(data));
                const ret = { state: {} };
                ret.state[key] = value;
                return ret;
            }
            if ((meta.state[key] !== undefined && meta.state[key] === "") || meta.state[key] === undefined) {
                data.push(0x03);
            }
            else {
                data.push(0x02);
                data.push(Number.parseInt(key.substr(-1), 10));
            }
            const tarray = value.replace(/ /g, "").split("/");
            if (tarray.length < 2) {
                throw new Error("Please check the format of the timer string");
            }
            if (tarray.length < 3) {
                tarray.push("MoTuWeThFrSaSu");
            }
            if (tarray.length < 4) {
                tarray.push("1");
            }
            const time = tarray[0];
            const duration = tarray[1];
            const weekdays = tarray[2];
            const active = Number.parseInt(tarray[3], 10);
            if (!(active === 0 || active === 1)) {
                throw new Error("Active value only 0 or 1 allowed");
            }
            data = data.concat(convertTimeTo2ByteHexArray(time));
            const durationPart = convertDecimalValueTo2ByteHexArray(duration);
            data = data.concat(durationPart);
            const weekdaysPart = convertWeekdaysTo1ByteHexArray(weekdays);
            data = data.concat(weekdaysPart);
            data = data.concat([64, active]);
            data = data.concat(footer);
            await sendDataPointRaw(entity, 17, Buffer.from(data));
            const ret = { state: {} };
            ret.state[key] = value;
            return ret;
        },
    },
    etop_thermostat_system_mode: {
        key: ["system_mode"],
        convertSet: async (entity, key, value, meta) => {
            switch (value) {
                case "off":
                    await sendDataPointBool(entity, dataPoints.state, false);
                    break;
                case "heat":
                    await sendDataPointBool(entity, dataPoints.state, true);
                    await utils.sleep(500);
                    await sendDataPointEnum(entity, dataPoints.mode, 0 /* manual */);
                    break;
                case "auto":
                    await sendDataPointBool(entity, dataPoints.state, true);
                    await utils.sleep(500);
                    await sendDataPointEnum(entity, dataPoints.mode, 2 /* auto */);
                    break;
            }
        },
    },
    etop_thermostat_away_mode: {
        key: ["away_mode"],
        convertSet: async (entity, key, value, meta) => {
            switch (value) {
                case "ON":
                    await sendDataPointBool(entity, dataPoints.state, true);
                    await utils.sleep(500);
                    await sendDataPointEnum(entity, dataPoints.mode, 1 /* away */);
                    break;
                case "OFF":
                    await sendDataPointEnum(entity, dataPoints.mode, 0 /* manual */);
                    break;
            }
        },
    },
    tuya_thermostat_weekly_schedule: {
        key: ["weekly_schedule"],
        convertSet: async (entity, key, value, meta) => {
            const thermostatMeta = utils.getMetaValue(entity, meta.mapped, "thermostat");
            // @ts-expect-error ignore
            const maxTransitions = thermostatMeta.weeklyScheduleMaxTransitions;
            // @ts-expect-error ignore
            const supportedModes = thermostatMeta.weeklyScheduleSupportedModes;
            // @ts-expect-error ignore
            const firstDayDpId = thermostatMeta.weeklyScheduleFirstDayDpId;
            let conversion = "generic";
            if (utils.isObject(thermostatMeta) && thermostatMeta.weeklyScheduleConversion !== undefined) {
                conversion = thermostatMeta.weeklyScheduleConversion;
            }
            function transitionToData(transition) {
                // Later it is possible to move converter to meta or to other place outside if other type of converter
                // will be needed for other device. Currently this converter is based on ETOP HT-08 thermostat.
                // see also fromZigbee.tuya_thermostat_weekly_schedule()
                const minutesSinceMidnight = transition.transitionTime;
                const heatSetpoint = Math.floor(transition.heatSetpoint * 10);
                return [(minutesSinceMidnight & 0xff00) >> 8, minutesSinceMidnight & 0xff, (heatSetpoint & 0xff00) >> 8, heatSetpoint & 0xff];
            }
            for (const [, daySchedule] of Object.entries(value)) {
                const dayofweek = Number.parseInt(daySchedule.dayofweek, 10);
                const numoftrans = Number.parseInt(daySchedule.numoftrans, 10);
                let transitions = [...daySchedule.transitions];
                const mode = Number.parseInt(daySchedule.mode, 10);
                if (!supportedModes.includes(mode)) {
                    throw new Error(`Invalid mode: ${mode} for device ${meta.options.friendly_name}`);
                }
                if (numoftrans !== transitions.length) {
                    throw new Error(`Invalid numoftrans provided. Real: ${transitions.length} ` +
                        `provided ${numoftrans} for device ${meta.options.friendly_name}`);
                }
                if (transitions.length > maxTransitions) {
                    throw new Error(`Too more transitions provided. Provided: ${transitions.length} ` +
                        `but supports only ${numoftrans} for device ${meta.options.friendly_name}`);
                }
                if (transitions.length < maxTransitions) {
                    logger_1.logger.warning(`Padding transitions from ${transitions.length} to ${maxTransitions} with last item for device ${meta.options.friendly_name}`, "zhc:legacy:tz:tuya_thermostat_weekly_schedule");
                    const lastTransition = transitions[transitions.length - 1];
                    while (transitions.length !== maxTransitions) {
                        transitions = [...transitions, lastTransition];
                    }
                }
                const payload = [];
                if (conversion === "saswell") {
                    // Single data point for setting schedule
                    // [
                    //     bitmap of days: |  7|  6|  5|  4|  3|  2|  1|
                    //                     |Sat|Fri|Thu|Wed|Tue|Mon|Sun|,
                    //     schedule mode - see legacy.thermostatScheduleMode, currently
                    //                     no known devices support modes other than "7 day"
                    //     4 transitions:
                    //       minutes from midnight high byte
                    //       minutes from midnight low byte
                    //       temperature * 10 high byte
                    //       temperature * 10 low byte
                    // ]
                    payload.push(1 << (dayofweek - 1), 4);
                }
                transitions.forEach((transition) => {
                    payload.push(...transitionToData(transition));
                });
                if (conversion === "saswell") {
                    await sendDataPointRaw(entity, dataPoints.saswellScheduleSet, Buffer.from(payload));
                }
                else {
                    await sendDataPointRaw(entity, firstDayDpId - 1 + dayofweek, Buffer.from(payload));
                }
            }
        },
    },
    tuya_thermostat_child_lock: {
        key: ["child_lock"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, dataPoints.childLock, value === "LOCK");
        },
    },
    tuya_thermostat_window_detection: {
        key: ["window_detection"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointRaw(entity, dataPoints.windowDetection, Buffer.from([value === "ON" ? 1 : 0]));
        },
    },
    siterwell_thermostat_window_detection: {
        key: ["window_detection"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, dataPoints.siterwellWindowDetection, value === "ON");
        },
    },
    tuya_thermostat_valve_detection: {
        key: ["valve_detection"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, dataPoints.valveDetection, value === "ON");
        },
    },
    tuya_thermostat_current_heating_setpoint: {
        key: ["current_heating_setpoint"],
        convertSet: async (entity, key, value, meta) => {
            utils.assertNumber(value);
            const temp = Math.round(value * 10);
            await sendDataPointValue(entity, dataPoints.heatingSetpoint, temp);
        },
    },
    tuya_thermostat_system_mode: {
        key: ["system_mode"],
        convertSet: async (entity, key, value, meta) => {
            const modeId = utils.getKey(utils.getMetaValue(entity, meta.mapped, "tuyaThermostatSystemMode"), value, null, Number);
            if (modeId !== null) {
                await sendDataPointEnum(entity, dataPoints.mode, modeId);
            }
            else {
                throw new Error(`TRV system mode ${value} is not recognized.`);
            }
        },
    },
    tuya_thermostat_preset: {
        key: ["preset"],
        convertSet: async (entity, key, value, meta) => {
            const presetId = utils.getKey(utils.getMetaValue(entity, meta.mapped, "tuyaThermostatPreset"), value, null, Number);
            if (presetId !== null) {
                await sendDataPointEnum(entity, dataPoints.mode, presetId);
            }
            else {
                throw new Error(`TRV preset ${value} is not recognized.`);
            }
        },
    },
    tuya_thermostat_away_mode: {
        key: ["away_mode"],
        convertSet: async (entity, key, value, meta) => {
            // HA has special behavior for the away mode
            const awayPresetId = utils.getKey(utils.getMetaValue(entity, meta.mapped, "tuyaThermostatPreset"), "away", null, Number);
            const schedulePresetId = utils.getKey(utils.getMetaValue(entity, meta.mapped, "tuyaThermostatPreset"), "schedule", null, Number);
            if (awayPresetId !== null) {
                if (value === "ON") {
                    await sendDataPointEnum(entity, dataPoints.mode, awayPresetId);
                }
                else if (schedulePresetId != null) {
                    await sendDataPointEnum(entity, dataPoints.mode, schedulePresetId);
                }
                // In case 'OFF' tuya_thermostat_preset() should be called with another preset
            }
            else {
                throw new Error(`TRV preset ${value} is not recognized.`);
            }
        },
    },
    tuya_thermostat_fan_mode: {
        key: ["fan_mode"],
        convertSet: async (entity, key, value, meta) => {
            const modeId = utils.getKey(fanModes, value, null, Number);
            if (modeId !== null) {
                await sendDataPointEnum(entity, dataPoints.fanMode, modeId);
            }
            else {
                throw new Error(`TRV fan mode ${value} is not recognized.`);
            }
        },
    },
    tuya_thermostat_bac_fan_mode: {
        key: ["fan_mode"],
        convertSet: async (entity, key, value, meta) => {
            const modeId = utils.getKey(fanModes, value, null, Number);
            if (modeId !== null) {
                await sendDataPointEnum(entity, dataPoints.bacFanMode, modeId);
            }
            else {
                throw new Error(`TRV fan mode ${value} is not recognized.`);
            }
        },
    },
    tuya_thermostat_auto_lock: {
        key: ["auto_lock"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, dataPoints.autoLock, value === "AUTO");
        },
    },
    tuya_thermostat_calibration: {
        key: ["local_temperature_calibration"],
        convertSet: async (entity, key, value, meta) => {
            utils.assertNumber(value);
            let temp = Math.round(value * 10);
            if (temp < 0) {
                temp = 0xffffffff + temp + 1;
            }
            await sendDataPointValue(entity, dataPoints.tempCalibration, temp);
        },
    },
    tuya_thermostat_min_temp: {
        key: ["min_temperature"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointValue(entity, dataPoints.minTemp, value);
        },
    },
    tuya_thermostat_max_temp: {
        key: ["max_temperature"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointValue(entity, dataPoints.maxTemp, value);
        },
    },
    tuya_thermostat_boost_time: {
        key: ["boost_time"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointValue(entity, dataPoints.boostTime, value);
        },
    },
    tuya_thermostat_comfort_temp: {
        key: ["comfort_temperature"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointValue(entity, dataPoints.comfortTemp, value);
        },
    },
    tuya_thermostat_eco_temp: {
        key: ["eco_temperature"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointValue(entity, dataPoints.ecoTemp, value);
        },
    },
    tuya_thermostat_force: {
        key: ["force"],
        convertSet: async (entity, key, value, meta) => {
            const modeId = utils.getKey(thermostatForceMode, value, null, Number);
            if (modeId !== null) {
                await sendDataPointEnum(entity, dataPoints.forceMode, modeId);
            }
            else {
                throw new Error(`TRV force mode ${value} is not recognized.`);
            }
        },
    },
    tuya_thermostat_force_to_mode: {
        key: ["system_mode"],
        convertSet: async (entity, key, value, meta) => {
            const modeId = utils.getKey(utils.getMetaValue(entity, meta.mapped, "tuyaThermostatSystemMode"), value, null, Number);
            if (modeId !== null) {
                await sendDataPointEnum(entity, dataPoints.forceMode, modeId);
            }
            else {
                throw new Error(`TRV system mode ${value} is not recognized.`);
            }
        },
    },
    tuya_thermostat_away_preset: {
        key: ["away_preset_temperature", "away_preset_days"],
        convertSet: async (entity, key, value, meta) => {
            switch (key) {
                case "away_preset_days":
                    await sendDataPointValue(entity, dataPoints.awayDays, value);
                    break;
                case "away_preset_temperature":
                    await sendDataPointValue(entity, dataPoints.awayTemp, value);
                    break;
            }
        },
    },
    tuya_thermostat_window_detect: {
        // payload example { "detect":"OFF", "temperature":5, "minutes":8}
        key: ["window_detect"],
        convertSet: async (entity, key, value, meta) => {
            const detect = value.detect.toUpperCase() === "ON" ? 1 : 0;
            await sendDataPointRaw(entity, dataPoints.windowDetection, Buffer.from([detect, value.temperature, value.minutes]));
        },
    },
    tuya_thermostat_schedule: {
        // payload example {"holidays":[{"hour":6,"minute":0,"temperature":20},{"hour":8,"minute":0,....  6x
        key: ["schedule"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            const prob = Object.keys(value)[0]; // "workdays" or "holidays"
            if (prob === "workdays" || prob === "holidays") {
                const dpId = prob === "workdays" ? dataPoints.scheduleWorkday : dataPoints.scheduleHoliday;
                const payload = [];
                for (let i = 0; i < 6; i++) {
                    if (value[prob][i].hour >= 0 && value[prob][i].hour < 24) {
                        payload[i * 3] = value[prob][i].hour;
                    }
                    if (value[prob][i].minute >= 0 && value[prob][i].minute < 60) {
                        payload[i * 3 + 1] = value[prob][i].minute;
                    }
                    if (value[prob][i].temperature >= 5 && value[prob][i].temperature < 35) {
                        payload[i * 3 + 2] = value[prob][i].temperature;
                    }
                }
                await sendDataPointRaw(entity, dpId, Buffer.from(payload));
            }
        },
    },
    tuya_thermostat_schedule_programming_mode: {
        // payload example "00:20/5°C 01:20/5°C 6:59/15°C 18:00/5°C 20:00/5°C 23:30/5°C"
        key: ["workdays_schedule", "holidays_schedule"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            const dpId = key === "workdays_schedule" ? dataPoints.scheduleWorkday : dataPoints.scheduleHoliday;
            const payload = [];
            const items = value.split(" ");
            for (let i = 0; i < 6; i++) {
                const hourTemperature = items[i].split("/");
                const hourMinute = hourTemperature[0].split(":", 2);
                const hour = Number.parseInt(hourMinute[0], 10);
                const minute = Number.parseInt(hourMinute[1], 10);
                const temperature = Number.parseInt(hourTemperature[1], 10);
                if (hour < 0 || hour >= 24 || minute < 0 || minute >= 60 || temperature < 5 || temperature >= 35) {
                    throw new Error(`Invalid hour, minute or temperature of:${items[i]}`);
                }
                payload[i * 3] = hour;
                payload[i * 3 + 1] = minute;
                payload[i * 3 + 2] = temperature;
            }
            await sendDataPointRaw(entity, dpId, Buffer.from(payload));
        },
    },
    tuya_thermostat_week: {
        key: ["week"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            const lookup = { "5+2": 0, "6+1": 1, "7": 2 };
            const week = lookup[value];
            await sendDataPointEnum(entity, dataPoints.weekFormat, week);
            return { state: { week: value } };
        },
    },
    tuya_cover_options: {
        key: ["options"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            if (value.reverse_direction != null) {
                if (value.reverse_direction) {
                    logger_1.logger.info("Motor direction reverse", "zhc:legacy:tz:tuya_cover_options");
                    await sendDataPointEnum(entity, dataPoints.motorDirection, 1);
                }
                else {
                    logger_1.logger.info("Motor direction forward", "zhc:legacy:tz:tuya_cover_options");
                    await sendDataPointEnum(entity, dataPoints.motorDirection, 0);
                }
            }
            if (value.motor_speed != null) {
                if (value.motor_speed < 0 || value.motor_speed > 255) {
                    throw new Error("Tuya_cover_control: Motor speed is out of range");
                }
                logger_1.logger.info(`Setting motor speed to ${value.motor_speed}`, "zhc:legacy:tz:tuya_cover_options");
                await sendDataPointValue(entity, dataPoints.coverSpeed, value.motor_speed);
            }
        },
    },
    neo_nas_pd07: {
        key: ["temperature_max", "temperature_min", "humidity_max", "humidity_min", "temperature_scale", "unknown_111", "unknown_112"],
        convertSet: async (entity, key, value, meta) => {
            switch (key) {
                case "temperature_max":
                    await sendDataPointValue(entity, dataPoints.neoMaxTemp, value);
                    break;
                case "temperature_min":
                    await sendDataPointValue(entity, dataPoints.neoMinTemp, value);
                    break;
                case "humidity_max":
                    await sendDataPointValue(entity, dataPoints.neoMaxHumidity, value);
                    break;
                case "humidity_min":
                    await sendDataPointValue(entity, dataPoints.neoMinHumidity, value);
                    break;
                case "temperature_scale":
                    await sendDataPointBool(entity, dataPoints.neoTempScale, value === "°C");
                    break;
                case "unknown_111":
                    await sendDataPointBool(entity, 111, value === "ON");
                    break;
                case "unknown_112":
                    await sendDataPointBool(entity, 112, value === "ON");
                    break;
                default: // Unknown key
                    throw new Error(`tz.neo_nas_pd07: Unhandled key ${key}`);
            }
        },
    },
    neo_t_h_alarm: {
        key: [
            "alarm",
            "melody",
            "volume",
            "duration",
            "temperature_max",
            "temperature_min",
            "humidity_min",
            "humidity_max",
            "temperature_alarm",
            "humidity_alarm",
        ],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            switch (key) {
                case "alarm":
                    await sendDataPointBool(entity, dataPoints.neoAlarm, value);
                    break;
                case "melody":
                    await sendDataPointEnum(entity, dataPoints.neoMelody, Number.parseInt(value, 10));
                    break;
                case "volume":
                    await sendDataPointEnum(entity, dataPoints.neoVolume, 
                    // @ts-expect-error ignore
                    { low: 2, medium: 1, high: 0 }[value]);
                    break;
                case "duration":
                    await sendDataPointValue(entity, dataPoints.neoDuration, value);
                    break;
                case "temperature_max":
                    await sendDataPointValue(entity, dataPoints.neoMaxTemp, value);
                    break;
                case "temperature_min":
                    await sendDataPointValue(entity, dataPoints.neoMinTemp, value);
                    break;
                case "humidity_max":
                    await sendDataPointValue(entity, dataPoints.neoMaxHumidity, value);
                    break;
                case "humidity_min":
                    await sendDataPointValue(entity, dataPoints.neoMinHumidity, value);
                    break;
                case "temperature_alarm":
                    await sendDataPointBool(entity, dataPoints.neoTempAlarm, value);
                    break;
                case "humidity_alarm":
                    await sendDataPointBool(entity, dataPoints.neoHumidityAlarm, value);
                    break;
                default: // Unknown key
                    throw new Error(`tz.neo_t_h_alarm: Unhandled key ${key}`);
            }
        },
    },
    neo_alarm: {
        key: ["alarm", "melody", "volume", "duration"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            switch (key) {
                case "alarm":
                    await sendDataPointBool(entity, dataPoints.neoAOAlarm, value);
                    break;
                case "melody":
                    await sendDataPointEnum(entity, dataPoints.neoAOMelody, Number.parseInt(value, 10));
                    break;
                case "volume":
                    await sendDataPointEnum(entity, dataPoints.neoAOVolume, 
                    // @ts-expect-error ignore
                    { low: 0, medium: 1, high: 2 }[value]);
                    break;
                case "duration":
                    await sendDataPointValue(entity, dataPoints.neoAODuration, value);
                    break;
                default: // Unknown key
                    throw new Error(`Unhandled key ${key}`);
            }
        },
    },
    nous_lcd_temperature_humidity_sensor: {
        key: [
            "min_temperature",
            "max_temperature",
            "temperature_sensitivity",
            "temperature_unit_convert",
            "temperature_report_interval",
            "min_humidity",
            "max_humidity",
            "humidity_sensitivity",
            "humidity_report_interval",
        ],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            switch (key) {
                case "temperature_unit_convert":
                    await sendDataPointEnum(entity, dataPoints.nousTempUnitConvert, ["celsius", "fahrenheit"].indexOf(value));
                    break;
                case "min_temperature":
                    await sendDataPointValue(entity, dataPoints.nousMinTemp, Math.round(value * 10));
                    break;
                case "max_temperature":
                    await sendDataPointValue(entity, dataPoints.nousMaxTemp, Math.round(value * 10));
                    break;
                case "temperature_sensitivity":
                    await sendDataPointValue(entity, dataPoints.nousTempSensitivity, Math.round(value * 10));
                    break;
                case "humidity_sensitivity":
                    await sendDataPointValue(entity, dataPoints.nousHumiSensitivity, value);
                    break;
                case "min_humidity":
                    await sendDataPointValue(entity, dataPoints.nousMinHumi, Math.round(value));
                    break;
                case "max_humidity":
                    await sendDataPointValue(entity, dataPoints.nousMaxHumi, Math.round(value));
                    break;
                case "temperature_report_interval":
                    await sendDataPointValue(entity, dataPoints.nousTempReportInterval, value);
                    break;
                case "humidity_report_interval":
                    await sendDataPointValue(entity, dataPoints.nousHumiReportInterval, value);
                    break;
                default: // Unknown key
                    logger_1.logger.warning(`Unhandled key ${key}`, "zhc:legacy:tz:nous_lcd_temperature_humidity_sensor");
            }
        },
    },
    saswell_thermostat_current_heating_setpoint: {
        key: ["current_heating_setpoint"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            const temp = Math.round(value * 10);
            await sendDataPointValue(entity, dataPoints.saswellHeatingSetpoint, temp);
        },
    },
    saswell_thermostat_mode: {
        key: ["system_mode"],
        convertSet: async (entity, key, value, meta) => {
            const schedule = value === "auto";
            const enable = !(value === "off");
            await sendDataPointBool(entity, dataPoints.saswellState, enable);
            // Older versions of Saswell TRVs need the delay to work reliably
            await utils.sleep(3000);
            await sendDataPointBool(entity, dataPoints.saswellScheduleEnable, schedule);
        },
    },
    saswell_thermostat_away: {
        key: ["away_mode"],
        convertSet: async (entity, key, value, meta) => {
            if (value === "ON") {
                await sendDataPointBool(entity, dataPoints.saswellAwayMode, true);
            }
            else {
                await sendDataPointBool(entity, dataPoints.saswellAwayMode, false);
            }
        },
    },
    saswell_thermostat_child_lock: {
        key: ["child_lock"],
        convertSet: async (entity, key, value, meta) => {
            // It seems that currently child lock can be sent and device responds,
            // but it's not entering lock state
            await sendDataPointBool(entity, dataPoints.saswellChildLock, value === "LOCK");
        },
    },
    saswell_thermostat_window_detection: {
        key: ["window_detection"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, dataPoints.saswellWindowDetection, value === "ON");
        },
    },
    saswell_thermostat_frost_detection: {
        key: ["frost_detection"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, dataPoints.saswellFrostDetection, value === "ON");
        },
    },
    saswell_thermostat_anti_scaling: {
        key: ["anti_scaling"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, dataPoints.saswellAntiScaling, value === "ON");
        },
    },
    saswell_thermostat_calibration: {
        key: ["local_temperature_calibration"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            if (value < 0)
                value = 0xffffffff + value + 1;
            await sendDataPointValue(entity, dataPoints.saswellTempCalibration, value);
        },
    },
    evanell_thermostat_current_heating_setpoint: {
        key: ["current_heating_setpoint"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            const temp = Math.round(value * 10);
            await sendDataPointValue(entity, dataPoints.evanellHeatingSetpoint, temp);
        },
    },
    evanell_thermostat_system_mode: {
        key: ["system_mode"],
        convertSet: async (entity, key, value, meta) => {
            switch (value) {
                case "off":
                    await sendDataPointEnum(entity, dataPoints.evanellMode, 3 /* off */);
                    break;
                case "heat":
                    await sendDataPointEnum(entity, dataPoints.evanellMode, 2 /* manual */);
                    break;
                case "auto":
                    await sendDataPointEnum(entity, dataPoints.evanellMode, 0 /* auto */);
                    break;
            }
        },
    },
    evanell_thermostat_child_lock: {
        key: ["child_lock"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, dataPoints.evanellChildLock, value === "LOCK");
        },
    },
    silvercrest_smart_led_string: {
        key: ["color", "brightness", "effect"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            if (key === "effect") {
                await sendDataPointEnum(entity, dataPoints.silvercrestChangeMode, silvercrestModes.effect);
                // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
                let data = [];
                const effect = silvercrestEffects[value.effect];
                data = data.concat(convertStringToHexArray(effect));
                let speed = utils.mapNumberRange(value.speed, 0, 100, 0, 64);
                // Max speed what the gateways sends is 64.
                if (speed > 64) {
                    speed = 64;
                }
                // Make it a string and attach a leading zero (0x30)
                let speedString = String(speed);
                if (speedString.length === 1) {
                    speedString = `0${speedString}`;
                }
                if (!speedString) {
                    speedString = "00";
                }
                data = data.concat(convertStringToHexArray(speedString));
                let colors = value.colors;
                // @ts-expect-error ignore
                if (!colors && meta.state && meta.state.effect && meta.state.effect.colors) {
                    // @ts-expect-error ignore
                    colors = meta.state.effect.colors;
                }
                if (colors) {
                    for (const color of colors) {
                        let r = "00";
                        let g = "00";
                        let b = "00";
                        if (color.r) {
                            r = color.r.toString(16);
                        }
                        if (r.length === 1) {
                            r = `0${r}`;
                        }
                        if (color.g) {
                            g = color.g.toString(16);
                        }
                        if (g.length === 1) {
                            g = `0${g}`;
                        }
                        if (color.b) {
                            b = color.b.toString(16);
                        }
                        if (b.length === 1) {
                            b = `0${b}`;
                        }
                        data = data.concat(convertStringToHexArray(r));
                        data = data.concat(convertStringToHexArray(g));
                        data = data.concat(convertStringToHexArray(b));
                    }
                }
                await sendDataPointStringBuffer(entity, dataPoints.silvercrestSetEffect, data);
            }
            else if (key === "brightness") {
                await sendDataPointEnum(entity, dataPoints.silvercrestChangeMode, silvercrestModes.white);
                // It expects 2 leading zero's.
                let data = [0x00, 0x00];
                // Scale it to what the device expects (0-1000 instead of 0-255)
                const scaled = utils.mapNumberRange(value, 0, 255, 0, 1000);
                data = data.concat(convertDecimalValueTo2ByteHexArray(scaled));
                await sendDataPoint(entity, { dp: dataPoints.silvercrestSetBrightness, datatype: dataTypes.value, data: Buffer.from(data) });
            }
            else if (key === "color") {
                await sendDataPointEnum(entity, dataPoints.silvercrestChangeMode, silvercrestModes.color);
                const make4sizedString = (v) => {
                    if (v.length >= 4) {
                        return v;
                    }
                    if (v.length === 3) {
                        return `0${v}`;
                    }
                    if (v.length === 2) {
                        return `00${v}`;
                    }
                    if (v.length === 1) {
                        return `000${v}`;
                    }
                    return "0000";
                };
                // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
                const fillInHSB = (h, s, b, state) => {
                    // Define default values. Device expects leading zero in string.
                    const hsb = {
                        h: "0168", // 360
                        s: "03e8", // 1000
                        b: "03e8", // 1000
                    };
                    if (h) {
                        // The device expects 0-359
                        // The device expects a round number, otherwise everything breaks
                        hsb.h = make4sizedString(utils.numberWithinRange(utils.precisionRound(h, 0), 0, 359).toString(16));
                    }
                    else if (state.color?.h) {
                        hsb.h = make4sizedString(utils.numberWithinRange(utils.precisionRound(state.color.h, 0), 0, 359).toString(16));
                    }
                    // Device expects 0-1000, saturation normally is 0-100 so we expect that from the user
                    // The device expects a round number, otherwise everything breaks
                    if (s) {
                        hsb.s = make4sizedString(utils.mapNumberRange(s, 0, 100, 0, 1000).toString(16));
                    }
                    else if (state.color?.s) {
                        hsb.s = make4sizedString(utils.mapNumberRange(state.color.s, 0, 100, 0, 1000).toString(16));
                    }
                    // Scale 0-255 to 0-1000 what the device expects.
                    if (b) {
                        hsb.b = make4sizedString(utils.mapNumberRange(b, 0, 255, 0, 1000).toString(16));
                    }
                    else if (state.brightness) {
                        hsb.b = make4sizedString(utils.mapNumberRange(state.brightness, 0, 255, 0, 1000).toString(16));
                    }
                    return hsb;
                };
                let hsb = {};
                if (value.hsb != null) {
                    const split = value.hsb.split(",").map((i) => Number.parseInt(i, 10));
                    hsb = fillInHSB(split[0], split[1], split[2], meta.state);
                }
                else {
                    hsb = fillInHSB(value.h || value.hue || null, value.s || value.saturation || null, value.b || value.brightness || null, meta.state);
                }
                // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
                let data = [];
                data = data.concat(convertStringToHexArray(hsb.h));
                data = data.concat(convertStringToHexArray(hsb.s));
                data = data.concat(convertStringToHexArray(hsb.b));
                await sendDataPointStringBuffer(entity, dataPoints.silvercrestSetColor, data);
            }
        },
    },
    tuya_data_point_test: {
        key: ["tuya_data_point_test"],
        convertSet: async (entity, key, value, meta) => {
            utils.assertString(value);
            const args = value.split(",");
            const mode = args[0];
            const dp = Number.parseInt(args[1], 10);
            const data = [];
            switch (mode) {
                case "raw":
                    for (let i = 2; i < args.length; i++) {
                        data.push(Number.parseInt(args[i], 10));
                    }
                    await sendDataPointRaw(entity, dp, Buffer.from(data));
                    break;
                case "bool":
                    await sendDataPointBool(entity, dp, args[2] === "1");
                    break;
                case "value":
                    await sendDataPointValue(entity, dp, Number.parseInt(args[2], 10));
                    break;
                case "enum":
                    await sendDataPointEnum(entity, dp, Number.parseInt(args[2], 10));
                    break;
                case "bitmap":
                    for (let i = 2; i < args.length; i++) {
                        data.push(Number.parseInt(args[i], 10));
                    }
                    await sendDataPointBitmap(entity, dp, Buffer.from(data));
                    break;
            }
        },
    },
    hy_thermostat: {
        key: [
            "child_lock",
            "current_heating_setpoint",
            "local_temperature_calibration",
            "max_temperature_protection",
            "min_temperature_protection",
            "state",
            "hysteresis",
            "hysteresis_for_protection",
            "max_temperature_for_protection",
            "min_temperature_for_protection",
            "max_temperature",
            "min_temperature",
            "sensor_type",
            "power_on_behavior",
            "week",
            "system_mode",
            "away_preset_days",
            "away_preset_temperature",
        ],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            switch (key) {
                case "max_temperature_protection":
                    await sendDataPointBool(entity, dataPoints.hyMaxTempProtection, value === "ON");
                    break;
                case "min_temperature_protection":
                    await sendDataPointBool(entity, dataPoints.hyMinTempProtection, value === "ON");
                    break;
                case "state":
                    await sendDataPointBool(entity, dataPoints.hyState, value === "ON");
                    break;
                case "child_lock":
                    await sendDataPointBool(entity, dataPoints.hyChildLock, value === "LOCK");
                    break;
                case "away_preset_days":
                    await sendDataPointValue(entity, dataPoints.hyAwayDays, value);
                    break;
                case "away_preset_temperature":
                    await sendDataPointValue(entity, dataPoints.hyAwayTemp, value);
                    break;
                case "local_temperature_calibration":
                    value = Math.round(value * 10);
                    if (value < 0)
                        value = 0xffffffff + value + 1;
                    await sendDataPointValue(entity, dataPoints.hyTempCalibration, value);
                    break;
                case "hysteresis":
                    value = Math.round(value * 10);
                    await sendDataPointValue(entity, dataPoints.hyHysteresis, value);
                    break;
                case "hysteresis_for_protection":
                    await sendDataPointValue(entity, dataPoints.hyProtectionHysteresis, value);
                    break;
                case "max_temperature_for_protection":
                    await sendDataPointValue(entity, dataPoints.hyProtectionMaxTemp, value);
                    break;
                case "min_temperature_for_protection":
                    await sendDataPointValue(entity, dataPoints.hyProtectionMinTemp, value);
                    break;
                case "max_temperature":
                    await sendDataPointValue(entity, dataPoints.hyMaxTemp, value);
                    break;
                case "min_temperature":
                    await sendDataPointValue(entity, dataPoints.hyMinTemp, value);
                    break;
                case "current_heating_setpoint":
                    value = Math.round(value * 10);
                    await sendDataPointValue(entity, dataPoints.hyHeatingSetpoint, value);
                    break;
                case "sensor_type":
                    await sendDataPointEnum(entity, dataPoints.hySensor, 
                    // @ts-expect-error ignore
                    { internal: 0, external: 1, both: 2 }[value]);
                    break;
                case "power_on_behavior":
                    await sendDataPointEnum(entity, dataPoints.hyPowerOnBehavior, 
                    // @ts-expect-error ignore
                    { restore: 0, off: 1, on: 2 }[value]);
                    break;
                case "week":
                    await sendDataPointEnum(entity, dataPoints.hyWeekFormat, utils.getKey(thermostatWeekFormat, value, value, Number));
                    break;
                case "system_mode":
                    await sendDataPointEnum(entity, dataPoints.hyMode, 
                    // @ts-expect-error ignore
                    { manual: 0, auto: 1, away: 2 }[value]);
                    break;
                default: // Unknown key
                    throw new Error(`Unhandled key ${key}`);
            }
        },
    },
    ZB003X: {
        key: [
            "reporting_time",
            "temperature_calibration",
            "humidity_calibration",
            "illuminance_calibration",
            "pir_enable",
            "led_enable",
            "reporting_enable",
            "sensitivity",
            "keep_time",
        ],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            switch (key) {
                case "reporting_time":
                    await sendDataPointValue(entity, dataPoints.fantemReportingTime, value, "sendData");
                    break;
                case "temperature_calibration":
                    value = Math.round(value * 10);
                    if (value < 0)
                        value = 0xffffffff + value + 1;
                    await sendDataPointValue(entity, dataPoints.fantemTempCalibration, value, "sendData");
                    break;
                case "humidity_calibration":
                    if (value < 0)
                        value = 0xffffffff + value + 1;
                    await sendDataPointValue(entity, dataPoints.fantemHumidityCalibration, value, "sendData");
                    break;
                case "illuminance_calibration":
                    if (value < 0)
                        value = 0xffffffff + value + 1;
                    await sendDataPointValue(entity, dataPoints.fantemLuxCalibration, value, "sendData");
                    break;
                case "pir_enable":
                    await sendDataPointBool(entity, dataPoints.fantemMotionEnable, value, "sendData");
                    break;
                case "led_enable":
                    await sendDataPointBool(entity, dataPoints.fantemLedEnable, value === false, "sendData");
                    break;
                case "reporting_enable":
                    await sendDataPointBool(entity, dataPoints.fantemReportingEnable, value, "sendData");
                    break;
                case "sensitivity":
                    // @ts-expect-error ignore
                    await entity.write("ssIasZone", { currentZoneSensitivityLevel: { low: 0, medium: 1, high: 2 }[value] });
                    break;
                case "keep_time":
                    // @ts-expect-error ignore
                    await entity.write("ssIasZone", { 61441: { value: { "0": 0, "30": 1, "60": 2, "120": 3, "240": 4, "480": 5 }[value], type: 0x20 } });
                    break;
                default: // Unknown key
                    throw new Error(`tz.ZB003X: Unhandled key ${key}`);
            }
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    ZB006X_settings: {
        key: ["switch_type", "load_detection_mode", "control_mode"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            switch (key) {
                case "switch_type":
                    await sendDataPointEnum(entity, dataPoints.fantemExtSwitchType, 
                    // @ts-expect-error ignore
                    { unknown: 0, toggle: 1, momentary: 2, rotary: 3, auto_config: 4 }[value], "sendData");
                    break;
                case "load_detection_mode":
                    await sendDataPointEnum(entity, dataPoints.fantemLoadDetectionMode, 
                    // @ts-expect-error ignore
                    { none: 0, first_power_on: 1, every_power_on: 2 }[value], "sendData");
                    break;
                case "control_mode":
                    // @ts-expect-error ignore
                    await sendDataPointEnum(entity, dataPoints.fantemControlMode, { ext_switch: 0, remote: 1, both: 2 }[value], "sendData");
                    break;
                default: // Unknown key
                    throw new Error(`tz.ZB006X_settings: Unhandled key ${key}`);
            }
        },
    },
    tuya_motion_sensor: {
        key: ["o_sensitivity", "v_sensitivity", "led_status", "vacancy_delay", "light_on_luminance_prefer", "light_off_luminance_prefer", "mode"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            switch (key) {
                case "o_sensitivity":
                    await sendDataPointEnum(entity, dataPoints.msOSensitivity, utils.getKey(msLookups.OSensitivity, value, undefined, Number));
                    break;
                case "v_sensitivity":
                    await sendDataPointEnum(entity, dataPoints.msVSensitivity, utils.getKey(msLookups.VSensitivity, value, undefined, Number));
                    break;
                case "led_status":
                    // @ts-expect-error ignore
                    await sendDataPointEnum(entity, dataPoints.msLedStatus, { on: 0, off: 1 }[value.toLowerCase()]);
                    break;
                case "vacancy_delay":
                    await sendDataPointValue(entity, dataPoints.msVacancyDelay, value);
                    break;
                case "light_on_luminance_prefer":
                    await sendDataPointValue(entity, dataPoints.msLightOnLuminancePrefer, value);
                    break;
                case "light_off_luminance_prefer":
                    await sendDataPointValue(entity, dataPoints.msLightOffLuminancePrefer, value);
                    break;
                case "mode":
                    await sendDataPointEnum(entity, dataPoints.msMode, utils.getKey(msLookups.Mode, value, undefined, Number));
                    break;
                default: // Unknown key
                    logger_1.logger.warning(`toZigbee.tuya_motion_sensor: Unhandled key ${key}`, "zhc:legacy:tz:tuya_motion_sensor");
            }
        },
    },
    javis_microwave_sensor: {
        key: ["illuminance_calibration", "led_enable", "sensitivity", "keep_time"],
        convertSet: async (entity, key, value, meta) => {
            switch (key) {
                case "illuminance_calibration": // (10--100) sensor illuminance sensitivity
                    if (meta.device.manufacturerName === "_TZE200_kagkgk0i") {
                        await sendDataPointRaw(entity, 102, Buffer.from([value]));
                        break;
                    }
                    await sendDataPointRaw(entity, 105, Buffer.from([value]));
                    break;
                case "led_enable": // OK (value true/false or 1/0)
                    if (meta.device.manufacturerName === "_TZE200_kagkgk0i") {
                        await sendDataPointRaw(entity, 107, Buffer.from([value ? 1 : 0]));
                        break;
                    }
                    await sendDataPointRaw(entity, 103, Buffer.from([value ? 1 : 0]));
                    break;
                case "sensitivity": // value: 25, 50, 75, 100
                    await sendDataPointRaw(entity, 2, Buffer.from([value]));
                    break;
                case "keep_time": // value 0 --> 7 corresponding 5s, 30s, 1, 3, 5, 10, 20, 30 min
                    if (meta.device.manufacturerName === "_TZE200_kagkgk0i") {
                        await sendDataPointRaw(entity, 106, Buffer.from([value]));
                        break;
                    }
                    await sendDataPointRaw(entity, 102, Buffer.from([value]));
                    break;
                default: // Unknown key
                    throw new Error(`Unhandled key ${key}`);
            }
        },
    },
    moes_thermostat_tv: {
        key: [
            "system_mode",
            "window_detection",
            "frost_detection",
            "child_lock",
            "current_heating_setpoint",
            "local_temperature_calibration",
            "holiday_temperature",
            "comfort_temperature",
            "eco_temperature",
            "open_window_temperature",
            "heating_stop",
            "preset",
        ],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            switch (key) {
                case "system_mode":
                    if (value !== "off") {
                        await sendDataPointBool(entity, dataPoints.tvHeatingStop, 0);
                        await sendDataPointEnum(entity, dataPoints.tvMode, utils.getKey(tvThermostatMode, value, undefined, Number));
                    }
                    else {
                        await sendDataPointBool(entity, dataPoints.tvHeatingStop, 1);
                    }
                    break;
                case "window_detection":
                    await sendDataPointBool(entity, dataPoints.tvWindowDetection, value);
                    break;
                case "frost_detection":
                    if (value === false) {
                        await sendDataPointBool(entity, dataPoints.tvFrostDetection, 0);
                        await sendDataPointEnum(entity, dataPoints.tvMode, 1);
                    }
                    else {
                        await sendDataPointBool(entity, dataPoints.tvFrostDetection, 1);
                    }
                    break;
                case "child_lock":
                    await sendDataPointBool(entity, dataPoints.tvChildLock, value === "LOCK");
                    break;
                case "local_temperature_calibration":
                    value = Math.round(value * 10);
                    value = value < 0 ? 0xffffffff + value + 1 : value;
                    await sendDataPointValue(entity, dataPoints.tvTempCalibration, value);
                    break;
                case "current_heating_setpoint":
                    value = Math.round(value * 10);
                    await sendDataPointValue(entity, dataPoints.tvHeatingSetpoint, value);
                    break;
                case "holiday_temperature":
                    value = Math.round(value * 10);
                    await sendDataPointValue(entity, dataPoints.tvHolidayTemp, value);
                    break;
                case "comfort_temperature":
                    value = Math.round(value * 10);
                    await sendDataPointValue(entity, dataPoints.tvComfortTemp, value);
                    break;
                case "eco_temperature":
                    value = Math.round(value * 10);
                    await sendDataPointValue(entity, dataPoints.tvEcoTemp, value);
                    break;
                case "heating_stop":
                    if (value === true) {
                        await sendDataPointBool(entity, dataPoints.tvHeatingStop, 1);
                    }
                    else {
                        await sendDataPointBool(entity, dataPoints.tvHeatingStop, 0);
                        await sendDataPointEnum(entity, dataPoints.tvMode, 1);
                    }
                    break;
                // case 'boost_mode':
                //     // set 300sec boost time
                //     await sendDataPointValue(entity, dataPoints.tvBoostTime, 300);
                //     await sendDataPointEnum(entity, dataPoints.tvBoostMode, (value) ? 0 : 1);
                //     break;
                case "open_window_temperature":
                    value = Math.round(value * 10);
                    await sendDataPointValue(entity, dataPoints.tvOpenWindowTemp, value);
                    break;
                case "preset":
                    await sendDataPointBool(entity, dataPoints.tvHeatingStop, 0);
                    await sendDataPointEnum(entity, dataPoints.tvMode, utils.getKey(tvThermostatPreset, value, undefined, Number));
                    break;
                default: // Unknown key
                    logger_1.logger.warning(`Unhandled key ${key}`, "zhc:legacy:tz:moes_thermostat_tv");
            }
        },
    },
    tuya_light_wz5: {
        key: ["color", "color_temp", "brightness", "white_brightness"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            if (Array.isArray(meta.mapped))
                throw new Error("Not supported for groups");
            const separateWhite = meta.mapped.meta?.separateWhite;
            if (key === "white_brightness" || (!separateWhite && key === "brightness")) {
                // upscale to 1000
                // biome-ignore lint/suspicious/noImplicitAnyLet: ignored using `--suppress`
                let newValue;
                if (value >= 0 && value <= 255) {
                    newValue = utils.mapNumberRange(value, 0, 255, 0, 1000);
                }
                else {
                    throw new Error("Dimmer brightness is out of range 0..255");
                }
                await sendDataPoints(entity, [
                    dpValueFromEnum(dataPoints.silvercrestChangeMode, silvercrestModes.white),
                    dpValueFromIntValue(dataPoints.dimmerLevel, newValue),
                ], "dataRequest");
                return { state: key === "white_brightness" ? { white_brightness: value } : { brightness: value } };
            }
            if (key === "color_temp") {
                const [colorTempMin, colorTempMax] = [250, 454];
                const preset = {
                    warmest: colorTempMax,
                    warm: 454,
                    neutral: 370,
                    cool: 250,
                    coolest: colorTempMin,
                };
                if (typeof value === "string" && Number.isNaN(value)) {
                    const presetName = value.toLowerCase();
                    if (presetName in preset) {
                        value = preset[presetName];
                    }
                    else {
                        throw new Error(`Unknown preset '${value}'`);
                    }
                }
                else {
                    value = light.clampColorTemp(Number(value), colorTempMin, colorTempMax);
                }
                const data = utils.mapNumberRange(value, colorTempMax, colorTempMin, 0, 1000);
                await sendDataPoints(entity, [
                    dpValueFromEnum(dataPoints.silvercrestChangeMode, silvercrestModes.white),
                    dpValueFromIntValue(dataPoints.silvercrestSetColorTemp, data),
                ], "dataRequest");
                return { state: { color_temp: value } };
            }
            if (key === "color" || (separateWhite && key === "brightness")) {
                const newState = {};
                if (key === "brightness") {
                    newState.brightness = value;
                }
                else if (key === "color") {
                    newState.color = value;
                    newState.color_mode = "hs";
                }
                const make4sizedString = (v) => {
                    if (v.length >= 4) {
                        return v;
                    }
                    if (v.length === 3) {
                        return `0${v}`;
                    }
                    if (v.length === 2) {
                        return `00${v}`;
                    }
                    if (v.length === 1) {
                        return `000${v}`;
                    }
                    return "0000";
                };
                const fillInHSB = (h, s, b, state) => {
                    // Define default values. Device expects leading zero in string.
                    const hsb = {
                        h: "0168", // 360
                        s: "03e8", // 1000
                        b: "03e8", // 1000
                    };
                    if (h) {
                        // The device expects 0-359
                        if (h >= 360) {
                            h = 359;
                        }
                        hsb.h = make4sizedString(h.toString(16));
                    }
                    else if (state.color?.hue) {
                        hsb.h = make4sizedString(state.color.hue.toString(16));
                    }
                    // Device expects 0-1000, saturation normally is 0-100 so we expect that from the user
                    // The device expects a round number, otherwise everything breaks
                    if (s) {
                        hsb.s = make4sizedString(utils.mapNumberRange(s, 0, 100, 0, 1000).toString(16));
                    }
                    else if (state.color?.saturation) {
                        hsb.s = make4sizedString(utils.mapNumberRange(state.color.saturation, 0, 100, 0, 1000).toString(16));
                    }
                    // Scale 0-255 to 0-1000 what the device expects.
                    if (b != null) {
                        hsb.b = make4sizedString(utils.mapNumberRange(b, 0, 255, 0, 1000).toString(16));
                    }
                    else if (state.brightness != null) {
                        hsb.b = make4sizedString(utils.mapNumberRange(state.brightness, 0, 255, 0, 1000).toString(16));
                    }
                    return hsb;
                };
                const hsb = fillInHSB(value.h || value.hue || null, value.s || value.saturation || null, value.b || value.brightness || key === "brightness" ? value : null, meta.state);
                // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
                let data = [];
                data = data.concat(convertStringToHexArray(hsb.h));
                data = data.concat(convertStringToHexArray(hsb.s));
                data = data.concat(convertStringToHexArray(hsb.b));
                const commands = [
                    dpValueFromEnum(dataPoints.silvercrestChangeMode, silvercrestModes.color),
                    dpValueFromStringBuffer(dataPoints.silvercrestSetColor, data),
                ];
                await sendDataPoints(entity, commands, "dataRequest");
                return { state: newState };
            }
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    ZMAM02_cover: {
        key: ["state", "position", "mode", "motor_direction", "border", "motor_working_mode"],
        options: [exposes.options.invert_cover()],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            if (key === "position") {
                if (value >= 0 && value <= 100) {
                    const invert = isCoverInverted(meta.device.manufacturerName) ? !meta.options.invert_cover : meta.options.invert_cover;
                    value = invert ? 100 - value : value;
                    await sendDataPointValue(entity, dataPoints.coverPosition, value);
                }
                else {
                    throw new Error("Tuya_cover_control: Curtain motor position is out of range");
                }
            }
            else if (key === "state") {
                const stateEnums = getCoverStateEnums(meta.device.manufacturerName);
                logger_1.logger.debug(`Using state enums for ${meta.device.manufacturerName}: ${JSON.stringify(stateEnums)}`, "zhc:legacy:tz:zmam02");
                value = value.toLowerCase();
                switch (value) {
                    case "close":
                        await sendDataPointEnum(entity, dataPoints.AM02Control, stateEnums.close);
                        break;
                    case "open":
                        await sendDataPointEnum(entity, dataPoints.AM02Control, stateEnums.open);
                        break;
                    case "stop":
                        await sendDataPointEnum(entity, dataPoints.AM02Control, stateEnums.stop);
                        break;
                    default:
                        throw new Error("ZMAM02: Invalid command received");
                }
            }
            switch (key) {
                case "mode":
                    await sendDataPointEnum(entity, dataPoints.AM02Mode, utils.getKey(ZMLookups.AM02Mode, value, undefined, Number));
                    break;
                case "motor_direction":
                    await sendDataPointEnum(entity, dataPoints.AM02Direction, utils.getKey(ZMLookups.AM02Direction, value, undefined, Number));
                    break;
                case "border":
                    await sendDataPointEnum(entity, dataPoints.AM02Border, utils.getKey(ZMLookups.AM02Border, value, undefined, Number));
                    break;
                case "motor_working_mode": {
                    await sendDataPointEnum(entity, dataPoints.AM02MotorWorkingMode, utils.getKey(ZMLookups.AM02MotorWorkingMode, value, undefined, Number));
                    break;
                }
            }
        },
    },
    tuya_smart_human_presense_sensor: {
        key: ["radar_sensitivity", "minimum_range", "maximum_range", "detection_delay", "fading_time"],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            switch (key) {
                case "radar_sensitivity":
                    await sendDataPointValue(entity, dataPoints.tshpscSensitivity, value);
                    break;
                case "minimum_range":
                    await sendDataPointValue(entity, dataPoints.tshpsMinimumRange, value * 100);
                    break;
                case "maximum_range":
                    await sendDataPointValue(entity, dataPoints.tshpsMaximumRange, value * 100);
                    break;
                case "detection_delay":
                    await sendDataPointValue(entity, dataPoints.tshpsDetectionDelay, value * 10);
                    break;
                case "fading_time":
                    await sendDataPointValue(entity, dataPoints.tshpsFadingTime, value * 10);
                    break;
                default: // Unknown Key
                    logger_1.logger.warning(`Unhandled Key ${key}`, "zhc:legacy:tz:tuya_smart_human_presense_sensor");
            }
        },
    },
    // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
    ZG204ZL_lms: {
        key: ["sensitivity", "keep_time"],
        convertSet: async (entity, key, value, meta) => {
            switch (key) {
                case "sensitivity":
                    // @ts-expect-error ignore
                    await sendDataPointEnum(entity, dataPoints.lmsSensitivity, { low: 0, medium: 1, high: 2 }[value]);
                    break;
                case "keep_time":
                    // @ts-expect-error ignore
                    await sendDataPointEnum(entity, dataPoints.lmsKeepTime, { "10": 0, "30": 1, "60": 2, "120": 3 }[value]);
                    break;
                default: // Unknown key
                    logger_1.logger.warning(`Unhandled SET key ${key}`, "zhc:legacy:tz:zg204zl_lms");
            }
        },
        convertGet: async (entity, key, meta) => {
            switch (key) {
                case "sensitivity":
                    await sendDataPointEnum(entity, dataPoints.lmsSensitivity, 0, "dataQuery");
                    break;
                case "keep_time":
                    await sendDataPointEnum(entity, dataPoints.lmsKeepTime, 0, "dataQuery");
                    break;
                default: // Unknown key
                    logger_1.logger.warning(`Unhandled GET key ${key}`, "zhc:legacy:tz:zg204zl_lms");
            }
        },
    },
    moes_cover: {
        key: ["backlight", "calibration", "motor_reversal", "state", "position"],
        options: [exposes.options.invert_cover()],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            switch (key) {
                case "position":
                    if (value >= 0 && value <= 100) {
                        const invert = !isCoverInverted(meta.device.manufacturerName) ? !meta.options.invert_cover : meta.options.invert_cover;
                        const position = invert ? 100 - value : value;
                        await sendDataPointValue(entity, dataPoints.coverPosition, position);
                        return { state: { position: value } };
                    }
                    break;
                case "state": {
                    // @ts-expect-error ignore
                    const state = { OPEN: 0, STOP: 1, CLOSE: 2 }[value.toUpperCase()];
                    await sendDataPointEnum(entity, dataPoints.state, state);
                    break;
                }
                case "backlight": {
                    const backlight = value.toUpperCase() === "ON";
                    await sendDataPointBool(entity, dataPoints.moesCoverBacklight, backlight);
                    return { state: { backlight: value } };
                }
                case "calibration": {
                    const calibration = value.toUpperCase() === "ON" ? 0 : 1;
                    await sendDataPointEnum(entity, dataPoints.moesCoverCalibration, calibration);
                    break;
                }
                case "motor_reversal": {
                    const motorReversal = value.toUpperCase() === "ON" ? 1 : 0;
                    await sendDataPointEnum(entity, dataPoints.moesCoverMotorReversal, motorReversal);
                    return { state: { motor_reversal: value } };
                }
            }
        },
    },
    hoch_din: {
        key: [
            "state",
            "child_lock",
            "countdown_timer",
            "power_on_behavior",
            "trip",
            "clear_device_data",
            /* TODO: Add the below keys when toZigbee converter work has been completed
            'voltage_setting',
            'current_setting',
            'temperature_setting',
            'leakage_current_setting'*/
        ],
        // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`
        convertSet: async (entity, key, value, meta) => {
            if (key === "state") {
                await sendDataPointBool(entity, dataPoints.state, value === "ON");
                return { state: { state: value } };
            }
            if (key === "child_lock") {
                await sendDataPointBool(entity, dataPoints.hochChildLock, value === "ON");
                return { state: { child_lock: value } };
            }
            if (key === "countdown_timer") {
                await sendDataPointValue(entity, dataPoints.hochCountdownTimer, value);
                return { state: { countdown_timer: value } };
            }
            if (key === "power_on_behavior") {
                const lookup = { off: 0, on: 1, previous: 2 };
                await sendDataPointEnum(entity, dataPoints.hochRelayStatus, lookup[value], "sendData");
                return { state: { power_on_behavior: value } };
            }
            if (key === "trip") {
                if (value === "clear") {
                    await sendDataPointBool(entity, dataPoints.hochLocking, true, "sendData");
                }
                return { state: { trip: "clear" } };
            }
            if (key === "clear_device_data") {
                await sendDataPointBool(entity, dataPoints.hochClearEnergy, true, "sendData");
                /* TODO: Release the below with other toZigbee converters for device composites
            } else if (key === 'temperature_setting') {
                if (value.over_temperature_threshold && value.over_temperature_trip && value.over_temperature_alarm){
                    const payload = [];
                    payload.push(value.over_temperature_threshold < 1
                        ? ((value.over_temperature_threshold * -1) + 128)
                        : value.over_temperature_threshold);
                    payload.push(value.over_temperature_trip === 'ON' ? 1 : 0);
                    payload.push(value.over_temperature_alarm === 'ON' ? 1 : 0);
                    await sendDataPointRaw(entity, dataPoints.hochTemperatureThreshold, payload, 'sendData');
                    return {state: {over_temperature_threshold: value.over_temperature_threshold,
                        over_temperature_trip: value.over_temperature_trip,
                        over_temperature_alarm: value.over_temperature_alarm}};
                }*/
            }
            else {
                throw new Error(`Not supported: '${key}'`);
            }
        },
    },
    woox_thermostat_child_lock: {
        key: ["child_lock"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, dataPoints.wooxChildLock, value === "LOCK");
        },
    },
    woox_thermostat_current_heating_setpoint: {
        key: ["current_heating_setpoint"],
        // biome-ignore lint/suspicious/noExplicitAny: ignore
        convertSet: async (entity, key, value, meta) => {
            const temp = Math.round(value * 10);
            await sendDataPointValue(entity, dataPoints.wooxControlTemperature, temp);
        },
    },
    woox_thermostat_system_mode: {
        key: ["system_mode"],
        convertSet: async (entity, key, value, meta) => {
            if (value === "auto") {
                await sendDataPointEnum(entity, dataPoints.wooxMode, 0);
                await sendDataPointValue(entity, dataPoints.wooxControlTemperature, 220);
                return { state: { current_heating_setpoint: 22 } };
            }
            if (value === "heat") {
                await sendDataPointEnum(entity, dataPoints.wooxMode, 1);
                await sendDataPointValue(entity, dataPoints.wooxControlTemperature, 170);
                return { state: { current_heating_setpoint: 17 } };
            }
            logger_1.logger.debug(`Woox thermostat: uknown mode ${value}`, "woox_thermostat_system_mode");
        },
    },
    woox_away_mode: {
        key: ["away_mode"],
        convertSet: async (entity, key, value, meta) => {
            if (value === "ON") {
                await sendDataPointEnum(entity, dataPoints.wooxMode, 2);
                return { state: { current_heating_setpoint: 0 } };
            }
            await sendDataPointEnum(entity, dataPoints.wooxMode, 0);
            await sendDataPointValue(entity, dataPoints.wooxControlTemperature, 220);
            return { state: { current_heating_setpoint: 22 } };
        },
    },
    woox_comfort_temperature: {
        key: ["comfort_temperature"],
        // biome-ignore lint/suspicious/noExplicitAny: ignore
        convertSet: async (entity, key, value, meta) => {
            const temp = Math.round(value * 2);
            await sendDataPointValue(entity, dataPoints.wooxComfortTemperature, temp);
        },
    },
    woox_eco_temperature: {
        key: ["eco_temperature"],
        // biome-ignore lint/suspicious/noExplicitAny: ignore
        convertSet: async (entity, key, value, meta) => {
            const temp = Math.round(value * 2);
            await sendDataPointValue(entity, dataPoints.wooxEnergySavingTemperature, temp);
        },
    },
    woox_local_temperature_calibration: {
        key: ["local_temperature_calibration"],
        // biome-ignore lint/suspicious/noExplicitAny: ignore
        convertSet: async (entity, key, value, meta) => {
            let val = value;
            if (val < 0) {
                val = val + 4096;
            }
            const temp = Math.round(val * 10);
            await sendDataPointValue(entity, dataPoints.wooxTemperatureCalibration, temp);
        },
    },
    woox_window_detection_temperature: {
        key: ["window_detection_temperature"],
        // biome-ignore lint/suspicious/noExplicitAny: ignore
        convertSet: async (entity, key, value, meta) => {
            const temp = Math.round(value * 2);
            await sendDataPointValue(entity, dataPoints.wooxWindowTemperature, temp);
        },
    },
    woox_window_detection_time: {
        key: ["window_detection_time"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointValue(entity, dataPoints.wooxWindowTime, value);
        },
    },
    woox_boost_heating: {
        key: ["boost_heating"],
        convertSet: async (entity, key, value, meta) => {
            await sendDataPointBool(entity, dataPoints.wooxBoostHeating, value === "ON");
        },
    },
    woox_holidays_schedule: {
        key: ["holidays_schedule"],
        convertSet: async (entity, key, value, meta) => {
            await logger_1.logger.warning("holidays_schedule", "woox_holidays_schedule");
        },
    },
    woox_monday_schedule: {
        key: ["monday_schedule"],
        convertSet: async (entity, key, value, meta) => {
            await logger_1.logger.warning("monday_schedule", "woox_monday_schedule");
        },
    },
    woox_tuesday_schedule: {
        key: ["tuesday_schedule"],
        convertSet: async (entity, key, value, meta) => {
            await logger_1.logger.warning("tuesday_schedule", "woox_tuesday_schedule");
        },
    },
    woox_wednesday_schedule: {
        key: ["wednesday_schedule"],
        convertSet: async (entity, key, value, meta) => {
            await logger_1.logger.warning("wednesday_schedule", "woox_wednesday_schedule");
        },
    },
    woox_thursday_schedule: {
        key: ["thursday_schedule"],
        convertSet: async (entity, key, value, meta) => {
            await logger_1.logger.warning("thursday_schedule", "woox_thursday_schedule");
        },
    },
    woox_friday_schedule: {
        key: ["friday_schedule"],
        convertSet: async (entity, key, value, meta) => {
            await logger_1.logger.warning("friday_schedule", "woox_friday_schedule");
        },
    },
    woox_saturday_schedule: {
        key: ["saturday_schedule"],
        convertSet: async (entity, key, value, meta) => {
            await logger_1.logger.warning("saturday_schedule", "woox_saturday_schedule");
        },
    },
    woox_sunday_schedule: {
        key: ["sunday_schedule"],
        convertSet: async (entity, key, value, meta) => {
            await logger_1.logger.warning("sunday_schedule", "woox_sunday_schedule");
        },
    },
};
const thermostatSystemModes = {
    0: "off",
    1: "auto",
    3: "cool",
    4: "heat",
    5: "emergency heating",
    6: "precooling",
    7: "fan_only",
    8: "dry",
    9: "Sleep",
};
exports.thermostatSystemModes = thermostatSystemModes;
const toZigbee = { ...toZigbee1, ...toZigbee2 };
exports.tz = toZigbee;
exports.toZigbee = toZigbee;
//# sourceMappingURL=legacy.js.map