UNPKG

zigbee-herdsman-converters

Version:

Collection of device converters to be used with zigbee-herdsman

977 lines (976 loc) 461 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const exposes = __importStar(require("../lib/exposes")); const legacy = __importStar(require("../lib/legacy")); const tuya = __importStar(require("../lib/tuya")); const ota = __importStar(require("../lib/ota")); const reporting = __importStar(require("../lib/reporting")); const libColor = __importStar(require("../lib/color")); const utils = __importStar(require("../lib/utils")); const zosung = __importStar(require("../lib/zosung")); const globalStore = __importStar(require("../lib/store")); const constants_1 = require("../lib/constants"); const fromZigbee_1 = __importDefault(require("../converters/fromZigbee")); const toZigbee_1 = __importDefault(require("../converters/toZigbee")); const modernExtend_1 = require("../lib/modernExtend"); const logger_1 = require("../lib/logger"); const NS = 'zhc:tuya'; const { tuyaLight } = tuya.modernExtend; const e = exposes.presets; const ea = exposes.access; const fzZosung = zosung.fzZosung; const tzZosung = zosung.tzZosung; const ez = zosung.presetsZosung; const tzLocal = { TS030F_border: { key: ['border'], convertSet: async (entity, key, value, meta) => { const lookup = { up: 0, down: 1, up_delete: 2, down_delete: 3 }; await entity.write(0xe001, { 0xe001: { value: utils.getFromLookup(value, lookup), type: 0x30 } }); }, }, TS0726_switch_mode: { key: ['switch_mode'], convertSet: async (entity, key, value, meta) => { await entity.write(0xe001, { 0xd020: { value: utils.getFromLookup(value, { switch: 0, scene: 1 }), type: 0x30 } }); return { state: { switch_mode: value } }; }, }, led_control: { key: ['brightness', 'color', 'color_temp', 'transition'], options: [exposes.options.color_sync()], convertSet: async (entity, _key, _value, meta) => { const newState = {}; // The color mode encodes whether the light is using its white LEDs or its color LEDs let colorMode = meta.state.color_mode ?? constants_1.colorModeLookup[constants_1.ColorMode.ColorTemp]; // Color mode switching is done by setting color temperature (switch to white LEDs) or setting color (switch // to color LEDs) if ('color_temp' in meta.message) colorMode = constants_1.colorModeLookup[constants_1.ColorMode.ColorTemp]; if ('color' in meta.message) colorMode = constants_1.colorModeLookup[constants_1.ColorMode.HS]; if (colorMode != meta.state.color_mode) { newState.color_mode = colorMode; // To switch between white mode and color mode, we have to send a special command: const rgbMode = (colorMode == constants_1.colorModeLookup[constants_1.ColorMode.HS]); await entity.command('lightingColorCtrl', 'tuyaRgbMode', { enable: rgbMode }); } // A transition time of 0 would be treated as about 1 second, probably some kind of fallback/default // transition time, so for "no transition" we use 1 (tenth of a second). const transtime = typeof meta.message.transition === 'number' ? (meta.message.transition * 10) : 0.1; if (colorMode == constants_1.colorModeLookup[constants_1.ColorMode.ColorTemp]) { if ('brightness' in meta.message) { const zclData = { level: Number(meta.message.brightness), transtime }; await entity.command('genLevelCtrl', 'moveToLevel', zclData, utils.getOptions(meta.mapped, entity)); newState.brightness = meta.message.brightness; } if ('color_temp' in meta.message) { const zclData = { colortemp: meta.message.color_temp, transtime: transtime }; await entity.command('lightingColorCtrl', 'moveToColorTemp', zclData, utils.getOptions(meta.mapped, entity)); newState.color_temp = meta.message.color_temp; } } else if (colorMode == constants_1.colorModeLookup[constants_1.ColorMode.HS]) { if ('brightness' in meta.message || 'color' in meta.message) { // We ignore the brightness of the color and instead use the overall brightness setting of the lamp // for the brightness because I think that's the expected behavior and also because the color // conversion below always returns 100 as brightness ("value") even for very dark colors, except // when the color is completely black/zero. // Load current state or defaults const newSettings = { brightness: meta.state.brightness ?? 254, // full brightness // @ts-expect-error hue: (meta.state.color ?? {}).hue ?? 0, // red // @ts-expect-error saturation: (meta.state.color ?? {}).saturation ?? 100, // full saturation }; // Apply changes if ('brightness' in meta.message) { newSettings.brightness = meta.message.brightness; newState.brightness = meta.message.brightness; } if ('color' in meta.message) { // The Z2M UI sends `{ hex:'#xxxxxx' }`. // Home Assistant sends `{ h: xxx, s: xxx }`. // We convert the former into the latter. const c = libColor.Color.fromConverterArg(meta.message.color); if (c.isRGB()) { // https://github.com/Koenkk/zigbee2mqtt/issues/13421#issuecomment-1426044963 c.hsv = c.rgb.gammaCorrected().toXY().toHSV(); } const color = c.hsv; newSettings.hue = color.hue; newSettings.saturation = color.saturation; newState.color = { hue: color.hue, saturation: color.saturation, }; } // Convert to device specific format and send const brightness = utils.toNumber(newSettings.brightness, 'brightness'); const zclData = { brightness: utils.mapNumberRange(brightness, 0, 254, 0, 1000), hue: newSettings.hue, saturation: utils.mapNumberRange(newSettings.saturation, 0, 100, 0, 1000), }; // This command doesn't support a transition time await entity.command('lightingColorCtrl', 'tuyaMoveToHueAndSaturationBrightness2', zclData, utils.getOptions(meta.mapped, entity)); } } // If we're in white mode, calculate a matching display color for the set color temperature. This also kind // of works in the other direction. Object.assign(newState, libColor.syncColorState(newState, meta.state, entity, meta.options)); return { state: newState }; }, convertGet: async (entity, key, meta) => { await entity.read('lightingColorCtrl', ['currentHue', 'currentSaturation', 'currentLevel', 'tuyaRgbMode', 'colorTemperature']); }, }, TS110E_options: { key: ['min_brightness', 'max_brightness', 'light_type', 'switch_type'], convertSet: async (entity, key, value, meta) => { let payload = null; if (key === 'min_brightness' || key == 'max_brightness') { const id = key === 'min_brightness' ? 64515 : 64516; payload = { [id]: { value: utils.mapNumberRange(utils.toNumber(value, key), 1, 255, 0, 1000), type: 0x21 } }; } else if (key === 'light_type' || key === 'switch_type') { utils.assertString(value, 'light_type/switch_type'); const lookup = key === 'light_type' ? { led: 0, incandescent: 1, halogen: 2 } : { momentary: 0, toggle: 1, state: 2 }; payload = { 64514: { value: lookup[value], type: 0x20 } }; } await entity.write('genLevelCtrl', payload, utils.getOptions(meta.mapped, entity)); return { state: { [key]: value } }; }, convertGet: async (entity, key, meta) => { let id = null; if (key === 'min_brightness') id = 64515; if (key === 'max_brightness') id = 64516; if (key === 'light_type' || key === 'switch_type') id = 64514; await entity.read('genLevelCtrl', [id]); }, }, TS110E_onoff_brightness: { key: ['state', 'brightness'], convertSet: async (entity, key, value, meta) => { const { message, state } = meta; if (message.state === 'OFF' || (message.hasOwnProperty('state') && !message.hasOwnProperty('brightness'))) { return await toZigbee_1.default.on_off.convertSet(entity, key, value, meta); } else if (message.hasOwnProperty('brightness')) { // set brightness if (state.state === 'OFF') { await entity.command('genOnOff', 'on', {}, utils.getOptions(meta.mapped, entity)); } const brightness = utils.toNumber(message.brightness, 'brightness'); const level = utils.mapNumberRange(brightness, 0, 254, 0, 1000); await entity.command('genLevelCtrl', 'moveToLevelTuya', { level, transtime: 100 }, utils.getOptions(meta.mapped, entity)); return { state: { state: 'ON', brightness } }; } }, convertGet: async (entity, key, meta) => { if (key === 'state') await toZigbee_1.default.on_off.convertGet(entity, key, meta); if (key === 'brightness') await entity.read('genLevelCtrl', [61440]); }, }, TS110E_light_onoff_brightness: { ...toZigbee_1.default.light_onoff_brightness, convertSet: async (entity, key, value, meta) => { const { message } = meta; if (message.state === 'ON' || (typeof message.brightness === 'number' && message.brightness > 1)) { // Does not turn off with physical press when turned on with just moveToLevelWithOnOff, required on before. // https://github.com/Koenkk/zigbee2mqtt/issues/15902#issuecomment-1382848150 await entity.command('genOnOff', 'on', {}, utils.getOptions(meta.mapped, entity)); } return toZigbee_1.default.light_onoff_brightness.convertSet(entity, key, value, meta); }, }, TS0504B_color: { key: ['color'], convertSet: async (entity, key, value, meta) => { const color = libColor.Color.fromConverterArg(value); const enableWhite = (color.isRGB() && (color.rgb.red === 1 && color.rgb.green === 1 && color.rgb.blue === 1)) || // Zigbee2MQTT frontend white value (color.isXY() && (color.xy.x === 0.3125 || color.xy.y === 0.32894736842105265)) || // Home Assistant white color picker value (color.isXY() && (color.xy.x === 0.323 || color.xy.y === 0.329)); if (enableWhite) { await entity.command('lightingColorCtrl', 'tuyaRgbMode', { enable: false }); const newState = { color_mode: 'xy' }; if (color.isXY()) { newState.color = color.xy; } else { newState.color = color.rgb.gammaCorrected().toXY().rounded(4); } return { state: libColor.syncColorState(newState, meta.state, entity, meta.options) }; } else { return await toZigbee_1.default.light_color.convertSet(entity, key, value, meta); } }, convertGet: toZigbee_1.default.light_color.convertGet, }, TS0224: { key: ['light', 'duration', 'volume'], convertSet: async (entity, key, value, meta) => { if (key === 'light') { utils.assertString(value, 'light'); await entity.command('genOnOff', value.toLowerCase() === 'on' ? 'on' : 'off', {}, utils.getOptions(meta.mapped, entity)); } else if (key === 'duration') { await entity.write('ssIasWd', { 'maxDuration': value }, utils.getOptions(meta.mapped, entity)); } else if (key === 'volume') { const lookup = { 'mute': 0, 'low': 10, 'medium': 30, 'high': 50 }; utils.assertString(value, 'volume'); const lookupValue = lookup[value]; value = value.toLowerCase(); utils.validateValue(value, Object.keys(lookup)); await entity.write('ssIasWd', { 0x0002: { value: lookupValue, type: 0x0a } }, utils.getOptions(meta.mapped, entity)); } return { state: { [key]: value } }; }, }, temperature_unit: { key: ['temperature_unit'], convertSet: async (entity, key, value, meta) => { switch (key) { case 'temperature_unit': { utils.assertString(value, 'temperature_unit'); await entity.write('manuSpecificTuya_2', { '57355': { value: { 'celsius': 0, 'fahrenheit': 1 }[value], type: 48 } }); break; } default: // Unknown key logger_1.logger.warning(`Unhandled key ${key}`, NS); } }, }, TS011F_threshold: { key: [ 'temperature_threshold', 'temperature_breaker', 'power_threshold', 'power_breaker', 'over_current_threshold', 'over_current_breaker', 'over_voltage_threshold', 'over_voltage_breaker', 'under_voltage_threshold', 'under_voltage_breaker', ], convertSet: async (entity, key, value, meta) => { const onOffLookup = { 'on': 1, 'off': 0 }; switch (key) { case 'temperature_threshold': { const state = meta.state['temperature_breaker']; const buf = Buffer.from([5, utils.getFromLookup(state, onOffLookup), 0, utils.toNumber(value, 'temperature_threshold')]); await entity.command('manuSpecificTuya_3', 'setOptions2', { data: buf }); break; } case 'temperature_breaker': { const threshold = meta.state['temperature_threshold']; const number = utils.toNumber(threshold, 'temperature_threshold'); const buf = Buffer.from([5, utils.getFromLookup(value, onOffLookup), 0, number]); await entity.command('manuSpecificTuya_3', 'setOptions2', { data: buf }); break; } case 'power_threshold': { const state = meta.state['power_breaker']; const buf = Buffer.from([7, utils.getFromLookup(state, onOffLookup), 0, utils.toNumber(value, 'power_breaker')]); await entity.command('manuSpecificTuya_3', 'setOptions2', { data: buf }); break; } case 'power_breaker': { const threshold = meta.state['power_threshold']; const number = utils.toNumber(threshold, 'power_breaker'); const buf = Buffer.from([7, utils.getFromLookup(value, onOffLookup), 0, number]); await entity.command('manuSpecificTuya_3', 'setOptions2', { data: buf }); break; } case 'over_current_threshold': { const state = meta.state['over_current_breaker']; const buf = Buffer.from([1, utils.getFromLookup(state, onOffLookup), 0, utils.toNumber(value, 'over_current_threshold')]); await entity.command('manuSpecificTuya_3', 'setOptions3', { data: buf }); break; } case 'over_current_breaker': { const threshold = meta.state['over_current_threshold']; const number = utils.toNumber(threshold, 'over_current_threshold'); const buf = Buffer.from([1, utils.getFromLookup(value, onOffLookup), 0, number]); await entity.command('manuSpecificTuya_3', 'setOptions3', { data: buf }); break; } case 'over_voltage_threshold': { const state = meta.state['over_voltage_breaker']; const buf = Buffer.from([3, utils.getFromLookup(state, onOffLookup), 0, utils.toNumber(value, 'over_voltage_breaker')]); await entity.command('manuSpecificTuya_3', 'setOptions3', { data: buf }); break; } case 'over_voltage_breaker': { const threshold = meta.state['over_voltage_threshold']; const number = utils.toNumber(threshold, 'over_voltage_threshold'); const buf = Buffer.from([3, utils.getFromLookup(value, onOffLookup), 0, number]); await entity.command('manuSpecificTuya_3', 'setOptions3', { data: buf }); break; } case 'under_voltage_threshold': { const state = meta.state['under_voltage_breaker']; const buf = Buffer.from([4, utils.getFromLookup(state, onOffLookup), 0, utils.toNumber(value, 'under_voltage_threshold')]); await entity.command('manuSpecificTuya_3', 'setOptions3', { data: buf }); break; } case 'under_voltage_breaker': { const threshold = meta.state['under_voltage_threshold']; const number = utils.toNumber(threshold, 'under_voltage_breaker'); const buf = Buffer.from([4, utils.getFromLookup(value, onOffLookup), 0, number]); await entity.command('manuSpecificTuya_3', 'setOptions3', { data: buf }); break; } default: // Unknown key logger_1.logger.warning(`Unhandled key ${key}`, NS); } }, }, }; const fzLocal = { TS0726_action: { cluster: 'genOnOff', type: ['commandTuyaAction'], convert: (model, msg, publish, options, meta) => { return { action: `scene_${msg.endpoint.ID}` }; }, }, TS0222_humidity: { ...fromZigbee_1.default.humidity, convert: async (model, msg, publish, options, meta) => { const result = await fromZigbee_1.default.humidity.convert(model, msg, publish, options, meta); if (result) result.humidity *= 10; return result; }, }, TS110E: { cluster: 'genLevelCtrl', type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data.hasOwnProperty('64515')) { result['min_brightness'] = utils.mapNumberRange(msg.data['64515'], 0, 1000, 1, 255); } if (msg.data.hasOwnProperty('64516')) { result['max_brightness'] = utils.mapNumberRange(msg.data['64516'], 0, 1000, 1, 255); } if (msg.data.hasOwnProperty('61440')) { result['brightness'] = utils.mapNumberRange(msg.data['61440'], 0, 1000, 0, 255); } return result; }, }, TS110E_light_type: { cluster: 'genLevelCtrl', type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data.hasOwnProperty('64514')) { const lookup = { 0: 'led', 1: 'incandescent', 2: 'halogen' }; result['light_type'] = lookup[msg.data['64514']]; } return result; }, }, TS110E_switch_type: { cluster: 'genLevelCtrl', type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data.hasOwnProperty('64514')) { const lookup = { 0: 'momentary', 1: 'toggle', 2: 'state' }; const propertyName = utils.postfixWithEndpointName('switch_type', msg, model, meta); result[propertyName] = lookup[msg.data['64514']]; } return result; }, }, scenes_recall_scene_65029: { cluster: '65029', type: ['raw', 'attributeReport'], convert: (model, msg, publish, options, meta) => { const id = meta.device.modelID === '005f0c3b' ? msg.data[0] : msg.data[msg.data.length - 1]; return { action: `scene_${id}` }; }, }, TS0201_battery: { cluster: 'genPowerCfg', type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { // https://github.com/Koenkk/zigbee2mqtt/issues/11470 if (msg.data.batteryPercentageRemaining == 200 && msg.data.batteryVoltage < 30) return; return fromZigbee_1.default.battery.convert(model, msg, publish, options, meta); }, }, TS0201_humidity: { ...fromZigbee_1.default.humidity, convert: (model, msg, publish, options, meta) => { if (['_TZ3210_ncw88jfq', '_TZ3000_ywagc4rj'].includes(meta.device.manufacturerName)) { msg.data['measuredValue'] *= 10; } return fromZigbee_1.default.humidity.convert(model, msg, publish, options, meta); }, }, humidity10: { cluster: 'msRelativeHumidity', type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { const humidity = parseFloat(msg.data['measuredValue']) / 10.0; if (humidity >= 0 && humidity <= 100) { return { humidity }; } }, }, temperature_unit: { cluster: 'manuSpecificTuya_2', type: ['attributeReport', 'readResponse'], convert: (model, msg, publish, options, meta) => { const result = {}; if (msg.data.hasOwnProperty('57355')) { result.temperature_unit = utils.getFromLookup(msg.data['57355'], { '0': 'celsius', '1': 'fahrenheit' }); } return result; }, }, TS011F_electrical_measurement: { ...fromZigbee_1.default.electrical_measurement, convert: async (model, msg, publish, options, meta) => { const result = await fromZigbee_1.default.electrical_measurement.convert(model, msg, publish, options, meta) ?? {}; const lookup = { power: 'activePower', current: 'rmsCurrent', voltage: 'rmsVoltage' }; // Wait 5 seconds before reporting a 0 value as this could be an invalid measurement. // https://github.com/Koenkk/zigbee2mqtt/issues/16709#issuecomment-1509599046 if (result) { for (const key of ['power', 'current', 'voltage']) { if (key in result) { const value = result[key]; clearTimeout(globalStore.getValue(msg.endpoint, key)); if (value === 0) { const configuredReporting = msg.endpoint.configuredReportings.find((c) => c.cluster.name === 'haElectricalMeasurement' && c.attribute.name === lookup[key]); const time = ((configuredReporting ? configuredReporting.minimumReportInterval : 5) * 2) + 1; globalStore.putValue(msg.endpoint, key, setTimeout(() => { const payload = { [key]: value }; // Device takes a lot of time to report power 0 in some cases. When current == 0 we can assume power == 0 // https://github.com/Koenkk/zigbee2mqtt/discussions/19680#discussioncomment-7868445 if (key === 'current') { payload.power = 0; } publish(payload); }, time * 1000)); delete result[key]; } } } } // Device takes a lot of time to report power 0 in some cases. When the state is OFF we can assume power == 0 // https://github.com/Koenkk/zigbee2mqtt/discussions/19680#discussioncomment-7868445 if (meta.state.state === 'OFF') { result.power = 0; } return result; }, }, TS011F_threshold: { cluster: 'manuSpecificTuya_3', type: 'raw', convert: (model, msg, publish, options, meta) => { const splitToAttributes = (value) => { const result = {}; const len = value.length; let i = 0; while (i < len) { const key = value.readUInt8(i); result[key] = [value.readUInt8(i + 1), value.readUInt16BE(i + 2)]; i += 4; } return result; }; const lookup = { 0: 'OFF', 1: 'ON' }; const command = msg.data[2]; const data = msg.data.slice(3); if (command == 0xE6) { const value = splitToAttributes(data); return { 'temperature_threshold': value[0x05][1], 'temperature_breaker': lookup[value[0x05][0]], 'power_threshold': value[0x07][1], 'power_breaker': lookup[value[0x07][0]], }; } if (command == 0xE7) { const value = splitToAttributes(data); return { 'over_current_threshold': value[0x01][1], 'over_current_breaker': lookup[value[0x01][0]], 'over_voltage_threshold': value[0x03][1], 'over_voltage_breaker': lookup[value[0x03][0]], 'under_voltage_threshold': value[0x04][1], 'under_voltage_breaker': lookup[value[0x04][0]], }; } }, }, }; const definitions = [ { zigbeeModel: ['TS0204'], model: 'TS0204', vendor: 'TuYa', description: 'Gas sensor', whiteLabel: [{ vendor: 'Tesla Smart', model: 'TSL-SEN-GAS' }], fromZigbee: [fromZigbee_1.default.ias_gas_alarm_1, fromZigbee_1.default.ignore_basic_report], toZigbee: [], exposes: [e.gas(), e.tamper()], }, { zigbeeModel: ['TS0205'], model: 'TS0205', vendor: 'TuYa', description: 'Smoke sensor', whiteLabel: [ { vendor: 'Tesla Smart', model: 'TSL-SEN-SMOKE' }, { vendor: 'Dongguan Daying Electornics Technology', model: 'YG400A' }, tuya.whitelabel('TuYa', 'TS0205_smoke_2', 'Smoke sensor', ['_TZ3210_up3pngle']), ], fromZigbee: [fromZigbee_1.default.ias_smoke_alarm_1, fromZigbee_1.default.ignore_basic_report], toZigbee: [], exposes: [e.smoke(), e.battery_low(), e.tamper()], extend: [(0, modernExtend_1.battery)()], }, { zigbeeModel: ['TS0111'], model: 'TS0111', vendor: 'TuYa', description: 'Socket', extend: [tuya.modernExtend.tuyaOnOff()], }, { zigbeeModel: ['TS0218'], model: 'TS0218', vendor: 'TuYa', description: 'Button', fromZigbee: [legacy.fromZigbee.TS0218_click, fromZigbee_1.default.battery], exposes: [e.battery(), e.action(['click'])], toZigbee: [], }, { zigbeeModel: ['TS0203'], model: 'TS0203', vendor: 'TuYa', description: 'Door sensor', fromZigbee: [fromZigbee_1.default.ias_contact_alarm_1, fromZigbee_1.default.battery, fromZigbee_1.default.ignore_basic_report, fromZigbee_1.default.ias_contact_alarm_1_report], toZigbee: [], whiteLabel: [ { vendor: 'CR Smart Home', model: 'TS0203' }, { vendor: 'TuYa', model: 'iH-F001' }, { vendor: 'Tesla Smart', model: 'TSL-SEN-DOOR' }, { vendor: 'Cleverio', model: 'SS100' }, tuya.whitelabel('Niceboy', 'ORBIS Windows & Door Sensor', 'Door sensor', ['_TZ3000_qrldbmfn']), tuya.whitelabel('TuYa', 'ZD08', 'Door sensor', ['_TZ3000_7d8yme6f']), tuya.whitelabel('TuYa', 'MC500A', 'Door sensor', ['_TZ3000_2mbfxlzr']), tuya.whitelabel('TuYa', '19DZT', 'Door sensor', ['_TZ3000_n2egfsli']), tuya.whitelabel('TuYa', 'DS04', 'Door sensor', ['_TZ3000_yfekcy3n']), tuya.whitelabel('Moes', 'ZSS-JM-GWM-C-MS', 'Smart door and window sensor', ['_TZ3000_decxrtwa']), tuya.whitelabel('Moes', 'ZSS-X-GWM-C', 'Door/window magnetic sensor', ['_TZ3000_gntwytxo']), tuya.whitelabel('Luminea', 'ZX-5232', 'Smart door and window sensor', ['_TZ3000_4ugnzsli']), ], exposes: (device, options) => { const exps = [e.contact(), e.battery_low(), e.battery(), e.battery_voltage()]; const noTamperModels = [ '_TZ3000_2mbfxlzr', // TuYa MC500A '_TZ3000_n2egfsli', // TuYa 19DZT '_TZ3000_yfekcy3n', // TuYa DS04 '_TZ3000_bpkijo14', '_TZ3000_gntwytxo', // Moes ZSS-X-GWM-C '_TZ3000_4ugnzsli', // Luminea ZX-5232 ]; if (!device || !noTamperModels.includes(device.manufacturerName)) { exps.push(e.tamper()); } exps.push(e.linkquality()); return exps; }, configure: async (device, coordinatorEndpoint) => { try { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']); await reporting.batteryPercentageRemaining(endpoint); await reporting.batteryVoltage(endpoint); } catch (error) { /* Fails for some*/ } }, }, { fingerprint: tuya.fingerprint('TS0203', ['_TZ3210_jowhpxop']), model: 'TS0203_1', vendor: 'TuYa', description: 'Door sensor with scene switch', fromZigbee: [tuya.fz.datapoints, fromZigbee_1.default.ias_contact_alarm_1, fromZigbee_1.default.battery, fromZigbee_1.default.ignore_basic_report, fromZigbee_1.default.ias_contact_alarm_1_report], toZigbee: [tuya.tz.datapoints], onEvent: tuya.onEventSetTime, configure: tuya.configureMagicPacket, exposes: [e.action(['single', 'double', 'hold']), e.contact(), e.battery_low(), e.tamper(), e.battery(), e.battery_voltage()], meta: { tuyaDatapoints: [ [101, 'action', tuya.valueConverterBasic.lookup({ 'single': 0, 'double': 1, 'hold': 2 })], ], }, whiteLabel: [ tuya.whitelabel('Linkoze', 'LKDSZ001', 'Door sensor with scene switch', ['_TZ3210_jowhpxop']), ], }, { fingerprint: [ { modelID: 'TS0021', manufacturerName: '_TZ3210_3ulg9kpo' }, ], model: 'LKWSZ211', vendor: 'TuYa', description: 'Scene remote with 2 keys', fromZigbee: [tuya.fz.datapoints, fromZigbee_1.default.ignore_basic_report], toZigbee: [tuya.tz.datapoints], onEvent: tuya.onEventSetTime, configure: tuya.configureMagicPacket, exposes: [ e.battery(), e.action([ 'button_1_single', 'button_1_double', 'button_1_hold', 'button_2_single', 'button_2_double', 'button_2_hold', ]), ], meta: { tuyaDatapoints: [ [1, 'action', tuya.valueConverterBasic.lookup({ 'button_1_single': tuya.enum(0), 'button_1_double': tuya.enum(1), 'button_1_hold': tuya.enum(2), }), ], [2, 'action', tuya.valueConverterBasic.lookup({ 'button_2_single': tuya.enum(0), 'button_2_double': tuya.enum(1), 'button_2_hold': tuya.enum(2), }), ], [10, 'battery', tuya.valueConverter.raw], ], }, whiteLabel: [ tuya.whitelabel('Linkoze', 'LKWSZ211', 'Wireless switch (2-key)', ['_TZ3210_3ulg9kpo']), tuya.whitelabel('Adaprox', 'LKWSZ211', 'Remote wireless switch (2-key)', ['_TZ3210_3ulg9kpo']), ], }, { fingerprint: [{ modelID: 'TS0601', manufacturerName: '_TZE200_bq5c8xfe' }, { modelID: 'TS0601', manufacturerName: '_TZE200_bjawzodf' }, { modelID: 'TS0601', manufacturerName: '_TZE200_qyflbnbj' }, { modelID: 'TS0601', manufacturerName: '_TZE200_vs0skpuc' }, { modelID: 'TS0601', manufacturerName: '_TZE200_44af8vyi' }, { modelID: 'TS0601', manufacturerName: '_TZE200_zl1kmjqx' }], model: 'TS0601_temperature_humidity_sensor_1', vendor: 'TuYa', description: 'Temperature & humidity sensor', fromZigbee: [legacy.fromZigbee.tuya_temperature_humidity_sensor], toZigbee: [], exposes: (device, options) => { const exps = [e.temperature(), e.humidity(), e.battery()]; if (!device || device.manufacturerName === '_TZE200_qyflbnbj') { exps.push(e.battery_low()); exps.push(e.enum('battery_level', ea.STATE, ['low', 'middle', 'high']).withDescription('Battery level state')); } exps.push(e.linkquality()); return exps; }, }, { fingerprint: [{ modelID: 'TS0601', manufacturerName: '_TZE200_mfamvsdb' }], model: 'F00MB00-04-1', vendor: 'FORIA', description: '4 scenes switch', extend: [ tuya.modernExtend.tuyaMagicPacket(), tuya.modernExtend.combineActions([ tuya.modernExtend.dpAction({ dp: 1, lookup: { 'scene_1': 0 } }), tuya.modernExtend.dpAction({ dp: 2, lookup: { 'scene_2': 0 } }), tuya.modernExtend.dpAction({ dp: 3, lookup: { 'scene_3': 0 } }), tuya.modernExtend.dpAction({ dp: 4, lookup: { 'scene_4': 0 } }), ]), tuya.modernExtend.dpBinary({ name: 'vibration', dp: 0x6c, type: tuya.dataTypes.enum, valueOn: ['ON', 1], valueOff: ['OFF', 0], description: 'Enable vibration', }), tuya.modernExtend.dpBinary({ name: 'approach', dp: 0x6b, type: tuya.dataTypes.enum, valueOn: ['ON', 1], valueOff: ['OFF', 0], description: 'Enable approach detection', }), tuya.modernExtend.dpBinary({ name: 'illumination', dp: 0x6a, type: tuya.dataTypes.enum, valueOn: ['ON', 1], valueOff: ['OFF', 0], description: 'Enable illumination detection', }), tuya.modernExtend.dpBinary({ name: 'backlight', dp: 0x69, type: tuya.dataTypes.enum, valueOn: ['ON', 1], valueOff: ['OFF', 0], description: 'Enable backlight', }), ], }, { fingerprint: tuya.fingerprint('TS0601', ['_TZE200_dhke3p9w']), model: 'F00YK04-18-1', vendor: 'FORIA', description: '18 scenes remote', extend: [ tuya.modernExtend.tuyaMagicPacket(), tuya.modernExtend.combineActions([ tuya.modernExtend.dpAction({ dp: 1, lookup: { 'scene_1': 0 } }), tuya.modernExtend.dpAction({ dp: 2, lookup: { 'scene_2': 0 } }), tuya.modernExtend.dpAction({ dp: 3, lookup: { 'scene_3': 0 } }), tuya.modernExtend.dpAction({ dp: 4, lookup: { 'scene_4': 0 } }), tuya.modernExtend.dpAction({ dp: 5, lookup: { 'scene_5': 0 } }), tuya.modernExtend.dpAction({ dp: 6, lookup: { 'scene_6': 0 } }), tuya.modernExtend.dpAction({ dp: 7, lookup: { 'scene_7': 0 } }), tuya.modernExtend.dpAction({ dp: 8, lookup: { 'scene_8': 0 } }), tuya.modernExtend.dpAction({ dp: 9, lookup: { 'scene_9': 0 } }), tuya.modernExtend.dpAction({ dp: 10, lookup: { 'scene_10': 0 } }), tuya.modernExtend.dpAction({ dp: 11, lookup: { 'scene_11': 0 } }), tuya.modernExtend.dpAction({ dp: 12, lookup: { 'scene_12': 0 } }), tuya.modernExtend.dpAction({ dp: 13, lookup: { 'scene_13': 0 } }), tuya.modernExtend.dpAction({ dp: 14, lookup: { 'scene_14': 0 } }), tuya.modernExtend.dpAction({ dp: 15, lookup: { 'scene_15': 0 } }), tuya.modernExtend.dpAction({ dp: 16, lookup: { 'scene_16': 0 } }), tuya.modernExtend.dpAction({ dp: 101, lookup: { 'scene_17': 0 } }), tuya.modernExtend.dpAction({ dp: 102, lookup: { 'scene_18': 0 } }), ]), ], }, { fingerprint: tuya.fingerprint('TS0601', [ '_TZE200_yjjdcqsq', '_TZE200_9yapgbuv', '_TZE200_utkemkbs', '_TZE204_utkemkbs', '_TZE204_9yapgbuv', '_TZE204_upagmta9', '_TZE200_cirvgep4', '_TZE200_upagmta9', '_TZE204_yjjdcqsq', '_TZE204_cirvgep4', ]), model: 'TS0601_temperature_humidity_sensor_2', vendor: 'TuYa', description: 'Temperature and humidity sensor', fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], onEvent: tuya.onEvent({ queryOnDeviceAnnounce: true }), configure: async (device, coordinatorEndpoint) => { await tuya.configureMagicPacket(device, coordinatorEndpoint); // Required to get the device to start reporting await device.getEndpoint(1).command('manuSpecificTuya', 'dataQuery', {}); }, exposes: [e.temperature(), e.humidity(), tuya.exposes.batteryState(), tuya.exposes.temperatureUnit()], meta: { tuyaDatapoints: [ [1, 'temperature', tuya.valueConverter.divideBy10], [2, 'humidity', tuya.valueConverter.raw], [3, 'battery_state', tuya.valueConverter.batteryState], [9, 'temperature_unit', tuya.valueConverter.temperatureUnitEnum], ], }, whiteLabel: [ tuya.whitelabel('TuYa', 'ZTH01', 'Temperature and humidity sensor', ['_TZE200_yjjdcqsq', '_TZE204_yjjdcqsq']), tuya.whitelabel('TuYa', 'SZTH02', 'Temperature and humidity sensor', ['_TZE200_utkemkbs', '_TZE204_utkemkbs']), tuya.whitelabel('TuYa', 'ZTH02', 'Temperature and humidity sensor', ['_TZE200_9yapgbuv', '_TZE204_9yapgbuv']), tuya.whitelabel('TuYa', 'ZTH05', 'Temperature and humidity sensor', ['_TZE204_upagmta9', '_TZE200_upagmta9']), tuya.whitelabel('TuYa', 'ZTH08-E', 'Temperature and humidity sensor', ['_TZE200_cirvgep4', '_TZE204_cirvgep4']), ], }, { fingerprint: tuya.fingerprint('TS0601', ['_TZE200_vvmbj46n']), model: 'ZTH05Z', vendor: 'TuYa', description: 'Temperature and humidity sensor', fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], onEvent: tuya.onEvent({ queryOnDeviceAnnounce: true }), configure: async (device, coordinatorEndpoint) => { await tuya.configureMagicPacket(device, coordinatorEndpoint); // Required to get the device to start reporting await device.getEndpoint(1).command('manuSpecificTuya', 'dataQuery', {}); }, exposes: [ e.temperature(), e.humidity(), e.battery(), e.enum('temperature_unit', ea.STATE_SET, ['celsius', 'fahrenheit']).withDescription('Temperature unit'), e.numeric('max_temperature_alarm', ea.STATE_SET).withUnit('°C').withValueMin(-20).withValueMax(60) .withDescription('Alarm temperature max'), e.numeric('min_temperature_alarm', ea.STATE_SET).withUnit('°C').withValueMin(-20).withValueMax(60) .withDescription('Alarm temperature min'), e.numeric('max_humidity_alarm', ea.STATE_SET).withUnit('%').withValueMin(0).withValueMax(100).withDescription('Alarm humidity max'), e.numeric('min_humidity_alarm', ea.STATE_SET).withUnit('%').withValueMin(0).withValueMax(100).withDescription('Alarm humidity min'), e.enum('temperature_alarm', ea.STATE_SET, ['lower_alarm', 'upper_alarm', 'cancel']).withDescription('Temperature alarm'), e.enum('humidity_alarm', ea.STATE_SET, ['lower_alarm', 'upper_alarm', 'cancel']).withDescription('Humidity alarm'), e.numeric('temperature_periodic_report', ea.STATE_SET).withUnit('%').withValueMin(0).withValueMax(100) .withDescription('Temp periodic report'), e.numeric('humidity_periodic_report', ea.STATE_SET).withUnit('%').withValueMin(0).withValueMax(100) .withDescription('Humidity periodic report'), e.numeric('temperature_sensitivity', ea.STATE_SET).withUnit('°C').withValueMin(3).withValueMax(10).withValueStep(1) .withDescription('Sensitivity of temperature'), e.numeric('humidity_sensitivity', ea.STATE_SET).withUnit('%').withValueMin(3).withValueMax(10).withValueStep(1) .withDescription('Sensitivity of humidity'), ], meta: { tuyaDatapoints: [ [1, 'temperature', tuya.valueConverter.divideBy10], [2, 'humidity', tuya.valueConverter.raw], [4, 'battery', tuya.valueConverter.raw], [9, 'temperature_unit', tuya.valueConverter.temperatureUnitEnum], [10, 'max_temperature_alarm', tuya.valueConverter.divideBy10], [11, 'min_temperature_alarm', tuya.valueConverter.divideBy10], [12, 'max_humidity_alarm', tuya.valueConverter.raw], [13, 'min_humidity_alarm', tuya.valueConverter.raw], [14, 'temperature_alarm', tuya.valueConverterBasic.lookup({ 'lower_alarm': tuya.enum(0), 'upper_alarm': tuya.enum(1), 'cancel': tuya.enum(2) })], [15, 'humidity_alarm', tuya.valueConverterBasic.lookup({ 'lower_alarm': tuya.enum(0), 'upper_alarm': tuya.enum(1), 'cancel': tuya.enum(2) })], [17, 'temperature_periodic_report', tuya.valueConverter.raw], [18, 'humidity_periodic_report', tuya.valueConverter.raw], [19, 'temperature_sensitivity', tuya.valueConverter.raw], [20, 'humidity_sensitivity', tuya.valueConverter.raw], ], }, }, { fingerprint: tuya.fingerprint('TS0601', ['_TZE200_nvups4nh']), model: 'TS0601_contact_temperature_humidity_sensor', vendor: 'TuYa', description: 'Contact, temperature and humidity sensor', fromZigbee: [tuya.fz.datapoints, tuya.fz.gateway_connection_status], toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, exposes: [e.contact(), e.temperature(), e.humidity(), e.battery()], meta: { tuyaDatapoints: [ [1, 'contact', tuya.valueConverter.trueFalseInvert], [2, 'battery', tuya.valueConverter.raw], [7, 'temperature', tuya.valueConverter.divideBy10], [8, 'humidity', tuya.valueConverter.raw], ], }, whiteLabel: [ tuya.whitelabel('Aubess', '1005005194831629', 'Contact, temperature and humidity sensor', ['_TZE200_nvups4nh']), ], }, { fingerprint: [{ modelID: 'TS0601', manufacturerName: '_TZE200_vzqtvljm' }], model: 'TS0601_illuminance_temperature_humidity_sensor_1', vendor: 'TuYa', description: 'Illuminance, temperature & humidity sensor', fromZigbee: [legacy.fromZigbee.tuya_illuminance_temperature_humidity_sensor], toZigbee: [], exposes: [e.temperature(), e.humidity(), e.illuminance_lux(), e.battery()], }, { fingerprint: [{ modelID: 'TS0601', manufacturerName: '_TZE200_8ygsuhe1' }, { modelID: 'TS0601', manufacturerName: '_TZE200_yvx5lh6k' }, { modelID: 'TS0601', manufacturerName: '_TZE200_ryfmq5rl' }, { modelID: 'TS0601', manufacturerName: '_TZE200_c2fmom5z' }, { modelID: 'TS0601', manufacturerName: '_TZE200_mja3fuja' }], model: 'TS0601_air_quality_sensor', vendor: 'TuYa', description: 'Air quality sensor', fromZigbee: [legacy.fromZigbee.tuya_air_quality], toZigbee: [], exposes: [e.temperature(), e.humidity(), e.co2(), e.voc().withUnit('ppm'), e.formaldehyd()], }, { fingerprint: [{ modelID: 'TS0601', manufacturerName: '_TZE200_rbbx5mfq' }], model: 'TS0601_illuminance_temperature_humidity_sensor_2', vendor: 'TuYa', description: 'Illuminance sensor', fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, exposes: [e.illuminance(), e.temperature().withUnit('lx'), e.humidity()], meta: { tuyaDatapoints: [ [2, 'illuminance', tuya.valueConverter.raw], [6, 'temperature', tuya.valueConverter.divideBy10], [7, 'humidity', tuya.valueConverter.divideBy10], ], }, }, { fingerprint: tuya.fingerprint('TS0601', ['_TZE200_dwcarsat']), model: 'TS0601_smart_air_house_keeper', vendor: 'TuYa', description: 'Smart air house keeper', fromZigbee: [legacy.fromZigbee.tuya_air_quality], toZigbee: [], exposes: [e.temperature(), e.humidity(), e.co2(), e.voc().withUnit('ppm'), e.formaldehyd().withUnit('µg/m³'), e.pm25().withValueMin(0).withValueMax(999).withValueStep(1)], }, { fingerprint: tuya.fingerprint('TS0601', ['_TZE200_ogkdpgy2', '_TZE200_3ejwxpmu']), model: 'TS0601_co2_sensor', vendor: 'TuYa', description: 'NDIR co2 sensor', fromZigbee: [legacy.fromZigbee.tuya_air_quality], toZigbee: [], exposes: [e.temperature(), e.humidity(), e.co2()], }, { fingerprint: [{ modelID: 'TS0601', manufacturerName: '_TZE200_7bztmfm1' }], model: 'TS0601_smart_CO_air_box', vendor: 'TuYa', description: 'Smart air box (carbon monoxide)', fromZigbee: [legacy.fromZigbee.tuya_CO], toZigbee: [], exposes: [e.carbon_monoxide(), e.co()], }, { fingerprint: tuya.fingerprint('TS0601', ['_TZE200_ggev5fsl', '_TZE200_u319yc66', '_TZE200_kvpwq8z7']), model: 'TS0601_gas_sensor_1', vendor: 'TuYa', description: 'Gas sensor', fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, exposes: [e.gas(), tuya.exposes.selfTest(), tuya.exposes.selfTestResult(), tuya.exposes.faultAlarm(), tuya.exposes.silence()], meta: { tuyaDatapoints: [ [1, 'gas', tuya.valueConverter.trueFalse0], [8, 'self_test', tuya.valueConverter.raw], [9, 'self_test_result', tuya.valueConverter.selfTestResult], [11, 'fault_alarm', tuya.valueConverter.trueFalse1],