zigbee-herdsman-converters
Version:
Collection of device converters to be used with zigbee-herdsman
977 lines (976 loc) • 461 kB
JavaScript
"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],