UNPKG

zigbee-herdsman-converters

Version:

Collection of device converters to be used with zigbee-herdsman

1,409 lines (1,408 loc) • 320 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.moesSwitch = exports.dataPoints = exports.ZMLookups = exports.msLookups = exports.giexWaterValve = exports.thermostatPresets = exports.thermostatSystemModes4 = exports.thermostatSystemModes3 = exports.thermostatSystemModes2 = exports.tuyaHPSCheckingResult = exports.thermostatSystemModes = exports.toZigbee = exports.tz = exports.fromZigbee = exports.fz = 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 [chunk1, chunk2, chunk3, chunk4].map((hexVal) => Number.parseInt(hexVal, 16)); } let gSec = undefined; // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress` async function sendDataPoints(entity, dpValues, cmd = "dataRequest", seq = undefined) { if (seq === undefined) { if (gSec === undefined) { gSec = 0; } else { gSec++; gSec %= 0xffff; } // biome-ignore lint/style/noParameterAssign: ignored using `--suppress` seq = gSec; } await entity.command("manuSpecificTuya", cmd || "dataRequest", { 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: [value ? 1 : 0] }; } function dpValueFromEnum(dp, value) { return { dp, datatype: dataTypes.enum, data: [value] }; } function dpValueFromStringBuffer(dp, stringBuffer) { return { dp, datatype: dataTypes.string, data: stringBuffer }; } // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress` function dpValueFromRaw(dp, rawBuffer) { return { dp, datatype: dataTypes.raw, data: rawBuffer }; } // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress` function dpValueFromBitmap(dp, bitmapBuffer) { return { dp, datatype: dataTypes.bitmap, data: bitmapBuffer }; } // Return `seq` - transaction ID for handling concrete response // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress` async function sendDataPoint(entity, dpValue, cmd, seq = undefined) { return await sendDataPoints(entity, [dpValue], cmd, seq); } // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress` 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); } // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress` async function sendDataPointRaw(entity, dp, value, cmd, seq = undefined) { return await sendDataPoints(entity, [dpValueFromRaw(dp, value)], cmd, seq); } // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress` async function sendDataPointBitmap(entity, dp, value, cmd, seq = undefined) { return await sendDataPoints(entity, [dpValueFromBitmap(dp, value)], cmd, seq); } // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress` 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)).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)).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)).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]); const timeMinute = Number.parseInt(timeArray[1]); 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: ["commandGetData", "commandSetDataResponse", "commandDataResponse"], 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();