diff --git a/zigbee-shepherd-converters/README.md b/zigbee-shepherd-converters/README.md new file mode 100644 index 0000000..08e87a2 --- /dev/null +++ b/zigbee-shepherd-converters/README.md @@ -0,0 +1 @@ +These files can be used for [zigbee2mqtt](https://www.zigbee2mqtt.io/) as long as the actual changed are not merged and released there. diff --git a/zigbee-shepherd-converters/converters/fromZigbee.js b/zigbee-shepherd-converters/converters/fromZigbee.js new file mode 100644 index 0000000..3e6fbfa --- /dev/null +++ b/zigbee-shepherd-converters/converters/fromZigbee.js @@ -0,0 +1,3143 @@ +'use strict'; + +const debounce = require('debounce'); +const common = require('./common'); + +const clickLookup = { + 1: 'single', + 2: 'double', + 3: 'triple', + 4: 'quadruple', +}; + +const occupancyTimeout = 90; // In seconds + +const defaultPrecision = { + temperature: 2, + humidity: 2, + pressure: 1, +}; + +const precisionRoundOptions = (number, options, type) => { + const key = `${type}_precision`; + const defaultValue = defaultPrecision[type]; + const precision = options && options.hasOwnProperty(key) ? options[key] : defaultValue; + return precisionRound(number, precision); +}; + +const precisionRound = (number, precision) => { + const factor = Math.pow(10, precision); + return Math.round(number * factor) / factor; +}; + +const toPercentage = (value, min, max) => { + if (value > max) { + value = max; + } else if (value < min) { + value = min; + } + + const normalised = (value - min) / (max - min); + return (normalised * 100).toFixed(2); +}; + +const toPercentageCR2032 = (voltage) => { + let percentage = null; + + if (voltage < 2100) { + percentage = 0; + } else if (voltage < 2440) { + percentage = 6 - ((2440 - voltage) * 6) / 340; + } else if (voltage < 2740) { + percentage = 18 - ((2740 - voltage) * 12) / 300; + } else if (voltage < 2900) { + percentage = 42 - ((2900 - voltage) * 24) / 160; + } else if (voltage < 3000) { + percentage = 100 - ((3000 - voltage) * 58) / 100; + } else if (voltage >= 3000) { + percentage = 100; + } + + return Math.round(percentage); +}; + +const numberWithinRange = (number, min, max) => { + if (number > max) { + return max; + } else if (number < min) { + return min; + } else { + return number; + } +}; + +// get object property name (key) by it's value +const getKey = (object, value) => { + for (const key in object) { + if (object[key]==value) return key; + } +}; + +// Global variable store that can be used by devices. +const store = {}; + +const ictcg1 = (model, msg, publish, options, action) => { + const deviceID = msg.endpoints[0].device.ieeeAddr; + const payload = {}; + + if (!store[deviceID]) { + const _publish = debounce((msg) => publish(msg), 250); + store[deviceID] = {since: false, direction: false, value: 255, publish: _publish}; + } + + const s = store[deviceID]; + if (s.since && s.direction) { + // Update value + const duration = Date.now() - s.since; + const delta = Math.round((duration / 10) * (s.direction === 'left' ? -1 : 1)); + const newValue = s.value + delta; + if (newValue >= 0 && newValue <= 255) { + s.value = newValue; + } + } + + if (action === 'move') { + s.since = Date.now(); + const direction = msg.data.data.movemode === 1 ? 'left' : 'right'; + s.direction = direction; + payload.action = `rotate_${direction}`; + } else if (action === 'stop' || action === 'level') { + if (action === 'level') { + s.value = msg.data.data.level; + const direction = s.value === 0 ? 'left' : 'right'; + payload.action = `rotate_${direction}_quick`; + } else { + payload.action = 'rotate_stop'; + } + + s.since = false; + s.direction = false; + } + + payload.brightness = s.value; + s.publish(payload); +}; + +const holdUpdateBrightness324131092621 = (deviceID) => { + if (store[deviceID] && store[deviceID].brightnessSince && store[deviceID].brightnessDirection) { + const duration = Date.now() - store[deviceID].brightnessSince; + const delta = (duration / 10) * (store[deviceID].brightnessDirection === 'up' ? 1 : -1); + const newValue = store[deviceID].brightnessValue + delta; + store[deviceID].brightnessValue = numberWithinRange(newValue, 0, 255); + } +}; + + +const converters = { + HS2SK_power: { + cid: 'haElectricalMeasurement', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + return { + power: msg.data.data['activePower'] / 10, + current: msg.data.data['rmsCurrent'] / 100, + voltage: msg.data.data['rmsVoltage'] / 100, + }; + }, + }, + generic_lock: { + cid: 'closuresDoorLock', + type: ['attReport', 'readRsp', 'devChange'], + convert: (model, msg, publish, options) => { + if (msg.data.data.hasOwnProperty('lockState')) { + return {state: msg.data.data.lockState == 2 ? 'UNLOCK' : 'LOCK'}; + } + }, + }, + generic_lock_operation_event: { + cid: 'closuresDoorLock', + type: 'cmdOperationEventNotification', + convert: (model, msg, publish, options) => { + return { + state: msg.data.data['opereventcode'] == 2 ? 'UNLOCK' : 'LOCK', + user: msg.data.data['userid'], + source: msg.data.data['opereventsrc'], + }; + }, + }, + genOnOff_cmdOn: { + cid: 'genOnOff', + type: 'cmdOn', + convert: (model, msg, publish, options) => { + return {click: 'on'}; + }, + }, + genOnOff_cmdOff: { + cid: 'genOnOff', + type: 'cmdOff', + convert: (model, msg, publish, options) => { + return {click: 'off'}; + }, + }, + E1743_brightness_up: { + cid: 'genLevelCtrl', + type: 'cmdMove', + convert: (model, msg, publish, options) => { + return {click: 'brightness_down'}; + }, + }, + E1743_brightness_down: { + cid: 'genLevelCtrl', + type: 'cmdMoveWithOnOff', + convert: (model, msg, publish, options) => { + return {click: 'brightness_up'}; + }, + }, + E1743_brightness_stop: { + cid: 'genLevelCtrl', + type: 'cmdStopWithOnOff', + convert: (model, msg, publish, options) => { + return {click: 'brightness_stop'}; + }, + }, + AC0251100NJ_long_middle: { + cid: 'lightingColorCtrl', + type: 'cmdMoveHue', + convert: (model, msg, publish, options) => { + return {click: 'long_middle'}; + }, + }, + AV2010_34_click: { + cid: 'genScenes', + type: 'cmdRecall', + convert: (model, msg, publish, options) => { + return {click: msg.data.data.groupid}; + }, + }, + bitron_power: { + cid: 'seMetering', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + return {power: parseFloat(msg.data.data['instantaneousDemand']) / 10.0}; + }, + }, + bitron_occupancy: { + cid: 'ssIasZone', + type: 'statusChange', + convert: (model, msg, publish, options) => { + // The occupancy sensor only sends a message when motion detected. + // Therefore we need to publish the no_motion detected by ourselves. + const useOptionsTimeout = options && options.hasOwnProperty('occupancy_timeout'); + const timeout = useOptionsTimeout ? options.occupancy_timeout : occupancyTimeout; + const deviceID = msg.endpoints[0].device.ieeeAddr; + + // Stop existing timer because motion is detected and set a new one. + 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: true}; + }, + }, + bitron_battery_att_report: { + cid: 'genPowerCfg', + type: 'attReport', + convert: (model, msg, publish, options) => { + const result = {}; + if (typeof msg.data.data['batteryVoltage'] == 'number') { + const battery = {max: 3200, min: 2500}; + const voltage = msg.data.data['batteryVoltage'] * 100; + result.battery = toPercentage(voltage, battery.min, battery.max); + result.voltage = voltage; + } + if (typeof msg.data.data['batteryAlarmState'] == 'number') { + result.battery_alarm_state = msg.data.data['batteryAlarmState']; + } + return result; + }, + }, + bitron_battery_dev_change: { + cid: 'genPowerCfg', + type: 'devChange', + convert: (model, msg, publish, options) => { + const result = {}; + if (typeof msg.data.data['batteryVoltage'] == 'number') { + const battery = {max: 3200, min: 2500}; + const voltage = msg.data.data['batteryVoltage'] * 100; + result.battery = toPercentage(voltage, battery.min, battery.max); + result.voltage = voltage; + } + if (typeof msg.data.data['batteryAlarmState'] == 'number') { + result.battery_alarm_state = msg.data.data['batteryAlarmState']; + } + return result; + }, + }, + bitron_thermostat_att_report: { + cid: 'hvacThermostat', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const result = {}; + if (typeof msg.data.data['localTemp'] == 'number') { + result.local_temperature = precisionRound(msg.data.data['localTemp'], 2) / 100; + } + if (typeof msg.data.data['localTemperatureCalibration'] == 'number') { + result.local_temperature_calibration = + precisionRound(msg.data.data['localTemperatureCalibration'], 2) / 10; + } + if (typeof msg.data.data['occupiedHeatingSetpoint'] == 'number') { + result.occupied_heating_setpoint = + precisionRound(msg.data.data['occupiedHeatingSetpoint'], 2) / 100; + } + if (typeof msg.data.data['runningState'] == 'number') { + result.running_state = msg.data.data['runningState']; + } + if (typeof msg.data.data['batteryAlarmState'] == 'number') { + result.battery_alarm_state = msg.data.data['batteryAlarmState']; + } + return result; + }, + }, + bitron_thermostat_dev_change: { + cid: 'hvacThermostat', + type: 'devChange', + convert: (model, msg, publish, options) => { + const result = {}; + if (typeof msg.data.data['localTemp'] == 'number') { + result.local_temperature = precisionRound(msg.data.data['localTemp'], 2) / 100; + } + if (typeof msg.data.data['localTemperatureCalibration'] == 'number') { + result.local_temperature_calibration = + precisionRound(msg.data.data['localTemperatureCalibration'], 2) / 10; + } + if (typeof msg.data.data['occupiedHeatingSetpoint'] == 'number') { + result.occupied_heating_setpoint = + precisionRound(msg.data.data['occupiedHeatingSetpoint'], 2) / 100; + } + if (typeof msg.data.data['runningState'] == 'number') { + result.running_state = msg.data.data['runningState']; + } + if (typeof msg.data.data['batteryAlarmState'] == 'number') { + result.battery_alarm_state = msg.data.data['batteryAlarmState']; + } + return result; + }, + }, + nue_click: { + cid: 'genScenes', + type: 'cmdRecall', + convert: (model, msg, publish, options) => { + return {click: msg.data.data.sceneid}; + }, + }, + smartthings_contact: { + cid: 'ssIasZone', + type: 'statusChange', + convert: (model, msg, publish, options) => { + return {contact: msg.data.zoneStatus === 48}; + }, + }, + xiaomi_battery_3v: { + cid: 'genBasic', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + let voltage = null; + + if (msg.data.data['65281']) { + voltage = msg.data.data['65281']['1']; + } else if (msg.data.data['65282']) { + voltage = msg.data.data['65282']['1'].elmVal; + } + + if (voltage) { + return { + battery: parseFloat(toPercentageCR2032(voltage)), + voltage: voltage, + }; + } + }, + }, + RTCGQ11LM_interval: { + cid: 'genBasic', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + if (msg.data.data['65281']) { + return {illuminance: msg.data.data['65281']['11']}; + } + }, + }, + WSDCGQ01LM_WSDCGQ11LM_interval: { + cid: 'genBasic', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + if (msg.data.data['65281']) { + const temperature = parseFloat(msg.data.data['65281']['100']) / 100.0; + const humidity = parseFloat(msg.data.data['65281']['101']) / 100.0; + const result = {}; + + // https://github.com/Koenkk/zigbee2mqtt/issues/798 + // Sometimes the sensor publishes non-realistic vales, as the sensor only works from + // -20 till +60, don't produce messages beyond these values. + if (temperature > -25 && temperature < 65) { + result.temperature = precisionRoundOptions(temperature, options, 'temperature'); + } + + // in the 0 - 100 range, don't produce messages beyond these values. + if (humidity >= 0 && humidity <= 100) { + result.humidity = precisionRoundOptions(humidity, options, 'humidity'); + } + + // Check if contains pressure (WSDCGQ11LM only) + if (msg.data.data['65281'].hasOwnProperty('102')) { + const pressure = parseFloat(msg.data.data['65281']['102']) / 100.0; + result.pressure = precisionRoundOptions(pressure, options, 'pressure'); + } + + return result; + } + }, + }, + WXKG01LM_click: { + cid: 'genOnOff', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const deviceID = msg.endpoints[0].device.ieeeAddr; + const state = msg.data.data['onOff']; + + if (!store[deviceID]) { + store[deviceID] = {}; + } + + // 0 = click down, 1 = click up, else = multiple clicks + if (state === 0) { + store[deviceID].timer = setTimeout(() => { + publish({click: 'long'}); + store[deviceID].timer = null; + store[deviceID].long = Date.now(); + }, options.long_timeout || 1000); // After 1000 milliseconds of not releasing we assume long click. + } else if (state === 1) { + if (store[deviceID].long) { + const duration = Date.now() - store[deviceID].long; + publish({click: 'long_release', duration: duration}); + store[deviceID].long = false; + } + + if (store[deviceID].timer) { + clearTimeout(store[deviceID].timer); + store[deviceID].timer = null; + publish({click: 'single'}); + } + } else { + const clicks = msg.data.data['32768']; + const payload = clickLookup[clicks] ? clickLookup[clicks] : 'many'; + publish({click: payload}); + } + }, + }, + generic_temperature: { + cid: 'msTemperatureMeasurement', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const temperature = parseFloat(msg.data.data['measuredValue']) / 100.0; + return {temperature: precisionRoundOptions(temperature, options, 'temperature')}; + }, + }, + generic_temperature_change: { + cid: 'msTemperatureMeasurement', + type: 'devChange', + convert: (model, msg, publish, options) => { + const temperature = parseFloat(msg.data.data['measuredValue']) / 100.0; + return {temperature: precisionRoundOptions(temperature, options, 'temperature')}; + }, + }, + xiaomi_temperature: { + cid: 'msTemperatureMeasurement', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const temperature = parseFloat(msg.data.data['measuredValue']) / 100.0; + + // https://github.com/Koenkk/zigbee2mqtt/issues/798 + // Sometimes the sensor publishes non-realistic vales, as the sensor only works from + // -20 till +60, don't produce messages beyond these values. + if (temperature > -25 && temperature < 65) { + return {temperature: precisionRoundOptions(temperature, options, 'temperature')}; + } + }, + }, + MFKZQ01LM_action_multistate: { + cid: 'genMultistateInput', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + /* + Source: https://github.com/kirovilya/ioBroker.zigbee + +---+ + | 2 | + +---+---+---+ + | 4 | 0 | 1 | + +---+---+---+ + |M5I| + +---+ + | 3 | + +---+ + Side 5 is with the MI logo, side 3 contains the battery door. + presentValue = 0 = shake + presentValue = 2 = wakeup + presentValue = 3 = fly/fall + presentValue = y + x * 8 + 64 = 90º Flip from side x on top to side y on top + presentValue = x + 128 = 180º flip to side x on top + presentValue = x + 256 = push/slide cube while side x is on top + presentValue = x + 512 = double tap while side x is on top + */ + const value = msg.data.data['presentValue']; + let action = null; + + if (value === 0) action = {'action': 'shake'}; + else if (value === 2) action = {'action': 'wakeup'}; + else if (value === 3) action = {'action': 'fall'}; + else if (value >= 512) action = {'action': 'tap', 'side': value-512}; + else if (value >= 256) action = {'action': 'slide', 'side': value-256}; + else if (value >= 128) action = {'action': 'flip180', 'side': value-128}; + else if (value >= 64) { + action = {'action': 'flip90', 'from_side': Math.floor((value-64) / 8), 'to_side': value % 8}; + } + + return action ? action : null; + }, + }, + MFKZQ01LM_action_analog: { + cid: 'genAnalogInput', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + /* + Source: https://github.com/kirovilya/ioBroker.zigbee + presentValue = rotation angle left < 0, right > 0 + */ + const value = msg.data.data['presentValue']; + return { + action: value < 0 ? 'rotate_left' : 'rotate_right', + angle: Math.floor(value * 100) / 100, + }; + }, + }, + WXKG12LM_action_click_multistate: { + cid: 'genMultistateInput', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const value = msg.data.data['presentValue']; + const lookup = { + 1: {click: 'single'}, // single click + 2: {click: 'double'}, // double click + 16: {action: 'hold'}, // hold for more than 400ms + 17: {action: 'release'}, // release after hold for more than 400ms + 18: {action: 'shake'}, // shake + }; + + return lookup[value] ? lookup[value] : null; + }, + }, + xiaomi_action_click_multistate: { + cid: 'genMultistateInput', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const value = msg.data.data['presentValue']; + const lookup = { + 1: {click: 'single'}, // single click + 2: {click: 'double'}, // double click + 0: {action: 'hold'}, // hold for more than 400ms + 255: {action: 'release'}, // release after hold for more than 400ms + }; + + return lookup[value] ? lookup[value] : null; + }, + }, + xiaomi_humidity: { + cid: 'msRelativeHumidity', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const humidity = parseFloat(msg.data.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) { + return {humidity: precisionRoundOptions(humidity, options, 'humidity')}; + } + }, + }, + generic_occupancy: { + // This is for occupancy sensor that send motion start AND stop messages + // Note: options.occupancy_timeout not available yet, to implement it will be + // needed to update device report intervall as well, see devices.js + cid: 'msOccupancySensing', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + if (msg.data.data.occupancy === 0) { + return {occupancy: false}; + } else if (msg.data.data.occupancy === 1) { + return {occupancy: true}; + } + }, + }, + E1525_occupancy: { + cid: 'genOnOff', + type: 'cmdOnWithTimedOff', + convert: (model, msg, publish, options) => { + const timeout = msg.data.data.ontime / 10; + const deviceID = msg.endpoints[0].device.ieeeAddr; + + // Stop existing timer because motion is detected and set a new one. + 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: true}; + }, + }, + generic_occupancy_no_off_msg: { + // This is for occupancy sensor that only send a message when motion detected, + // but do not send a motion stop. + // Therefore we need to publish the no_motion detected by ourselves. + cid: 'msOccupancySensing', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + if (msg.data.data.occupancy !== 1) { + // In case of 0 no occupancy is reported. + // https://github.com/Koenkk/zigbee2mqtt/issues/467 + return; + } + + // The occupancy sensor only sends a message when motion detected. + // Therefore we need to publish the no_motion detected by ourselves. + const useOptionsTimeout = options && options.hasOwnProperty('occupancy_timeout'); + const timeout = useOptionsTimeout ? options.occupancy_timeout : occupancyTimeout; + const deviceID = msg.endpoints[0].device.ieeeAddr; + + // Stop existing timers because motion is detected and set a new one. + if (store[deviceID]) { + store[deviceID].forEach((t) => clearTimeout(t)); + } + + store[deviceID] = []; + + if (timeout !== 0) { + const timer = setTimeout(() => { + publish({occupancy: false}); + }, timeout * 1000); + + store[deviceID].push(timer); + } + + // No occupancy since + if (options && options.no_occupancy_since) { + options.no_occupancy_since.forEach((since) => { + const timer = setTimeout(() => { + publish({no_occupancy_since: since}); + }, since * 1000); + store[deviceID].push(timer); + }); + } + + if (options && options.no_occupancy_since) { + return {occupancy: true, no_occupancy_since: 0}; + } else { + return {occupancy: true}; + } + }, + }, + xiaomi_contact: { + cid: 'genOnOff', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + return {contact: msg.data.data['onOff'] === 0}; + }, + }, + xiaomi_contact_interval: { + cid: 'genBasic', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + if (msg.data.data.hasOwnProperty('65281')) { + return {contact: msg.data.data['65281']['100'] === 0}; + } + }, + }, + brightness: { + cid: 'genLevelCtrl', + type: 'devChange', + convert: (model, msg, publish, options) => { + if (msg.data.data.hasOwnProperty('currentLevel')) { + return {brightness: msg.data.data['currentLevel']}; + } + }, + }, + color_colortemp: { + cid: 'lightingColorCtrl', + type: 'devChange', + convert: (model, msg, publish, options) => { + const result = {}; + + if (msg.data.data['colorTemperature']) { + result.color_temp = msg.data.data['colorTemperature']; + } + + if (msg.data.data['colorMode']) { + result.color_mode = msg.data.data['colorMode']; + } + + if ( + msg.data.data['currentX'] + || msg.data.data['currentY'] + || msg.data.data['currentSaturation'] + || msg.data.data['enhancedCurrentHue'] + ) { + result.color = {}; + + if (msg.data.data['currentX']) { + result.color.x = precisionRound(msg.data.data['currentX'] / 65535, 3); + } + + if (msg.data.data['currentY']) { + result.color.y = precisionRound(msg.data.data['currentY'] / 65535, 3); + } + + if (msg.data.data['currentSaturation']) { + result.color.saturation = precisionRound(msg.data.data['currentSaturation'] / 2.54, 1); + } + + if (msg.data.data['enhancedCurrentHue']) { + result.color.hue = precisionRound(msg.data.data['enhancedCurrentHue'] / (65535 / 360), 1); + } + } + + return result; + }, + }, + color_colortemp_report: { + cid: 'lightingColorCtrl', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const result = {}; + + if (msg.data.data['colorTemperature']) { + result.color_temp = msg.data.data['colorTemperature']; + } + + if (msg.data.data['colorMode']) { + result.color_mode = msg.data.data['colorMode']; + } + + if ( + msg.data.data['currentX'] + || msg.data.data['currentY'] + || msg.data.data['currentSaturation'] + || msg.data.data['enhancedCurrentHue'] + ) { + result.color = {}; + + if (msg.data.data['currentX']) { + result.color.x = precisionRound(msg.data.data['currentX'] / 65535, 3); + } + + if (msg.data.data['currentY']) { + result.color.y = precisionRound(msg.data.data['currentY'] / 65535, 3); + } + + if (msg.data.data['currentSaturation']) { + result.color.saturation = precisionRound(msg.data.data['currentSaturation'] / 2.54, 1); + } + + if (msg.data.data['enhancedCurrentHue']) { + result.color.hue = precisionRound(msg.data.data['enhancedCurrentHue'] / (65535 / 360), 1); + } + } + + return result; + }, + }, + WXKG11LM_click: { + cid: 'genOnOff', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const data = msg.data.data; + let clicks; + + if (data.onOff) { + clicks = 1; + } else if (data['32768']) { + clicks = data['32768']; + } + + if (clickLookup[clicks]) { + return {click: clickLookup[clicks]}; + } + }, + }, + generic_illuminance: { + cid: 'msIlluminanceMeasurement', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + return {illuminance: msg.data.data['measuredValue']}; + }, + }, + generic_pressure: { + cid: 'msPressureMeasurement', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const pressure = parseFloat(msg.data.data['measuredValue']); + return {pressure: precisionRoundOptions(pressure, options, 'pressure')}; + }, + }, + WXKG02LM_click: { + cid: 'genOnOff', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const ep = msg.endpoints[0]; + return {click: getKey(model.ep(ep.device), ep.epId)}; + }, + }, + WXKG02LM_click_multistate: { + cid: 'genMultistateInput', + type: ['attReport', 'readRsp'], + 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 = { + 0: 'long', + 1: null, + 2: 'double', + }; + + const action = actionLookup[value]; + + if (button) { + return {click: button + (action ? `_${action}` : '')}; + } + }, + }, + WXKG03LM_click: { + cid: 'genOnOff', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + return {click: 'single'}; + }, + }, + KEF1PA_arm: { + cid: 'ssIasAce', + type: 'cmdArm', + convert: (model, msg, publish, options) => { + const action = msg.data.data['armmode']; + delete msg.data.data['armmode']; + const modeLookup = { + 0: 'home', + 2: 'sleep', + 3: 'away', + }; + return {action: modeLookup[action]}; + }, + }, + KEF1PA_panic: { + cid: 'ssIasAce', + type: 'cmdPanic', + convert: (model, msg, publish, options) => { + delete msg.data.data['armmode']; + return {action: 'panic'}; + }, + }, + SJCGQ11LM_water_leak_iaszone: { + cid: 'ssIasZone', + type: 'statusChange', + convert: (model, msg, publish, options) => { + return {water_leak: msg.data.zoneStatus === 1}; + }, + }, + state: { + cid: 'genOnOff', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + if (msg.data.data.hasOwnProperty('onOff')) { + return {state: msg.data.data['onOff'] === 1 ? 'ON' : 'OFF'}; + } + }, + }, + state_report: { + cid: 'genOnOff', + type: 'attReport', + convert: (model, msg, publish, options) => { + if (msg.data.data.hasOwnProperty('onOff')) { + return {state: msg.data.data['onOff'] === 1 ? 'ON' : 'OFF'}; + } + }, + }, + state_change: { + cid: 'genOnOff', + type: 'devChange', + convert: (model, msg, publish, options) => { + if (msg.data.data.hasOwnProperty('onOff')) { + return {state: msg.data.data['onOff'] === 1 ? 'ON' : 'OFF'}; + } + }, + }, + xiaomi_power: { + cid: 'genAnalogInput', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + return {power: precisionRound(msg.data.data['presentValue'], 2)}; + }, + }, + xiaomi_plug_state: { + cid: 'genBasic', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + if (msg.data.data['65281']) { + const data = msg.data.data['65281']; + return { + state: data['100'] === 1 ? 'ON' : 'OFF', + power: precisionRound(data['152'], 2), + voltage: precisionRound(data['150'] * 0.1, 1), + consumption: precisionRound(data['149'], 2), + temperature: precisionRoundOptions(data['3'], options, 'temperature'), + }; + } + }, + }, + xiaomi_bulb_interval: { + cid: 'genBasic', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + if (msg.data.data['65281']) { + const data = msg.data.data['65281']; + return { + state: data['100'] === 1 ? 'ON' : 'OFF', + brightness: data['101'], + color_temp: data['102'], + }; + } + }, + }, + QBKG11LM_power: { + cid: 'genBasic', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + if (msg.data.data['65281']) { + const data = msg.data.data['65281']; + return { + power: precisionRound(data['152'], 2), + consumption: precisionRound(data['149'], 2), + temperature: precisionRoundOptions(data['3'], options, 'temperature'), + }; + } + }, + }, + QBKG12LM_LLKZMK11LM_power: { + cid: 'genBasic', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + if (msg.data.data['65281']) { + const data = msg.data.data['65281']; + return { + power: precisionRound(data['152'], 2), + consumption: precisionRound(data['149'], 2), + temperature: precisionRoundOptions(data['3'], options, 'temperature'), + }; + } + }, + }, + QBKG04LM_QBKG11LM_state: { + cid: 'genOnOff', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + if (msg.data.data['61440']) { + return {state: msg.data.data['onOff'] === 1 ? 'ON' : 'OFF'}; + } else { + return {click: 'single'}; + } + }, + }, + QBKG04LM_buttons: { + cid: 'genOnOff', + type: 'devChange', + convert: (model, msg, publish, options) => { + if (msg.endpoints[0].epId == 4) { + return {action: msg.data.data['onOff'] === 1 ? 'release' : 'hold'}; + } + }, + }, + QBKG04LM_QBKG11LM_operation_mode: { + cid: 'genBasic', + type: 'devChange', + convert: (model, msg, publish, options) => { + const mappingMode = { + 0x12: 'control_relay', + 0xFE: 'decoupled', + }; + const key = '65314'; + if (msg.data.data.hasOwnProperty(key)) { + const mode = mappingMode[msg.data.data[key]]; + return {operation_mode: mode}; + } + }, + }, + QBKG03LM_QBKG12LM_LLKZMK11LM_state: { + cid: 'genOnOff', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + if (msg.data.data['61440']) { + 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; + } else { + const mapping = {4: 'left', 5: 'right', 6: 'both'}; + const button = mapping[msg.endpoints[0].epId]; + return {click: button}; + } + }, + }, + QBKG11LM_click: { + cid: 'genMultistateInput', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + if ([1, 2].includes(msg.data.data.presentValue)) { + const times = {1: 'single', 2: 'double'}; + return {click: times[msg.data.data.presentValue]}; + } + }, + }, + QBKG12LM_click: { + cid: 'genMultistateInput', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + if ([1, 2].includes(msg.data.data.presentValue)) { + const mapping = {5: 'left', 6: 'right', 7: 'both'}; + const times = {1: 'single', 2: 'double'}; + const button = mapping[msg.endpoints[0].epId]; + return {click: `${button}_${times[msg.data.data.presentValue]}`}; + } + }, + }, + QBKG03LM_buttons: { + cid: 'genOnOff', + type: 'devChange', + convert: (model, msg, publish, options) => { + const mapping = {4: 'left', 5: 'right'}; + const button = mapping[msg.endpoints[0].epId]; + if (button) { + const payload = {}; + payload[`button_${button}`] = msg.data.data['onOff'] === 1 ? 'release' : 'hold'; + return payload; + } + }, + }, + QBKG03LM_QBKG12LM_operation_mode: { + cid: 'genBasic', + type: 'devChange', + convert: (model, msg, publish, options) => { + const mappingButton = { + '65314': 'left', + '65315': 'right', + }; + const mappingMode = { + 0x12: 'control_left_relay', + 0x22: 'control_right_relay', + 0xFE: 'decoupled', + }; + for (const key in mappingButton) { + if (msg.data.data.hasOwnProperty(key)) { + const payload = {}; + const mode = mappingMode[msg.data.data[key]]; + payload[`operation_mode_${mappingButton[key]}`] = mode; + return payload; + } + } + }, + }, + xiaomi_lock_report: { + cid: 'genBasic', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + if (msg.data.data['65328']) { + const data = msg.data.data['65328']; + const state = data.substr(2, 2); + const action = data.substr(4, 2); + const keynum = data.substr(6, 2); + if (state == 11) { + if (action == 1) { + // unknown key + return {keyerror: true, inserted: 'unknown'}; + } + if (action == 3) { + // explicitly disabled key (i.e. reported lost) + return {keyerror: true, inserted: keynum}; + } + if (action == 7) { + // strange object introduced into the cylinder (e.g. a lock pick) + return {keyerror: true, inserted: 'strange'}; + } + } + if (state == 12) { + if (action == 1) { + return {inserted: keynum}; + } + if (action == 11) { + return {forgotten: keynum}; + } + } + } + }, + }, + ZNCLDJ11LM_curtain_genAnalogOutput_change: { + cid: 'genAnalogOutput', + type: 'devChange', + convert: (model, msg, publish, options) => { + let running = false; + + if (msg.data.data['61440']) { + running = msg.data.data['61440'] !== 0; + } + + const position = precisionRound(msg.data.data['presentValue'], 2); + return {position: position, running: running}; + }, + }, + ZNCLDJ11LM_curtain_genAnalogOutput_report: { + cid: 'genAnalogOutput', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + let running = false; + + if (msg.data.data['61440']) { + running = msg.data.data['61440'] !== 0; + } + + const position = precisionRound(msg.data.data['presentValue'], 2); + return {position: position, running: running}; + }, + }, + JTYJGD01LMBW_smoke: { + cid: 'ssIasZone', + type: 'statusChange', + convert: (model, msg, publish, options) => { + return {smoke: msg.data.zoneStatus === 1}; + }, + }, + heiman_pir: { + cid: 'ssIasZone', + type: 'statusChange', + convert: (model, msg, publish, options) => { + const zoneStatus = msg.data.zoneStatus; + return { + occupancy: (zoneStatus & 1) > 0, // Bit 1 = Alarm: Motion detection + tamper: (zoneStatus & 1<<2) > 0, // Bit 3 = Tamper status + battery_low: (zoneStatus & 1<<3) > 0, // Bit 4 = Battery LOW indicator + }; + }, + }, + heiman_smoke: { + cid: 'ssIasZone', + type: 'statusChange', + convert: (model, msg, publish, options) => { + const zoneStatus = msg.data.zoneStatus; + return { + smoke: (zoneStatus & 1) > 0, // Bit 1 = Alarm: Smoke + battery_low: (zoneStatus & 1<<3) > 0, // Bit 4 = Battery LOW indicator + }; + }, + }, + heiman_smart_controller_armmode: { + cid: 'ssIasAce', + type: 'cmdArm', + convert: (model, msg, publish, options) => { + if (msg.data.data.armmode != null) { + const lookup = { + 0: 'disarm', + 1: 'arm_partial_zones', + 3: 'arm_all_zones', + }; + + const value = msg.data.data.armmode; + return {action: lookup[value] || `armmode_${value}`}; + } + }, + }, + heiman_smart_controller_emergency: { + cid: 'ssIasAce', + type: 'cmdEmergency', + convert: (model, msg, publish, options) => { + return {action: 'emergency'}; + }, + }, + battery_200: { + cid: 'genPowerCfg', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const batt = msg.data.data.batteryPercentageRemaining; + const battLow = msg.data.data.batteryAlarmState; + const results = {}; + if (batt != null) { + const value = Math.round(batt/200.0*10000)/100; // Out of 200 + results['battery'] = value; + } + if (battLow != null) { + if (battLow) { + results['battery_low'] = true; + } else { + results['battery_low'] = false; + } + } + return results; + }, + }, + heiman_smoke_enrolled: { + cid: 'ssIasZone', + type: 'devChange', + convert: (model, msg, publish, options) => { + const zoneId = msg.data.data.zoneId; + const zoneState = msg.data.data.zoneState; + const results = {}; + if (zoneState) { + results['enrolled'] = true; + } else { + results['enrolled'] = false; + } + results['zone_id'] = zoneId; + return results; + }, + }, + heiman_gas: { + cid: 'ssIasZone', + type: 'statusChange', + convert: (model, msg, publish, options) => { + const zoneStatus = msg.data.zoneStatus; + return { + gas: (zoneStatus & 1) > 0, // Bit 1 = Alarm: Gas + battery_low: (zoneStatus & 1<<3) > 0, // Bit 4 = Battery LOW indicator + }; + }, + }, + heiman_water_leak: { + cid: 'ssIasZone', + type: 'statusChange', + convert: (model, msg, publish, options) => { + const zoneStatus = msg.data.zoneStatus; + return { + water_leak: (zoneStatus & 1) > 0, // Bit 1 = Alarm: Water leak + tamper: (zoneStatus & 1<<2) > 0, // Bit 3 = Tamper status + battery_low: (zoneStatus & 1<<3) > 0, // Bit 4 = Battery LOW indicator + }; + }, + }, + heiman_contact: { + cid: 'ssIasZone', + type: 'statusChange', + convert: (model, msg, publish, options) => { + const zoneStatus = msg.data.zoneStatus; + return { + contact: (zoneStatus & 1) > 0, // Bit 1 = Alarm: Contact detection + tamper: (zoneStatus & 1<<2) > 0, // Bit 3 = Tamper status + battery_low: (zoneStatus & 1<<3) > 0, // Bit 4 = Battery LOW indicator + }; + }, + }, + heiman_carbon_monoxide: { + cid: 'ssIasZone', + type: 'statusChange', + convert: (model, msg, publish, options) => { + const zoneStatus = msg.data.zoneStatus; + return { + carbon_monoxide: (zoneStatus & 1) > 0, // Bit 1 = Alarm: Carbon monoxide + battery_low: (zoneStatus & 1<<3) > 0, // Bit 4 = Battery LOW indicator + }; + }, + }, + JTQJBF01LMBW_gas: { + cid: 'ssIasZone', + type: 'statusChange', + convert: (model, msg, publish, options) => { + return {gas: msg.data.zoneStatus === 1}; + }, + }, + JTQJBF01LMBW_gas_density: { + cid: 'genBasic', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const data = msg.data.data; + if (data && data['65281']) { + const basicAttrs = data['65281']; + if (basicAttrs.hasOwnProperty('100')) { + return {gas_density: basicAttrs['100']}; + } + } + }, + }, + JTQJBF01LMBW_sensitivity: { + cid: 'ssIasZone', + type: 'devChange', + convert: (model, msg, publish, options) => { + const data = msg.data.data; + const lookup = { + '1': 'low', + '2': 'medium', + '3': 'high', + }; + + if (data && data.hasOwnProperty('65520')) { + const value = data['65520']; + if (value && value.startsWith('0x020')) { + return { + sensitivity: lookup[value.charAt(5)], + }; + } + } + }, + }, + DJT11LM_vibration: { + cid: 'closuresDoorLock', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const result = {}; + const vibrationLookup = { + 1: 'vibration', + 2: 'tilt', + 3: 'drop', + }; + + if (msg.data.data['85']) { + const data = msg.data.data['85']; + result.action = vibrationLookup[data]; + } + if (msg.data.data['1283']) { + const data = msg.data.data['1283']; + result.angle = data; + } + + if (msg.data.data['1288']) { + const data = msg.data.data['1288']; + + // array interpretation: + // 12 bit two's complement sign extended integer + // data[1][bit0..bit15] : x + // data[1][bit16..bit31]: y + // data[0][bit0..bit15] : z + // left shift first to preserve sign extension for 'x' + const x = ((data['1'] << 16) >> 16); + const y = (data['1'] >> 16); + // left shift first to preserve sign extension for 'z' + const z = ((data['0'] << 16) >> 16); + + // calculate angle + result.angle_x = Math.round(Math.atan(x/Math.sqrt(y*y+z*z)) * 180 / Math.PI); + result.angle_y = Math.round(Math.atan(y/Math.sqrt(x*x+z*z)) * 180 / Math.PI); + result.angle_z = Math.round(Math.atan(z/Math.sqrt(x*x+y*y)) * 180 / Math.PI); + + // calculate absolulte angle + const R = Math.sqrt(x * x + y * y + z * z); + result.angle_x_absolute = Math.round((Math.acos(x / R)) * 180 / Math.PI); + result.angle_y_absolute = Math.round((Math.acos(y / R)) * 180 / Math.PI); + } + + return result; + }, + }, + generic_power: { + cid: 'seMetering', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const result = {}; + const endpoint = msg.endpoints[0]; + let factor = null; + + if (endpoint.clusters.has('seMetering')) { + const attrs = endpoint.clusters['seMetering'].attrs; + + if (attrs.multiplier && attrs.divisor) { + factor = attrs.multiplier / attrs.divisor; + } + } + + if (msg.data.data.hasOwnProperty('instantaneousDemand')) { + let power = msg.data.data['instantaneousDemand']; + if (factor != null) { + power = (power * factor) * 1000; // kWh to Watt + } + result.power = precisionRound(power, 2); + } + + if (factor != null && (msg.data.data.hasOwnProperty('currentSummDelivered') || + msg.data.data.hasOwnProperty('currentSummReceived'))) { + result.energy = 0; + if (msg.data.data.hasOwnProperty('currentSummDelivered')) { + const data = msg.data.data['currentSummDelivered']; + const value = (parseInt(data[0]) << 32) + parseInt(data[1]); + result.energy += value * factor; + } + if (msg.data.data.hasOwnProperty('currentSummReceived')) { + const data = msg.data.data['currentSummReceived']; + const value = (parseInt(data[0]) << 32) + parseInt(data[1]); + result.energy -= value * factor; + } + } + + return result; + }, + }, + CC2530ROUTER_state: { + cid: 'genOnOff', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + return {state: true, led_state: msg.data.data['onOff'] === 1}; + }, + }, + CC2530ROUTER_meta: { + cid: 'genBinaryValue', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const data = msg.data.data; + return { + description: data['description'], + type: data['inactiveText'], + rssi: data['presentValue'], + }; + }, + }, + DNCKAT_S00X_state: { + cid: 'genOnOff', + type: ['attReport', 'readRsp'], + 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; + }, + }, + DNCKAT_S00X_buttons: { + cid: 'genOnOff', + type: 'devChange', + convert: (model, msg, publish, options) => { + const ep = msg.endpoints[0]; + const key = `button_${getKey(model.ep(ep.device), ep.epId)}`; + const payload = {}; + payload[key] = msg.data.data['onOff'] === 1 ? 'release' : 'hold'; + return payload; + }, + }, + ZigUP_parse: { + cid: 'genOnOff', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const lookup = { + '0': 'timer', + '1': 'key', + '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); + } + + return { + state: msg.data.data['onOff'] === 1 ? 'ON' : 'OFF', + cpu_temperature: precisionRound(msg.data.data['41361'], 2), + external_temperature: precisionRound(msg.data.data['41362'], 1), + external_humidity: precisionRound(msg.data.data['41363'], 1), + s0_counts: msg.data.data['41364'], + adc_volt: precisionRound(msg.data.data['41365'], 3), + dig_input: msg.data.data['41366'], + reason: lookup[msg.data.data['41367']], + [`${ds18b20_id}`]: ds18b20_value, + }; + }, + }, + Z809A_power: { + cid: 'haElectricalMeasurement', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + return { + power: msg.data.data['activePower'], + current: msg.data.data['rmsCurrent'], + voltage: msg.data.data['rmsVoltage'], + power_factor: msg.data.data['powerFactor'], + }; + }, + }, + SP120_power: { + cid: 'haElectricalMeasurement', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const result = {}; + + if (msg.data.data.hasOwnProperty('activePower')) { + result.power = msg.data.data['activePower']; + } + + if (msg.data.data.hasOwnProperty('rmsCurrent')) { + result.current = msg.data.data['rmsCurrent'] / 1000; + } + + if (msg.data.data.hasOwnProperty('rmsVoltage')) { + result.voltage = msg.data.data['rmsVoltage']; + } + + return result; + }, + }, + peanut_electrical: { + cid: 'haElectricalMeasurement', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const result = {}; + const deviceID = msg.endpoints[0].device.ieeeAddr; + + // initialize stored defaults with observed values + if (!store[deviceID]) { + store[deviceID] = { + acVoltageMultiplier: 180, acVoltageDivisor: 39321, acCurrentMultiplier: 72, + acCurrentDivisor: 39321, acPowerMultiplier: 10255, acPowerDivisor: 39321, + }; + } + + // if new multipliers/divisors come in, replace prior values or defaults + Object.keys(store[deviceID]).forEach((key) => { + if (msg.data.data.hasOwnProperty(key)) { + store[deviceID][key] = msg.data.data[key]; + } + }); + + // if raw measurement comes in, apply stored/default multiplier and divisor + if (msg.data.data.hasOwnProperty('rmsVoltage')) { + result.voltage = (msg.data.data['rmsVoltage'] + * store[deviceID].acVoltageMultiplier + / store[deviceID].acVoltageDivisor).toFixed(2); + } + + if (msg.data.data.hasOwnProperty('rmsCurrent')) { + result.current = (msg.data.data['rmsCurrent'] + * store[deviceID].acCurrentMultiplier + / store[deviceID].acCurrentDivisor).toFixed(2); + } + + if (msg.data.data.hasOwnProperty('activePower')) { + result.power = (msg.data.data['activePower'] + * store[deviceID].acPowerMultiplier + / store[deviceID].acPowerDivisor).toFixed(2); + } + + return result; + }, + }, + STS_PRS_251_presence: { + cid: 'genBinaryInput', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const useOptionsTimeout = options && options.hasOwnProperty('presence_timeout'); + const timeout = useOptionsTimeout ? options.presence_timeout : 100; // 100 seconds by default + const deviceID = msg.endpoints[0].device.ieeeAddr; + + // Stop existing timer because presence is detected and set a new one. + if (store.hasOwnProperty(deviceID)) { + clearTimeout(store[deviceID]); + store[deviceID] = null; + } + + store[deviceID] = setTimeout(() => { + publish({presence: false}); + store[deviceID] = null; + }, timeout * 1000); + + return {presence: true}; + }, + }, + generic_batteryvoltage_3000_2500: { + cid: 'genPowerCfg', + type: ['attReport', 'readRsp'], + 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, + }; + }, + }, + STS_PRS_251_beeping: { + cid: 'genIdentify', + type: 'devChange', + convert: (model, msg, publish, options) => { + return {action: 'beeping'}; + }, + }, + _324131092621_notification: { + cid: 'manuSpecificPhilips', + type: 'cmdHueNotification', + convert: (model, msg, publish, options) => { + const multiplePressTimeout = options && options.hasOwnProperty('multiple_press_timeout') ? + options.multiple_press_timeout : 0.25; + + const getPayload = function(button, pressType, pressDuration, pressCounter, + brightnessSend, brightnessValue) { + const payLoad = {}; + payLoad['action'] = `${button}-${pressType}`; + payLoad['duration'] = pressDuration / 1000; + if (pressCounter) { + payLoad['counter'] = pressCounter; + } + if (brightnessSend) { + payLoad['brightness'] = store[deviceID].brightnessValue; + } + return payLoad; + }; + + const deviceID = msg.endpoints[0].device.ieeeAddr; + let button = null; + switch (msg.data.data['button']) { + case 1: + button = 'on'; + break; + case 2: + button = 'up'; + break; + case 3: + button = 'down'; + break; + case 4: + button = 'off'; + break; + } + let type = null; + switch (msg.data.data['type']) { + case 0: + type = 'press'; + break; + case 1: + type = 'hold'; + break; + case 2: + case 3: + type = 'release'; + break; + } + + const brightnessEnabled = options && options.hasOwnProperty('send_brightess') ? + options.send_brightess : true; + const brightnessSend = brightnessEnabled && button && (button == 'up' || button == 'down'); + + // Initialize store + if (!store[deviceID]) { + store[deviceID] = {pressStart: null, pressType: null, + delayedButton: null, delayedBrightnessSend: null, delayedType: null, + delayedCounter: 0, delayedTimerStart: null, delayedTimer: null}; + if (brightnessEnabled) { + store[deviceID].brightnessValue = 255; + store[deviceID].brightnessSince = null; + store[deviceID].brightnessDirection = null; + } + } + + if (button && type) { + if (type == 'press') { + store[deviceID].pressStart = Date.now(); + store[deviceID].pressType = 'press'; + if (brightnessSend) { + const newValue = store[deviceID].brightnessValue + (button === 'up' ? 50 : -50); + store[deviceID].brightnessValue = numberWithinRange(newValue, 0, 255); + } + } else if (type == 'hold') { + store[deviceID].pressType = 'hold'; + if (brightnessSend) { + holdUpdateBrightness324131092621(deviceID); + store[deviceID].brightnessSince = Date.now(); + store[deviceID].brightnessDirection = button; + } + } else if (type == 'release') { + if (brightnessSend) { + store[deviceID].brightnessSince = null; + store[deviceID].brightnessDirection = null; + } + if (store[deviceID].pressType == 'hold') { + store[deviceID].pressType += '-release'; + } + } + if (type == 'press') { + // pressed different button + if (store[deviceID].delayedTimer && (store[deviceID].delayedButton != button)) { + clearTimeout(store[deviceID].delayedTimer); + store[deviceID].delayedTimer = null; + publish(getPayload(store[deviceID].delayedButton, + store[deviceID].delayedType, 0, store[deviceID].delayedCounter, + store[deviceID].delayedBrightnessSend, + store[deviceID].brightnessValue)); + } + } else { + // released after press: start timer + if (store[deviceID].pressType == 'press') { + if (store[deviceID].delayedTimer) { + clearTimeout(store[deviceID].delayedTimer); + store[deviceID].delayedTimer = null; + } else { + store[deviceID].delayedCounter = 0; + } + store[deviceID].delayedButton = button; + store[deviceID].delayedBrightnessSend = brightnessSend; + store[deviceID].delayedType = store[deviceID].pressType; + store[deviceID].delayedCounter++; + store[deviceID].delayedTimerStart = Date.now(); + store[deviceID].delayedTimer = setTimeout(() => { + publish(getPayload(store[deviceID].delayedButton, + store[deviceID].delayedType, 0, store[deviceID].delayedCounter, + store[deviceID].delayedBrightnessSend, + store[deviceID].brightnessValue)); + store[deviceID].delayedTimer = null; + }, multiplePressTimeout * 1000); + } else { + const pressDuration = + (store[deviceID].pressType == 'hold' || store[deviceID].pressType == 'hold-release') ? + Date.now() - store[deviceID].pressStart : 0; + return getPayload(button, + store[deviceID].pressType, pressDuration, null, brightnessSend, + store[deviceID].brightnessValue); + } + } + } + + return {}; + }, + }, + generic_battery: { + cid: 'genPowerCfg', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + if (msg.data.data.hasOwnProperty('batteryPercentageRemaining')) { + return {battery: msg.data.data['batteryPercentageRemaining']}; + } + }, + }, + generic_battery_change: { + cid: 'genPowerCfg', + type: 'devChange', + convert: (model, msg, publish, options) => { + if (msg.data.data.hasOwnProperty('batteryPercentageRemaining')) { + return {battery: msg.data.data['batteryPercentageRemaining']}; + } + }, + }, + generic_battery_remaining: { + cid: 'genPowerCfg', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + if (msg.data.data.hasOwnProperty('batteryPercentageRemaining')) { + return {battery: precisionRound(msg.data.data['batteryPercentageRemaining'] / 2, 2)}; + } + }, + }, + generic_battery_voltage: { + cid: 'genPowerCfg', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + if (msg.data.data.hasOwnProperty('batteryVoltage')) { + return {voltage: msg.data.data['batteryVoltage'] / 100}; + } + }, + }, + cmd_move: { + cid: 'genLevelCtrl', + type: 'cmdMove', + convert: (model, msg, publish, options) => { + ictcg1(model, msg, publish, options, 'move'); + const direction = msg.data.data.movemode === 1 ? 'left' : 'right'; + return {action: `rotate_${direction}`, rate: msg.data.data.rate}; + }, + }, + cmd_move_with_onoff: { + cid: 'genLevelCtrl', + type: 'cmdMoveWithOnOff', + convert: (model, msg, publish, options) => { + ictcg1(model, msg, publish, options, 'move'); + const direction = msg.data.data.movemode === 1 ? 'left' : 'right'; + return {action: `rotate_${direction}`, rate: msg.data.data.rate}; + }, + }, + cmd_stop: { + cid: 'genLevelCtrl', + type: 'cmdStop', + convert: (model, msg, publish, options) => { + ictcg1(model, msg, publish, options, 'stop'); + return {action: `rotate_stop`}; + }, + }, + cmd_stop_with_onoff: { + cid: 'genLevelCtrl', + type: 'cmdStopWithOnOff', + convert: (model, msg, publish, options) => { + ictcg1(model, msg, publish, options, 'stop'); + return {action: `rotate_stop`}; + }, + }, + cmd_move_to_level_with_onoff: { + cid: 'genLevelCtrl', + type: 'cmdMoveToLevelWithOnOff', + convert: (model, msg, publish, options) => { + ictcg1(model, msg, publish, options, 'level'); + const direction = msg.data.data.level === 0 ? 'left' : 'right'; + return {action: `rotate_${direction}_quick`, level: msg.data.data.level}; + }, + }, + iris_3210L_power: { + cid: 'haElectricalMeasurement', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + return {power: msg.data.data['activePower'] / 10.0}; + }, + }, + iris_3320L_contact: { + cid: 'ssIasZone', + type: 'statusChange', + convert: (model, msg, publish, options) => { + return {contact: msg.data.zoneStatus === 36}; + }, + }, + nue_power_state: { + cid: 'genOnOff', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const ep = msg.endpoints[0]; + const button = getKey(model.ep(ep.device), ep.epId); + if (button) { + const payload = {}; + payload[`state_${button}`] = msg.data.data['onOff'] === 1 ? 'ON' : 'OFF'; + return payload; + } + }, + }, + generic_state_multi_ep: { + cid: 'genOnOff', + type: ['attReport', 'readRsp'], + 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; + }, + }, + RZHAC_4256251_power: { + cid: 'haElectricalMeasurement', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + return { + power: msg.data.data['activePower'], + current: msg.data.data['rmsCurrent'], + voltage: msg.data.data['rmsVoltage'], + }; + }, + }, + ias_zone_motion_dev_change: { + cid: 'ssIasZone', + type: 'devChange', + convert: (model, msg, publish, options) => { + if (msg.data.data.zoneType === 0x000D) { // type 0x000D = motion sensor + const zoneStatus = msg.data.data.zoneStatus; + return { + occupancy: (zoneStatus & 1<<1) > 0, // Bit 1 = Alarm 2: 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) + }; + } + }, + }, + ias_zone_motion_status_change: { + cid: 'ssIasZone', + type: 'statusChange', + convert: (model, msg, publish, options) => { + const zoneStatus = msg.data.zoneStatus; + return { + occupancy: (zoneStatus & 1<<1) > 0, // Bit 1 = Alarm 2: 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_occupancy_status_change: { + cid: 'ssIasZone', + type: 'statusChange', + convert: (model, msg, publish, options) => { + const zoneStatus = msg.data.zoneStatus; + 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', + convert: (model, msg, publish, options) => { + const zoneStatus = msg.data.zoneStatus; + 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) + }; + }, + }, + ias_contact_dev_change: { + cid: 'ssIasZone', + type: 'devChange', + convert: (model, msg, publish, options) => { + const zoneStatus = msg.data.zoneStatus; + return { + contact: !((zoneStatus & 1) > 0), + }; + }, + }, + ias_contact_status_change: { + cid: 'ssIasZone', + type: 'statusChange', + convert: (model, msg, publish, options) => { + const zoneStatus = msg.data.zoneStatus; + return { + contact: !((zoneStatus & 1) > 0), + }; + }, + }, + brightness_report: { + cid: 'genLevelCtrl', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + if (msg.data.data.hasOwnProperty('currentLevel')) { + return {brightness: msg.data.data['currentLevel']}; + } + }, + }, + smartsense_multi: { + cid: 'ssIasZone', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const zoneStatus = msg.data.data.zoneStatus; + return { + contact: !(zoneStatus & 1), // Bit 1 = Contact + // Bit 5 = Currently always set? + }; + }, + }, + st_leak: { + cid: 'ssIasZone', + type: 'attReport', + convert: (model, msg, publish, options) => { + const zoneStatus = msg.data.data.zoneStatus; + return { + water_leak: (zoneStatus & 1) > 0, // Bit 1 = wet + }; + }, + }, + st_leak_change: { + cid: 'ssIasZone', + type: 'devChange', + convert: (model, msg, publish, options) => { + const zoneStatus = msg.data.data.zoneStatus; + return { + water_leak: (zoneStatus & 1) > 0, // Bit 1 = wet + }; + }, + }, + st_contact_status_change: { + cid: 'ssIasZone', + type: 'statusChange', + convert: (model, msg, publish, options) => { + const zoneStatus = msg.data.zoneStatus; + return { + contact: !((zoneStatus & 1) > 0), // Bit 0 = Alarm: Contact detection + battery_low: (zoneStatus & 1<<3) > 0, // Bit 3 = Battery LOW indicator + }; + }, + }, + st_button_state: { + cid: 'ssIasZone', + type: 'statusChange', + convert: (model, msg, publish, options) => { + const buttonStates = { + 0: 'off', + 1: 'single', + 2: 'double', + 3: 'hold', + }; + + if (msg.data.hasOwnProperty('data')) { + const zoneStatus = msg.data.data.zoneStatus; + return {click: buttonStates[zoneStatus]}; + } else { + const zoneStatus = msg.data.zoneStatus; + return {click: buttonStates[zoneStatus]}; + } + }, + }, + CTR_U_brightness_updown_click: { + cid: 'genLevelCtrl', + type: 'cmdStep', + convert: (model, msg, publish, options) => { + const deviceID = msg.endpoints[0].device.ieeeAddr; + const direction = msg.data.data.stepmode === 1 ? 'down' : 'up'; + + // Save last direction for release event + if (!store[deviceID]) { + store[deviceID] = {}; + } + store[deviceID].direction = direction; + + return { + action: `brightness_${direction}_click`, + step_size: msg.data.data.stepsize, + transition_time: msg.data.data.transtime, + }; + }, + }, + CTR_U_brightness_updown_hold: { + cid: 'genLevelCtrl', + type: 'cmdMove', + convert: (model, msg, publish, options) => { + const deviceID = msg.endpoints[0].device.ieeeAddr; + const direction = msg.data.data.movemode === 1 ? 'down' : 'up'; + + // Save last direction for release event + if (!store[deviceID]) { + store[deviceID] = {}; + } + store[deviceID].direction = direction; + + return { + action: `brightness_${direction}_hold`, + rate: msg.data.data.rate, + }; + }, + }, + CTR_U_brightness_updown_release: { + cid: 'genLevelCtrl', + type: 'cmdStop', + convert: (model, msg, publish, options) => { + const deviceID = msg.endpoints[0].device.ieeeAddr; + if (!store[deviceID]) { + return null; + } + + const direction = store[deviceID].direction; + return { + action: `brightness_${direction}_release`, + }; + }, + }, + CTR_U_scene: { + cid: 'genScenes', + type: 'cmdRecall', + convert: (model, msg, publish, options) => { + return {click: `scene_${msg.data.data.groupid}_${msg.data.data.sceneid}`}; + }, + }, + thermostat_dev_change: { + cid: 'hvacThermostat', + type: 'devChange', + convert: (model, msg, publish, options) => { + const result = {}; + if (typeof msg.data.data['localTemp'] == 'number') { + result.local_temperature = precisionRound(msg.data.data['localTemp'], 2) / 100; + } + if (typeof msg.data.data['localTemperatureCalibration'] == 'number') { + result.local_temperature_calibration = + precisionRound(msg.data.data['localTemperatureCalibration'], 2) / 10; + } + if (typeof msg.data.data['occupancy'] == 'number') { + result.occupancy = msg.data.data['occupancy']; + } + if (typeof msg.data.data['occupiedHeatingSetpoint'] == 'number') { + result.occupied_heating_setpoint = + precisionRound(msg.data.data['occupiedHeatingSetpoint'], 2) / 100; + } + if (typeof msg.data.data['unoccupiedHeatingSetpoint'] == 'number') { + result.unoccupied_heating_setpoint = + precisionRound(msg.data.data['unoccupiedHeatingSetpoint'], 2) / 100; + } + if (typeof msg.data.data['weeklySchedule'] == 'number') { + result.weekly_schedule = msg.data.data['weeklySchedule']; + } + if (typeof msg.data.data['setpointChangeAmount'] == 'number') { + result.setpoint_change_amount = msg.data.data['setpointChangeAmount'] / 100; + } + if (typeof msg.data.data['setpointChangeSource'] == 'number') { + result.setpoint_change_source = msg.data.data['setpointChangeSource']; + } + if (typeof msg.data.data['setpointChangeSourceTimeStamp'] == 'number') { + result.setpoint_change_source_timestamp = msg.data.data['setpointChangeSourceTimeStamp']; + } + if (typeof msg.data.data['remoteSensing'] == 'number') { + result.remote_sensing = msg.data.data['remoteSensing']; + } + const ctrl = msg.data.data['ctrlSeqeOfOper']; + if (typeof ctrl == 'number' && common.thermostatControlSequenceOfOperations.hasOwnProperty(ctrl)) { + result.control_sequence_of_operation = common.thermostatControlSequenceOfOperations[ctrl]; + } + const smode = msg.data.data['systemMode']; + if (typeof smode == 'number' && common.thermostatSystemModes.hasOwnProperty(smode)) { + result.system_mode = common.thermostatSystemModes[smode]; + } + const rmode = msg.data.data['runningMode']; + if (typeof rmode == 'number' && common.thermostatSystemModes.hasOwnProperty(rmode)) { + result.running_mode = common.thermostatSystemModes[rmode]; + } + const state = msg.data.data['runningState']; + if (typeof state == 'number' && common.thermostatRunningStates.hasOwnProperty(state)) { + result.running_state = common.thermostatRunningStates[state]; + } + if (typeof msg.data.data['pIHeatingDemand'] == 'number') { + result.pi_heating_demand = msg.data.data['pIHeatingDemand']; + } + return result; + }, + }, + thermostat_att_report: { + cid: 'hvacThermostat', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const result = {}; + if (typeof msg.data.data['localTemp'] == 'number') { + result.local_temperature = precisionRound(msg.data.data['localTemp'], 2) / 100; + } + if (typeof msg.data.data['localTemperatureCalibration'] == 'number') { + result.local_temperature_calibration = + precisionRound(msg.data.data['localTemperatureCalibration'], 2) / 10; + } + if (typeof msg.data.data['occupancy'] == 'number') { + result.occupancy = msg.data.data['occupancy']; + } + if (typeof msg.data.data['occupiedHeatingSetpoint'] == 'number') { + result.occupied_heating_setpoint = + precisionRound(msg.data.data['occupiedHeatingSetpoint'], 2) / 100; + } + if (typeof msg.data.data['unoccupiedHeatingSetpoint'] == 'number') { + result.unoccupied_heating_setpoint = + precisionRound(msg.data.data['unoccupiedHeatingSetpoint'], 2) / 100; + } + if (typeof msg.data.data['weeklySchedule'] == 'number') { + result.weekly_schedule = msg.data.data['weeklySchedule']; + } + if (typeof msg.data.data['setpointChangeAmount'] == 'number') { + result.setpoint_change_amount = msg.data.data['setpointChangeAmount'] / 100; + } + if (typeof msg.data.data['setpointChangeSource'] == 'number') { + result.setpoint_change_source = msg.data.data['setpointChangeSource']; + } + if (typeof msg.data.data['setpointChangeSourceTimeStamp'] == 'number') { + result.setpoint_change_source_timestamp = msg.data.data['setpointChangeSourceTimeStamp']; + } + if (typeof msg.data.data['remoteSensing'] == 'number') { + result.remote_sensing = msg.data.data['remoteSensing']; + } + const ctrl = msg.data.data['ctrlSeqeOfOper']; + if (typeof ctrl == 'number' && common.thermostatControlSequenceOfOperations.hasOwnProperty(ctrl)) { + result.control_sequence_of_operation = common.thermostatControlSequenceOfOperations[ctrl]; + } + const smode = msg.data.data['systemMode']; + if (typeof smode == 'number' && common.thermostatSystemModes.hasOwnProperty(smode)) { + result.system_mode = common.thermostatSystemModes[smode]; + } + const rmode = msg.data.data['runningMode']; + if (typeof rmode == 'number' && common.thermostatSystemModes.hasOwnProperty(rmode)) { + result.running_mode = common.thermostatSystemModes[rmode]; + } + const state = msg.data.data['runningState']; + if (typeof state == 'number' && common.thermostatRunningStates.hasOwnProperty(state)) { + result.running_state = common.thermostatRunningStates[state]; + } + if (typeof msg.data.data['pIHeatingDemand'] == 'number') { + result.pi_heating_demand = msg.data.data['pIHeatingDemand']; + } + return result; + }, + }, + eurotronic_thermostat_dev_change: { + cid: 'hvacThermostat', + type: 'devChange', + convert: (model, msg, publish, options) => { + const result = {}; + if (typeof msg.data.data[0x4003] == 'number') { + result.current_heating_setpoint = + precisionRound(msg.data.data[0x4003], 2) / 100; + } + if (typeof msg.data.data[0x4008] == 'number') { + result.eurotronic_system_mode = msg.data.data[0x4008]; + } + if (typeof msg.data.data[0x4002] == 'number') { + result.eurotronic_error_status = msg.data.data[0x4002]; + } + if (typeof msg.data.data[0x4000] == 'number') { + result.eurotronic_trv_mode = msg.data.data[0x4000]; + } + if (typeof msg.data.data[0x4001] == 'number') { + result.eurotronic_valve_position = msg.data.data[0x4001]; + } + return result; + }, + }, + tint404011_on: { + cid: 'genOnOff', + type: 'cmdOn', + convert: (model, msg, publish, options) => { + return {action: 'on'}; + }, + }, + tint404011_off: { + cid: 'genOnOff', + type: 'cmdOff', + convert: (model, msg, publish, options) => { + return {action: 'off'}; + }, + }, + tint404011_brightness_updown_click: { + cid: 'genLevelCtrl', + type: 'cmdStep', + convert: (model, msg, publish, options) => { + const direction = msg.data.data.stepmode === 1 ? 'down' : 'up'; + return { + action: `brightness_${direction}_click`, + step_size: msg.data.data.stepsize, + transition_time: msg.data.data.transtime, + }; + }, + }, + tint404011_brightness_updown_hold: { + cid: 'genLevelCtrl', + type: 'cmdMove', + convert: (model, msg, publish, options) => { + const deviceID = msg.endpoints[0].device.ieeeAddr; + const direction = msg.data.data.movemode === 1 ? 'down' : 'up'; + + // Save last direction for release event + if (!store[deviceID]) { + store[deviceID] = {}; + } + store[deviceID].movemode = direction; + + return { + action: `brightness_${direction}_hold`, + rate: msg.data.data.rate, + }; + }, + }, + tint404011_brightness_updown_release: { + cid: 'genLevelCtrl', + type: 'cmdStop', + convert: (model, msg, publish, options) => { + const deviceID = msg.endpoints[0].device.ieeeAddr; + if (!store[deviceID]) { + return null; + } + + const direction = store[deviceID].movemode; + return { + action: `brightness_${direction}_release`, + }; + }, + }, + tint404011_scene: { + cid: 'genBasic', + type: 'cmdWrite', + convert: (model, msg, publish, options) => { + return {action: `scene${msg.data.data[0].attrData}`}; + }, + }, + tint404011_move_to_color_temp: { + cid: 'lightingColorCtrl', + type: 'cmdMoveToColorTemp', + convert: (model, msg, publish, options) => { + return { + action: `color_temp`, + action_color_temperature: msg.data.data.colortemp, + transition_time: msg.data.data.transtime, + }; + }, + }, + tint404011_move_to_color: { + cid: 'lightingColorCtrl', + type: 'cmdMoveToColor', + convert: (model, msg, publish, options) => { + return { + action_color: { + x: precisionRound(msg.data.data.colorx / 65535, 3), + y: precisionRound(msg.data.data.colory / 65535, 3), + }, + action: 'color_wheel', + transition_time: msg.data.data.transtime, + }; + }, + }, + cmdToggle: { + cid: 'genOnOff', + type: 'cmdToggle', + convert: (model, msg, publish, options) => { + return {action: 'toggle'}; + }, + }, + E1524_hold: { + cid: 'genLevelCtrl', + type: 'cmdMoveToLevelWithOnOff', + convert: (model, msg, publish, options) => { + return {action: 'toggle_hold'}; + }, + }, + E1524_arrow_click: { + cid: 'genScenes', + type: 'cmdTradfriArrowSingle', + convert: (model, msg, publish, options) => { + if (msg.data.data.value === 2) { + // This is send on toggle hold, ignore it as a toggle_hold is already handled above. + return; + } + + const direction = msg.data.data.value === 257 ? 'left' : 'right'; + return {action: `arrow_${direction}_click`}; + }, + }, + E1524_arrow_hold: { + cid: 'genScenes', + type: 'cmdTradfriArrowHold', + convert: (model, msg, publish, options) => { + const direction = msg.data.data.value === 3329 ? 'left' : 'right'; + store[msg.endpoints[0].device.ieeeAddr] = direction; + return {action: `arrow_${direction}_hold`}; + }, + }, + E1524_arrow_release: { + cid: 'genScenes', + type: 'cmdTradfriArrowRelease', + convert: (model, msg, publish, options) => { + const direction = store[msg.endpoints[0].device.ieeeAddr]; + if (direction) { + delete store[msg.endpoints[0].device.ieeeAddr]; + return {action: `arrow_${direction}_release`, duration: msg.data.data.value / 1000}; + } + }, + }, + E1524_brightness_up_click: { + cid: 'genLevelCtrl', + type: 'cmdStepWithOnOff', + convert: (model, msg, publish, options) => { + return {action: `brightness_up_click`}; + }, + }, + E1524_brightness_down_click: { + cid: 'genLevelCtrl', + type: 'cmdStep', + convert: (model, msg, publish, options) => { + return {action: `brightness_down_click`}; + }, + }, + E1524_brightness_up_hold: { + cid: 'genLevelCtrl', + type: 'cmdMoveWithOnOff', + convert: (model, msg, publish, options) => { + return {action: `brightness_up_hold`}; + }, + }, + E1524_brightness_up_release: { + cid: 'genLevelCtrl', + type: 'cmdStopWithOnOff', + convert: (model, msg, publish, options) => { + return {action: `brightness_up_release`}; + }, + }, + E1524_brightness_down_hold: { + cid: 'genLevelCtrl', + type: 'cmdMove', + convert: (model, msg, publish, options) => { + return {action: `brightness_down_hold`}; + }, + }, + E1524_brightness_down_release: { + cid: 'genLevelCtrl', + type: 'cmdStop', + convert: (model, msg, publish, options) => { + return {action: `brightness_down_release`}; + }, + }, + livolo_switch_dev_change: { + cid: 'genOnOff', + type: 'devChange', + convert: (model, msg, publish, options) => { + const status = msg.data.data.onOff; + const payload = {}; + payload['state_left'] = status & 1 ? 'ON' : 'OFF'; + payload['state_right'] = status & 2 ? 'ON' : 'OFF'; + if (msg.endpoints[0].hasOwnProperty('linkquality')) { + payload['linkquality'] = msg.endpoints[0].linkquality; + } + return payload; + }, + }, + eria_81825_on: { + cid: 'genOnOff', + type: 'cmdOn', + convert: (model, msg, publish, options) => { + return {action: 'on'}; + }, + }, + eria_81825_off: { + cid: 'genOnOff', + type: 'cmdOff', + convert: (model, msg, publish, options) => { + return {action: 'off'}; + }, + }, + eria_81825_updown: { + cid: 'genLevelCtrl', + type: 'cmdStep', + convert: (model, msg, publish, options) => { + const direction = msg.data.data.stepmode === 0 ? 'up' : 'down'; + return {action: `${direction}`}; + }, + }, + ZYCT202_on: { + cid: 'genOnOff', + type: 'cmdOn', + convert: (model, msg, publish, options) => { + return {action: 'on', action_group: msg.groupid}; + }, + }, + ZYCT202_off: { + cid: 'genOnOff', + type: 'cmdOffWithEffect', + convert: (model, msg, publish, options) => { + return {action: 'off', action_group: msg.groupid}; + }, + }, + ZYCT202_stop: { + cid: 'genLevelCtrl', + type: 'cmdStop', + convert: (model, msg, publish, options) => { + return {action: 'stop', action_group: msg.groupid}; + }, + }, + ZYCT202_up_down: { + cid: 'genLevelCtrl', + type: 'cmdMove', + convert: (model, msg, publish, options) => { + const value = msg.data.data['movemode']; + let action = null; + if (value === 0) action = {'action': 'up-press', 'action_group': msg.groupid}; + else if (value === 1) action = {'action': 'down-press', 'action_group': msg.groupid}; + return action ? action : null; + }, + }, + cover_position: { + cid: 'genLevelCtrl', + type: 'devChange', + convert: (model, msg, publish, options) => { + const currentLevel = msg.data.data['currentLevel']; + const position = Math.round(Number(currentLevel) / 2.55).toString(); + const state = position > 0 ? 'OPEN' : 'CLOSE'; + return {state: state, position: position}; + }, + }, + cover_position_report: { + cid: 'genLevelCtrl', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const currentLevel = msg.data.data['currentLevel']; + const position = Math.round(Number(currentLevel) / 2.55).toString(); + const state = position > 0 ? 'OPEN' : 'CLOSE'; + return {state: state, position: position}; + }, + }, + cover_state_report: { + cid: 'genOnOff', + type: 'attReport', + convert: (model, msg, publish, options) => { + if (msg.data.data.hasOwnProperty('onOff')) { + return {state: msg.data.data['onOff'] === 1 ? 'OPEN' : 'CLOSE'}; + } + }, + }, + cover_state_change: { + cid: 'genOnOff', + type: 'devChange', + convert: (model, msg, publish, options) => { + if (msg.data.data.hasOwnProperty('onOff')) { + return {state: msg.data.data['onOff'] === 1 ? 'OPEN' : 'CLOSE'}; + } + }, + }, + keen_home_smart_vent_pressure: { + cid: 'msPressureMeasurement', + type: 'devChange', + convert: (model, msg, publish, options) => { + // '{"cid":"msPressureMeasurement","data":{"32":990494}}' + const pressure = parseFloat(msg.data.data['32']) / 1000.0; + return {pressure: precisionRoundOptions(pressure, options, 'pressure')}; + }, + }, + keen_home_smart_vent_pressure_report: { + cid: 'msPressureMeasurement', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + // '{"cid":"msPressureMeasurement","data":{"32":990494}}' + const pressure = parseFloat(msg.data.data['32']) / 1000.0; + return {pressure: precisionRoundOptions(pressure, options, 'pressure')}; + }, + }, + AC0251100NJ_cmdOn: { + cid: 'genOnOff', + type: 'cmdOn', + convert: (model, msg, publish, options) => { + return {action: 'up'}; + }, + }, + AC0251100NJ_cmdOff: { + cid: 'genOnOff', + type: 'cmdOff', + convert: (model, msg, publish, options) => { + return {action: 'down'}; + }, + }, + AC0251100NJ_cmdMoveWithOnOff: { + cid: 'genLevelCtrl', + type: 'cmdMoveWithOnOff', + convert: (model, msg, publish, options) => { + return {action: 'up_hold'}; + }, + }, + AC0251100NJ_cmdStop: { + cid: 'genLevelCtrl', + type: 'cmdStop', + convert: (model, msg, publish, options) => { + const map = { + 1: 'up_release', + 2: 'down_release', + }; + + return {action: map[msg.endpoints[0].epId]}; + }, + }, + AC0251100NJ_cmdMove: { + cid: 'genLevelCtrl', + type: 'cmdMove', + convert: (model, msg, publish, options) => { + return {action: 'down_hold'}; + }, + }, + AC0251100NJ_cmdMoveHue: { + cid: 'lightingColorCtrl', + type: 'cmdMoveHue', + convert: (model, msg, publish, options) => { + if (msg.data.data.movemode === 0) { + return {action: 'circle_release'}; + } + }, + }, + AC0251100NJ_cmdMoveToSaturation: { + cid: 'lightingColorCtrl', + type: 'cmdMoveToSaturation', + convert: (model, msg, publish, options) => { + return {action: 'circle_hold'}; + }, + }, + AC0251100NJ_cmdMoveToLevelWithOnOff: { + cid: 'genLevelCtrl', + type: 'cmdMoveToLevelWithOnOff', + convert: (model, msg, publish, options) => { + return {action: 'circle_click'}; + }, + }, + AC0251100NJ_cmdMoveToColorTemp: { + cid: 'lightingColorCtrl', + type: 'cmdMoveToColorTemp', + convert: (model, msg, publish, options) => null, + }, + visonic_contact: { + cid: 'ssIasZone', + type: 'statusChange', + convert: (model, msg, publish, options) => { + const zoneStatus = msg.data.zoneStatus; + return { + contact: !((zoneStatus & 1) > 0), // Bit 1 = Alarm: Contact detection + tamper: (zoneStatus & 1<<2) > 0, // Bit 3 = Tamper status + battery_low: (zoneStatus & 1<<3) > 0, // Bit 4 = Battery LOW indicator + }; + }, + }, + OJBCR701YZ_statuschange: { + cid: 'ssIasZone', + type: 'statusChange', + convert: (model, msg, publish, options) => { + const {zoneStatus} = msg.data; + return { + carbon_monoxide: (zoneStatus & 1) > 0, // Bit 0 = Alarm 1: Carbon Monoxide (CO) + gas: (zoneStatus & 1 << 1) > 0, // Bit 1 = Alarm 2: Gas (CH4) + tamper: (zoneStatus & 1 << 2) > 0, // Bit 2 = Tamper + battery_low: (zoneStatus & 1 << 3) > 0, // Bit 3 = Low battery alarm + trouble: (zoneStatus & 1 << 6) > 0, // Bit 6 = Trouble/Failure + ac_connected: !((zoneStatus & 1 << 7) > 0), // Bit 7 = AC Connected + test: (zoneStatus & 1 << 8) > 0, // Bit 8 = Self test + battery_defect: (zoneStatus & 1 << 9) > 0, // Bit 9 = Battery Defect + }; + }, + }, + closuresWindowCovering_report: { + cid: 'closuresWindowCovering', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + return {position: msg.data.data.currentPositionLiftPercentage}; + }, + }, + generic_fan_mode: { + cid: 'hvacFanCtrl', + type: 'attReport', + convert: (model, msg, publish, options) => { + const key = getKey(common.fanMode, msg.data.data.fanMode); + return {fan_mode: key, fan_state: key === 'off' ? 'OFF' : 'ON'}; + }, + }, + GIRA2430_scene_click: { + cid: 'genScenes', + type: 'cmdRecall', + convert: (model, msg, publish, options) => { + return { + action: `select_${msg.data.data.sceneid}`, + }; + }, + }, + GIRA2430_on_click: { + cid: 'genOnOff', + type: 'cmdOn', + convert: (model, msg, publish, options) => { + return {action: 'on'}; + }, + }, + GIRA2430_off_click: { + cid: 'genOnOff', + type: 'cmdOffWithEffect', + convert: (model, msg, publish, options) => { + return {action: 'off'}; + }, + }, + GIRA2430_down_hold: { + cid: 'genLevelCtrl', + type: 'cmdStep', + convert: (model, msg, publish, options) => { + return { + action: 'down', + step_mode: msg.data.data.stepmode, + step_size: msg.data.data.stepsize, + transition_time: msg.data.data.transtime, + }; + }, + }, + GIRA2430_up_hold: { + cid: 'genLevelCtrl', + type: 'cmdStepWithOnOff', + convert: (model, msg, publish, options) => { + return { + action: 'up', + step_mode: msg.data.data.stepmode, + step_size: msg.data.data.stepsize, + transition_time: msg.data.data.transtime, + }; + }, + }, + GIRA2430_stop: { + cid: 'genLevelCtrl', + type: 'cmdStop', + convert: (model, msg, publish, options) => { + return { + action: 'stop', + }; + }, + }, + ZGRC013_cmdOn: { + cid: 'genOnOff', + type: 'cmdOn', + convert: (model, msg, publish, options) => { + const button = msg.endpoints[0].epId; + if (button) { + return {click: `${button}_on`}; + } + }, + }, + ZGRC013_cmdOff: { + cid: 'genOnOff', + type: 'cmdOff', + convert: (model, msg, publish, options) => { + const button = msg.endpoints[0].epId; + if (button) { + return {click: `${button}_off`}; + } + }, + }, + ZGRC013_brightness: { + cid: 'genLevelCtrl', + type: 'cmdMove', + convert: (model, msg, publish, options) => { + const button = msg.endpoints[0].epId; + const direction = msg.data.data.movemode == 0 ? 'up' : 'down'; + if (button) { + return {click: `${button}_${direction}`}; + } + }, + }, + ZGRC013_brightness_onoff: { + cid: 'genLevelCtrl', + type: 'cmdMoveWithOnOff', + convert: (model, msg, publish, options) => { + const button = msg.endpoints[0].epId; + const direction = msg.data.data.movemode == 0 ? 'up' : 'down'; + if (button) { + return {click: `${button}_${direction}`}; + } + }, + }, + ZGRC013_brightness_stop: { + cid: 'genLevelCtrl', + type: 'cmdStopWithOnOff', + convert: (model, msg, publish, options) => { + const button = msg.endpoints[0].epId; + if (button) { + return {click: `${button}_stop`}; + } + }, + }, + ZGRC013_scene: { + cid: 'genScenes', + type: 'cmdRecall', + convert: (model, msg, publish, options) => { + return {click: `scene_${msg.data.data.groupid}_${msg.data.data.sceneid}`}; + }, + }, + SZ_ESW01_AU_power: { + cid: 'seMetering', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + if (msg.data.data.hasOwnProperty('instantaneousDemand')) { + return {power: precisionRound(msg.data.data['instantaneousDemand'] / 1000, 2)}; + } + }, + }, + meazon_meter: { + cid: 'seMetering', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => { + const result = {}; + // typo on property name to stick with zcl definition + if (msg.data.data.hasOwnProperty('inletTempreature')) { + result.inletTemperature = precisionRound(msg.data.data['inletTempreature'], 2); + } + + if (msg.data.data.hasOwnProperty('status')) { + result.status = precisionRound(msg.data.data['status'], 2); + } + + if (msg.data.data.hasOwnProperty('8192')) { + result.linefrequency = precisionRound((parseFloat(msg.data.data['8192'])) / 100.0, 2); + } + + if (msg.data.data.hasOwnProperty('8193')) { + result.power = precisionRound(msg.data.data['8193'], 2); + } + + if (msg.data.data.hasOwnProperty('8196')) { + result.voltage = precisionRound(msg.data.data['8196'], 2); + } + + if (msg.data.data.hasOwnProperty('8213')) { + result.voltage = precisionRound(msg.data.data['8213'], 2); + } + + if (msg.data.data.hasOwnProperty('8199')) { + result.current = precisionRound(msg.data.data['8199'], 2); + } + + if (msg.data.data.hasOwnProperty('8216')) { + result.current = precisionRound(msg.data.data['8216'], 2); + } + + if (msg.data.data.hasOwnProperty('8202')) { + result.reactivepower = precisionRound(msg.data.data['8202'], 2); + } + + if (msg.data.data.hasOwnProperty('12288')) { + result.energyconsumed = precisionRound(msg.data.data['12288'], 2); + } + + if (msg.data.data.hasOwnProperty('12291')) { + result.energyproduced = precisionRound(msg.data.data['12291'], 2); + } + + if (msg.data.data.hasOwnProperty('12294')) { + result.reactivesummation = precisionRound(msg.data.data['12294'], 2); + } + + if (msg.data.data.hasOwnProperty('16408')) { + result.measureserial = precisionRound(msg.data.data['16408'], 2); + } + + return result; + }, + }, + ZNMS12LM_closuresDoorLock_report: { + cid: 'closuresDoorLock', + type: 'attReport', + convert: (model, msg, publish, options) => { + const result = {}; + const lockStatusLookup = { + 1: 'finger_not_match', + 2: 'password_not_match', + 3: 'reverse_lock', // disable open from outside + 4: 'reverse_lock_cancel', // enable open from outside + 5: 'locked', + 6: 'lock_opened', + 7: 'finger_add', + 8: 'finger_delete', + 9: 'password_add', + 10: 'password_delete', + 11: 'lock_opened_inside', // Open form inside reverse lock enbable + 12: 'lock_opened_outside', // Open form outside reverse lock disable + 13: 'ring_bell', + 14: 'change_language_to', + 15: 'finger_open', + 16: 'password_open', + }; + result.user = null; + result.repeat = null; + if (msg.data.data['65526']) { // lock final status + // 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') { + result.action = lockStatusLookup[4]; + result.state = 'UNLOCK'; + result.reverse = 'UNLOCK'; + } else if (command === '0311') { + result.action = lockStatusLookup[4]; + result.state = 'LOCK'; + result.reverse = 'UNLOCK'; + } else if (command === '0205') { + result.action = lockStatusLookup[3]; + result.state = 'UNLOCK'; + result.reverse = 'LOCK'; + } else if (command === '0215') { + result.action = lockStatusLookup[3]; + result.state = 'LOCK'; + result.reverse = 'LOCK'; + } else if (command === '0111') { + result.action = lockStatusLookup[5]; + result.state = 'LOCK'; + result.reverse = 'UNLOCK'; + } else if (command === '0b00') { + result.action = lockStatusLookup[12]; + result.state = 'UNLOCK'; + result.reverse = 'UNLOCK'; + } else if (command === '0c00') { + result.action = lockStatusLookup[11]; + 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'); + const command = data.substr(6, 2); // 1 finger open, 2 password open + const userId = data.substr(12, 2); + const userType = data.substr(8, 1); // 1 admin, 2 user + result.action = (lockStatusLookup[14+parseInt(command, 16)] + + (userType === '1' ? '_admin' : '_user') + '_id' + parseInt(userId, 16).toString()); + result.user = parseInt(userId, 16); + } else if (msg.data.data['65297']) { // finger, password failed or bell + const data = Buffer.from(msg.data.data['65297'], 'ascii').toString('hex'); + const times = data.substr(6, 2); + const type = data.substr(12, 2); // 00 bell, 02 password, 40 error finger + if (type === '40') { + result.action = lockStatusLookup[1]; + result.repeat = parseInt(times, 16); + } else if (type === '00') { + result.action = lockStatusLookup[13]; + result.repeat = null; + } else if (type === '02') { + result.action = lockStatusLookup[2]; + result.repeat = parseInt(times, 16); + } + } else if (msg.data.data['65281']) { // password added/delete + const data = Buffer.from(msg.data.data['65281'], 'ascii').toString('hex'); + const command = data.substr(18, 2); // 1 add, 2 delete + const userId = data.substr(12, 2); + result.action = lockStatusLookup[6+parseInt(command, 16)]; + result.user = parseInt(userId, 16); + result.repeat = null; + } else if (msg.data.data['65522']) { // set languge + const data = Buffer.from(msg.data.data['65522'], 'ascii').toString('hex'); + const langId = data.substr(6, 2); // 1 chinese, 2: english + result.action = (lockStatusLookup[14])+ (langId==='2'?'_english':'_chinese'); + result.user = null; + result.repeat = null; + } + return result; + }, + }, + + // Ignore converters (these message dont need parsing). + ignore_fan_change: { + cid: 'hvacFanCtrl', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_light_brightness_change: { + cid: 'genLevelCtrl', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_doorlock_change: { + cid: 'closuresDoorLock', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_onoff_change: { + cid: 'genOnOff', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_onoff_report: { + cid: 'genOnOff', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => null, + }, + ignore_basic_change: { + cid: 'genBasic', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_basic_report: { + cid: 'genBasic', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => null, + }, + ignore_illuminance_change: { + cid: 'msIlluminanceMeasurement', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_occupancy_change: { + cid: 'msOccupancySensing', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_illuminance_report: { + cid: 'msIlluminanceMeasurement', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => null, + }, + ignore_occupancy_report: { + cid: 'msOccupancySensing', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => null, + }, + ignore_temperature_change: { + cid: 'msTemperatureMeasurement', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_temperature_report: { + cid: 'msTemperatureMeasurement', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => null, + }, + ignore_humidity_change: { + cid: 'msRelativeHumidity', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_humidity_report: { + cid: 'msRelativeHumidity', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => null, + }, + ignore_pressure_change: { + cid: 'msPressureMeasurement', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_pressure_report: { + cid: 'msPressureMeasurement', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => null, + }, + ignore_analog_change: { + cid: 'genAnalogInput', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_analog_report: { + cid: 'genAnalogInput', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => null, + }, + ignore_multistate_report: { + cid: 'genMultistateInput', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => null, + }, + ignore_multistate_change: { + cid: 'genMultistateInput', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_power_change: { + cid: 'genPowerCfg', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_power_report: { + cid: 'genPowerCfg', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => null, + }, + ignore_metering_change: { + cid: 'seMetering', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_electrical_change: { + cid: 'haElectricalMeasurement', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_light_brightness_report: { + cid: 'genLevelCtrl', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => null, + }, + ignore_light_color_colortemp_report: { + cid: 'lightingColorCtrl', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => null, + }, + ignore_closuresWindowCovering_change: { + cid: 'closuresWindowCovering', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_closuresWindowCovering_report: { + cid: 'closuresWindowCovering', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => null, + }, + ignore_thermostat_change: { + cid: 'hvacThermostat', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_thermostat_report: { + cid: 'hvacThermostat', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => null, + }, + ignore_genGroups_devChange: { + cid: 'genGroups', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_iaszone_attreport: { + cid: 'ssIasZone', + type: 'attReport', + convert: (model, msg, publish, options) => null, + }, + ignore_iaszone_change: { + cid: 'ssIasZone', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_iaszone_statuschange: { + cid: 'ssIasZone', + type: 'statusChange', + convert: (model, msg, publish, options) => null, + }, + ignore_iaszone_report: { + cid: 'ssIasZone', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => null, + }, + ignore_genIdentify: { + cid: 'genIdentify', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => null, + }, + _324131092621_ignore_on: { + cid: 'genOnOff', + type: 'cmdOn', + convert: (model, msg, publish, options) => null, + }, + _324131092621_ignore_off: { + cid: 'genOnOff', + type: 'cmdOffWithEffect', + convert: (model, msg, publish, options) => null, + }, + _324131092621_ignore_step: { + cid: 'genLevelCtrl', + type: 'cmdStep', + convert: (model, msg, publish, options) => null, + }, + _324131092621_ignore_stop: { + cid: 'genLevelCtrl', + type: 'cmdStop', + convert: (model, msg, publish, options) => null, + }, + ignore_poll_ctrl: { + cid: 'genPollCtrl', + type: ['attReport', 'readRsp'], + convert: (model, msg, publish, options) => null, + }, + ignore_poll_ctrl_change: { + cid: 'genPollCtrl', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_genIdentify_change: { + cid: 'genIdentify', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_diagnostic_change: { + cid: 'haDiagnostic', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_genScenes_change: { + cid: 'genScenes', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_lightLink_change: { + cid: 'lightLink', + type: 'devChange', + convert: (model, msg, publish, options) => null, + }, + ignore_genLevelCtrl_report: { + cid: 'genLevelCtrl', + type: 'attReport', + convert: (model, msg, publish, options) => null, + }, +}; + +module.exports = converters; diff --git a/zigbee-shepherd-converters/converters/toZigbee.js b/zigbee-shepherd-converters/converters/toZigbee.js new file mode 100644 index 0000000..583d8d4 --- /dev/null +++ b/zigbee-shepherd-converters/converters/toZigbee.js @@ -0,0 +1,1545 @@ +'use strict'; + +const utils = require('./utils'); +const common = require('./common'); +const zclId = require('zigbee-herdsman/dist/zcl-id'); + +const cfg = { + default: { + manufSpec: 0, + disDefaultRsp: 0, + }, + defaultdisFeedbackRsp: { + manufSpec: 0, + disDefaultRsp: 0, + disFeedbackRsp: true, + }, + xiaomi: { + manufSpec: 1, + disDefaultRsp: 1, + manufCode: 0x115F, + }, + eurotronic: { + manufSpec: 1, + manufCode: 4151, + }, + hue: { + manufSpec: 1, + manufCode: 4107, + }, + osram: { + manufSpec: 1, + manufCode: 0x110c, + }, +}; + +const converters = { + /** + * Generic + */ + factory_reset: { + key: ['reset'], + convert: (key, value, message, type, postfix, options) => { + if (type === 'set') { + return [{ + cid: 'genBasic', + cmd: 'resetFactDefault', + cmdType: 'functional', + zclData: {}, + cfg: cfg.default, + }]; + } + }, + }, + on_off: { + key: ['state'], + convert: (key, value, message, type, postfix, options) => { + const cid = 'genOnOff'; + const attrId = 'onOff'; + + if (type === 'set') { + if (typeof value !== 'string') { + return; + } + + return [{ + cid: cid, + cmd: value.toLowerCase(), + cmdType: 'functional', + zclData: {}, + cfg: options.disFeedbackRsp ? cfg.defaultdisFeedbackRsp : cfg.default, + newState: {state: value.toUpperCase()}, + }]; + } else if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: zclId.attr(cid, attrId).value}], + cfg: cfg.default, + }]; + } + }, + }, + cover_open_close: { + key: ['state'], + convert: (key, value, message, type, postfix, options) => { + if (type === 'set') { + if (typeof value !== 'string') { + return; + } + + const positionByState = { + 'open': 100, + 'close': 0, + }; + + value = positionByState[value.toLowerCase()]; + } + + return converters.cover_position.convert(key, value, message, type, postfix, options); + }, + }, + cover_position: { + key: ['position'], + convert: (key, value, message, type, postfix, options) => { + const cid = 'genLevelCtrl'; + const attrId = 'currentLevel'; + + if (type === 'set') { + return [{ + cid: cid, + cmd: 'moveToLevelWithOnOff', + cmdType: 'functional', + zclData: { + level: Math.round(Number(value) * 2.55).toString(), + transtime: 0, + }, + cfg: cfg.default, + newState: {position: value}, + readAfterWriteTime: 0, + }]; + } else if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: zclId.attr(cid, attrId).value}], + cfg: cfg.default, + }]; + } + }, + }, + warning: { + key: ['warning'], + convert: (key, value, message, type, postfix, options) => { + if (type === 'set') { + const mode = { + 'stop': 0, + 'burglar': 1, + 'fire': 2, + 'emergency': 3, + 'police_panic': 4, + 'fire_panic': 5, + 'emergency_panic': 6, + }; + + const level = { + 'low': 0, + 'medium': 1, + 'high': 2, + 'very_high': 3, + }; + + const values = { + mode: value.mode || 'emergency', + level: value.level || 'medium', + strobe: value.hasOwnProperty('strobe') ? value.strobe : true, + duration: value.hasOwnProperty('duration') ? value.duration : 10, + }; + + const info = (mode[values.mode] << 4) + ((values.strobe ? 1 : 0) << 2) + (level[values.level]); + + return [{ + cid: 'ssIasWd', + cmd: 'startWarning', + cmdType: 'functional', + zclData: {startwarninginfo: info, warningduration: values.duration}, + 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 + + if (type === 'set') { + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: attrId, + dataType: 33, // uint16 + // hue_sml001: + // in seconds, minimum 10 seconds, <10 values result + // in 10 seconds delay + // make sure you write to second endpoint! + attrData: value, + }], + cfg: cfg.default, + }]; + } else if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{ + attrId: attrId, + }], + cfg: cfg.default, + }]; + } + }, + }, + light_brightness: { + key: ['brightness', 'brightness_percent'], + convert: (key, value, message, type, postfix, options) => { + const cid = 'genLevelCtrl'; + const attrId = 'currentLevel'; + + if (type === 'set') { + if (key === 'brightness_percent') { + value = Math.round(Number(value) * 2.55).toString(); + } + + if (Number(value) === 0) { + const converted = converters.on_off.convert('state', 'off', message, 'set', postfix, options); + converted[0].newState.brightness = 0; + return converted; + } else { + return [{ + cid: cid, + cmd: 'moveToLevel', + cmdType: 'functional', + zclData: { + level: Number(value), + transtime: message.hasOwnProperty('transition') ? message.transition * 10 : 0, + }, + cfg: cfg.default, + newState: {brightness: Number(value)}, + readAfterWriteTime: message.hasOwnProperty('transition') ? message.transition * 1000 : 0, + }]; + } + } else if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: zclId.attr(cid, attrId).value}], + cfg: cfg.default, + }]; + } + }, + }, + light_onoff_brightness: { + key: ['state', 'brightness', 'brightness_percent'], + convert: (key, value, message, type, postfix, options) => { + if (type === 'set') { + const hasBrightness = message.hasOwnProperty('brightness') || + message.hasOwnProperty('brightness_percent'); + const brightnessValue = message.hasOwnProperty('brightness') ? + message.brightness : message.brightness_percent; + const hasState = message.hasOwnProperty('state'); + const hasTrasition = message.hasOwnProperty('transition'); + const state = hasState ? message.state.toLowerCase() : null; + + if (hasState && (state === 'off' || !hasBrightness) && !hasTrasition) { + const converted = converters.on_off.convert('state', state, message, 'set', postfix, options); + if (state === 'on') { + converted[0].readAfterWriteTime = 0; + } + return converted; + } else if (!hasState && hasBrightness && Number(brightnessValue) === 0) { + const converted = converters.on_off.convert('state', 'off', message, 'set', postfix, options); + converted[0].newState.brightness = 0; + return converted; + } else { + let brightness = 0; + + if (hasState && !hasBrightness && state == 'on') { + brightness = 255; + } else if (message.hasOwnProperty('brightness')) { + brightness = message.brightness; + } else if (message.hasOwnProperty('brightness_percent')) { + brightness = Math.round(Number(message.brightness_percent) * 2.55).toString(); + } + + return [{ + cid: 'genLevelCtrl', + cmd: 'moveToLevelWithOnOff', + cmdType: 'functional', + zclData: { + level: Number(brightness), + transtime: message.hasOwnProperty('transition') ? message.transition * 10 : 0, + }, + cfg: options.disFeedbackRsp ? cfg.defaultdisFeedbackRsp : cfg.default, + newState: {state: brightness === 0 ? 'OFF' : 'ON', brightness: Number(brightness)}, + readAfterWriteTime: message.hasOwnProperty('transition') ? message.transition * 1000 : 0, + }]; + } + } else if (type === 'get') { + return [ + { + cid: 'genOnOff', + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: zclId.attr('genOnOff', 'onOff').value}], + cfg: cfg.default, + }, + { + cid: 'genLevelCtrl', + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: zclId.attr('genLevelCtrl', 'currentLevel').value}], + cfg: cfg.default, + }, + ]; + } + }, + }, + light_colortemp: { + key: ['color_temp', 'color_temp_percent'], + convert: (key, value, message, type, postfix, options) => { + const cid = 'lightingColorCtrl'; + const attrId = 'colorTemperature'; + + if (type === 'set') { + if (key === 'color_temp_percent') { + value = Number(value) * 3.46; + value = Math.round(value + 154).toString(); + } + + value = Number(value); + + return [{ + cid: cid, + cmd: 'moveToColorTemp', + cmdType: 'functional', + zclData: { + colortemp: value, + transtime: message.hasOwnProperty('transition') ? message.transition * 10 : 0, + }, + cfg: cfg.default, + newState: {color_temp: value}, + readAfterWriteTime: message.hasOwnProperty('transition') ? message.transition * 1000 : 0, + }]; + } else if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: zclId.attr(cid, attrId).value}], + cfg: cfg.default, + }]; + } + }, + }, + light_color: { + key: ['color'], + convert: (key, value, message, type, postfix, options) => { + const cid = 'lightingColorCtrl'; + + if (type === 'set') { + // Check if we need to convert from RGB to XY and which cmd to use + let cmd; + if (value.hasOwnProperty('r') && value.hasOwnProperty('g') && value.hasOwnProperty('b')) { + const xy = utils.rgbToXY(value.r, value.g, value.b); + value.x = xy.x; + value.y = xy.y; + } else if (value.hasOwnProperty('rgb')) { + const rgb = value.rgb.split(',').map((i) => parseInt(i)); + const xy = utils.rgbToXY(rgb[0], rgb[1], rgb[2]); + value.x = xy.x; + value.y = xy.y; + } else if (value.hasOwnProperty('hex')) { + const xy = utils.hexToXY(value.hex); + value.x = xy.x; + value.y = xy.y; + } else if (value.hasOwnProperty('hue') && value.hasOwnProperty('saturation')) { + value.hue = value.hue * (65535 / 360); + value.saturation = value.saturation * (2.54); + cmd = 'enhancedMoveToHueAndSaturation'; + } else if (value.hasOwnProperty('hue')) { + value.hue = value.hue * (65535 / 360); + cmd = 'enhancedMoveToHue'; + } else if (value.hasOwnProperty('saturation')) { + value.saturation = value.saturation * (2.54); + cmd = 'moveToSaturation'; + } + + const zclData = { + transtime: message.hasOwnProperty('transition') ? message.transition * 10 : 0, + }; + + let newState = null; + switch (cmd) { + case 'enhancedMoveToHueAndSaturation': + zclData.enhancehue = value.hue; + zclData.saturation = value.saturation; + zclData.direction = value.direction || 0; + break; + case 'enhancedMoveToHue': + zclData.enhancehue = value.hue; + zclData.direction = value.direction || 0; + break; + + case 'moveToSaturation': + zclData.saturation = value.saturation; + break; + + default: + cmd = 'moveToColor'; + newState = {color: {x: value.x, y: value.y}}; + zclData.colorx = Math.round(value.x * 65535); + zclData.colory = Math.round(value.y * 65535); + } + + return [{ + cid: cid, + cmd: cmd, + cmdType: 'functional', + zclData: zclData, + cfg: cfg.default, + newState: newState, + readAfterWriteTime: message.hasOwnProperty('transition') ? message.transition * 1000 : 0, + }]; + } else if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [ + {attrId: zclId.attr(cid, 'currentX').value}, + {attrId: zclId.attr(cid, 'currentY').value}, + ], + cfg: cfg.default, + }]; + } + }, + }, + light_color_colortemp: { + /** + * This converter is a combination of light_color and light_colortemp and + * can be used instead of the two individual converters. When used to set, + * it actually calls out to light_color or light_colortemp to get the + * return value. When used to get, it gets both color and colorTemp in + * one call. + * The reason for the existence of this somewhat peculiar converter is + * that some lights don't report their state when changed. To fix this, + * we query the state after we set it. We want to query color and colorTemp + * both when setting either, because both change when setting one. This + * converter is used to do just that. + */ + key: ['color', 'color_temp', 'color_temp_percent'], + convert: (key, value, message, type, postfix, options) => { + const cid = 'lightingColorCtrl'; + if (type === 'set') { + if (key == 'color') { + return converters.light_color.convert(key, value, message, type, postfix, options); + } else if (key == 'color_temp' || key == 'color_temp_percent') { + return converters.light_colortemp.convert(key, value, message, type, postfix, options); + } + } else if (type == 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [ + {attrId: zclId.attr(cid, 'currentX').value}, + {attrId: zclId.attr(cid, 'currentY').value}, + {attrId: zclId.attr(cid, 'colorTemperature').value}, + ], + cfg: cfg.default, + }]; + } + }, + }, + light_alert: { + key: ['alert', 'flash'], + convert: (key, value, message, type, postfix, options) => { + const cid = 'genIdentify'; + if (type === 'set') { + const lookup = { + 'select': 0x00, + 'lselect': 0x01, + 'none': 0xFF, + }; + if (key === 'flash') { + if (value === 2) { + value = 'select'; + } else if (value === 10) { + value = 'lselect'; + } + } + return [{ + cid: cid, + cmd: 'triggerEffect', + cmdType: 'functional', + zclData: { + effectid: lookup[value.toLowerCase()], + effectvariant: 0x01, + }, + cfg: cfg.default, + }]; + } + }, + }, + thermostat_local_temperature: { + key: 'local_temperature', + convert: (key, value, message, type, postfix, options) => { + const cid = 'hvacThermostat'; + const attrId = 'localTemp'; + if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: zclId.attr(cid, attrId).value}], + cfg: cfg.default, + }]; + } + }, + }, + thermostat_local_temperature_calibration: { + key: 'local_temperature_calibration', + convert: (key, value, message, type, postfix, options) => { + const cid = 'hvacThermostat'; + const attrId = 'localTemperatureCalibration'; + if (type === 'set') { + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: zclId.attr(cid, attrId).value, + dataType: zclId.attrType(cid, attrId).value, + attrData: Math.round(value * 10), + }], + cfg: cfg.default, + }]; + } else if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: zclId.attr(cid, attrId).value}], + cfg: cfg.default, + }]; + } + }, + }, + thermostat_occupancy: { + key: 'occupancy', + convert: (key, value, message, type, postfix, options) => { + const cid = 'hvacThermostat'; + const attrId = 'ocupancy'; + if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: zclId.attr(cid, attrId).value}], + cfg: cfg.default, + }]; + } + }, + }, + thermostat_occupied_heating_setpoint: { + key: 'occupied_heating_setpoint', + convert: (key, value, message, type, postfix, options) => { + const cid = 'hvacThermostat'; + const attrId = 'occupiedHeatingSetpoint'; + if (type === 'set') { + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: zclId.attr(cid, attrId).value, + dataType: zclId.attrType(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: zclId.attr(cid, attrId).value}], + cfg: cfg.default, + }]; + } + }, + }, + thermostat_unoccupied_heating_setpoint: { + key: 'unoccupied_heating_setpoint', + convert: (key, value, message, type, postfix, options) => { + const cid = 'hvacThermostat'; + const attrId = 'unoccupiedHeatingSetpoint'; + if (type === 'set') { + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: zclId.attr(cid, attrId).value, + dataType: zclId.attrType(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: zclId.attr(cid, attrId).value}], + cfg: cfg.default, + }]; + } + }, + }, + thermostat_remote_sensing: { + key: 'remote_sensing', + convert: (key, value, message, type, postfix, options) => { + const cid = 'hvacThermostat'; + const attrId = 'remoteSensing'; + if (type === 'set') { + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + // Bit 0 = 0 – local temperature sensed internally + // Bit 0 = 1 – local temperature sensed remotely + // Bit 1 = 0 – outdoor temperature sensed internally + // 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, + attrData: value, // TODO: Lookup in Zigbee documentation + }], + cfg: cfg.default, + }]; + } else if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: zclId.attr(cid, attrId).value}], + cfg: cfg.default, + }]; + } + }, + }, + thermostat_control_sequence_of_operation: { + key: 'control_sequence_of_operation', + convert: (key, value, message, type, postfix, options) => { + const cid = 'hvacThermostat'; + const attrId = 'ctrlSeqeOfOper'; + if (type === 'set') { + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: zclId.attr(cid, attrId).value, + dataType: zclId.attrType(cid, attrId).value, + attrData: utils.getKeyByValue(common.thermostatControlSequenceOfOperations, value, value), + }], + cfg: cfg.default, + }]; + } else if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: zclId.attr(cid, attrId).value}], + cfg: cfg.default, + }]; + } + }, + }, + thermostat_system_mode: { + key: 'system_mode', + convert: (key, value, message, type, postfix, options) => { + const cid = 'hvacThermostat'; + const attrId = 'systemMode'; + if (type === 'set') { + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: zclId.attr(cid, attrId).value, + dataType: zclId.attrType(cid, attrId).value, + attrData: utils.getKeyByValue(common.thermostatSystemModes, value, value), + }], + cfg: cfg.default, + readAfterWriteTime: 250, + }]; + } else if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: zclId.attr(cid, attrId).value}], + cfg: cfg.default, + }]; + } + }, + }, + thermostat_setpoint_raise_lower: { + key: 'setpoint_raise_lower', + convert: (key, value, message, type, postfix, options) => { + const cid = 'hvacThermostat'; + if (type === 'set') { + return [{ + cid: cid, + cmd: 'setpointRaiseLower', + cmdType: 'functional', + zclData: { + mode: value.mode, + amount: Math.round(value.amount) * 100, + }, + cfg: cfg.default, + }]; + } + }, + }, + thermostat_weekly_schedule: { + key: 'weekly_schedule', + convert: (key, value, message, type, postfix, options) => { + const cid = 'hvacThermostat'; + const attrId = 'weeklySchedule'; + if (type === 'set') { + return [{ + cid: cid, + cmd: 'setWeeklySchedule', + cmdType: 'functional', + zclData: { + dataType: zclId.attrType(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, + thermostat_programming_operation_mode: value.thermostat_programming_operation_mode, + thermostat_running_state: value.thermostat_running_state, + }, + cfg: cfg.default, + }]; + } else if (type === 'get') { + return [{ + cid: cid, + cmd: 'getWeeklySchedule', + cmdType: 'functional', + zclData: {}, + cfg: cfg.default, + }]; + } + }, + }, + thermostat_clear_weekly_schedule: { + key: 'clear_weekly_schedule', + attr: [], + convert: (key, value, message, type, postfix, options) => { + return [{ + cid: 'hvacThermostat', + cmd: 'clearWeeklySchedule', + type: 'functional', + zclData: {}, + }]; + }, + }, + thermostat_relay_status_log: { + key: 'relay_status_log', + attr: [], + convert: (key, value, message, type, postfix, options) => { + return [{ + cid: 'hvacThermostat', + cmd: 'getRelayStatusLog', + type: 'functional', + zclData: {}, + }]; + }, + }, + thermostat_weekly_schedule_rsp: { + key: 'weekly_schedule_rsp', + attr: [], + convert: (key, value, message, type, postfix, options) => { + return [{ + cid: 'hvacThermostat', + cmd: 'getWeeklyScheduleRsp', + type: 'functional', + zclData: { + number_of_transitions: value.numoftrans, // TODO: Lookup in Zigbee documentation + day_of_week: value.dayofweek, + mode: value.mode, + thermoseqmode: value.thermoseqmode, + }, + }]; + }, + }, + thermostat_relay_status_log_rsp: { + key: 'relay_status_log_rsp', + attr: [], + convert: (key, value, message, type, postfix, options) => { + return [{ + cid: 'hvacThermostat', + cmd: 'getRelayStatusLogRsp', + type: 'functional', + zclData: { + time_of_day: value.timeofday, // TODO: Lookup in Zigbee documentation + relay_status: value.relaystatus, + local_temperature: value.localtemp, + humidity: value.humidity, + setpoint: value.setpoint, + unread_entries: value.unreadentries, + }, + }]; + }, + }, + thermostat_running_mode: { + key: 'running_mode', + convert: (key, value, message, type, postfix, options) => { + const cid = 'hvacThermostat'; + const attrId = 'runningMode'; + if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: zclId.attr(cid, attrId).value}], + cfg: cfg.default, + }]; + } + }, + }, + thermostat_running_state: { + key: 'running_state', + convert: (key, value, message, type, postfix, options) => { + const cid = 'hvacThermostat'; + const attrId = 'runningState'; + if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: zclId.attr(cid, attrId).value}], + cfg: cfg.default, + }]; + } + }, + }, + thermostat_temperature_display_mode: { + key: 'temperature_display_mode', + convert: (key, value, message, type, postfix, options) => { + const cid = 'hvacUserInterfaceCfg'; + const attrId = 'tempDisplayMode'; + if (type === 'set') { + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: zclId.attr(cid, attrId).value, + dataType: zclId.attrType(cid, attrId).value, + attrData: value, + }], + cfg: cfg.default, + }]; + } + }, + }, + fan_mode: { + key: ['fan_mode', 'fan_state'], + convert: (key, value, message, type, postfix, options) => { + const cid = 'hvacFanCtrl'; + const attrId = 'fanMode'; + + if (type === 'set') { + value = value.toLowerCase(); + const attrData = common.fanMode[value]; + + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{attrId: zclId.attr(cid, attrId).value, attrData: attrData, dataType: 48}], + cfg: cfg.default, + newState: {fan_mode: value, fan_state: value === 'off' ? 'OFF' : 'ON'}, + }]; + } else if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: zclId.attr(cid, attrId).value}], + cfg: cfg.default, + }]; + } + }, + }, + + /** + * Device specific + */ + DJT11LM_vibration_sensitivity: { + key: ['sensitivity'], + convert: (key, value, message, type, postfix, options) => { + const cid = 'genBasic'; + const attrId = 0xFF0D; + + if (type === 'set') { + const lookup = { + 'low': 0x15, + 'medium': 0x0B, + 'high': 0x01, + }; + + if (lookup.hasOwnProperty(value)) { + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: attrId, + dataType: 0x20, + attrData: lookup[value], + }], + cfg: cfg.xiaomi, + }]; + } + } else if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: attrId}], + cfg: cfg.xiaomi, + }]; + } + }, + }, + JTQJBF01LMBW_sensitivity: { + key: ['sensitivity'], + convert: (key, value, message, type, postfix, options) => { + const cid = 'ssIasZone'; + + if (type === 'set') { + const lookup = { + 'low': 0x04010000, + 'medium': 0x04020000, + 'high': 0x04030000, + }; + + if (lookup.hasOwnProperty(value)) { + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: 0xFFF1, // presentValue + dataType: 0x23, // dataType + attrData: lookup[value], + }], + cfg: cfg.xiaomi, + }]; + } + } else if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{ + attrId: 0xFFF0, // presentValue + dataType: 0x39, // dataType + }], + cfg: cfg.xiaomi, + }]; + } + }, + }, + JTQJBF01LMBW_selfest: { + key: ['selftest'], + convert: (key, value, message, type, postfix, options) => { + if (type === 'set') { + return [{ + cid: 'ssIasZone', + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: 0xFFF1, // presentValue + dataType: 0x23, // dataType + attrData: 0x03010000, + }], + cfg: cfg.xiaomi, + }]; + } + }, + }, + xiaomi_switch_operation_mode: { + key: ['operation_mode'], + convert: (key, value, message, type, postfix, options) => { + const cid = 'genBasic'; + const lookupAttrId = { + 'single': 0xFF22, + 'left': 0xFF22, + 'right': 0xFF23, + }; + const lookupState = { + 'control_relay': 0x12, + 'control_left_relay': 0x12, + 'control_right_relay': 0x22, + 'decoupled': 0xFE, + }; + let button; + if (value.hasOwnProperty('button')) { + button = value.button; + } else { + button = 'single'; + } + if (type === 'set') { + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: lookupAttrId[button], + dataType: 0x20, + attrData: lookupState[value.state], + }], + cfg: cfg.xiaomi, + }]; + } else if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{ + attrId: lookupAttrId[button], + dataType: 0x20, + }], + cfg: cfg.xiaomi, + }]; + } + }, + }, + STS_PRS_251_beep: { + key: ['beep'], + convert: (key, value, message, type, postfix, options) => { + const cid = 'genIdentify'; + const attrId = 'identifyTime'; + + if (type === 'set') { + return [{ + cid: cid, + cmd: 'identify', + cmdType: 'functional', + zclData: { + identifytime: value, + }, + cfg: cfg.default, + }]; + } else if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: zclId.attr(cid, attrId).value}], + cfg: cfg.default, + }]; + } + }, + }, + ZNCLDJ11LM_control: { + key: ['state', 'position'], + convert: (key, value, message, type, postfix, options) => { + if (key === 'state' && value.toLowerCase() === 'stop') { + return [{ + cid: 'closuresWindowCovering', + cmd: 'stop', + cmdType: 'functional', + zclData: {}, + cfg: cfg.default, + }]; + } else { + const lookup = { + 'open': 100, + 'close': 0, + 'on': 100, + 'off': 0, + }; + + value = typeof value === 'string' ? value.toLowerCase() : value; + value = lookup.hasOwnProperty(value) ? lookup[value] : value; + + return [{ + cid: 'genAnalogOutput', + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: 0x0055, + dataType: 0x39, + attrData: value, + }], + cfg: cfg.default, + }]; + } + }, + }, + osram_cmds: { + key: ['osram_set_transition', 'osram_remember_state'], + convert: (key, value, message, type, postfix, options) => { + if (type === 'set') { + if ( key === 'osram_set_transition' ) { + if (value) { + const transition = ( value > 1 ) ? (Math.round((value * 2).toFixed(1))/2).toFixed(1) * 10 : 1; + return [{ + cid: 'genLevelCtrl', + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: 0x0012, + dataType: 0x21, + attrData: transition, + }, {attrId: 0x0013, + dataType: 0x21, + attrData: transition, + }], + cfg: cfg.default, + }]; + } + } else if ( key == 'osram_remember_state' ) { + if (value === true) { + return [{ + cid: 'manuSpecificOsram', + cmd: 'saveStartupParams', + cmdType: 'functional', + zclData: {}, + cfg: cfg.osram, + }]; + } else if ( value === false ) { + return [{ + cid: 'manuSpecificOsram', + cmd: 'resetStartupParams', + cmdType: 'functional', + zclData: {}, + cfg: cfg.osram, + }]; + } + } + } + }, + }, + eurotronic_system_mode: { + key: 'eurotronic_system_mode', + convert: (key, value, message, type, postfix, options) => { + const cid = 'hvacThermostat'; + const attrId = 0x4008; + if (type === 'set') { + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + // Bit 0 = ? (default 1) + // Bit 2 = Boost + // Bit 4 = Window open + // Bit 7 = Child protection + attrId: attrId, + dataType: 0x22, + attrData: value, + }], + cfg: cfg.eurotronic, + }]; + } else if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: attrId}], + cfg: cfg.eurotronic, + }]; + } + }, + }, + eurotronic_error_status: { + key: 'eurotronic_error_status', + convert: (key, value, message, type, postfix, options) => { + const cid = 'hvacThermostat'; + const attrId = 0x4002; + if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: attrId}], + cfg: cfg.eurotronic, + }]; + } + }, + }, + eurotronic_current_heating_setpoint: { + key: 'current_heating_setpoint', + convert: (key, value, message, type, postfix, options) => { + const cid = 'hvacThermostat'; + const attrId = 0x4003; + if (type === 'set') { + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: attrId, + dataType: 0x29, + attrData: (Math.round((value * 2).toFixed(1))/2).toFixed(1) * 100, + }], + cfg: cfg.eurotronic, + }]; + } else if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: attrId}], + cfg: cfg.eurotronic, + }]; + } + }, + }, + eurotronic_valve_position: { + key: 'eurotronic_valve_position', + convert: (key, value, message, type, postfix, options) => { + const cid = 'hvacThermostat'; + const attrId = 0x4001; + if (type === 'set') { + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: attrId, + dataType: 0x20, + attrData: value, + }], + cfg: cfg.eurotronic, + }]; + } else if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: attrId}], + cfg: cfg.eurotronic, + }]; + } + }, + }, + eurotronic_trv_mode: { + key: 'eurotronic_trv_mode', + convert: (key, value, message, type, postfix, options) => { + const cid = 'hvacThermostat'; + const attrId = 0x4000; + if (type === 'set') { + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: attrId, + dataType: 0x30, + attrData: value, + }], + cfg: cfg.eurotronic, + }]; + } else if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: attrId}], + cfg: cfg.eurotronic, + }]; + } + }, + }, + livolo_switch_on_off: { + key: ['state'], + convert: (key, value, message, type, postfix, options) => { + if (type === 'set') { + if (typeof value !== 'string') { + return; + } + + postfix = postfix || 'left'; + + const cid = 'genLevelCtrl'; + let state = value.toLowerCase(); + let channel = 1; + + if (state === 'on') { + state = 108; + } else if (state === 'off') { + state = 1; + } else { + return; + } + + if (postfix === 'left') { + channel = 1.0; + } else if (postfix === 'right') { + channel = 2.0; + } else { + return; + } + + return [{ + cid: cid, + cmd: 'moveToLevelWithOnOff', + cmdType: 'functional', + zclData: { + level: state, + transtime: channel, + }, + cfg: cfg.default, + newState: {state: value.toUpperCase()}, + readAfterWriteTime: 250, + }]; + } else if (type === 'get') { + const cid = 'genOnOff'; + const attrId = 'onOff'; + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: zclId.attr(cid, attrId).value}], + cfg: cfg.default, + }]; + } + }, + }, + generic_lock: { + key: ['state'], + convert: (key, value, message, type, postfix, options) => { + const cid = 'closuresDoorLock'; + const attrId = 'lockState'; + + if (type === 'set') { + if (typeof value !== 'string') { + return; + } + + return [{ + cid: cid, + cmd: `${value.toLowerCase()}Door`, + cmdType: 'functional', + zclData: { + 'pincodevalue': '', + }, + cfg: cfg.default, + readAfterWriteTime: 200, + }]; + } else if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{attrId: zclId.attr(cid, attrId).value}], + cfg: cfg.default, + }]; + } + }, + }, + gledopto_light_onoff_brightness: { + key: ['state', 'brightness', 'brightness_percent'], + convert: (key, value, message, type, postfix, options) => { + if (message && message.hasOwnProperty('transition')) { + message.transition = message.transition * 3.3; + } + + return converters.light_onoff_brightness.convert(key, value, message, type, postfix, options); + }, + }, + gledopto_light_color_colortemp: { + key: ['color', 'color_temp', 'color_temp_percent'], + convert: (key, value, message, type, postfix, options) => { + if (message.hasOwnProperty('transition')) { + message.transition = message.transition * 3.3; + } + + return converters.light_color_colortemp.convert(key, value, message, type, postfix, options); + }, + }, + gledopto_light_colortemp: { + key: ['color_temp', 'color_temp_percent'], + convert: (key, value, message, type, postfix, options) => { + if (message.hasOwnProperty('transition')) { + message.transition = message.transition * 3.3; + } + + return converters.light_colortemp.convert(key, value, message, type, postfix, options); + }, + }, + hue_power_on_behavior: { + key: ['hue_power_on_behavior'], + convert: (key, value, message, type, postfix, options) => { + const lookup = { + 'default': 0x01, + 'on': 0x01, + 'off': 0x00, + 'recover': 0xff, + }; + + if (type === 'set') { + return [{ + cid: 'genOnOff', + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: 0x4003, + dataType: 0x30, + attrData: lookup[value], + }], + cfg: cfg.default, + }]; + } + }, + }, + hue_power_on_brightness: { + key: ['hue_power_on_brightness'], + convert: (key, value, message, type, postfix, options) => { + if (type === 'set') { + if (value === 'default') { + value = 255; + } + return [{ + cid: 'genLevelCtrl', + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: 0x4000, + dataType: 0x20, + attrData: value, + }], + cfg: cfg.default, + }]; + } + }, + }, + hue_power_on_color_temperature: { + key: ['hue_power_on_color_temperature'], + convert: (key, value, message, type, postfix, options) => { + if (type === 'set') { + if (value === 'default') { + value = 366; + } + return [{ + cid: 'lightingColorCtrl', + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: 0x4010, + dataType: 0x21, + attrData: value, + }], + cfg: cfg.default, + }]; + } + }, + }, + hue_motion_sensitivity: { + // motion detect sensitivity, philips specific + key: ['motion_sensitivity'], + convert: (key, value, message, type, postfix, options) => { + const cid = 'msOccupancySensing'; // 1030 + const attrId = 48; + if (type === 'set') { + // hue_sml: + // 0: low, 1: medium, 2: high (default) + // make sure you write to second endpoint! + const lookup = { + 'low': 0, + 'medium': 1, + 'high': 2, + }; + + return [{ + cid: cid, + cmd: 'write', + cmdType: 'foundation', + zclData: [{ + attrId: attrId, + dataType: 32, + attrData: lookup[value], + }], + cfg: cfg.hue, + }]; + } else if (type === 'get') { + return [{ + cid: cid, + cmd: 'read', + cmdType: 'foundation', + zclData: [{ + attrId: attrId, + }], + cfg: cfg.hue, + }]; + } + }, + }, + ZigUP_lock: { + key: ['led'], + convert: (key, value, message, type, postfix, options) => { + const lookup = { + 'off': 'lockDoor', + 'on': 'unlockDoor', + 'toggle': 'toggleDoor', + }; + const cid = 'closuresDoorLock'; + + if (type === 'set') { + if (typeof value !== 'string') { + return; + } + + return [{ + cid: cid, + cmd: lookup[value], + cmdType: 'functional', + zclData: { + 'pincodevalue': '', + }, + cfg: cfg.default, + }]; + } + }, + }, + + /** + * Ignore converters + */ + ignore_transition: { + key: ['transition'], + attr: [], + convert: (key, value, message, type, postfix, options) => null, + }, +}; + +module.exports = converters; diff --git a/zigbee-shepherd-converters/devices.js b/zigbee-shepherd-converters/devices.js new file mode 100644 index 0000000..b6e3519 --- /dev/null +++ b/zigbee-shepherd-converters/devices.js @@ -0,0 +1,4726 @@ +'use strict'; + +const debug = require('debug')('zigbee-shepherd-converters:devices'); +const fz = require('./converters/fromZigbee'); +const tz = require('./converters/toZigbee'); + +const repInterval = { + MAX: 62000, + HOUR: 3600, + MINUTE: 60, +}; + +const defaultIgnoreConverters = [ + fz.ignore_genGroups_devChange, fz.ignore_genIdentify_change, fz.ignore_genScenes_change, + fz.ignore_diagnostic_change, fz.ignore_lightLink_change, fz.ignore_basic_change, +]; + +const generic = { + light_onoff_brightness: { + supports: 'on/off, brightness', + fromZigbee: [ + fz.ignore_light_brightness_change, fz.state_change, fz.state, fz.brightness_report, + ].concat(defaultIgnoreConverters), + toZigbee: [tz.light_onoff_brightness, tz.ignore_transition, tz.light_alert], + }, + light_onoff_brightness_colortemp: { + supports: 'on/off, brightness, color temperature', + fromZigbee: [ + fz.ignore_light_brightness_change, fz.color_colortemp, fz.state_change, fz.state, + fz.brightness_report, fz.color_colortemp_report, + ].concat(defaultIgnoreConverters), + toZigbee: [tz.light_onoff_brightness, tz.light_colortemp, tz.ignore_transition, tz.light_alert], + }, + light_onoff_brightness_colorxy: { + supports: 'on/off, brightness, color xy', + fromZigbee: [ + fz.ignore_light_brightness_change, fz.color_colortemp, fz.state_change, fz.state, + fz.brightness_report, fz.color_colortemp_report, + ].concat(defaultIgnoreConverters), + toZigbee: [tz.light_onoff_brightness, tz.light_color, tz.ignore_transition, tz.light_alert], + }, + light_onoff_brightness_colortemp_colorxy: { + supports: 'on/off, brightness, color temperature, color xy', + fromZigbee: [ + fz.ignore_light_brightness_change, fz.color_colortemp, fz.state_change, fz.state, + fz.brightness_report, fz.color_colortemp_report, + ].concat(defaultIgnoreConverters), + toZigbee: [ + tz.light_onoff_brightness, tz.light_color_colortemp, tz.ignore_transition, + tz.light_alert, + ], + }, +}; + +const gledopto = { + light_onoff_brightness: { + supports: generic.light_onoff_brightness.supports, + fromZigbee: generic.light_onoff_brightness.fromZigbee, + toZigbee: [tz.gledopto_light_onoff_brightness, tz.ignore_transition, tz.light_alert], + }, + light_onoff_brightness_colortemp: { + supports: generic.light_onoff_brightness_colortemp.supports, + fromZigbee: generic.light_onoff_brightness_colortemp.fromZigbee, + toZigbee: [ + tz.gledopto_light_onoff_brightness, tz.gledopto_light_colortemp, tz.ignore_transition, + tz.light_alert, + ], + }, + light_onoff_brightness_colortemp_colorxy: { + supports: generic.light_onoff_brightness_colortemp_colorxy.supports, + fromZigbee: generic.light_onoff_brightness_colortemp_colorxy.fromZigbee, + toZigbee: [ + tz.gledopto_light_onoff_brightness, tz.gledopto_light_color_colortemp, tz.ignore_transition, + tz.light_alert, + ], + }, +}; + +const tzHuePowerOnBehavior = [tz.hue_power_on_behavior, tz.hue_power_on_brightness, tz.hue_power_on_color_temperature]; +const hue = { + light_onoff_brightness: { + supports: generic.light_onoff_brightness.supports + ', power-on behavior', + fromZigbee: generic.light_onoff_brightness.fromZigbee, + toZigbee: generic.light_onoff_brightness.toZigbee.concat(tzHuePowerOnBehavior), + }, + light_onoff_brightness_colortemp: { + supports: generic.light_onoff_brightness_colortemp.supports + ', power-on behavior', + fromZigbee: generic.light_onoff_brightness_colortemp.fromZigbee, + toZigbee: generic.light_onoff_brightness_colortemp.toZigbee.concat(tzHuePowerOnBehavior), + }, + light_onoff_brightness_colorxy: { + supports: generic.light_onoff_brightness_colorxy.supports + ', power-on behavior', + fromZigbee: generic.light_onoff_brightness_colorxy.fromZigbee, + toZigbee: generic.light_onoff_brightness_colorxy.toZigbee.concat(tzHuePowerOnBehavior), + }, + light_onoff_brightness_colortemp_colorxy: { + supports: generic.light_onoff_brightness_colortemp_colorxy.supports + ', power-on behavior', + fromZigbee: generic.light_onoff_brightness_colortemp_colorxy.fromZigbee, + toZigbee: generic.light_onoff_brightness_colortemp_colorxy.toZigbee.concat(tzHuePowerOnBehavior), + }, +}; + +const foundationCfg = {manufSpec: 0, disDefaultRsp: 0}; + +const execute = (device, actions, callback, delay) => { + if (!device) { + callback(false, 'No device'); + return; + } + delay || (delay = 300); + const len = actions.length; + let nextActionIndex = 0; + + const next = () => { + if (nextActionIndex === len) { + callback(true, ''); + return; + } + + const nextAction = actions[nextActionIndex++]; + + setTimeout(nextAction, + delay, + (error) => { + debug(`Configured '${nextAction.toString()}' with result '${error ? error : 'OK'}'`); + if (error) { + callback(false, error); + return; + } + next(); + } + ); + }; + + next(); +}; + +const devices = [ + // Xiaomi + { + zigbeeModel: ['lumi.light.aqcn02'], + model: 'ZNLDP12LM', + vendor: 'Xiaomi', + description: 'Aqara smart LED bulb', + extend: generic.light_onoff_brightness_colortemp, + fromZigbee: [ + fz.brightness, fz.color_colortemp, fz.state_report, fz.xiaomi_bulb_interval, + fz.ignore_light_brightness_report, fz.ignore_light_color_colortemp_report, fz.ignore_onoff_change, + fz.ignore_basic_change, fz.ignore_occupancy_report, fz.ignore_temperature_change, + fz.ignore_humidity_change, fz.ignore_pressure_change, fz.ignore_humidity_report, + fz.ignore_pressure_report, fz.ignore_temperature_report, + ], + }, + { + zigbeeModel: ['lumi.sensor_switch'], + model: 'WXKG01LM', + vendor: 'Xiaomi', + description: 'MiJia wireless switch', + supports: 'single, double, triple, quadruple, many, long, long_release click', + fromZigbee: [fz.xiaomi_battery_3v, fz.WXKG01LM_click, fz.ignore_onoff_change, fz.ignore_basic_change], + toZigbee: [], + }, + { + zigbeeModel: ['lumi.sensor_switch.aq2', 'lumi.remote.b1acn01\u0000\u0000\u0000\u0000\u0000\u0000'], + model: 'WXKG11LM', + vendor: 'Xiaomi', + description: 'Aqara wireless switch', + supports: 'single, double click (and triple, quadruple, hold, release depending on model)', + fromZigbee: [ + fz.xiaomi_battery_3v, fz.WXKG11LM_click, fz.ignore_onoff_change, fz.ignore_basic_change, + fz.xiaomi_action_click_multistate, fz.ignore_multistate_change, + ], + toZigbee: [], + }, + { + zigbeeModel: ['lumi.sensor_switch.aq3', 'lumi.sensor_swit'], + model: 'WXKG12LM', + vendor: 'Xiaomi', + description: 'Aqara wireless switch (with gyroscope)', + supports: 'single, double, shake, hold, release', + fromZigbee: [ + fz.xiaomi_battery_3v, fz.WXKG12LM_action_click_multistate, fz.ignore_onoff_change, + fz.ignore_basic_change, fz.ignore_multistate_change, + ], + toZigbee: [], + }, + { + zigbeeModel: ['lumi.sensor_86sw1\u0000lu', 'lumi.remote.b186acn01\u0000\u0000\u0000'], + model: 'WXKG03LM', + vendor: 'Xiaomi', + description: 'Aqara single key wireless wall switch', + supports: 'single (and double, hold, release and long click depending on model)', + fromZigbee: [ + fz.xiaomi_battery_3v, fz.WXKG03LM_click, fz.ignore_basic_change, + fz.xiaomi_action_click_multistate, fz.ignore_multistate_change, + ], + toZigbee: [], + }, + { + zigbeeModel: ['lumi.sensor_86sw2\u0000Un', 'lumi.sensor_86sw2.es1', 'lumi.remote.b286acn01\u0000\u0000\u0000'], + model: 'WXKG02LM', + vendor: 'Xiaomi', + description: 'Aqara double key wireless wall switch', + supports: 'left, right, both click (and double, long click for left, right and both depending on model)', + fromZigbee: [ + fz.xiaomi_battery_3v, fz.WXKG02LM_click, fz.ignore_basic_change, + fz.WXKG02LM_click_multistate, fz.ignore_multistate_change, + ], + toZigbee: [], + ep: (device) => { + return {'left': 1, 'right': 2, 'both': 3}; + }, + }, + { + zigbeeModel: ['lumi.ctrl_neutral1'], + model: 'QBKG04LM', + vendor: 'Xiaomi', + // eslint-disable-next-line + description: 'Aqara single key wired wall switch without neutral wire. Doesn\'t work as a router and doesn\'t support power meter', + supports: 'release/hold, on/off', + fromZigbee: [ + fz.QBKG04LM_QBKG11LM_state, fz.QBKG04LM_buttons, + fz.QBKG04LM_QBKG11LM_operation_mode, fz.ignore_basic_report, + ], + toZigbee: [tz.on_off, tz.xiaomi_switch_operation_mode], + ep: (device) => { + return {'system': 1, 'default': 2}; + }, + }, + { + zigbeeModel: ['lumi.ctrl_ln1.aq1', 'lumi.ctrl_ln1'], + model: 'QBKG11LM', + vendor: 'Xiaomi', + description: 'Aqara single key wired wall switch', + supports: 'on/off, power measurement', + fromZigbee: [ + fz.QBKG04LM_QBKG11LM_state, fz.QBKG11LM_power, fz.QBKG04LM_QBKG11LM_operation_mode, + fz.ignore_onoff_change, fz.ignore_basic_change, fz.QBKG11LM_click, + fz.ignore_multistate_report, fz.ignore_multistate_change, fz.ignore_analog_change, fz.xiaomi_power, + ], + toZigbee: [tz.on_off, tz.xiaomi_switch_operation_mode], + }, + { + zigbeeModel: ['lumi.ctrl_neutral2'], + model: 'QBKG03LM', + 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', + fromZigbee: [ + fz.QBKG03LM_QBKG12LM_LLKZMK11LM_state, fz.QBKG03LM_buttons, + fz.QBKG03LM_QBKG12LM_operation_mode, fz.ignore_basic_report, + ], + toZigbee: [tz.on_off, tz.xiaomi_switch_operation_mode], + ep: (device) => { + return {'system': 1, 'left': 2, 'right': 3}; + }, + }, + { + zigbeeModel: ['lumi.ctrl_ln2.aq1'], + model: 'QBKG12LM', + vendor: 'Xiaomi', + description: 'Aqara double key wired wall switch', + supports: 'on/off, power measurement', + fromZigbee: [ + fz.QBKG03LM_QBKG12LM_LLKZMK11LM_state, fz.QBKG12LM_LLKZMK11LM_power, fz.QBKG03LM_QBKG12LM_operation_mode, + fz.ignore_analog_change, fz.ignore_basic_change, fz.QBKG12LM_click, + fz.ignore_multistate_report, fz.ignore_multistate_change, fz.ignore_onoff_change, fz.xiaomi_power, + ], + toZigbee: [tz.on_off, tz.xiaomi_switch_operation_mode], + ep: (device) => { + return {'left': 1, 'right': 2}; + }, + }, + { + zigbeeModel: ['lumi.sens', 'lumi.sensor_ht'], + model: 'WSDCGQ01LM', + vendor: 'Xiaomi', + 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.ignore_basic_change, + ], + toZigbee: [], + }, + { + zigbeeModel: ['lumi.weather'], + model: 'WSDCGQ11LM', + vendor: 'Xiaomi', + 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.ignore_basic_change, fz.ignore_temperature_change, fz.ignore_humidity_change, + fz.ignore_pressure_change, fz.WSDCGQ01LM_WSDCGQ11LM_interval, + ], + toZigbee: [], + }, + { + zigbeeModel: ['lumi.sensor_motion'], + model: 'RTCGQ01LM', + vendor: 'Xiaomi', + description: 'MiJia human body movement sensor', + supports: 'occupancy', + fromZigbee: [fz.xiaomi_battery_3v, fz.generic_occupancy_no_off_msg, fz.ignore_basic_change], + toZigbee: [], + }, + { + zigbeeModel: ['lumi.sensor_motion.aq2'], + model: 'RTCGQ11LM', + vendor: 'Xiaomi', + description: 'Aqara human body movement and illuminance sensor', + supports: 'occupancy and illuminance', + fromZigbee: [ + fz.xiaomi_battery_3v, fz.generic_occupancy_no_off_msg, fz.generic_illuminance, fz.ignore_basic_change, + fz.ignore_illuminance_change, fz.ignore_occupancy_change, fz.RTCGQ11LM_interval, + ], + toZigbee: [], + }, + { + zigbeeModel: ['lumi.sensor_magnet'], + model: 'MCCGQ01LM', + vendor: 'Xiaomi', + description: 'MiJia door & window contact sensor', + supports: 'contact', + fromZigbee: [ + fz.xiaomi_battery_3v, fz.xiaomi_contact, fz.ignore_onoff_change, + fz.ignore_basic_change, + ], + toZigbee: [], + }, + { + zigbeeModel: ['lumi.sensor_magnet.aq2'], + model: 'MCCGQ11LM', + vendor: 'Xiaomi', + description: 'Aqara door & window contact sensor', + supports: 'contact', + fromZigbee: [ + fz.xiaomi_battery_3v, fz.xiaomi_contact, fz.xiaomi_contact_interval, fz.ignore_onoff_change, + fz.ignore_basic_change, + ], + toZigbee: [], + }, + { + zigbeeModel: ['lumi.sensor_wleak.aq1'], + model: 'SJCGQ11LM', + 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], + toZigbee: [], + }, + { + zigbeeModel: ['lumi.sensor_cube', 'lumi.sensor_cube.aqgl01'], + model: 'MFKZQ01LM', + vendor: 'Xiaomi', + description: 'Mi/Aqara smart home cube', + supports: 'shake, wakeup, fall, tap, slide, flip180, flip90, rotate_left and rotate_right', + fromZigbee: [ + fz.xiaomi_battery_3v, fz.MFKZQ01LM_action_multistate, fz.MFKZQ01LM_action_analog, + fz.ignore_analog_change, fz.ignore_multistate_change, fz.ignore_basic_change, + ], + toZigbee: [], + }, + { + zigbeeModel: ['lumi.plug'], + model: 'ZNCZ02LM', + description: 'Mi power plug ZigBee', + 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', + description: 'Aqara socket Zigbee', + 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, + ], + toZigbee: [tz.on_off], + }, + { + zigbeeModel: ['lumi.sensor_smoke'], + model: 'JTYJ-GD-01LM/BW', + description: 'MiJia Honeywell smoke detector', + supports: 'smoke', + vendor: 'Xiaomi', + fromZigbee: [fz.xiaomi_battery_3v, fz.JTYJGD01LMBW_smoke, fz.ignore_basic_change], + toZigbee: [], + }, + { + zigbeeModel: ['lumi.sensor_natgas'], + model: 'JTQJ-BF-01LM/BW', + vendor: 'Xiaomi', + description: 'MiJia gas leak detector ', + supports: 'gas', + fromZigbee: [ + fz.JTQJBF01LMBW_gas, + fz.JTQJBF01LMBW_sensitivity, + fz.JTQJBF01LMBW_gas_density, + fz.ignore_basic_change, + ], + toZigbee: [tz.JTQJBF01LMBW_sensitivity, tz.JTQJBF01LMBW_selfest], + }, + { + zigbeeModel: ['lumi.lock.v1'], + model: 'A6121', + vendor: 'Xiaomi', + description: 'Vima Smart Lock', + supports: 'inserted, forgotten, key error', + fromZigbee: [fz.xiaomi_lock_report, fz.ignore_basic_change], + toZigbee: [], + }, + { + zigbeeModel: ['lumi.vibration.aq1'], + model: 'DJT11LM', + vendor: 'Xiaomi', + description: 'Aqara vibration sensor', + supports: 'drop, tilt and touch', + fromZigbee: [ + fz.xiaomi_battery_3v, fz.DJT11LM_vibration, fz.ignore_basic_change, fz.ignore_doorlock_change, + ], + toZigbee: [tz.DJT11LM_vibration_sensitivity], + }, + { + zigbeeModel: ['lumi.curtain'], + model: 'ZNCLDJ11LM', + description: 'Aqara curtain motor', + supports: 'open, close, stop, position', + vendor: 'Xiaomi', + fromZigbee: [ + fz.ZNCLDJ11LM_curtain_genAnalogOutput_change, fz.ZNCLDJ11LM_curtain_genAnalogOutput_report, + fz.ignore_closuresWindowCovering_change, fz.closuresWindowCovering_report, + fz.ignore_basic_change, fz.ignore_basic_report, + ], + toZigbee: [tz.ZNCLDJ11LM_control], + }, + { + zigbeeModel: ['lumi.relay.c2acn01'], + model: 'LLKZMK11LM', + vendor: 'Xiaomi', + description: 'Aqara wireless relay controller', + supports: 'on/off, power measurement', + fromZigbee: [ + fz.QBKG03LM_QBKG12LM_LLKZMK11LM_state, fz.QBKG12LM_LLKZMK11LM_power, fz.xiaomi_power, + fz.ignore_analog_change, fz.ignore_basic_change, + fz.ignore_multistate_report, fz.ignore_multistate_change, fz.ignore_onoff_change, + ], + toZigbee: [tz.on_off], + ep: (device) => { + return {'l1': 1, 'l2': 2}; + }, + }, + { + zigbeeModel: ['lumi.lock.acn02'], + model: 'ZNMS12LM', + description: 'Aqara S2 Lock', + supports: 'report: open, close, operation', + vendor: 'Xiaomi', + fromZigbee: [ + fz.ZNMS12LM_closuresDoorLock_report, fz.ignore_basic_report, + fz.ignore_doorlock_change, fz.ignore_basic_change, + ], + toZigbee: [], + }, + // IKEA + { + zigbeeModel: [ + 'TRADFRI bulb E27 WS opal 980lm', 'TRADFRI bulb E26 WS opal 980lm', + 'TRADFRI bulb E27 WS\uFFFDopal 980lm', + ], + model: 'LED1545G12', + vendor: 'IKEA', + description: 'TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['TRADFRI bulb E27 WS clear 950lm', 'TRADFRI bulb E26 WS clear 950lm'], + model: 'LED1546G12', + vendor: 'IKEA', + description: 'TRADFRI LED bulb E26/E27 950 lumen, dimmable, white spectrum, clear', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['TRADFRI bulb E27 opal 1000lm', 'TRADFRI bulb E27 W opal 1000lm'], + model: 'LED1623G12', + vendor: 'IKEA', + description: 'TRADFRI LED bulb E27 1000 lumen, dimmable, opal white', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['TRADFRI bulb GU10 WS 400lm'], + model: 'LED1537R6', + vendor: 'IKEA', + description: 'TRADFRI LED bulb GU10 400 lumen, dimmable, white spectrum', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['TRADFRI bulb GU10 W 400lm'], + model: 'LED1650R5', + vendor: 'IKEA', + description: 'TRADFRI LED bulb GU10 400 lumen, dimmable', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['TRADFRI bulb E14 WS opal 400lm', 'TRADFRI bulb E12 WS opal 400lm'], + model: 'LED1536G5', + vendor: 'IKEA', + description: 'TRADFRI LED bulb E12/E14 400 lumen, dimmable, white spectrum, opal white', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['TRADFRI bulb E14 WS opal 600lm'], + model: 'LED1733G7', + vendor: 'IKEA', + description: 'TRADFRI LED bulb E14 600 lumen, dimmable, white spectrum, opal white', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['TRADFRI bulb E26 opal 1000lm', 'TRADFRI bulb E26 W opal 1000lm'], + model: 'LED1622G12', + vendor: 'IKEA', + description: 'TRADFRI LED bulb E26 1000 lumen, dimmable, opal white', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['TRADFRI bulb E27 CWS opal 600lm', 'TRADFRI bulb E26 CWS opal 600lm'], + model: 'LED1624G9', + vendor: 'IKEA', + description: 'TRADFRI LED bulb E27/E26 600 lumen, dimmable, color, opal white', + extend: generic.light_onoff_brightness_colorxy, + }, + { + zigbeeModel: [ + 'TRADFRI bulb E14 W op/ch 400lm', 'TRADFRI bulb E12 W op/ch 400lm', + 'TRADFRI bulb E17 W op/ch 400lm', + ], + model: 'LED1649C5', + vendor: 'IKEA', + description: 'TRADFRI LED bulb E12/E14/E17 400 lumen, dimmable warm white, chandelier opal', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['TRADFRI bulb E27 WS opal 1000lm'], + model: 'LED1732G11', + vendor: 'IKEA', + description: 'TRADFRI LED bulb E27 1000 lumen, dimmable, white spectrum, opal white', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['TRADFRI wireless dimmer'], + model: 'ICTC-G-1', + vendor: 'IKEA', + description: 'TRADFRI wireless dimmer', + supports: 'brightness [0-255] (quick rotate for instant 0/255), action', + fromZigbee: [ + fz.cmd_move, fz.cmd_move_with_onoff, fz.cmd_stop, fz.cmd_stop_with_onoff, + fz.cmd_move_to_level_with_onoff, fz.generic_battery, 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: 0, maxRepIntval: repInterval.MAX, repChange: 0, + }; + + const actions = [ + (cb) => device.bind('genLevelCtrl', coordinator, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.foundation('genPowerCfg', 'configReport', [cfg], foundationCfg, cb), + ]; + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['TRADFRI transformer 10W', 'TRADFRI Driver 10W'], + model: 'ICPSHC24-10EU-IL-1', + vendor: 'IKEA', + description: 'TRADFRI driver for wireless control (10 watt)', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['TRADFRI transformer 30W', 'TRADFRI Driver 30W'], + model: 'ICPSHC24-30EU-IL-1', + vendor: 'IKEA', + description: 'TRADFRI driver for wireless control (30 watt)', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['FLOALT panel WS 30x30'], + model: 'L1527', + vendor: 'IKEA', + description: 'FLOALT LED light panel, dimmable, white spectrum (30x30 cm)', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['FLOALT panel WS 60x60'], + model: 'L1529', + vendor: 'IKEA', + description: 'FLOALT LED light panel, dimmable, white spectrum (60x60 cm)', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['FLOALT panel WS 30x90'], + model: 'L1528', + vendor: 'IKEA', + description: 'FLOALT LED light panel, dimmable, white spectrum (30x90 cm)', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['SURTE door WS 38x64'], + model: 'L1531', + vendor: 'IKEA', + description: 'SURTE door light panel, dimmable, white spectrum (38x64 cm)', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['TRADFRI control outlet'], + model: 'E1603/E1702', + description: 'TRADFRI control outlet', + supports: 'on/off', + vendor: 'IKEA', + fromZigbee: [fz.ignore_onoff_change, fz.state, fz.ignore_genLevelCtrl_report], + toZigbee: [tz.on_off], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const cfg = {direction: 0, attrId: 0, dataType: 16, minRepIntval: 0, maxRepIntval: 1000, repChange: 0}; + const actions = [ + (cb) => device.bind('genOnOff', coordinator, cb), + (cb) => device.foundation('genOnOff', 'configReport', [cfg], foundationCfg, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['TRADFRI remote control'], + model: 'E1524', + description: 'TRADFRI remote control', + supports: + 'toggle, arrow left/right click/hold/release, brightness up/down click/hold/release', + vendor: 'IKEA', + fromZigbee: [ + fz.cmdToggle, fz.E1524_arrow_click, fz.E1524_arrow_hold, fz.E1524_arrow_release, + fz.E1524_brightness_up_click, fz.E1524_brightness_down_click, fz.E1524_brightness_up_hold, + fz.E1524_brightness_up_release, fz.E1524_brightness_down_hold, fz.E1524_brightness_down_release, + fz.generic_battery, fz.ignore_power_change, fz.ignore_basic_change, fz.E1524_hold, + ], + toZigbee: [], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const cfg = { + direction: 0, attrId: 33, dataType: 32, minRepIntval: 0, maxRepIntval: repInterval.MAX, repChange: 0, + }; + + const actions = [ + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.foundation('genPowerCfg', 'configReport', [cfg], foundationCfg, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['TRADFRI on/off switch'], + model: 'E1743', + vendor: 'IKEA', + description: 'TRADFRI ON/OFF switch', + supports: 'on, off, brightness up/down/stop', + fromZigbee: [ + fz.genOnOff_cmdOn, fz.genOnOff_cmdOff, fz.E1743_brightness_up, fz.E1743_brightness_down, + fz.E1743_brightness_stop, fz.generic_battery, 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: 0, maxRepIntval: repInterval.MAX, repChange: 0, + }; + + const actions = [ + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.foundation('genPowerCfg', 'configReport', [cfg], foundationCfg, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['TRADFRI motion sensor'], + model: 'E1525', + vendor: 'IKEA', + description: 'TRADFRI motion sensor', + supports: 'occupancy', + fromZigbee: [fz.generic_battery, fz.ignore_power_change, fz.E1525_occupancy, fz.ignore_basic_change], + toZigbee: [], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const cfg = { + direction: 0, attrId: 33, dataType: 32, minRepIntval: 0, maxRepIntval: repInterval.MAX, repChange: 0, + }; + + const actions = [ + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.foundation('genPowerCfg', 'configReport', [cfg], foundationCfg, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['TRADFRI signal repeater'], + model: 'E1746', + description: 'TRADFRI signal repeater', + supports: '', + vendor: 'IKEA', + fromZigbee: [fz.ignore_basic_change, fz.ignore_diagnostic_change], + toZigbee: [], + }, + + // Philips + { + zigbeeModel: ['LLC012', 'LLC011'], + model: '7299760PH', + vendor: 'Philips', + description: 'Hue Bloom', + extend: hue.light_onoff_brightness_colorxy, + }, + { + zigbeeModel: ['LLC020'], + model: '7146060PH', + vendor: 'Philips', + description: 'Hue Go', + extend: hue.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['LCC001'], + model: '4090531P7', + vendor: 'Philips', + description: 'Hue Flourish white and color ambiance ceiling light', + extend: hue.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['LWB004'], + model: '433714', + vendor: 'Philips', + description: 'Hue Lux A19 bulb E27', + extend: hue.light_onoff_brightness, + }, + { + zigbeeModel: ['LWB006', 'LWB014'], + model: '9290011370', + vendor: 'Philips', + description: 'Hue white A60 bulb E27', + extend: hue.light_onoff_brightness, + }, + { + zigbeeModel: ['LWB010'], + model: '8718696449691', + vendor: 'Philips', + description: 'Hue White Single bulb B22', + extend: hue.light_onoff_brightness, + }, + { + zigbeeModel: ['LWG001'], + model: '9290018195', + vendor: 'Philips', + description: 'Hue white GU10', + extend: hue.light_onoff_brightness, + }, + { + zigbeeModel: ['LST001'], + model: '7299355PH', + vendor: 'Philips', + description: 'Hue white and color ambiance LightStrip', + extend: hue.light_onoff_brightness_colorxy, + }, + { + zigbeeModel: ['LST002'], + model: '915005106701', + vendor: 'Philips', + description: 'Hue white and color ambiance LightStrip plus', + extend: hue.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['LCT001', 'LCT007', 'LCT010', 'LCT012', 'LCT014', 'LCT015', 'LCT016'], + model: '9290012573A', + vendor: 'Philips', + description: 'Hue white and color ambiance E26/E27/E14', + extend: hue.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['LCT002'], + model: '9290002579A', + vendor: 'Philips', + description: 'Hue white and color ambiance BR30', + extend: hue.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['LCT003'], + model: '8718696485880', + vendor: 'Philips', + description: 'Hue white and color ambiance GU10', + extend: hue.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['LCT024'], + model: '915005733701', + vendor: 'Philips', + description: 'Hue White and color ambiance Play Lightbar', + extend: hue.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['LTW011'], + model: '464800', + vendor: 'Philips', + description: 'Hue white ambiance BR30 flood light', + extend: hue.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['LTW012'], + model: '8718696695203', + vendor: 'Philips', + description: 'Hue white ambiance E14', + extend: hue.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['LTW013'], + model: '8718696598283', + vendor: 'Philips', + description: 'Hue white ambiance GU10', + extend: hue.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['LTW015'], + model: '9290011998B', + vendor: 'Philips', + description: 'Hue white ambiance E26', + extend: hue.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['LTW010', 'LTW001', 'LTW004'], + model: '8718696548738', + vendor: 'Philips', + description: 'Hue white ambiance E26/E27', + extend: hue.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['LCW001'], + model: '4090130P7', + vendor: 'Philips', + description: 'Hue Sana', + extend: hue.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['LTC001'], + model: '3261030P7', + vendor: 'Philips', + description: 'Hue Being', + extend: hue.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['LTC003'], + model: '3261331P7', + vendor: 'Philips', + description: 'Hue white ambiance Still', + extend: hue.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['LTC011'], + model: '4096730U7', + vendor: 'Philips', + description: 'Hue Cher ceiling light', + extend: hue.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['LTC013'], + model: '3216131P5', + vendor: 'Philips', + description: 'Hue white ambiance Aurelle square panel light', + extend: hue.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['LTC015'], + model: '3216331P5', + vendor: 'Philips', + description: 'Hue white ambiance Aurelle rectangle panel light', + extend: hue.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['LTC016'], + model: '3216431P5', + vendor: 'Philips', + description: 'Hue white ambiance Aurelle round panel light', + extend: hue.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['LTP003', 'LTP001'], + model: '4033930P7', + vendor: 'Philips', + description: 'Hue white ambiance suspension Fair', + extend: hue.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['LWF002'], + model: '9290011370B', + vendor: 'Philips', + description: 'Hue white A60 bulb E27', + extend: hue.light_onoff_brightness, + }, + { + zigbeeModel: ['LWB015'], + model: '046677476816', + vendor: 'Philips', + description: 'Hue white PAR38 outdoor', + extend: hue.light_onoff_brightness, + }, + { + zigbeeModel: ['LLC010'], + model: '7199960PH', + vendor: 'Philips', + description: 'Hue Iris', + extend: hue.light_onoff_brightness_colorxy, + }, + { + zigbeeModel: ['RWL020', 'RWL021'], + model: '324131092621', + vendor: 'Philips', + description: 'Hue dimmer switch', + supports: 'on/off, brightness, up/down/hold/release, click count', + fromZigbee: [ + fz._324131092621_ignore_on, fz._324131092621_ignore_off, fz._324131092621_ignore_step, + fz._324131092621_ignore_stop, fz._324131092621_notification, + fz.ignore_power_change, fz.generic_battery_remaining, + ], + toZigbee: [], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const ep1 = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => ep1.bind('genOnOff', coordinator, cb), + (cb) => ep1.bind('genLevelCtrl', coordinator, cb), + ]; + + execute(ep1, actions, (result) => { + if (result) { + const ep2 = shepherd.find(ieeeAddr, 2); + const actions = [ + (cb) => ep2.foundation('genBasic', 'write', + [{attrId: 0x0031, dataType: 0x19, attrData: 0x000B}], + {manufSpec: 1, disDefaultRsp: 1, manufCode: 0x100B}, cb), + (cb) => ep2.bind('manuSpecificPhilips', coordinator, cb), + (cb) => ep2.bind('genPowerCfg', coordinator, cb), + (cb) => ep2.report('genPowerCfg', 'batteryPercentageRemaining', 0, 1000, 0, cb), + ]; + + execute(ep2, actions, callback); + } else { + callback(result); + } + }); + }, + }, + { + zigbeeModel: ['SML001'], + model: '9290012607', + vendor: 'Philips', + description: 'Hue motion sensor', + supports: 'occupancy, temperature, illuminance', + 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, + ], + toZigbee: [tz.occupancy_timeout, tz.hue_motion_sensitivity], + ep: (device) => { + return { + '': 2, // default + 'ep1': 1, + 'ep2': 2, // e.g. for write to msOccupancySensing + }; + }, + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 2); + + const actions = [ + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.bind('msIlluminanceMeasurement', coordinator, cb), + (cb) => device.bind('msTemperatureMeasurement', coordinator, cb), + (cb) => device.bind('msOccupancySensing', coordinator, cb), + (cb) => device.report('genPowerCfg', 'batteryPercentageRemaining', 0, 1000, 0, cb), + (cb) => device.report('msOccupancySensing', 'occupancy', 0, 600, null, cb), + (cb) => device.report('msTemperatureMeasurement', 'measuredValue', 30, 600, 1, cb), + (cb) => device.report('msIlluminanceMeasurement', 'measuredValue', 0, 600, null, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['SML002'], + model: '9290019758', + vendor: 'Philips', + description: 'Hue motion outdoor sensor', + supports: 'occupancy, temperature, illuminance', + 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, + ], + toZigbee: [tz.occupancy_timeout, tz.hue_motion_sensitivity], + ep: (device) => { + return { + '': 2, // default + 'ep1': 1, + 'ep2': 2, // e.g. for write to msOccupancySensing + }; + }, + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 2); + + const actions = [ + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.bind('msIlluminanceMeasurement', coordinator, cb), + (cb) => device.bind('msTemperatureMeasurement', coordinator, cb), + (cb) => device.bind('msOccupancySensing', coordinator, cb), + (cb) => device.report('genPowerCfg', 'batteryPercentageRemaining', 0, 1000, 0, cb), + (cb) => device.report('msOccupancySensing', 'occupancy', 0, 600, null, cb), + (cb) => device.report('msTemperatureMeasurement', 'measuredValue', 30, 600, 1, cb), + (cb) => device.report('msIlluminanceMeasurement', 'measuredValue', 0, 600, null, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['LLC014'], + model: '7099860PH', + vendor: 'Philips', + description: 'LivingColors Aura', + extend: hue.light_onoff_brightness_colorxy, + }, + { + zigbeeModel: ['LTC014'], + model: '3216231P5', + vendor: 'Philips', + description: 'Hue white ambiance Aurelle rectangle panel light', + extend: hue.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['1744530P7'], + model: '8718696170625', + vendor: 'Philips', + description: 'Hue Fuzo outdoor wall light', + extend: hue.light_onoff_brightness, + }, + + // Belkin + { + zigbeeModel: ['MZ100'], + model: 'F7C033', + vendor: 'Belkin', + description: 'WeMo smart LED bulb', + fromZigbee: [ + fz.brightness, fz.state_change, fz.state_report, fz.brightness_report, + fz.ignore_genGroups_devChange, fz.ignore_basic_change, + ], + supports: generic.light_onoff_brightness.supports, + toZigbee: generic.light_onoff_brightness.toZigbee, + }, + + // EDP + { + zigbeeModel: ['ZB-SmartPlug-1.0.0'], + model: 'PLUG EDP RE:DY', + vendor: 'EDP', + description: 're:dy plug', + 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, 85); + const actions = [ + (cb) => device.report('seMetering', 'instantaneousDemand', 10, 60, 1, cb), + (cb) => device.report('genOnOff', 'onOff', 1, 60, 1, cb), + ]; + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['ZB-RelayControl-1.0.0'], + model: 'SWITCH EDP RE:DY', + vendor: 'EDP', + description: 're:dy switch', + supports: 'on/off', + fromZigbee: [fz.state, fz.ignore_onoff_change], + toZigbee: [tz.on_off], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 85); + const actions = [ + (cb) => device.report('genOnOff', 'onOff', 1, 60, 1, cb), + ]; + execute(device, actions, callback); + }, + }, + + // Custom devices (DiY) + { + zigbeeModel: ['lumi.router'], + model: 'CC2530.ROUTER', + vendor: 'Custom devices (DiY)', + 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: [], + }, + { + zigbeeModel: ['DNCKAT_S001'], + model: 'DNCKATSW001', + vendor: 'Custom devices (DiY)', + description: '[DNCKAT single key wired wall light switch](https://github.com/dzungpv/dnckatsw00x/)', + supports: 'on/off', + fromZigbee: [fz.state, fz.ignore_onoff_change], + toZigbee: [tz.on_off], + }, + { + zigbeeModel: ['DNCKAT_S002'], + model: 'DNCKATSW002', + vendor: 'Custom devices (DiY)', + description: '[DNCKAT double key wired wall light switch](https://github.com/dzungpv/dnckatsw00x/)', + supports: 'hold/release, on/off', + fromZigbee: [fz.DNCKAT_S00X_state, fz.DNCKAT_S00X_buttons], + toZigbee: [tz.on_off], + ep: (device) => { + return {'left': 1, 'right': 2}; + }, + }, + { + zigbeeModel: ['DNCKAT_S003'], + model: 'DNCKATSW003', + vendor: 'Custom devices (DiY)', + description: '[DNCKAT triple key wired wall light switch](https://github.com/dzungpv/dnckatsw00x/)', + supports: 'hold/release, on/off', + fromZigbee: [fz.DNCKAT_S00X_state, fz.DNCKAT_S00X_buttons], + toZigbee: [tz.on_off], + ep: (device) => { + return {'left': 1, 'center': 2, 'right': 3}; + }, + }, + { + zigbeeModel: ['DNCKAT_S004'], + model: 'DNCKATSW004', + vendor: 'Custom devices (DiY)', + description: '[DNCKAT quadruple key wired wall light switch](https://github.com/dzungpv/dnckatsw00x/)', + supports: 'hold/release, on/off', + fromZigbee: [fz.DNCKAT_S00X_state, fz.DNCKAT_S00X_buttons], + toZigbee: [tz.on_off], + ep: (device) => { + return {'bottom_left': 1, 'bottom_right': 2, 'top_left': 3, 'top_right': 4}; + }, + }, + { + zigbeeModel: ['ZigUP'], + model: 'ZigUP', + vendor: 'Custom devices (DiY)', + description: '[CC2530 based ZigBee relais, switch, sensor and router](https://github.com/formtapez/ZigUP/)', + supports: 'relais, RGB-stripe, sensors, S0-counter, ADC, digital I/O', + fromZigbee: [fz.ZigUP_parse, fz.ignore_onoff_change], + toZigbee: [tz.on_off, tz.light_color, tz.ZigUP_lock], + }, + { + 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', + fromZigbee: [fz.DNCKAT_S00X_state], + toZigbee: [tz.on_off], + ep: (device) => { + return {'bottom_left': 1, 'bottom_right': 2, 'top_left': 3, 'top_right': 4, 'center': 5}; + }, + }, + + // eCozy + { + zigbeeModel: ['Thermostat'], + model: '1TST-EU', + vendor: 'eCozy', + description: 'Smart heating thermostat', + supports: 'temperature, occupancy, un-/occupied heating, schedule', + 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_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 = [ + // from https://github.com/ckpt-martin/Hubitat/blob/master/eCozy/eCozy-ZigBee-Thermostat-Driver.groovy + (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); + }, + }, + + // OSRAM + { + zigbeeModel: ['Outdoor Lantern W RGBW OSRAM'], + model: '4058075816718', + vendor: 'OSRAM', + description: 'SMART+ outdoor wall lantern RGBW', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['Classic A60 RGBW'], + model: 'AA69697', + vendor: 'OSRAM', + description: 'Classic A60 RGBW', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['CLA60 RGBW OSRAM'], + model: 'AC03645', + vendor: 'OSRAM', + description: 'LIGHTIFY LED CLA60 E27 RGBW', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['CLA60 TW OSRAM'], + model: 'AC03642', + vendor: 'OSRAM', + description: 'SMART+ CLASSIC A 60 TW', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['CLA60 RGBW Z3'], + model: 'AC03647', + vendor: 'OSRAM', + description: 'SMART+ LED CLASSIC E27 RGBW', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + { + // AA70155 is model number of both bulbs. + zigbeeModel: ['LIGHTIFY A19 Tunable White', 'Classic A60 TW'], + model: 'AA70155', + vendor: 'OSRAM', + description: 'LIGHTIFY LED A19 tunable white / Classic A60 TW', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['PAR16 50 TW'], + model: 'AA68199', + vendor: 'OSRAM', + description: 'LIGHTIFY LED PAR16 50 GU10 tunable white', + supports: generic.light_onoff_brightness_colortemp.supports, + toZigbee: generic.light_onoff_brightness_colortemp.toZigbee.concat([tz.osram_cmds]), + fromZigbee: generic.light_onoff_brightness_colortemp.fromZigbee, + }, + { + zigbeeModel: ['Classic B40 TW - LIGHTIFY'], + model: 'AB32840', + vendor: 'OSRAM', + description: 'LIGHTIFY LED Classic B40 tunable white', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['Ceiling TW OSRAM'], + model: '4058075816794', + vendor: 'OSRAM', + description: 'Smart+ Ceiling TW', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['Classic A60 W clear - LIGHTIFY'], + model: 'AC03641', + vendor: 'OSRAM', + description: 'LIGHTIFY LED Classic A60 clear', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['Surface Light W �C LIGHTIFY'], + model: '4052899926158', + vendor: 'OSRAM', + description: 'LIGHTIFY Surface Light TW', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['Surface Light TW'], + model: 'AB401130055', + vendor: 'OSRAM', + description: 'LIGHTIFY Surface Light LED Tunable White', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['Plug 01'], + model: 'AB3257001NJ', + description: 'Smart+ plug', + supports: 'on/off', + vendor: 'OSRAM', + fromZigbee: [fz.ignore_onoff_change, fz.state], + toZigbee: [tz.on_off], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 3); + const cfg = {direction: 0, attrId: 0, dataType: 16, minRepIntval: 0, maxRepIntval: 1000, repChange: 0}; + const actions = [ + (cb) => device.bind('genOnOff', coordinator, cb), + (cb) => device.foundation('genOnOff', 'configReport', [cfg], foundationCfg, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['Flex RGBW', 'LIGHTIFY Indoor Flex RGBW', 'LIGHTIFY Flex RGBW'], + model: '4052899926110', + vendor: 'OSRAM', + description: 'Flex RGBW', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['LIGHTIFY Outdoor Flex RGBW', 'LIGHTIFY FLEX OUTDOOR RGBW'], + model: '4058075036185', + vendor: 'OSRAM', + description: 'Outdoor Flex RGBW', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['Gardenpole RGBW-Lightify'], + model: '4058075036147', + vendor: 'OSRAM', + description: 'Smart+ gardenpole RGBW', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['Gardenpole Mini RGBW OSRAM'], + model: 'AC0363900NJ', + vendor: 'OSRAM', + description: 'Smart+ mini gardenpole RGBW', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['PAR 16 50 RGBW - LIGHTIFY'], + model: 'AB35996', + vendor: 'OSRAM', + description: 'Smart+ Spot GU10 Multicolor', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['B40 DIM Z3'], + model: 'AC08562', + vendor: 'OSRAM', + description: 'SMART+ Candle E14 Dimmable White', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['Motion Sensor-A'], + model: 'AC01353010G', + vendor: 'OSRAM', + description: 'SMART+ Motion Sensor', + supports: 'occupancy and temperature', + fromZigbee: [ + fz.generic_temperature, fz.ignore_temperature_change, fz.ias_zone_motion_dev_change, + fz.ias_zone_motion_status_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.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 23}, cb), + (cb) => device.bind('msTemperatureMeasurement', coordinator, cb), + (cb) => device.report('msTemperatureMeasurement', 'measuredValue', 30, 600, 1, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.report('genPowerCfg', 'batteryPercentageRemaining', 0, 1000, 0, cb), + ]; + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['MR16 TW OSRAM'], + model: 'AC03648', + vendor: 'OSRAM', + description: 'SMART+ spot GU5.3 tunable white', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['Lightify Switch Mini', 'Lightify Switch Mini\u0000'], + model: 'AC0251100NJ', + vendor: 'OSRAM', + description: 'Smart+ switch mini', + supports: 'circle, up, down and hold/release', + fromZigbee: [ + fz.AC0251100NJ_cmdOn, fz.AC0251100NJ_cmdMoveWithOnOff, fz.AC0251100NJ_cmdStop, + fz.AC0251100NJ_cmdMoveToColorTemp, fz.AC0251100NJ_cmdMoveHue, fz.AC0251100NJ_cmdMoveToSaturation, + fz.AC0251100NJ_cmdOff, fz.AC0251100NJ_cmdMove, fz.generic_batteryvoltage_3000_2500, + fz.AC0251100NJ_cmdMoveToLevelWithOnOff, + ], + toZigbee: [], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const ep1 = shepherd.find(ieeeAddr, 1); + const ep2 = shepherd.find(ieeeAddr, 2); + const ep3 = shepherd.find(ieeeAddr, 3); + + const actions = [ + (cb) => ep1.bind('genOnOff', coordinator, cb), + (cb) => ep1.bind('genLevelCtrl', coordinator, cb), + (cb) => ep2.bind('genOnOff', coordinator, cb), + (cb) => ep2.bind('genLevelCtrl', coordinator, cb), + (cb) => ep3.bind('genLevelCtrl', coordinator, cb), + (cb) => ep3.bind('lightingColorCtrl', coordinator, cb), + (cb) => ep1.bind('genPowerCfg', coordinator, cb), + (cb) => ep1.report('genPowerCfg', 'batteryVoltage', 900, 3600, 0, cb), + ]; + execute(ep1, actions, callback); + }, + + }, + { + zigbeeModel: ['SubstiTube'], + model: 'ST8AU-CON', + vendor: 'OSRAM', + description: 'OSRAM SubstiTUBE T8 Advanced UO Connected', + extend: generic.light_onoff_brightness, + }, + + + // Hive + { + zigbeeModel: ['FWBulb01'], + model: 'HALIGHTDIMWWE27', + vendor: 'Hive', + description: 'Active smart bulb white LED (E27)', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['FWBulb02UK'], + model: 'HALIGHTDIMWWB22', + vendor: 'Hive', + description: 'Active smart bulb white LED (B22)', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['SLP2b'], + model: '1613V', + vendor: 'Hive', + description: 'Active plug', + supports: 'on/off, power measurement', + fromZigbee: [ + fz.state, fz.ignore_onoff_change, fz.generic_power, fz.ignore_metering_change, + fz.generic_temperature, fz.ignore_temperature_change, + ], + toZigbee: [tz.on_off], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 9); + const cfg = {direction: 0, attrId: 0, dataType: 16, minRepIntval: 0, maxRepIntval: 1000, repChange: 0}; + const actions = [ + (cb) => device.bind('genOnOff', coordinator, cb), + (cb) => device.foundation('genOnOff', 'configReport', [cfg], foundationCfg, cb), + (cb) => device.report('seMetering', 'instantaneousDemand', 10, 60, 1, cb), + ]; + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['TWBulb01US'], + model: 'HV-GSCXZB269', + vendor: 'Hive', + description: 'Active light cool to warm white (E26) ', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['TWBulb01UK'], + model: 'HV-GSCXZB279_HV-GSCXZB229', + vendor: 'Hive', + description: 'Active light, warm to cool white (E27 & B22)', + extend: generic.light_onoff_brightness_colortemp, + }, + + // Innr + { + zigbeeModel: ['RB 185 C'], + model: 'RB 185 C', + vendor: 'Innr', + description: 'E27 bulb RGBW', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['BY 185 C'], + model: 'BY 185 C', + vendor: 'Innr', + description: 'B22 bulb RGBW', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['RB 250 C'], + model: 'RB 250 C', + vendor: 'Innr', + description: 'E14 bulb RGBW', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['RB 265'], + model: 'RB 265', + vendor: 'Innr', + description: 'E27 bulb', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['RB 278 T'], + model: 'RB 278 T', + vendor: 'Innr', + description: 'E27 bulb', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['RB 285 C'], + model: 'RB 285 C', + vendor: 'Innr', + description: 'E27 bulb RGBW', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['BY 285 C'], + model: 'BY 285 C', + vendor: 'Innr', + description: 'B22 bulb RGBW', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['RB 165'], + model: 'RB 165', + vendor: 'Innr', + description: 'E27 bulb', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['RB 175 W'], + model: 'RB 175 W', + vendor: 'Innr', + description: 'E27 bulb warm dimming', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['RB 178 T'], + model: 'RB 178 T', + vendor: 'Innr', + description: 'Smart bulb tunable white E27', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['RS 122'], + model: 'RS 122', + vendor: 'Innr', + description: 'GU10 spot', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['RS 125'], + model: 'RS 125', + vendor: 'Innr', + description: 'GU10 spot', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['RS 225'], + model: 'RS 225', + vendor: 'Innr', + description: 'GU10 Spot', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['RS 128 T'], + model: 'RS 128 T', + vendor: 'Innr', + description: 'GU10 spot 350 lm, dimmable, white spectrum', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['RS 228 T'], + model: 'RS 228 T', + vendor: 'Innr', + description: 'GU10 spot 350 lm, dimmable, white spectrum', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['RB 145'], + model: 'RB 145', + vendor: 'Innr', + description: 'E14 candle', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['RB 245'], + model: 'RB 245', + vendor: 'Innr', + description: 'E14 candle', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['RB 248 T'], + model: 'RB 248 T', + vendor: 'Innr', + description: 'E14 candle with white spectrum', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['BY 165', 'BY 265'], + model: 'BY 165', + vendor: 'Innr', + description: 'B22 bulb dimmable', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['PL 110'], + model: 'PL 110', + vendor: 'Innr', + description: 'Puck Light', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['ST 110'], + model: 'ST 110', + vendor: 'Innr', + description: 'Strip Light', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['UC 110'], + model: 'UC 110', + vendor: 'Innr', + description: 'Under cabinet light', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['DL 110 N'], + model: 'DL 110 N', + vendor: 'Innr', + description: 'Spot narrow', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['DL 110 W'], + model: 'DL 110 W', + vendor: 'Innr', + description: 'Spot wide', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['SL 110 N'], + model: 'SL 110 N', + vendor: 'Innr', + description: 'Spot Flex narrow', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['SL 110 M'], + model: 'SL 110 M', + vendor: 'Innr', + description: 'Spot Flex medium', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['SL 110 W'], + model: 'SL 110 W', + vendor: 'Innr', + description: 'Spot Flex wide', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['SP 120'], + model: 'SP 120', + vendor: 'Innr', + description: 'Smart plug', + supports: 'on/off, power measurement', + fromZigbee: [fz.ignore_electrical_change, fz.SP120_power, fz.state, fz.ignore_onoff_change], + 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 activePower = { + direction: 0, attrId: 1291, dataType: 41, minRepIntval: 1, maxRepIntval: 300, repChange: 1, + }; + + const rmsCurrent = { + direction: 0, attrId: 1288, dataType: 33, minRepIntval: 1, maxRepIntval: 300, repChange: 100, + }; + + const rmsVoltage = { + direction: 0, attrId: 1285, dataType: 33, minRepIntval: 1, maxRepIntval: 300, repChange: 1, + }; + + const electricalCfg = [rmsCurrent, rmsVoltage, activePower]; + const actions = [ + (cb) => device.foundation('genOnOff', 'configReport', [onOff], foundationCfg, cb), + (cb) => device.bind('genOnOff', coordinator, cb), + (cb) => device.foundation('haElectricalMeasurement', 'configReport', electricalCfg, foundationCfg, cb), + (cb) => device.bind('haElectricalMeasurement', coordinator, cb), + ]; + + execute(device, actions, callback); + }, + }, + + // Sylvania + { + zigbeeModel: ['LIGHTIFY RT Tunable White'], + model: '73742', + vendor: 'Sylvania', + description: 'LIGHTIFY LED adjustable white RT 5/6', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['LIGHTIFY BR Tunable White'], + model: '73740', + vendor: 'Sylvania', + description: 'LIGHTIFY LED adjustable white BR30', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['LIGHTIFY BR RGBW', 'BR30 RGBW'], + model: '73739', + vendor: 'Sylvania', + description: 'LIGHTIFY LED RGBW BR30', + supports: generic.light_onoff_brightness_colortemp_colorxy.supports, + toZigbee: generic.light_onoff_brightness_colortemp_colorxy.toZigbee.concat([tz.osram_cmds]), + fromZigbee: generic.light_onoff_brightness_colortemp_colorxy.fromZigbee.concat([ + fz.ignore_genIdentify_change, + fz.ignore_diagnostic_change, + fz.ignore_genScenes_change, + ]), + }, + { + zigbeeModel: ['LIGHTIFY A19 RGBW'], + model: '73693', + vendor: 'Sylvania', + description: 'LIGHTIFY LED RGBW A19', + supports: generic.light_onoff_brightness_colortemp_colorxy.supports, + toZigbee: generic.light_onoff_brightness_colortemp_colorxy.toZigbee.concat([tz.osram_cmds]), + fromZigbee: generic.light_onoff_brightness_colortemp_colorxy.fromZigbee, + }, + { + zigbeeModel: ['LIGHTIFY A19 ON/OFF/DIM', 'LIGHTIFY A19 ON/OFF/DIM 10 Year'], + model: '74283', + vendor: 'Sylvania', + description: 'LIGHTIFY LED soft white dimmable A19', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['A19 W 10 year'], + model: '74696', + vendor: 'Sylvania', + description: 'LIGHTIFY LED soft white dimmable A19', + supports: generic.light_onoff_brightness.supports, + toZigbee: generic.light_onoff_brightness.toZigbee.concat([tz.osram_cmds]), + fromZigbee: generic.light_onoff_brightness.fromZigbee.concat([ + fz.ignore_genIdentify_change, + fz.ignore_diagnostic_change, + fz.ignore_genScenes_change, + fz.ignore_light_color_colortemp_report, + ]), + }, + { + zigbeeModel: ['PLUG'], + model: '72922-A', + vendor: 'Sylvania', + description: 'SMART+ Smart Plug', + supports: 'on/off', + fromZigbee: [fz.ignore_onoff_change, fz.state], + toZigbee: [tz.on_off], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const cfg = {direction: 0, attrId: 0, dataType: 16, minRepIntval: 0, maxRepIntval: 1000, repChange: 0}; + const actions = [ + (cb) => device.bind('genOnOff', coordinator, cb), + (cb) => device.foundation('genOnOff', 'configReport', [cfg], foundationCfg, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['A19 TW 10 year'], + model: '71831', + vendor: 'Sylvania', + description: 'Smart Home adjustable white A19 LED bulb', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['MR16 TW'], + model: '74282', + vendor: 'Sylvania', + description: 'Smart Home adjustable white MR16 LED bulb', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['LIGHTIFY Gardenspot RGB'], + model: 'LTFY004', + vendor: 'Sylvania', + description: 'LIGHTIFY LED gardenspot mini RGB', + extend: generic.light_onoff_brightness_colorxy, + }, + { + zigbeeModel: ['PAR38 W 10 year'], + model: '74580', + vendor: 'Sylvania', + description: 'Smart Home soft white PAR38 outdoor bulb', + extend: generic.light_onoff_brightness, + }, + + // GE + { + zigbeeModel: ['ZLL Light'], + model: '22670', + vendor: 'GE', + description: 'Link smart LED light bulb, A19/BR30 soft white (2700K)', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['45852'], + model: '45852GE', + vendor: 'GE', + description: 'ZigBee plug-in smart dimmer', + supports: 'on/off, brightness', + fromZigbee: [fz.brightness, fz.ignore_onoff_change, fz.state], + toZigbee: [tz.light_onoff_brightness, tz.ignore_transition], + 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); + }, + }, + { + zigbeeModel: ['45853'], + model: '45853GE', + vendor: 'GE', + description: 'Plug-in smart switch', + supports: 'on/off', + fromZigbee: [fz.state, fz.ignore_onoff_change, fz.generic_power, fz.ignore_metering_change], + toZigbee: [tz.on_off, tz.ignore_transition], + 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), + (cb) => device.report('seMetering', 'instantaneousDemand', 10, 60, 1, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['45856'], + model: '45856GE', + vendor: 'GE', + description: 'In-wall smart switch', + supports: 'on/off', + fromZigbee: [fz.ignore_onoff_change, fz.state], + toZigbee: [tz.on_off, tz.ignore_transition], + 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); + }, + }, + { + zigbeeModel: ['45857'], + model: '45857GE', + vendor: 'GE', + description: 'ZigBee in-wall smart dimmer', + supports: 'on/off, brightness', + fromZigbee: [fz.brightness, fz.ignore_onoff_change, fz.state], + toZigbee: [tz.light_onoff_brightness, tz.ignore_transition], + 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); + }, + }, + + // Sengled + { + zigbeeModel: ['E11-G13'], + model: 'E11-G13', + vendor: 'Sengled', + description: 'Element Classic (A19)', + supports: generic.light_onoff_brightness.supports, + fromZigbee: generic.light_onoff_brightness.fromZigbee.concat([ + fz.ignore_metering_change, + fz.ignore_diagnostic_change, + ]), + toZigbee: generic.light_onoff_brightness.toZigbee, + }, + { + zigbeeModel: ['E11-G23', 'E11-G33'], + model: 'E11-G23/E11-G33', + vendor: 'Sengled', + description: 'Element Classic (A60)', + supports: generic.light_onoff_brightness.supports, + fromZigbee: generic.light_onoff_brightness.fromZigbee.concat([ + fz.ignore_metering_change, + fz.ignore_diagnostic_change, + ]), + toZigbee: generic.light_onoff_brightness.toZigbee, + }, + { + zigbeeModel: ['Z01-CIA19NAE26'], + model: 'Z01-CIA19NAE26', + vendor: 'Sengled', + description: 'Element Touch (A19)', + supports: generic.light_onoff_brightness.supports, + fromZigbee: generic.light_onoff_brightness.fromZigbee.concat([ + fz.ignore_metering_change, + fz.ignore_diagnostic_change, + ]), + toZigbee: generic.light_onoff_brightness.toZigbee, + }, + { + zigbeeModel: ['Z01-A19NAE26'], + model: 'Z01-A19NAE26', + vendor: 'Sengled', + description: 'Element Plus (A19)', + 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', + vendor: 'Sengled', + description: 'Element Plus Color (A19)', + supports: generic.light_onoff_brightness_colortemp_colorxy.supports, + fromZigbee: generic.light_onoff_brightness_colortemp_colorxy.fromZigbee.concat([ + fz.ignore_metering_change, + fz.ignore_diagnostic_change, + ]), + toZigbee: generic.light_onoff_brightness_colortemp_colorxy.toZigbee, + }, + { + zigbeeModel: ['E12-N14'], + model: 'E12-N14', + vendor: 'Sengled', + description: 'Element Classic (BR30)', + supports: generic.light_onoff_brightness.supports, + fromZigbee: generic.light_onoff_brightness.fromZigbee.concat([ + fz.ignore_metering_change, + fz.ignore_diagnostic_change, + ]), + toZigbee: generic.light_onoff_brightness.toZigbee, + }, + { + zigbeeModel: ['E1A-AC2'], + model: 'E1ACA4ABE38A', + vendor: 'Sengled', + description: 'Element downlight smart LED bulb', + extend: generic.light_onoff_brightness, + }, + + // Swann + { + zigbeeModel: ['SWO-KEF1PA'], + model: 'SWO-KEF1PA', + vendor: 'Swann', + description: 'Key fob remote', + supports: 'panic, home, away, sleep', + fromZigbee: [fz.KEF1PA_arm, fz.KEF1PA_panic], + toZigbee: [tz.factory_reset], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.bind('ssIasZone', coordinator, cb), + (cb) => device.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 23}, cb), + ]; + + execute(device, actions, callback, 250); + }, + }, + { + zigbeeModel: ['SWO-WDS1PA'], + model: 'SWO-WDS1PA', + vendor: 'Swann', + description: 'Window/door sensor', + supports: 'contact', + fromZigbee: [fz.ias_contact_dev_change, fz.ias_contact_status_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.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 23}, cb), + ]; + + execute(device, actions, callback, 1000); + }, + }, + + // JIAWEN + { + zigbeeModel: ['FB56-ZCW08KU1.1'], + model: 'K2RGBW01', + vendor: 'JIAWEN', + description: 'Wireless Bulb E27 9W RGBW', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + + // Netvox + { + zigbeeModel: ['Z809AE3R'], + model: 'Z809A', + vendor: 'Netvox', + description: 'Power socket with power consumption monitoring', + supports: 'on/off, power measurement', + fromZigbee: [fz.state, fz.ignore_onoff_change, fz.ignore_electrical_change, fz.Z809A_power], + toZigbee: [tz.on_off], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 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), + ]; + + execute(device, actions, callback); + }, + }, + + // Nanoleaf + { + zigbeeModel: ['NL08-0800'], + model: 'NL08-0800', + vendor: 'Nanoleaf', + description: 'Smart Ivy Bulb E27', + extend: generic.light_onoff_brightness, + }, + + // Nue, 3A + { + zigbeeModel: ['FTB56+ZSN15HG1.0'], + model: 'HGZB-1S', + vendor: 'Nue / 3A', + description: 'Smart 1 key scene wall switch', + supports: 'on/off, click', + toZigbee: [tz.on_off], + fromZigbee: [fz.nue_click, fz.ignore_power_report, fz.ignore_power_change], + }, + { + zigbeeModel: ['FTB56+ZSN16HG1.0'], + model: 'HGZB-02S', + vendor: 'Nue / 3A', + description: 'Smart 2 key scene wall switch', + supports: 'on/off, click', + toZigbee: [tz.on_off], + fromZigbee: [fz.nue_click, fz.ignore_power_report, fz.ignore_power_change], + }, + { + zigbeeModel: ['FB56+ZSN08KJ2.3'], + model: 'HGZB-045', + vendor: 'Nue / 3A', + description: 'Smart 4 key scene wall switch', + supports: 'on/off, click', + toZigbee: [tz.on_off], + fromZigbee: [fz.nue_click, fz.ignore_power_report, fz.ignore_power_change], + }, + { + zigbeeModel: ['LXN56-DC27LX1.1'], + model: 'LXZB-02A', + vendor: 'Nue / 3A', + description: 'Smart light controller', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['FNB56-ZSW03LX2.0'], + model: 'HGZB-43', + vendor: 'Nue / 3A', + description: 'Smart light switch - 3 gang v2.0', + 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: ['FB56+ZSW1IKJ1.7', 'FB56+ZSW1IKJ2.5'], + model: 'HGZB-043', + vendor: 'Nue / 3A', + description: 'Smart light switch - 3 gang', + supports: 'on/off', + fromZigbee: [fz.generic_state_multi_ep, fz.ignore_onoff_change], + toZigbee: [tz.on_off], + ep: (device) => { + return {'top': 16, 'center': 17, 'bottom': 18}; + }, + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const ep16 = shepherd.find(ieeeAddr, 16); + execute(ep16, [(cb) => ep16.bind('genOnOff', coordinator, cb)], () => { + const ep17 = shepherd.find(ieeeAddr, 17); + execute(ep17, [(cb) => ep17.bind('genOnOff', coordinator, cb)], () => { + const ep18 = shepherd.find(ieeeAddr, 18); + execute(ep18, [(cb) => ep18.bind('genOnOff', coordinator, cb)], callback); + }); + }); + }, + }, + { + zigbeeModel: ['FB56+ZSC05HG1.0'], + model: 'HGZB-04D', + vendor: 'Nue / 3A', + description: 'Smart dimmer wall switch', + supports: 'on/off, brightness', + toZigbee: [tz.on_off, tz.light_brightness], + fromZigbee: [fz.state, fz.ignore_onoff_change, fz.brightness_report, fz.ignore_light_brightness_change], + }, + { + zigbeeModel: ['FB56+ZSW1HKJ1.7', 'FB56+ZSW1HKJ2.5'], + model: 'HGZB-042', + vendor: 'Nue / 3A', + description: 'Smart light switch - 2 gang', + supports: 'on/off', + fromZigbee: [fz.generic_state_multi_ep, fz.ignore_onoff_change], + toZigbee: [tz.on_off], + ep: (device) => { + return {'top': 16, 'bottom': 17}; + }, + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const ep16 = shepherd.find(ieeeAddr, 16); + execute(ep16, [(cb) => ep16.bind('genOnOff', coordinator, cb)], () => { + const ep17 = shepherd.find(ieeeAddr, 17); + execute(ep17, [(cb) => ep17.bind('genOnOff', coordinator, cb)], callback); + }); + }, + }, + { + zigbeeModel: ['FNB56-ZSW02LX2.0'], + model: 'HGZB-42', + vendor: 'Nue / 3A', + description: 'Smart light switch - 2 gang. ', + supports: 'on/off', + fromZigbee: [fz.generic_state_multi_ep, fz.ignore_onoff_change], + toZigbee: [tz.on_off], + ep: (device) => { + return {'top': 11, 'bottom': 12}; + }, + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const ep11 = shepherd.find(ieeeAddr, 11); + execute(ep11, [(cb) => ep11.bind('genOnOff', coordinator, cb)], () => { + const ep12 = shepherd.find(ieeeAddr, 12); + execute(ep12, [(cb) => ep12.bind('genOnOff', coordinator, cb)], callback); + }); + }, + }, + { + 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', + vendor: 'Nue / 3A', + description: 'Smart one gang wall switch', + supports: 'on/off', + fromZigbee: [fz.state, fz.ignore_onoff_change], + toZigbee: [tz.on_off], + }, + { + zigbeeModel: ['FNB56-SKT1DHG1.4'], + model: 'MG-AUWS01', + vendor: 'Nue / 3A', + description: 'Smart Double GPO', + supports: 'on/off', + fromZigbee: [fz.nue_power_state, fz.ignore_onoff_change], + toZigbee: [tz.on_off], + ep: (device) => { + 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', + vendor: 'Nue / 3A', + description: 'Smart light controller RGBW', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['FNB56-ZSC01LX1.2'], + model: 'HGZB-02A', + vendor: 'Nue / 3A', + description: 'Smart light controller', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['FNB56-ZSW01LX2.0'], + model: 'HGZB-42-UK / HGZB-41', + description: 'Smart switch 1 or 2 gang', + vendor: 'Nue / 3A', + supports: 'on/off', + fromZigbee: [fz.ignore_onoff_change, fz.state], + toZigbee: [tz.on_off], + }, + { + zigbeeModel: ['FNB56-ZCW25FB1.6'], + model: 'HGZB-06A', + vendor: 'Nue / 3A', + description: 'Smart 7W E27 light bulb', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + + // Smart Home Pty + { + zigbeeModel: ['FB56-ZCW11HG1.2'], + model: 'HGZB-07A', + vendor: 'Smart Home Pty', + description: 'RGBW Downlight', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['FNB56-SKT1EHG1.2'], + model: 'HGZB-20-DE', + vendor: 'Smart Home Pty', + description: 'Power plug', + supports: 'on/off', + fromZigbee: [fz.state_change], + toZigbee: [tz.on_off], + }, + + // Gledopto + { + zigbeeModel: ['GLEDOPTO', 'GL-C-008', 'GL-C-007'], + model: 'GL-C-008', + vendor: 'Gledopto', + description: 'Zigbee LED controller RGB + CCT / RGBW / WWCW / Dimmer', + extend: gledopto.light_onoff_brightness_colortemp_colorxy, + ep: (device) => { + if (device.epList.toString() === '11,12,13') { + return {'': 12}; + } else if (device.epList.toString() === '10,11,13' || device.epList.toString() === '11,13') { + return {'': 11}; + } else if (device.epList.toString() === '11,12,13,15') { + return { + 'rgb': 12, + 'white': 15, + }; + } else if (device.epList.toString() === '11,13,15') { + return { + 'rgb': 11, + 'white': 15, + }; + } else { + return {}; + } + }, + }, + { + zigbeeModel: ['GL-S-004Z'], + model: 'GL-S-004Z', + vendor: 'Gledopto', + description: 'Zigbee Smart WW/CW GU10', + extend: gledopto.light_onoff_brightness_colortemp, + ep: (device) => { + if (device.epList.toString() === '11,12,13') { + return {'': 12}; + } else if (device.epList.toString() === '10,11,13' || device.epList.toString() === '11,13') { + return {'': 11}; + } else { + return {}; + } + }, + }, + { + zigbeeModel: ['GL-C-006', 'GL-C-009'], + model: 'GL-C-006/GL-C-009', + vendor: 'Gledopto', + description: 'Zigbee LED controller WW/CW Dimmer', + extend: gledopto.light_onoff_brightness_colortemp, + ep: (device) => { + if (device.epList.toString() === '11,12,13') { + return {'': 12}; + } else if (device.epList.toString() === '10,11,13' || device.epList.toString() === '11,13') { + return {'': 11}; + } else { + return {}; + } + }, + }, + { + zigbeeModel: ['GL-S-007Z'], + model: 'GL-S-007Z', + vendor: 'Gledopto', + description: 'Smart RGBW GU10', + extend: gledopto.light_onoff_brightness_colortemp_colorxy, + ep: (device) => { + if (device.epList.toString() === '11,12,13') { + return {'': 12}; + } else if (device.epList.toString() === '10,11,13' || device.epList.toString() === '11,13') { + return {'': 11}; + } else { + return {}; + } + }, + }, + { + zigbeeModel: ['GL-B-001Z'], + model: 'GL-B-001Z', + vendor: 'Gledopto', + description: 'Smart 4W E14 RGB / CW LED bulb', + extend: gledopto.light_onoff_brightness_colortemp_colorxy, + ep: (device) => { + if (device.epList.toString() === '11,12,13') { + return {'': 12}; + } else if (device.epList.toString() === '10,11,13' || device.epList.toString() === '11,13') { + return {'': 11}; + } else { + return {}; + } + }, + }, + { + zigbeeModel: ['GL-G-001Z'], + model: 'GL-G-001Z', + vendor: 'Gledopto', + description: 'Smart garden lamp', + extend: gledopto.light_onoff_brightness_colortemp_colorxy, + ep: (device) => { + if (device.epList.toString() === '11,12,13') { + return {'': 12}; + } else if (device.epList.toString() === '10,11,13' || device.epList.toString() === '11,13') { + return {'': 11}; + } else { + return {}; + } + }, + }, + { + zigbeeModel: ['GL-B-007Z'], + model: 'GL-B-007Z', + vendor: 'Gledopto', + description: 'Smart 6W E27 RGB / CW LED bulb', + extend: gledopto.light_onoff_brightness_colortemp_colorxy, + ep: (device) => { + if (device.epList.toString() === '11,12,13') { + return {'': 12}; + } else if (device.epList.toString() === '10,11,13' || device.epList.toString() === '11,13') { + return {'': 11}; + } else { + return {}; + } + }, + }, + { + zigbeeModel: ['GL-B-008Z'], + model: 'GL-B-008Z', + vendor: 'Gledopto', + description: 'Smart 12W E27 RGB / CW LED bulb', + extend: gledopto.light_onoff_brightness_colortemp_colorxy, + ep: (device) => { + if (device.epList.toString() === '11,12,13') { + return {'': 12}; + } else if (device.epList.toString() === '10,11,13' || device.epList.toString() === '11,13') { + return {'': 11}; + } else { + return {}; + } + }, + }, + { + zigbeeModel: ['GL-D-003Z', 'GL-D-005Z'], + model: 'GL-D-003Z', + vendor: 'Gledopto', + description: 'LED RGB + CCT downlight ', + extend: gledopto.light_onoff_brightness_colortemp_colorxy, + ep: (device) => { + if (device.epList.toString() === '11,12,13') { + return {'': 12}; + } else if (device.epList.toString() === '10,11,13') { + return {'': 11}; + } else { + return {}; + } + }, + }, + { + zigbeeModel: ['GL-S-003Z'], + model: 'GL-S-003Z', + vendor: 'Gledopto', + description: 'Smart RGBW GU10 ', + extend: gledopto.light_onoff_brightness_colortemp_colorxy, + ep: (device) => { + if (device.epList.toString() === '11,12,13') { + return {'': 12}; + } else if (device.epList.toString() === '10,11,13') { + return {'': 11}; + } else { + return {}; + } + }, + }, + { + zigbeeModel: ['HOMA2023'], + model: 'GD-CZ-006', + vendor: 'Gledopto', + description: 'Zigbee LED Driver', + extend: gledopto.light_onoff_brightness, + }, + { + zigbeeModel: ['GL-FL-004TZ'], + model: 'GL-FL-004TZ', + vendor: 'Gledopto', + description: 'Zigbee 10W floodlight RGB CCT', + extend: generic.light_onoff_brightness_colortemp_colorxy, + ep: (device) => { + if (device.epList.toString() === '11,12,13') { + return {'': 12}; + } else if (device.epList.toString() === '10,11,13' || device.epList.toString() === '11,13') { + return {'': 11}; + } else { + return {}; + } + }, + }, + + // ROBB + { + zigbeeModel: ['ROB_200-004-0'], + model: 'ROB_200-004-0', + vendor: 'ROBB', + description: 'ZigBee AC phase-cut dimmer', + supports: 'on/off, brightness', + fromZigbee: [fz.brightness, fz.ignore_onoff_change, fz.state, fz.ignore_light_brightness_report], + toZigbee: [tz.light_onoff_brightness, tz.ignore_transition], + 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), + (cb) => device.bind('genLevelCtrl', coordinator, cb), + ]; + + execute(device, actions, callback); + }, + }, + + // SmartThings + { + zigbeeModel: ['PGC313'], + model: 'STSS-MULT-001', + vendor: 'SmartThings', + description: 'Multipurpose sensor', + supports: 'contact', + fromZigbee: [fz.smartthings_contact], + toZigbee: [], + }, + { + zigbeeModel: ['tagv4'], + model: 'STS-PRS-251', + vendor: 'SmartThings', + description: 'Arrival sensor', + supports: 'presence', + fromZigbee: [ + fz.STS_PRS_251_presence, fz.generic_batteryvoltage_3000_2500, fz.ignore_power_change, + fz.STS_PRS_251_beeping, + ], + toZigbee: [tz.STS_PRS_251_beep], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.report('genBinaryInput', 'presentValue', 10, 30, 1, cb), + (cb) => device.report('genPowerCfg', 'batteryVoltage', 1800, 3600, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['3325-S'], + model: '3325-S', + vendor: 'SmartThings', + description: 'Motion sensor (2015 model)', + supports: 'occupancy and temperature', + fromZigbee: [ + fz.generic_temperature, fz.ignore_temperature_change, fz.ias_zone_motion_dev_change, + fz.ias_zone_motion_status_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.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 23}, cb), + (cb) => device.bind('msTemperatureMeasurement', coordinator, cb), + (cb) => device.report('msTemperatureMeasurement', 'measuredValue', 30, 600, 1, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.report('genPowerCfg', 'batteryPercentageRemaining', 0, 1000, 0, cb), + ]; + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['3321-S'], + model: '3321-S', + vendor: 'SmartThings', + description: 'Multi Sensor (2015 model)', + supports: 'contact and temperature', + fromZigbee: [ + fz.generic_temperature, fz.ignore_temperature_change, fz.smartsense_multi, + ], + 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.write('ssIasZone', 'iasCieAddr', coordinator.device.getIeeeAddr(), cb), + (cb) => device.report('ssIasZone', 'zoneStatus', 0, 1000, null, cb), + (cb) => device.functional('ssIasZone', 'enrollRsp', { + enrollrspcode: 1, + zoneid: 23, + }, cb), + ]; + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['outlet'], + model: 'IM6001-OTP05', + vendor: 'SmartThings', + description: 'Outlet', + supports: 'on/off', + fromZigbee: [fz.state, fz.ignore_onoff_report], + toZigbee: [tz.on_off], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const cfg = {direction: 0, attrId: 0, dataType: 16, minRepIntval: 0, maxRepIntval: 1000, repChange: 0}; + const actions = [ + (cb) => device.bind('genOnOff', coordinator, cb), + (cb) => device.foundation('genOnOff', 'configReport', [cfg], foundationCfg, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['motion'], + model: 'IM6001-MTP01', + vendor: 'SmartThings', + description: 'Motion sensor (2018 model)', + supports: 'occupancy and temperature', + fromZigbee: [ + fz.generic_temperature, fz.ignore_temperature_change, + fz.ignore_iaszone_report, fz.generic_ias_zone_motion_dev_change, + fz.generic_ias_zone_occupancy_status_change, fz.generic_batteryvoltage_3000_2500, + ], + 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('msTemperatureMeasurement', coordinator, cb), + (cb) => device.report('msTemperatureMeasurement', 'measuredValue', 30, 600, 1, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.report('genPowerCfg', 'batteryVoltage', 0, 1000, 0, cb), + ]; + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['motionv4'], + model: 'STS-IRM-250', + vendor: 'SmartThings', + description: 'Motion sensor (2016 model)', + supports: 'occupancy and temperature', + fromZigbee: [ + fz.generic_temperature, fz.ignore_temperature_change, fz.ias_zone_motion_dev_change, + fz.generic_ias_zone_occupancy_status_change, fz.generic_batteryvoltage_3000_2500, + ], + 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('msTemperatureMeasurement', coordinator, cb), + (cb) => device.report('msTemperatureMeasurement', 'measuredValue', 30, 600, 1, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.report('genPowerCfg', 'batteryVoltage', 0, 1000, 0, cb), + ]; + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['3305-S'], + model: '3305-S', + vendor: 'SmartThings', + description: 'Motion sensor (2014 model)', + supports: 'occupancy and temperature', + fromZigbee: [ + fz.generic_temperature, fz.ignore_temperature_change, fz.ias_zone_motion_dev_change, + fz.ias_zone_motion_status_change, fz.generic_batteryvoltage_3000_2500, fz.ignore_power_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.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 255}, cb), + (cb) => device.bind('msTemperatureMeasurement', coordinator, cb), + (cb) => device.report('msTemperatureMeasurement', 'measuredValue', 30, 600, 1, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.report('genPowerCfg', 'batteryVoltage', 0, 1000, 0, cb), + ]; + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['3300-S'], + model: '3300-S', + vendor: 'SmartThings', + description: 'Door sensor', + supports: 'contact and temperature', + fromZigbee: [ + fz.generic_temperature, fz.ignore_temperature_change, fz.smartsense_multi, + fz.ias_contact_status_change, fz.ignore_iaszone_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: ['multiv4'], + model: 'F-MLT-US-2', + vendor: 'SmartThings', + description: 'Multipurpose sensor (2016 model)', + supports: 'contact', + fromZigbee: [ + fz.generic_temperature, fz.ignore_temperature_change, fz.st_contact_status_change, + fz.generic_batteryvoltage_3000_2500, fz.ias_contact_dev_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.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 23}, cb), + (cb) => device.bind('msTemperatureMeasurement', coordinator, cb), + (cb) => device.report('msTemperatureMeasurement', 'measuredValue', 30, 600, 1, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.report('genPowerCfg', 'batteryVoltage', 0, 1000, 0, cb), + ]; + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['multi'], + model: 'IM6001-MPP01', + vendor: 'SmartThings', + description: 'Multipurpose sensor (2018 model)', + supports: 'contact', + fromZigbee: [ + fz.generic_temperature, fz.ignore_temperature_change, fz.st_contact_status_change, + fz.generic_batteryvoltage_3000_2500, fz.ignore_iaszone_change, fz.ignore_iaszone_attreport, + ], + 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('msTemperatureMeasurement', coordinator, cb), + (cb) => device.report('msTemperatureMeasurement', 'measuredValue', 30, 600, 1, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.report('genPowerCfg', 'batteryVoltage', 0, 1000, 0, cb), + ]; + execute(device, actions, callback); + }, + }, + { + /** + * Note: humidity not (yet) implemented, as this seems to use proprietary cluster + * see Smartthings device handler (profileID: 0x9194, clusterId: 0xFC45 + * https://github.com/SmartThingsCommunity/SmartThingsPublic/blob/861ec6b88eb45273e341436a23d35274dc367c3b/ + * devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy#L153-L156 + */ + zigbeeModel: ['3310-S'], + model: '3310-S', + vendor: 'SmartThings', + description: 'Temperature and humidity sensor', + supports: 'temperature', + fromZigbee: [ + fz.generic_temperature, fz.ignore_temperature_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', 0, 1000, 0, cb), + ]; + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['3315-S'], + model: '3315-S', + vendor: 'SmartThings', + description: 'Water sensor', + supports: 'water 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', + vendor: 'SmartThings', + description: 'Water sensor', + supports: 'water 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: ['button'], + model: 'IM6001-BTP01', + vendor: 'SmartThings', + description: 'Button', + supports: 'single click, double click, hold and temperature', + fromZigbee: [ + fz.st_button_state, + fz.generic_battery_change, + fz.generic_temperature, + fz.ignore_basic_change, + fz.ignore_diagnostic_change, + fz.ignore_genIdentify_change, + fz.ignore_iaszone_attreport, + fz.ignore_iaszone_change, + fz.ignore_poll_ctrl_change, + fz.ignore_temperature_change, + fz.ignore_temperature_report, + ], + 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), + ]; + execute(device, actions, callback); + }, + }, + + // Trust + { + zigbeeModel: ['\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'+ + '\u0000\u0000\u0000\u0000\u0000'], + model: 'ZYCT-202', + vendor: 'Trust', + description: 'Remote control', + supports: 'on, off, stop, up-press, down-press', + fromZigbee: [ + fz.ZYCT202_on, fz.ZYCT202_off, fz.ZYCT202_stop, fz.ZYCT202_up_down, + ], + toZigbee: [], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const cfg = {direction: 0, attrId: 0, dataType: 16, minRepIntval: 0, maxRepIntval: 1000, repChange: 0}; + const actions = [ + (cb) => device.foundation('genOnOff', 'configReport', [cfg], foundationCfg, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['ZLL-DimmableLigh'], + model: 'ZLED-2709', + vendor: 'Trust', + description: 'Smart Dimmable LED Bulb', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['VMS_ADUROLIGHT'], + model: 'ZPIR-8000', + vendor: 'Trust', + description: 'Motion Sensor', + supports: 'occupancy', + fromZigbee: [fz.ias_zone_motion_dev_change, fz.ias_zone_motion_status_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.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 23}, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['CSW_ADUROLIGHT'], + model: 'ZCTS-808', + vendor: 'Trust', + description: 'Wireless contact sensor', + supports: 'contact', + fromZigbee: [fz.ias_contact_dev_change, fz.ias_contact_status_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.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 23}, cb), + ]; + + execute(device, actions, callback); + }, + }, + + // Paulmann + { + zigbeeModel: ['Switch Controller '], + model: '50043', + vendor: 'Paulmann', + description: 'SmartHome Zigbee Cephei Switch Controller', + supports: 'on/off', + fromZigbee: [fz.state, fz.state_change], + toZigbee: [tz.on_off], + }, + { + zigbeeModel: ['Dimmablelight '], + model: '50045', + vendor: 'Paulmann', + description: 'SmartHome Zigbee LED-stripe', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['RGBW light'], + model: '50049', + vendor: 'Paulmann', + description: 'SmartHome Yourled RGB Controller', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['CCT light'], + model: '50064', + vendor: 'Paulmann', + description: 'SmartHome led spot', + extend: generic.light_onoff_brightness_colortemp, + }, + + // Bitron + { + zigbeeModel: ['AV2010/34'], + model: 'AV2010/34', + vendor: 'Bitron', + description: '4-Touch single click buttons', + supports: 'click', + fromZigbee: [fz.ignore_power_report, fz.AV2010_34_click], + toZigbee: [], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.bind('genPowerCfg', coordinator, cb), + ]; + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['902010/22'], + model: 'AV2010/22', + vendor: 'Bitron', + description: 'Wireless motion detector', + supports: 'occupancy', + fromZigbee: [fz.bitron_occupancy], + 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: ['902010/25'], + model: 'AV2010/25', + vendor: 'Bitron', + description: 'Video wireless socket', + supports: 'on/off, power measurement', + fromZigbee: [fz.state, fz.ignore_onoff_change, fz.ignore_metering_change, fz.bitron_power], + toZigbee: [tz.on_off], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.report('seMetering', 'instantaneousDemand', 10, 60, 1, cb), + (cb) => device.bind('genOnOff', coordinator, cb), + ]; + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['902010/32'], + model: 'AV2010/32', + vendor: 'Bitron', + description: 'Wireless wall thermostat with relay', + supports: 'temperature, heating/cooling system control', + fromZigbee: [ + fz.ignore_basic_change, fz.bitron_thermostat_att_report, + fz.bitron_thermostat_dev_change, fz.bitron_battery_att_report, + fz.bitron_battery_dev_change, + ], + toZigbee: [ + tz.thermostat_occupied_heating_setpoint, tz.thermostat_local_temperature_calibration, + tz.thermostat_local_temperature, tz.thermostat_running_state, + tz.thermostat_temperature_display_mode, + ], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + 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', 300, 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), + (cb) => device.report('genPowerCfg', 'batteryVoltage', 1800, 43200, 0, cb), + (cb) => device.report('genPowerCfg', 'batteryAlarmState', 1, 0, 1, cb), + ]; + + execute(device, actions, callback); + }, + }, + + // Iris + { + zigbeeModel: ['3210-L'], + model: '3210-L', + vendor: 'Iris', + description: 'Smart plug', + supports: 'on/off', + fromZigbee: [fz.ignore_onoff_change, fz.ignore_electrical_change, fz.state, fz.iris_3210L_power], + toZigbee: [tz.on_off], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.report('haElectricalMeasurement', 'activePower', 10, 1000, 1, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['3326-L'], + model: '3326-L', + vendor: 'Iris', + description: 'Motion and temperature sensor', + supports: 'occupancy and temperature', + fromZigbee: [ + fz.generic_temperature, fz.ignore_temperature_change, fz.ias_zone_motion_dev_change, + fz.ias_zone_motion_status_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.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 23}, cb), + (cb) => device.bind('msTemperatureMeasurement', coordinator, cb), + (cb) => device.report('msTemperatureMeasurement', 'measuredValue', 30, 600, 1, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.report('genPowerCfg', 'batteryPercentageRemaining', 0, 1000, 0, cb), + ]; + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['3320-L'], + model: '3320-L', + vendor: 'Iris', + description: 'Contact sensor', + supports: 'contact', + fromZigbee: [fz.iris_3320L_contact], + 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), + ]; + execute(device, actions, callback, 1000); + }, + }, + + // ksentry + { + zigbeeModel: ['Lamp_01'], + model: 'KS-SM001', + vendor: 'Ksentry Electronics', + description: '[Zigbee OnOff Controller](http://ksentry.manufacturer.globalsources.com/si/6008837134660'+ + '/pdtl/ZigBee-module/1162731630/zigbee-on-off-controller-modules.htm)', + supports: 'on/off', + fromZigbee: [fz.state_change, fz.state], + toZigbee: [tz.on_off], + }, + + // Ninja Blocks + { + zigbeeModel: ['Ninja Smart plug'], + model: 'Z809AF', + vendor: 'Ninja Blocks', + description: 'Zigbee smart plug with power meter', + supports: 'on/off, power measurement', + fromZigbee: [fz.ignore_onoff_change, fz.state, fz.generic_power, fz.ignore_metering_change], + toZigbee: [tz.on_off], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const cfg = {direction: 0, attrId: 0, dataType: 16, minRepIntval: 0, maxRepIntval: 1000, repChange: 0}; + const actions = [ + (cb) => device.foundation('genOnOff', 'configReport', [cfg], foundationCfg, cb), + (cb) => device.bind('genOnOff', coordinator, cb), + (cb) => device.report('seMetering', 'instantaneousDemand', 10, 60, 1, cb), + ]; + execute(device, actions, callback); + }, + }, + + // Commercial Electric + { + zigbeeModel: ['Zigbee CCT Downlight'], + model: '53170161', + vendor: 'Commercial Electric', + description: 'Matte White Recessed Retrofit Smart Led Downlight - 4 Inch', + extend: generic.light_onoff_brightness_colortemp, + }, + + // ilux + { + zigbeeModel: ['LEColorLight'], + model: '900008-WW', + vendor: 'ilux', + description: 'Dimmable A60 E27 LED Bulb', + extend: generic.light_onoff_brightness, + }, + + // Dresden Elektronik + { + zigbeeModel: ['FLS-PP3'], + model: 'Mega23M12', + vendor: 'Dresden Elektronik', + description: 'ZigBee Light Link wireless electronic ballast', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['FLS-CT'], + model: 'XVV-Mega23M12', + vendor: 'Dresden Elektronik', + description: 'ZigBee Light Link wireless electronic ballast color temperature', + extend: generic.light_onoff_brightness_colortemp, + }, + + // Centralite Swiss Plug + { + zigbeeModel: ['4256251-RZHAC', '4257050-RZHAC', '4257050-ZHAC'], + model: '4256251-RZHAC', + vendor: 'Centralite', + description: 'White Swiss power outlet switch with power meter', + supports: 'switch and power meter', + fromZigbee: [fz.ignore_onoff_change, fz.state, fz.ignore_electrical_change, fz.RZHAC_4256251_power], + toZigbee: [tz.on_off], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const cfg = {direction: 0, attrId: 0, dataType: 16, minRepIntval: 0, maxRepIntval: 1000, repChange: 0}; + const actions = [ + (cb) => device.bind('genOnOff', coordinator, cb), + (cb) => device.foundation('genOnOff', 'configReport', [cfg], foundationCfg, cb), + (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), + ]; + + execute(device, actions, callback); + }, + }, + + // Blaupunkt + { + zigbeeModel: ['SCM-R_00.00.03.15TC'], + model: 'SCM-S1', + vendor: 'Blaupunkt', + description: 'Roller shutter', + supports: 'open/close', + fromZigbee: [fz.cover_position_report, fz.cover_position, fz.cover_state_change, fz.cover_state_report], + toZigbee: [tz.cover_position, tz.cover_open_close], + }, + + // Lupus + { + zigbeeModel: ['SCM_00.00.03.11TC'], + model: '12031', + vendor: 'Lupus', + description: 'Roller shutter', + supports: 'open/close', + fromZigbee: [fz.cover_position_report, fz.cover_position, fz.cover_state_change, fz.cover_state_report], + toZigbee: [tz.cover_position, tz.cover_open_close], + }, + { + zigbeeModel: ['PSMP5_00.00.03.11TC'], + model: '12050', + vendor: 'Lupus', + description: 'LUPUSEC mains socket with power meter', + supports: 'on/off, power measurement', + fromZigbee: [fz.state, fz.ignore_onoff_change, fz.ignore_metering_change, fz.bitron_power], + toZigbee: [tz.on_off], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.report('seMetering', 'instantaneousDemand', 10, 60, 1, cb), + (cb) => device.bind('genOnOff', coordinator, cb), + ]; + execute(device, actions, callback); + }, + }, + + // Climax + { + zigbeeModel: ['PSS_00.00.00.15TC'], + model: 'PSS-23ZBS', + vendor: 'Climax', + description: 'Power plug', + supports: 'on/off', + fromZigbee: [fz.state_change], + toZigbee: [tz.on_off], + }, + { + zigbeeModel: ['SCM-3_00.00.03.15'], + model: 'SCM-5ZBS', + vendor: 'Climax', + description: 'Roller shutter', + supports: 'open/close', + fromZigbee: [fz.cover_position_report, fz.cover_position, fz.cover_state_change, fz.cover_state_report], + toZigbee: [tz.cover_position, tz.cover_open_close], + }, + { + zigbeeModel: ['PSM_00.00.00.35TC'], + model: 'PSM-29ZBSR', + vendor: 'Climax', + description: 'Power plug', + supports: 'on/off', + fromZigbee: [fz.state_report, fz.state_change], + toZigbee: [tz.on_off], + }, + + // HEIMAN + { + zigbeeModel: ['CO_V15'], + model: 'HS1CA-M', + description: 'Smart carbon monoxide sensor', + supports: 'carbon monoxide', + vendor: 'HEIMAN', + fromZigbee: [fz.heiman_carbon_monoxide, fz.battery_200, fz.ignore_power_change], + toZigbee: [], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.bind('ssIasZone', coordinator, cb), + (cb) => device.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 23}, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + // Time is in seconds. 65535 means no report. 65534 is max value: 18 hours, 12 minutes 14 seconds. + (cb) => device.report('genPowerCfg', 'batteryPercentageRemaining', 0, 65534, 0, cb), + (cb) => device.report('genPowerCfg', 'batteryAlarmState', 1, 65534, 1, cb), + ]; + + execute(device, actions, callback, 1000); + }, + }, + { + zigbeeModel: ['PIRSensor-N'], + model: 'HS3MS', + vendor: 'HEIMAN', + description: 'Smart motion sensor', + supports: 'occupancy', + fromZigbee: [fz.heiman_pir], + toZigbee: [], + }, + { + zigbeeModel: ['SmartPlug'], + model: 'HS2SK', + description: 'Smart metering plug', + supports: 'on/off, power measurement', + vendor: 'HEIMAN', + fromZigbee: [fz.state, fz.ignore_onoff_change, fz.ignore_electrical_change, fz.HS2SK_power], + toZigbee: [tz.on_off], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const cfg = {direction: 0, attrId: 0, dataType: 16, minRepIntval: 0, maxRepIntval: 1000, repChange: 0}; + const actions = [ + (cb) => device.bind('genOnOff', coordinator, cb), + (cb) => device.foundation('genOnOff', 'configReport', [cfg], foundationCfg, cb), + (cb) => device.report('haElectricalMeasurement', 'rmsVoltage', 1, 1000, 1, cb), + (cb) => device.report('haElectricalMeasurement', 'rmsCurrent', 1, 1000, 1, cb), + (cb) => device.report('haElectricalMeasurement', 'activePower', 1, 1000, 1, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['SMOK_V16', 'b5db59bfd81e4f1f95dc57fdbba17931', 'SMOK_YDLV10', 'SmokeSensor-EM'], + model: 'HS1SA', + vendor: 'HEIMAN', + description: 'Smoke detector', + supports: 'smoke', + fromZigbee: [ + fz.heiman_smoke, + fz.battery_200, + fz.heiman_smoke_enrolled, + fz.ignore_power_change, + ], + toZigbee: [], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.bind('ssIasZone', coordinator, cb), + (cb) => device.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 23}, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + // Time is in seconds. 65535 means no report. 65534 is max value: 18 hours, 12 minutes 14 seconds. + (cb) => device.report('genPowerCfg', 'batteryPercentageRemaining', 0, 65534, 0, cb), + (cb) => device.report('genPowerCfg', 'batteryAlarmState', 1, 65534, 1, cb), + ]; + + execute(device, actions, callback, 1000); + }, + }, + { + zigbeeModel: ['SmokeSensor-N'], + model: 'HS3SA', + vendor: 'HEIMAN', + description: 'Smoke detector', + supports: 'smoke', + fromZigbee: [fz.heiman_smoke], + 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), + ]; + + 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); + const actions = [ + (cb) => device.write('ssIasZone', 'iasCieAddr', coordinator.device.getIeeeAddr(), cb), + (cb) => device.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 23}, cb), + ]; + + execute(device, actions, callback, 1000); + }, + }, + { + zigbeeModel: ['DoorSensor-N'], + model: 'HS1DS/HS3DS', + vendor: 'HEIMAN', + description: 'Door sensor', + supports: 'contact', + fromZigbee: [fz.heiman_contact], + toZigbee: [], + }, + { + zigbeeModel: ['DOOR_TPV13'], + model: 'HEIMAN-M1', + vendor: 'HEIMAN', + description: 'Door sensor', + supports: 'contact', + fromZigbee: [fz.heiman_contact], + toZigbee: [], + }, + { + zigbeeModel: ['DoorSensor-EM'], + model: 'HS1DS-E', + vendor: 'HEIMAN', + description: 'Door sensor', + supports: 'contact', + fromZigbee: [fz.heiman_contact], + 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), + ]; + + execute(device, actions, callback, 1000); + }, + }, + { + zigbeeModel: ['WaterSensor-N'], + model: 'HS1WL/HS3WL', + vendor: 'HEIMAN', + description: 'Water leakage sensor', + supports: 'water leak', + fromZigbee: [fz.heiman_water_leak], + toZigbee: [], + }, + { + zigbeeModel: ['WaterSensor-EM'], + model: 'HS1-WL-E', + vendor: 'HEIMAN', + description: 'Water leakage sensor', + supports: 'water leak', + fromZigbee: [fz.heiman_water_leak], + 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), + ]; + + execute(device, actions, callback, 1000); + }, + }, + { + zigbeeModel: ['RC_V14'], + model: 'HS1RC-M', + vendor: 'HEIMAN', + description: 'Smart remote controller', + supports: 'action', + fromZigbee: [ + fz.battery_200, fz.ignore_power_change, + fz.heiman_smart_controller_armmode, fz.heiman_smart_controller_emergency, + ], + 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), + ]; + + execute(device, actions, callback, 1000); + }, + }, + { + zigbeeModel: ['COSensor-EM'], + model: 'HS1CA-E', + vendor: 'HEIMAN', + description: 'Smart carbon monoxide sensor', + supports: 'carbon monoxide', + fromZigbee: [fz.heiman_carbon_monoxide, fz.battery_200, fz.ignore_power_change, fz.ignore_basic_change], + toZigbee: [], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.bind('ssIasZone', coordinator, cb), + (cb) => device.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 23}, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + // Time is in seconds. 65535 means no report. 65534 is max value: 18 hours, 12 minutes 14 seconds. + (cb) => device.report('genPowerCfg', 'batteryPercentageRemaining', 0, 65534, 0, cb), + (cb) => device.report('genPowerCfg', 'batteryAlarmState', 1, 65534, 1, cb), + ]; + + execute(device, actions, callback, 1000); + }, + }, + { + zigbeeModel: ['WarningDevice'], + model: 'HS2WD-E', + vendor: 'HEIMAN', + description: 'Smart siren', + supports: 'warning', + fromZigbee: [fz.battery_200, fz.ignore_iaszone_change], + toZigbee: [tz.warning], + 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: 1, zoneid: 23}, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.report('genPowerCfg', 'batteryVoltage', 'batteryPercentageRemaining', 0, 3600, 0, cb), + ]; + + execute(device, actions, callback, 1000); + }, + }, + + // Oujiabao + { + zigbeeModel: ['OJB-CR701-YZ'], + model: 'CR701-YZ', + vendor: 'Oujiabao', + description: 'Gas and carbon monoxide alarm', + supports: 'gas and carbon monoxide', + fromZigbee: [fz.OJBCR701YZ_statuschange], + toZigbee: [], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.bind('ssIasZone', coordinator, cb), + (cb) => device.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 23}, cb), + ]; + + execute(device, actions, callback, 1000); + }, + }, + + // Calex + { + zigbeeModel: ['EC-Z3.0-CCT'], + model: '421786', + vendor: 'Calex', + description: 'LED A60 Zigbee GLS-lamp', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['EC-Z3.0-RGBW'], + model: '421792', + vendor: 'Calex', + description: 'LED A60 Zigbee RGB lamp', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + + // EcoSmart + { + zigbeeModel: ['zhaRGBW'], + model: 'D1821', + vendor: 'EcoSmart', + description: 'A19 RGB bulb', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + { + // eslint-disable-next-line + zigbeeModel: ['\u0000\u0002\u0000\u0004\u0000\f^I\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e'], + model: 'D1531', + vendor: 'EcoSmart', + description: 'A19 bright white bulb', + extend: generic.light_onoff_brightness, + }, + { + // eslint-disable-next-line + zigbeeModel: ['\u0000\u0002\u0000\u0004\u0012 �P\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e'], + model: 'D1532', + vendor: 'EcoSmart', + description: 'A19 soft white bulb', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['zhaTunW'], + model: 'D1542', + vendor: 'EcoSmart', + description: 'GU10 adjustable white bulb', + extend: generic.light_onoff_brightness_colortemp, + }, + + // Airam + { + zigbeeModel: ['ZBT-DimmableLight'], + model: '4713407', + vendor: 'Airam', + description: 'LED OP A60 ZB 9W/827 E27', + extend: generic.light_onoff_brightness, + fromZigbee: [fz.state_change, fz.brightness_report, fz.brightness, fz.state], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const cfgOnOff = {direction: 0, attrId: 0, dataType: 16, minRepIntval: 0, maxRepIntval: 1000, repChange: 0}; + const cfgLevel = {direction: 0, attrId: 0, dataType: 32, minRepIntval: 0, maxRepIntval: 1000, repChange: 1}; + const actions = [ + (cb) => device.bind('genOnOff', coordinator, cb), + (cb) => device.foundation('genOnOff', 'configReport', [cfgOnOff], foundationCfg, cb), + (cb) => device.bind('genLevelCtrl', coordinator, cb), + (cb) => device.foundation('genLevelCtrl', 'configReport', [cfgLevel], foundationCfg, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['ZBT-Remote-EU-DIMV1A2'], + model: 'AIRAM-CTR.U', + vendor: 'Airam', + description: 'CTR.U remote', + supports: 'on/off, brightness up/down and click/hold/release', + fromZigbee: [ + fz.genOnOff_cmdOn, fz.genOnOff_cmdOff, fz.CTR_U_brightness_updown_click, + fz.CTR_U_brightness_updown_hold, fz.CTR_U_brightness_updown_release, fz.CTR_U_scene, + ], + toZigbee: [], + }, + + // Paul Neuhaus + { + zigbeeModel: ['NLG-CCT light '], + model: '100.424.11', + vendor: 'Paul Neuhaus', + description: 'Q-INIGO LED ceiling light', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['NLG-RGBW light '], + model: '100.110.39', + vendor: 'Paul Neuhaus', + description: 'Q-FLAG LED Panel, Smart-Home RGBW', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['NLG-plug '], + model: '100.425.90', + vendor: 'Paul Neuhaus', + description: 'Q-PLUG adapter plug with night orientation light', + supports: 'on/off', + fromZigbee: [fz.ignore_basic_change], + toZigbee: [tz.on_off], + }, + + // iCasa + { + zigbeeModel: ['ICZB-IW11D'], + model: 'ICZB-IW11D', + vendor: 'iCasa', + description: 'Zigbee 3.0 Dimmer', + extend: generic.light_onoff_brightness, + }, + + // Müller Licht + { + zigbeeModel: ['ZBT-ExtendedColor'], + model: '404000/404005/404012', + vendor: 'Müller Licht', + description: 'Tint LED bulb GU10/E14/E27 350/470/806 lumen, dimmable, color, opal white', + extend: generic.light_onoff_brightness_colortemp_colorxy, + }, + { + zigbeeModel: ['ZBT-ColorTemperature'], + model: '404006/404008/404004', + vendor: 'Müller Licht', + description: 'Tint LED bulb GU10/E14/E27 350/470/806 lumen, dimmable, opal white', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['ZBT-Remote-ALL-RGBW'], + model: 'MLI-404011', + description: 'Tint remote control', + supports: 'toggle, brightness, other buttons are not supported yet!', + vendor: 'Müller Licht', + fromZigbee: [ + fz.tint404011_on, fz.tint404011_off, fz.cmdToggle, fz.tint404011_brightness_updown_click, + fz.tint404011_move_to_color_temp, fz.tint404011_move_to_color, fz.tint404011_scene, + fz.tint404011_brightness_updown_release, fz.tint404011_brightness_updown_hold, + ], + toZigbee: [], + }, + + // Salus + { + zigbeeModel: ['SP600'], + model: 'SP600', + vendor: 'Salus', + description: 'Smart plug', + 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, 9); + const onOff = {direction: 0, attrId: 0, dataType: 16, minRepIntval: 0, maxRepIntval: 5, repChange: 0}; + const actions = [ + (cb) => device.foundation('genOnOff', 'configReport', [onOff], foundationCfg, cb), + (cb) => device.bind('genOnOff', coordinator, cb), + (cb) => device.report('seMetering', 'instantaneousDemand', 1, 5, 1, cb), + ]; + + execute(device, actions, callback); + }, + }, + + // AduroSmart + { + zigbeeModel: ['ZLL-ExtendedColo'], + model: '81809', + vendor: 'AduroSmart', + description: 'ERIA colors and white shades smart light bulb A19', + extend: generic.light_onoff_brightness_colortemp_colorxy, + ep: (device) => { + return { + '': 2, + }; + }, + }, + { + zigbeeModel: ['Adurolight_NCC'], + model: '81825', + vendor: 'AduroSmart', + description: 'ERIA smart wireless dimming switch', + supports: 'on, off, up, down', + fromZigbee: [fz.eria_81825_on, fz.eria_81825_off, fz.eria_81825_updown], + toZigbee: [], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.bind('genOnOff', coordinator, cb), + (cb) => device.bind('genLevelCtrl', coordinator, cb), + ]; + + execute(device, actions, callback); + }, + }, + + // Eurotronic + { + zigbeeModel: ['SPZB0001'], + model: 'SPZB0001', + vendor: 'Eurotronic', + description: 'Spirit Zigbee wireless heater thermostat', + supports: 'temperature, heating system control', + fromZigbee: [ + fz.thermostat_dev_change, + fz.eurotronic_thermostat_dev_change, + fz.ignore_thermostat_report, fz.generic_battery_remaining, fz.ignore_power_change, + ], + toZigbee: [ + tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint, + tz.thermostat_local_temperature_calibration, tz.thermostat_system_mode, + tz.eurotronic_system_mode, tz.eurotronic_error_status, tz.thermostat_setpoint_raise_lower, + tz.thermostat_control_sequence_of_operation, tz.thermostat_remote_sensing, + tz.eurotronic_current_heating_setpoint, tz.eurotronic_trv_mode, tz.eurotronic_valve_position, + ], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.bind('hvacThermostat', coordinator, cb), + (cb) => device.report('hvacThermostat', 'localTemp', 1, 1200, 25, cb), + (cb) => device.foundation('hvacThermostat', 'configReport', [{ + direction: 0, attrId: 0x4003, dataType: 41, minRepIntval: 0, + maxRepIntval: 600, repChange: 25}], {manufSpec: 1, manufCode: 4151}, cb), + ]; + + execute(device, actions, callback); + }, + }, + + // Livolo + { + zigbeeModel: ['TI0001 '], + model: 'TI0001', + description: + 'Zigbee switch (1 and 2 gang) ' + + '[work in progress](https://github.com/Koenkk/zigbee2mqtt/issues/592)', + vendor: 'Livolo', + supports: 'on/off', + fromZigbee: [fz.ignore_onoff_report, fz.livolo_switch_dev_change], + toZigbee: [tz.livolo_switch_on_off], + }, + + // Bosch + { + zigbeeModel: ['RFDL-ZB-MS'], + model: 'RADON TriTech ZB', + vendor: 'Bosch', + description: 'Wireless motion detector', + supports: 'occupancy and temperature', + fromZigbee: [ + fz.generic_temperature, fz.ignore_temperature_change, fz.generic_batteryvoltage_3000_2500, + fz.ignore_power_change, fz.generic_ias_zone_occupancy_status_change, fz.ignore_iaszone_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.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 23}, cb), + (cb) => device.bind('msTemperatureMeasurement', coordinator, cb), + (cb) => device.report('msTemperatureMeasurement', 'measuredValue', 60, 58000, 1, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.report('genPowerCfg', 'batteryVoltage', 1800, 3600, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['ISW-ZPR1-WP13'], + model: 'ISW-ZPR1-WP13', + vendor: 'Bosch', + description: 'Motion sensor', + supports: 'occupancy and temperature', + fromZigbee: [ + fz.generic_temperature, fz.ignore_temperature_change, fz.ignore_power_change, + fz.generic_batteryvoltage_3000_2500, fz.generic_ias_zone_occupancy_status_change, + fz.ignore_iaszone_report, fz.ignore_iaszone_change, + ], + toZigbee: [], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 5); + const actions = [ + (cb) => device.write('ssIasZone', 'iasCieAddr', coordinator.device.getIeeeAddr(), cb), + (cb) => device.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 23}, cb), + (cb) => device.bind('msTemperatureMeasurement', coordinator, cb), + (cb) => device.report( + 'msTemperatureMeasurement', 'measuredValue', repInterval.MINUTE, repInterval.MAX, 0, cb + ), + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.report('genPowerCfg', 'batteryVoltage', repInterval.HOUR, repInterval.MAX, cb), + ]; + + execute(device, actions, callback); + }, + }, + + // Immax + { + zigbeeModel: ['IM-Z3.0-DIM'], + model: 'IM-Z3.0-DIM', + vendor: 'Immax', + description: 'LED E14/230V C35 5W TB 440LM ZIGBEE DIM', + extend: generic.light_onoff_brightness, + }, + + // Yale + { + zigbeeModel: ['YRD446 BLE TSDB'], + model: 'YRD426NRSC', + vendor: 'Yale', + description: 'Assure lock', + supports: 'lock/unlock, battery', + fromZigbee: [fz.generic_lock, fz.generic_lock_operation_event, fz.battery_200], + toZigbee: [tz.generic_lock], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.report('closuresDoorLock', 'lockState', 0, repInterval.HOUR, 0, cb), + (cb) => device.report('genPowerCfg', 'batteryPercentageRemaining', 0, repInterval.MAX, 0, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['YRD226 TSDB'], + model: 'YRD226HA2619', + vendor: 'Yale', + description: 'Assure lock', + supports: 'lock/unlock, battery', + fromZigbee: [fz.generic_lock, fz.battery_200], + toZigbee: [tz.generic_lock], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.report('closuresDoorLock', 'lockState', 0, repInterval.HOUR, 0, cb), + (cb) => device.report('genPowerCfg', 'batteryPercentageRemaining', 0, repInterval.MAX, 0, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['YRD256 TSDB'], + model: 'YRD256HA20BP', + vendor: 'Yale', + description: 'Assure lock SL', + supports: 'lock/unlock, battery', + fromZigbee: [ + fz.generic_lock, + fz.generic_lock_operation_event, + fz.battery_200, + fz.ignore_power_change, + ], + toZigbee: [tz.generic_lock], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.report('closuresDoorLock', 'lockState', 0, repInterval.HOUR, 0, cb), + (cb) => device.report('genPowerCfg', 'batteryPercentageRemaining', 0, repInterval.MAX, 0, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['iZBModule01'], + model: 'YMF40', + vendor: 'Yale', + description: 'Real living lock', + supports: 'lock/unlock, battery', + fromZigbee: [fz.generic_lock_operation_event], + toZigbee: [tz.generic_lock], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + + const actions = [ + (cb) => device.report('closuresDoorLock', 'lockState', 0, 3, 0, cb), + (cb) => device.report('genPowerCfg', 'batteryPercentageRemaining', 0, 3, 0, cb), + ]; + + execute(device, actions, callback); + }, + + }, + + // Keen Home + { + zigbeeModel: [ + 'SV01-410-MP-1.0', 'SV01-410-MP-1.1', 'SV01-410-MP-1.4', 'SV01-410-MP-1.5', 'SV01-412-MP-1.0', + 'SV01-610-MP-1.0', 'SV01-612-MP-1.0', + ], + model: 'SV01', + vendor: 'Keen Home', + description: 'Smart vent', + supports: 'open, close, position, temperature, pressure, battery', + fromZigbee: [ + fz.cover_position, fz.cover_position_report, fz.generic_temperature, fz.generic_temperature_change, + fz.generic_battery, fz.generic_battery_change, fz.keen_home_smart_vent_pressure, + fz.keen_home_smart_vent_pressure_report, fz.ignore_onoff_change, fz.ignore_onoff_report, + ], + toZigbee: [ + tz.cover_open_close, + tz.cover_position, + ], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.bind('genLevelCtrl', coordinator, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.bind('msTemperatureMeasurement', coordinator, cb), + (cb) => device.bind('msPressureMeasurement', coordinator, cb), + + // eslint-disable-next-line + // https://github.com/yracine/keenhome.device-type/blob/master/devicetypes/keensmartvent.src/keensmartvent.groovy + (cb) => device.report( + 'msTemperatureMeasurement', 'measuredValue', repInterval.MINUTE * 2, repInterval.HOUR, 50, cb + ), + (cb) => device.foundation( + 'msPressureMeasurement', + 'configReport', + [{ + direction: 0, attrId: 32, dataType: 34, minRepIntval: repInterval.MINUTE * 5, + maxRepIntval: repInterval.HOUR, repChange: 500, + }], + {manufSpec: 1, manufCode: 4443}, + cb + ), + (cb) => device.report( + 'genPowerCfg', 'batteryPercentageRemaining', repInterval.HOUR, repInterval.HOUR * 12, 0, cb + ), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['SV02-410-MP-1.3'], + model: 'SV02', + vendor: 'Keen Home', + description: 'Smart vent', + supports: 'open, close, position, temperature, pressure, battery', + fromZigbee: [ + fz.cover_position, fz.cover_position_report, fz.generic_temperature, fz.generic_temperature_change, + fz.generic_battery, fz.generic_battery_change, fz.keen_home_smart_vent_pressure, + fz.keen_home_smart_vent_pressure_report, fz.ignore_onoff_change, fz.ignore_onoff_report, + ], + toZigbee: [ + tz.cover_open_close, + tz.cover_position, + ], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.bind('genLevelCtrl', coordinator, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.bind('msTemperatureMeasurement', coordinator, cb), + (cb) => device.bind('msPressureMeasurement', coordinator, cb), + + // eslint-disable-next-line + // https://github.com/yracine/keenhome.device-type/blob/master/devicetypes/keensmartvent.src/keensmartvent.groovy + (cb) => device.report( + 'msTemperatureMeasurement', 'measuredValue', repInterval.MINUTE * 2, repInterval.HOUR, 50, cb + ), + (cb) => device.foundation( + 'msPressureMeasurement', + 'configReport', + [{ + direction: 0, attrId: 32, dataType: 34, minRepIntval: repInterval.MINUTE * 5, + maxRepIntval: repInterval.HOUR, repChange: 500, + }], + {manufSpec: 1, manufCode: 4443}, + cb + ), + (cb) => device.report( + 'genPowerCfg', 'batteryPercentageRemaining', repInterval.HOUR, repInterval.HOUR * 12, 0, cb + ), + ]; + + execute(device, actions, callback); + }, + }, + + // AXIS + { + zigbeeModel: ['Gear'], + model: 'GR-ZB01-W', + vendor: 'AXIS', + description: 'Gear window shade motor', + supports: 'open, close, position, battery', + fromZigbee: [fz.cover_position, fz.cover_position_report, fz.generic_battery, fz.generic_battery_change], + toZigbee: [tz.cover_open_close, tz.cover_position], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.bind('genLevelCtrl', coordinator, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.report('genLevelCtrl', 'currentLevel', repInterval.MINUTE, repInterval.HOUR * 12, 0, cb), + (cb) => device.report( + 'genPowerCfg', 'batteryPercentageRemaining', repInterval.HOUR, repInterval.HOUR * 12, 0, cb + ), + ]; + + execute(device, actions, callback); + }, + }, + + // ELKO + { + zigbeeModel: ['ElkoDimmerZHA'], + model: '316GLEDRF', + vendor: 'ELKO', + description: 'ZigBee in-wall smart dimmer', + supports: 'on/off, brightness', + fromZigbee: [fz.brightness, fz.ignore_onoff_change, fz.state], + toZigbee: [tz.light_onoff_brightness, tz.ignore_transition], + 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); + }, + }, + + // LivingWise + { + zigbeeModel: ['abb71ca5fe1846f185cfbda554046cce'], + model: 'LVS-ZB500D', + vendor: 'LivingWise', + description: 'ZigBee smart dimmer switch', + supports: 'on/off, brightness', + toZigbee: [tz.light_onoff_brightness], + fromZigbee: [ + fz.state, fz.brightness, fz.ignore_light_brightness_report, fz.ignore_onoff_change, + fz.ignore_genIdentify, + ], + }, + { + zigbeeModel: ['e70f96b3773a4c9283c6862dbafb6a99'], + model: 'LVS-SM10ZW', + vendor: 'LivingWise', + description: 'Door or window contact switch', + supports: 'contact', + fromZigbee: [fz.ias_contact_dev_change, fz.ias_contact_status_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.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 23}, cb), + ]; + + execute(device, actions, callback); + }, + }, + + // Stelpro + { + zigbeeModel: ['ST218'], + model: 'ST218', + vendor: 'Stelpro', + description: 'Built-in electronic thermostat', + supports: 'temperature ', + fromZigbee: [fz.thermostat_att_report, fz.thermostat_dev_change], + toZigbee: [ + tz.thermostat_local_temperature, tz.thermostat_occupied_heating_setpoint, + ], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 25); + 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', 300, 3600, 0, cb), + ]; + execute(device, actions, callback); + }, + }, + + // Nyce + { + zigbeeModel: ['3011'], + model: 'NCZ-3011-HA', + vendor: 'Nyce', + description: 'Door/window sensor', + supports: 'motion, humidity and temperature', + fromZigbee: [ + fz.ignore_basic_report, + fz.ignore_genIdentify, fz.ignore_basic_change, fz.ignore_poll_ctrl, + fz.generic_battery_change, fz.ignore_iaszone_change, + fz.ignore_poll_ctrl_change, fz.ignore_genIdentify_change, fz.ignore_iaszone_report, + fz.ias_zone_motion_status_change, fz.generic_battery, fz.ias_contact_status_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.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 255}, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['3043'], + model: 'NCZ-3043-HA', + vendor: 'Nyce', + description: 'Ceiling motion sensor', + supports: 'motion, humidity and temperature', + fromZigbee: [ + fz.generic_occupancy, fz.xiaomi_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, + fz.ignore_poll_ctrl_change, fz.ignore_genIdentify_change, fz.ignore_iaszone_report, + fz.ias_zone_motion_status_change, fz.generic_battery, fz.generic_battery_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.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 255}, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['3041'], + model: 'NCZ-3041-HA', + vendor: 'Nyce', + description: 'Wall motion sensor', + supports: 'motion, humidity and temperature', + fromZigbee: [ + fz.generic_occupancy, fz.xiaomi_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, + fz.ignore_poll_ctrl_change, fz.ignore_genIdentify_change, fz.ignore_iaszone_report, + fz.ias_zone_motion_status_change, fz.generic_battery, fz.generic_battery_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.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 255}, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['3045'], + model: 'NCZ-3045-HA', + vendor: 'Nyce', + description: 'Curtain motion sensor', + supports: 'motion, humidity and temperature', + fromZigbee: [ + fz.generic_occupancy, fz.xiaomi_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, + fz.ignore_poll_ctrl_change, fz.ignore_genIdentify_change, fz.ignore_iaszone_report, + fz.ias_zone_motion_status_change, fz.generic_battery, fz.generic_battery_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.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 255}, cb), + ]; + + execute(device, actions, callback); + }, + }, + + // Securifi + { + zigbeeModel: ['PP-WHT-US'], + model: 'PP-WHT-US', + vendor: 'Securifi', + description: 'Peanut Smart Plug', + supports: 'on/off, power measurement', + fromZigbee: [fz.state, fz.ignore_onoff_change, fz.peanut_electrical, fz.ignore_electrical_change], + 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}; + + // Observed Voltage Multiplier 180 / Divisor 39321 = 0.004578 + // (218.4 units / V = about 22 units per 1/10 V) + const rmsVoltage = { + direction: 0, attrId: 1285, dataType: 33, + minRepIntval: 10, maxRepIntval: 600, repChange: 22, + }; + + // Observed Current Multiplier 72 / Divisor 39321 = 0.001831 + // (546.1 units / A = about 5 units per 1/100 A) + const rmsCurrent = { + direction: 0, attrId: 1288, dataType: 33, + minRepIntval: 10, maxRepIntval: 600, repChange: 5, + }; + + // Observed Power Multiplier 10255 / Divisor 39321 = 0.2608 + // (3.834 units / W = about 1 unit per 1/4 W) + const activePower = { + direction: 0, attrId: 1291, dataType: 41, + minRepIntval: 10, maxRepIntval: 600, repChange: 1, + }; + + // Multipliers and Divisors might never change, + // but report at max 10 min. to ensure first report comes in reasonably promptly + const acVoltageMultiplier = { + direction: 0, attrId: 1536, dataType: 33, + minRepIntval: 10, maxRepIntval: 600, repChange: 0, + }; + const acVoltageDivisor = { + direction: 0, attrId: 1537, dataType: 33, + minRepIntval: 10, maxRepIntval: 600, repChange: 0, + }; + + const acCurrentMultiplier = { + direction: 0, attrId: 1538, dataType: 33, + minRepIntval: 10, maxRepIntval: 600, repChange: 0, + }; + const acCurrentDivisor = { + direction: 0, attrId: 1539, dataType: 33, + minRepIntval: 10, maxRepIntval: 600, repChange: 0, + }; + + const acPowerMultiplier = { + direction: 0, attrId: 1540, dataType: 33, + minRepIntval: 10, maxRepIntval: 600, repChange: 0, + }; + const acPowerDivisor = { + direction: 0, attrId: 1541, dataType: 33, + minRepIntval: 10, maxRepIntval: 600, repChange: 0, + }; + + const electricalCfg = [ + rmsVoltage, rmsCurrent, activePower, + acVoltageMultiplier, acVoltageDivisor, + acCurrentMultiplier, acCurrentDivisor, + acPowerMultiplier, acPowerDivisor, + ]; + const actions = [ + (cb) => device.foundation('genOnOff', 'configReport', [onOff], foundationCfg, cb), + (cb) => device.bind('genOnOff', coordinator, cb), + (cb) => device.foundation('haElectricalMeasurement', 'configReport', electricalCfg, foundationCfg, cb), + (cb) => device.bind('haElectricalMeasurement', coordinator, cb), + ]; + + execute(device, actions, callback); + }, + }, + + // Visonic + { + zigbeeModel: ['MCT-350 SMA'], + model: 'MCT-350 SMA', + vendor: 'Visonic', + description: 'Magnetic door & window contact sensor', + supports: 'contact', + fromZigbee: [fz.visonic_contact, fz.ignore_power_change], + toZigbee: [], + }, + { + zigbeeModel: ['MCT-340 E'], + model: 'MCT-340 E', + vendor: 'Visonic', + description: 'Magnetic door & window contact sensor', + supports: 'contact', + fromZigbee: [fz.visonic_contact, fz.ignore_power_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.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 0}, cb), + ]; + execute(device, actions, callback); + }, + }, + + // Sunricher + { + zigbeeModel: ['ZG9101SAC-HP'], + model: 'ZG9101SAC-HP', + vendor: 'Sunricher', + description: 'ZigBee AC phase-cut dimmer', + extend: generic.light_onoff_brightness, + }, + + // Shenzhen Homa + { + zigbeeModel: ['HOMA1008'], + model: 'HLD812-Z-SC', + vendor: 'Shenzhen Homa', + description: 'Smart LED driver', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['HOMA1002'], + model: 'HLC610-Z', + vendor: 'Shenzhen Homa', + description: 'Wireless dimmable controller', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['HOMA1031'], + model: 'HLC821-Z-SC', + vendor: 'Shenzhen Homa', + description: 'ZigBee AC phase-cut dimmer', + extend: generic.light_onoff_brightness, + }, + + // Honyar + { + zigbeeModel: ['00500c35'], + model: 'U86K31ND6', + vendor: 'Honyar', + description: '3 gang switch ', + supports: 'on/off', + fromZigbee: [], + toZigbee: [tz.on_off], + ep: (device) => { + return {'left': 1, 'center': 2, 'right': 3}; + }, + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const cfg = {direction: 0, attrId: 0, dataType: 16, minRepIntval: 0, maxRepIntval: 1000, repChange: 0}; + const ep1 = shepherd.find(ieeeAddr, 1); + const ep2 = shepherd.find(ieeeAddr, 2); + const ep3 = shepherd.find(ieeeAddr, 3); + const actions = [ + (cb) => ep1.bind('genOnOff', coordinator, cb), + (cb) => ep1.foundation('genOnOff', 'configReport', [cfg], foundationCfg, cb), + (cb) => ep2.bind('genOnOff', coordinator, cb), + (cb) => ep2.foundation('genOnOff', 'configReport', [cfg], foundationCfg, cb), + (cb) => ep3.bind('genOnOff', coordinator, cb), + (cb) => ep3.foundation('genOnOff', 'configReport', [cfg], foundationCfg, cb), + ]; + + execute(ep1, actions, callback); + }, + }, + + // Danalock + { + zigbeeModel: ['V3-BTZB'], + model: 'V3-BTZB', + vendor: 'Danalock', + description: 'BT/ZB smartlock', + supports: 'lock/unlock, battery', + fromZigbee: [fz.generic_lock, fz.generic_lock_operation_event, fz.battery_200, fz.ignore_power_change], + toZigbee: [tz.generic_lock], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.report('closuresDoorLock', 'lockState', 0, repInterval.HOUR, 0, cb), + (cb) => device.report('genPowerCfg', 'batteryPercentageRemaining', 0, repInterval.MAX, 0, cb), + ]; + + execute(device, actions, callback); + }, + }, + + // NET2GRID + { + zigbeeModel: ['SP31 '], + model: 'N2G-SP', + vendor: 'NET2GRID', + description: 'White Net2Grid power outlet switch with power meter', + supports: 'on/off, power and energy measurement', + fromZigbee: [ + fz.genOnOff_cmdOn, fz.genOnOff_cmdOff, fz.state, fz.ignore_onoff_change, + fz.ignore_metering_change, fz.generic_power, + ], + toZigbee: [tz.on_off], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const ep1 = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => ep1.bind('genOnOff', coordinator, cb), + (cb) => ep1.report('genOnOff', 'onOff', 10, 30, 1, cb), + ]; + + execute(ep1, actions, (result) => { + if (result) { + const ep10 = shepherd.find(ieeeAddr, 10); + const actions = [ + (cb) => ep10.report('seMetering', 'instantaneousDemand', 10, 300, 10, cb), + (cb) => ep10.report('seMetering', 'currentSummDelivered', 10, 300, [0, 1], cb), + (cb) => ep10.report('seMetering', 'currentSummReceived', 10, 300, [0, 1], cb), + (cb) => ep10.read('seMetering', 'unitOfMeasure', cb), + (cb) => ep10.read('seMetering', 'multiplier', cb), + (cb) => ep10.read('seMetering', 'divisor', cb), + ]; + + execute(ep10, actions, callback); + } else { + callback(result); + } + }); + }, + }, + + // Third Reality + { + zigbeeModel: ['3RSS008Z'], + model: '3RSS008Z', + vendor: 'Third Reality', + description: 'RealitySwitch Plus', + supports: 'on/off, battery', + fromZigbee: [ + fz.ignore_onoff_change, fz.state, fz.ignore_genIdentify_change, + fz.ignore_basic_change, + ], + toZigbee: [tz.on_off, tz.ignore_transition], + }, + + // Hampton Bay + { + zigbeeModel: ['HDC52EastwindFan', 'HBUniversalCFRemote'], + model: '99432', + vendor: 'Hampton Bay', + description: 'Universal wink enabled white ceiling fan premier remote control', + supports: 'on/off, brightness, fan_mode and fan_state', + fromZigbee: generic.light_onoff_brightness.fromZigbee.concat([ + fz.ignore_fan_change, fz.generic_fan_mode, + ]), + toZigbee: generic.light_onoff_brightness.toZigbee.concat([tz.fan_mode]), + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.bind('genOnOff', coordinator, cb), + (cb) => device.report('genOnOff', 'onOff', 0, 1000, 0, cb), + (cb) => device.bind('genLevelCtrl', coordinator, cb), + (cb) => device.report('genLevelCtrl', 'currentLevel', 0, 1000, 0, cb), + (cb) => device.bind('hvacFanCtrl', coordinator, cb), + (cb) => device.report('hvacFanCtrl', 'fanMode', 0, 1000, 0, cb), + ]; + + execute(device, actions, callback); + }, + options: { + disFeedbackRsp: true, + }, + }, + + // Iluminize + { + zigbeeModel: ['DIM Lighting'], + model: '511.10', + vendor: 'Iluminize', + description: 'Zigbee LED-Controller ', + extend: generic.light_onoff_brightness, + }, + + // Anchor + { + zigbeeModel: ['FB56-SKT17AC1.4'], + model: '67200BL', + description: 'Vetaar smart plug', + supports: 'on/off', + vendor: 'Anchor', + fromZigbee: [fz.ignore_onoff_change, fz.state], + toZigbee: [tz.on_off], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 3); + const cfg = {direction: 0, attrId: 0, dataType: 16, minRepIntval: 0, maxRepIntval: 1000, repChange: 0}; + const actions = [ + (cb) => device.bind('genOnOff', coordinator, cb), + (cb) => device.foundation('genOnOff', 'configReport', [cfg], foundationCfg, cb), + ]; + + execute(device, actions, callback); + }, + }, + + // Gira + { + zigbeeModel: [' Remote'], + model: '2430-100', + vendor: 'Gira', + description: 'ZigBee Light Link wall transmitter', + supports: 'action', + fromZigbee: [ + fz.GIRA2430_scene_click, fz.GIRA2430_on_click, fz.GIRA2430_off_click, fz.GIRA2430_down_hold, + fz.GIRA2430_up_hold, fz.GIRA2430_stop, + ], + toZigbee: [], + }, + + // RGB genie + { + zigbeeModel: ['ZGRC-KEY-013'], + model: 'ZGRC-KEY-013', + vendor: 'RGB Genie', + description: '3 Zone remote and dimmer', + supports: 'click', + fromZigbee: [ + fz.generic_battery, fz.ZGRC013_brightness_onoff, fz.ZGRC013_brightness, fz.ZGRC013_brightness_stop, + fz.ZGRC013_cmdOn, fz.ZGRC013_cmdOff, fz.ZGRC013_scene, + ], + toZigbee: [], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const cfg = {direction: 0, attrId: 0, dataType: 16, minRepIntval: 0, maxRepIntval: 1000, repChange: 0}; + const actions = [ + (cb) => device.bind('genOnOff', coordinator, cb), + (cb) => device.foundation('genOnOff', 'configReport', [cfg], foundationCfg, cb), + ]; + + execute(device, actions, callback); + }, + }, + + // Sercomm + { + zigbeeModel: ['SZ-ESW01-AU'], + model: 'SZ-ESW01-AU', + vendor: 'Sercomm', + description: 'Telstra smart plug', + supports: 'on/off, power consumption', + fromZigbee: [fz.state, fz.state_change, fz.SZ_ESW01_AU_power, fz.ignore_metering_change], + toZigbee: [tz.on_off], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const cfg = {direction: 0, attrId: 0, dataType: 16, minRepIntval: 0, maxRepIntval: 1000, repChange: 0}; + const actions = [ + (cb) => device.bind('genOnOff', coordinator, cb), + (cb) => device.foundation('genOnOff', 'configReport', [cfg], foundationCfg, cb), + (cb) => device.report('seMetering', 'instantaneousDemand', 10, 60, 1, cb), + ]; + + execute(device, actions, callback); + }, + }, + + // Leedarson + { + zigbeeModel: ['LED_GU10_OWDT'], + model: 'ZM350STW1TCF', + vendor: 'Leedarson', + description: 'LED PAR16 50 GU10 tunable white', + extend: generic.light_onoff_brightness_colortemp, + }, + { + zigbeeModel: ['M350ST-W1R-01'], + model: 'M350STW1', + vendor: 'Leedarson', + description: 'LED PAR16 50 GU10', + extend: generic.light_onoff_brightness, + }, + { + zigbeeModel: ['ZHA-DimmableLight'], + model: 'A806S-Q1R', + vendor: 'Leedarson', + description: 'LED E27 tunable white', + extend: generic.light_onoff_brightness, + }, + + // GMY + { + zigbeeModel: ['CCT box'], + model: 'B07KG5KF5R', + vendor: 'GMY Smart Bulb', + description: 'GMY Smart bulb, 470lm, vintage dimmable, 2700-6500k, E27', + extend: generic.light_onoff_brightness_colortemp, + }, + + // Meazon + { + zigbeeModel: [ + '101.301.001649', '101.301.001838', '101.301.001802', '101.301.001738', + '101.301.001412', '101.301.001765', '101.301.001814', + ], + model: 'MEAZON_BIZY_PLUG', + vendor: 'Meazon', + description: 'Bizy plug meter', + supports: 'on/off, power, energy measurement and temperature', + fromZigbee: [ + fz.genOnOff_cmdOn, fz.genOnOff_cmdOff, fz.state, fz.ignore_onoff_change, + fz.meazon_meter, fz.ignore_metering_change, + ], + toZigbee: [tz.on_off], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 10); + const onOff = {direction: 0, attrId: 0, dataType: 0x10, minRepIntval: 0x0001, maxRepIntval: 0xfffe}; + const linefrequency = {direction: 0, attrId: 0x2000, dataType: 0x29, minRepIntval: 0x0001, + maxRepIntval: 300, repChange: 1}; + const actions = [ + (cb) => device.bind('genOnOff', coordinator, cb), + (cb) => device.foundation('genOnOff', 'configReport', [onOff], foundationCfg, cb), + (cb) => device.bind('seMetering', coordinator, cb), + (cb) => device.foundation('seMetering', 'write', + [{attrId: 0x1005, dataType: 25, attrData: 0x063e}], + {manufSpec: 1, disDefaultRsp: 0, manufCode: 4406}, cb), + (cb) => device.foundation('seMetering', 'configReport', [linefrequency], + {manufSpec: 1, disDefaultRsp: 0, manufCode: 4406}, cb), + ]; + + execute(device, actions, callback); + }, + }, + { + zigbeeModel: ['102.106.000235', '102.106.001111', '102.106.000348', '102.106.000256', '102.106.001242'], + model: 'MEAZON_DINRAIL', + vendor: 'Meazon', + description: 'DinRail 1-phase meter', + supports: 'on/off, power, energy measurement and temperature', + fromZigbee: [ + fz.genOnOff_cmdOn, fz.genOnOff_cmdOff, fz.state, fz.ignore_onoff_change, + fz.meazon_meter, fz.ignore_metering_change, + ], + toZigbee: [tz.on_off], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 10); + const onOff = {direction: 0, attrId: 0, dataType: 0x10, minRepIntval: 0x0001, maxRepIntval: 0xfffe}; + const linefrequency = {direction: 0, attrId: 0x2000, dataType: 0x29, minRepIntval: 0x0001, + maxRepIntval: 300, repChange: 1}; + const actions = [ + (cb) => device.bind('genOnOff', coordinator, cb), + (cb) => device.foundation('genOnOff', 'configReport', [onOff], foundationCfg, cb), + (cb) => device.bind('seMetering', coordinator, cb), + (cb) => device.foundation('seMetering', 'write', + [{attrId: 0x1005, dataType: 25, attrData: 0x063e}], + {manufSpec: 1, disDefaultRsp: 0, manufCode: 4406}, cb), + (cb) => device.foundation('seMetering', 'configReport', [linefrequency], + {manufSpec: 1, disDefaultRsp: 0, manufCode: 4406}, cb), + ]; + + execute(device, actions, callback); + }, + }, + + // TUYATEC + { + zigbeeModel: ['RH3040'], + model: 'RH3040', + vendor: 'TUYATEC', + description: 'PIR sensor', + supports: 'occupancy', + fromZigbee: [ + fz.generic_battery_remaining, fz.ignore_power_change, fz.generic_battery_voltage, + fz.ignore_basic_report, fz.ignore_basic_change, fz.ignore_iaszone_change, + fz.generic_ias_zone_occupancy_status_change, + ], + toZigbee: [], + configure: (ieeeAddr, shepherd, coordinator, callback) => { + const device = shepherd.find(ieeeAddr, 1); + const actions = [ + (cb) => device.bind('ssIasZone', coordinator, cb), + (cb) => device.write('ssIasZone', 'iasCieAddr', coordinator.device.getIeeeAddr(), cb), + (cb) => device.functional('ssIasZone', 'enrollRsp', {enrollrspcode: 0, zoneid: 23}, cb), + (cb) => device.bind('genBasic', coordinator, cb), + (cb) => device.bind('ssIasZone', coordinator, cb), + (cb) => device.bind('genIdentify', coordinator, cb), + (cb) => device.bind('genPowerCfg', coordinator, cb), + (cb) => device.report('genPowerCfg', 'batteryVoltage', 'batteryPercentageRemaining', 1, 1000, 1, cb), + ]; + execute(device, actions, callback); + }, + }, +]; + +module.exports = devices.map((device) => + device.extend ? Object.assign({}, device.extend, device) : device +);