UNPKG

zigbee-herdsman-converters

Version:

Collection of device converters to be used with zigbee-herdsman

981 lines 47.1 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 (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __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 }); const dataType_1 = __importDefault(require("zigbee-herdsman/dist/zcl/definition/dataType")); const exposes = __importStar(require("../lib/exposes")); const fromZigbee_1 = __importDefault(require("../converters/fromZigbee")); const legacy = __importStar(require("../lib/legacy")); const toZigbee_1 = __importDefault(require("../converters/toZigbee")); const reporting = __importStar(require("../lib/reporting")); const ota = __importStar(require("../lib/ota")); const e = exposes.presets; const ea = exposes.access; const utils_1 = require("../lib/utils"); const modernExtend_1 = require("../lib/modernExtend"); const switchTypesList = { 'switch': 0x00, 'multi-click': 0x02, }; const tzLocal = { tirouter: { key: ['transmit_power'], convertSet: async (entity, key, value, meta) => { await entity.write('genBasic', { 0x1337: { 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 toZigbee_1.default.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 toZigbee_1.default.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' const humidity = parseFloat(msg.data['measuredValue']) / 100.0; // 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 && model.meta.hasOwnProperty('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' // DEPRECATED: only return lux here (change illuminance_lux -> illuminance) const illuminance = msg.data['measuredValue']; const illuminanceLux = illuminance === 0 ? 0 : Math.pow(10, (illuminance - 1) / 10000); const multiEndpoint = model.meta && model.meta.hasOwnProperty('multiEndpoint') && model.meta.multiEndpoint; const property1 = (multiEndpoint) ? (0, utils_1.postfixWithEndpointName)('illuminance', msg, model, meta) : 'illuminance'; const property2 = (multiEndpoint) ? (0, utils_1.postfixWithEndpointName)('illuminance_lux', msg, model, meta) : 'illuminance_lux'; return { [property1]: illuminance, [property2]: 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.hasOwnProperty('scaledValue')) { const scale = msg.endpoint.getClusterAttributeValue('msPressureMeasurement', 'scale'); pressure = msg.data['scaledValue'] / Math.pow(10, scale) / 100.0; // convert to hPa } else { pressure = parseFloat(msg.data['measuredValue']); } const multiEndpoint = model.meta && model.meta.hasOwnProperty('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; } else { 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('l1').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') || endpoint.supportsOutputCluster('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().withEndpoint(epName)); } if (endpoint.supportsInputCluster('pm25Measurement')) { expose.push(e.pm25().withEndpoint(epName)); } if (endpoint.supportsInputCluster('haElectricalMeasurement')) { expose.push(e.voltage().withEndpoint(epName)); expose.push(e.current().withEndpoint(epName)); expose.push(e.power().withEndpoint(epName)); } if (endpoint.supportsInputCluster('seMetering')) { 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; } } const definitions = [ { 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: [fromZigbee_1.default.CC2530ROUTER_led, fromZigbee_1.default.CC2530ROUTER_meta, fromZigbee_1.default.ignore_basic_report], toZigbee: [toZigbee_1.default.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: [fromZigbee_1.default.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: [fromZigbee_1.default.ignore_basic_report, fromZigbee_1.default.device_temperature], toZigbee: [], exposes: [e.device_temperature()], }, { zigbeeModel: ['ptvo.switch'], model: 'ptvo.switch', vendor: 'Custom devices (DiY)', description: 'Multi-functional device', fromZigbee: [fromZigbee_1.default.battery, fromZigbee_1.default.on_off, fromZigbee_1.default.ptvo_multistate_action, legacy.fz.ptvo_switch_buttons, fromZigbee_1.default.ptvo_switch_uart, fromZigbee_1.default.ptvo_switch_analog_input, fromZigbee_1.default.brightness, fromZigbee_1.default.ignore_basic_report, fromZigbee_1.default.temperature, fzLocal.humidity2, fzLocal.pressure2, fzLocal.illuminance2, fromZigbee_1.default.electrical_measurement, fromZigbee_1.default.metering, fromZigbee_1.default.co2], toZigbee: [toZigbee_1.default.ptvo_switch_trigger, toZigbee_1.default.ptvo_switch_uart, toZigbee_1.default.ptvo_switch_analog_input, toZigbee_1.default.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 != null) && 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 = []; for (let i = 0; i < deviceConfigArray.length; i++) { const epConfig = deviceConfigArray[i]; const epId = parseInt(epConfig.substr(0, 1), 16); if (epId <= 0) { continue; } allEndpoints[epId] = epConfig; allEndpointsSorted.push(epId); } for (const endpoint of device.endpoints) { if (allEndpoints.hasOwnProperty(endpoint.ID)) { continue; } allEndpointsSorted.push(endpoint.ID); allEndpoints[endpoint.ID] = ''; } allEndpointsSorted.sort(); let prevEp = -1; for (let i = 0; i < allEndpointsSorted.length; i++) { const epId = allEndpointsSorted[i]; const epConfig = allEndpoints[epId]; if (epId <= 0) { continue; } const epName = `l${epId}`; const epValueAccessRights = epConfig.substr(1, 1); const epStateType = ((epValueAccessRights === 'W') || (epValueAccessRights === '*')) ? ea.STATE_SET : ea.STATE; const valueConfig = epConfig.substr(2); const valueConfigItems = (valueConfig) ? valueConfig.split(',') : []; let valueId = (valueConfigItems[0]) ? valueConfigItems[0] : ''; let valueDescription = (valueConfigItems[1]) ? valueConfigItems[1] : ''; let valueUnit = (valueConfigItems[2] !== undefined) ? valueConfigItems[2] : ''; const exposeEpOptions = {}; 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 = 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_lux', }; valueName = (valueName !== undefined) ? valueName : infoLookup[valueId]; if ((valueName === undefined) && valueNumIndex) { valueName = 'val' + valueNumIndex; } 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 endpoint = device.getEndpoint(epId); if (!endpoint) { continue; } if (prevEp !== epId) { prevEp = epId; 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()); } expose.push(e.linkquality()); return expose; }, meta: { multiEndpoint: true, tuyaThermostatPreset: legacy.fz /* for subclassed custom converters */ }, endpoint: (device) => { // eslint-disable-next-line const endpointList = []; const deviceConfig = ptvoGetMetaOption(device, 'device_config', ''); if (deviceConfig === '') { if ((device != null) && device.endpoints) { for (const endpoint of device.endpoints) { const epId = endpoint.ID; const epName = `l${epId}`; endpointList[epName] = epId; } } else { // fallback code for (let epId = 1; epId <= 8; epId++) { const epName = `l${epId}`; endpointList[epName] = epId; } } } else { for (let i = 0; i < deviceConfig.length; i++) { const epConfig = deviceConfig.charCodeAt(i); if (epConfig === 0x20) { continue; } const epId = i + 1; 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 (err) { /* 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: [(0, modernExtend_1.light)()], }, { zigbeeModel: ['DNCKAT_S001'], model: 'DNCKATSW001', vendor: 'Custom devices (DiY)', description: 'DNCKAT single key wired wall light switch', extend: [(0, modernExtend_1.onOff)()], }, { zigbeeModel: ['DNCKAT_S002'], model: 'DNCKATSW002', vendor: 'Custom devices (DiY)', description: 'DNCKAT double key wired wall light switch', fromZigbee: [fromZigbee_1.default.DNCKAT_S00X_buttons], extend: [(0, modernExtend_1.deviceEndpoints)({ endpoints: { left: 1, right: 2 } }), (0, modernExtend_1.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: [fromZigbee_1.default.DNCKAT_S00X_buttons], extend: [(0, modernExtend_1.deviceEndpoints)({ endpoints: { left: 1, center: 2, right: 3 } }), (0, modernExtend_1.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: [fromZigbee_1.default.DNCKAT_S00X_buttons], extend: [ (0, modernExtend_1.deviceEndpoints)({ endpoints: { bottom_left: 1, bottom_right: 2, top_left: 3, top_right: 4 } }), (0, modernExtend_1.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: [fromZigbee_1.default.ZigUP], toZigbee: [toZigbee_1.default.on_off, toZigbee_1.default.light_color, toZigbee_1.default.ZigUP_lock], exposes: [e.switch()], }, { zigbeeModel: ['ZWallRemote0'], model: 'ZWallRemote0', vendor: 'Custom devices (DiY)', description: 'Matts Wall Switch Remote', fromZigbee: [fromZigbee_1.default.command_toggle], toZigbee: [], exposes: [e.action(['toggle'])], }, { zigbeeModel: ['ZeeFlora'], model: 'ZeeFlora', vendor: 'Custom devices (DiY)', description: 'Flower sensor with rechargeable battery', fromZigbee: [fromZigbee_1.default.temperature, fromZigbee_1.default.illuminance, fromZigbee_1.default.soil_moisture, fromZigbee_1.default.battery], toZigbee: [], meta: { multiEndpoint: true }, configure: async (device, coordinatorEndpoint) => { const firstEndpoint = device.getEndpoint(1); await reporting.bind(firstEndpoint, coordinatorEndpoint, [ 'genPowerCfg', 'msTemperatureMeasurement', 'msIlluminanceMeasurement', '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.illuminance(firstEndpoint, overrides); await reporting.soil_moisture(firstEndpoint, overrides); }, exposes: [e.soil_moisture(), e.battery(), e.illuminance(), e.temperature()], }, { zigbeeModel: ['UT-02'], model: 'EFR32MG21.Router', 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: [fromZigbee_1.default.temperature, fromZigbee_1.default.humidity, fromZigbee_1.default.battery, fromZigbee_1.default.soil_moisture, fromZigbee_1.default.illuminance], toZigbee: [], exposes: [e.temperature(), e.humidity(), e.battery(), e.soil_moisture(), e.illuminance_lux()], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(10); await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'msTemperatureMeasurement', 'msRelativeHumidity', 'msSoilMoisture', 'msIlluminanceMeasurement']); await reporting.batteryPercentageRemaining(endpoint); await reporting.temperature(endpoint); await reporting.humidity(endpoint); await reporting.soil_moisture(endpoint); await reporting.illuminance(endpoint); }, }, { zigbeeModel: ['MULTI-ZIG-SW'], model: 'MULTI-ZIG-SW', vendor: 'smarthjemmet.dk', description: 'Multi switch from Smarthjemmet.dk', fromZigbee: [fromZigbee_1.default.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: [ (0, modernExtend_1.quirkAddEndpointCluster)({ endpointID: 1, outputClusters: [], inputClusters: [ 'genPowerCfg', 'msTemperatureMeasurement', 'msRelativeHumidity', 'hvacUserInterfaceCfg', ], }), (0, modernExtend_1.battery)(), (0, modernExtend_1.temperature)({ reporting: { min: 10, max: 300, change: 10 } }), (0, modernExtend_1.humidity)({ reporting: { min: 10, max: 300, change: 50 } }), (0, modernExtend_1.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.', }), (0, modernExtend_1.binary)({ name: 'show_smiley', valueOn: ['SHOW', 1], valueOff: ['HIDE', 0], cluster: 'hvacUserInterfaceCfg', attribute: { ID: 0x0010, type: dataType_1.default.boolean }, description: 'Whether to show a smiley on the device screen.', }), (0, modernExtend_1.binary)({ name: 'enable_display', valueOn: ['ON', 1], valueOff: ['OFF', 0], cluster: 'hvacUserInterfaceCfg', attribute: { ID: 0x0011, type: dataType_1.default.boolean }, description: 'Whether to turn display on/off.', }), (0, modernExtend_1.numeric)({ name: 'temperature_calibration', unit: '°C', cluster: 'msTemperatureMeasurement', attribute: { ID: 0x0010, type: dataType_1.default.int16 }, valueMin: -100.0, valueMax: 100.0, valueStep: 0.01, scale: 100, description: 'The temperature calibration offset is set in 0.01° steps.', }), (0, modernExtend_1.numeric)({ name: 'humidity_calibration', unit: '%', cluster: 'msRelativeHumidity', attribute: { ID: 0x0010, type: dataType_1.default.int16 }, valueMin: -100.0, valueMax: 100.0, valueStep: 0.01, scale: 100, description: 'The humidity calibration offset is set in 0.01 % steps.', }), (0, modernExtend_1.numeric)({ name: 'comfort_temperature_min', unit: '°C', cluster: 'hvacUserInterfaceCfg', attribute: { ID: 0x0102, type: dataType_1.default.int16 }, valueMin: -100.0, valueMax: 100.0, scale: 100, description: 'Comfort parameters/Temperature minimum, in 0.01°C steps.', }), (0, modernExtend_1.numeric)({ name: 'comfort_temperature_max', unit: '°C', cluster: 'hvacUserInterfaceCfg', attribute: { ID: 0x0103, type: dataType_1.default.int16 }, valueMin: -100.0, valueMax: 100.0, scale: 100, description: 'Comfort parameters/Temperature maximum, in 0.01°C steps.', }), (0, modernExtend_1.numeric)({ name: 'comfort_humidity_min', unit: '%', cluster: 'hvacUserInterfaceCfg', attribute: { ID: 0x0104, type: dataType_1.default.uint16 }, valueMin: 0.0, valueMax: 100.0, scale: 100, description: 'Comfort parameters/Humidity minimum, in 0.01% steps.', }), (0, modernExtend_1.numeric)({ name: 'comfort_humidity_max', unit: '%', cluster: 'hvacUserInterfaceCfg', attribute: { ID: 0x0105, type: dataType_1.default.uint16 }, valueMin: 0.0, valueMax: 100.0, scale: 100, description: 'Comfort parameters/Humidity maximum, in 0.01% steps.', }), ], ota: ota.zigbeeOTA, 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 (e) { /* backward compatibility */ } }, }, { zigbeeModel: ['MHO-C401N'], model: 'MHO-C401N', vendor: 'Custom devices (DiY)', description: 'Xiaomi temperature & humidity sensor with custom firmware', extend: [ (0, modernExtend_1.quirkAddEndpointCluster)({ endpointID: 1, outputClusters: [ 'hvacUserInterfaceCfg', ], inputClusters: [ 'genPowerCfg', 'msTemperatureMeasurement', 'msRelativeHumidity', 'hvacUserInterfaceCfg', ], }), (0, modernExtend_1.battery)(), (0, modernExtend_1.temperature)({ reporting: { min: 10, max: 300, change: 10 } }), (0, modernExtend_1.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 (0, modernExtend_1.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.', }), (0, modernExtend_1.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 (0, modernExtend_1.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.', }), (0, modernExtend_1.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 (0, modernExtend_1.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.', }), (0, modernExtend_1.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.', }), (0, modernExtend_1.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.', }), (0, modernExtend_1.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: ota.zigbeeOTA, }, { zigbeeModel: ['QUAD-ZIG-SW'], model: 'QUAD-ZIG-SW', vendor: 'smarthjemmet.dk', description: 'FUGA compatible switch from Smarthjemmet.dk', fromZigbee: [fromZigbee_1.default.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: [fromZigbee_1.default.ignore_basic_report, fromZigbee_1.default.battery, fromZigbee_1.default.ptvo_switch_analog_input, fromZigbee_1.default.on_off], toZigbee: [toZigbee_1.default.ptvo_switch_trigger, toZigbee_1.default.ptvo_switch_analog_input, toZigbee_1.default.on_off], exposes: [e.battery(), e.enum('l3', ea.ALL, ['set']).withDescription('Counter value. Write zero or positive value to set a counter value. ' + 'Write a negative value to set a wakeup interval in minutes'), e.enum('l5', ea.ALL, ['set']).withDescription('Counter 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: [ (0, modernExtend_1.deviceEndpoints)({ endpoints: { 'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4, 'in1': 5, 'in2': 6, 'in3': 7, 'in4': 8 } }), (0, modernExtend_1.onOff)({ powerOnBehavior: false, configureReporting: false, endpointNames: ['l1', 'l2', 'l3', 'l4'] }), (0, modernExtend_1.commandsOnOff)({ endpointNames: ['l1', 'l2', 'l3', 'l4'] }), (0, modernExtend_1.numeric)({ name: 'input_state', valueMin: 0, valueMax: 1, cluster: 'genAnalogInput', attribute: 'presentValue', description: 'Input state', endpointNames: ['in1', 'in2', 'in3', 'in4'], }), ], }, ]; exports.default = definitions; module.exports = definitions; //# sourceMappingURL=custom_devices_diy.js.map