diff --git a/zigbee-shepherd-converters/converters/fromZigbee.js b/zigbee-shepherd-converters/converters/fromZigbee.js index 3e6fbfa..68c8ca2 100644 --- a/zigbee-shepherd-converters/converters/fromZigbee.js +++ b/zigbee-shepherd-converters/converters/fromZigbee.js @@ -30,6 +30,17 @@ const precisionRound = (number, precision) => { return Math.round(number * factor) / factor; }; +const calibrateOptions = (number, options, type) => { + const key = `${type}_calibration`; + let calibrationOffset = options && options.hasOwnProperty(key) ? options[key] : 0; + if (type == 'illuminance') { + // linear calibration because measured value is zero based + // +/- percent + calibrationOffset = Math.round(number * calibrationOffset / 100); + } + return number + calibrationOffset; +}; + const toPercentage = (value, min, max) => { if (value > max) { value = max; @@ -447,7 +458,8 @@ const converters = { type: ['attReport', 'readRsp'], convert: (model, msg, publish, options) => { const temperature = parseFloat(msg.data.data['measuredValue']) / 100.0; - return {temperature: precisionRoundOptions(temperature, options, 'temperature')}; + const calTemperature = calibrateOptions(temperature, options, 'temperature'); + return {temperature: precisionRoundOptions(calTemperature, options, 'temperature')}; }, }, generic_temperature_change: { @@ -455,7 +467,8 @@ const converters = { type: 'devChange', convert: (model, msg, publish, options) => { const temperature = parseFloat(msg.data.data['measuredValue']) / 100.0; - return {temperature: precisionRoundOptions(temperature, options, 'temperature')}; + const calTemperature = calibrateOptions(temperature, options, 'temperature'); + return {temperature: precisionRoundOptions(calTemperature, options, 'temperature')}; }, }, xiaomi_temperature: { @@ -558,7 +571,7 @@ const converters = { return lookup[value] ? lookup[value] : null; }, }, - xiaomi_humidity: { + generic_humidity: { cid: 'msRelativeHumidity', type: ['attReport', 'readRsp'], convert: (model, msg, publish, options) => { @@ -671,7 +684,7 @@ const converters = { cid: 'genBasic', type: ['attReport', 'readRsp'], convert: (model, msg, publish, options) => { - if (msg.data.data.hasOwnProperty('65281')) { + if (msg.data.data.hasOwnProperty('65281') && msg.data.data['65281'].hasOwnProperty('100')) { return {contact: msg.data.data['65281']['100'] === 0}; } }, @@ -708,11 +721,11 @@ const converters = { result.color = {}; if (msg.data.data['currentX']) { - result.color.x = precisionRound(msg.data.data['currentX'] / 65535, 3); + result.color.x = precisionRound(msg.data.data['currentX'] / 65535, 4); } if (msg.data.data['currentY']) { - result.color.y = precisionRound(msg.data.data['currentY'] / 65535, 3); + result.color.y = precisionRound(msg.data.data['currentY'] / 65535, 4); } if (msg.data.data['currentSaturation']) { @@ -750,11 +763,11 @@ const converters = { result.color = {}; if (msg.data.data['currentX']) { - result.color.x = precisionRound(msg.data.data['currentX'] / 65535, 3); + result.color.x = precisionRound(msg.data.data['currentX'] / 65535, 4); } if (msg.data.data['currentY']) { - result.color.y = precisionRound(msg.data.data['currentY'] / 65535, 3); + result.color.y = precisionRound(msg.data.data['currentY'] / 65535, 4); } if (msg.data.data['currentSaturation']) { @@ -791,7 +804,10 @@ const converters = { cid: 'msIlluminanceMeasurement', type: ['attReport', 'readRsp'], convert: (model, msg, publish, options) => { - return {illuminance: msg.data.data['measuredValue']}; + const illuminance = msg.data.data['measuredValue']; + const calIlluminance = calibrateOptions(illuminance, options, 'illuminance'); + // calibration value in +/- percent! + return {illuminance: calIlluminance}; }, }, generic_pressure: { @@ -799,7 +815,8 @@ const converters = { type: ['attReport', 'readRsp'], convert: (model, msg, publish, options) => { const pressure = parseFloat(msg.data.data['measuredValue']); - return {pressure: precisionRoundOptions(pressure, options, 'pressure')}; + const calPressure = calibrateOptions(pressure, options, 'pressure'); + return {pressure: precisionRoundOptions(calPressure, options, 'pressure')}; }, }, WXKG02LM_click: { @@ -867,6 +884,15 @@ const converters = { return {water_leak: msg.data.zoneStatus === 1}; }, }, + SJCGQ11LM_water_leak_interval: { + cid: 'genBasic', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + if (msg.data.data.hasOwnProperty('65281') && msg.data.data['65281'].hasOwnProperty('100')) { + return {water_leak: msg.data.data['65281']['100'] === 1}; + } + }, + }, state: { cid: 'genOnOff', type: ['attReport', 'readRsp'], @@ -1190,6 +1216,7 @@ const converters = { convert: (model, msg, publish, options) => { const batt = msg.data.data.batteryPercentageRemaining; const battLow = msg.data.data.batteryAlarmState; + const voltage = msg.data.data.batteryVoltage; const results = {}; if (batt != null) { const value = Math.round(batt/200.0*10000)/100; // Out of 200 @@ -1202,6 +1229,9 @@ const converters = { results['battery_low'] = false; } } + if (voltage != null) { + results['voltage'] = voltage * 100; + } return results; }, }, @@ -1449,12 +1479,12 @@ const converters = { '2': 'dig-in', }; - var ds18b20_id = null; - var ds18b20_value = null; - if (msg.data.data['41368']) { - ds18b20_id = msg.data.data['41368'].split(':')[0]; - ds18b20_value = precisionRound(msg.data.data['41368'].split(':')[1], 2); - } + let ds18b20Id = null; + let ds18b20Value = null; + if (msg.data.data['41368']) { + ds18b20Id = msg.data.data['41368'].split(':')[0]; + ds18b20Value = precisionRound(msg.data.data['41368'].split(':')[1], 2); + } return { state: msg.data.data['onOff'] === 1 ? 'ON' : 'OFF', @@ -1465,7 +1495,7 @@ const converters = { adc_volt: precisionRound(msg.data.data['41365'], 3), dig_input: msg.data.data['41366'], reason: lookup[msg.data.data['41367']], - [`${ds18b20_id}`]: ds18b20_value, + [`${ds18b20Id}`]: ds18b20Value, }; }, }, @@ -1889,6 +1919,34 @@ const converters = { }; }, }, + generic_ias_zone_occupancy_status_change_no_off_msg: { + cid: 'ssIasZone', + type: 'statusChange', + convert: (model, msg, publish, options) => { + const zoneStatus = msg.data.zoneStatus; + const useOptionsTimeout = options && options.hasOwnProperty('occupancy_timeout'); + const timeout = useOptionsTimeout ? options.occupancy_timeout : occupancyTimeout; + const deviceID = msg.endpoints[0].device.ieeeAddr; + + if (store[deviceID]) { + clearTimeout(store[deviceID]); + store[deviceID] = null; + } + + if (timeout !== 0) { + store[deviceID] = setTimeout(() => { + publish({occupancy: false}); + store[deviceID] = null; + }, timeout * 1000); + } + + return { + occupancy: (zoneStatus & 1) > 0, // Bit 0 = Alarm 1: Presence Indication + tamper: (zoneStatus & 1<<2) > 0, // Bit 2 = Tamper status + battery_low: (zoneStatus & 1<<3) > 0, // Bit 3 = Battery LOW indicator (trips around 2.4V) + }; + }, + }, generic_ias_zone_motion_dev_change: { cid: 'ssIasZone', type: 'devChange', @@ -2076,6 +2134,10 @@ const converters = { result.unoccupied_heating_setpoint = precisionRound(msg.data.data['unoccupiedHeatingSetpoint'], 2) / 100; } + if (typeof msg.data.data['occupiedCoolingSetpoint'] == 'number') { + result.occupied_cooling_setpoint = + precisionRound(msg.data.data['occupiedCoolingSetpoint'], 2) / 100; + } if (typeof msg.data.data['weeklySchedule'] == 'number') { result.weekly_schedule = msg.data.data['weeklySchedule']; } @@ -2136,6 +2198,10 @@ const converters = { result.unoccupied_heating_setpoint = precisionRound(msg.data.data['unoccupiedHeatingSetpoint'], 2) / 100; } + if (typeof msg.data.data['occupiedCoolingSetpoint'] == 'number') { + result.occupied_cooling_setpoint = + precisionRound(msg.data.data['occupiedCoolingSetpoint'], 2) / 100; + } if (typeof msg.data.data['weeklySchedule'] == 'number') { result.weekly_schedule = msg.data.data['weeklySchedule']; } @@ -2606,6 +2672,25 @@ const converters = { return {position: msg.data.data.currentPositionLiftPercentage}; }, }, + closuresWindowCovering_report_pos_and_tilt: { + cid: 'closuresWindowCovering', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const result = {}; + // ZigBee officially expects "open" to be 0 and "closed" to be 100 whereas + // HomeAssistant etc. work the other way round. + // ubisys J1 will report 255 if lift or tilt positions are not known. + if (msg.data.data.hasOwnProperty('currentPositionLiftPercentage')) { + const liftPercentage = msg.data.data['currentPositionLiftPercentage']; + result.position = liftPercentage <= 100 ? (100 - liftPercentage) : null; + } + if (msg.data.data.hasOwnProperty('currentPositionTiltPercentage')) { + const tiltPercentage = msg.data.data['currentPositionTiltPercentage']; + result.tilt = tiltPercentage <= 100 ? (100 - tiltPercentage) : null; + } + return result; + }, + }, generic_fan_mode: { cid: 'hvacFanCtrl', type: 'attReport', @@ -2799,7 +2884,7 @@ const converters = { return result; }, }, - ZNMS12LM_closuresDoorLock_report: { + ZNMS12LM_ZNMS13LM_closuresDoorLock_report: { cid: 'closuresDoorLock', type: 'attReport', convert: (model, msg, publish, options) => { @@ -2821,6 +2906,7 @@ const converters = { 14: 'change_language_to', 15: 'finger_open', 16: 'password_open', + 17: 'door_closed', }; result.user = null; result.repeat = null; @@ -2828,34 +2914,68 @@ const converters = { // Convert data back to hex to decode const data = Buffer.from(msg.data.data['65526'], 'ascii').toString('hex'); const command = data.substr(6, 4); - if (command === '0301') { + if ( + command === '0301' // ZNMS12LM + || command === '0341' // ZNMS13LM + ) { result.action = lockStatusLookup[4]; result.state = 'UNLOCK'; result.reverse = 'UNLOCK'; - } else if (command === '0311') { + } else if ( + command === '0311' // ZNMS12LM + || command === '0351' // ZNMS13LM + ) { result.action = lockStatusLookup[4]; result.state = 'LOCK'; result.reverse = 'UNLOCK'; - } else if (command === '0205') { + } else if ( + command === '0205' // ZNMS12LM + || command === '0245' // ZNMS13LM + ) { result.action = lockStatusLookup[3]; result.state = 'UNLOCK'; result.reverse = 'LOCK'; - } else if (command === '0215') { + } else if ( + command === '0215' // ZNMS12LM + || command === '0255' // ZNMS13LM + || command === '1355' // ZNMS13LM + ) { result.action = lockStatusLookup[3]; result.state = 'LOCK'; result.reverse = 'LOCK'; - } else if (command === '0111') { + } else if ( + command === '0111' // ZNMS12LM + || command === '1351' // ZNMS13LM locked from inside + || command === '1451' // ZNMS13LM locked from outside + ) { result.action = lockStatusLookup[5]; result.state = 'LOCK'; result.reverse = 'UNLOCK'; - } else if (command === '0b00') { + } else if ( + command === '0b00' // ZNMS12LM + || command === '0640' // ZNMS13LM + ||command === '0600' // ZNMS13LM + + ) { result.action = lockStatusLookup[12]; result.state = 'UNLOCK'; result.reverse = 'UNLOCK'; - } else if (command === '0c00') { + } else if ( + command === '0c00' // ZNMS12LM + || command === '2300' // ZNMS13LM + || command === '0540' // ZNMS13LM + || command === '0440' // ZNMS13LM + ) { result.action = lockStatusLookup[11]; result.state = 'UNLOCK'; result.reverse = 'UNLOCK'; + } else if ( + command === '2400' // ZNMS13LM door closed from insed + || command === '2401' // ZNMS13LM door closed from outside + ) { + result.action = lockStatusLookup[17]; + result.state = 'UNLOCK'; + result.reverse = 'UNLOCK'; } } else if (msg.data.data['65296']) { // finger/password success const data = Buffer.from(msg.data.data['65296'], 'ascii').toString('hex'); @@ -2896,6 +3016,126 @@ const converters = { return result; }, }, + DTB190502A1_parse: { + cid: 'genOnOff', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const lookupKEY = { + '0': 'KEY_SYS', + '1': 'KEY_UP', + '2': 'KEY_DOWN', + '3': 'KEY_NONE', + }; + const lookupLED = { + '0': 'OFF', + '1': 'ON', + }; + return { + cpu_temperature: precisionRound(msg.data.data['41361'], 2), + key_state: lookupKEY[msg.data.data['41362']], + led_state: lookupLED[msg.data.data['41363']], + }; + }, + }, + konke_click: { + cid: 'genOnOff', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const value = msg.data.data['onOff']; + const lookup = { + 128: {click: 'single'}, // single click + 129: {click: 'double'}, // double and many click + 130: {click: 'long'}, // hold + }; + + return lookup[value] ? lookup[value] : null; + }, + }, + E1746_linkquality: { + cid: 'genBasic', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + return {linkquality: msg.linkquality}; + }, + }, + generic_change_batteryvoltage_3000_2500: { + cid: 'genPowerCfg', + type: ['devChange'], + convert: (model, msg, publish, options) => { + const battery = {max: 3000, min: 2500}; + const voltage = msg.data.data['batteryVoltage'] * 100; + return { + battery: toPercentage(voltage, battery.min, battery.max), + voltage: voltage, + }; + }, + }, + generic_device_temperature: { + cid: 'genDeviceTempCfg', + type: ['devChange'], + convert: (model, msg, publish, options) => { + if (msg.data.data.hasOwnProperty('currentTemperature')) { + return {temperature: msg.data.data.currentTemperature}; + } + }, + }, + ptvo_switch_state: { + cid: 'genOnOff', + type: 'attReport', + convert: (model, msg, publish, options) => { + const ep = msg.endpoints[0]; + const key = `state_${getKey(model.ep(ep.device), ep.epId)}`; + const payload = {}; + payload[key] = msg.data.data['onOff'] === 1 ? 'ON' : 'OFF'; + return payload; + }, + }, + ptvo_switch_buttons: { + cid: 'genMultistateInput', + type: 'attReport', + convert: (model, msg, publish, options) => { + const ep = msg.endpoints[0]; + const button = getKey(model.ep(ep.device), ep.epId); + const value = msg.data.data['presentValue']; + + const actionLookup = { + 1: 'single', + 2: 'double', + 3: 'tripple', + 4: 'hold', + }; + + const action = actionLookup[value]; + + if (button) { + return {click: button + (action ? `_${action}` : '')}; + } + }, + }, + keypad20states: { + cid: 'genOnOff', + type: ['devChange', 'attReport'], + convert: (model, msg, publish, options) => { + const ep = msg.endpoints[0]; + const button = getKey(model.ep(ep.device), ep.epId); + const state = msg.data.data['onOff'] === 1 ? true : false; + if (button) { + return {[button]: state}; + } + }, + }, + keypad20_battery: { + cid: 'genPowerCfg', + type: ['devChange', 'attReport'], + convert: (model, msg, publish, options) => { + const battery = {max: 3000, min: 2100}; + const voltage = msg.data.data['mainsVoltage'] /10; + return { + battery: toPercentage(voltage, battery.min, battery.max), + voltage: voltage, + }; + }, + }, // Ignore converters (these message dont need parsing). ignore_fan_change: { @@ -3015,7 +3255,7 @@ const converters = { }, ignore_metering_change: { cid: 'seMetering', - type: 'devChange', + type: ['devChange', 'attReport'], convert: (model, msg, publish, options) => null, }, ignore_electrical_change: { diff --git a/zigbee-shepherd-converters/converters/toZigbee.js b/zigbee-shepherd-converters/converters/toZigbee.js index 583d8d4..cf2589b 100644 --- a/zigbee-shepherd-converters/converters/toZigbee.js +++ b/zigbee-shepherd-converters/converters/toZigbee.js @@ -2,7 +2,7 @@ const utils = require('./utils'); const common = require('./common'); -const zclId = require('zigbee-herdsman/dist/zcl-id'); +const Zcl = require('zigbee-herdsman/dist/zcl'); const cfg = { default: { @@ -31,8 +31,22 @@ const cfg = { manufSpec: 1, manufCode: 0x110c, }, + sinope: { + manufSpec: 1, + manufCode: 0x119C, + }, }; +function getTransition(message, options) { + if (message.hasOwnProperty('transition')) { + return message.transition; + } else if (options.hasOwnProperty('transition')) { + return options.transition; + } else { + return 0; + } +} + const converters = { /** * Generic @@ -75,7 +89,7 @@ const converters = { cid: cid, cmd: 'read', cmdType: 'foundation', - zclData: [{attrId: zclId.attr(cid, attrId).value}], + zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } @@ -124,7 +138,7 @@ const converters = { cid: cid, cmd: 'read', cmdType: 'foundation', - zclData: [{attrId: zclId.attr(cid, attrId).value}], + zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } @@ -170,12 +184,64 @@ const converters = { } }, }, + cover_control: { + key: ['state'], + convert: (key, value, message, type, postfix, options) => { + const zclCmdLookup = { + 'open': 'upOpen', + 'close': 'downClose', + 'stop': 'stop', + 'on': 'upOpen', + 'off': 'downClose', + }; + + const zclCmd = zclCmdLookup[value.toLowerCase()]; + if (zclCmd) { + return [{ + cid: 'closuresWindowCovering', + cmdType: 'functional', + cmd: zclCmd, + zclData: {}, + cfg: cfg.default, + }]; + } + }, + }, + cover_gotopercentage: { + key: ['position', 'tilt'], + convert: (key, value, message, type, postfix, options) => { + const isPosition = (key === 'position'); + const cid = 'closuresWindowCovering'; + const attrId = isPosition ? 'currentPositionLiftPercentage' : 'currentPositionTiltPercentage'; + // ZigBee officially expects "open" to be 0 and "closed" to be 100 whereas + // HomeAssistant etc. work the other way round. + value = 100 - value; + + if (type === 'set') { + return [{ + cid: cid, + cmdType: 'functional', + cmd: isPosition ? 'goToLiftPercentage' : 'goToTiltPercentage', + zclData: isPosition ? {percentageliftvalue: value} : {percentagetiltvalue: value}, + cfg: cfg.default, + }]; + } else if (type === 'get') { + return [{ + cid: cid, + cmdType: 'foundation', + cmd: 'read', + zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], + cfg: cfg.default, + }]; + } + }, + }, occupancy_timeout: { // set delay after motion detector changes from occupied to unoccupied key: ['occupancy_timeout'], convert: (key, value, message, type, postfix, options) => { const cid = 'msOccupancySensing'; // 1030 - const attrId = zclId.attr(cid, 'pirOToUDelay').value; // = 16 + const attrId = Zcl.getAttributeLegacy(cid, 'pirOToUDelay').value; // = 16 if (type === 'set') { return [{ @@ -228,7 +294,7 @@ const converters = { cmdType: 'functional', zclData: { level: Number(value), - transtime: message.hasOwnProperty('transition') ? message.transition * 10 : 0, + transtime: getTransition(message, options) * 10, }, cfg: cfg.default, newState: {brightness: Number(value)}, @@ -240,7 +306,7 @@ const converters = { cid: cid, cmd: 'read', cmdType: 'foundation', - zclData: [{attrId: zclId.attr(cid, attrId).value}], + zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } @@ -255,7 +321,7 @@ const converters = { const brightnessValue = message.hasOwnProperty('brightness') ? message.brightness : message.brightness_percent; const hasState = message.hasOwnProperty('state'); - const hasTrasition = message.hasOwnProperty('transition'); + const hasTrasition = message.hasOwnProperty('transition') || options.hasOwnProperty('transition'); const state = hasState ? message.state.toLowerCase() : null; if (hasState && (state === 'off' || !hasBrightness) && !hasTrasition) { @@ -279,17 +345,19 @@ const converters = { brightness = Math.round(Number(message.brightness_percent) * 2.55).toString(); } + const transition = getTransition(message, options); + return [{ cid: 'genLevelCtrl', cmd: 'moveToLevelWithOnOff', cmdType: 'functional', zclData: { level: Number(brightness), - transtime: message.hasOwnProperty('transition') ? message.transition * 10 : 0, + transtime: transition * 10, }, cfg: options.disFeedbackRsp ? cfg.defaultdisFeedbackRsp : cfg.default, newState: {state: brightness === 0 ? 'OFF' : 'ON', brightness: Number(brightness)}, - readAfterWriteTime: message.hasOwnProperty('transition') ? message.transition * 1000 : 0, + readAfterWriteTime: transition * 1000, }]; } } else if (type === 'get') { @@ -298,14 +366,14 @@ const converters = { cid: 'genOnOff', cmd: 'read', cmdType: 'foundation', - zclData: [{attrId: zclId.attr('genOnOff', 'onOff').value}], + zclData: [{attrId: Zcl.getAttributeLegacy('genOnOff', 'onOff').value}], cfg: cfg.default, }, { cid: 'genLevelCtrl', cmd: 'read', cmdType: 'foundation', - zclData: [{attrId: zclId.attr('genLevelCtrl', 'currentLevel').value}], + zclData: [{attrId: Zcl.getAttributeLegacy('genLevelCtrl', 'currentLevel').value}], cfg: cfg.default, }, ]; @@ -332,7 +400,7 @@ const converters = { cmdType: 'functional', zclData: { colortemp: value, - transtime: message.hasOwnProperty('transition') ? message.transition * 10 : 0, + transtime: getTransition(message, options) * 10, }, cfg: cfg.default, newState: {color_temp: value}, @@ -343,7 +411,7 @@ const converters = { cid: cid, cmd: 'read', cmdType: 'foundation', - zclData: [{attrId: zclId.attr(cid, attrId).value}], + zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } @@ -383,7 +451,7 @@ const converters = { } const zclData = { - transtime: message.hasOwnProperty('transition') ? message.transition * 10 : 0, + transtime: getTransition(message, options) * 10, }; let newState = null; @@ -424,8 +492,8 @@ const converters = { cmd: 'read', cmdType: 'foundation', zclData: [ - {attrId: zclId.attr(cid, 'currentX').value}, - {attrId: zclId.attr(cid, 'currentY').value}, + {attrId: Zcl.getAttributeLegacy(cid, 'currentX').value}, + {attrId: Zcl.getAttributeLegacy(cid, 'currentY').value}, ], cfg: cfg.default, }]; @@ -460,9 +528,9 @@ const converters = { cmd: 'read', cmdType: 'foundation', zclData: [ - {attrId: zclId.attr(cid, 'currentX').value}, - {attrId: zclId.attr(cid, 'currentY').value}, - {attrId: zclId.attr(cid, 'colorTemperature').value}, + {attrId: Zcl.getAttributeLegacy(cid, 'currentX').value}, + {attrId: Zcl.getAttributeLegacy(cid, 'currentY').value}, + {attrId: Zcl.getAttributeLegacy(cid, 'colorTemperature').value}, ], cfg: cfg.default, }]; @@ -509,7 +577,7 @@ const converters = { cid: cid, cmd: 'read', cmdType: 'foundation', - zclData: [{attrId: zclId.attr(cid, attrId).value}], + zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } @@ -526,8 +594,8 @@ const converters = { cmd: 'write', cmdType: 'foundation', zclData: [{ - attrId: zclId.attr(cid, attrId).value, - dataType: zclId.attrType(cid, attrId).value, + attrId: Zcl.getAttributeLegacy(cid, attrId).value, + dataType: Zcl.getAttributeTypeLegacy(cid, attrId).value, attrData: Math.round(value * 10), }], cfg: cfg.default, @@ -537,7 +605,7 @@ const converters = { cid: cid, cmd: 'read', cmdType: 'foundation', - zclData: [{attrId: zclId.attr(cid, attrId).value}], + zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } @@ -553,7 +621,7 @@ const converters = { cid: cid, cmd: 'read', cmdType: 'foundation', - zclData: [{attrId: zclId.attr(cid, attrId).value}], + zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } @@ -570,8 +638,8 @@ const converters = { cmd: 'write', cmdType: 'foundation', zclData: [{ - attrId: zclId.attr(cid, attrId).value, - dataType: zclId.attrType(cid, attrId).value, + attrId: Zcl.getAttributeLegacy(cid, attrId).value, + dataType: Zcl.getAttributeTypeLegacy(cid, attrId).value, attrData: (Math.round((value * 2).toFixed(1))/2).toFixed(1) * 100, }], cfg: cfg.default, @@ -581,7 +649,7 @@ const converters = { cid: cid, cmd: 'read', cmdType: 'foundation', - zclData: [{attrId: zclId.attr(cid, attrId).value}], + zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } @@ -598,8 +666,8 @@ const converters = { cmd: 'write', cmdType: 'foundation', zclData: [{ - attrId: zclId.attr(cid, attrId).value, - dataType: zclId.attrType(cid, attrId).value, + attrId: Zcl.getAttributeLegacy(cid, attrId).value, + dataType: Zcl.getAttributeTypeLegacy(cid, attrId).value, attrData: (Math.round((value * 2).toFixed(1))/2).toFixed(1) * 100, }], cfg: cfg.default, @@ -609,7 +677,35 @@ const converters = { cid: cid, cmd: 'read', cmdType: 'foundation', - zclData: [{attrId: zclId.attr(cid, attrId).value}], + zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], + cfg: cfg.default, + }]; + } + }, + }, + thermostat_occupied_cooling_setpoint: { + key: 'occupied_cooling_setpoint', + convert: (key, value, message, type, postfix, options) => { + const cid = 'hvacThermostat'; + const attrId = 'occupiedCoolingSetpoint'; + if (type === 'set') { + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: Zcl.getAttributeLegacy(cid, attrId).value, + dataType: Zcl.getAttributeTypeLegacy(cid, attrId).value, + attrData: (Math.round((value * 2).toFixed(1))/2).toFixed(1) * 100, + }], + cfg: cfg.default, + }]; + } else if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } @@ -632,8 +728,8 @@ const converters = { // Bit 1 = 1 – outdoor temperature sensed remotely // Bit 2 = 0 – occupancy sensed internally // Bit 2 = 1 – occupancy sensed remotely - attrId: zclId.attr(cid, attrId).value, - dataType: zclId.attrType(cid, attrId).value, + attrId: Zcl.getAttributeLegacy(cid, attrId).value, + dataType: Zcl.getAttributeTypeLegacy(cid, attrId).value, attrData: value, // TODO: Lookup in Zigbee documentation }], cfg: cfg.default, @@ -643,7 +739,7 @@ const converters = { cid: cid, cmd: 'read', cmdType: 'foundation', - zclData: [{attrId: zclId.attr(cid, attrId).value}], + zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } @@ -660,8 +756,8 @@ const converters = { cmd: 'write', cmdType: 'foundation', zclData: [{ - attrId: zclId.attr(cid, attrId).value, - dataType: zclId.attrType(cid, attrId).value, + attrId: Zcl.getAttributeLegacy(cid, attrId).value, + dataType: Zcl.getAttributeTypeLegacy(cid, attrId).value, attrData: utils.getKeyByValue(common.thermostatControlSequenceOfOperations, value, value), }], cfg: cfg.default, @@ -671,7 +767,7 @@ const converters = { cid: cid, cmd: 'read', cmdType: 'foundation', - zclData: [{attrId: zclId.attr(cid, attrId).value}], + zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } @@ -688,8 +784,8 @@ const converters = { cmd: 'write', cmdType: 'foundation', zclData: [{ - attrId: zclId.attr(cid, attrId).value, - dataType: zclId.attrType(cid, attrId).value, + attrId: Zcl.getAttributeLegacy(cid, attrId).value, + dataType: Zcl.getAttributeTypeLegacy(cid, attrId).value, attrData: utils.getKeyByValue(common.thermostatSystemModes, value, value), }], cfg: cfg.default, @@ -700,7 +796,7 @@ const converters = { cid: cid, cmd: 'read', cmdType: 'foundation', - zclData: [{attrId: zclId.attr(cid, attrId).value}], + zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } @@ -735,7 +831,7 @@ const converters = { cmd: 'setWeeklySchedule', cmdType: 'functional', zclData: { - dataType: zclId.attrType(cid, attrId).value, + dataType: Zcl.getAttributeTypeLegacy(cid, attrId).value, attrData: value, // TODO: Combine attributes in attrData? temperature_setpoint_hold: value.temperature_setpoint_hold, temperature_setpoint_hold_duration: value.temperature_setpoint_hold_duration, @@ -825,7 +921,7 @@ const converters = { cid: cid, cmd: 'read', cmdType: 'foundation', - zclData: [{attrId: zclId.attr(cid, attrId).value}], + zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } @@ -841,7 +937,7 @@ const converters = { cid: cid, cmd: 'read', cmdType: 'foundation', - zclData: [{attrId: zclId.attr(cid, attrId).value}], + zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } @@ -858,9 +954,29 @@ const converters = { cmd: 'write', cmdType: 'foundation', zclData: [{ - attrId: zclId.attr(cid, attrId).value, - dataType: zclId.attrType(cid, attrId).value, - attrData: value, + attrId: Zcl.getAttributeLegacy(cid, attrId).value, + dataType: Zcl.getAttributeTypeLegacy(cid, attrId).value, + attrData: utils.getKeyByValue(common.temperatureDisplayMode, value, value), + }], + cfg: cfg.default, + }]; + } + }, + }, + thermostat_keypad_lockout: { + key: 'keypad_lockout', + convert: (key, value, message, type, postfix, options) => { + const cid = 'hvacUserInterfaceCfg'; + const attrId = 'keypadLockout'; + if (type === 'set') { + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: Zcl.getAttributeLegacy(cid, attrId).value, + dataType: Zcl.getAttributeTypeLegacy(cid, attrId).value, + attrData: utils.getKeyByValue(common.keypadLockoutMode, value, value), }], cfg: cfg.default, }]; @@ -881,7 +997,7 @@ const converters = { cid: cid, cmd: 'write', cmdType: 'foundation', - zclData: [{attrId: zclId.attr(cid, attrId).value, attrData: attrData, dataType: 48}], + zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value, attrData: attrData, dataType: 48}], cfg: cfg.default, newState: {fan_mode: value, fan_state: value === 'off' ? 'OFF' : 'ON'}, }]; @@ -890,7 +1006,7 @@ const converters = { cid: cid, cmd: 'read', cmdType: 'foundation', - zclData: [{attrId: zclId.attr(cid, attrId).value}], + zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } @@ -1062,7 +1178,7 @@ const converters = { cid: cid, cmd: 'read', cmdType: 'foundation', - zclData: [{attrId: zclId.attr(cid, attrId).value}], + zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } @@ -1329,7 +1445,7 @@ const converters = { cid: cid, cmd: 'read', cmdType: 'foundation', - zclData: [{attrId: zclId.attr(cid, attrId).value}], + zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } @@ -1361,7 +1477,7 @@ const converters = { cid: cid, cmd: 'read', cmdType: 'foundation', - zclData: [{attrId: zclId.attr(cid, attrId).value}], + zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } @@ -1380,7 +1496,7 @@ const converters = { gledopto_light_color_colortemp: { key: ['color', 'color_temp', 'color_temp_percent'], convert: (key, value, message, type, postfix, options) => { - if (message.hasOwnProperty('transition')) { + if (message && message.hasOwnProperty('transition')) { message.transition = message.transition * 3.3; } @@ -1390,7 +1506,7 @@ const converters = { gledopto_light_colortemp: { key: ['color_temp', 'color_temp_percent'], convert: (key, value, message, type, postfix, options) => { - if (message.hasOwnProperty('transition')) { + if (message && message.hasOwnProperty('transition')) { message.transition = message.transition * 3.3; } @@ -1532,6 +1648,195 @@ const converters = { }, }, + // Sinope + sinope_thermostat_backlight_autodim_param: { + key: 'backlight_auto_dim', + convert: (key, value, message, type, postfix, options) => { + const cid = 'hvacThermostat'; + const attrId = 0x0402; + if (type === 'set') { + const sinopeBacklightAutoDimParam = { + 0: 'on demand', + 1: 'sensing', + }; + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: attrId, + dataType: 0x30, + attrData: utils.getKeyByValue(sinopeBacklightAutoDimParam, value, value), + }], + cfg: cfg.default, + }]; + } + }, + }, + sinope_thermostat_enable_outdoor_temperature: { + key: 'enable_outdoor_temperature', + convert: (key, value, message, type, postfix, options) => { + const cid = 0xFF01; + const attrId = 0x0011; + if (type === 'set' && value.toLowerCase()=='on') { + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: attrId, + dataType: 0x21, + // set outdoor temperature timer to 3 hours + attrData: 10800, + }], + cfg: cfg.sinope, + }]; + } else if (type === 'set' && value.toLowerCase()=='off') { + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: attrId, + dataType: 0x21, + // set timer to 30sec in order to disable outdoor temperature + attrData: 30, + }], + cfg: cfg.sinope, + }]; + } + }, + }, + sinope_thermostat_outdoor_temperature: { + key: 'thermostat_outdoor_temperature', + convert: (key, value, message, type, postfix, options) => { + const cid = 0xFF01; + const attrId = 0x0010; + if (type === 'set' && value > -100 && value < 100) { + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: attrId, + dataType: 0x29, + attrData: value * 100, + }], + cfg: cfg.sinope, + }]; + } + }, + }, + sinope_thermostat_time: { + key: 'thermostat_time', + convert: (key, value, message, type, postfix, options) => { + const cid = 0xFF01; + const attrId = 0x0020; + if (type === 'set' && value === '' ) { + const thermostatDate = new Date(); + const thermostatTimeSec = thermostatDate.getTime() / 1000; + const thermostatTimezoneOffsetSec = thermostatDate.getTimezoneOffset() * 60; + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: attrId, + dataType: 0x23, + // Current time in second since 2000-01-01T00:00 in the current time zone + attrData: Math.round(thermostatTimeSec - thermostatTimezoneOffsetSec - 946684800), + }], + cfg: cfg.sinope, + }]; + } else if (type === 'set' && value !== '' ) { + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: attrId, + dataType: 0x23, + attrData: value, + }], + cfg: cfg.sinope, + }]; + } + }, + }, + DTB190502A1_LED: { + key: ['LED'], + convert: (key, value, message, type, postfix, options) => { + if (type === 'set') { + if (value === 'default') { + value = 1; + } + const lookup = { + 'OFF': '0', + 'ON': '1', + }; + value = lookup[value]; + // Check for valid data + if ( ((value >= 0) && value < 2) == false ) value = 0; + return [{ + cid: 'genBasic', + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: 0x4010, + dataType: 0x21, + attrData: value, + }], + cfg: cfg.default, + }]; + } + }, + }, + ptvo_switch_trigger: { + key: ['trigger', 'interval'], + convert: (key, value, message, type, postfix, options) => { + console.log('ptvo_switch_trigger:', key, value, message, type, postfix); + if (type === 'set') { + value = parseInt(value); + if (!value) { + return; + } + + const cid = 'genOnOff'; + + if (key === 'trigger') { + return [{ + cid: cid, + cmd: 'onWithTimedOff', + cmdType: 'functional', + zclData: { + ctrlbits: 0, + ontime: value, + offwaittime: 0, + }, + cfg: cfg.default, + }]; + } else if (key === 'interval') { + const attrId = 'onOff'; + return [{ + cid: cid, + cmd: 'configReport', + cmdType: 'foundation', + zclData: [{ + direction: 0, + attrId: Zcl.getAttributeLegacy(cid, attrId).value, + dataType: Zcl.getAttributeTypeLegacy(cid, attrId).value, + attrData: value, + minRepIntval: value, + maxRepIntval: value, + }], + cfg: cfg.default, + }]; + } + } + return; + }, + }, + /** * Ignore converters */ diff --git a/zigbee-shepherd-converters/devices.js b/zigbee-shepherd-converters/devices.js index b6e3519..03a4b13 100644 --- a/zigbee-shepherd-converters/devices.js +++ b/zigbee-shepherd-converters/devices.js @@ -246,10 +246,11 @@ const devices = [ vendor: 'Xiaomi', // eslint-disable-next-line description: 'Aqara double key wired wall switch without neutral wire. Doesn\'t work as a router and doesn\'t support power meter', - supports: 'release/hold, on/off', + supports: 'release/hold, on/off, temperature', fromZigbee: [ fz.QBKG03LM_QBKG12LM_LLKZMK11LM_state, fz.QBKG03LM_buttons, fz.QBKG03LM_QBKG12LM_operation_mode, fz.ignore_basic_report, + fz.generic_device_temperature, ], toZigbee: [tz.on_off, tz.xiaomi_switch_operation_mode], ep: (device) => { @@ -279,7 +280,7 @@ const devices = [ description: 'MiJia temperature & humidity sensor', supports: 'temperature and humidity', fromZigbee: [ - fz.xiaomi_battery_3v, fz.WSDCGQ01LM_WSDCGQ11LM_interval, fz.xiaomi_temperature, fz.xiaomi_humidity, + fz.xiaomi_battery_3v, fz.WSDCGQ01LM_WSDCGQ11LM_interval, fz.xiaomi_temperature, fz.generic_humidity, fz.ignore_basic_change, ], toZigbee: [], @@ -291,7 +292,7 @@ const devices = [ description: 'Aqara temperature, humidity and pressure sensor', supports: 'temperature, humidity and pressure', fromZigbee: [ - fz.xiaomi_battery_3v, fz.xiaomi_temperature, fz.xiaomi_humidity, fz.generic_pressure, + fz.xiaomi_battery_3v, fz.xiaomi_temperature, fz.generic_humidity, fz.generic_pressure, fz.ignore_basic_change, fz.ignore_temperature_change, fz.ignore_humidity_change, fz.ignore_pressure_change, fz.WSDCGQ01LM_WSDCGQ11LM_interval, ], @@ -348,7 +349,10 @@ const devices = [ vendor: 'Xiaomi', description: 'Aqara water leak sensor', supports: 'water leak true/false', - fromZigbee: [fz.xiaomi_battery_3v, fz.SJCGQ11LM_water_leak_iaszone, fz.ignore_basic_change], + fromZigbee: [ + fz.xiaomi_battery_3v, fz.SJCGQ11LM_water_leak_iaszone, + fz.SJCGQ11LM_water_leak_interval, fz.ignore_basic_change, + ], toZigbee: [], }, { @@ -377,6 +381,20 @@ const devices = [ ], toZigbee: [tz.on_off], }, + { + zigbeeModel: ['lumi.plug.mitw01'], + model: 'ZNCZ03LM', + description: 'Mi power plug ZigBee TW', + supports: 'on/off, power measurement', + vendor: 'Xiaomi', + fromZigbee: [ + fz.state, fz.xiaomi_power, fz.xiaomi_plug_state, fz.ignore_onoff_change, + fz.ignore_basic_change, fz.ignore_analog_change, fz.ignore_occupancy_report, + fz.ignore_illuminance_report, fz.ignore_temperature_change, + fz.ignore_humidity_change, fz.ignore_pressure_change, + ], + toZigbee: [tz.on_off], + }, { zigbeeModel: ['lumi.ctrl_86plug', 'lumi.ctrl_86plug.aq1'], model: 'QBCZ11LM', @@ -468,7 +486,19 @@ const devices = [ supports: 'report: open, close, operation', vendor: 'Xiaomi', fromZigbee: [ - fz.ZNMS12LM_closuresDoorLock_report, fz.ignore_basic_report, + fz.ZNMS12LM_ZNMS13LM_closuresDoorLock_report, fz.ignore_basic_report, + fz.ignore_doorlock_change, fz.ignore_basic_change, + ], + toZigbee: [], + }, + { + zigbeeModel: ['lumi.lock.acn03'], + model: 'ZNMS13LM', + description: 'Aqara S2 Lock Pro', + supports: 'report: open, close, operation', + vendor: 'Xiaomi', + fromZigbee: [ + fz.ZNMS12LM_ZNMS13LM_closuresDoorLock_report, fz.ignore_basic_report, fz.ignore_doorlock_change, fz.ignore_basic_change, ], toZigbee: [], @@ -534,7 +564,10 @@ const devices = [ extend: generic.light_onoff_brightness, }, { - zigbeeModel: ['TRADFRI bulb E27 CWS opal 600lm', 'TRADFRI bulb E26 CWS opal 600lm'], + zigbeeModel: [ + 'TRADFRI bulb E27 CWS opal 600lm', + 'TRADFRI bulb E26 CWS opal 600lm', + 'TRADFRI bulb E14 CWS opal 600lm'], model: 'LED1624G9', vendor: 'IKEA', description: 'TRADFRI LED bulb E27/E26 600 lumen, dimmable, color, opal white', @@ -557,6 +590,13 @@ const devices = [ description: 'TRADFRI LED bulb E27 1000 lumen, dimmable, white spectrum, opal white', extend: generic.light_onoff_brightness_colortemp, }, + { + zigbeeModel: ['TRADFRI bulb E27 WS clear 806lm'], + model: 'LED1736G9', + vendor: 'IKEA', + description: 'TRADFRI LED bulb E27 806 lumen, dimmable, white spectrum, clear', + extend: generic.light_onoff_brightness_colortemp, + }, { zigbeeModel: ['TRADFRI wireless dimmer'], model: 'ICTC-G-1', @@ -645,7 +685,7 @@ const devices = [ }, { zigbeeModel: ['TRADFRI remote control'], - model: 'E1524', + model: 'E1524/E1810', description: 'TRADFRI remote control', supports: 'toggle, arrow left/right click/hold/release, brightness up/down click/hold/release', @@ -722,13 +762,29 @@ const devices = [ zigbeeModel: ['TRADFRI signal repeater'], model: 'E1746', description: 'TRADFRI signal repeater', - supports: '', + supports: 'linkquality', vendor: 'IKEA', - fromZigbee: [fz.ignore_basic_change, fz.ignore_diagnostic_change], + fromZigbee: [fz.ignore_basic_change, fz.ignore_diagnostic_change, fz.E1746_linkquality], toZigbee: [], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + + const actions = [ + (cb) => device.report('genBasic', 'modelId', 3600, 14400, 0, cb), + ]; + + execute(device, actions, callback); + }, }, // Philips + { + zigbeeModel: ['LTC012'], + model: '3306431P7', + vendor: 'Philips', + description: 'Hue Struana', + extend: hue.light_onoff_brightness_colortemp, + }, { zigbeeModel: ['LLC012', 'LLC011'], model: '7299760PH', @@ -792,6 +848,20 @@ const devices = [ description: 'Hue white and color ambiance LightStrip plus', extend: hue.light_onoff_brightness_colortemp_colorxy, }, + { + zigbeeModel: ['LST003'], + model: '9290018187B', + vendor: 'Philips', + description: 'Hue white and color ambiance LightStrip Outdoor', + extend: hue.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['LCA002'], + model: '9290022166', + vendor: 'Philips', + description: 'Hue white and color ambiance E26', + extend: hue.light_onoff_brightness_colortemp_colorxy, + }, { zigbeeModel: ['LCT001', 'LCT007', 'LCT010', 'LCT012', 'LCT014', 'LCT015', 'LCT016'], model: '9290012573A', @@ -855,6 +925,13 @@ const devices = [ description: 'Hue white ambiance E26/E27', extend: hue.light_onoff_brightness_colortemp, }, + { + zigbeeModel: ['LTW017'], + model: '915005587401', + vendor: 'Philips', + description: 'Hue white ambiance Adore light', + extend: hue.light_onoff_brightness_colortemp, + }, { zigbeeModel: ['LCW001'], model: '4090130P7', @@ -979,7 +1056,7 @@ const devices = [ fromZigbee: [ fz.generic_battery_remaining, fz.generic_occupancy, fz.generic_temperature, fz.ignore_occupancy_change, fz.generic_illuminance, fz.ignore_illuminance_change, - fz.ignore_temperature_change, + fz.ignore_temperature_change, fz.ignore_basic_change, fz.ignore_basic_report, ], toZigbee: [tz.occupancy_timeout, tz.hue_motion_sensitivity], ep: (device) => { @@ -1063,6 +1140,20 @@ const devices = [ description: 'Hue Fuzo outdoor wall light', extend: hue.light_onoff_brightness, }, + { + zigbeeModel: ['1743630P7'], + model: '17436/30/P7', + vendor: 'Philips', + description: 'Hue Welcome white flood light', + extend: hue.light_onoff_brightness, + }, + { + zigbeeModel: ['LCS001'], + model: '1741830P7', + vendor: 'Philips', + description: 'Hue Lily outdoor spot light', + extend: hue.light_onoff_brightness_colortemp_colorxy, + }, // Belkin { @@ -1121,7 +1212,21 @@ const devices = [ description: '[CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/)', supports: 'state, description, type, rssi', fromZigbee: [fz.CC2530ROUTER_state, fz.CC2530ROUTER_meta, fz.ignore_onoff_change], - toZigbee: [], + toZigbee: [tz.ptvo_switch_trigger], + }, + { + zigbeeModel: ['ptvo.switch'], + model: 'ptvo.switch', + vendor: 'Custom devices (DiY)', + description: '[Multi-channel relay switch](https://ptvo.info/zigbee-switch-configurable-firmware-router-199/)', + supports: 'hold, single, double and triple click, on/off', + fromZigbee: [fz.ptvo_switch_state, fz.ptvo_switch_buttons, + fz.ignore_basic_change, fz.ignore_onoff_change, fz.ignore_multistate_change, + ], + toZigbee: [tz.on_off, tz.ptvo_switch_trigger], + ep: (device) => { + return {'bottom_left': 1, 'bottom_right': 2, 'top_left': 3, 'top_right': 4, 'center': 5}; + }, }, { zigbeeModel: ['DNCKAT_S001'], @@ -1178,8 +1283,8 @@ const devices = [ toZigbee: [tz.on_off, tz.light_color, tz.ZigUP_lock], }, { - zigbeeModel: ['DIYRUZ_R4_5'], - model: 'DIYRUZ_R4_5', + zigbeeModel: ['DIYRuZ_R4_5'], + model: 'DIYRuZ_R4_5', vendor: 'Custom devices (DiY)', description: '[DiY 4 Relays + 4 switches + 1 buzzer](http://modkam.ru/?p=1054)', supports: 'on/off', @@ -1189,6 +1294,32 @@ const devices = [ return {'bottom_left': 1, 'bottom_right': 2, 'top_left': 3, 'top_right': 4, 'center': 5}; }, }, + { + zigbeeModel: ['DIYRuZ_KEYPAD20'], + model: 'DIYRuZ_KEYPAD20', + vendor: 'Custom devices (DiY)', + description: '[DiY 20 button keypad](http://modkam.ru/?p=1114)', + supports: 'click', + fromZigbee: [fz.keypad20states, fz.keypad20_battery], + toZigbee: [], + ep: (device) => { + return { + 'btn_1': 1, 'btn_2': 2, 'btn_3': 3, 'btn_4': 4, 'btn_5': 5, + 'btn_6': 6, 'btn_7': 7, 'btn_8': 8, 'btn_9': 9, 'btn_10': 10, + 'btn_11': 11, 'btn_12': 12, 'btn_13': 13, 'btn_14': 14, 'btn_15': 15, + 'btn_16': 16, 'btn_17': 17, 'btn_18': 18, 'btn_19': 19, 'btn_20': 20, + }; + }, + }, + { + zigbeeModel: ['DTB190502A1'], + model: 'DTB190502A1', + vendor: 'Custom devices (DiY)', + description: '[CC2530 based IO Board https://databyte.ch/?portfolio=zigbee-erstes-board-dtb190502a)', + supports: 'switch, buttons', + fromZigbee: [fz.DTB190502A1_parse, fz.ignore_onoff_change], + toZigbee: [tz.DTB190502A1_LED], + }, // eCozy { @@ -1256,6 +1387,13 @@ const devices = [ description: 'SMART+ CLASSIC A 60 TW', extend: generic.light_onoff_brightness_colortemp, }, + { + zigbeeModel: ['PAR16 DIM Z3'], + model: 'AC08560', + vendor: 'OSRAM', + description: 'SMART+ LED PAR16 GU10', + extend: generic.light_onoff_brightness, + }, { zigbeeModel: ['CLA60 RGBW Z3'], model: 'AC03647', @@ -1369,6 +1507,13 @@ const devices = [ description: 'Smart+ Spot GU10 Multicolor', extend: generic.light_onoff_brightness_colortemp_colorxy, }, + { + zigbeeModel: ['PAR16 RGBW Z3'], + model: 'AC08559', + vendor: 'OSRAM', + description: 'SMART+ Spot GU10 Multicolor', + extend: generic.light_onoff_brightness_colortemp, + }, { zigbeeModel: ['B40 DIM Z3'], model: 'AC08562', @@ -1464,7 +1609,7 @@ const devices = [ extend: generic.light_onoff_brightness, }, { - zigbeeModel: ['SLP2b'], + zigbeeModel: ['SLP2b', 'SLP2c'], model: '1613V', vendor: 'Hive', description: 'Active plug', @@ -1529,6 +1674,13 @@ const devices = [ description: 'E27 bulb', extend: generic.light_onoff_brightness, }, + { + zigbeeModel: ['RF 265'], + model: 'RF 265', + vendor: 'Innr', + description: 'E27 bulb filament clear', + extend: generic.light_onoff_brightness, + }, { zigbeeModel: ['RB 278 T'], model: 'RB 278 T', @@ -1627,6 +1779,13 @@ const devices = [ description: 'E14 candle with white spectrum', extend: generic.light_onoff_brightness_colortemp, }, + { + zigbeeModel: ['RF 263'], + model: 'RF 263', + vendor: 'Innr', + description: 'E27 filament bulb dimmable', + extend: generic.light_onoff_brightness, + }, { zigbeeModel: ['BY 165', 'BY 265'], model: 'BY 165', @@ -1733,6 +1892,13 @@ const devices = [ description: 'LIGHTIFY LED adjustable white RT 5/6', extend: generic.light_onoff_brightness_colortemp, }, + { + zigbeeModel: ['RT RGBW'], + model: '73741', + vendor: 'Sylvania', + description: 'LIGHTIFY LED adjustable color RT 5/6', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, { zigbeeModel: ['LIGHTIFY BR Tunable White'], model: '73740', @@ -1754,7 +1920,7 @@ const devices = [ ]), }, { - zigbeeModel: ['LIGHTIFY A19 RGBW'], + zigbeeModel: ['LIGHTIFY A19 RGBW', 'A19 RGBW'], model: '73693', vendor: 'Sylvania', description: 'LIGHTIFY LED RGBW A19', @@ -1832,6 +1998,13 @@ const devices = [ }, // GE + { + zigbeeModel: ['SoftWhite'], + model: 'PSB19-SW27', + vendor: 'GE', + description: 'Link smart LED light bulb, A19 soft white (2700K)', + extend: generic.light_onoff_brightness, + }, { zigbeeModel: ['ZLL Light'], model: '22670', @@ -1966,6 +2139,18 @@ const devices = [ ]), toZigbee: generic.light_onoff_brightness_colortemp.toZigbee, }, + { + zigbeeModel: ['Z01-A60EAE27'], + model: 'Z01-A60EAE27', + vendor: 'Sengled', + description: 'Element Plus (A60)', + supports: generic.light_onoff_brightness_colortemp.supports, + fromZigbee: generic.light_onoff_brightness_colortemp.fromZigbee.concat([ + fz.ignore_metering_change, + fz.ignore_diagnostic_change, + ]), + toZigbee: generic.light_onoff_brightness_colortemp.toZigbee, + }, { zigbeeModel: ['E11-N1EA'], model: 'E11-N1EA', @@ -2038,7 +2223,7 @@ const devices = [ // JIAWEN { - zigbeeModel: ['FB56-ZCW08KU1.1'], + zigbeeModel: ['FB56-ZCW08KU1.1', 'FB56-ZCW08KU1.0'], model: 'K2RGBW01', vendor: 'JIAWEN', description: 'Wireless Bulb E27 9W RGBW', @@ -2056,11 +2241,25 @@ const devices = [ toZigbee: [tz.on_off], configure: (ieeeAddr, shepherd, coordinator, callback) => { const device = shepherd.find(ieeeAddr, 1); + const onOff = {direction: 0, attrId: 0, dataType: 16, minRepIntval: 0, maxRepIntval: 1000, repChange: 0}; + + const rmsVoltage = 1285; + const rmsCurrent = 1288; + const activePower = 1291; + const powerFactor = 1296; + + const electricalCfg = [ + {direction: 0, attrId: rmsVoltage, dataType: 33, minRepIntval: 10, maxRepIntval: 1000, repChange: 1}, + {direction: 0, attrId: rmsCurrent, dataType: 33, minRepIntval: 10, maxRepIntval: 1000, repChange: 1}, + {direction: 0, attrId: activePower, dataType: 41, minRepIntval: 10, maxRepIntval: 1000, repChange: 1}, + {direction: 0, attrId: powerFactor, dataType: 40, minRepIntval: 10, maxRepIntval: 1000, repChange: 1}, + ]; + const actions = [ - (cb) => device.report('haElectricalMeasurement', 'rmsVoltage', 10, 1000, 1, cb), - (cb) => device.report('haElectricalMeasurement', 'rmsCurrent', 10, 1000, 1, cb), - (cb) => device.report('haElectricalMeasurement', 'activePower', 10, 1000, 1, cb), - (cb) => device.report('haElectricalMeasurement', 'powerFactor', 10, 1000, 1, cb), + (cb) => device.bind('genOnOff', coordinator, cb), + (cb) => device.foundation('genOnOff', 'configReport', [onOff], foundationCfg, cb), + (cb) => device.bind('haElectricalMeasurement', coordinator, cb), + (cb) => device.foundation('haElectricalMeasurement', 'configReport', electricalCfg, foundationCfg, cb), ]; execute(device, actions, callback); @@ -2202,15 +2401,6 @@ const devices = [ }); }, }, - { - zigbeeModel: ['FB56+ZSW05HG1.2'], - model: 'HGZB-01A/02A', - vendor: 'Nue / 3A', - description: 'Smart 1 gang wall or in-wall switch', - supports: 'on/off', - fromZigbee: [fz.state, fz.ignore_onoff_change], - toZigbee: [tz.on_off], - }, { zigbeeModel: ['FB56+ZSW1GKJ2.5'], model: 'HGZB-41', @@ -2232,13 +2422,6 @@ const devices = [ return {'left': 12, 'right': 11}; }, }, - { - zigbeeModel: ['FNB56-ZSW23HG1.1'], - model: 'HGZB-01A', - vendor: 'Nue / 3A', - description: 'Smart light controller', - extend: generic.light_onoff_brightness, - }, { zigbeeModel: ['FNB56-ZCW25FB1.9'], model: 'XY12S-15', @@ -2247,7 +2430,16 @@ const devices = [ extend: generic.light_onoff_brightness_colortemp_colorxy, }, { - zigbeeModel: ['FNB56-ZSC01LX1.2'], + zigbeeModel: ['FNB56-ZSW23HG1.1', 'LXN56-LC27LX1.1'], + model: 'HGZB-01A', + vendor: 'Nue / 3A', + description: 'Smart in-wall switch', + supports: 'on/off', + fromZigbee: [fz.state, fz.ignore_onoff_change], + toZigbee: [tz.on_off], + }, + { + zigbeeModel: ['FNB56-ZSC01LX1.2', 'FB56+ZSW05HG1.2'], model: 'HGZB-02A', vendor: 'Nue / 3A', description: 'Smart light controller', @@ -2263,7 +2455,7 @@ const devices = [ toZigbee: [tz.on_off], }, { - zigbeeModel: ['FNB56-ZCW25FB1.6'], + zigbeeModel: ['FNB56-ZCW25FB1.6', 'FNB56-ZCW25FB2.1'], model: 'HGZB-06A', vendor: 'Nue / 3A', description: 'Smart 7W E27 light bulb', @@ -2272,7 +2464,7 @@ const devices = [ // Smart Home Pty { - zigbeeModel: ['FB56-ZCW11HG1.2'], + zigbeeModel: ['FB56-ZCW11HG1.2', 'FB56-ZCW11HG1.4'], model: 'HGZB-07A', vendor: 'Smart Home Pty', description: 'RGBW Downlight', @@ -2781,6 +2973,28 @@ const devices = [ execute(device, actions, callback); }, }, + { + zigbeeModel: ['IM6001-WLP01'], + model: 'IM6001-WLP01', + vendor: 'SmartThings', + description: 'Water leak sensor', + supports: 'water leak', + fromZigbee: [ + fz.generic_temperature, fz.ignore_temperature_change, fz.ignore_power_change, + fz.st_leak, fz.st_leak_change, fz.generic_batteryvoltage_3000_2500, fz.ignore_diagnostic_change, + ], + toZigbee: [], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.write('ssIasZone', 'iasCieAddr', coordinator.device.getIeeeAddr(), cb), + (cb) => device.report('ssIasZone', 'zoneStatus', 0, 30, null, cb), + (cb) => device.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 1, zoneid: 23}, cb), + ]; + + execute(device, actions, callback); + }, + }, { zigbeeModel: ['3315-S'], model: '3315-S', @@ -2809,6 +3023,34 @@ const devices = [ execute(device, actions, callback); }, }, + { + zigbeeModel: ['water'], + model: 'F-WTR-UK-V2', + vendor: 'SmartThings', + description: 'Water leak sensor (2018 model)', + supports: 'water leak and temperature', + fromZigbee: [ + fz.generic_temperature, fz.ignore_temperature_change, fz.ignore_power_change, + fz.st_leak, fz.st_leak_change, fz.generic_batteryvoltage_3000_2500, + ], + toZigbee: [], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.bind('msTemperatureMeasurement', coordinator, cb), + (cb) => device.report('msTemperatureMeasurement', 'measuredValue', 300, 600, 1, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.report('genPowerCfg', 'batteryVoltage', 0, 1000, 0, cb), + (cb) => device.write('ssIasZone', 'iasCieAddr', coordinator.device.getIeeeAddr(), cb), + (cb) => device.report('ssIasZone', 'zoneStatus', 0, 1000, null, cb), + (cb) => device.functional('ssIasZone', 'enrollRsp', { + enrollrspcode: 1, + zoneid: 255, + }, cb), + ]; + execute(device, actions, callback); + }, + }, { zigbeeModel: ['3315-G'], model: '3315-G', @@ -2920,13 +3162,17 @@ const devices = [ vendor: 'Trust', description: 'Wireless contact sensor', supports: 'contact', - fromZigbee: [fz.ias_contact_dev_change, fz.ias_contact_status_change], + fromZigbee: [fz.ias_contact_dev_change, fz.ias_contact_status_change, fz.generic_battery_remaining], toZigbee: [], configure: (ieeeAddr, shepherd, coordinator, callback) => { const device = shepherd.find(ieeeAddr, 1); const actions = [ (cb) => device.write('ssIasZone', 'iasCieAddr', coordinator.device.getIeeeAddr(), cb), (cb) => device.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 23}, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.report( + 'genPowerCfg', 'batteryPercentageRemaining', repInterval.MINUTE, repInterval.MAX, 1, cb + ), ]; execute(device, actions, callback); @@ -3040,11 +3286,10 @@ const devices = [ (cb) => device.bind('genBasic', coordinator, cb), (cb) => device.bind('genPowerCfg', coordinator, cb), (cb) => device.bind('genIdentify', coordinator, cb), - (cb) => device.bind('genTime', coordinator, cb), (cb) => device.bind('genPollCtrl', coordinator, cb), (cb) => device.bind('hvacThermostat', coordinator, cb), (cb) => device.bind('hvacUserInterfaceCfg', coordinator, cb), - (cb) => device.report('hvacThermostat', 'localTemp', 300, 3600, 0, cb), + (cb) => device.report('hvacThermostat', 'localTemp', 1200, 3600, 0, cb), (cb) => device.report('hvacThermostat', 'localTemperatureCalibration', 1, 0, 0, cb), (cb) => device.report('hvacThermostat', 'occupiedHeatingSetpoint', 1, 0, 1, cb), (cb) => device.report('hvacThermostat', 'runningState', 1, 0, 0, cb), @@ -3362,7 +3607,31 @@ const devices = [ vendor: 'HEIMAN', description: 'Smoke detector', supports: 'smoke', - fromZigbee: [fz.heiman_smoke], + fromZigbee: [fz.heiman_smoke, fz.battery_200, fz.ignore_power_change], + toZigbee: [], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const cfg = { + direction: 0, attrId: 33, dataType: 32, minRepIntval: 10, maxRepIntval: repInterval.MAX, repChange: 0, + }; + + const actions = [ + (cb) => device.write('ssIasZone', 'iasCieAddr', coordinator.device.getIeeeAddr(), cb), + (cb) => device.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 23}, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.foundation('genPowerCfg', 'configReport', [cfg], foundationCfg, cb), + ]; + + execute(device, actions, callback, 1000); + }, + }, + { + zigbeeModel: ['GASSensor-N'], + model: 'HS3CG', + vendor: 'HEIMAN', + description: 'Combustible gas sensor', + supports: 'gas', + fromZigbee: [fz.heiman_gas, fz.ignore_iaszone_change], toZigbee: [], configure: (ieeeAddr, shepherd, coordinator, callback) => { const device = shepherd.find(ieeeAddr, 1); @@ -3375,8 +3644,8 @@ const devices = [ }, }, { - zigbeeModel: ['GASSensor-N'], - model: 'HS3CG', + zigbeeModel: ['GASSensor-EN'], + model: 'HS1CG-M', vendor: 'HEIMAN', description: 'Combustible gas sensor', supports: 'gas', @@ -3982,7 +4251,7 @@ const devices = [ }, }, { - zigbeeModel: ['SV02-410-MP-1.3'], + zigbeeModel: ['SV02-410-MP-1.3', 'SV02-610-MP-1.3'], model: 'SV02', vendor: 'Keen Home', description: 'Smart vent', @@ -4071,6 +4340,9 @@ const devices = [ execute(device, actions, callback); }, + options: { + disFeedbackRsp: true, + }, }, // LivingWise @@ -4086,6 +4358,15 @@ const devices = [ fz.ignore_genIdentify, ], }, + { + zigbeeModel: ['545df2981b704114945f6df1c780515a'], + model: 'LVS-ZB15S', + vendor: 'LivingWise', + description: 'ZigBee smart in-wall switch', + supports: 'on/off', + toZigbee: [tz.on_off], + fromZigbee: [fz.state, fz.ignore_onoff_change, fz.ignore_basic_report, fz.ignore_basic_change], + }, { zigbeeModel: ['e70f96b3773a4c9283c6862dbafb6a99'], model: 'LVS-SM10ZW', @@ -4104,6 +4385,24 @@ const devices = [ execute(device, actions, callback); }, }, + { + zigbeeModel: ['895a2d80097f4ae2b2d40500d5e03dcc'], + model: 'LVS-SN10ZW_SN11', + vendor: 'LivingWise', + description: 'Occupancy sensor', + supports: 'occupancy', + fromZigbee: [fz.battery_200, fz.generic_ias_zone_occupancy_status_change_no_off_msg], + toZigbee: [], + }, + { + zigbeeModel: ['55e0fa5cdb144ba3a91aefb87c068cff'], + model: 'LVS-ZB15R', + vendor: 'LivingWise', + description: 'Zigbee smart outlet', + supports: 'on/off', + fromZigbee: [fz.state, fz.ignore_onoff_change, fz.ignore_basic_report, fz.ignore_basic_change], + toZigbee: [tz.on_off], + }, // Stelpro { @@ -4163,7 +4462,7 @@ const devices = [ description: 'Ceiling motion sensor', supports: 'motion, humidity and temperature', fromZigbee: [ - fz.generic_occupancy, fz.xiaomi_humidity, fz.generic_temperature, fz.ignore_basic_report, + fz.generic_occupancy, fz.generic_humidity, fz.generic_temperature, fz.ignore_basic_report, fz.ignore_genIdentify, fz.ignore_basic_change, fz.ignore_poll_ctrl, fz.generic_temperature_change, fz.generic_battery_change, fz.ignore_humidity_change, fz.ignore_iaszone_change, @@ -4188,7 +4487,7 @@ const devices = [ description: 'Wall motion sensor', supports: 'motion, humidity and temperature', fromZigbee: [ - fz.generic_occupancy, fz.xiaomi_humidity, fz.generic_temperature, fz.ignore_basic_report, + fz.generic_occupancy, fz.generic_humidity, fz.generic_temperature, fz.ignore_basic_report, fz.ignore_genIdentify, fz.ignore_basic_change, fz.ignore_poll_ctrl, fz.generic_temperature_change, fz.generic_battery_change, fz.ignore_humidity_change, fz.ignore_iaszone_change, @@ -4213,7 +4512,7 @@ const devices = [ description: 'Curtain motion sensor', supports: 'motion, humidity and temperature', fromZigbee: [ - fz.generic_occupancy, fz.xiaomi_humidity, fz.generic_temperature, fz.ignore_basic_report, + fz.generic_occupancy, fz.generic_humidity, fz.generic_temperature, fz.ignore_basic_report, fz.ignore_genIdentify, fz.ignore_basic_change, fz.ignore_poll_ctrl, fz.generic_temperature_change, fz.generic_battery_change, fz.ignore_humidity_change, fz.ignore_iaszone_change, @@ -4616,6 +4915,13 @@ const devices = [ description: 'LED E27 tunable white', extend: generic.light_onoff_brightness, }, + { + zigbeeModel: ['LED_E27_OWDT'], + model: 'ZA806SQ1TCF', + vendor: 'Leedarson', + description: 'LED E27 tunable white', + extend: generic.light_onoff_brightness_colortemp, + }, // GMY { @@ -4691,6 +4997,101 @@ const devices = [ }, }, + // Konke + { + zigbeeModel: ['3AFE170100510001'], + model: '2AJZ4KPKEY', + vendor: 'Konke', + description: 'Multi-function button', + supports: 'single, double and long click', + fromZigbee: [ + fz.konke_click, fz.ignore_onoff_change, + fz.generic_change_batteryvoltage_3000_2500, fz.generic_batteryvoltage_3000_2500, + ], + toZigbee: [], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.report('genPowerCfg', 'batteryVoltage', repInterval.HOUR, repInterval.MAX, cb), + ]; + execute(device, actions, callback); + }, + }, + /* + { + zigbeeModel: ['3AFE14010402000D'], + model: '2AJZ4KPBS', + vendor: 'Konke', + description: 'Motion sensor', + supports: '', + fromZigbee: [ + fz.bosch_ias_zone_motion_status_change, + fz.generic_change_batteryvoltage_3000_2500, fz.generic_batteryvoltage_3000_2500, + ], + // TODO: Not fully supported - need to be configured correctly. Look at + // https://github.com/Koenkk/zigbee2mqtt/issues/1689 + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.write('ssIasZone', 'iasCieAddr', coordinator.device.getIeeeAddr(), cb), + (cb) => device.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 23}, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.report('genPowerCfg', 'batteryVoltage', repInterval.HOUR, repInterval.MAX, cb), + ]; + execute(device, actions, callback); + } + }, + */ + { + zigbeeModel: ['3AFE140103020000'], + model: '2AJZ4KPFT', + vendor: 'Konke', + description: 'Temperature and humidity sensor', + supports: 'temperature and humidity', + fromZigbee: [ + fz.generic_temperature, fz.ignore_temperature_change, + fz.generic_humidity, fz.ignore_humidity_change, + fz.generic_batteryvoltage_3000_2500, + ], + toZigbee: [], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.bind('msTemperatureMeasurement', coordinator, cb), + (cb) => device.report('msTemperatureMeasurement', 'measuredValue', 150, 300, 0.5, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.report('genPowerCfg', 'batteryVoltage', repInterval.HOUR, repInterval.MAX, cb), + ]; + execute(device, actions, callback); + }, + }, + /* + { + zigbeeModel: ['3AFE130104020015'], + model: '2AJZ4KPDR', + vendor: 'Konke', + description: 'Contact sensor', + supports: '', + fromZigbee: [ + fz.heiman_contact, + fz.generic_change_batteryvoltage_3000_2500, fz.generic_batteryvoltage_3000_2500, + ], + // TODO: Not fully supported - need to be configured correctly. Look at + // https://github.com/Koenkk/zigbee2mqtt/issues/1689 + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.write('ssIasZone', 'iasCieAddr', coordinator.device.getIeeeAddr(), cb), + (cb) => device.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 23}, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.report('genPowerCfg', 'batteryVoltage', repInterval.HOUR, repInterval.MAX, cb), + ]; + execute(device, actions, callback); + } + }, + */ + // TUYATEC { zigbeeModel: ['RH3040'], @@ -4719,6 +5120,376 @@ const devices = [ execute(device, actions, callback); }, }, + { + zigbeeModel: ['RH3052'], + model: 'TT001ZAV20', + vendor: 'TUYATEC', + description: 'Temperature & humidity sensor', + supports: 'temperature and humidity', + fromZigbee: [ + fz.generic_humidity, fz.generic_temperature, fz.battery_200, + fz.ignore_humidity_change, fz.ignore_temperature_change, + ], + toZigbee: [], + }, + + // Zemismart + { + zigbeeModel: ['TS0002'], + model: 'ZM-CSW002-D', + vendor: 'Zemismart', + description: '2 gang switch', + supports: 'on/off', + fromZigbee: [fz.generic_state_multi_ep, fz.ignore_onoff_change, fz.generic_power, fz.ignore_metering_change], + toZigbee: [tz.on_off, tz.ignore_transition], + ep: (device) => { + return {'l1': 1, 'l2': 2}; + }, + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const cfg = {direction: 0, attrId: 0, dataType: 16, minRepIntval: 0, maxRepIntval: 1000, repChange: 0}; + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.bind('genOnOff', coordinator, cb), + (cb) => device.foundation('genOnOff', 'configReport', [cfg], foundationCfg, cb), + ]; + + execute(device, actions, callback); + }, + }, + + // Sinope + { + zigbeeModel: ['TH1123ZB'], + model: 'TH1123ZB', + vendor: 'Sinope', + description: 'Zigbee line volt thermostat', + supports: 'local temp, units, keypad lockout, mode, state, backlight, outdoor temp, time', + fromZigbee: [ + fz.thermostat_att_report, fz.thermostat_dev_change, + ], + toZigbee: [ + tz.thermostat_local_temperature, tz.thermostat_occupancy, + tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint, + tz.thermostat_temperature_display_mode, tz.thermostat_keypad_lockout, + tz.thermostat_system_mode, tz.thermostat_running_state, + tz.sinope_thermostat_backlight_autodim_param, tz.sinope_thermostat_time, + tz.sinope_thermostat_enable_outdoor_temperature, tz.sinope_thermostat_outdoor_temperature, + ], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.bind('genBasic', coordinator, cb), + (cb) => device.bind('genIdentify', coordinator, cb), + (cb) => device.bind('genGroups', coordinator, cb), + (cb) => device.bind('hvacThermostat', coordinator, cb), + (cb) => device.bind('hvacUserInterfaceCfg', coordinator, cb), + (cb) => device.bind('msTemperatureMeasurement', coordinator, cb), + (cb) => device.report('hvacThermostat', 'localTemp', 19, 300, 50, cb), + (cb) => device.report('hvacThermostat', 'pIHeatingDemand', 4, 300, 10, cb), + (cb) => device.report('hvacThermostat', 'occupiedHeatingSetpoint', 15, 300, 40, cb), + ]; + execute(device, actions, callback); + }, + }, + + // Lutron + { + zigbeeModel: ['LZL4BWHL01 Remote'], + model: 'LZL4BWHL01', + vendor: 'Lutron', + description: 'Connected bulb remote control', + supports: 'on/off, brightness', + fromZigbee: [fz.GIRA2430_down_hold, fz.GIRA2430_up_hold, fz.E1524_hold, fz.GIRA2430_stop], + toZigbee: [], + }, + + // Zen + { + zigbeeModel: ['Zen-01'], + model: 'Zen-01-W', + vendor: 'Zen', + description: 'Thermostat', + supports: 'temperature, heating/cooling system control', + fromZigbee: [ + fz.ignore_basic_change, fz.generic_battery_voltage, + fz.thermostat_att_report, fz.thermostat_dev_change, + ], + toZigbee: [ + tz.factory_reset, tz.thermostat_local_temperature, tz.thermostat_local_temperature_calibration, + tz.thermostat_occupancy, tz.thermostat_occupied_heating_setpoint, + tz.thermostat_occupied_cooling_setpoint, + tz.thermostat_unoccupied_heating_setpoint, tz.thermostat_setpoint_raise_lower, + tz.thermostat_remote_sensing, tz.thermostat_control_sequence_of_operation, tz.thermostat_system_mode, + tz.thermostat_weekly_schedule, tz.thermostat_clear_weekly_schedule, tz.thermostat_weekly_schedule_rsp, + tz.thermostat_relay_status_log, tz.thermostat_relay_status_log_rsp, + ], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 3); + const actions = [ + (cb) => device.bind('genBasic', coordinator, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.bind('genIdentify', coordinator, cb), + (cb) => device.bind('genTime', coordinator, cb), + (cb) => device.bind('genPollCtrl', coordinator, cb), + (cb) => device.bind('hvacThermostat', coordinator, cb), + (cb) => device.bind('hvacUserInterfaceCfg', coordinator, cb), + (cb) => device.report('hvacThermostat', 'localTemp', 5, 30, 0, cb), + ]; + + execute(device, actions, callback); + }, + }, + + // Hej + { + zigbeeModel: ['HejSW01'], + model: 'GLSK3ZB-1711', + vendor: 'Hej', + description: 'Goqual 1 gang Switch', + supports: 'on/off', + fromZigbee: [fz.state, fz.ignore_onoff_change], + toZigbee: [tz.on_off], + }, + { + zigbeeModel: ['HejSW02'], + model: 'GLSK3ZB-1712', + vendor: 'Hej', + description: 'Goqual 2 gang Switch', + supports: 'on/off', + fromZigbee: [fz.generic_state_multi_ep, fz.ignore_onoff_change], + toZigbee: [tz.on_off], + ep: (device) => { + return {'top': 1, 'bottom': 2}; + }, + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const ep1 = shepherd.find(ieeeAddr, 1); + execute(ep1, [(cb) => ep1.bind('genOnOff', coordinator, cb)], () => { + const ep2 = shepherd.find(ieeeAddr, 2); + execute(ep2, [(cb) => ep2.bind('genOnOff', coordinator, cb)], callback); + }); + }, + }, + { + zigbeeModel: ['HejSW03'], + model: 'GLSK3ZB-1713', + vendor: 'Hej', + description: 'Goqual 3 gang Switch', + supports: 'on/off', + fromZigbee: [fz.generic_state_multi_ep, fz.ignore_onoff_change], + toZigbee: [tz.on_off], + ep: (device) => { + return {'top': 1, 'center': 2, 'bottom': 3}; + }, + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const ep1 = shepherd.find(ieeeAddr, 1); + execute(ep1, [(cb) => ep1.bind('genOnOff', coordinator, cb)], () => { + const ep2 = shepherd.find(ieeeAddr, 2); + execute(ep2, [(cb) => ep2.bind('genOnOff', coordinator, cb)], () => { + const ep3 = shepherd.find(ieeeAddr, 3); + execute(ep3, [(cb) => ep3.bind('genOnOff', coordinator, cb)], callback); + }); + }); + }, + }, + { + zigbeeModel: ['HejSW04'], + model: 'GLSK6ZB-1714', + vendor: 'Hej', + description: 'Goqual 4 gang Switch', + supports: 'on/off', + fromZigbee: [fz.generic_state_multi_ep, fz.ignore_onoff_change], + toZigbee: [tz.on_off], + ep: (device) => { + return {'top_left': 1, 'bottom_left': 2, 'top_right': 3, 'bottom_right': 4}; + }, + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const ep1 = shepherd.find(ieeeAddr, 1); + execute(ep1, [(cb) => ep1.bind('genOnOff', coordinator, cb)], () => { + const ep2 = shepherd.find(ieeeAddr, 2); + execute(ep2, [(cb) => ep2.bind('genOnOff', coordinator, cb)], () => { + const ep3 = shepherd.find(ieeeAddr, 3); + execute(ep3, [(cb) => ep3.bind('genOnOff', coordinator, cb)], () => { + const ep4 = shepherd.find(ieeeAddr, 4); + execute(ep4, [(cb) => ep4.bind('genOnOff', coordinator, cb)], callback); + }); + }); + }); + }, + }, + { + zigbeeModel: ['HejSW05'], + model: 'GLSK6ZB-1715', + vendor: 'Hej', + description: 'Goqual 5 gang Switch', + supports: 'on/off', + fromZigbee: [fz.generic_state_multi_ep, fz.ignore_onoff_change], + toZigbee: [tz.on_off], + ep: (device) => { + return {'top_left': 1, 'center_left': 2, 'bottom_left': 3, 'top_right': 4, 'bottom_right': 5}; + }, + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const ep1 = shepherd.find(ieeeAddr, 1); + execute(ep1, [(cb) => ep1.bind('genOnOff', coordinator, cb)], () => { + const ep2 = shepherd.find(ieeeAddr, 2); + execute(ep2, [(cb) => ep2.bind('genOnOff', coordinator, cb)], () => { + const ep3 = shepherd.find(ieeeAddr, 3); + execute(ep3, [(cb) => ep3.bind('genOnOff', coordinator, cb)], () => { + const ep4 = shepherd.find(ieeeAddr, 4); + execute(ep4, [(cb) => ep4.bind('genOnOff', coordinator, cb)], () => { + const ep5 = shepherd.find(ieeeAddr, 5); + execute(ep5, [(cb) => ep5.bind('genOnOff', coordinator, cb)], callback); + }); + }); + }); + }); + }, + }, + { + zigbeeModel: ['HejSW06'], + model: 'GLSK6ZB-1716', + vendor: 'Hej', + description: 'Goqual 6 gang Switch', + supports: 'on/off', + fromZigbee: [fz.generic_state_multi_ep, fz.ignore_onoff_change], + toZigbee: [tz.on_off], + ep: (device) => { + return { + 'top_left': 1, 'center_left': 2, 'bottom_left': 3, + 'top_right': 4, 'center_right': 5, 'bottom_right': 6, + }; + }, + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const ep1 = shepherd.find(ieeeAddr, 1); + execute(ep1, [(cb) => ep1.bind('genOnOff', coordinator, cb)], () => { + const ep2 = shepherd.find(ieeeAddr, 2); + execute(ep2, [(cb) => ep2.bind('genOnOff', coordinator, cb)], () => { + const ep3 = shepherd.find(ieeeAddr, 3); + execute(ep3, [(cb) => ep3.bind('genOnOff', coordinator, cb)], () => { + const ep4 = shepherd.find(ieeeAddr, 4); + execute(ep4, [(cb) => ep4.bind('genOnOff', coordinator, cb)], () => { + const ep5 = shepherd.find(ieeeAddr, 5); + execute(ep5, [(cb) => ep5.bind('genOnOff', coordinator, cb)], () => { + const ep6 = shepherd.find(ieeeAddr, 6); + execute(ep6, [(cb) => ep6.bind('genOnOff', coordinator, cb)], callback); + }); + }); + }); + }); + }); + }, + }, + + // Dawon DNS + { + zigbeeModel: ['PM-C140-ZB'], + model: 'PM-C140-ZB', + vendor: 'Dawon DNS', + description: 'IOT remote control smart buried-type outlet', + supports: 'on/off, power and energy measurement', + fromZigbee: [fz.state, fz.ignore_onoff_change, fz.ignore_metering_change, fz.generic_power], + toZigbee: [tz.on_off], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.bind('genOnOff', coordinator, cb), + (cb) => device.report('seMetering', 'instantaneousDemand', 10, 60, 1, cb), + ]; + + execute(device, actions, callback); + }, + }, + + // CREE + { + zigbeeModel: ['Connected A-19 60W Equivalent ', 'Connected A-19 60W Equivalent '], + model: 'B00TN589ZG', + vendor: 'CREE', + description: 'Connected bulb', + extend: generic.light_onoff_brightness, + }, + + // Ubisys + { + zigbeeModel: ['S1 (5501)', 'S1-R (5601)'], + model: 'S1', + vendor: 'Ubisys', + description: 'Power switch S1', + supports: 'on/off, power measurement', + fromZigbee: [fz.state, fz.ignore_onoff_change, fz.generic_power, fz.ignore_metering_change], + toZigbee: [tz.on_off], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 3); // metering + const actions = [ + (cb) => device.report('seMetering', 'instantaneousDemand', 10, 60, 1, cb), + ]; + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['S2 (5502)', 'S2-R (5602)'], + model: 'S2', + vendor: 'Ubisys', + description: 'Power switch S2', + supports: 'on/off, power measurement', + fromZigbee: [fz.state, fz.ignore_onoff_change, fz.generic_power, fz.ignore_metering_change], + toZigbee: [tz.on_off], + ep: (device) => { + return {'l1': 1, 'l2': 2}; + }, + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 5); // metering + const actions = [ + (cb) => device.report('seMetering', 'instantaneousDemand', 10, 60, 1, cb), + ]; + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['D1 (5503)', 'D1-R (5603)'], + model: 'D1', + vendor: 'Ubisys', + description: 'Universal dimmer D1', + supports: 'on/off, brightness, power measurement', + fromZigbee: [ + fz.state, fz.brightness_report, fz.ignore_onoff_change, fz.ignore_light_brightness_change, + fz.generic_power, fz.ignore_metering_change, + ], + toZigbee: [tz.light_onoff_brightness], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 4); // metering + const actions = [ + (cb) => device.report('seMetering', 'instantaneousDemand', 10, 60, 1, cb), + ]; + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['J1 (5502)', 'J1-R (5602)'], + model: 'J1', + vendor: 'Ubisys', + description: 'Shutter control J1', + supports: 'open, close, stop, position, tilt', + fromZigbee: [fz.closuresWindowCovering_report_pos_and_tilt, fz.ignore_closuresWindowCovering_change], + toZigbee: [tz.cover_control, tz.cover_gotopercentage], + }, + + // Lingan + { + zigbeeModel: ['SA-003-Zigbee'], + model: 'DZ4743-00B', + vendor: 'Lingan', + description: 'Zigbee OnOff controller', + supports: 'on/off', + fromZigbee: [fz.state_change, fz.state], + toZigbee: [tz.on_off], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.bind('genOnOff', coordinator, cb), + ]; + execute(device, actions, callback); + }, + }, ]; module.exports = devices.map((device) =>