UNPKG

zigbee-herdsman-converters

Version:

Collection of device converters to be used with zigbee-herdsman

1,036 lines 83.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.definitions = void 0; const fz = __importStar(require("../converters/fromZigbee")); const tz = __importStar(require("../converters/toZigbee")); const exposes = __importStar(require("../lib/exposes")); const m = __importStar(require("../lib/modernExtend")); const reporting = __importStar(require("../lib/reporting")); const globalStore = __importStar(require("../lib/store")); const utils = __importStar(require("../lib/utils")); const NS = "zhc:slacky-diy"; const e = exposes.presets; const ea = exposes.access; const defaultReporting = { min: 0, max: 300, change: 0 }; const ppmReporting = { min: 10, max: 300, change: 0.000001 }; const batteryReporting = { min: 3600, max: 0, change: 0 }; const model_r01 = "Tuya_Thermostat_r01"; const model_r02 = "Tuya_Thermostat_r02"; const model_r03 = "Tuya_Thermostat_r03"; const model_r04 = "Tuya_Thermostat_r04"; const model_r05 = "Tuya_Thermostat_r05"; const model_r06 = "Tuya_Thermostat_r06"; const model_r07 = "Tuya_Thermostat_r07"; const model_r08 = "Tuya_Thermostat_r08"; const attrThermSensorUser = 0xf000; const attrThermFrostProtect = 0xf001; const attrThermHeatProtect = 0xf002; const attrThermEcoMode = 0xf003; const attrThermEcoModeHeatTemperature = 0xf004; const attrThermFrostProtectOnOff = 0xf005; const attrThermSettingsReset = 0xf006; const attrThermScheduleMode = 0xf007; const attrThermSound = 0xf008; const attrThermLevel = 0xf009; const attrThermInversion = 0xf00a; const attrThermEcoModeCoolTemperature = 0xf00b; const attrThermExtTemperatureCalibration = 0xf00c; const attrFanCtrlControl = 0xf000; const switchSensorUsed = ["Inner (IN)", "All (AL)", "Outer (OU)"]; const attrElCityMeterModelPreset = 0xf000; const attrElCityMeterAddressPreset = 0xf001; const attrElCityMeterMeasurementPreset = 0xf002; const attrElCityMeterDateRelease = 0xf003; const attrElCityMeterModelName = 0xf004; const attrElCityMeterPasswordPreset = 0xf005; const fzLocal = { thermostat_custom_fw: { cluster: "hvacThermostat", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data[attrThermSensorUser] !== undefined) { const lookup = { 0: "Inner (IN)", 1: "All (AL)", 2: "Outer (OU)" }; result.sensor = utils.getFromLookup(msg.data[attrThermSensorUser], lookup); } if (msg.data.minSetpointDeadBand !== undefined) { let data; if (model.model === model_r06) { data = Number.parseFloat(msg.data.minSetpointDeadBand) / 10; result.histeresis_temperature = data; } else { data = Number.parseInt(msg.data.minSetpointDeadBand); result.deadzone_temperature = data; } //logger.info(`DeadBand: ${data}`, NS); } if (msg.data[attrThermFrostProtect] !== undefined) { const data = Number.parseInt(msg.data[attrThermFrostProtect]) / 100; result.frost_protect = data; } if (msg.data[attrThermHeatProtect] !== undefined) { const data = Number.parseInt(msg.data[attrThermHeatProtect]) / 100; result.heat_protect = data; } if (msg.data[attrThermEcoMode] !== undefined) { result.eco_mode = msg.data[attrThermEcoMode] === 1 ? "On" : "Off"; } if (msg.data[attrThermEcoModeCoolTemperature] !== undefined) { const data = Number.parseInt(msg.data[attrThermEcoModeCoolTemperature]) / 100; result.eco_mode_cool_temperature = data; } if (msg.data[attrThermEcoModeHeatTemperature] !== undefined) { const data = Number.parseInt(msg.data[attrThermEcoModeHeatTemperature]) / 100; result.eco_mode_heat_temperature = data; } if (msg.data[attrThermFrostProtectOnOff] !== undefined) { result.frost_protect_on_off = msg.data[attrThermFrostProtectOnOff] === 1 ? "On" : "Off"; } if (msg.data[attrThermLevel] !== undefined) { const lookup = { 0: "Off", 1: "Low", 2: "Medium", 3: "High" }; result.brightness_level = utils.getFromLookup(msg.data[attrThermLevel], lookup); } if (msg.data[attrThermSound] !== undefined) { result.sound = msg.data[attrThermSound] === 1 ? "On" : "Off"; } if (msg.data[attrThermInversion] !== undefined) { result.inversion = msg.data[attrThermInversion] === 1 ? "On" : "Off"; } if (msg.data[attrThermScheduleMode] !== undefined) { const lookup = { 0: "Off", 1: "5+2", 2: "6+1", 3: "7" }; result.schedule_mode = utils.getFromLookup(msg.data[attrThermScheduleMode], lookup); } if (msg.data[attrThermExtTemperatureCalibration] !== undefined) { const data = Number.parseInt(msg.data[attrThermExtTemperatureCalibration]) / 10; result.external_temperature_calibration = data; } return result; }, }, thermostat_schedule: { cluster: "hvacThermostat", type: ["commandSetWeeklySchedule"], convert: (model, msg, publish, options, meta) => { const result = {}; const { data } = msg; const daysOfWeekNums = [...Array.from(Array(7).keys()).filter((x) => (2 ** x) & data.dayofweek)]; // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress` const schedule = `${data.transitions.map((t) => `${String(Math.floor(t.transitionTime / 60)).padStart(2, "0")}:${String(t.transitionTime % 60).padStart(2, "0")}/${t.heatSetpoint / 100.0}°C`).join(" ")}`; const daysOfWeek = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"]; return Object.fromEntries(daysOfWeekNums.map((d) => [`schedule_${daysOfWeek[d]}`, schedule])); }, }, fancontrol_control: { cluster: "hvacFanCtrl", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data[attrFanCtrlControl] !== undefined) { result.fan_control = msg.data[attrFanCtrlControl] === 1 ? "On" : "Off"; } return result; }, }, display_brightness: { cluster: "genLevelCtrl", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data.currentLevel !== undefined) { const property = `brightness_${utils.getEndpointName(msg, model, meta)}`; //logger.info('property: ' + property); return { [property]: msg.data.currentLevel }; } return result; }, }, }; const tzLocal = { display_brightness: { key: ["brightness", "brightness_day", "brightness_night"], options: [exposes.options.transition()], convertSet: async (entity, key, value, meta) => { await entity.command("genLevelCtrl", "moveToLevel", { level: value, transtime: 0 }, utils.getOptions(meta.mapped, entity)); return { state: { brightness: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("genLevelCtrl", ["currentLevel"]); }, }, thermostat_sensor_used: { key: ["sensor"], convertSet: async (entity, key, value, meta) => { const endpoint = meta.device.getEndpoint(1); const lookup = { "Inner (IN)": 0, "All (AL)": 1, "Outer (OU)": 2 }; await endpoint.write("hvacThermostat", { [attrThermSensorUser]: { value: utils.getFromLookup(value, lookup), type: 0x30 } }); return { state: { [key]: value }, }; }, convertGet: async (entity, key, meta) => { await entity.read("hvacThermostat", [attrThermSensorUser]); }, }, thermostat_deadzone: { key: ["deadzone_temperature"], convertSet: async (entity, key, value, meta) => { utils.assertNumber(value); const minSetpointDeadBand = Number(Math.round(value)); await entity.write("hvacThermostat", { minSetpointDeadBand }); return { readAfterWriteTime: 250, state: { minSetpointDeadBand: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("hvacThermostat", ["minSetpointDeadBand"]); }, }, thermostat_deadzone_10: { key: ["histeresis_temperature"], convertSet: async (entity, key, value, meta) => { utils.assertNumber(value); const minSetpointDeadBand = Number(value) * 10; await entity.write("hvacThermostat", { minSetpointDeadBand }); return { readAfterWriteTime: 250, state: { histeresis_temperature: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("hvacThermostat", ["minSetpointDeadBand"]); }, }, thermostat_frost_protect: { key: ["frost_protect"], convertSet: async (entity, key, value, meta) => { utils.assertNumber(value); if (!utils.isInRange(0, 10, Number(value))) throw new Error(`Invalid value: ${value} (expected ${0} to ${10})`); const frost_protect = Number(Math.round(value)) * 100; await entity.write("hvacThermostat", { [attrThermFrostProtect]: { value: frost_protect, type: 0x29 } }); return { readAfterWriteTime: 250, state: { frost_protect: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("hvacThermostat", [attrThermFrostProtect]); }, }, thermostat_heat_protect: { key: ["heat_protect"], convertSet: async (entity, key, value, meta) => { utils.assertNumber(value); if (!utils.isInRange(25, 70, Number(value))) throw new Error(`Invalid value: ${value} (expected ${25} to ${70})`); const heat_protect = Number(Math.round(value)) * 100; await entity.write("hvacThermostat", { [attrThermHeatProtect]: { value: heat_protect, type: 0x29 } }); return { readAfterWriteTime: 250, state: { heat_protect: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("hvacThermostat", [attrThermHeatProtect]); }, }, thermostat_setpoint_raise_lower: { key: ["setpoint_raise_lower"], convertSet: async (entity, key, value, meta) => { utils.assertNumber(value); if (!utils.isInRange(-5, 5, Number(value))) throw new Error(`Invalid value: ${value} (expected ${-5} to ${5})`); const setpoint_raise_lower = Number(Math.fround(value)) * 10; //Step 0.1°C. 5°C - 50, 1°C - 10 etc. await entity.command("hvacThermostat", "setpointRaiseLower", { mode: 0, amount: setpoint_raise_lower }); return { readAfterWriteTime: 250, state: { setpoint_raise_lower: value } }; }, }, fancontrol_control: { key: ["fan_control"], convertSet: async (entity, key, value, meta) => { const fan_control = Number(value === "On"); await entity.write("hvacFanCtrl", { [attrFanCtrlControl]: { value: fan_control, type: 0x10 } }); return { readAfterWriteTime: 250, state: { fan_control: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("hvacFanCtrl", [attrFanCtrlControl]); }, }, thermostat_eco_mode: { key: ["eco_mode"], convertSet: async (entity, key, value, meta) => { const eco_mode = Number(value === "On"); await entity.write("hvacThermostat", { [attrThermEcoMode]: { value: eco_mode, type: 0x30 } }); return { readAfterWriteTime: 250, state: { eco_mode: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("hvacThermostat", [attrThermEcoMode]); }, }, thermostat_eco_mode_cool_temperature: { key: ["eco_mode_cool_temperature"], convertSet: async (entity, key, value, meta) => { utils.assertNumber(value); if (!utils.isInRange(5, 45, Number(value))) throw new Error(`Invalid value: ${value} (expected ${5} to ${45})`); const eco_mode_cool_temperature = Number(Math.round(value)) * 100; await entity.write("hvacThermostat", { [attrThermEcoModeCoolTemperature]: { value: eco_mode_cool_temperature, type: 0x29 } }); return { readAfterWriteTime: 250, state: { eco_mode_cool_temperature: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("hvacThermostat", [attrThermEcoModeCoolTemperature]); }, }, thermostat_eco_mode_heat_temperature: { key: ["eco_mode_heat_temperature"], convertSet: async (entity, key, value, meta) => { utils.assertNumber(value); if (!utils.isInRange(5, 45, Number(value))) throw new Error(`Invalid value: ${value} (expected ${5} to ${45})`); const eco_mode_heat_temperature = Number(Math.round(value)) * 100; await entity.write("hvacThermostat", { [attrThermEcoModeHeatTemperature]: { value: eco_mode_heat_temperature, type: 0x29 } }); return { readAfterWriteTime: 250, state: { eco_mode_heat_temperature: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("hvacThermostat", [attrThermEcoModeHeatTemperature]); }, }, thermostat_frost_protect_onoff: { key: ["frost_protect_on_off"], convertSet: async (entity, key, value, meta) => { const frost_protect_on_off = Number(value === "On"); await entity.write("hvacThermostat", { [attrThermFrostProtectOnOff]: { value: frost_protect_on_off, type: 0x10 } }); return { readAfterWriteTime: 250, state: { frost_protect_on_off: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("hvacThermostat", [attrThermFrostProtectOnOff]); }, }, thermostat_sound: { key: ["sound"], convertSet: async (entity, key, value, meta) => { const sound = Number(value === "On"); await entity.write("hvacThermostat", { [attrThermSound]: { value: sound, type: 0x10 } }); return { readAfterWriteTime: 250, state: { sound: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("hvacThermostat", [attrThermSound]); }, }, thermostat_brightness_level: { key: ["brightness_level"], convertSet: async (entity, key, value, meta) => { //utils.assertNumber(value); const lookup = { Off: 0, Low: 1, Medium: 2, High: 3 }; await entity.write("hvacThermostat", { [attrThermLevel]: { value: utils.getFromLookup(value, lookup), type: 0x30 } }); return { state: { brightness_level: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("hvacThermostat", [attrThermLevel]); }, }, thermostat_inversion: { key: ["inversion"], convertSet: async (entity, key, value, meta) => { const inversion = Number(value === "On"); await entity.write("hvacThermostat", { [attrThermInversion]: { value: inversion, type: 0x10 } }); return { readAfterWriteTime: 250, state: { inversion: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("hvacThermostat", [attrThermInversion]); }, }, thermostat_schedule_mode: { key: ["schedule_mode"], convertSet: async (entity, key, value, meta) => { utils.assertNumber(value); const lookup = { Off: 0, "5+2": 1, "6+1": 2, "7": 3 }; await entity.write("hvacThermostat", { [attrThermScheduleMode]: { value: utils.getFromLookup(value, lookup), type: 0x30 } }); return { state: { schedule_mode: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("hvacThermostat", [attrThermScheduleMode]); }, }, thermostat_settings_reset: { key: ["settings_reset"], convertSet: async (entity, key, value, meta) => { const settings_reset = Number(value === "Default"); await entity.write("hvacThermostat", { [attrThermSettingsReset]: { value: settings_reset, type: 0x10 } }); return { readAfterWriteTime: 250, state: { settings_reset: value } }; }, }, thermostat_ext_temperature_calibration: { key: ["external_temperature_calibration"], convertSet: async (entity, key, value, meta) => { utils.assertNumber(value); if (!utils.isInRange(-9, 9, Number(value))) throw new Error(`Invalid value: ${value} (expected ${-9} to ${9})`); const external_temperature_calibration = Number(Math.round(value)) * 10; await entity.write("hvacThermostat", { [attrThermExtTemperatureCalibration]: { value: external_temperature_calibration, type: 0x28 } }); return { readAfterWriteTime: 250, state: { external_temperature_calibration: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("hvacThermostat", [attrThermExtTemperatureCalibration]); }, }, }; const localFromZigbeeThermostat = [ fz.ignore_basic_report, fz.thermostat, fz.fan, fz.namron_hvac_user_interface, fz.thermostat_weekly_schedule, fzLocal.thermostat_custom_fw, fzLocal.thermostat_schedule, fzLocal.display_brightness, fzLocal.fancontrol_control, ]; const localToZigbeeThermostat = [ tz.thermostat_local_temperature, tz.thermostat_outdoor_temperature, tz.thermostat_system_mode, tz.thermostat_occupied_heating_setpoint, tz.thermostat_running_state, tz.thermostat_local_temperature_calibration, tz.thermostat_min_heat_setpoint_limit, tz.thermostat_max_heat_setpoint_limit, tz.thermostat_programming_operation_mode, tz.namron_thermostat_child_lock, tz.thermostat_weekly_schedule, tz.fan_mode, tzLocal.display_brightness, tzLocal.thermostat_sensor_used, tzLocal.thermostat_deadzone, tzLocal.thermostat_deadzone_10, tzLocal.thermostat_setpoint_raise_lower, tzLocal.thermostat_frost_protect, tzLocal.thermostat_heat_protect, tzLocal.thermostat_eco_mode, tzLocal.thermostat_eco_mode_cool_temperature, tzLocal.thermostat_eco_mode_heat_temperature, tzLocal.thermostat_frost_protect_onoff, tzLocal.thermostat_brightness_level, tzLocal.thermostat_sound, tzLocal.thermostat_inversion, tzLocal.thermostat_schedule_mode, tzLocal.thermostat_settings_reset, tzLocal.thermostat_ext_temperature_calibration, tzLocal.fancontrol_control, ]; async function configureCommon(device, coordinatorEndpoint, definition) { //logger.info(definition.model); const endpoint1 = device.getEndpoint(1); const endpoint2 = device.getEndpoint(2); await endpoint1.read("hvacUserInterfaceCfg", ["keypadLockout"]); await endpoint1.read("genLevelCtrl", ["currentLevel"]); await endpoint2.read("genLevelCtrl", ["currentLevel"]); await endpoint1.read("hvacThermostat", ["localTemp"]); await endpoint1.read("hvacThermostat", ["outdoorTemp"]); await endpoint1.read("hvacThermostat", ["absMinHeatSetpointLimit"]); await endpoint1.read("hvacThermostat", ["absMaxHeatSetpointLimit"]); await endpoint1.read("hvacThermostat", ["minHeatSetpointLimit"]); await endpoint1.read("hvacThermostat", ["maxHeatSetpointLimit"]); await endpoint1.read("hvacThermostat", ["localTemperatureCalibration"]); await endpoint1.read("hvacThermostat", ["occupiedHeatingSetpoint"]); await endpoint1.read("hvacThermostat", ["programingOperMode"]); await endpoint1.read("hvacThermostat", ["systemMode"]); await endpoint1.read("hvacThermostat", ["runningState"]); await endpoint1.read("hvacThermostat", ["minSetpointDeadBand"]); await endpoint1.read("hvacThermostat", [attrThermSensorUser]); await endpoint1.read("hvacThermostat", [attrThermFrostProtect]); await endpoint1.read("hvacThermostat", [attrThermHeatProtect]); await endpoint1.read("hvacThermostat", [attrThermEcoMode]); await endpoint1.read("hvacThermostat", [attrThermEcoModeCoolTemperature]); await endpoint1.read("hvacThermostat", [attrThermEcoModeHeatTemperature]); await endpoint1.read("hvacThermostat", [attrThermFrostProtectOnOff]); await endpoint1.read("hvacThermostat", [attrThermScheduleMode]); await endpoint1.read("hvacThermostat", [attrThermSound]); await endpoint1.read("hvacThermostat", [attrThermLevel]); await endpoint1.read("hvacThermostat", [attrThermInversion]); await endpoint1.read("hvacThermostat", [attrThermExtTemperatureCalibration]); await endpoint1.read("hvacFanCtrl", ["fanMode"]); await endpoint1.read("hvacFanCtrl", [attrFanCtrlControl]); await reporting.bind(endpoint1, coordinatorEndpoint, ["hvacThermostat", "hvacUserInterfaceCfg", "hvacFanCtrl"]); if (definition.model === model_r03 || definition.model === model_r04) { await reporting.bind(endpoint1, coordinatorEndpoint, ["genLevelCtrl"]); const payloadCurrentLevel = [ { attribute: { ID: 0x0000, type: 0x20 }, minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0 }, ]; await endpoint1.configureReporting("genLevelCtrl", payloadCurrentLevel); if (definition.model === model_r03) { await reporting.bind(endpoint2, coordinatorEndpoint, ["genLevelCtrl"]); await endpoint2.configureReporting("genLevelCtrl", payloadCurrentLevel); } } await reporting.thermostatTemperature(endpoint1, { min: 0, max: 3600, change: 0 }); await reporting.thermostatOccupiedHeatingSetpoint(endpoint1, { min: 0, max: 3600, change: 0 }); await reporting.thermostatRunningState(endpoint1, { min: 0, max: 3600, change: 0 }); await reporting.thermostatSystemMode(endpoint1, { min: 0, max: 3600, change: 0 }); await reporting.thermostatTemperatureCalibration(endpoint1, { min: 0, max: 3600, change: 0 }); await reporting.thermostatKeypadLockMode(endpoint1, { min: 0, max: 3600, change: 0 }); const payload_oper_mode = [{ attribute: "programingOperMode", minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0 }]; await endpoint1.configureReporting("hvacThermostat", payload_oper_mode); const payload_sensor_used = [ { attribute: { ID: attrThermSensorUser, type: 0x30 }, minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0 }, ]; await endpoint1.configureReporting("hvacThermostat", payload_sensor_used); const payload_deadzone = [{ attribute: { ID: 0x0019, type: 0x28 }, minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0 }]; await endpoint1.configureReporting("hvacThermostat", payload_deadzone); const payload_min = [{ attribute: { ID: 0x0015, type: 0x29 }, minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0 }]; await endpoint1.configureReporting("hvacThermostat", payload_min); const payload_max = [{ attribute: { ID: 0x0016, type: 0x29 }, minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0 }]; await endpoint1.configureReporting("hvacThermostat", payload_max); if (definition.model !== model_r01 && definition.model !== model_r06) { const payload_outdoor = [{ attribute: { ID: 0x0001, type: 0x29 }, minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0 }]; await endpoint1.configureReporting("hvacThermostat", payload_outdoor); } const payload_frost_protect = [ { attribute: { ID: attrThermFrostProtect, type: 0x29 }, minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0, }, ]; await endpoint1.configureReporting("hvacThermostat", payload_frost_protect); const payload_heat_protect = [ { attribute: { ID: attrThermHeatProtect, type: 0x29 }, minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0 }, ]; await endpoint1.configureReporting("hvacThermostat", payload_heat_protect); if (definition.model === model_r03 || definition.model === model_r04 || definition.model === model_r07) { const payload_eco_mode = [ { attribute: { ID: attrThermEcoMode, type: 0x30 }, minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0 }, ]; await endpoint1.configureReporting("hvacThermostat", payload_eco_mode); const payload_eco_mode_heat_temp = [ { attribute: { ID: attrThermEcoModeHeatTemperature, type: 0x29 }, minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0, }, ]; await endpoint1.configureReporting("hvacThermostat", payload_eco_mode_heat_temp); if (definition.model === model_r07) { const payload_eco_mode_cool_temp = [ { attribute: { ID: attrThermEcoModeCoolTemperature, type: 0x29 }, minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0, }, ]; await endpoint1.configureReporting("hvacThermostat", payload_eco_mode_cool_temp); const payload_fan_control = [ { attribute: { ID: attrFanCtrlControl, type: 0x10 }, minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0 }, ]; await endpoint1.configureReporting("hvacFanCtrl", payload_fan_control); await reporting.fanMode(endpoint1, { min: 0, max: 3600, change: 0 }); } } if (definition.model === model_r06) { const payload_frost_protect_onoff = [ { attribute: { ID: attrThermFrostProtectOnOff, type: 0x10 }, minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0 }, ]; await endpoint1.configureReporting("hvacThermostat", payload_frost_protect_onoff); const payload_sound = [ { attribute: { ID: attrThermSound, type: 0x10 }, minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0 }, ]; await endpoint1.configureReporting("hvacThermostat", payload_sound); const payload_inversion = [ { attribute: { ID: attrThermInversion, type: 0x10 }, minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0 }, ]; await endpoint1.configureReporting("hvacThermostat", payload_inversion); const payload_level = [ { attribute: { ID: attrThermLevel, type: 0x30 }, minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0 }, ]; await endpoint1.configureReporting("hvacThermostat", payload_level); const payload_schedule_mode = [ { attribute: { ID: attrThermScheduleMode, type: 0x30 }, minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0 }, ]; await endpoint1.configureReporting("hvacThermostat", payload_schedule_mode); } if (definition.model === model_r08) { const payload_eco_mode = [ { attribute: { ID: attrThermEcoMode, type: 0x30 }, minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0 }, ]; await endpoint1.configureReporting("hvacThermostat", payload_eco_mode); const payload_ext_temp_calibration = [ { attribute: { ID: attrThermExtTemperatureCalibration, type: 0x28 }, minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0, }, ]; await endpoint1.configureReporting("hvacThermostat", payload_ext_temp_calibration); } } const electricityMeterExtend = { elMeter: () => { const exposes = [ e.numeric("energy_tier_1", ea.STATE_GET).withUnit("kWh").withDescription("Energy consumed at Tier 1"), e.numeric("energy_tier_2", ea.STATE_GET).withUnit("kWh").withDescription("Energy consumed at Tier 2"), e.numeric("energy_tier_3", ea.STATE_GET).withUnit("kWh").withDescription("Energy consumed at Tier 3"), e.numeric("energy_tier_4", ea.STATE_GET).withUnit("kWh").withDescription("Energy consumed at Tier 4"), e.text("model_name", ea.STATE_GET).withDescription("Meter Model Name"), e.text("serial_number", ea.STATE_GET).withDescription("Meter Serial Number"), e.text("date_release", ea.STATE_GET).withDescription("Meter Date Release"), e.numeric("battery_life", ea.STATE_GET).withUnit("%").withDescription("Battery Life"), e.binary("tamper", ea.STATE, true, false).withDescription("Tamper"), e.binary("battery_low", ea.STATE, true, false).withDescription("Battery Low"), e.numeric("device_address_preset", ea.STATE_SET).withDescription("Device Address").withValueMin(1).withValueMax(9999999), e.text("device_password_preset", ea.STATE_SET).withDescription("Meter Password"), e.numeric("device_measurement_preset", ea.ALL).withDescription("Measurement Period").withValueMin(1).withValueMax(255), ]; const toZigbee = [ { key: ["energy_tier_1", "energy_tier_2", "energy_tier_3", "energy_tier_4"], convertGet: async (entity, key, meta) => { await entity.read("seMetering", [ "currentTier1SummDelivered", "currentTier2SummDelivered", "currentTier3SummDelivered", "currentTier4SummDelivered", ]); }, convertSet: async (entity, key, value, meta) => { return await null; }, }, { key: ["model_name"], convertGet: async (entity, key, meta) => { await entity.read("seMetering", [attrElCityMeterModelName]); }, convertSet: async (entity, key, value, meta) => { return await null; }, }, { key: ["serial_number"], convertGet: async (entity, key, meta) => { await entity.read("seMetering", ["meterSerialNumber"]); }, convertSet: async (entity, key, value, meta) => { return await null; }, }, { key: ["date_release"], convertGet: async (entity, key, meta) => { await entity.read("seMetering", [attrElCityMeterDateRelease]); }, convertSet: async (entity, key, value, meta) => { return await null; }, }, { key: ["battery_life"], convertGet: async (entity, key, meta) => { await entity.read("seMetering", ["remainingBattLife"]); }, convertSet: async (entity, key, value, meta) => { return await null; }, }, { key: ["device_address_preset"], convertSet: async (entity, key, value, meta) => { const device_address_preset = value; await entity.write("seMetering", { [attrElCityMeterAddressPreset]: { value: device_address_preset, type: 0x23 } }); return { readAfterWriteTime: 250, state: { device_address_preset: value } }; }, }, { key: ["device_password_preset"], convertSet: async (entity, key, value, meta) => { const device_password_preset = value.toString(); await entity.write("seMetering", { [attrElCityMeterPasswordPreset]: { value: device_password_preset, type: 0x41 } }); return { readAfterWriteTime: 250, state: { device_password_preset: value } }; }, }, { key: ["device_measurement_preset"], convertSet: async (entity, key, value, meta) => { const device_measurement_preset = value; await entity.write("seMetering", { [attrElCityMeterMeasurementPreset]: { value: device_measurement_preset, type: 0x20 } }); return { readAfterWriteTime: 250, state: { device_measurement_preset: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("seMetering", [attrElCityMeterMeasurementPreset]); }, }, ]; const fromZigbee = [ { cluster: "seMetering", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data.divisor !== undefined) { const energyDivisor = Number.parseInt(msg.data.divisor); globalStore.putValue(meta.device, "energyDivisor", energyDivisor); result.e_divisor = energyDivisor; } return result; }, }, { cluster: "seMetering", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data.multiplier !== undefined) { const energyMultiplier = Number.parseInt(msg.data.multiplier); globalStore.putValue(meta.device, "energyMultiplier", energyMultiplier); result.e_multiplier = energyMultiplier; } return result; }, }, { cluster: "seMetering", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data.currentTier1SummDelivered !== undefined) { let energyDivisor = globalStore.getValue(meta.device, "energyDivisor"); let energyMultiplier = globalStore.getValue(meta.device, "energyMultiplier"); if (energyDivisor === undefined) { energyDivisor = 1; } if (energyMultiplier === undefined) { energyMultiplier = 1; } const data = msg.data.currentTier1SummDelivered; result.energy_tier_1 = (Number.parseInt(data) / energyDivisor) * energyMultiplier; } return result; }, }, { cluster: "seMetering", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data.currentTier2SummDelivered !== undefined) { let energyDivisor = globalStore.getValue(meta.device, "energyDivisor"); let energyMultiplier = globalStore.getValue(meta.device, "energyMultiplier"); if (energyDivisor === undefined) { energyDivisor = 1; } if (energyMultiplier === undefined) { energyMultiplier = 1; } const data = msg.data.currentTier2SummDelivered; result.energy_tier_2 = (Number.parseInt(data) / energyDivisor) * energyMultiplier; } return result; }, }, { cluster: "seMetering", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data.currentTier3SummDelivered !== undefined) { let energyDivisor = globalStore.getValue(meta.device, "energyDivisor"); let energyMultiplier = globalStore.getValue(meta.device, "energyMultiplier"); if (energyDivisor === undefined) { energyDivisor = 1; } if (energyMultiplier === undefined) { energyMultiplier = 1; } const data = msg.data.currentTier3SummDelivered; result.energy_tier_3 = (Number.parseInt(data) / energyDivisor) * energyMultiplier; } return result; }, }, { cluster: "seMetering", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data.currentTier4SummDelivered !== undefined) { let energyDivisor = globalStore.getValue(meta.device, "energyDivisor"); let energyMultiplier = globalStore.getValue(meta.device, "energyMultiplier"); if (energyDivisor === undefined) { energyDivisor = 1; } if (energyMultiplier === undefined) { energyMultiplier = 1; } const data = msg.data.currentTier4SummDelivered; result.energy_tier_4 = (Number.parseInt(data) / energyDivisor) * energyMultiplier; } return result; }, }, { cluster: "seMetering", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data[attrElCityMeterModelName] !== undefined) { const data = msg.data[attrElCityMeterModelName]; result.model_name = data.toString(); } return result; }, }, { cluster: "seMetering", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data.meterSerialNumber !== undefined) { const data = msg.data.meterSerialNumber; result.serial_number = data.toString(); } return result; }, }, { cluster: "seMetering", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data[attrElCityMeterDateRelease] !== undefined) { const data = msg.data[attrElCityMeterDateRelease]; result.date_release = data.toString(); } return result; }, }, { cluster: "seMetering", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data.status !== undefined) { const data = msg.data.status; const value = Number.parseInt(data); return { battery_low: (value & (1 << 1)) > 0, tamper: (value & (1 << 2)) > 0, }; } return result; }, }, { cluster: "seMetering", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data.remainingBattLife !== undefined) { const data = Number.parseInt(msg.data.remainingBattLife); result.battery_life = data; } return result; }, }, { cluster: "seMetering", type: ["readResponse"], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data[attrElCityMeterMeasurementPreset] !== undefined) { const data = Number.parseInt(msg.data[attrElCityMeterMeasurementPreset]); result.device_measurement_preset = data; } return result; }, }, ]; return { exposes, fromZigbee, toZigbee, isModernExtend: true, }; }, }; function waterPreset() { const exposes = [ e .composite("preset", "preset", ea.SET) .withFeature(e .numeric("hot_water_preset", ea.SET) .withValueMin(0) .withValueMax(99999999) .withValueStep(1) .withUnit("L") .withDescription("Preset hot water")) .withFeature(e .numeric("cold_water_preset", ea.SET) .withValueMin(0) .withValueMax(99999999) .withValueStep(1) .withUnit("L") .withDescription("Preset cold water")) .withFeature(e .numeric("step_water_preset", ea.SET) .withValueMin(1) .withValueMax(100) .withValueStep(1) .withUnit("L") .withDescription("Preset step water")), ]; const toZigbee = [ { key: ["preset"], convertSet: async (entity, key, value, meta) => { const endpoint = meta.device.getEndpoint(3); const values = { // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress` hot_water: value.hot_water_preset, // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress` cold_water: value.cold_water_preset, // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress` step_water: value.step_water_preset, }; if (values.hot_water != null && values.hot_water >= 0) { const hot_water_preset = Number.parseInt(values.hot_water); await endpoint.write("seMetering", { 61440: { value: hot_water_preset, type: 0x23 } }); } if (values.cold_water != null && values.cold_water >= 0) { const cold_water_preset = Number.parseInt(values.cold_water); await endpoint.write("seMetering", { 61441: { value: cold_water_preset, type: 0x23 } }); } if (values.step_water != null && values.step_water >= 0) { const step_water_preset = Number.parseInt(values.step_water); await endpoint.write("seMetering", { 61442: { value: step_water_preset, type: 0x21 } }); } }, }, ]; return { toZigbee, exposes, isModernExtend: true }; } exports.definitions = [ { zigbeeModel: ["Tuya_CO2Sensor_r01"], model: "SLACKY_DIY_CO2_SENSOR_R01", vendor: "Slacky-DIY", description: "Tuya CO2 sensor with custom Firmware", extend: [m.co2({ reporting: ppmReporting })], ota: true, }, { zigbeeModel: ["Tuya_CO2Sensor_r02"], model: "SLACKY_DIY_CO2_SENSOR_R02", vendor: "Slacky-DIY", description: "Tuya CO2 sensor with custom Firmware", extend: [ m.co2({ reporting: ppmReporting }), m.numeric({ name: "formaldehyde", access: "STATE_GET", cluster: "msFormaldehyde", attribute: "measuredValue", reporting: ppmReporting, unit: "ppm", scale: 0.000001, precision: 2, description: "Measured Formaldehyde value", }), m.numeric({ name: "voc", access: "STATE_GET", cluster: "genAnalogInput", attribute: "presentValue", reporting: ppmReporting, unit: "ppm", scale: 0.000001, precision: 2, description: "Measured VOC value", }), m.temperature(), m.humidity(), ], ota: true, }, { zigbeeModel: ["Watermeter_TLSR8258"], model: "Watermeter_TLSR8258", vendor: "Slacky-DIY", description: "Water Meter", configure: async (device, coordinatorEndpoint, logger) => { const thirdEndpoint = device.getEndpoint(3); await thirdEndpoint.read("seMetering", [0xf000, 0xf001, 0xf002]); }, extend: [ m.deviceEndpoints({ endpoints: { "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, }, }), m.iasZoneAlarm({ zoneType: "water_leak", zoneAttributes: ["alarm_1"] }), m.battery({ voltage: true, voltageReporting: true, percentageReportingConfig: batteryReporting, voltageReportingConfig: batteryReporting, }), m.enumLookup({ name: "switch_actions", endpointName: "4", lookup: { on_off: 0, off_on: 1, toggle: 2 }, cluster: "genOnOffSwitchCfg", attribute: "switchActions", description: "Actions switch 1", }), m.enumLookup({ name: "switch_actions", endpointName: "5", lookup: { on_off: 0, off_on: 1, toggle: 2 }, cluster: "genOnOffSwitchCfg", attribute: "switchActions", description: "Actions switch 2", }), m.numeric({ name: "volume", endpointNames: ["1"], access: "STATE_GET", cluster: "seMetering", attribute: "currentSummDelivered", reporting: { min: 0, max: 300, change: 0 }, unit: "L", description: "Hot water", }), m.numeric({ name: "volume", endpointNames: ["2"], access: "STATE_GET", cluster: "seMetering"