UNPKG

zigbee-herdsman-converters

Version:

Collection of device converters to be used with zigbee-herdsman

1,087 lines 49.7 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 zigbee_herdsman_1 = require("zigbee-herdsman"); const fz = __importStar(require("../converters/fromZigbee")); const tz = __importStar(require("../converters/toZigbee")); const exposes = __importStar(require("../lib/exposes")); const legacy = __importStar(require("../lib/legacy")); const m = __importStar(require("../lib/modernExtend")); const reporting = __importStar(require("../lib/reporting")); const utils_1 = require("../lib/utils"); const e = exposes.presets; const ea = exposes.access; const switchTypesList = { switch: 0x00, "multi-click": 0x02, }; const tzLocal = { tirouter: { key: ["transmit_power"], convertSet: async (entity, key, value, meta) => { await entity.write("genBasic", { 4919: { value, type: 0x28 } }); return { state: { [key]: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("genBasic", [0x1337]); }, }, multi_zig_sw_switch_type: { key: ["switch_type_1", "switch_type_2", "switch_type_3", "switch_type_4"], convertGet: async (entity, key, meta) => { await entity.read("genOnOffSwitchCfg", ["switchType"]); }, convertSet: async (entity, key, value, meta) => { const data = (0, utils_1.getFromLookup)(value, switchTypesList); const payload = { switchType: data }; await entity.write("genOnOffSwitchCfg", payload); return { state: { [`${key}`]: value } }; }, }, ptvo_on_off: { key: ["state"], convertSet: async (entity, key, value, meta) => { return await tz.on_off.convertSet(entity, key, value, meta); }, convertGet: async (entity, key, meta) => { const cluster = "genOnOff"; if ((0, utils_1.isEndpoint)(entity) && (entity.supportsInputCluster(cluster) || entity.supportsOutputCluster(cluster))) { return await tz.on_off.convertGet(entity, key, meta); } return; }, }, }; const fzLocal = { tirouter: { cluster: "genBasic", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const result = { linkquality: msg.linkquality }; if (msg.data["4919"]) result.transmit_power = msg.data["4919"]; return result; }, }, humidity2: { cluster: "msRelativeHumidity", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { // multi-endpoint version based on the stastard onverter 'fz.humidity' let humidity = Number.parseFloat(msg.data.measuredValue) / 100.0; humidity = (0, utils_1.calibrateAndPrecisionRoundOptions)(humidity, options, "humidity"); // https://github.com/Koenkk/zigbee2mqtt/issues/798 // Sometimes the sensor publishes non-realistic vales, it should only publish message // in the 0 - 100 range, don't produce messages beyond these values. if (humidity >= 0 && humidity <= 100) { const multiEndpoint = model.meta?.multiEndpoint; const property = multiEndpoint ? (0, utils_1.postfixWithEndpointName)("humidity", msg, model, meta) : "humidity"; return { [property]: humidity }; } }, }, illuminance2: { cluster: "msIlluminanceMeasurement", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { // multi-endpoint version based on the stastard onverter 'fz.illuminance' const illuminance = msg.data.measuredValue; let illuminanceLux = illuminance === 0 ? 0 : 10 ** ((illuminance - 1) / 10000); illuminanceLux = (0, utils_1.calibrateAndPrecisionRoundOptions)(illuminanceLux, options, "illuminance"); const multiEndpoint = model.meta?.multiEndpoint; const property1 = multiEndpoint ? (0, utils_1.postfixWithEndpointName)("illuminance", msg, model, meta) : "illuminance"; return { [property1]: illuminanceLux }; }, }, pressure2: { cluster: "msPressureMeasurement", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { // multi-endpoint version based on the stastard onverter 'fz.pressure' let pressure = 0; if (msg.data.scaledValue !== undefined) { const scale = msg.endpoint.getClusterAttributeValue("msPressureMeasurement", "scale"); pressure = msg.data.scaledValue / 10 ** scale / 100.0; // convert to hPa } else { pressure = Number.parseFloat(msg.data.measuredValue); } pressure = (0, utils_1.calibrateAndPrecisionRoundOptions)(pressure, options, "pressure"); const multiEndpoint = model.meta?.multiEndpoint; const property = multiEndpoint ? (0, utils_1.postfixWithEndpointName)("pressure", msg, model, meta) : "pressure"; return { [property]: pressure }; }, }, multi_zig_sw_battery: { cluster: "genPowerCfg", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const voltage = msg.data.batteryVoltage * 100; const battery = (voltage - 2200) / 8; return { battery: battery > 100 ? 100 : battery, voltage: voltage }; }, }, multi_zig_sw_switch_buttons: { cluster: "genMultistateInput", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const button = (0, utils_1.getKey)(model.endpoint?.(msg.device) ?? {}, msg.endpoint.ID); const actionLookup = { 0: "release", 1: "single", 2: "double", 3: "triple", 4: "hold" }; const value = msg.data.presentValue; const action = actionLookup[value]; return { action: `${button}_${action}` }; }, }, multi_zig_sw_switch_config: { cluster: "genOnOffSwitchCfg", type: ["readResponse", "attributeReport"], convert: (model, msg, publish, options, meta) => { const channel = (0, utils_1.getKey)(model.endpoint?.(msg.device) ?? {}, msg.endpoint.ID); const { switchType } = msg.data; return { [`switch_type_${channel}`]: (0, utils_1.getKey)(switchTypesList, switchType) }; }, }, }; function ptvoGetMetaOption(device, key, defaultValue) { if (device != null) { const value = device.meta[key]; if (value === undefined) { return defaultValue; } return value; } return defaultValue; } function ptvoSetMetaOption(device, key, value) { if (device != null && key != null) { device.meta[key] = value; } } function ptvoAddStandardExposes(endpoint, expose, options, deviceOptions) { const epId = endpoint.ID; const epName = `l${epId}`; if (endpoint.supportsInputCluster("lightingColorCtrl")) { expose.push(e.light_brightness_colorxy().withEndpoint(epName)); options.exposed_onoff = true; options.exposed_analog = true; options.exposed_colorcontrol = true; } else if (endpoint.supportsInputCluster("genLevelCtrl")) { expose.push(e.light_brightness().withEndpoint(epName)); options.exposed_onoff = true; options.exposed_analog = true; options.exposed_levelcontrol = true; } if (endpoint.supportsInputCluster("genOnOff")) { if (!options.exposed_onoff) { expose.push(e.switch().withEndpoint(epName)); } } if (endpoint.supportsInputCluster("genAnalogInput") || endpoint.supportsOutputCluster("genAnalogInput")) { if (!options.exposed_analog) { options.exposed_analog = true; expose.push(e.text(epName, ea.ALL).withEndpoint(epName).withProperty(epName).withDescription("State or sensor value")); } } if (endpoint.supportsInputCluster("msTemperatureMeasurement")) { expose.push(e.temperature().withEndpoint(epName)); } if (endpoint.supportsInputCluster("msRelativeHumidity")) { expose.push(e.humidity().withEndpoint(epName)); } if (endpoint.supportsInputCluster("msPressureMeasurement")) { expose.push(e.pressure().withEndpoint(epName)); } if (endpoint.supportsInputCluster("msIlluminanceMeasurement")) { expose.push(e.illuminance().withEndpoint(epName)); } if (endpoint.supportsInputCluster("msCO2")) { expose.push(e.co2()); } if (endpoint.supportsInputCluster("pm25Measurement")) { expose.push(e.pm25()); } if (endpoint.supportsInputCluster("haElectricalMeasurement")) { // haElectricalMeasurement may expose only one value defined explicitly if (!(options.exposed_voltage || options.exposed_current || options.exposed_power)) { expose.push(e.voltage().withEndpoint(epName)); expose.push(e.current().withEndpoint(epName)); expose.push(e.power().withEndpoint(epName)); } } if (endpoint.supportsInputCluster("seMetering")) { if (!options.exposed_energy) { expose.push(e.energy().withEndpoint(epName)); } } if (endpoint.supportsInputCluster("genPowerCfg")) { deviceOptions.expose_battery = true; } if (endpoint.supportsInputCluster("genMultistateInput") || endpoint.supportsOutputCluster("genMultistateInput")) { deviceOptions.expose_action = true; } } exports.definitions = [ { /** @see https://github.com/Nerivec/silabs-firmware-builder/releases */ fingerprint: [ { modelID: "ZGA008", manufacturerName: "Aeotec", applicationVersion: 200 }, { modelID: "ZB-GW04", manufacturerName: "easyiot", applicationVersion: 200 }, { modelID: "ZB-GW04-1v1", manufacturerName: "easyiot", applicationVersion: 200 }, { modelID: "ZB-GW04-1v2", manufacturerName: "easyiot", applicationVersion: 200 }, { modelID: "SkyConnect", manufacturerName: "NabuCasa", applicationVersion: 200 }, { modelID: "SLZB-06M", manufacturerName: "SMLIGHT", applicationVersion: 200 }, { modelID: "SLZB-06MG24", manufacturerName: "SMLIGHT", applicationVersion: 200 }, { modelID: "SLZB-07", manufacturerName: "SMLIGHT", applicationVersion: 200 }, { modelID: "SLZB-07MG24", manufacturerName: "SMLIGHT", applicationVersion: 200 }, { modelID: "DONGLE-E", manufacturerName: "SONOFF", applicationVersion: 200 }, { modelID: "MGM240P", manufacturerName: "SparkFun", applicationVersion: 200 }, { modelID: "MGM24", manufacturerName: "TubesZB", applicationVersion: 200 }, { modelID: "MGM24PB", manufacturerName: "TubesZB", applicationVersion: 200 }, ], model: "Silabs series 2 router", vendor: "Silabs", description: "Silabs series 2 adapter with router firmware", toZigbee: [tz.factory_reset], exposes: [ e .enum("reset", ea.SET, ["reset"]) .withDescription("Resets and launches the bootloader for flashing. If USB, ensure the device is already connected to the machine where you intend to flash it before triggering this."), ], extend: [m.linkQuality({ reporting: true })], // prevent timeout with tz.factory_reset (reboots adapter into bootloader, hence disconnected) // since this is the only tz, it's not a problem to disable this globally meta: { disableDefaultResponse: true }, }, { zigbeeModel: ["ti.router"], model: "ti.router", vendor: "Custom devices (DiY)", description: "Texas Instruments router", fromZigbee: [fzLocal.tirouter], toZigbee: [tzLocal.tirouter], exposes: [ e .numeric("transmit_power", ea.ALL) .withValueMin(-20) .withValueMax(20) .withValueStep(1) .withUnit("dBm") .withDescription("Transmit power, supported from firmware 20221102. The max for CC1352 is 20 dBm and 5 dBm for CC2652" + " (any higher value is converted to 5dBm)"), ], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(8); const payload = [{ attribute: "zclVersion", minimumReportInterval: 0, maximumReportInterval: 3600, reportableChange: 0 }]; await reporting.bind(endpoint, coordinatorEndpoint, ["genBasic"]); await endpoint.configureReporting("genBasic", payload); }, }, { zigbeeModel: ["lumi.router"], model: "CC2530.ROUTER", vendor: "Custom devices (DiY)", description: "CC2530 router", fromZigbee: [fz.CC2530ROUTER_led, fz.CC2530ROUTER_meta, fz.ignore_basic_report], toZigbee: [tz.ptvo_switch_trigger], exposes: [e.binary("led", ea.STATE, true, false)], }, { zigbeeModel: ["cc2538.router.v1"], model: "CC2538.ROUTER.V1", vendor: "Custom devices (DiY)", description: "MODKAM stick СС2538 router", fromZigbee: [fz.ignore_basic_report], toZigbee: [], exposes: [], }, { zigbeeModel: ["cc2538.router.v2"], model: "CC2538.ROUTER.V2", vendor: "Custom devices (DiY)", description: "MODKAM stick СС2538 router with temperature sensor", fromZigbee: [fz.ignore_basic_report, fz.device_temperature], toZigbee: [], exposes: [e.device_temperature()], }, { zigbeeModel: ["ptvo.switch"], model: "ptvo.switch", vendor: "Custom devices (DiY)", description: "Multi-functional device", fromZigbee: [ fz.battery, fz.on_off, fz.ptvo_multistate_action, fz.ptvo_switch_uart, fz.ptvo_switch_analog_input, fz.brightness, fz.ignore_basic_report, fz.temperature, fzLocal.humidity2, fzLocal.pressure2, fzLocal.illuminance2, fz.electrical_measurement, fz.metering, fz.co2, ], toZigbee: [tz.ptvo_switch_trigger, tz.ptvo_switch_uart, tz.ptvo_switch_analog_input, tz.ptvo_switch_light_brightness, tzLocal.ptvo_on_off], exposes: (device, options) => { const expose = []; const exposeDeviceOptions = {}; const deviceConfig = ptvoGetMetaOption(device, "device_config", ""); if (deviceConfig === "") { if (device?.endpoints) { for (const endpoint of device.endpoints) { const exposeEpOptions = {}; ptvoAddStandardExposes(endpoint, expose, exposeEpOptions, exposeDeviceOptions); } } else { // fallback code for (let epId = 1; epId <= 8; epId++) { const epName = `l${epId}`; expose.push(e.text(epName, ea.ALL).withEndpoint(epName).withProperty(epName).withDescription("State or sensor value")); expose.push(e.switch().withEndpoint(epName)); } } } else { // device configuration description from a device const deviceConfigArray = deviceConfig.split(/[\r\n]+/); const allEndpoints = {}; const allEndpointsSorted = []; // biome-ignore lint/suspicious/noImplicitAnyLet: ignored using `--suppress` let epConfig; for (let i = 0; i < deviceConfigArray.length; i++) { epConfig = deviceConfigArray[i]; const matches = epConfig.match(/^([0-9A-F]+)/); if (!matches || matches.length === 0) { continue; } const epId = Number.parseInt(matches[0], 16); const epId2 = epId < 10 ? `0${epId}` : epId; epConfig = epConfig.replace(/^[0-9A-F]+/, epId2); allEndpoints[epId] = "1"; allEndpointsSorted.push(epConfig); } for (const endpoint of device.endpoints) { if (allEndpoints[endpoint.ID] !== undefined) { continue; } epConfig = endpoint.ID.toString(); if (endpoint.ID < 10) { epConfig = `0${epConfig}`; } allEndpointsSorted.push(epConfig); } allEndpointsSorted.sort(); for (let i = 0; i < allEndpointsSorted.length; i++) { epConfig = allEndpointsSorted[i]; const epId = Number.parseInt(epConfig.substr(0, 2), 10); epConfig = epConfig.substring(2); const epName = `l${epId}`; const epValueAccessRights = epConfig.substr(0, 1); const epStateType = epValueAccessRights === "W" || epValueAccessRights === "*" ? ea.STATE_SET : ea.STATE; const valueConfig = epConfig.substr(1); const valueConfigItems = valueConfig ? valueConfig.split(",") : []; let valueId = valueConfigItems[0] ? valueConfigItems[0] : ""; let valueDescription = valueConfigItems[1] ? valueConfigItems[1] : ""; let valueUnit = valueConfigItems[2] !== undefined ? valueConfigItems[2] : ""; if (exposeDeviceOptions[epName] === undefined) { exposeDeviceOptions[epName] = {}; } const exposeEpOptions = exposeDeviceOptions[epName]; if (valueId === "*") { // GPIO output (Generic) exposeEpOptions.exposed_onoff = true; expose.push(e.switch().withEndpoint(epName)); } else if (valueId === "#") { // GPIO state (contact, gas, noise, occupancy, presence, smoke, sos, tamper, vibration, water leak) exposeEpOptions.exposed_onoff = true; let exposeObj = undefined; switch (valueDescription) { case "g": exposeObj = e.gas(); break; case "n": exposeObj = e.noise_detected(); break; case "o": exposeObj = e.occupancy(); break; case "p": exposeObj = e.presence(); break; case "m": exposeObj = e.smoke(); break; case "s": exposeObj = e.sos(); break; case "t": exposeObj = e.tamper(); break; case "v": exposeObj = e.vibration(); break; case "w": exposeObj = e.water_leak(); break; default: // 'c' exposeObj = e.contact(); } expose.push(exposeObj.withProperty("state").withEndpoint(epName)); } else if (valueConfigItems.length > 0) { let valueName = undefined; // name in Z2M let valueNumIndex = undefined; const idxPos = valueId.search(/(\d+)$/); if (valueId.startsWith("mcpm") || valueId.startsWith("ncpm")) { const num = Number.parseInt(valueId.substr(4, 1), 16); valueName = valueId.substr(0, 4) + num; } else if (idxPos >= 0) { valueNumIndex = valueId.substr(idxPos); valueId = valueId.substr(0, idxPos); } // analog value // 1: value name (if empty, use the EP name) // 2: description (if empty or undefined, use the value name) // 3: units (if undefined, use the key name) const infoLookup = { C: "temperature", "%": "humidity", m: "altitude", Pa: "pressure", ppm: "quality", psize: "particle_size", V: "voltage", A: "current", Wh: "energy", W: "power", Hz: "frequency", pf: "power_factor", lx: "illuminance", }; valueName = valueName !== undefined ? valueName : infoLookup[valueId]; if (valueName === undefined && valueNumIndex) { valueName = `val${valueNumIndex}`; } if (valueName) { exposeEpOptions[`exposed_${valueName}`] = true; } valueName = valueName === undefined ? epName : `${valueName}_${epName}`; if (valueDescription === undefined || valueDescription === "") { if (infoLookup[valueId]) { valueDescription = infoLookup[valueId]; valueDescription = valueDescription.replace("_", " "); } else { valueDescription = "Sensor value"; } } valueDescription = valueDescription.substring(0, 1).toUpperCase() + valueDescription.substring(1); if (valueNumIndex) { valueDescription = `${valueDescription} ${valueNumIndex}`; } if ((valueUnit === undefined || valueUnit === "") && infoLookup[valueId]) { valueUnit = valueId; } exposeEpOptions.exposed_analog = true; expose.push(e .numeric(valueName, epStateType) .withValueMin(-9999999) .withValueMax(9999999) .withValueStep(1) .withDescription(valueDescription) .withUnit(valueUnit)); } const epConfigNext = allEndpointsSorted[i + 1] || "-1"; const epIdNext = Number.parseInt(epConfigNext.substr(0, 2), 10); if (epIdNext !== epId) { const endpoint = device.getEndpoint(epId); if (!endpoint) { continue; } ptvoAddStandardExposes(endpoint, expose, exposeEpOptions, exposeDeviceOptions); } } } if (exposeDeviceOptions.expose_action) { expose.push(e.action(["single", "double", "triple", "hold", "release"])); } if (exposeDeviceOptions.expose_battery) { expose.push(e.battery()); } return expose; }, meta: { multiEndpoint: true, tuyaThermostatPreset: legacy.fz /* for subclassed custom converters */ }, endpoint: (device) => { // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress` const endpointList = []; const deviceConfig = ptvoGetMetaOption(device, "device_config", ""); if (device?.endpoints) { for (const endpoint of device.endpoints) { const epId = endpoint.ID; const epName = `l${epId}`; endpointList[epName] = epId; } } if (deviceConfig === "") { if (endpointList.length === 0) { // fallback code for (let epId = 1; epId <= 8; epId++) { const epName = `l${epId}`; endpointList[epName] = epId; } } } else { const deviceConfigArray = deviceConfig.split(/[\r\n]+/); // biome-ignore lint/suspicious/noImplicitAnyLet: ignored using `--suppress` let epConfig; for (let i = 0; i < deviceConfigArray.length; i++) { epConfig = deviceConfigArray[i]; const matches = epConfig.match(/^([0-9A-F]+)/); if (!matches || matches.length === 0) { continue; } const epId = Number.parseInt(matches[0], 16); const epName = `l${epId}`; endpointList[epName] = epId; } } endpointList.action = 1; return endpointList; }, configure: async (device, coordinatorEndpoint) => { if (device != null) { const controlEp = device.getEndpoint(1); if (controlEp != null) { try { let deviceConfig = await controlEp.read("genBasic", [32768]); if (deviceConfig) { deviceConfig = deviceConfig["32768"]; ptvoSetMetaOption(device, "device_config", deviceConfig); device.save(); } } catch { /* do nothing */ } } for (const endpoint of device.endpoints) { if (endpoint.supportsInputCluster("haElectricalMeasurement")) { endpoint.saveClusterAttributeKeyValue("haElectricalMeasurement", { dcCurrentDivisor: 1000, dcCurrentMultiplier: 1, dcPowerDivisor: 10, dcPowerMultiplier: 1, dcVoltageDivisor: 100, dcVoltageMultiplier: 1, acVoltageDivisor: 100, acVoltageMultiplier: 1, acCurrentDivisor: 1000, acCurrentMultiplier: 1, acPowerDivisor: 10, acPowerMultiplier: 1, }); } if (endpoint.supportsInputCluster("seMetering")) { endpoint.saveClusterAttributeKeyValue("seMetering", { divisor: 1000, multiplier: 1 }); } } } }, }, { zigbeeModel: ["DNCKAT_D001"], model: "DNCKATSD001", vendor: "Custom devices (DiY)", description: "DNCKAT single key wired wall dimmable light switch", extend: [m.light()], }, { zigbeeModel: ["DNCKAT_S001"], model: "DNCKATSW001", vendor: "Custom devices (DiY)", description: "DNCKAT single key wired wall light switch", extend: [m.onOff()], }, { zigbeeModel: ["DNCKAT_S002"], model: "DNCKATSW002", vendor: "Custom devices (DiY)", description: "DNCKAT double key wired wall light switch", fromZigbee: [fz.DNCKAT_S00X_buttons], extend: [m.deviceEndpoints({ endpoints: { left: 1, right: 2 } }), m.onOff({ endpointNames: ["left", "right"] })], exposes: [e.action(["release_left", "hold_left", "release_right", "hold_right"])], }, { zigbeeModel: ["DNCKAT_S003"], model: "DNCKATSW003", vendor: "Custom devices (DiY)", description: "DNCKAT triple key wired wall light switch", fromZigbee: [fz.DNCKAT_S00X_buttons], extend: [m.deviceEndpoints({ endpoints: { left: 1, center: 2, right: 3 } }), m.onOff({ endpointNames: ["left", "center", "right"] })], exposes: [e.action(["release_left", "hold_left", "release_right", "hold_right", "release_center", "hold_center"])], }, { zigbeeModel: ["DNCKAT_S004"], model: "DNCKATSW004", vendor: "Custom devices (DiY)", description: "DNCKAT quadruple key wired wall light switch", fromZigbee: [fz.DNCKAT_S00X_buttons], extend: [ m.deviceEndpoints({ endpoints: { bottom_left: 1, bottom_right: 2, top_left: 3, top_right: 4 } }), m.onOff({ endpointNames: ["bottom_left", "bottom_right", "top_left", "top_right"] }), ], exposes: [ e.action([ "release_bottom_left", "hold_bottom_left", "release_bottom_right", "hold_bottom_right", "release_top_left", "hold_top_left", "release_top_right", "hold_top_right", ]), ], }, { zigbeeModel: ["ZigUP"], model: "ZigUP", vendor: "Custom devices (DiY)", description: "CC2530 based ZigBee relais, switch, sensor and router", fromZigbee: [fz.ZigUP], toZigbee: [tz.on_off, tz.light_color, tz.ZigUP_lock], exposes: [e.switch()], }, { zigbeeModel: ["ZWallRemote0"], model: "ZWallRemote0", vendor: "Custom devices (DiY)", description: "Matts Wall Switch Remote", fromZigbee: [fz.command_toggle], toZigbee: [], exposes: [e.action(["toggle"])], }, { zigbeeModel: ["ZeeFlora"], model: "ZeeFlora", vendor: "Custom devices (DiY)", description: "Flower sensor with rechargeable battery", fromZigbee: [fz.temperature, fz.soil_moisture, fz.battery], toZigbee: [], meta: { multiEndpoint: true }, configure: async (device, coordinatorEndpoint) => { const firstEndpoint = device.getEndpoint(1); await reporting.bind(firstEndpoint, coordinatorEndpoint, ["genPowerCfg", "msTemperatureMeasurement", "msSoilMoisture"]); const overrides = { min: 0, max: 3600, change: 0 }; await reporting.batteryVoltage(firstEndpoint, overrides); await reporting.batteryPercentageRemaining(firstEndpoint, overrides); await reporting.temperature(firstEndpoint, overrides); await reporting.soil_moisture(firstEndpoint, overrides); }, exposes: [e.soil_moisture(), e.battery(), e.temperature()], extend: [m.illuminance()], }, { zigbeeModel: ["UT-01"], model: "EFR32MG21.Router.1", vendor: "Custom devices (DiY)", description: "EFR32MG21 Zigbee bridge router", extend: [m.forcePowerSource({ powerSource: "Mains (single phase)" })], }, { zigbeeModel: ["UT-02"], model: "EFR32MG21.Router.2", vendor: "Custom devices (DiY)", description: "EFR32MG21 router", fromZigbee: [], toZigbee: [], exposes: [], }, { zigbeeModel: ["b-parasite"], model: "b-parasite", vendor: "Custom devices (DiY)", description: "b-parasite open source soil moisture sensor", fromZigbee: [fz.temperature, fz.humidity, fz.battery, fz.soil_moisture], toZigbee: [], exposes: [e.temperature(), e.humidity(), e.battery(), e.soil_moisture()], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(10); await reporting.bind(endpoint, coordinatorEndpoint, ["genPowerCfg", "msTemperatureMeasurement", "msRelativeHumidity", "msSoilMoisture"]); await reporting.batteryPercentageRemaining(endpoint); await reporting.temperature(endpoint); await reporting.humidity(endpoint); await reporting.soil_moisture(endpoint); }, extend: [m.illuminance(), m.identify()], }, { zigbeeModel: ["MULTI-ZIG-SW"], model: "MULTI-ZIG-SW", vendor: "smarthjemmet.dk", description: "Multi switch from Smarthjemmet.dk", fromZigbee: [fz.ignore_basic_report, fzLocal.multi_zig_sw_switch_buttons, fzLocal.multi_zig_sw_battery, fzLocal.multi_zig_sw_switch_config], toZigbee: [tzLocal.multi_zig_sw_switch_type], exposes: [ ...[e.enum("switch_type_1", exposes.access.ALL, Object.keys(switchTypesList)).withEndpoint("button_1")], ...[e.enum("switch_type_2", exposes.access.ALL, Object.keys(switchTypesList)).withEndpoint("button_2")], ...[e.enum("switch_type_3", exposes.access.ALL, Object.keys(switchTypesList)).withEndpoint("button_3")], ...[e.enum("switch_type_4", exposes.access.ALL, Object.keys(switchTypesList)).withEndpoint("button_4")], e.battery(), e.action(["single", "double", "triple", "hold", "release"]), e.battery_voltage(), ], meta: { multiEndpoint: true }, endpoint: (device) => { return { button_1: 2, button_2: 3, button_3: 4, button_4: 5 }; }, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await endpoint.read("genBasic", ["modelId", "swBuildId", "powerSource"]); }, }, { // https://github.com/devbis/z03mmc/ zigbeeModel: ["LYWSD03MMC"], model: "LYWSD03MMC", vendor: "Custom devices (DiY)", description: "Xiaomi temperature & humidity sensor with custom firmware", extend: [ m.quirkAddEndpointCluster({ endpointID: 1, outputClusters: [], inputClusters: ["genPowerCfg", "msTemperatureMeasurement", "msRelativeHumidity", "hvacUserInterfaceCfg"], }), m.battery(), m.temperature({ reporting: { min: 10, max: 300, change: 10 } }), m.humidity({ reporting: { min: 10, max: 300, change: 50 } }), m.enumLookup({ name: "temperature_display_mode", lookup: { celsius: 0, fahrenheit: 1 }, cluster: "hvacUserInterfaceCfg", attribute: "tempDisplayMode", description: "The units of the temperature displayed on the device screen.", }), m.binary({ name: "show_smiley", valueOn: ["SHOW", 1], valueOff: ["HIDE", 0], cluster: "hvacUserInterfaceCfg", attribute: { ID: 0x0010, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN }, description: "Whether to show a smiley on the device screen.", }), m.binary({ name: "enable_display", valueOn: ["ON", 1], valueOff: ["OFF", 0], cluster: "hvacUserInterfaceCfg", attribute: { ID: 0x0011, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN }, description: "Whether to turn display on/off.", }), m.numeric({ name: "temperature_calibration", unit: "°C", cluster: "msTemperatureMeasurement", attribute: { ID: 0x0010, type: zigbee_herdsman_1.Zcl.DataType.INT16 }, valueMin: -100.0, valueMax: 100.0, valueStep: 0.01, scale: 100, description: "The temperature calibration offset is set in 0.01° steps.", }), m.numeric({ name: "humidity_calibration", unit: "%", cluster: "msRelativeHumidity", attribute: { ID: 0x0010, type: zigbee_herdsman_1.Zcl.DataType.INT16 }, valueMin: -100.0, valueMax: 100.0, valueStep: 0.01, scale: 100, description: "The humidity calibration offset is set in 0.01 % steps.", }), m.numeric({ name: "comfort_temperature_min", unit: "°C", cluster: "hvacUserInterfaceCfg", attribute: { ID: 0x0102, type: zigbee_herdsman_1.Zcl.DataType.INT16 }, valueMin: -100.0, valueMax: 100.0, scale: 100, description: "Comfort parameters/Temperature minimum, in 0.01°C steps.", }), m.numeric({ name: "comfort_temperature_max", unit: "°C", cluster: "hvacUserInterfaceCfg", attribute: { ID: 0x0103, type: zigbee_herdsman_1.Zcl.DataType.INT16 }, valueMin: -100.0, valueMax: 100.0, scale: 100, description: "Comfort parameters/Temperature maximum, in 0.01°C steps.", }), m.numeric({ name: "comfort_humidity_min", unit: "%", cluster: "hvacUserInterfaceCfg", attribute: { ID: 0x0104, type: zigbee_herdsman_1.Zcl.DataType.UINT16 }, valueMin: 0.0, valueMax: 100.0, scale: 100, description: "Comfort parameters/Humidity minimum, in 0.01% steps.", }), m.numeric({ name: "comfort_humidity_max", unit: "%", cluster: "hvacUserInterfaceCfg", attribute: { ID: 0x0105, type: zigbee_herdsman_1.Zcl.DataType.UINT16 }, valueMin: 0.0, valueMax: 100.0, scale: 100, description: "Comfort parameters/Humidity maximum, in 0.01% steps.", }), ], ota: true, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); const bindClusters = ["msTemperatureMeasurement", "msRelativeHumidity", "genPowerCfg"]; await reporting.bind(endpoint, coordinatorEndpoint, bindClusters); await reporting.temperature(endpoint, { min: 10, max: 300, change: 10 }); await reporting.humidity(endpoint, { min: 10, max: 300, change: 50 }); await reporting.batteryPercentageRemaining(endpoint); try { await endpoint.read("hvacThermostat", [0x0010, 0x0011, 0x0102, 0x0103, 0x0104, 0x0105]); await endpoint.read("msTemperatureMeasurement", [0x0010]); await endpoint.read("msRelativeHumidity", [0x0010]); } catch { /* backward compatibility */ } }, }, { zigbeeModel: ["MHO-C401N"], model: "MHO-C401N", vendor: "Custom devices (DiY)", description: "Xiaomi temperature & humidity sensor with custom firmware", extend: [ m.quirkAddEndpointCluster({ endpointID: 1, outputClusters: ["hvacUserInterfaceCfg"], inputClusters: ["genPowerCfg", "msTemperatureMeasurement", "msRelativeHumidity", "hvacUserInterfaceCfg"], }), m.battery(), m.temperature({ reporting: { min: 10, max: 300, change: 10 } }), m.humidity({ reporting: { min: 10, max: 300, change: 50 } }), // Temperature display and show smile. // For details, see: https://github.com/pvvx/ZigbeeTLc/issues/28#issue-2033984519 m.enumLookup({ name: "temperature_display_mode", lookup: { celsius: 0, fahrenheit: 1 }, cluster: "hvacUserInterfaceCfg", attribute: "tempDisplayMode", description: "The units of the temperature displayed on the device screen.", }), m.binary({ name: "show_smile", valueOn: ["HIDE", 1], valueOff: ["SHOW", 0], cluster: "hvacUserInterfaceCfg", attribute: "programmingVisibility", description: "Whether to show a smile on the device screen.", }), // Setting offsets for temperature and humidity. // For details, see: https://github.com/pvvx/ZigbeeTLc/issues/30 m.numeric({ name: "temperature_calibration", unit: "C", cluster: "hvacUserInterfaceCfg", attribute: { ID: 0x0100, type: 40 }, valueMin: -12.7, valueMax: 12.7, valueStep: 0.1, scale: 10, description: "The temperature calibration, in 0.1° steps. Requires v0.1.1.6 or newer.", }), m.numeric({ name: "humidity_calibration", unit: "%", cluster: "hvacUserInterfaceCfg", attribute: { ID: 0x0101, type: 40 }, valueMin: -12.7, valueMax: 12.7, valueStep: 0.1, scale: 10, description: "The humidity offset is set in 0.1 % steps. Requires v0.1.1.6 or newer.", }), // Comfort parameters. // For details, see: https://github.com/pvvx/ZigbeeTLc/issues/28#issuecomment-1855763432 m.numeric({ name: "comfort_temperature_min", unit: "C", cluster: "hvacUserInterfaceCfg", attribute: { ID: 0x0102, type: 40 }, valueMin: -127, valueMax: 127, description: "Comfort parameters/Temperature minimum, in 1° steps. Requires v0.1.1.7 or newer.", }), m.numeric({ name: "comfort_temperature_max", unit: "C", cluster: "hvacUserInterfaceCfg", attribute: { ID: 0x0103, type: 40 }, valueMin: -127, valueMax: 127, description: "Comfort parameters/Temperature maximum, in 1° steps. Requires v0.1.1.7 or newer.", }), m.numeric({ name: "comfort_humidity_min", unit: "%", cluster: "hvacUserInterfaceCfg", attribute: { ID: 0x0104, type: 32 }, valueMin: 0, valueMax: 100, description: "Comfort parameters/Humidity minimum, in 1% steps. Requires v0.1.1.7 or newer.", }), m.numeric({ name: "comfort_humidity_max", unit: "%", cluster: "hvacUserInterfaceCfg", attribute: { ID: 0x0105, type: 32 }, valueMin: 0, valueMax: 100, description: "Comfort parameters/Humidity maximum, in 1% steps. Requires v0.1.1.7 or newer.", }), ], ota: true, }, { zigbeeModel: ["QUAD-ZIG-SW"], model: "QUAD-ZIG-SW", vendor: "smarthjemmet.dk", description: "FUGA compatible switch from Smarthjemmet.dk", fromZigbee: [fz.ignore_basic_report, fzLocal.multi_zig_sw_switch_buttons, fzLocal.multi_zig_sw_battery, fzLocal.multi_zig_sw_switch_config], toZigbee: [tzLocal.multi_zig_sw_switch_type], exposes: [ ...[e.enum("switch_type_1", exposes.access.ALL, Object.keys(switchTypesList)).withEndpoint("button_1")], ...[e.enum("switch_type_2", exposes.access.ALL, Object.keys(switchTypesList)).withEndpoint("button_2")], ...[e.enum("switch_type_3", exposes.access.ALL, Object.keys(switchTypesList)).withEndpoint("button_3")], ...[e.enum("switch_type_4", exposes.access.ALL, Object.keys(switchTypesList)).withEndpoint("button_4")], e.battery(), e.action(["single", "double", "triple", "hold", "release"]), e.battery_voltage(), ], meta: { multiEndpoint: true }, endpoint: (device) => { return { button_1: 2, button_2: 3, button_3: 4, button_4: 5 }; }, configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(1); await endpoint.read("genBasic", ["modelId", "swBuildId", "powerSource"]); }, }, { zigbeeModel: ["ptvo_counter_2ch"], model: "ptvo_counter_2ch", vendor: "Custom devices (DiY)", description: "2 channel counter", fromZigbee: [fz.ignore_basic_report, fz.battery, fz.ptvo_switch_analog_input, fz.on_off], toZigbee: [tz.ptvo_switch_trigger, tz.ptvo_switch_analog_input, tz.on_off], exposes: [ e.battery(), e .numeric("l3", ea.ALL) .withValueMin(-999999999) .withValueMax(999999999) .withDescription("Counter 1 value. Write zero or positive value to set a counter value. " + "Write a negative value to set a wakeup interval in minutes"), e .numeric("l5", ea.ALL) .withValueMin(-999999999) .withValueMax(999999999) .withDescription("Counter 2 value. Write zero or positive value to set a counter value. " + "Write a negative value to set a wakeup interval in minutes"), e.switch().withEndpoint("l6"), e.battery_voltage(), ], meta: { multiEndpoint: true }, endpoint: (device) => { return { l3: 3, l5: 5, l6: 6 }; }, }, { zigbeeModel: ["alab.switch"], model: "alab.switch", vendor: "Alab", description: "Four channel relay board with four inputs", extend: [ m.deviceEndpoints({ endpoints: { l1: 1, l2: 2, l3: 3, l4: 4, in1: 5, in2: 6, in3: 7, in4: 8 } }), m.onOff({ powerOnBehavior: false, configureReporting: false, endpointNames: ["l1", "l2", "l3", "l4"], }), m.commandsOnOff({ endpointNames: ["l1", "l2", "l3", "l4"] }), m.numeric({ name: "input_state", valueMin: 0, valueMax: 1, cluster: "genAnalogInput", attribute: "presentValue", description: "Input state", endpointNames: ["in1", "in2", "in3", "in4"], }), ], }, { zigbeeModel: ["FanBee1", "Fanbox2"], model: "FanBee", vendor: "Lorenz Brun", description: "Fan with valve", fromZigbee: [fz.on_off, fz.fan_speed], toZigbee: [tz.on_off, tz.fan_speed], exposes: [e.fan().withState().withSpeed()], }, ]; //# sourceMappingURL=custom_devices_diy.js.map