zigbee-herdsman-converters
Version:
Collection of device converters to be used with zigbee-herdsman
29,581 lines • 1.42 MB
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 () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.definitions = void 0;
const node_util_1 = require("node:util");
const zigbee_herdsman_1 = require("zigbee-herdsman");
const zcl_1 = require("zigbee-herdsman/dist/zspec/zcl");
const fz = __importStar(require("../converters/fromZigbee"));
const tz = __importStar(require("../converters/toZigbee"));
const libColor = __importStar(require("../lib/color"));
const constants_1 = require("../lib/constants");
const exposes = __importStar(require("../lib/exposes"));
const legacy = __importStar(require("../lib/legacy"));
const logger_1 = require("../lib/logger");
const m = __importStar(require("../lib/modernExtend"));
const reporting = __importStar(require("../lib/reporting"));
const globalStore = __importStar(require("../lib/store"));
const tuya = __importStar(require("../lib/tuya"));
const utils = __importStar(require("../lib/utils"));
const utils_1 = require("../lib/utils");
const zosung = __importStar(require("../lib/zosung"));
const NS = "zhc:tuya";
const { tuyaLight, tuyaBase, tuyaMagicPacket, dpBinary, dpNumeric, dpEnumLookup, tuyaWeatherForecast } = tuya.modernExtend;
const e = exposes.presets;
const ea = exposes.access;
const te = tuya.exposes;
const tvc = tuya.valueConverter;
const fzZosung = zosung.fzZosung;
const tzZosung = zosung.tzZosung;
const ez = zosung.presetsZosung;
const storeLocal = {
getPrivatePJ1203A: (device) => {
let priv = globalStore.getValue(device, "private_state");
if (priv === undefined) {
//
// The PJ-1203A is sending quick sequences of messages containing a single datapoint.
// A sequence occurs every `update_frequency` seconds (10s by default)
//
// A typical sequence is composed of two identical groups for channel a and b.
//
// 102 energy_flow_a
// 112 voltage
// 113 current_a
// 101 power_a
// 110 power_factor_a
// 111 ac_frequency
// 115 power_ab
// ---
// 104 energy_flow_b
// 112 voltage
// 114 current_b
// 105 power_b
// 121 power_factor_b
// 111 ac_frequency
// 115 power_ab
//
// It should be noted that when no current is detected on channel x then
// energy_flow_x is not emitted and current_x==0, power_x==0 and power_factor_x==100.
//
// The other datapoints are emitted every few minutes.
//
// There is a known issue on the _TZE204_81yrt3lo (with appVersion 74, stackVersion 0 and hwVersion 1).
// The energy_flow datapoints are (incorrectly) emitted during the next update. This is quite problematic
// because that means that the direction can be inverted for up to update_frequency seconds.
//
// The features implemented here are
// - cache the datapoints for each channel and publish them together.
// - (OPTIONAL) solve the issue described above by waiting for the next energy flow datapoint
// before publishing the cached channel data.
// - (OPTIONAL) provide signed power instead of energy flow.
// - detect missing or reordered Zigbee message using the Tuya 'seq' attribute and invalidate
// cached data accordingly.
//
priv = {
// Cached values for both channels
sign_a: null,
sign_b: null,
power_a: null,
power_b: null,
current_a: null,
current_b: null,
power_factor_a: null,
power_factor_b: null,
timestamp_a: null,
timestamp_b: null,
// Used to detect missing or misordered messages.
last_seq: -99999,
// Do all PJ-1203A increment seq by 256? If not, then this is
// the value that will have to be customized.
seq_inc: 256,
// Also need to save the last published SIGNED values of
// power_a and power_b to recompute power_ab on the fly.
pub_power_a: null,
pub_power_b: null,
recompute_power_ab: function (result) {
let modified = false;
if ("power_a" in result) {
this.pub_power_a = result.power_a * (result.energy_flow_a === "producing" ? -1 : 1);
modified = true;
}
if ("power_b" in result) {
this.pub_power_b = result.power_b * (result.energy_flow_b === "producing" ? -1 : 1);
modified = true;
}
if (modified) {
if (this.pub_power_a !== null && this.pub_power_b !== null) {
// Cancel and reapply the scaling by 10 to avoid floating-point rounding errors
// such as 79.8 - 37.1 = 42.699999999999996
result.power_ab = Math.round(10 * this.pub_power_a + 10 * this.pub_power_b) / 10;
}
}
},
flush: function (result, channel, options) {
const sign = this[`sign_${channel}`];
const power = this[`power_${channel}`];
const current = this[`current_${channel}`];
const powerFactor = this[`power_factor_${channel}`];
this[`sign_${channel}`] = this[`power_${channel}`] = this[`current_${channel}`] = this[`power_factor_${channel}`] = null;
// Only publish if the set is complete otherwise discard everything.
if (sign !== null && power !== null && current !== null && powerFactor !== null) {
const signedPowerKey = `signed_power_${channel}`;
const invertedEnergyFlowKey = `invert_energy_flow_${channel}`;
const signedPower = options[signedPowerKey] != null ? options[signedPowerKey] : false;
const invertedEnergyFlow = options[invertedEnergyFlowKey] != null ? options[invertedEnergyFlowKey] : false;
if (signedPower) {
result[`power_${channel}`] = sign * power;
result[`energy_flow_${channel}`] = "sign";
}
else {
result[`power_${channel}`] = power;
result[`energy_flow_${channel}`] = sign * (invertedEnergyFlow ? -1 : 1) >= 0 ? "consuming" : "producing";
}
result[`timestamp_${channel}`] = this[`timestamp_${channel}`];
result[`current_${channel}`] = current;
result[`power_factor_${channel}`] = powerFactor;
this.recompute_power_ab(result);
return true;
}
return false;
},
// When the device does not detect any flow, it stops sending
// the energy_flow datapoint (102 and 104) and always set
// current_x=0, power_x=0 and power_factor_x=100.
//
// So if we see a datapoint with current==0 or power==0
// then we can safely assume that we are in that zero energy state.
//
// Also, the publication of a zero energy state is not delayed
// when option late_energy_flow_a|b is set.
flushZero: function (result, channel, options) {
this[`sign_${channel}`] = +1;
this[`power_${channel}`] = 0;
this[`timestamp_${channel}`] = new Date().toISOString();
this[`current_${channel}`] = 0;
this[`power_factor_${channel}`] = 100;
this.flush(result, channel, options);
},
// Some times the device sends a single zero value (either power or current).
// This is most likely a glitch. We flush all values but set them to null
// to indicate that they are not valid.
//
flushNull: function (result, channel, options) {
this[`sign_${channel}`] = null;
this[`power_${channel}`] = null;
this[`current_${channel}`] = null;
this[`power_factor_${channel}`] = null;
this.flush(result, channel, options);
},
clear: () => {
priv.sign_a = null;
priv.sign_b = null;
priv.power_a = null;
priv.power_b = null;
priv.current_a = null;
priv.current_b = null;
priv.power_factor_a = null;
priv.power_factor_b = null;
// Used to detect single zero values
priv.zero_power_a = null;
priv.zero_power_b = null;
priv.zero_current_a = null;
priv.zero_current_b = null;
},
};
globalStore.putValue(device, "private_state", priv);
}
return priv;
},
};
const convLocal = {
novaDigitalToDmBrightness: {
from: (value) => {
const clamped = Math.max(10, Math.min(1000, Number(value) || 10));
return Math.round(((clamped - 10) * 254) / 990);
},
to: (value) => {
const clamped = Math.max(0, Math.min(254, Number(value) || 0));
return Math.round(10 + (clamped * 990) / 254);
},
},
novaDigitalToDmBrightnessPercent: {
from: (value) => {
const clamped = Math.max(10, Math.min(1000, Number(value) || 10));
return Math.round(clamped / 10);
},
to: (value) => {
const clamped = Math.max(1, Math.min(100, Number(value) || 1));
return Math.round(clamped * 10);
},
},
novaDigitalToWkInching: {
from: (value) => {
const buffer = Buffer.isBuffer(value)
? value
: typeof value === "string"
? Buffer.from(value, "base64")
: Array.isArray(value)
? Buffer.from(value)
: Buffer.alloc(0);
if (buffer.length < 3) {
return 0;
}
const enabled = buffer.readUInt8(0) === 1;
const totalSeconds = buffer.readUInt16BE(1);
return enabled ? totalSeconds : 0;
},
to: (value) => {
const totalSeconds = Math.max(0, Math.min(3600, Number(value) || 0));
const buffer = Buffer.alloc(3);
buffer.writeUInt8(totalSeconds > 0 ? 1 : 0, 0);
buffer.writeUInt16BE(totalSeconds > 0 ? totalSeconds : 1, 1);
return buffer.toString("base64");
},
},
novaDigitalToWk2InchingFrom: {
from: (value) => {
if (value === undefined || value === null) {
return {};
}
let rawValue = "";
if (Buffer.isBuffer(value)) {
rawValue = value.toString("utf8");
}
else if (Array.isArray(value)) {
rawValue = Buffer.from(value).toString("utf8");
}
else {
rawValue = String(value);
}
const result = {};
for (let i = 0; i < rawValue.length; i += 4) {
const block = rawValue.substring(i, i + 4);
if (block.length < 4) {
continue;
}
const buffer = Buffer.from(block, "base64");
if (buffer.length < 3) {
continue;
}
const controlByte = buffer.readUInt8(0);
const seconds = buffer.readUInt16BE(1);
let channel = 1;
let enabled = false;
if (controlByte === 0 || controlByte === 1) {
channel = 1;
enabled = controlByte === 1;
}
else {
channel = Math.trunc(Math.log2(controlByte)) + 1;
enabled = controlByte % 2 === 1;
}
if (channel === 1) {
result.inching_l1 = enabled ? seconds : 0;
}
if (channel === 2) {
result.inching_l2 = enabled ? seconds : 0;
}
}
return result;
},
},
novaDigitalToWk2InchingTo: (channel) => ({
to: (value) => {
const totalSeconds = Math.max(0, Math.min(3600, Number(value) || 0));
let controlByte = totalSeconds > 0 ? 1 : 0;
if (channel !== 1) {
controlByte += 2 ** (channel - 1);
}
const buffer = Buffer.alloc(3);
buffer.writeUInt8(controlByte, 0);
buffer.writeUInt16BE(totalSeconds > 0 ? totalSeconds : 1, 1);
return buffer.toString("base64");
},
}),
energyFlowPJ1203A: (channel) => {
return {
from: (v, meta, options) => {
const priv = storeLocal.getPrivatePJ1203A(meta.device);
const result = {};
priv[`sign_${channel}`] = v === 1 ? -1 : +1;
const lateEnergyFlowKey = `late_energy_flow_${channel}`;
const lateEnergyFlow = options[lateEnergyFlowKey] != null ? options[lateEnergyFlowKey] : false;
if (lateEnergyFlow) {
priv.flush(result, channel, options);
}
return result;
},
};
},
powerPJ1203A: (channel) => {
return {
from: (v, meta, options) => {
const priv = storeLocal.getPrivatePJ1203A(meta.device);
const result = {};
priv[`power_${channel}`] = v / 10;
priv[`timestamp_${channel}`] = new Date().toISOString();
if (v === 0) {
const singleZeroRemoveKey = "single_zero_remove";
const singleZeroRemove = options[singleZeroRemoveKey] != null ? options[singleZeroRemoveKey] : false;
if (singleZeroRemove && !priv[`zero_power_${channel}`]) {
logger_1.logger.info("[PJ1203A] power is zero, flushing one time", NS);
priv.flushNull(result, channel, options);
}
else {
priv.flushZero(result, channel, options);
}
priv[`zero_power_${channel}`] = true;
}
else {
priv[`zero_power_${channel}`] = false;
}
return result;
},
};
},
currentPJ1203A: (channel) => {
return {
from: (v, meta, options) => {
const priv = storeLocal.getPrivatePJ1203A(meta.device);
const result = {};
priv[`current_${channel}`] = v / 1000;
if (v === 0) {
const singleZeroRemoveKey = "single_zero_remove";
const singleZeroRemove = options[singleZeroRemoveKey] != null ? options[singleZeroRemoveKey] : false;
if (singleZeroRemove && !priv[`zero_current_${channel}`]) {
logger_1.logger.info("[PJ1203A] current is zero, flushing one time", NS);
priv.flushNull(result, channel, options);
}
else {
priv.flushZero(result, channel, options);
}
priv[`zero_current_${channel}`] = true;
}
else {
priv[`zero_current_${channel}`] = false;
}
return result;
},
};
},
powerFactorPJ1203A: (channel) => {
return {
from: (v, meta, options) => {
const priv = storeLocal.getPrivatePJ1203A(meta.device);
const result = {};
priv[`power_factor_${channel}`] = v;
const lateEnergyFlowKey = `late_energy_flow_${channel}`;
const lateEnergyFlow = options[lateEnergyFlowKey] != null ? options[lateEnergyFlowKey] : false;
if (!lateEnergyFlow) {
priv.flush(result, channel, options);
}
return result;
},
};
},
powerAbPJ1203A: () => {
return {
// power_ab datapoint is broken and will be recomputed so ignore it.
from: (v, meta, options) => {
return {};
},
};
},
energyForwardPJ1203A: (channel) => {
return {
from: (v, meta, options) => {
const invertedEnergyFlowKey = `invert_energy_flow_${channel}`;
const result_key = options[invertedEnergyFlowKey] ? `energy_produced_${channel}` : `energy_${channel}`;
const result = {};
result[result_key] = v / 100;
return result;
},
};
},
energyReversePJ1203A: (channel) => {
return {
from: (v, meta, options) => {
const invertedEnergyFlowKey = `invert_energy_flow_${channel}`;
const result_key = options[invertedEnergyFlowKey] ? `energy_${channel}` : `energy_produced_${channel}`;
const result = {};
result[result_key] = v / 100;
return result;
},
};
},
sceneCubeAction: () => {
const lookup = ["side_1", "side_2", "side_3", "side_4", "knock", "shake"];
const expose = e.action(lookup);
return [
tuya.modernExtend.dpEnumLookup({
dp: 0x01,
name: "action",
type: tuya.dataTypes.bool,
readOnly: true,
expose: expose,
lookup: { side_1: 0 },
}),
tuya.modernExtend.dpEnumLookup({
dp: 0x02,
name: "action",
type: tuya.dataTypes.bool,
readOnly: true,
expose: expose,
lookup: { side_2: 0 },
}),
tuya.modernExtend.dpEnumLookup({
dp: 0x03,
name: "action",
type: tuya.dataTypes.bool,
readOnly: true,
expose: expose,
lookup: { side_3: 0 },
}),
tuya.modernExtend.dpEnumLookup({
dp: 0x04,
name: "action",
type: tuya.dataTypes.bool,
readOnly: true,
expose: expose,
lookup: { side_4: 0 },
}),
tuya.modernExtend.dpEnumLookup({
dp: 0x05,
name: "action",
type: tuya.dataTypes.bool,
readOnly: true,
expose: expose,
lookup: { knock: 0 },
}),
tuya.modernExtend.dpEnumLookup({
dp: 0x06,
name: "action",
type: tuya.dataTypes.bool,
readOnly: true,
expose: expose,
lookup: { shake: 0 },
}),
];
},
name: {
to: (v, meta) => {
const utf8bytes = new node_util_1.TextEncoder().encode(v);
return Array.from(utf8bytes, (utf8bytes) => utf8bytes.toString(16).padStart(4, "0")).join("");
},
from: (v, meta) => {
const bytes = [];
for (let i = 0; i < v.length; i += 4) {
bytes.push(Number.parseInt(v.slice(i, i + 4), 16));
}
const hexToBytes = Uint8Array.from(bytes);
return new node_util_1.TextDecoder("utf-8").decode(hexToBytes);
},
},
nameTrunc: {
to: (v, meta, len = 8) => {
const truncated = v.slice(0, len);
const utf8bytes = new node_util_1.TextEncoder().encode(truncated);
return Array.from(utf8bytes, (utf8bytes) => utf8bytes.toString(16).padStart(4, "0")).join("");
},
from: (v, meta) => {
const bytes = [];
for (let i = 0; i < v.length; i += 4) {
bytes.push(Number.parseInt(v.slice(i, i + 4), 16));
}
const hexToBytes = Uint8Array.from(bytes);
return new node_util_1.TextDecoder("utf-8").decode(hexToBytes);
},
},
};
// TS0601_smart_scene_knob constants and helpers //
// 4 physical buttons with knob rotation, 3 modes: Scene (DP 1-4), Light (ZCL), Curtain (DP broadcast)
// Mode switch: hold button 2 or 4 for 5 seconds (only cycles through bound modes)
// Group ID pattern: base + (button-1) * 20, detected from first button press
const modeScene = 0x01;
const modeLight = 0x03;
const modeCurtain = 0x04;
const groupIdOffset = 20;
const statusUnassigned = "unassigned";
const statusWaiting = "waiting_button_1";
const statusReady = "ready";
const getButtonFromGroupId = (groupId, baseGroupId) => {
if (!baseGroupId || !groupId)
return null;
const offset = groupId - baseGroupId;
if (offset >= 0 && offset % groupIdOffset === 0) {
const button = Math.floor(offset / groupIdOffset) + 1;
if (button >= 1 && button <= 4)
return button;
}
return null;
};
// DP 102 payload: [0x01, 0x01, mode, ...name(12 bytes), ...suffix(4 bytes)]
// Suffix: slot number at position (slot-1), rest 0xff
const bindSlotTS0601SmartSceneKnob = async (entity, slot, mode) => {
const modeNames = { [modeScene]: "Scene", [modeLight]: "Light", [modeCurtain]: "Curtain" };
const slotName = `${modeNames[mode] || "Scene"} ${slot}`;
const nameBuffer = Buffer.alloc(12, 0);
nameBuffer.write(slotName, "utf8");
const suffix = Buffer.from([0xff, 0xff, 0xff, 0xff]);
suffix[slot - 1] = slot;
const payload = Buffer.concat([Buffer.from([0x01, 0x01, mode]), nameBuffer, suffix]);
await tuya.sendDataPointRaw(entity, 102, payload, "dataRequest", 0x10 + (slot - 1));
};
// The BAC-001 reports its power state (dp 1) and its operating mode (dp 2) on separate datapoints,
// combine both into a single `system_mode` in which the power state maps to `off`.
const bac001SystemMode = (meta) => meta.state.power_state_device === false ? "off" : (meta.state.system_mode_device ?? "cool");
const trv603ScheduleConverter = (dayNumber) => {
return {
from: (value) => {
const buf = Buffer.isBuffer(value)
? value
: Array.isArray(value)
? Buffer.from(value)
: value && typeof value === "object" && "data" in value
? Buffer.from(value.data)
: null;
if (!buf || buf.length < 17)
return;
const schedule = [];
for (let i = 1; i <= 13; i += 4) {
const hh = buf[i];
const mm = buf[i + 1];
const tempRaw = (buf[i + 2] << 8) | buf[i + 3];
const temp = (tempRaw / 10).toFixed(1);
if (hh > 23 || mm > 59)
return;
schedule.push(`${hh.toString().padStart(2, "0")}:${mm.toString().padStart(2, "0")}/${temp}`);
}
return schedule.join(" ");
},
to: (value) => {
const parts = value.split(" ");
const buf = Buffer.alloc(17);
buf[0] = dayNumber;
parts.forEach((part, index) => {
if (index < 4) {
const [time, temp] = part.split("/");
const [hh, mm] = time.split(":");
const offset = 1 + index * 4;
const tempVal = Math.round(Number.parseFloat(temp) * 10);
buf[offset] = Number.parseInt(hh, 10);
buf[offset + 1] = Number.parseInt(mm, 10);
buf[offset + 2] = (tempVal >> 8) & 0xff;
buf[offset + 3] = tempVal & 0xff;
}
});
return Array.from(buf);
},
};
};
const zht002ProgrammingModeConverter = {
from: (v) => v,
to: (v, meta) => {
let existing = {};
if (meta?.state?.programming_mode && typeof meta.state.programming_mode === "object") {
existing = { ...meta.state.programming_mode };
}
const merged = { ...existing, ...v };
const buffer = [];
for (let i = 0; i < 48; i++) {
buffer[i] = merged[i.toString()] || 0;
}
return buffer;
},
};
// AR331 Pro (DP 106): holiday start/end as 9-byte LE: [prefix, ts_start_LE4, ts_end_LE4]
const ar331ProHolidayTimeConverter = {
from: (v) => {
if (!v || v.length < 9)
return "";
const readLE32 = (arr, offset) => (arr[offset] | (arr[offset + 1] << 8) | (arr[offset + 2] << 16) | (arr[offset + 3] * 16777216)) >>> 0;
const startTS = readLE32(v, 1);
const endTS = readLE32(v, 5);
const fmt = (ts) => {
const d = new Date(ts * 1000);
const pad = (n) => String(n).padStart(2, "0");
return `${d.getUTCFullYear()}/${pad(d.getUTCMonth() + 1)}/${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
};
return `${fmt(startTS)} | ${fmt(endTS)}`;
},
to: (v) => {
const [startStr, endStr] = v.split("|").map((s) => s.trim());
const parseTS = (s) => {
const [datePart, timePart] = s.split(" ");
const [y, m, d] = datePart.split("/").map(Number);
const [h, min] = timePart.split(":").map(Number);
const ts = Math.floor(Date.UTC(y, m - 1, d, h, min) / 1000);
return [ts & 0xff, (ts >> 8) & 0xff, (ts >> 16) & 0xff, (ts >> 24) & 0xff];
};
return [0, ...parseTS(startStr), ...parseTS(endStr)];
},
};
const tzLocal = {
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS0301_dual_rail_2: {
key: ["state", "position"],
options: [
e
.binary("invert_top_rail", ea.SET, true, false)
.withDescription("Invert the top rail so that 'open' (position 100) raises the rail instead of lowering it; default false keeps the upstream convention (position 100 = down)."),
],
convertSet: async (entity, key, value, meta) => {
const ep = meta.endpoint_name;
const invertTop = meta.options?.invert_top_rail === true;
const goTo = async (lift) => await entity.command("closuresWindowCovering", "goToLiftPercentage", { percentageliftvalue: Math.max(0, Math.min(100, Math.round(lift))) }, utils.getOptions(meta.mapped, entity));
if (key === "position") {
utils.assertNumber(value, key);
let lift;
if (ep === "bottom")
lift = value === 0 ? 0 : utils.mapNumberRange(value, 0, 100, 50, 100);
else if (ep === "top")
lift = utils.mapNumberRange(invertTop ? 100 - value : value, 0, 100, 0, 50);
else
lift = value;
await goTo(lift);
return;
}
const cmd = String(value).toUpperCase();
if (cmd === "STOP") {
await entity.command("closuresWindowCovering", "stop", {}, utils.getOptions(meta.mapped, entity));
return;
}
const open = cmd === "OPEN";
if (ep === "bottom")
await goTo(open ? 100 : 0);
else if (ep === "top")
await goTo(utils.mapNumberRange(invertTop ? (open ? 0 : 100) : open ? 100 : 0, 0, 100, 0, 50));
else
await entity.command("closuresWindowCovering", open ? "upOpen" : "downClose", {}, utils.getOptions(meta.mapped, entity));
},
convertGet: async (entity, key, meta) => {
await entity.read("closuresWindowCovering", ["currentPositionLiftPercentage"]);
},
},
acmelec_ae720k_state_double_on: {
key: ["state"],
convertSet: async (entity, key, value, meta) => {
const on = String(value).toLowerCase() === "on";
if (!on) {
await tuya.sendDataPointBool(entity, 1, false);
return { state: { state: "OFF" } };
}
// AE-720K requires ON twice (similar to pressing ON twice on device)
await tuya.sendDataPointBool(entity, 1, true);
await utils.sleep(220);
await tuya.sendDataPointBool(entity, 1, true);
return { state: { state: "ON" } };
},
},
ts0049_countdown: {
key: ["water_countdown"],
convertSet: async (entity, key, value, meta) => {
utils.assertNumber(value);
const data = Buffer.alloc(5);
const scaledValue = value * 60; //
data.writeUInt32BE(scaledValue, 1); //
data[0] = 0x0b; //
await entity.command("manuSpecificTuyaE001", "setCountdown", { data });
},
},
ts110eCountdown: {
key: ["countdown"],
convertSet: async (entity, key, value, meta) => {
utils.assertNumber(value);
const data = Buffer.alloc(4);
data.writeUInt32LE(value);
await entity.command("genOnOff", "tuyaCountdown", { data });
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
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, {
57345: { value: utils.getFromLookup(value, lookup), type: 0x30 },
});
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS0726_switch_mode: {
key: ["switch_mode"],
convertSet: async (entity, key, value, meta) => {
await entity.write(0xe001, {
53280: {
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] ? 1 : 0;
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) {
await entity.command("genLevelCtrl", "moveToLevel", { level: Number(meta.message.brightness), transtime, optionsMask: 0, optionsOverride: 0 }, utils.getOptions(meta.mapped, entity));
newState.brightness = meta.message.brightness;
}
if ("color_temp" in meta.message) {
await entity.command("lightingColorCtrl", "moveToColorTemp", {
colortemp: meta.message.color_temp,
transtime: transtime,
optionsMask: 0,
optionsOverride: 0,
}, 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 ignore
hue: meta.state.color?.hue ?? 0, // red
// @ts-expect-error ignore
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("genLevelCtrl", ["currentLevel"]);
await entity.read("lightingColorCtrl", [
"currentHue",
"currentSaturation",
"tuyaRgbMode",
"colorTemperature",
]);
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
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: 0 });
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),
};
}
return await tz.light_color.convertSet(entity, key, value, meta);
},
convertGet: tz.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 === "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", { 2: { 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("manuSpecificTuya2", {
"57355": { value: { celsius: 0, fahrenheit: 1 }[value], type: 48 },
});
break;
}
default: // Unknown key
logger_1.logger.warning(`Unhandled key ${key}`, NS);
}
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
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("manuSpecificTuya3", "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("manuSpecificTuya3", "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("manuSpecificTuya3", "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("manuSpecificTuya3", "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("manuSpecificTuya3", "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("manuSpecificTuya3", "setOptions3", {
data: buf,
});
break;
}
case "over_voltage_threshold": {
const state = meta.state.over_voltage_breaker;
const buf = Buffer.alloc(4);
buf.writeUInt8(3, 0);
buf.writeUInt8(utils.getFromLookup(state, onOffLookup), 1);
buf.writeUInt16BE(utils.toNumber(value, "over_voltage_threshold"), 2);
await entity.command("manuSpecificTuya3", "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("manuSpecificTuya3", "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("manuSpecificTuya3", "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("manuSpecificTuya3", "setOptions3", {
data: buf,
});
break;
}
default: // Unknown key
logger_1.logger.warning(`Unhandled key ${key}`, NS);
}
},
},
invert_cover_percent_fix: {
key: ["state", "position"],
convertSet: async (entity, key, value, meta) => {
const shouldInvert = key === "position" && meta.options.cover_position_percent_fix;
const newValue = shouldInvert ? 100 - Number(value) : value;
return await legacy.toZigbee.tuya_cover_control.convertSet(entity, key, newValue, meta);
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS0505B_1_transitionFixesOnOffBrightness: {
...tz.light_onoff_brightness,
convertSet: async (entity, key, value, meta) => {
// This light has two issues:
// 1. If passing transition = 0, it will behave as if transition = 1s.
// 2. If turning off with a transition, and turning on during the transition, it will turn off
// at the end of the first transition timer, despite order to turn on
// Workaround for issue 1: patch transition in input message
const transition = utils.getTransition(entity, "brightness", meta);
let transitionSeconds = transition.time / 10;
let newMeta = meta;
if (transitionSeconds === 0) {
const { message } = meta;
const wantedState = message.state != null ? (typeof message.state === "string" ? message.state.toLowerCase() : null) : undefined;
newMeta = { ...meta }; // Clone meta to avoid modifying the original
if (wantedState === "off") {
// Erase transition because that way we get actual instant turn off
newMeta.message = { state: "OFF" };
}
else {
// Best we can do is set the transition to 0.1 seconds
// That is the same thing as is done for TS0505B_2
transitionSeconds = 0.1;
newMeta.message = { ...message, transition: transitionSeconds }; // Will get re-parsed by original light_onoff_brightness
}
}
const ret = await tz.light_onoff_brightness.convertSet(entity, key, value, newMeta);
// Workaround for issue 2:
// Get the current state of the light after transition time + 0.1s
// This won't fix the light's state, but at least it will make us aware that it's off,
// allowing user apps to turn it on again if needed.
// This could probably be improved by actually turning it on again if necessary.
if (transitionSeconds !== 0) {
setTimeout(() => {
tz.on_off.convertGet(entity, "state", meta).catch((error) => {
logger_1.logger.warning(`Error getting state of TS0505B_1 after transition: ${error.message}`, NS);
});
}, transitionSeconds * 1000 + 100).unref();
}
return ret;
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS0601_knob_dimmer_switch_group_id: {
key: ["group_id"],
convertSet: async (entity, key, value, meta) => {
// The device uses custom group command known from miboxer switches to bind to a group.
await entity.command("genGroups", "miboxerSetZones", {
zones: [{ zoneNum: 1, groupId: Number(value) }],
});
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS0601_smart_light_group_id: {
key: ["group_id"],
convertSet: async (entity, key, value, meta) => {
// The device uses custom group command known from miboxer switches to bind to a group.
const zone_map = Object.fromEntries(Object.keys(meta.state)
.filter((key) => key.startsWith("group_id"))
.map((k) => [k.replace("group_id_l", ""), meta.state[k]]));
zone_map[meta.endpoint_name.replace("l", "")] = value;
await entity.command("genGroups", "miboxerSetZones", {
zones: Object.entries(zone_map).map(([k, v]) => {
return { zoneNum: Number(k), groupId: Number(v) };
}),
});
return { state: { group_id: value } };
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS0004_backlight_mode: {
key: ["backlight_mode"],
convertSet: async (entity, key, value, meta) => {
const lookup = { red_when_on: 0, pink_when_on: 1, red_on_blue_off: 2, pink_on_blue_off: 3 };
const modeValue = utils.getFromLookup(value, lookup);
await entity.write("genOnOff", { tuyaBacklightMode: modeValue });
return { state: { backlight_mode: value } };
},
},
// TS0601_smart_scene_knob //
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS0601_smart_scene_knob_bind_all: {
key: ["bind_all_scene", "bind_all_light", "bind_all_curtain"],
convertSet: async (entity, key, value, meta) => {
const modes = {
bind_all_scene: modeScene,
bind_all_light: modeLight,
bind_all_curtain: modeCurtain,
};
const mode = modes[key];
for (let slot = 1; slot <= 4; slot++) {
await bindSlotTS0601SmartSceneKnob(entity, slot, mode);
if (slot < 4) {
await utils.sleep(500);
}
}
// Scene mode doesn't use Group ID
if (key === "bind_all_scene") {
return {};
}
const currentStatus = meta.state?.assignment_status;
const baseGroupId = meta.state?.base_group_id;
if (currentStatus !== statusReady || !baseGroupId) {
return { state: { assignment_status: statusWaiting } };
}
return {};
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS0601_smart_scene_knob_assign_button_1: {
key: ["assign_button_1"],
convertSet: (entity, key, value, meta) => {
return { state: { assignment_status: statusWaiting } };
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS0601_smart_scene_knob_set_base_group_id: {
key: ["set_base_group_id"],
convertSet: (entity, key, value, meta) => {
const baseGroupId = Number.parseInt(value, 10);
if (Number.isNaN(baseGroupId) || baseGroupId < 1 || baseGroupId > 65000)
return {};
return {
state: {
base_group_id: baseGroupId,
assignment_status: statusReady,
},
};
},
},
ts0201_temperature_humidity_alarm: {
key: ["alarm_humidity_max", "alarm_humidity_min", "alarm_temperature_max", "alarm_temperature_min"],
convertSet: async (entity, key, value, meta) => {
switch (key) {
case "alarm_temperature_max":
case "alarm_temperature_min":
case "alarm_humidity_max":
case "alarm_humidity_min": {
// await entity.write('manuSpecificTuya2', {[key]: value});
// instead write as custom attribute to override incorrect herdsman dataType from uint16 to int16
// https://github.com/Koenkk/zigbee-herdsman/blob/v0.13.191/src/zcl/definition/cluster.ts#L4235
const keyToAttributeLookup = {
alarm_temperature_max: 0xd00a,
alarm_temperature_min: 0xd00b,
alarm_humidity_max: 0xd00d,
alarm_humidity_min: 0xd00e,
};
const payload = { [keyToAttributeLookup[key]]: { value: value, type: zigbee_herdsman_1.Zcl.DataType.INT16 } };
await entity.write("manuSpecificTuya2", payload);
break;
}
default: // Unknown key
logger_1.logger.warning(`Unhandled key ${key}`, NS);
}
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
ZM35HQ_attr: {
key: ["sensitivity", "keep_time"],
convertSet: async (entity, key, value, meta) => {
switch (key) {
case "sensitivity":
await entity.write("ssIasZone", { currentZoneSensitivityLevel: utils.getFromLookup(value, { low: 0, medium: 1, high: 2 }) });
break;
case "keep_time":
await entity.write("ssIasZone", { 61441: { value: utils.getFromLookup(value, { 30: 0, 60: 1, 120: 2 }), type: 0x20 } });
break;
default: // Unknown key
throw new Error(`Unhandled key ${key}`);
}
},
convertGet: async (entity, key, meta) => {
// Apparently, reading values may interfere with a commandStatusChangeNotification for changed occupancy.
// Therefore, read "zoneStatus" as well.
await entity.read("ssIasZone", ["currentZoneSensitivityLevel", 61441, "zoneStatus"]);
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS0210_sensitivity: {
key: ["sensitivity"],
convertSet: async (entity, key, value, meta) => {
value = utils.toNumber(value, "sensitivity");
await entity.write("ssIasZone", { currentZoneSensitivityLevel: value });
return { state: { sensitivity: value } };
},
},
};
const fzLocal = {
// FUT035Z+ (_TZB210_ue01a0s2) reports RF color temperature changes
// as raw lightingColorCtrl frames without updating colorTemperature.
FUT035ZrfColorTemp: {
cluster: "lightingColorCtrl",
type: "raw",
convert: (model, msg, publish, options, meta) => {
if (meta.device.manufacturerName !== "_TZB210_ue01a0s2")
return;
if (!Buffer.isBuffer(msg.data) || msg.data.length < 2)
return;
const raw = msg.data.readUInt16LE(msg.data.length - 2);
if (raw < 0 || raw > 1000)
return;
return {
color_temp: Math.round(500 - (raw * 347) / 1000),
};
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS0301_dual_rail_2: {
cluster: "closuresWindowCovering",
type: ["attributeReport", "readResponse"],
options: [
e
.binary("invert_top_rail", ea.SET, true, false)
.withDescription("Invert the top rail so that 'open' (position 100) raises the rail instead of lowering it; default false keeps the upstream convention (position 100 = down)."),
],
convert: (model, msg, publish, options, meta) => {
const N = msg.data.currentPositionLiftPercentage;
if (N === undefined || N > 100)
return;
const bottom = N < 50 ? 0 : Math.round(utils.mapNumberRange(N, 50, 100, 0, 100));
let top = N > 50 ? 0 : Math.round(utils.mapNumberRange(N, 0, 50, 0, 100));
if (options?.invert_top_rail === true)
top = 100 - top;
return {
position: N,
state: N === 0 ? "CLOSE" : "OPEN",
position_bottom: bottom,
state_bottom: bottom === 0 ? "CLOSE" : "OPEN",
position_top: top,
state_top: top === 0 ? "CLOSE" : "OPEN",
};
},
},
// ZT08 (_TZE284_hodyryli) — LCD weather station with clock.
// The MCU expects mcuSyncTime in raw Unix seconds (8 bytes: UTC + local),
// followed by a DP 17 write ~500 ms later that commits the new time to the LCD.
// Without the DP 17 commit the MCU silently ignores the sync and the clock stays at 00:xx.
// See: https://github.com/Koenkk/zigbee2mqtt/issues/29627
zt08ClockSync: {
cluster: "manuSpecificTuya",
type: ["commandMcuSyncTime"],
convert: (model, msg, publish, options, meta) => {
void (async () => {
try {
const ep = msg.endpoint;
const now = new Date();
const utcTime = Math.round(now.getTime() / 1000);
const localTime = utcTime - now.getTimezoneOffset() * 60;
const be4 = (n) => [(n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff];
const payload = [...be4(utcTime), ...be4(localTime)];
await ep.command("manuSpecificTuya", "mcuSyncTime", { payloadSize: 8, payload }, { disableDefaultResponse: true });
await utils.sleep(500);
try {
await tuya.sendDataPointBool(ep, 17, false);
}
catch {
/* DP 17 may not exist on no-clock siblings; safe to ignore */
}
}
catch {
/* time sync best-effort */
}
})();
},
},
TLSR82xxAction: {
cluster: "genOnOff",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
if (Object.hasOwn(msg.data, "onOff")) {
const btn = msg.endpoint.ID;
const state = msg.data["onOff"] === 1 ? "on" : "off";
return { action: `${state}_${btn}` };
}
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS0726_action: {
cluster: "genOnOff",
type: ["commandTuyaAction"],
convert: (model, msg, publish, options, meta) => {
return { action: `scene_${msg.endpoint.ID}` };
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS0222_humidity: {
...fz.humidity,
convert: (model, msg, publish, options, meta) => {
const result = fz.humidity.convert(model, msg, publish, options, meta);
if (result)
result.humidity *= 10;
return result;
},
},
scene_recall: {
cluster: "genScenes",
type: "commandRecall",
convert: (model, msg, publish, options, meta) => {
if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
return;
const payload = {
action: (0, utils_1.postfixWithEndpointName)(`scene_${msg.data.sceneid}`, msg, model, meta),
};
(0, utils_1.addActionGroup)(payload, msg, model);
return payload;
},
},
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}` };
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS0201_battery: {
cluster: "genPowerCfg",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
// https://github.com/Koenkk/zigbee2mqtt/issues/11470
// https://github.com/Koenkk/zigbee-herdsman-converters/pull/8246
if (msg.data.batteryPercentageRemaining === 200 &&
msg.data.batteryVoltage < 30 &&
!["_TZ3000_lqmvrwa2"].includes(meta.device.manufacturerName))
return;
return fz.battery.convert(model, msg, publish, options, meta);
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS0201_humidity: {
...fz.humidity,
convert: (model, msg, publish, options, meta) => {
if (["_TZ3210_ncw88jfq", "_TZ3000_ywagc4rj", "_TZ3000_isw9u95y", "_TZ3000_yupc0pb7", "_TZ3000_wol32olt"].includes(meta.device.manufacturerName)) {
msg.data.measuredValue *= 10;
}
return fz.humidity.convert(model, msg, publish, options, meta);
},
},
ts0201_temperature_humidity_alarm: {
cluster: "manuSpecificTuya2",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
if (msg.data.alarmTemperatureMax !== undefined) {
result.alarm_temperature_max = msg.data.alarmTemperatureMax;
}
if (msg.data.alarmTemperatureMin !== undefined) {
result.alarm_temperature_min = msg.data.alarmTemperatureMin;
}
if (msg.data.alarmHumidityMax !== undefined) {
result.alarm_humidity_max = msg.data.alarmHumidityMax;
}
if (msg.data.alarmHumidityMin !== undefined) {
result.alarm_humidity_min = msg.data.alarmHumidityMin;
}
if (msg.data.alarmHumidity !== undefined) {
const sensorAlarmLookup = { "0": "below_min_humdity", "1": "over_humidity", "2": "off" };
result.alarm_humidity = sensorAlarmLookup[msg.data.alarmHumidity];
}
if (msg.data.alarmTemperature !== undefined) {
const sensorAlarmLookup = { "0": "below_min_temperature", "1": "over_temperature", "2": "off" };
result.alarm_temperature = sensorAlarmLookup[msg.data.alarmTemperature];
}
return result;
},
},
humidity10: {
cluster: "msRelativeHumidity",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const humidity = msg.data.measuredValue / 10.0;
if (humidity >= 0 && humidity <= 100) {
return { humidity };
}
},
},
temperature_unit: {
cluster: "manuSpecificTuya2",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
if (msg.data["57355"] !== undefined) {
result.temperature_unit = utils.getFromLookup(msg.data["57355"], {
"0": "celsius",
"1": "fahrenheit",
});
}
return result;
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS011F_threshold: {
cluster: "manuSpecificTuya3",
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);
const result = {};
if (0x05 in value) {
result.temperature_threshold = value[0x05][1];
result.temperature_breaker = lookup[value[0x05][0]];
}
if (0x07 in value) {
result.power_threshold = value[0x07][1];
result.power_breaker = lookup[value[0x07][0]];
}
return result;
}
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]],
};
}
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
PJ1203A_sync_time_increase_seq: {
cluster: "manuSpecificTuya",
type: ["commandMcuSyncTime"],
convert: (model, msg, publish, options, meta) => {
const priv = storeLocal.getPrivatePJ1203A(meta.device);
priv.last_seq += priv.seq_inc;
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
PJ1203A_strict_fz_datapoints: {
...tuya.fz.datapoints,
convert: (model, msg, publish, options, meta) => {
// Uncomment the next line to test the behavior when random messages are lost
// if ( Math.random() < 0.05 ) return;
const priv = storeLocal.getPrivatePJ1203A(meta.device);
// Detect missing or re-ordered messages but allow duplicate messages (should we?).
const expectedSeq = (priv.last_seq + priv.seq_inc) & 0xffff;
if (msg.data.seq !== expectedSeq && msg.data.seq !== priv.last_seq) {
logger_1.logger.debug(`[PJ1203A] Missing or re-ordered message detected: Got seq=${msg.data.seq}, expected ${priv.next_seq}`, NS);
priv.clear();
}
priv.last_seq = msg.data.seq;
// And finally, process the datapoint using tuya.fz.datapoints
return tuya.fz.datapoints.convert(model, msg, publish, options, meta);
},
},
ts020cIlluminance: {
cluster: "manuSpecificTuya2",
type: ["attributeReport"],
convert: (model, msg, publish, options, meta) => {
const result = {};
if ("57345" in msg.data) {
result.illuminance = msg.data["57345"];
}
return result;
},
},
ts0049_countdown: {
cluster: "manuSpecificTuyaE001",
type: "raw",
convert: (model, msg, publish, options, meta) => {
const len = msg.data.length;
const command = msg.data[2];
if (len > 10 && command === 0x0a && msg.data[7] === 0x0b && (msg.data[6] === 0x05 || msg.data[6] === 0x06)) {
const data = msg.data.slice(8);
const value = data.readUInt32BE(0);
return {
countdown: value / 60,
};
}
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS0004_backlight_mode: {
cluster: "genOnOff",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
if (msg.data.tuyaBacklightMode !== undefined) {
const backlightLookup = { 0: "red_when_on", 1: "pink_when_on", 2: "red_on_blue_off", 3: "pink_on_blue_off" };
return { backlight_mode: backlightLookup[msg.data.tuyaBacklightMode] };
}
},
},
// TS0601_smart_scene_knob //
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS0601_smart_scene_knob_light_onoff: {
cluster: "genOnOff",
type: ["commandOn", "commandOff"],
convert: (model, msg, publish, options, meta) => {
const groupId = msg.groupID;
if (!groupId)
return;
const baseGroupId = meta.state?.base_group_id;
const status = meta.state?.assignment_status || statusUnassigned;
const command = msg.type === "commandOn" ? "on" : "off";
if (status === statusWaiting) {
return {
base_group_id: groupId,
assignment_status: statusReady,
action: `light_1_${command}`,
action_group: groupId,
action_button: 1,
};
}
if (status === statusReady && baseGroupId) {
const button = getButtonFromGroupId(groupId, baseGroupId);
if (!button)
return;
return {
action: `light_${button}_${command}`,
action_group: groupId,
action_button: button,
};
}
return { action_group: groupId, assignment_status: statusUnassigned };
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS0601_smart_scene_knob_light_brightness: {
cluster: "genLevelCtrl",
type: ["commandMoveToLevelWithOnOff", "commandMoveToLevel"],
convert: (model, msg, publish, options, meta) => {
const groupId = msg.groupID;
if (!groupId)
return;
const baseGroupId = meta.state?.base_group_id;
const status = meta.state?.assignment_status || statusUnassigned;
const level = msg.data.level;
const prevBrightness = meta.state?.brightness;
const prevDirection = meta.state?.brightness_direction;
let direction = "";
if (prevBrightness !== undefined) {
if (level > prevBrightness)
direction = "_up";
else if (level < prevBrightness)
direction = "_down";
else
direction = prevDirection || "";
}
if (status === statusWaiting) {
return {
base_group_id: groupId,
assignment_status: statusReady,
action: `light_1_brightness${direction}`,
action_group: groupId,
action_button: 1,
brightness: level,
brightness_direction: direction,
};
}
if (status === statusReady && baseGroupId) {
const button = getButtonFromGroupId(groupId, baseGroupId);
if (!button)
return;
return {
action: `light_${button}_brightness${direction}`,
action_group: groupId,
action_button: button,
brightness: level,
brightness_direction: direction,
};
}
return { action_group: groupId, brightness: level };
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS0601_smart_scene_knob_light_colortemp: {
// Direction: higher mired = warmer = down, lower mired = cooler = up
cluster: "lightingColorCtrl",
type: ["commandMoveToColorTemp"],
convert: (model, msg, publish, options, meta) => {
const groupId = msg.groupID;
if (!groupId)
return;
const baseGroupId = meta.state?.base_group_id;
const status = meta.state?.assignment_status || statusUnassigned;
const colortemp = msg.data.colortemp;
const prevColortemp = meta.state?.color_temp;
const prevDirection = meta.state?.color_temp_direction;
let direction = "";
if (prevColortemp !== undefined) {
if (colortemp > prevColortemp)
direction = "_down";
else if (colortemp < prevColortemp)
direction = "_up";
else
direction = prevDirection || "";
}
if (status === statusWaiting) {
return {
base_group_id: groupId,
assignment_status: statusReady,
action: `light_1_colortemp${direction}`,
action_group: groupId,
action_button: 1,
color_temp: colortemp,
color_temp_direction: direction,
};
}
if (status === statusReady && baseGroupId) {
const button = getButtonFromGroupId(groupId, baseGroupId);
if (!button)
return;
return {
action: `light_${button}_colortemp${direction}`,
action_group: groupId,
action_button: button,
color_temp: colortemp,
color_temp_direction: direction,
};
}
return { action_group: groupId, color_temp: colortemp };
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS0601_smart_scene_knob_curtain_command: {
// DP 1: 0=start, 2=stop (1=init rotation, ignored)
// DP 2: 4-byte big-endian position 0-100%
cluster: "manuSpecificTuya",
type: ["commandDataRequest"],
convert: (model, msg, publish, options, meta) => {
const groupId = msg.groupID;
if (!groupId)
return;
const baseGroupId = meta.state?.base_group_id;
const status = meta.state?.assignment_status || statusUnassigned;
const dpValues = msg.data.dpValues;
if (!dpValues || dpValues.length === 0)
return;
const dp = dpValues[0].dp;
const data = dpValues[0].data;
let action = null;
let position = null;
if (dp === 1) {
const commands = { 0: "start", 2: "stop" };
action = commands[data[0]] || null;
}
else if (dp === 2) {
position = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
const prevPosition = meta.state?.curtain_position;
const prevDirection = meta.state?.curtain_position_direction;
if (prevPosition !== undefined) {
if (position > prevPosition)
action = "position_open";
else if (position < prevPosition)
action = "position_close";
else
action = prevDirection ? `position${prevDirection}` : "position";
}
else {
action = "position";
}
}
if (!action)
return;
const result = { action_group: groupId };
if (position !== null) {
result.curtain_position = position;
if (action.includes("_open"))
result.curtain_position_direction = "_open";
else if (action.includes("_close"))
result.curtain_position_direction = "_close";
}
if (status === statusWaiting) {
return {
...result,
base_group_id: groupId,
assignment_status: statusReady,
action: `curtain_1_${action}`,
action_button: 1,
};
}
if (status === statusReady && baseGroupId) {
const button = getButtonFromGroupId(groupId, baseGroupId);
if (!button)
return;
return {
...result,
action: `curtain_${button}_${action}`,
action_button: button,
};
}
return result;
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS0601_smart_light_brightness: {
cluster: "genLevelCtrl",
type: ["commandMoveToLevel", "commandMoveToLevelWithOnOff"],
convert: (model, msg, publish, options, meta) => {
const zone_map = Object.fromEntries(Object.keys(meta.state)
.filter((key) => key.startsWith("group_id"))
.map((k) => [meta.state[k], k.replace("group_id_l", "")]));
var endppointN = zone_map[msg.groupID];
if (!endppointN) {
return;
}
return {
[`brightness_l${endppointN}`]: msg.data?.level,
};
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS0601_smart_light_color_temp: {
cluster: "lightingColorCtrl",
type: ["commandMoveToColorTemp"],
convert: (model, msg, publish, options, meta) => {
const zone_map = Object.fromEntries(Object.keys(meta.state)
.filter((key) => key.startsWith("group_id"))
.map((k) => [meta.state[k], k.replace("group_id_l", "")]));
var endppointN = zone_map[msg.groupID];
if (!endppointN) {
return;
}
return {
[`color_temp_l${endppointN}`]: msg.data?.colortemp,
};
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
TS0601_smart_thermostat_command: {
cluster: "manuSpecificTuya",
type: ["commandDataRequest"],
convert: (model, msg, publish, options, meta) => {
const dpValues = msg.data.dpValues;
if (!dpValues || dpValues.length === 0)
return;
const dp = dpValues[0].dp;
const data = dpValues[0].data;
if (dp === 1) {
logger_1.logger.info(JSON.stringify(data[0]), NS);
return {
thermostat: data[0] ? "ON" : "OFF",
};
}
if (dp === 2) {
return {
current_heating_setpoint: data[3],
};
}
if (dp === 5) {
return {
fan_mode: data[0],
};
}
},
},
// AR331 Pro (DP 114): corrects out-of-range override_temperature written by the device
// (signed int16 underflow when knob is turned below 5°C). Uses a 2-sec debounce because
// the device echoes DP114 twice per change. Writes back the clamped minimum.
ar331ProDp114Corrector: {
cluster: "manuSpecificTuya",
type: ["commandDataReport", "commandDataResponse"],
convert: (model, msg, _publish, _options, _meta) => {
const dpValues = msg.data.dpValues;
if (!dpValues)
return;
for (const dpValue of dpValues) {
if (dpValue.dp !== 114)
continue;
const buf = dpValue.data;
let raw;
if (dpValue.datatype === 2) {
// value type: 4-byte unsigned BE integer
raw = buf.readUInt32BE(0);
if (raw > 32767)
raw -= 65536;
}
else if (dpValue.datatype === 0) {
// raw type: 2-byte LE (device quirk)
if (buf.length < 2)
continue;
raw = buf.readUInt16LE(0);
if (raw > 32767)
raw -= 65536;
}
else {
continue;
}
const temp = raw / 10;
if (temp >= 5 && temp <= 40)
continue;
const now = Date.now();
const lastCorrection = globalStore.getValue(msg.device, "ar331ProDp114CorrectionTime", 0);
if (now - lastCorrection < 2000)
continue;
globalStore.putValue(msg.device, "ar331ProDp114CorrectionTime", now);
const corrected = Math.round(Math.max(5, Math.min(40, temp)) * 10);
tuya.sendDataPointValue(msg.endpoint, 114, corrected).catch(() => { });
}
},
},
// biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
ZM35HQ_attr: {
cluster: "ssIasZone",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
let result = {};
const data = msg.data;
if (data && data.zoneStatus !== undefined) {
const result1 = fz.ias_occupancy_alarm_1_report.convert(model, msg, publish, options, meta);
result = { ...result1 };
}
if (data && data.currentZoneSensitivityLevel !== undefined) {
const senslookup = { 0: "low", 1: "medium", 2: "high" };
result.sensitivity = senslookup[data.currentZoneSensitivityLevel];
}
if (data && data["61441"] !== undefined) {
const keeptimelookup = { 0: 30, 1: 60, 2: 120 };
result.keep_time = keeptimelookup[data["61441"]];
}
return result;
},
},
command_stop_move_raw: {
cluster: "lightingColorCtrl",
type: "raw",
convert: (model, msg, publish, options, meta) => {
// commandStopMove without params
if (msg.data[2] !== 71)
return;
if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model))
return;
const movestop = "stop";
const action = (0, utils_1.postfixWithEndpointName)(`hue_${movestop}`, msg, model, meta);
const payload = { action };
(0, utils_1.addActionGroup)(payload, msg, model);
return payload;
},
},
};
exports.definitions = [
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_rjjsib2d"]),
model: "ZSN-03P",
vendor: "Novato",
description: "Temperature & humidity sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.temperature(),
e.humidity(),
tuya.exposes.batteryState(),
tuya.exposes.temperatureUnit(),
e
.numeric("temperature_calibration", ea.STATE_SET)
.withValueMin(-2)
.withValueMax(2)
.withValueStep(0.1)
.withUnit("°C")
.withDescription("Temperature calibration offset"),
e
.numeric("humidity_calibration", ea.STATE_SET)
.withValueMin(-10)
.withValueMax(10)
.withValueStep(1)
.withUnit("%")
.withDescription("Humidity calibration offset"),
],
meta: {
tuyaDatapoints: [
[1, "temperature", tuya.valueConverter.divideBy10],
[2, "humidity", tuya.valueConverter.raw],
[3, "battery_state", tuya.valueConverter.batteryState],
[9, "temperature_unit", tuya.valueConverter.temperatureUnitEnum],
[23, "temperature_calibration", tuya.valueConverter.divideBy10],
[24, "humidity_calibration", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_lb0fsvba"]),
model: "ZBN-DJ-63",
vendor: "Tuya",
description: "Smart circuit breaker",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.switch().setAccess("state", ea.STATE_SET).withDescription("Circuit Breaker Switch"),
e.energy().withDescription("Total Forward Energy (kWh)").withAccess(ea.STATE),
exposes.numeric("reverse_energy", ea.STATE).withUnit("kWh").withDescription("Total Reverse Energy (kWh)"),
e.power().withDescription("Active Power (W)").withAccess(ea.STATE),
e.voltage().withDescription("Phase A Voltage (V)").withAccess(ea.STATE),
e.current().withDescription("Phase A Current (A)").withAccess(ea.STATE),
exposes.numeric("temperature", ea.STATE).withUnit("°C").withDescription("Current Temperature"),
exposes.binary("switch_prepayment", ea.STATE_SET, "ON", "OFF").withDescription("Prepaid Function Switch"),
exposes.binary("clear_energy", ea.SET, "ON", "OFF").withDescription("Clear Remaining Available Energy"),
exposes.numeric("balance_energy", ea.STATE).withUnit("kWh").withDescription("Remaining Available Energy Display (kWh)"),
exposes.numeric("charge_energy", ea.SET).withValueMin(0).withValueMax(999999).withUnit("kWh").withDescription("Energy Recharge"),
exposes.binary("over_current_breaker", ea.ALL, "ON", "OFF").withDescription("Overcurrent Alarm Enabled"),
exposes
.numeric("over_current_threshold", ea.ALL)
.withValueMin(10)
.withValueMax(63)
.withUnit("A")
.withDescription("Overcurrent Threshold"),
exposes.binary("over_voltage_breaker", ea.ALL, "ON", "OFF").withDescription("Overvoltage Alarm Enabled"),
exposes
.numeric("over_voltage_threshold", ea.ALL)
.withValueMin(240)
.withValueMax(260)
.withUnit("V")
.withDescription("Overvoltage Threshold"),
exposes.binary("under_voltage_breaker", ea.ALL, "ON", "OFF").withDescription("Undervoltage Alarm Enabled"),
exposes
.numeric("under_voltage_threshold", ea.ALL)
.withValueMin(195)
.withValueMax(220)
.withUnit("V")
.withDescription("Undervoltage Threshold"),
exposes.numeric("leakage_current", ea.STATE).withUnit("mA").withDescription("Residual Current Display"),
exposes.binary("leakage_breaker", ea.ALL, "ON", "OFF").withDescription("Leakage Current Alarm Enabled"),
exposes.numeric("leakage_delay", ea.STATE_SET).withValueMin(1).withValueMax(9999).withUnit("s").withDescription("Leakage Delay"),
exposes
.numeric("leakage_threshold", ea.ALL)
.withValueMin(10)
.withValueMax(99)
.withUnit("mA")
.withDescription("Leakage Current Threshold"),
exposes.binary("high_temperature_breaker", ea.ALL, "ON", "OFF").withDescription("Temperature Alarm Enabled"),
exposes
.numeric("high_temperature_threshold", ea.ALL)
.withValueMin(10)
.withValueMax(85)
.withUnit("°C")
.withDescription("Temperature Threshold"),
exposes.numeric("countdown", ea.STATE_SET).withValueMin(0).withValueMax(43200).withUnit("s").withDescription("Countdown Timer"),
exposes
.numeric("power_on_delay", ea.STATE_SET)
.withValueMin(1)
.withValueMax(9999)
.withUnit("s")
.withDescription("Power-On Delay Energization Time"),
exposes.numeric("recover_count", ea.STATE_SET).withValueMin(0).withValueMax(999).withDescription("Reclosing Attempt Limit"),
exposes.binary("recover_enable", ea.STATE_SET, "ON", "OFF").withDescription("Reclosing Enable"),
exposes
.enum("fault", ea.STATE, [
"clear",
"short_circuit_alarm",
"surge_alarm",
"overload_alarm",
"leakagecurr_alarm",
"temp_dif_fault",
"fire_alarm",
"high_power_alarm",
"self_test_alarm",
"ov_cr",
"unbalance_alarm",
"ov_vol",
"undervoltage_alarm",
"miss_phase_alarm",
"outage_alarm",
"magnetism_alarm",
"credit_alarm",
"no_balance_alarm",
])
.withDescription("Fault Alarm"),
exposes.enum("relay_power_on_state", ea.STATE_SET, ["Off", "On", "Restore"]).withDescription("Relay Power-On State Setting"),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[6, null, tuya.valueConverter.phaseVariant2],
[
9,
"fault",
tuya.valueConverterBasic.lookup({
clear: 0,
short_circuit_alarm: 1 << 0,
surge_alarm: 1 << 1,
overload_alarm: 1 << 2,
leakagecurr_alarm: 1 << 3,
temp_dif_fault: 1 << 4,
fire_alarm: 1 << 5,
high_power_alarm: 1 << 6,
self_test_alarm: 1 << 7,
ov_cr: 1 << 8,
unbalance_alarm: 1 << 9,
ov_vol: 1 << 10,
undervoltage_alarm: 1 << 11,
miss_phase_alarm: 1 << 12,
outage_alarm: 1 << 13,
magnetism_alarm: 1 << 14,
credit_alarm: 1 << 15,
no_balance_alarm: 1 << 16,
}),
],
[11, "switch_prepayment", tuya.valueConverter.onOff],
[12, "clear_energy", tuya.valueConverter.onOff],
[13, "balance_energy", tuya.valueConverter.divideBy100],
[14, "charge_energy", tuya.valueConverter.divideBy100],
[15, "leakage_current", tuya.valueConverter.raw],
[16, "state", tuya.valueConverter.onOff],
[17, null, tuya.valueConverter.threshold_2],
[17, "leakage_threshold", tuya.valueConverter.threshold_2],
[17, "leakage_breaker", tuya.valueConverter.threshold_2],
[17, "high_temperature_threshold", tuya.valueConverter.threshold_2],
[17, "high_temperature_breaker", tuya.valueConverter.threshold_2],
[18, null, tuya.valueConverter.threshold_3],
[18, "over_current_threshold", tuya.valueConverter.threshold_3],
[18, "over_current_breaker", tuya.valueConverter.threshold_3],
[18, "over_voltage_threshold", tuya.valueConverter.threshold_3],
[18, "over_voltage_breaker", tuya.valueConverter.threshold_3],
[18, "under_voltage_threshold", tuya.valueConverter.threshold_3],
[18, "under_voltage_breaker", tuya.valueConverter.threshold_3],
[102, "recover_count", tuya.valueConverter.raw],
[103, "temperature", tuya.valueConverter.raw],
[104, "recover_enable", tuya.valueConverter.onOff],
[105, "countdown", tuya.valueConverter.countdown],
[106, "cycle_time", tuya.valueConverter.raw],
[107, "leakage_delay", tuya.valueConverter.raw],
[110, "reverse_energy", tuya.valueConverter.divideBy100],
[119, "power_on_delay", tuya.valueConverter.raw],
[124, "alarm_over_current_count", tuya.valueConverter.raw],
[125, "alarm_low_current_count", tuya.valueConverter.raw],
[127, "status", tuya.valueConverter.raw],
[134, "relay_power_on_state", tuya.valueConverterBasic.lookup({ Off: 0, On: 1, Restore: 2 })],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_zofmmt9s"]),
model: "ZSM-01",
vendor: "Novato",
description: "Curtain motor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [te.coverPosition(), te.motorDirection(), te.clickControl(), te.favoritePosition()],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
// DP9 = set position, DP8 = position report; DP9 first so writes go to it
[9, "position", tuya.valueConverter.coverPosition],
[8, "position", tuya.valueConverter.coverPosition],
[11, "motor_direction", tuya.valueConverter.tubularMotorDirection],
[19, "favorite_position", tuya.valueConverter.raw],
[20, "click_control", tuya.valueConverter.clickControl],
],
},
},
{
zigbeeModel: ["TS0204"],
model: "TS0204",
vendor: "Tuya",
description: "Gas sensor",
whiteLabel: [{ vendor: "Tesla Smart", model: "TSL-SEN-GAS" }],
fromZigbee: [fz.ias_gas_alarm_1],
toZigbee: [],
exposes: [e.gas(), e.tamper()],
},
{
zigbeeModel: ["TS020C"],
model: "TS020C",
vendor: "Tuya",
description: "PIR sensor",
fromZigbee: [fz.ias_occupancy_alarm_1, fz.battery, fzLocal.ts020cIlluminance],
extend: [tuya.modernExtend.tuyaBase({ dp: true, queryOnDeviceAnnounce: true, queryOnConfigure: true })],
exposes: [
e.occupancy(),
e.battery(),
e.illuminance(),
e
.enum("sensitivity", ea.STATE_SET, ["low", "medium", "high"])
.withDescription("PIR sensor sensitivity (refresh and update only while active)"),
e
.enum("hold_time", ea.STATE_SET, ["10", "30", "60", "120"])
.withDescription("PIR keep time in seconds (refresh and update only while active)"),
e
.numeric("light_interval", ea.STATE_SET)
.withValueMin(1)
.withValueMax(720)
.withValueStep(1)
.withUnit("minutes")
.withDescription("Brightness acquisition interval (refresh and update only while active)"),
],
meta: {
tuyaDatapoints: [
[1, "occupancy", tuya.valueConverter.trueFalse0],
[4, "battery", tuya.valueConverter.raw],
[12, "illuminance", tuya.valueConverter.raw], // doesn't work, hence fzLocal.TS020C_illuminance
[
101,
"sensitivity",
tuya.valueConverterBasic.lookup({
low: tuya.enum(0),
medium: tuya.enum(1),
high: tuya.enum(2),
}),
],
[
102,
"hold_time",
tuya.valueConverterBasic.lookup({
"10": tuya.enum(0),
"30": tuya.enum(1),
"60": tuya.enum(2),
"120": tuya.enum(3),
}),
],
[103, "light_interval", tuya.valueConverter.raw],
],
},
},
{
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", "Optical smoke sensor (model YG500A on the PCB)", ["_TZ3210_up3pngle"]),
tuya.whitelabel("Nedis", "ZBDS10WT", "Smoke sensor", ["_TYZB01_wqcac7lo"]),
],
// Configure battery % fails
// https://github.com/Koenkk/zigbee2mqtt/issues/22421
extend: [
m.battery({ percentageReporting: false }),
m.iasZoneAlarm({
zoneType: "smoke",
zoneAttributes: ["alarm_1", "tamper"],
}),
],
configure: async (device, coordinatorEndpoint) => {
if (device.manufacturerName === "_TZ3210_up3pngle") {
// Required for this version
// https://github.com/Koenkk/zigbee-herdsman-converters/pull/8004
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genPowerCfg"]);
await reporting.batteryPercentageRemaining(endpoint);
}
},
},
{
zigbeeModel: ["TS0111"],
model: "TS0111",
vendor: "Tuya",
description: "Socket",
extend: [tuya.modernExtend.tuyaBase(), tuya.modernExtend.tuyaOnOff()],
},
{
zigbeeModel: ["TS0218"],
model: "TS0218",
vendor: "Tuya",
description: "Button",
fromZigbee: [fz.command_emergency, fz.battery],
exposes: [e.battery(), e.action(["click"])],
toZigbee: [],
},
{
fingerprint: [
{
manufacturerName: "Zbeacon",
modelID: "TS0203",
endpoints: [{ ID: 1, profileID: 260, deviceID: 770, inputClusters: [0, 3, 1, 32, 1026, 1029], outputClusters: [25] }],
},
{
manufacturerName: "Zbeacon",
modelID: "TS0202",
endpoints: [{ ID: 1, profileID: 260, deviceID: 770, inputClusters: [0, 3, 1, 32, 1026, 1029], outputClusters: [25] }],
},
],
model: "ZC-W1",
vendor: "iHseno",
description: "Temperature & humidity sensor",
extend: [m.temperature(), m.humidity(), m.battery()],
},
{
zigbeeModel: ["TS0203", "ZG-102Z", "AY-101Z"],
model: "TS0203",
vendor: "Tuya",
description: "Door/window sensor",
fromZigbee: [fz.ias_contact_alarm_1, fz.battery, fz.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" },
{ vendor: "HOBEIAN", model: "ZG-102ZA", fingerprint: [{ modelID: "ZG-102Z" }] },
{
vendor: "AOYAN",
model: "AY-101Z",
description: "Door/window sensor",
fingerprint: [{ modelID: "AY-101Z", manufacturerName: "AOYAN " }],
},
tuya.whitelabel("Niceboy", "ORBIS Windows & Door Sensor", "Door sensor", ["_TZ3000_qrldbmfn"]),
tuya.whitelabel("Tuya", "ZD06", "Door window sensor", ["_TZ3000_rcuyhwe3", "_TZ3000_996rpfy6"]),
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-S01-GWM-C-MS", "Door/window alarm sensor", ["_TZ3000_8yhypbo7"]),
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"]),
tuya.whitelabel("QA", "QASD1", "Door sensor", ["_TZ3000_udyjylt7"]),
tuya.whitelabel("Nous", "E3", "Door sensor", ["_TZ3000_v7chgqso"]),
tuya.whitelabel("Woox", "R7047", "Smart Door & Window Sensor", ["_TZ3000_timx9ivq"]),
tuya.whitelabel("Wing", "WZDA1", "Door sensor", ["_TZ3000_rid8lzvo"]),
],
exposes: (device, options) => {
const exps = [e.contact(), e.battery(), e.battery_voltage()];
const noTamperModels = [
// manufacturerName for models without a tamper sensor
"_TZ3000_rcuyhwe3",
"_TZ3000_996rpfy6", // Tuya ZD06
"_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
"_TZ3000_timx9ivq", //Woox R7047
"_TZ3000_t3vvhrmh",
];
if (!device || !noTamperModels.includes(device.manufacturerName)) {
exps.push(e.tamper());
}
const noBatteryLowModels = ["_TZ3000_26fmupbb", "_TZ3000_oxslv1c9", "_TZ3000_osu834un", "_TZ3000_timx9ivq"];
if (!device || !noBatteryLowModels.includes(device.manufacturerName)) {
exps.push(e.battery_low());
}
return exps;
},
meta: {
battery: {
// These sensors do send a Battery Percentage Remaining (0x0021)
// value, but is usually incorrect. For example, a coin battery tested
// with a load tester may show 80%, but report 2.5V / 1%. This voltage
// calculation matches what ZHA does by default.
// https://github.com/Koenkk/zigbee2mqtt/discussions/17337
// https://github.com/zigpy/zha-device-handlers/blob/c6ed94a52a469e72b32ece2a92d528060c7fd034/zhaquirks/__init__.py#L195-L228
voltageToPercentage: "3V_1500_2800",
},
},
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 {
/* Fails for some*/
}
const endpoint = device.getEndpoint(1);
if (endpoint.binds.some((b) => b.cluster.name === "genPollCtrl")) {
await endpoint.unbind("genPollCtrl", coordinatorEndpoint);
}
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_vuwtqx0t", "_TZE200_vuwtqx0t"]),
model: "TS0601_water_valve",
vendor: "Tuya",
description: "Ultrasonic water meter valve",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.switch().setAccess("state", ea.STATE_SET),
e.numeric("water_consumed", ea.STATE).withUnit("L").withDescription("Total water consumption").withValueMin(0).withValueStep(0.001),
e
.numeric("reverse_water_consumed", ea.STATE)
.withUnit("L")
.withDescription("Total reverse water consumption")
.withValueMin(0)
.withValueStep(0.001),
e.numeric("month_consumption", ea.STATE).withUnit("L").withDescription("month consumption").withValueMin(0).withValueStep(0.001),
e.numeric("daily_consumption", ea.STATE).withUnit("L").withDescription("daily consumption").withValueMin(0).withValueStep(0.001),
e.numeric("flow_rate", ea.STATE).withUnit("L/h").withDescription("Instantaneous water flow rate"),
e.binary("auto_clean", ea.STATE_SET, "ON", "OFF").withDescription("Auto clean"),
e.temperature(),
e.battery_voltage(),
e.text("meter_id", ea.STATE).withDescription("Meter identification number"),
e.text("faults", ea.STATE).withDescription("Active fault status"),
e.enum("report_period", ea.STATE_SET, ["1h", "2h", "3h", "4h", "6h", "8h", "12h", "24h"]).withDescription("Report period"),
],
meta: {
tuyaDatapoints: [
[1, "water_consumed", tuya.valueConverter.raw],
[
2,
"month_consumption",
{
from: (v) => {
const buf = Buffer.isBuffer(v) ? v : Buffer.from(v || []);
if (buf.length >= 8) {
const value = (buf.readUInt8(4) << 24) + (buf.readUInt8(5) << 16) + (buf.readUInt8(6) << 8) + buf.readUInt8(7);
return value;
}
return 0;
},
},
],
[
3,
"daily_consumption",
{
from: (v) => {
const buf = Buffer.isBuffer(v) ? v : Buffer.from(v || []);
if (buf.length >= 8) {
const value = (buf.readUInt8(4) << 24) + (buf.readUInt8(5) << 16) + (buf.readUInt8(6) << 8) + buf.readUInt8(7);
return value;
}
return 0;
},
},
],
[
4,
"report_period",
tuya.valueConverterBasic.lookup({
"1h": tuya.enum(0),
"2h": tuya.enum(1),
"3h": tuya.enum(2),
"4h": tuya.enum(3),
"6h": tuya.enum(4),
"8h": tuya.enum(5),
"12h": tuya.enum(6),
"24h": tuya.enum(7),
}),
],
[
5,
"faults",
{
from: (value, meta) => {
const faults = [];
const faultMap = {
1: "battery_alarm",
2: "magnetism_alarm",
4: "cover_alarm",
8: "credit_alarm",
16: "switch_gaps_alarm",
32: "meter_body_alarm",
64: "abnormal_water_alarm",
128: "arrearage_alarm",
256: "overflow_alarm",
512: "revflow_alarm",
1024: "over_pre_alarm",
2048: "empty_pipe_alarm",
4096: "transducer_alarm",
};
if (value === 0) {
return "no_alarm";
}
for (const [bit, name] of Object.entries(faultMap)) {
if (value & Number.parseInt(bit, 10)) {
faults.push(name);
}
}
// Return as comma-separated string
return faults.join(",");
},
},
],
[13, "state", tuya.valueConverter.onOffNotStrict],
[14, "auto_clean", tuya.valueConverter.onOff],
[16, "meter_id", tuya.valueConverter.raw],
[
18,
"reverse_water_consumed",
{
from: (value, meta) => {
const buffer = Buffer.from(value);
if (buffer.length >= 4) {
return buffer.readUInt32BE(0) / 1000;
}
},
},
],
[
21,
"flow_rate",
{
from: (v) => {
const buf = Buffer.isBuffer(v) ? v : Buffer.from(v || []);
if (buf.length >= 4) {
const value = buf.readUInt32BE(0);
return value;
}
return 0;
},
},
],
[22, "temperature", tuya.valueConverter.divideBy100],
[26, "voltage", tuya.valueConverter.multiplyBy10],
],
},
// Optional: Add device-specific options
options: [
exposes.options.precision("water_consumed"),
exposes.options.calibration("water_consumed"),
exposes.options.precision("month_consumption"),
exposes.options.calibration("month_consumption"),
exposes.options.precision("daily_consumption"),
exposes.options.calibration("daily_consumption"),
exposes.options.precision("flow_rate"),
exposes.options.calibration("flow_rate"),
exposes.options.precision("reverse_water_consumed"),
exposes.options.calibration("reverse_water_consumed"),
exposes.options.precision("temperature"),
exposes.options.calibration("temperature"),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_jt50ea5d"]),
model: "TS0601_heat_meter",
vendor: "Tuya",
description: "Ultrasonic heat meter",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.numeric("water_consumed", ea.STATE).withUnit("m³").withDescription("Total water consumption").withValueMin(0).withValueStep(0.001),
e
.numeric("monthly_water_consumption", ea.STATE)
.withUnit("m³")
.withDescription("Monthly water consumption")
.withValueMin(0)
.withValueStep(0.001),
e
.numeric("daily_water_consumption", ea.STATE)
.withUnit("m³")
.withDescription("Daily water consumption")
.withValueMin(0)
.withValueStep(0.001),
e.enum("report_period", ea.STATE_SET, ["1h", "2h", "3h", "4h", "6h", "8h", "12h", "24h", "48h", "72h"]).withDescription("Report period"),
e.text("fault", ea.STATE).withDescription("Alarm event status"),
e.binary("prepayment_switch", ea.STATE_SET, "ON", "OFF").withDescription("Cumulative metering switch"),
e.numeric("cumulative_heat", ea.STATE).withUnit("kWh").withDescription("Cumulative heat").withValueMin(0).withValueStep(0.01),
e.text("meter_id", ea.STATE).withDescription("Meter identification number"),
e
.numeric("instantaneous_flow_rate", ea.STATE)
.withUnit("m³/h")
.withDescription("Instantaneous flow rate")
.withValueMin(0)
.withValueStep(0.001),
e
.numeric("inlet_water_temperature", ea.STATE)
.withUnit("°C")
.withDescription("Inlet water temperature")
.withValueMin(0)
.withValueStep(0.01),
e
.numeric("outlet_water_temperature", ea.STATE)
.withUnit("°C")
.withDescription("Outlet water temperature")
.withValueMin(0)
.withValueStep(0.01),
e.battery_voltage(),
],
meta: {
tuyaDatapoints: [
// DP 1 - Cumulative Flow
[1, "water_consumed", tuya.valueConverter.divideBy1000],
// DP 2 - Monthly Water Consumption
[2, "monthly_water_consumption", tuya.valueConverter.waterConsumption],
// DP 3 - Daily Water Consumption
[3, "daily_water_consumption", tuya.valueConverter.waterConsumption],
// DP 4 - Report Period Setting
[
4,
"report_period",
tuya.valueConverterBasic.lookup({
"1h": tuya.enum(0),
"2h": tuya.enum(1),
"3h": tuya.enum(2),
"4h": tuya.enum(3),
"6h": tuya.enum(4),
"8h": tuya.enum(5),
"12h": tuya.enum(6),
"24h": tuya.enum(7),
"48h": tuya.enum(8),
"72h": tuya.enum(9),
}),
],
// DP 5 - Fault bitmap
[
5,
"fault",
{
from: (value, meta) => {
const faults = [];
const faultMap = {
1: "battery_alarm",
2: "magnetism_alarm",
4: "cover_alarm",
8: "credit_alarm",
16: "switch_gaps_alarm",
32: "meter_body_alarm",
64: "abnormal_water_alarm",
128: "arrearage_alarm",
256: "overflow_alarm",
512: "revflow_alarm",
1024: "over_pre_alarm",
2048: "empty_pip_alarm",
4096: "transduce_alarm",
};
if (value === 0) {
return "OK";
}
for (const [bit, name] of Object.entries(faultMap)) {
if (value & Number.parseInt(bit, 10)) {
faults.push(name);
}
}
return faults.join(", ");
},
},
],
// DP 6 - Prepayment Switch
[6, "prepayment_switch", tuya.valueConverter.onOff],
// DP 7 - Cumulative Heat
[7, "cumulative_heat", tuya.valueConverter.divideBy100],
// DP 16 - Meter ID
[16, "meter_id", tuya.valueConverter.raw],
// DP 19 - Instantaneous Flow Rate
[
19,
"instantaneous_flow_rate",
{
from: (v) => {
const buf = Buffer.isBuffer(v) ? v : Buffer.from(v || []);
if (buf.length >= 4) {
const value = buf.readUInt32BE(0);
return value / 1000;
}
return 0;
},
},
],
// DP 21 - Inlet Water Temperature
[21, "inlet_water_temperature", tuya.valueConverter.divideBy100],
// DP 22 - Outlet Water Temperature
[22, "outlet_water_temperature", tuya.valueConverter.divideBy100],
// DP 24 - Power Supply Voltage
[24, "voltage", tuya.valueConverter.multiplyBy10],
],
},
options: [
exposes.options.precision("cumulative_flow"),
exposes.options.calibration("cumulative_flow"),
exposes.options.precision("monthly_water_consumption"),
exposes.options.calibration("monthly_water_consumption"),
exposes.options.precision("daily_water_consumption"),
exposes.options.calibration("daily_water_consumption"),
exposes.options.precision("instantaneous_flow_rate"),
exposes.options.calibration("instantaneous_flow_rate"),
exposes.options.precision("cumulative_heat"),
exposes.options.calibration("cumulative_heat"),
exposes.options.precision("inlet_water_temperature"),
exposes.options.calibration("inlet_water_temperature"),
exposes.options.precision("outlet_water_temperature"),
exposes.options.calibration("outlet_water_temperature"),
exposes.options.precision("voltage"),
exposes.options.calibration("voltage"),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_ajlu4cud"]),
model: "TS0601_water_meter",
vendor: "Tuya",
description: "Ultrasonic water meter",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.numeric("water_consumed", ea.STATE).withUnit("m³").withDescription("Total water consumption").withValueMin(0).withValueStep(0.001),
e.numeric("month_consumption", ea.STATE).withUnit("m³").withDescription("Monthly water consumption").withValueMin(0).withValueStep(0.001),
e.numeric("daily_consumption", ea.STATE).withUnit("m³").withDescription("Daily water consumption").withValueMin(0).withValueStep(0.001),
e.numeric("flow_rate", ea.STATE).withUnit("m³/h").withDescription("Instantaneous flow rate").withValueMin(0).withValueStep(0.001),
e
.numeric("reverse_water_consumed", ea.STATE)
.withUnit("m³")
.withDescription("Reverse water consumption")
.withValueMin(0)
.withValueStep(0.001),
e.enum("report_period", ea.STATE_SET, ["1h", "2h", "3h", "4h", "6h", "8h", "12h", "24h"]).withDescription("Report period"),
e.text("meter_id", ea.STATE).withDescription("Meter identification number"),
e.temperature(),
e.battery_voltage(),
e.text("faults", ea.STATE).withDescription("Active fault status"),
],
meta: {
tuyaDatapoints: [
[1, "water_consumed", tuya.valueConverter.divideBy1000],
[
2,
"month_consumption",
{
from: (value, meta) => {
const buffer = Buffer.from(value);
if (buffer.length >= 8) {
try {
const uintValue = buffer.slice(-4).readUInt32BE(0);
return uintValue / 1000;
}
catch (_error) {
return null;
}
}
return null;
},
},
],
[
3,
"daily_consumption",
{
from: (value, meta) => {
const buffer = Buffer.from(value);
if (buffer.length >= 8) {
try {
const uintValue = buffer.slice(-4).readUInt32BE(0);
return uintValue / 1000;
}
catch (_error) {
return null;
}
}
return null;
},
},
],
[
21,
"flow_rate",
{
from: (value, meta) => {
const buffer = Buffer.from(value);
if (buffer.length >= 4) {
try {
const uintValue = buffer.slice(-4).readUInt32BE(0);
return uintValue / 1000;
}
catch (_error) {
return null;
}
}
return null;
},
},
],
[
4,
"report_period",
tuya.valueConverterBasic.lookup({
"1h": 0,
"2h": 1,
"3h": 2,
"4h": 3,
"6h": 4,
"8h": 5,
"12h": 6,
"24h": 7,
}),
],
[
5,
"faults",
{
from: (value, meta) => {
const faults = [];
const faultMap = {
1: "battery_alarm",
2: "magnetism_alarm",
4: "cover_alarm",
8: "credit_alarm",
16: "switch_gaps_alarm",
32: "meter_body_alarm",
64: "abnormal_water_alarm",
128: "arrearage_alarm",
256: "overflow_alarm",
512: "revflow_alarm",
1024: "over_pre_alarm",
2048: "empty_pipe_alarm",
4096: "transducer_alarm",
};
if (value === 0) {
return "no_alarm";
}
for (const [bit, name] of Object.entries(faultMap)) {
if (value & Number.parseInt(bit, 10)) {
faults.push(name);
}
}
// Return as comma-separated string
return faults.join(",");
},
},
],
[16, "meter_id", tuya.valueConverter.raw],
[
18,
"reverse_water_consumed",
{
from: (value, meta) => {
const buffer = Buffer.from(value);
if (buffer.length >= 4) {
try {
const uintValue = buffer.readUInt32BE(0);
return uintValue / 1000;
}
catch (_error) {
return null;
}
}
return null;
},
},
],
[22, "temperature", tuya.valueConverter.divideBy100],
[26, "voltage", tuya.valueConverter.multiplyBy10],
],
},
options: [
exposes.options.precision("water_consumed"),
exposes.options.calibration("water_consumed"),
exposes.options.precision("month_consumption"),
exposes.options.calibration("month_consumption"),
exposes.options.precision("daily_consumption"),
exposes.options.calibration("daily_consumption"),
exposes.options.precision("flow_rate"),
exposes.options.calibration("flow_rate"),
exposes.options.precision("reverse_water_consumed"),
exposes.options.calibration("reverse_water_consumed"),
exposes.options.precision("temperature"),
exposes.options.calibration("temperature"),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_zpvusbtv"]),
model: "ZN2S-RS02E",
vendor: "Tuya",
description: "Two gang switch with colored backlight modes",
extend: [tuya.modernExtend.tuyaBase({ dp: true }), m.deviceEndpoints({ endpoints: { l1: 1, l2: 1 } })],
exposes: [
tuya.exposes.switch().withDescription("All switches"),
tuya.exposes.switch().withEndpoint("l1"),
tuya.exposes.switch().withEndpoint("l2"),
e.binary("backlight_switch", ea.STATE_SET, "ON", "OFF").withDescription("Backlight master switch"),
e.numeric("backlight", ea.STATE_SET).withValueMin(0).withValueMax(100).withUnit("%").withDescription("Backlight brightness percentage"),
e.enum("indicator_mode", ea.STATE_SET, ["off", "on_off_status", "switch_position"]).withDescription("LED indicator mode"),
e.power_on_behavior().withAccess(ea.STATE_SET),
e.binary("child_lock", ea.STATE_SET, "ON", "OFF").withDescription("Child lock"),
e
.enum("on_color", ea.STATE_SET, ["red", "blue", "green", "white", "yellow", "magenta", "cyan", "warm_white", "warm_yellow"])
.withDescription("ON color"),
e
.enum("off_color", ea.STATE_SET, ["red", "blue", "green", "white", "yellow", "magenta", "cyan", "warm_white", "warm_yellow"])
.withDescription("OFF color"),
e.numeric("countdown_l1", ea.STATE_SET).withUnit("s").withDescription("Countdown for l1").withValueMin(0).withValueMax(86400),
e.numeric("countdown_l2", ea.STATE_SET).withUnit("s").withDescription("Countdown for l2").withValueMin(0).withValueMax(86400),
],
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[13, "state", tuya.valueConverter.onOff],
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[7, "countdown_l1", tuya.valueConverter.countdown],
[8, "countdown_l2", tuya.valueConverter.countdown],
[14, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum],
[
15,
"indicator_mode",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
on_off_status: tuya.enum(1),
switch_position: tuya.enum(2),
}),
],
[16, "backlight_switch", tuya.valueConverter.onOff],
[101, "child_lock", tuya.valueConverter.onOff],
[102, "backlight", tuya.valueConverter.raw],
[
103,
"on_color",
tuya.valueConverterBasic.lookup({
red: tuya.enum(0),
blue: tuya.enum(1),
green: tuya.enum(2),
white: tuya.enum(3),
yellow: tuya.enum(4),
magenta: tuya.enum(5),
cyan: tuya.enum(6),
warm_white: tuya.enum(7),
warm_yellow: tuya.enum(8),
}),
],
[
104,
"off_color",
tuya.valueConverterBasic.lookup({
red: tuya.enum(0),
blue: tuya.enum(1),
green: tuya.enum(2),
white: tuya.enum(3),
yellow: tuya.enum(4),
magenta: tuya.enum(5),
cyan: tuya.enum(6),
warm_white: tuya.enum(7),
warm_yellow: tuya.enum(8),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_rkbxtclc"]),
model: "TS0601_3gang_rkbxtclc",
vendor: "Tuya",
description: "Three gang smart switch with colored backlight modes",
extend: [tuya.modernExtend.tuyaBase({ dp: true }), m.deviceEndpoints({ endpoints: { l1: 1, l2: 1, l3: 1 } })],
exposes: [
tuya.exposes.switch().withDescription("All switches"),
tuya.exposes.switch().withEndpoint("l1"),
tuya.exposes.switch().withEndpoint("l2"),
tuya.exposes.switch().withEndpoint("l3"),
e.binary("backlight_switch", ea.STATE_SET, "ON", "OFF").withDescription("Backlight master switch"),
e.numeric("backlight", ea.STATE_SET).withValueMin(0).withValueMax(100).withUnit("%").withDescription("Backlight brightness percentage"),
e.enum("indicator_mode", ea.STATE_SET, ["off", "on_off_status", "switch_position"]).withDescription("LED indicator mode"),
e.power_on_behavior().withAccess(ea.STATE_SET),
e.binary("child_lock", ea.STATE_SET, "ON", "OFF").withDescription("Child lock"),
e
.enum("on_color", ea.STATE_SET, ["red", "blue", "green", "white", "yellow", "magenta", "cyan", "warm_white", "warm_yellow"])
.withDescription("ON color"),
e
.enum("off_color", ea.STATE_SET, ["red", "blue", "green", "white", "yellow", "magenta", "cyan", "warm_white", "warm_yellow"])
.withDescription("OFF color"),
e.numeric("countdown_l1", ea.STATE_SET).withUnit("s").withDescription("Countdown for l1").withValueMin(0).withValueMax(86400),
e.numeric("countdown_l2", ea.STATE_SET).withUnit("s").withDescription("Countdown for l2").withValueMin(0).withValueMax(86400),
e.numeric("countdown_l3", ea.STATE_SET).withUnit("s").withDescription("Countdown for l3").withValueMin(0).withValueMax(86400),
],
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[13, "state", tuya.valueConverter.onOff],
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
[7, "countdown_l1", tuya.valueConverter.countdown],
[8, "countdown_l2", tuya.valueConverter.countdown],
[9, "countdown_l3", tuya.valueConverter.countdown],
[14, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum],
[
15,
"indicator_mode",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
on_off_status: tuya.enum(1),
switch_position: tuya.enum(2),
}),
],
[16, "backlight_switch", tuya.valueConverter.onOff],
[101, "child_lock", tuya.valueConverter.onOff],
[102, "backlight", tuya.valueConverter.raw],
[
103,
"on_color",
tuya.valueConverterBasic.lookup({
red: tuya.enum(0),
blue: tuya.enum(1),
green: tuya.enum(2),
white: tuya.enum(3),
yellow: tuya.enum(4),
magenta: tuya.enum(5),
cyan: tuya.enum(6),
warm_white: tuya.enum(7),
warm_yellow: tuya.enum(8),
}),
],
[
104,
"off_color",
tuya.valueConverterBasic.lookup({
red: tuya.enum(0),
blue: tuya.enum(1),
green: tuya.enum(2),
white: tuya.enum(3),
yellow: tuya.enum(4),
magenta: tuya.enum(5),
cyan: tuya.enum(6),
warm_white: tuya.enum(7),
warm_yellow: tuya.enum(8),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_7ytnacie", "_TZE204_hewlydpz"]),
model: "TS0601_4gang_7ytnacie",
vendor: "Tuya",
description: "Four gang smart switch with colored backlight modes",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.switch().withDescription("All switches"),
tuya.exposes.switch().withEndpoint("l1"),
tuya.exposes.switch().withEndpoint("l2"),
tuya.exposes.switch().withEndpoint("l3"),
tuya.exposes.switch().withEndpoint("l4"),
e.binary("backlight_switch", ea.STATE_SET, "ON", "OFF").withDescription("Backlight master switch"),
e.numeric("backlight", ea.STATE_SET).withValueMin(0).withValueMax(100).withUnit("%").withDescription("Backlight brightness percentage"),
e.enum("indicator_mode", ea.STATE_SET, ["off", "on_off_status", "switch_position"]).withDescription("LED indicator mode"),
e.power_on_behavior().withAccess(ea.STATE_SET),
e.binary("child_lock", ea.STATE_SET, "ON", "OFF").withDescription("Child lock"),
e
.enum("on_color", ea.STATE_SET, ["red", "blue", "green", "white", "yellow", "magenta", "cyan", "warm_white", "warm_yellow"])
.withDescription("ON color"),
e
.enum("off_color", ea.STATE_SET, ["red", "blue", "green", "white", "yellow", "magenta", "cyan", "warm_white", "warm_yellow"])
.withDescription("OFF color"),
e.numeric("countdown_l1", ea.STATE_SET).withUnit("s").withDescription("Countdown for l1").withValueMin(0).withValueMax(86400),
e.numeric("countdown_l2", ea.STATE_SET).withUnit("s").withDescription("Countdown for l2").withValueMin(0).withValueMax(86400),
e.numeric("countdown_l3", ea.STATE_SET).withUnit("s").withDescription("Countdown for l3").withValueMin(0).withValueMax(86400),
e.numeric("countdown_l4", ea.STATE_SET).withUnit("s").withDescription("Countdown for l4").withValueMin(0).withValueMax(86400),
],
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[13, "state", tuya.valueConverter.onOff],
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
[4, "state_l4", tuya.valueConverter.onOff],
[7, "countdown_l1", tuya.valueConverter.countdown],
[8, "countdown_l2", tuya.valueConverter.countdown],
[9, "countdown_l3", tuya.valueConverter.countdown],
[10, "countdown_l4", tuya.valueConverter.countdown],
[14, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum],
[
15,
"indicator_mode",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
on_off_status: tuya.enum(1),
switch_position: tuya.enum(2),
}),
],
[16, "backlight_switch", tuya.valueConverter.onOff],
[101, "child_lock", tuya.valueConverter.onOff],
[102, "backlight", tuya.valueConverter.raw],
[
103,
"on_color",
tuya.valueConverterBasic.lookup({
red: tuya.enum(0),
blue: tuya.enum(1),
green: tuya.enum(2),
white: tuya.enum(3),
yellow: tuya.enum(4),
magenta: tuya.enum(5),
cyan: tuya.enum(6),
warm_white: tuya.enum(7),
warm_yellow: tuya.enum(8),
}),
],
[
104,
"off_color",
tuya.valueConverterBasic.lookup({
red: tuya.enum(0),
blue: tuya.enum(1),
green: tuya.enum(2),
white: tuya.enum(3),
yellow: tuya.enum(4),
magenta: tuya.enum(5),
cyan: tuya.enum(6),
warm_white: tuya.enum(7),
warm_yellow: tuya.enum(8),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0203", ["_TZ3210_jowhpxop"]),
model: "TS0203_1",
vendor: "Tuya",
description: "Door sensor with scene switch",
fromZigbee: [fz.ias_contact_alarm_1, fz.battery, fz.ias_contact_alarm_1_report],
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
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: tuya.fingerprint("TS0203", ["_TZ3000_pjb1ua0m"]),
model: "C3007",
vendor: "Tuya",
description: "Pressure pad sensor",
extend: [
m.iasZoneAlarm({
zoneType: "pressure",
zoneAttributes: ["alarm_1", "battery_low"],
}),
m.battery({ voltage: true }),
],
},
{
fingerprint: tuya.fingerprint("TS0021", ["_TZ3210_3ulg9kpo"]),
model: "LKWSZ211",
vendor: "Linkoze",
description: "Scene remote with 2 keys",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
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],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE200_bq5c8xfe",
"_TZE200_bjawzodf",
"_TZE200_qyflbnbj",
"_TZE200_44af8vyi",
"_TZE200_zl1kmjqx",
"_TZE204_qyflbnbj",
"_TZE284_qyflbnbj",
]),
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" ||
device.manufacturerName === "_TZE204_qyflbnbj" ||
device.manufacturerName === "_TZE284_qyflbnbj") {
exps.push(e.battery_low());
exps.push(e.enum("battery_level", ea.STATE, ["low", "middle", "high"]).withDescription("Battery level state"));
}
return exps;
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_gnpflcoq"]),
model: "TS0601_TZE284_gnpflcoq",
vendor: "Tuya",
description: "4-in-1 mmWave presence sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence(),
e.illuminance(),
e.temperature(),
e.humidity(),
e.battery(),
e.numeric("sensitivity", ea.STATE_SET).withValueMin(0).withValueMax(10).withDescription("Radar sensitivity"),
e.numeric("fading_time", ea.STATE_SET).withValueMin(0).withValueMax(1000).withUnit("s").withDescription("Delay before vacant"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse0],
[2, "sensitivity", tuya.valueConverter.raw],
[4, "battery", tuya.valueConverter.raw],
[7, "temperature", tuya.valueConverter.divideBy10],
[8, "humidity", tuya.valueConverter.raw],
[11, "illuminance", tuya.valueConverter.raw],
[102, "fading_time", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_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", "_TZE284_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",
"_TZE284_9yapgbuv",
"_TZE200_9yapgbuv",
"_TZE200_utkemkbs",
"_TZE204_utkemkbs",
"_TZE284_utkemkbs",
"_TZE204_9yapgbuv",
"_TZE204_upagmta9",
"_TZE200_cirvgep4",
"_TZE204_d7lpruvi",
"_TZE200_upagmta9",
"_TZE204_yjjdcqsq",
"_TZE204_jygvp6fk",
"_TZE204_cirvgep4",
"_TZE284_yjjdcqsq",
"_TZE200_d7lpruvi",
"_TZE284_hdyjyqjm",
"_TZE284_d7lpruvi",
"_TZE284_upagmta9",
"_TZE204_ksz749x8",
"_TZE204_1wnh8bqp",
"_TZE284_1wnh8bqp",
"_TZE284_ksz749x8",
]),
model: "TS0601_temperature_humidity_sensor_2",
vendor: "Tuya",
description: "Temperature and humidity sensor",
extend: [
tuya.modernExtend.tuyaBase({
dp: true,
queryOnDeviceAnnounce: true,
queryOnConfigure: true,
respondToMcuVersionResponse: true,
timeStart: "1970",
}),
],
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", "_TZE284_yjjdcqsq"]),
tuya.whitelabel("Tuya", "SZTH02", "Temperature and humidity sensor", ["_TZE200_utkemkbs", "_TZE204_utkemkbs", "_TZE284_utkemkbs"]),
tuya.whitelabel("Tuya", "ZTH02", "Temperature and humidity sensor", ["_TZE200_9yapgbuv", "_TZE204_9yapgbuv", "_TZE284_ksz749x8"]),
tuya.whitelabel("Tuya", "ZTH05", "Temperature and humidity sensor", ["_TZE204_upagmta9", "_TZE200_upagmta9", "_TZE284_upagmta9"]),
tuya.whitelabel("Tuya", "ZTH08-E", "Temperature and humidity sensor", ["_TZE200_cirvgep4", "_TZE204_cirvgep4"]),
tuya.whitelabel("Tuya", "ZTH08", "Temperature and humidity sensor", ["_TZE204_d7lpruvi", "_TZE284_d7lpruvi", "_TZE284_hdyjyqjm"]),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_s1xgth2u", "_TZE200_t3xd7l44", "_TZE284_kdqrazmy"]),
model: "TS0601_temperature_humidity_sensor_3",
vendor: "Tuya",
description: "Temperature & humidity sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true, queryOnConfigure: true, timeStart: "1970" })],
exposes: [e.temperature(), e.humidity(), e.battery(), tuya.exposes.temperatureUnit()],
meta: {
tuyaDatapoints: [
[1, "temperature", tuya.valueConverter.divideBy10],
[2, "humidity", tuya.valueConverter.raw],
[4, "battery", tuya.valueConverter.raw], // maybe?
[9, "temperature_unit", tuya.valueConverter.temperatureUnitEnum],
[19, "temperature_sensitivity", tuya.valueConverter.raw], // maybe? commented this out for now
],
},
whiteLabel: [tuya.whitelabel("Tuya", "TZE200_t3xd7l44", "Temperature and humidity sensor with clock", ["_TZE200_t3xd7l44"])],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_9ern5sfh"]),
model: "TS0601_temperature_humidity_sensor_4",
vendor: "Tuya",
description: "Temperature & humidity sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true, queryOnConfigure: true })],
exposes: [e.temperature(), e.humidity(), e.battery(), tuya.exposes.temperatureUnit()],
meta: {
tuyaDatapoints: [
[1, "temperature", tuya.valueConverter.divideBy10],
[2, "humidity", tuya.valueConverter.divideBy10],
[4, "battery", tuya.valueConverter.raw],
[9, "temperature_unit", tuya.valueConverter.temperatureUnitEnum],
[19, "temperature_sensitivity", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE200_vvmbj46n",
"_TZE284_vvmbj46n",
"_TZE200_w6n8jeuu",
"_TZE284_cwyqwqbf",
"_TZE2841000000_qf5mzewi", // ONENUO TH05Z - has calibration (DP23/24) instead of DP18
]),
model: "ZTH05Z",
vendor: "Tuya",
description: "Temperature and humidity sensor",
extend: [
tuya.modernExtend.tuyaBase({
dp: true,
queryOnDeviceAnnounce: true,
queryOnConfigure: true,
respondToMcuVersionResponse: true,
timeStart: "1970",
}),
],
exposes: (device, options) => {
const exps = [
e.temperature(),
e.humidity(),
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, ["lower_alarm", "upper_alarm", "cancel"]).withDescription("Temperature alarm"),
e.enum("humidity_alarm", ea.STATE, ["lower_alarm", "upper_alarm", "cancel"]).withDescription("Humidity alarm"),
e
.numeric("temperature_periodic_report", ea.STATE_SET)
.withUnit("min")
.withValueMin(1)
.withValueMax(120)
.withDescription("Temp periodic report"),
];
if (device.manufacturerName !== "_TZE2841000000_qf5mzewi") {
// Original ZTH05Z position for this expose - unchanged.
// Not present on the ONENUO TH05Z (_TZE2841000000_qf5mzewi) batch:
// this unit never reports DP18, tested empirically.
exps.push(e
.numeric("humidity_periodic_report", ea.STATE_SET)
.withUnit("min")
.withValueMin(1)
.withValueMax(120)
.withDescription("Humidity periodic report"));
}
if (device.manufacturerName === "_TZE2841000000_qf5mzewi") {
// ONENUO TH05Z (this specific firmware batch): wider sensitivity
// range than other ZTH05Z batches
exps.push(e
.numeric("temperature_sensitivity", ea.STATE_SET)
.withUnit("°C")
.withValueMin(0.6)
.withValueMax(2)
.withValueStep(0.1)
.withDescription("Sensitivity of temperature"));
exps.push(e
.numeric("humidity_sensitivity", ea.STATE_SET)
.withUnit("%")
.withValueMin(6)
.withValueMax(20)
.withValueStep(1)
.withDescription("Sensitivity of humidity"));
}
else {
exps.push(e
.numeric("temperature_sensitivity", ea.STATE_SET)
.withUnit("°C")
.withValueMin(0.3)
.withValueMax(1)
.withValueStep(0.1)
.withDescription("Sensitivity of temperature"));
exps.push(e
.numeric("humidity_sensitivity", ea.STATE_SET)
.withUnit("%")
.withValueMin(3)
.withValueMax(10)
.withValueStep(1)
.withDescription("Sensitivity of humidity"));
}
if (device.manufacturerName === "_TZE2841000000_qf5mzewi") {
// ONENUO TH05Z (this specific firmware batch): has calibration,
// which the other ZTH05Z batches don't have. Purely additive -
// doesn't move or replace anything for other manufacturerNames.
exps.push(tuya.exposes
.temperatureCalibration()
.withDescription("Calibration offset applied to the measured temperature. Takes effect on the device's next report/wake cycle, not instantly."));
exps.push(e
.numeric("humidity_calibration", ea.STATE_SET)
.withUnit("%")
.withValueMin(-20)
.withValueMax(20)
.withValueStep(1)
.withDescription("Calibration offset applied to the measured humidity. Takes effect on the device's next report/wake cycle, not instantly."));
}
if (device && device.manufacturerName === "_TZE284_cwyqwqbf") {
exps.push(tuya.exposes.batteryState());
}
else {
exps.push(e.battery());
}
return exps;
},
meta: {
tuyaDatapoints: [
[1, "temperature", tuya.valueConverter.divideBy10],
[2, "humidity", tuya.valueConverter.raw],
[3, "battery_state", tuya.valueConverter.batteryState],
[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.divideBy10],
[20, "humidity_sensitivity", tuya.valueConverter.raw],
// DP23/24: only present on the ONENUO _TZE2841000000_qf5mzewi
[23, "temperature_calibration", tuya.valueConverter.divideBy10],
[24, "humidity_calibration", tuya.valueConverter.raw],
],
},
whiteLabel: [
tuya.whitelabel("ONENUO", "TH05Z", "Temperature & humidity sensor with clock and humidity display", [
"_TZE200_vvmbj46n",
"_TZE2841000000_qf5mzewi",
]),
tuya.whitelabel("Tuya", "TZE284_cwyqwqbf", "Temperature & humidity sensor with LCD clock", ["_TZE284_cwyqwqbf"]),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_nvups4nh"]),
model: "TS0601_contact_temperature_humidity_sensor",
vendor: "Tuya",
description: "Contact, temperature and humidity sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
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: tuya.fingerprint("TS0601", ["_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(), e.battery()],
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE200_8ygsuhe1",
"_TZE200_yvx5lh6k",
"_TZE200_ryfmq5rl",
"_TZE200_c2fmom5z",
"_TZE200_mja3fuja",
"_TZE204_c2fmom5z",
"_TZE204_yvx5lh6k",
]),
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("ppb"), e.formaldehyd().withUnit("µg/m³")],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_8b9zpaav"]),
model: "TS0601_airbox",
vendor: "Tuya",
description: "zigbee air quality sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [e.temperature(), e.humidity(), e.co2(), e.voc().withUnit("ppb"), e.formaldehyd().withUnit("µg/m³")],
meta: {
tuyaDatapoints: [
[2, "co2", tuya.valueConverter.raw],
[18, "temperature", tuya.valueConverter.divideBy10],
[19, "humidity", tuya.valueConverter.raw],
[21, "voc", tuya.valueConverter.divideBy100],
[22, "formaldehyd", tuya.valueConverter.divideBy100],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_it9utkro"]),
model: "PM2.5_airbox",
vendor: "Tuya",
description: "PM2.5 air quality sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.pm25().withValueMin(0).withValueMax(999).withValueStep(1),
e.temperature(),
e.humidity(),
e.co2(),
e.voc().withUnit("ppb"),
e.formaldehyd().withUnit("µg/m³"),
],
meta: {
tuyaDatapoints: [
[2, "co2", tuya.valueConverter.raw],
[18, "temperature", tuya.valueConverter.divideBy10],
[19, "humidity", tuya.valueConverter.divideBy10],
[20, "co2", tuya.valueConverter.raw],
[21, "voc", tuya.valueConverter.divideBy100],
[22, "formaldehyd", tuya.valueConverter.divideBy100],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_rbbx5mfq", "_TZE204_rbbx5mfq"]),
model: "TS0601_illuminance_temperature_humidity_sensor_2",
vendor: "Tuya",
description: "Illuminance sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [e.illuminance(), e.temperature(), 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", "_TZE204_dwcarsat", "_TZE200_blfcpsxz"]),
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("ppb"),
e.formaldehyd().withUnit("µg/m³"),
e.pm25().withValueMin(0).withValueMax(999).withValueStep(1),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_ogkdpgy2", "_TZE200_3ejwxpmu", "_TZE204_3ejwxpmu"]),
model: "TS0601_temperature_humidity_co2_sensor",
vendor: "Tuya",
description: "CO2 sensor",
fromZigbee: [legacy.fromZigbee.tuya_air_quality],
toZigbee: [],
exposes: [e.temperature(), e.humidity(), e.co2()],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_ogkdpgy2"]),
model: "TS0601_co2_sensor",
vendor: "Tuya",
description: "NDIR co2 sensor",
fromZigbee: [legacy.fromZigbee.tuya_air_quality],
toZigbee: [],
exposes: [e.co2()],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_pkpfn9hc"]),
model: "TS0601_co2_temperature_humidity_sensor",
vendor: "Tuya",
description: "CO2/temperature/humidity sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true, queryOnConfigure: true, queryOnDeviceAnnounce: true })],
exposes: [e.co2(), e.temperature(), e.humidity()],
meta: {
tuyaDatapoints: [
[2, "co2", tuya.valueConverter.raw],
[18, "temperature", tuya.valueConverter.divideBy10],
[19, "humidity", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_7bztmfm1", "_TZE204_7bztmfm1"]),
model: "DCR-CO",
vendor: "Tuya",
description: "Smart air box (carbon monoxide)",
fromZigbee: [legacy.fromZigbee.tuya_CO],
toZigbee: [],
exposes: [e.binary("carbon_monoxide", ea.STATE, "ON", "OFF").withDescription("Indicates if CO (carbon monoxide) is detected"), e.co()],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_ggev5fsl", "_TZE200_u319yc66", "_TZE200_kvpwq8z7", "_TZE204_kvpwq8z7"]),
model: "TS0601_gas_sensor_1",
vendor: "Tuya",
description: "Gas sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
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],
[16, "silence", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE200_yojqa8xn",
"_TZE204_zougpkpy",
"_TZE204_chbyv06x",
"_TZE204_yojqa8xn",
"_TZE284_chbyv06x",
"_TZE28C1000000_chbyv06x",
]),
model: "TS0601_gas_sensor_2",
vendor: "Tuya",
description: "Gas sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.gas(),
tuya.exposes.gasValue().withUnit("LEL"),
tuya.exposes.selfTest(),
tuya.exposes.selfTestResult(),
tuya.exposes.silence(),
e
.enum("alarm_ringtone", ea.STATE_SET, ["melody_1", "melody_2", "melody_3", "melody_4", "melody_5"])
.withDescription("Ringtone of the alarm"),
e.numeric("alarm_time", ea.STATE_SET).withValueMin(1).withValueMax(180).withValueStep(1).withUnit("s").withDescription("Alarm time"),
e.binary("preheat", ea.STATE, true, false).withDescription("Indicates sensor preheat is active"),
],
whiteLabel: [
tuya.whitelabel("DYGSM", "DY-RQ500A", "Gas sensor", [
"_TZE204_zougpkpy",
"_TZE204_chbyv06x",
"_TZE284_chbyv06x",
"_TZE28C1000000_chbyv06x",
]),
],
meta: {
tuyaDatapoints: [
[1, "gas", tuya.valueConverter.trueFalseEnum0],
[2, "gas_value", tuya.valueConverter.divideBy10],
[
6,
"alarm_ringtone",
tuya.valueConverterBasic.lookup({
melody_1: tuya.enum(0),
melody_2: tuya.enum(1),
melody_3: tuya.enum(2),
melody_4: tuya.enum(3),
melody_5: tuya.enum(4),
}),
],
[7, "alarm_time", tuya.valueConverter.raw],
[8, "self_test", tuya.valueConverter.raw],
[9, "self_test_result", tuya.valueConverter.selfTestResult],
[10, "preheat", tuya.valueConverter.raw],
[13, null, null], // alarm_switch; ignore for now since it is unclear what it does
[16, "silence", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_nus5kk3n"]),
model: "TS0601_gas_sensor_3",
vendor: "Tuya",
description: "Gas sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [e.gas(), tuya.exposes.selfTest(), tuya.exposes.selfTestResult(), tuya.exposes.faultAlarm(), tuya.exposes.silence()],
meta: {
tuyaDatapoints: [
[1, "gas", tuya.valueConverter.trueFalse0],
[9, "self_test_result", tuya.valueConverter.selfTestResult],
[11, "fault_alarm", tuya.valueConverter.trueFalse1],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_mby4kbtq", "_TZE204_mby4kbtq", "_TZE204_uo8qcagc", "_TZE284_uo8qcagc"]),
model: "TS0601_gas_sensor_4", // _TZE200_mby4kbtq looks like TS0601_gas_sensor_2
vendor: "Tuya",
description: "Gas sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.gas(),
tuya.exposes.gasValue().withUnit("LEL"),
e.binary("preheat", ea.STATE, true, false).withDescription("Indicates sensor preheat is active"),
tuya.exposes.faultAlarm(),
e.binary("alarm_switch", ea.STATE_SET, true, false),
tuya.exposes.silence(),
],
meta: {
tuyaDatapoints: [
[1, "gas", tuya.valueConverter.trueFalse0],
[2, "gas_value", tuya.valueConverter.divideBy10],
[10, "preheat", tuya.valueConverter.raw],
[11, "fault_alarm", tuya.valueConverter.trueFalse1],
[13, "alarm_switch", tuya.valueConverter.raw],
[16, "silence", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_ioxkjvuz"]),
model: "GA01",
vendor: "Meian",
description: "Gas sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.gas(),
tuya.exposes.selfTestResult(),
e.binary("preheat", ea.STATE, true, false).withDescription("Indicates sensor preheat is active"),
],
meta: {
tuyaDatapoints: [
[1, "gas", tuya.valueConverter.trueFalse0],
[9, "self_test_result", tuya.valueConverter.selfTestResult],
[16, "preheat", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_v6iczj35"]),
model: "ZB-DG03",
vendor: "Spacetronik",
description: "LPG gas sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true, queryOnConfigure: true })],
exposes: [
e.gas(),
e.binary("preheat", ea.STATE, true, false).withDescription("Sensor preheat active"),
e
.enum("fault", ea.STATE, ["none", "fault", "serious_fault", "sensor_fault", "probe_fault", "power_fault"])
.withDescription("Fault status of the device (none = no fault)"),
e.binary("lifecycle", ea.STATE, true, false).withDescription("Sensor lifetime limit"),
],
meta: {
tuyaDatapoints: [
[1, "gas", tuya.valueConverter.trueFalseEnum0],
[10, "preheat", tuya.valueConverter.trueFalse1],
[
11,
"fault",
tuya.valueConverterBasic.lookup({
none: 0,
fault: 1,
serious_fault: 2,
sensor_fault: 3,
probe_fault: 4,
power_fault: 5,
}),
],
[12, "lifecycle", tuya.valueConverter.trueFalse0],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_hiith90n"]),
model: "R3 Smart Switch",
vendor: "Tuya",
description: "Circuit Breaker/Switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [e.switch()],
meta: {
tuyaDatapoints: [[16, "state", tuya.valueConverter.onOff]],
},
},
{
// Senoro window sensor variant with 3-state opening on DP101.
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_6teua268"]),
model: "Senoro.Win v2",
vendor: "Senoro",
description: "Window sensor with 3-state opening (DP101), optional alarm, battery",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.enum("opening_state", ea.STATE, ["open", "closed", "tilted"]).withDescription("Opening state"),
e.binary("alarm_state", ea.STATE_SET, true, false).withDescription("Alarm state"),
e.binary("setup_mode", ea.STATE_SET, true, false).withDescription("Exit Setup Mode"),
e.binary("alarm_siren", ea.STATE_SET, true, false).withDescription("Alarm siren"),
e
.numeric("alarm_siren_duration", ea.STATE_SET)
.withDescription("Alarm siren duration")
.withValueMin(5)
.withValueMax(180)
.withValueStep(1),
e.numeric("vibration", ea.STATE).withDescription("Value of vibration."),
e.numeric("vibration_limit", ea.STATE_SET).withDescription("Vibration limit").withValueMin(0).withValueMax(100).withValueStep(1),
e.binary("vibration_siren", ea.STATE_SET, true, false).withDescription("Vibration siren"),
e
.numeric("vibration_siren_duration", ea.STATE_SET)
.withDescription("Vibration siren duration")
.withValueMin(5)
.withValueMax(180)
.withValueStep(1),
e.binary("close_signal", ea.STATE_SET, true, false).withDescription("Close signal"),
e.numeric("transmission_power", ea.STATE_SET).withDescription("Transmission power").withValueMin(11).withValueMax(19).withValueStep(1),
e.binary("magnetic_status", ea.STATE, true, false).withDescription("Magnetic status"),
e.battery(),
],
meta: {
tuyaDatapoints: [
[
101,
"opening_state",
tuya.valueConverterBasic.lookup({
open: 0,
closed: 1,
tilted: 2,
}),
],
[16, "alarm_state", tuya.valueConverter.raw],
[107, "setup_mode", tuya.valueConverter.raw],
[103, "alarm_siren", tuya.valueConverter.raw],
[109, "alarm_siren_duration", tuya.valueConverter.raw],
[102, "vibration", tuya.valueConverter.raw],
[106, "vibration_limit", tuya.valueConverter.raw],
[110, "vibration_siren_duration", tuya.valueConverter.raw],
[108, "vibration_siren", tuya.valueConverter.raw],
[104, "close_signal", tuya.valueConverter.raw],
[105, "transmission_power", tuya.valueConverter.raw],
[111, "magnetic_status", tuya.valueConverter.raw],
[2, "battery", tuya.valueConverter.raw],
],
},
},
{
zigbeeModel: ["ZG-301Z", "AY301Z"],
fingerprint: [
...tuya.fingerprint("TS0001", [
"_TZ3000_hktqahrq",
"_TZ3000_q6a3tepg",
"_TZ3000_skueekg3",
"_TZ3000_npzfdcof",
"_TZ3000_5ng23zjs",
"_TZ3000_rmjr4ufz",
"_TZ3000_v7gnj3ad",
"_TZ3000_3a9beq8a",
"_TZ3000_ark8nv4y",
"_TZ3000_mx3vgyea",
"_TZ3000_fdxihpp7",
"_TZ3000_qsp2pwtf",
"_TZ3000_kycczpw8",
"_TZ3000_46t1rvdu",
"_TZ3000_bhcpnvud",
"_TZ3000_i9oy2rdq",
]),
...tuya.fingerprint("TS000F", [
"_TZ3000_hktqahrq",
"_TZ3000_m9af2l6g",
"_TZ3000_mx3vgyea",
"_TZ3000_skueekg3",
"_TZ3000_dlhhrhs8",
"_TZ3000_fdxihpp7",
]),
],
model: "WHD02",
vendor: "Tuya",
whiteLabel: [
{ vendor: "Tuya", model: "iHSW02" },
{
vendor: "HOBEIAN",
model: "ZG-301Z",
fingerprint: [{ modelID: "ZG-301Z" }],
},
{
vendor: "AOYAN",
model: "AY301Z",
description: "Wall switch module",
fingerprint: [{ modelID: "AY301Z" }, { manufacturerName: "AOYAN" }],
},
tuya.whitelabel("Tuya", "QS-zigbee-S08-16A-RF", "Wall switch module", ["_TZ3000_dlhhrhs8"]),
],
description: "Wall switch module",
extend: [tuya.modernExtend.tuyaBase(), tuya.modernExtend.tuyaOnOff({ switchType: true, onOffCountdown: true })],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff"]);
await reporting.onOff(endpoint);
},
},
{
zigbeeModel: ["AY301Z-2CH"],
fingerprint: [{ modelID: "AY301Z-2CH", manufacturerName: "AOYAN" }],
model: "AY301Z-2CH",
vendor: "AOYAN",
description: "2 gang wall switch module",
extend: [
tuya.modernExtend.tuyaBase(),
m.deviceEndpoints({ endpoints: { l1: 1, l2: 2 } }),
tuya.modernExtend.tuyaOnOff({
switchType: true,
onOffCountdown: true,
endpoints: ["l1", "l2"],
}),
],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
for (const endpoint of [device.getEndpoint(1), device.getEndpoint(2)]) {
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff"]);
await reporting.onOff(endpoint);
}
},
},
{
fingerprint: tuya.fingerprint("TS011F", [
"_TZ3000_mvn6jl7x",
"_TZ3000_raviyuvk",
"_TYZB01_hlla45kx",
"_TZ3000_92qd4sqa",
"_TZ3000_zwaadvus",
"_TZ3000_k6fvknrr",
"_TZ3000_6s5dc9lx",
"_TZ3000_helyqdvs",
"_TZ3000_rgpqqmbj",
"_TZ3000_8nyaanzb",
"_TZ3000_iy2c3n6p",
"_TZ3000_46vasa5h",
"_TZ3000_qlmnxmac",
"_TZ3000_sgb0xhwn",
"_TZ3210_ph1joc22",
"_TZ3210_sgb0xhwn",
]),
model: "TS011F_2_gang_wall",
vendor: "Tuya",
description: "2 gang wall outlet",
ota: true,
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
backlightModeLowMediumHigh: true,
childLock: true,
endpoints: ["l1", "l2"],
}),
],
whiteLabel: [
tuya.whitelabel("ClickSmart+", "CMA30036", "2 gang socket outlet", ["_TYZB01_hlla45kx"]),
tuya.whitelabel("Rylike", "RY-WS02Z", "2 gang socket outlet AU", [
"_TZ3000_rgpqqmbj",
"_TZ3000_8nyaanzb",
"_TZ3000_iy2c3n6p",
"_TZ3000_46vasa5h",
"_TZ3000_46vasa5h",
]),
tuya.whitelabel("Nova Digital", "NT-S2", "2 gang socket outlet BR", ["_TZ3000_sgb0xhwn", "_TZ3210_sgb0xhwn"]),
tuya.whitelabel("Nova Digital", "QZ-S2Q", "2 gang socket outlet BR with non-switchable USB", ["_TZ3210_ph1joc22"]),
],
endpoint: (device) => {
return { l1: 1, l2: 2 };
},
meta: { multiEndpoint: true, multiEndpointSkip: ["power_on_behavior"] },
configure: tuya.configureMagicPacket,
},
{
fingerprint: [
...tuya.fingerprint("TS011F", ["_TZ3000_rk2yzt0u", "_TZ3000_o4cjetlm"]),
...tuya.fingerprint("TS0001", [
"_TZ3000_o4cjetlm",
"_TZ3000_iedbgyxt",
"_TZ3000_h3noz0a5",
"_TYZB01_4tlksk8a",
"_TZ3000_5ucujjts",
"_TZ3000_h8ngtlxy",
"_TZ3000_w0ypwa1f",
"_TZ3000_wpueorev",
"_TZ3000_cmcjbqup",
]),
...tuya.fingerprint("TS0011", ["_TYZB01_rifa0wlb"]),
],
model: "ZN231392",
vendor: "Tuya",
description: "Smart water/gas valve",
extend: [tuya.modernExtend.tuyaBase(), tuya.modernExtend.tuyaOnOff({ indicatorMode: true })],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
const endpoint = device.getEndpoint(1);
await endpoint.read("genOnOff", ["onOff", "moesStartUpOnOff"]);
},
},
{
zigbeeModel: ["CK-BL702-AL-01(7009_Z102LG03-1)", "CK-BL702-AL-01(7009_Z102LG04-1)", "CK-BL702-AL-01(7009_Z102LG04-2)"],
model: "CK-BL702-AL-01",
vendor: "Tuya",
description: "Zigbee LED bulb",
toZigbee: [tz.power_on_behavior],
fromZigbee: [fz.power_on_behavior],
extend: [
tuya.modernExtend.tuyaLight({
colorTemp: { range: [142, 500] },
color: true,
}),
],
exposes: [e.power_on_behavior(["off", "on", "toggle", "previous"])],
meta: {
moveToLevelWithOnOffDisable: true,
noOffTransitionWhenOff: true,
},
},
{
zigbeeModel: ["CK-BL702-AL-01(7008_Z102LG01-1)"],
model: "CK-BL702-AL-01(7008_Z102LG01-1)",
vendor: "Tuya",
description: "Zigbee LED bulb",
toZigbee: [tz.power_on_behavior],
fromZigbee: [fz.power_on_behavior],
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaLight({
colorTemp: { range: [142, 500] },
color: true,
}),
],
exposes: [e.power_on_behavior(["off", "on", "toggle", "previous"])],
meta: {
moveToLevelWithOnOffDisable: true,
noOffTransitionWhenOff: true,
},
},
{
zigbeeModel: ["SM0001"],
model: "SM0001",
vendor: "Tuya",
description: "Switch",
extend: [tuya.modernExtend.tuyaBase(), tuya.modernExtend.tuyaOnOff()],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
},
whiteLabel: [tuya.whitelabel("Zemismart", "ZM-H7", "Hand wave wall smart switch", ["_TZ3000_jcqs2mrv"])],
},
{
zigbeeModel: ["TS0505"],
model: "TS0505",
vendor: "Tuya",
description: " GU10 zbeacon Zigbee LED bulb",
extend: [
m.light({
colorTemp: { range: [153, 500] },
color: { modes: ["xy", "hs"] },
}),
],
},
{
zigbeeModel: ["TS0505B"],
model: "TS0505B_1",
vendor: "Tuya",
description: "Zigbee RGB+CCT light",
whiteLabel: [
{ vendor: "Mercator Ikuü", model: "SMD4106W-RGB-ZB" },
{ vendor: "Tuya", model: "A5C-21F7-01" },
{ vendor: "Mercator Ikuü", model: "S9E27LED9W-RGB-Z" },
{ vendor: "Hatsy", model: "SDL-312Z" },
{
vendor: "Aldi",
model: "L122CB63H11A9.0W",
description: "LIGHTWAY smart home LED-lamp - bulb",
},
{
vendor: "Lidl",
model: "14153706L",
description: "Livarno smart LED ceiling light",
},
{
vendor: "Zemismart",
model: "LXZB-ZB-09A",
description: "Zemismart LED Surface Mounted Downlight 9W RGBW",
},
{
vendor: "Feconn",
model: "FE-GU10-5W",
description: "Zigbee GU10 5W smart bulb",
},
{ vendor: "Nedis", model: "ZBLC1E14" },
{ vendor: "Emos", model: "GoSmart ZQZ516R" },
{ vendor: "Emos", model: "GoSmart ZQZ322R" },
tuya.whitelabel("Aldi", "L122FF63H11A5.0W", "LIGHTWAY smart home LED-lamp - spot", ["_TZ3000_j0gtlepx"]),
tuya.whitelabel("Aldi", "L122AA63H11A6.5W", "LIGHTWAY smart home LED-lamp - candle", ["_TZ3000_iivsrikg"]),
tuya.whitelabel("Aldi", "C422AC11D41H140.0W", "MEGOS LED panel RGB+CCT 40W 3600lm 62 x 62 cm", ["_TZ3000_v1srfw9x"]),
tuya.whitelabel("Aldi", "C422AC14D41H140.0W", "MEGOS LED panel RGB+CCT 40W 3600lm 30 x 120 cm", ["_TZ3000_gb5gaeca"]),
tuya.whitelabel("Lidl", "14156506L", "Livarno Lux smart LED mood light", ["_TZ3210_r0xgkft5"]),
tuya.whitelabel("Lidl", "HG08010", "Livarno Home outdoor spotlight", ["_TZ3210_umi6vbsz"]),
tuya.whitelabel("Lidl", "HG08008", "Livarno Home LED ceiling light", ["_TZ3210_p9ao60da"]),
tuya.whitelabel("Lidl", "14158704L", "Livarno Home LED floor lamp, RGBW", ["_TZ3210_z1vlyufu"]),
tuya.whitelabel("Lidl", "14158804L", "Livarno Home LED desk lamp RGBW", ["_TZ3210_hxtfthp5"]),
tuya.whitelabel("Lidl", "HG07834A/HG09155A/HG08131A", "Livarno Home GU10 spot RGB+CCT", ["_TZ3000_quqaeew6"]),
tuya.whitelabel("Lidl", "HG07834B/HG09155B/HG08131B", "Livarno Home E14 candle RGB+CCT", ["_TZ3000_th6zqqy6"]),
tuya.whitelabel("Lidl", "HG07834B", "Livarno Home E14 candle RGB+CCT", ["_TZ3000_wr6g6olr"]),
tuya.whitelabel("Lidl", "HG08131C", "Livarno Home outdoor E27 bulb in set with flare", ["_TZ3000_q50zhdsc"]),
tuya.whitelabel("Lidl", "HG07834C/HG09155C/HG08131C", "Livarno Home E27 bulb RGB+CCT", ["_TZ3000_qd7hej8u"]),
tuya.whitelabel("Lidl", "HG08383B", "Livarno outdoor LED light chain", ["_TZ3000_bwlvyjwk"]),
tuya.whitelabel("Lidl", "HG08383A", "Livarno outdoor LED light chain", ["_TZ3000_taspddvq"]),
tuya.whitelabel("Garza Smart", "Garza-Standard-A60", "Standard A60 bulb", ["_TZ3210_sln7ah6r"]),
tuya.whitelabel("UR Lighting", "TH008L10RGBCCT", "10W RGB+CCT downlight", ["_TZ3210_dn5higyl", "_TZ3210_hicxa0rh"]),
tuya.whitelabel("Lidl", "HG08007", "Livarno Home outdoor LED band", ["_TZ3210_zbabx9wh"]),
tuya.whitelabel("Lidl", "399629_2110", "Livarno Lux Ceiling Panel RGB+CCT", ["_TZ3210_c0s1xloa", "_TZ3210_x13bu7za"]),
tuya.whitelabel("Nous", "P3Z", "E27 RGB light bulb", ["_TZ3210_cieijuw1"]),
tuya.whitelabel("Nous", "P4Z", "E14 RGB candle bulb", ["_TZ3210_htdm5hvw"]),
tuya.whitelabel("Nous", "P8Z", "GU10 RGB spot bulb", ["_TZ3210_r3wubmyh"]),
tuya.whitelabel("Moes", "ZLD-RCW_1", "RGB+CCT Zigbee LED controller", ["_TZ3000_7hcgjxpc"]),
tuya.whitelabel("Moes", "ZB-TD5-RCW-GU10", "RGB+CCT 4.7W GU10 LED bulb", ["_TZ3210_rcggc0ys", "_TZ3210_ljoasixl"]),
tuya.whitelabel("Moes", "ZB-TDA9-RCW-E27-MS", "RGB+CCT 9W E27 LED bulb", ["_TZ3210_wxa85bwk"]),
tuya.whitelabel("Moes", "ZB-TDA14-RCW-E27-MS", "RGB+CCT 14W E27 LED bulb", ["_TZ3210_dkul5xix"]),
tuya.whitelabel("Moes", "ZB-LZD10-RCW", "10W RGB+CCT Smart Downlight", [
"_TZ3210_s9lumfhn",
"_TZ3210_jjqdqxfq",
"_TZ3210_dwzfzfjc",
"_TZ3210_wbsgmojq",
]),
tuya.whitelabel("Moes", "ZB-TDC6-RCW-E14", "RGB+CCT 5W E14 LED bulb", ["_TZ3210_ifga63rg"]),
tuya.whitelabel("Moes", "ZB-TDD6-RCW-4", "RGB+CCT 6W Smart Downlight", ["_TZ3210_b8jdosxo"]),
tuya.whitelabel("Moes", "ZB-TD6-RCW-GX53-MS", "ZigBee Smart LED Lamp GX53 Light Bulb 6W Color Changing Dimmable Music Sync", [
"_TZ3210_qigbovcq",
]),
tuya.whitelabel("MiBoxer", "E3-ZR", "3 in 1 LED Controller", ["_TZB210_wy1pyu1q", "_TZB210_yatkpuha"]),
tuya.whitelabel("MiBoxer", "SZ5", "5 in 1 LED Controller", ["_TZB210_w9hcix2r"]),
tuya.whitelabel("MiBoxer", "FUT037Z+", "RGB led controller", ["_TZB210_417ikxay", "_TZB210_wxazcmsh", "_TZB210_u3ri0968"]),
tuya.whitelabel("MiBoxer", "FUT039Z", "RGB+CCT LED controller", ["_TZ3210_jicmoite", "_TZB210_rs0ufzwg"]),
tuya.whitelabel("MiBoxer", "FUT066Z", "RGB+CCT LED Downlight", ["_TZ3210_zrvxvydd"]),
tuya.whitelabel("MiBoxer", "FUT068ZR", "RGB+CCT LED Downlight", ["_TZB210_endmggws"]),
tuya.whitelabel("MiBoxer", "FUT103ZR", "GU10 RGB+CCT", ["_TZB210_6eed09b9"]),
tuya.whitelabel("MiBoxer", "FUT105ZR", "RGB+CCT LED bulb", ["_TZB210_uoiqhjqe"]),
tuya.whitelabel("MiBoxer", "FUT106ZR", "GU10 RGB+CCT", ["_TZB210_rwy5hexp", "_TZB210_lnnkh3f9"]),
tuya.whitelabel("Tuya", "TS0505B_1_1", "Zigbee 3.0 18W led light bulb E27 RGBCW", [
"_TZ3210_jd3z4yig",
"_TZ3210_r5afgmkl",
"_TZ3210_mja6r5ix",
]),
tuya.whitelabel("MiBoxer", "FUTC11ZR", "Outdoor light", ["_TZB210_zmppwawa"]),
tuya.whitelabel("TechToy", "_TZ3210_iw0zkcu8", "Smart bulb RGB 9W E27", ["_TZ3210_iw0zkcu8"]),
tuya.whitelabel("LUUMR", "10010128", "Smart LED, GU10, 4,7W, RGBW, CCT, Tuya, WLAN, mat", ["_TZ3210_sw9uxoea"]),
tuya.whitelabel("KOJIMA", "GX53-RGB-WW-CW-7W-ZGB", "Smart RGB LED Lamp GX53 7W", ["_TZ3210_b3kiq1i0"]),
],
extend: [
tuya.modernExtend.tuyaLight({
colorTemp: { range: [153, 500] },
color: true,
}),
],
// https://github.com/Koenkk/zigbee2mqtt/issues/30584
meta: {
applyRedFix: true, // https://github.com/Koenkk/zigbee-herdsman-converters/issues/11467
moveToLevelWithOnOffDisable: (e) => e.getDevice().manufacturerName === "_TZB210_uoiqhjqe" ||
e.getDevice().manufacturerName === "_TZB210_417ikxay" ||
e.getDevice().manufacturerName === "_TZB210_qzsxaqqe" ||
e.getDevice().manufacturerName === "_TZB210_u3ri0968" || // https://github.com/Koenkk/zigbee2mqtt/issues/31733
e.getDevice().manufacturerName === "_TZB210_rs0ufzwg" || // https://github.com/Koenkk/zigbee2mqtt/issues/31872
e.getDevice().manufacturerName === "_TZ3210_mja6r5ix", // https://github.com/Koenkk/zigbee2mqtt/issues/32428
},
toZigbee: [tzLocal.TS0505B_1_transitionFixesOnOffBrightness],
configure: (device, coordinatorEndpoint) => {
device.getEndpoint(1).saveClusterAttributeKeyValue("lightingColorCtrl", {
colorCapabilities: 29,
});
},
},
{
fingerprint: tuya.fingerprint("TS0505B", ["_TZ3210_iystcadi", "_TZ3210_it1u8ahz"]),
model: "TS0505B_2",
vendor: "Tuya",
description: "Zigbee RGB+CCT light",
whiteLabel: [
tuya.whitelabel("Lidl", "14149505L/14149506L_2", "Livarno Lux light bar RGB+CCT (black/white)", ["_TZ3210_iystcadi"]),
tuya.whitelabel("Tuya", "TS0505B_2_2", "Zigbee GU10/E14 5W smart bulb", ["_TZ3210_it1u8ahz"]),
],
extend: [tuya.modernExtend.tuyaLight({ colorTemp: { range: [153, 500] }, color: { modes: ["hs"], applyRedFix: true } })],
// https://github.com/Koenkk/zigbee2mqtt/issues/32204
toZigbee: [tz.on_off, tuya.tz.led_control],
fromZigbee: [fz.on_off, tuya.fz.led_controller, fz.brightness],
configure: (device, coordinatorEndpoint) => {
device.getEndpoint(1).saveClusterAttributeKeyValue("lightingColorCtrl", { colorCapabilities: 29 });
},
},
{
fingerprint: tuya.fingerprint("TS0505B", ["_TZB210_3zfp8mki", "_TZB210_gj0ccsar", "_TZ3210_jaap6jeb"]),
model: "TS0505B_3",
vendor: "Tuya",
description: "Zigbee RGB+CCT light",
whiteLabel: [
tuya.whitelabel("Skydance", "WZ5_dim_2", "Zigbee & RF 5 in 1 LED controller (DIM mode)", ["_TZB210_3zfp8mki"]),
tuya.whitelabel("QA", "QADZC5", "5 in 1 LED controller", ["_TZB210_gj0ccsar"]),
tuya.whitelabel("LEDEPLY", "SG45-E26", "Zigbee A15 5W Smart Bulbs", ["_TZ3210_jaap6jeb"]),
],
extend: [
m.light({
colorTemp: { range: [153, 500] },
color: { modes: ["hs"], applyRedFix: true, enhancedHue: false },
}),
],
configure: (device, coordinatorEndpoint) => {
device.getEndpoint(1).saveClusterAttributeKeyValue("lightingColorCtrl", {
colorCapabilities: 29,
});
},
},
{
fingerprint: tuya.fingerprint("TS0505B", ["_TZ3210_bfwvfyx1"]),
model: "CK-BL702-AL-01_1",
vendor: "eWeLink",
description: "Zigbee 3.0 15W led light bulb E27 RGBCW",
toZigbee: [tuya.tz.do_not_disturb],
extend: [
m.light({
colorTemp: { range: [25, 1000], startup: true },
effect: true,
powerOnBehavior: true,
moveToLevelWithOnOffDisable: true,
color: { modes: ["hs", "xy"], enhancedHue: false },
}),
],
exposes: [tuya.exposes.doNotDisturb()],
configure: (device, coordinatorEndpoint) => {
device.getEndpoint(1).saveClusterAttributeKeyValue("lightingColorCtrl", { colorCapabilities: 29 });
},
},
{
zigbeeModel: ["TS0503B"],
model: "TS0503B",
vendor: "Tuya",
description: "Zigbee RGB light",
whiteLabel: [{ vendor: "BTF-Lighting", model: "C03Z" }, tuya.whitelabel("MiBoxer", "FUT037Z", "RGB led controller", ["_TZ3210_778drfdt"])],
extend: [tuya.modernExtend.tuyaLight({ color: true })],
},
{
zigbeeModel: ["TS0504B"],
model: "TS0504B",
vendor: "Tuya",
description: "Zigbee RGBW light",
toZigbee: [tzLocal.TS0504B_color],
extend: [tuya.modernExtend.tuyaLight({ color: true })],
},
{
zigbeeModel: ["TS0501A"],
model: "TS0501A",
description: "Zigbee light",
vendor: "Tuya",
extend: [tuyaLight()],
whiteLabel: [
tuya.whitelabel("Lidl", "HG06463A", "Livarno Lux E27 ST64 filament bulb", ["_TZ3000_j2w1dw29"]),
tuya.whitelabel("Lidl", "HG06463B", "Livarno Lux E27 G95 filament bulb", ["_TZ3000_nosnx7im"]),
tuya.whitelabel("Lidl", "HG06462A", "Livarno Lux E27 A60 filament bulb", ["_TZ3000_7dcddnye", "_TZ3000_nbnmw9nc"]),
],
},
{
fingerprint: tuya.fingerprint("TS0501B", ["_TZB210_rkgngb5o"]),
model: "TS0501B_dimmer",
description: "Zigbee dimmer",
vendor: "Tuya",
extend: [tuyaLight({ colorTemp: { range: [153, 500] }, configureReporting: true, effect: false })],
whiteLabel: [tuya.whitelabel("Tuya", "L1(ZW)", "Light dimmer 0-10V", ["_TZB210_rkgngb5o"])],
},
{
fingerprint: tuya.fingerprint("TS0501B", ["_TZB210_g01ie5wu"]),
model: "TS0501B_dimmer_2",
description: "Zigbee dimmer",
vendor: "Tuya",
extend: [tuyaLight({ minBrightness: "command", effect: false })],
},
{
zigbeeModel: ["TS0501B"],
model: "TS0501B",
description: "Zigbee light",
vendor: "Tuya",
extend: [
tuya.modernExtend.tuyaLight({
doNotDisturb: true,
}),
],
whiteLabel: [
tuya.whitelabel("MiBoxer", "FUT036Z", "Single color LED controller", ["_TZ3210_dxroobu3", "_TZ3210_dbilpfqk"]),
tuya.whitelabel("Mercator Ikuü", "SMFL20W-ZB", "Ridley Floodlight", ["_TZ3000_juq7i1fr"]),
],
meta: {
moveToLevelWithOnOffDisable: (e) => e.getDevice().manufacturerName === "_TZ3218_ofguu6mz",
},
},
{
fingerprint: tuya.fingerprint("TS0202", ["_TYZB01_vwqnz1sn"]),
model: "TS0202_3",
vendor: "Tuya",
description: "Motion detector with illuminance",
fromZigbee: [fz.ias_occupancy_alarm_1, fz.battery, fz.ias_occupancy_alarm_1_report],
toZigbee: [],
extend: [m.illuminance(), tuya.modernExtend.tuyaBase()],
exposes: [e.occupancy(), e.battery_low(), e.battery(), e.tamper()],
},
{
fingerprint: tuya.fingerprint("TS0202", ["_TZ3210_cwamkvua"]),
model: "TS0202_2",
vendor: "Tuya",
description: "Motion sensor with scene switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
fromZigbee: [fz.ias_occupancy_alarm_1, fz.battery],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.batteryPercentageRemaining(endpoint);
await reporting.batteryVoltage(endpoint);
},
exposes: [
e.battery(),
e.battery_voltage(),
e.occupancy(),
e.action(["single", "double", "hold"]),
e.enum("light", ea.STATE, ["dark", "bright"]),
],
meta: {
tuyaDatapoints: [
[102, "light", tuya.valueConverterBasic.lookup({ dark: false, bright: true })],
[101, "action", tuya.valueConverterBasic.lookup({ single: 0, double: 1, hold: 2 })],
],
},
whiteLabel: [{ vendor: "Linkoze", model: "LKMSZ001" }],
},
{
fingerprint: tuya.fingerprint("TS0202", ["_TYZB01_jytabjkb", "_TZ3000_lltemgsf", "_TYZB01_5nr7ncpl", "_TZ3000_mg4dy6z6", "_TZ3000_bsvqrxru"]),
model: "TS0202_1",
vendor: "Tuya",
description: "Motion sensor",
// Requires alarm_1_with_timeout https://github.com/Koenkk/zigbee2mqtt/issues/2818#issuecomment-776119586
fromZigbee: [fz.ias_occupancy_alarm_1_with_timeout, fz.battery],
toZigbee: [],
exposes: [e.occupancy(), e.battery_low(), e.battery(), e.battery_voltage()],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genPowerCfg"]);
await reporting.batteryPercentageRemaining(endpoint);
},
},
{
fingerprint: tuya.fingerprint("WHD02", ["_TZ3000_hktqahrq"]),
zigbeeModel: ["TS0202"],
model: "TS0202",
vendor: "Tuya",
description: "Motion sensor",
whiteLabel: [
{ vendor: "Mercator Ikuü", model: "SMA02P" },
{ vendor: "Tuya", model: "TY-ZPR06" },
{ vendor: "Tesla Smart", model: "TS0202" },
tuya.whitelabel("MiBoxer", "PIR1-ZB", "PIR sensor", ["_TZ3040_wqmtjsyk"]),
tuya.whitelabel("Tuya", "ZMS01", "Motion sensor", ["_TZ3000_otvn3lne"]),
tuya.whitelabel("Nous", "E2", "Motion sensor", ["_TZ3000_h4wnrtck"]),
tuya.whitelabel("Tuya", "809WZT", "Motion sensor", ["_TZ3040_bb6xaihh"]),
tuya.whitelabel("Niceboy", "ORBIS Motion Sensor", "Motion sensor", ["_TZ3000_qomxlryd"]),
tuya.whitelabel("Luminea", "ZX-5311", "Motion sensor", ["_TZ3000_jmrgyl7o"]),
tuya.whitelabel("Tuya", "ZP01", "Motion sensor", ["_TZ3000_lf56vpxj", "_TZ3040_o4mkahkc"]),
tuya.whitelabel("Tuya", "HW500A", "Motion sensor", ["_TZ3000_bsvqrxru"]),
tuya.whitelabel("Nedis", "ZBSM10WT", "Motion sensor", ["_TZ3000_nss8amz9"]),
],
fromZigbee: [fz.ias_occupancy_alarm_1, fz.battery, fz.ias_occupancy_alarm_1_report],
toZigbee: [],
exposes: (device, options) => {
const exps = [e.occupancy(), e.battery_low(), e.battery(), e.battery_voltage()];
if (!device || !["_TZ3000_bsvqrxru", "_TZ3000_nss8amz9"].includes(device.manufacturerName)) {
exps.push(e.tamper());
}
return exps;
},
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genPowerCfg"]);
try {
await reporting.batteryPercentageRemaining(endpoint);
await reporting.batteryVoltage(endpoint);
}
catch {
/* Fails for some https://github.com/Koenkk/zigbee2mqtt/issues/13708 */
}
},
},
{
fingerprint: tuya.fingerprint("TS0202", ["_TZ3040_fwxuzcf4", "_TZ3040_msl6wxk9"]),
model: "ZM-35H-Q",
vendor: "Tuya",
description: "Motion sensor",
fromZigbee: [fz.ias_occupancy_alarm_1, fz.battery, fzLocal.ZM35HQ_attr, legacy.fromZigbee.ZM35HQ_battery],
toZigbee: [tzLocal.ZM35HQ_attr],
extend: [m.quirkCheckinInterval(15000)],
exposes: [
e.occupancy(),
e.battery_low(),
e.battery(),
e.enum("sensitivity", ea.ALL, ["low", "medium", "high"]).withDescription("PIR sensor sensitivity"),
e.enum("keep_time", ea.ALL, [30, 60, 120]).withDescription("PIR keep time in seconds"),
],
configure: tuya.configureMagicPacket,
whiteLabel: [tuya.whitelabel("Aubess", "40ZH-O", "Motion sensor", ["_TZ3040_msl6wxk9"])],
},
{
zigbeeModel: ["ZG-204Z"],
fingerprint: tuya.fingerprint("TS0202", ["_TZ3000_mcxw5ehu", "_TZ3000_6ygjfyll", "_TZ3040_6ygjfyll", "_TZ3000_msl6wxk9"]),
model: "IH012-RT01",
vendor: "Tuya",
description: "Motion sensor",
fromZigbee: [fzLocal.ZM35HQ_attr, fz.battery],
toZigbee: [tzLocal.ZM35HQ_attr],
extend: [
m.quirkCheckinInterval(15000),
// Occupancy reporting interval is 60s, so allow for one dropped update plus a small safety margin of 5s
m.iasZoneAlarm({
zoneType: "occupancy",
zoneAttributes: ["alarm_1", "battery_low"],
// No keepAlivetimeout for ZG-204Z
// https://github.com/Koenkk/zigbee2mqtt/issues/30676
keepAliveTimeout: (d) => (d.modelID === "ZG-204Z" ? 0 : 125),
}),
],
exposes: [
e.battery(),
e.battery_voltage(),
e.enum("sensitivity", ea.ALL, ["low", "medium", "high"]).withDescription("PIR sensor sensitivity"),
e.enum("keep_time", ea.ALL, [30, 60, 120]).withDescription("PIR keep time in seconds"),
],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genPowerCfg"]);
await reporting.batteryPercentageRemaining(endpoint);
await reporting.batteryVoltage(endpoint);
},
whiteLabel: [
{
vendor: "HOBEIAN",
model: "ZG-204Z",
fingerprint: [{ modelID: "ZG-204Z" }],
},
tuya.whitelabel("Tuya", "ZMS-102", "Motion sensor", ["_TZ3000_msl6wxk9"]),
],
},
{
fingerprint: tuya.fingerprint("TS0202", ["_TZ3000_o4mkahkc"]),
model: "IH012-RT02",
vendor: "Tuya",
description: "Motion sensor",
fromZigbee: [fz.ias_occupancy_alarm_1, fzLocal.ZM35HQ_attr, fz.battery],
toZigbee: [tzLocal.ZM35HQ_attr],
extend: [m.quirkCheckinInterval(15000)],
exposes: [
e.occupancy(),
e.battery_low(),
e.tamper(),
e.battery(),
e.battery_voltage(),
e.enum("sensitivity", ea.ALL, ["low", "medium", "high"]).withDescription("PIR sensor sensitivity"),
e.enum("keep_time", ea.ALL, [30, 60, 120]).withDescription("PIR keep time in seconds"),
],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genPowerCfg"]);
await reporting.batteryPercentageRemaining(endpoint);
await reporting.batteryVoltage(endpoint);
},
},
{
fingerprint: [
...tuya.fingerprint("TS0207", [
"_TZ3000_m0vaazab",
"_TZ3000_ufttklsz",
"_TZ3000_nkkl7uzv",
"_TZ3000_misw04hq",
"_TZ3000_nlsszmzl",
"_TZ3000_gszjt2xx",
"_TZ3000_wlquqiiz",
"_TZ3000_5k5vh43t",
"_TZ3000_kxlmv9ag",
"_TZ3000_wmlc9p9z",
"_TZ3000_shopg9ss",
"_TZ3000_n0lphcok",
"_TZ3000_r80pzsb9",
"_TZ3000_sgpbz53b",
]),
...tuya.fingerprint("TS0001", ["_TZ3000_n0lphcok", "_TZ3000_wn65ixz9", "_TZ3000_trdx8uxs", "_TZ3000_gdsvhfao"]),
],
model: "TS0207_repeater",
vendor: "Tuya",
description: "Repeater",
fromZigbee: [fz.linkquality_from_basic],
toZigbee: [],
whiteLabel: [
tuya.whitelabel("Tuya", "TS0001_repeater", "Zigbee signal repeater", [
"_TZ3000_n0lphcok",
"_TZ3000_wn65ixz9",
"_TZ3000_trdx8uxs",
"_TZ3000_gdsvhfao",
]),
],
exposes: [],
},
{
zigbeeModel: ["TS0207", "FNB54-WTS08ML1.0", "ZG-222Z", "AY222Z"],
model: "TS0207_water_leak_detector",
vendor: "Tuya",
description: "Water leak detector",
fromZigbee: [fz.ias_water_leak_alarm_1, fz.battery],
whiteLabel: [
{ vendor: "CR Smart Home", model: "TS0207" },
{
vendor: "HOBEIAN",
model: "ZG-222Z",
fingerprint: [{ modelID: "ZG-222Z" }],
},
{
vendor: "AOYAN",
model: "AY222Z",
description: "Water leak detector",
fingerprint: [
{ modelID: "AY222Z", manufacturerName: "AOYAN" },
{ modelID: "AY222Z", manufacturerName: "AOYAN " },
],
},
tuya.whitelabel("Meian", "SW02", "Water leak sensor", ["_TZ3000_kyb656no"]),
tuya.whitelabel("Aubess", "IH-K665", "Water leak sensor", ["_TZ3000_kstbkt6a"]),
tuya.whitelabel("HOBEIAN", "ZG-222ZA", "Water leak sensor", ["_TZ3000_k4ej3ww2", "_TZ3000_abaplimj"]),
tuya.whitelabel("Tuya", "_TZ3000_mqiev3jk", "Solar powered rain sensor", ["_TZ3000_mqiev3jk"]),
tuya.whitelabel("Tuya", "TS0207_water_leak_detector_1", "Zigbee water flood sensor + 1m probe cable", [
"_TZ3000_ocjlo4ea",
"_TZ3000_upgcbody",
]),
tuya.whitelabel("Tuya", "TS0207_water_leak_detector_3", "Zigbee water leak sensor", ["_TYZB01_sqmd19i1"]),
tuya.whitelabel("Moes", "ZSS-QY-WL-C-MS", "Zigbee water leak sensor", ["_TZ3000_t6jriawg"]),
tuya.whitelabel("Tuya", "899WZ", "Water leak detector with 80DB Alarm", ["_TZ3000_mugyhz0q"]),
tuya.whitelabel("Niceboy", "ORBIS Water Sensor", "Water leak sensor", ["_TZ3000_awvmkayh"]),
tuya.whitelabel("Nous", "E4", "Water Leakage Sensor)", ["_TZ3000_0s9gukzt"]),
],
toZigbee: [],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genPowerCfg"]);
await reporting.batteryPercentageRemaining(endpoint);
},
exposes: (device, options) => {
const exps = [e.water_leak(), e.battery_low(), e.battery()];
const noTamperModels = [
// manufacturerName for models without a tamper sensor
"_TZ3000_mugyhz0q", // Tuya 899WZ
"_TZ3000_k4ej3ww2", // Aubess IH-K665
"_TZ3000_kstbkt6a", // Aubess IH-K665
"_TZ3000_upgcbody", // Zigbee water leak sensor
"_TYZB01_ttvdudvx", // Zigbee water leak sensor
];
if (!device || !noTamperModels.includes(device.manufacturerName)) {
exps.push(e.tamper());
}
return exps;
},
},
{
fingerprint: tuya.fingerprint("TS0207", ["_TZ3210_tgvtvdoc", "_TZ3210_p68kms0l"]),
model: "RB-SRAIN01",
vendor: "Tuya",
description: "Solar rain sensor",
fromZigbee: [tuya.fz.datapoints],
extend: [m.iasZoneAlarm({ zoneType: "rain", zoneAttributes: ["alarm_1"] }), m.battery({ percentageReporting: true })],
exposes: [
e.illuminance_raw().withUnit("mV"),
e.numeric("illuminance_average_20min", ea.STATE).withUnit("mV").withDescription("Illuminance average for the last 20 minutes"),
e.numeric("illuminance_maximum_today", ea.STATE).withUnit("mV").withDescription("Illuminance maximum for the last 24 hours"),
e.binary("cleaning_reminder", ea.STATE, true, false).withDescription("Cleaning reminder"),
e.numeric("rain_intensity", ea.STATE).withUnit("mV").withDescription("Rainfall intensity"),
],
meta: {
tuyaDatapoints: [
[4, "battery", tuya.valueConverter.raw],
[101, "illuminance_raw", tuya.valueConverter.raw],
[102, "illuminance_average_20min", tuya.valueConverter.raw],
[103, "illuminance_maximum_today", tuya.valueConverter.raw],
[104, "cleaning_reminder", tuya.valueConverter.trueFalse1],
[105, "rain_intensity", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0101", ["_TYZB01_ijihzffk", "_TZ3210_tfxwxklq", "_TZ3210_2dfy6tol"]),
model: "TS0101",
vendor: "Tuya",
description: "Zigbee Socket",
whiteLabel: [
{ vendor: "Larkkey", model: "PS080" },
{ vendor: "Mercator Ikuü", model: "SPBS01G" },
tuya.whitelabel("Mercator Ikuü", "SISW01", "Inline Switch", ["_TZ3210_2dfy6tol"]),
],
extend: [tuya.modernExtend.tuyaBase(), tuya.modernExtend.tuyaOnOff()],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff"]);
await reporting.onOff(endpoint);
},
},
{
fingerprint: tuya.fingerprint("TS0108", ["_TYZB01_7yidyqxd"]),
model: "TS0108",
vendor: "Tuya",
description: "Socket with 2 USB",
whiteLabel: [{ vendor: "Larkkey", model: "PS580" }],
extend: [tuya.modernExtend.tuyaBase(), tuya.modernExtend.tuyaOnOff()],
exposes: [e.switch().withEndpoint("l1"), e.switch().withEndpoint("l2")],
endpoint: (device) => {
return { l1: 1, l2: 7 };
},
meta: { multiEndpoint: true, disableDefaultResponse: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(7), coordinatorEndpoint, ["genOnOff"]);
},
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE200_myd45weu",
"_TZE200_ga1maeof",
"_TZE200_2se8efxh",
"_TZE204_myd45weu",
"_TZE284_myd45weu",
"_TZE284_oitavov2",
"_TZE284_2nhqasjh",
"_TZE284_2se8efxh",
"_TZE200_9cqcpkgb",
]),
model: "TS0601_soil",
vendor: "Tuya",
description: "Soil sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [e.temperature(), e.soil_moisture(), tuya.exposes.temperatureUnit(), e.battery(), tuya.exposes.batteryState()],
meta: {
tuyaDatapoints: [
[3, "soil_moisture", tuya.valueConverter.raw],
[5, "temperature", tuya.valueConverter.raw],
[9, "temperature_unit", tuya.valueConverter.temperatureUnit],
[14, "battery_state", tuya.valueConverter.batteryState],
[15, "battery", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_g2e6cpnw", "_TZE284_sgabhwa6"]),
model: "TS0601_soil_2",
vendor: "Tuya",
description: "Soil sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.soil_moisture(),
e.numeric("temperature", ea.STATE).withUnit("°C").withValueMin(-10).withValueMax(60).withDescription("Soil temperature"),
e.numeric("temperature_f", ea.STATE).withUnit("°F").withValueMin(14).withValueMax(140).withDescription("Soil temperature"),
e
.numeric("temperature_sensitivity", ea.STATE_SET)
.withUnit("°C")
.withValueMin(0.3)
.withValueMax(1)
.withValueStep(0.1)
.withDescription("Temperature sensitivity"),
e.numeric("humidity_sensitivity", ea.STATE_SET).withUnit("%").withValueMin(1).withValueMax(5).withDescription("Humidity sensitivity"),
e.enum("temperature_alarm", ea.STATE, ["lower_alarm", "upper_alarm", "cancel"]).withDescription("Temperature alarm state"),
e.enum("humidity_alarm", ea.STATE, ["lower_alarm", "upper_alarm", "cancel"]).withDescription("Humidity alarm state"),
e
.numeric("max_temperature_alarm", ea.STATE_SET)
.withUnit("°C")
.withValueMin(0)
.withValueMax(60)
.withDescription("Upper temperature limit"),
e
.numeric("min_temperature_alarm", ea.STATE_SET)
.withUnit("°C")
.withValueMin(0)
.withValueMax(60)
.withDescription("Lower temperature limit"),
e.numeric("max_humidity_alarm", ea.STATE_SET).withUnit("%").withValueMin(0).withValueMax(100).withDescription("Upper humidity limit"),
e.numeric("min_humidity_alarm", ea.STATE_SET).withUnit("%").withValueMin(0).withValueMax(100).withDescription("Lower humidity limit"),
e.numeric("schedule_periodic", ea.STATE_SET).withUnit("min").withValueMin(5).withValueMax(60).withDescription("Report sensitivity"),
e.battery(),
tuya.exposes.batteryState(),
],
meta: {
tuyaDatapoints: [
[3, "soil_moisture", tuya.valueConverter.raw],
[5, "temperature", tuya.valueConverter.divideBy10],
// [9, "temp_unit_convert", tuya.valueConverter.temperatureUnitEnum],
[14, "battery_state", tuya.valueConverter.batteryState],
[15, "battery", tuya.valueConverter.raw],
[101, "temperature_alarm", tuya.valueConverter.temperature_humidity_alarm],
[102, "humidity_alarm", tuya.valueConverter.temperature_humidity_alarm],
[103, "max_temperature_alarm", tuya.valueConverter.divideBy10],
[104, "min_temperature_alarm", tuya.valueConverter.divideBy10],
[105, "max_humidity_alarm", tuya.valueConverter.raw],
[106, "min_humidity_alarm", tuya.valueConverter.raw],
[107, "temperature_sensitivity", tuya.valueConverter.divideBy10],
[108, "humidity_sensitivity", tuya.valueConverter.raw],
[109, "schedule_periodic", tuya.valueConverter.raw], // detection_interval_time
[110, "temperature_f", tuya.valueConverter.divideBy10],
// [111, "temperature_sensitivity_f", tuya.valueConverter.divideBy10],
// [112, "max_temperature_alarm_f", tuya.valueConverter.divideBy10],
// [113, "min_temperature_alarm_f", tuya.valueConverter.divideBy10],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE284_aao3yzhs",
"_TZE284_nhgdf6qr",
"_TZE2841000000_nhgdf6qr",
"_TZE284_ap9owrsa",
"_TZE284_33bwcga2",
"_TZE284_wckqztdq",
"_TZE284_3urschql",
"_TZE284_tgrzpqf4",
"_TZE2841000000_tgrzpqf4",
]),
model: "TS0601_soil_3",
vendor: "Tuya",
description: "Soil sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: (device) => [
e.temperature(),
e.soil_moisture(),
...(device?.manufacturerName === "_TZE284_3urschql"
? [e.enum("brightness_level", ea.STATE, ["lower", "low", "normal", "high", "higher"]).withDescription("Brightness level")]
: []),
tuya.exposes.temperatureUnit(),
e.battery(),
tuya.exposes.batteryState(),
],
meta: {
tuyaDatapoints: [
[
2,
"brightness_level",
tuya.valueConverterBasic.lookup({
lower: tuya.enum(0),
low: tuya.enum(1),
normal: tuya.enum(2),
high: tuya.enum(3),
higher: tuya.enum(4),
}),
],
[3, "soil_moisture", tuya.valueConverter.raw],
[5, "temperature", tuya.valueConverter.divideBy10],
[9, "temperature_unit", tuya.valueConverter.temperatureUnit],
[14, "battery_state", tuya.valueConverter.batteryState],
// https://github.com/Koenkk/zigbee-herdsman-converters/pull/8449
// [15, "battery", tuya.valueConverterBasic.scale(6, 60, 0, 100)], //device reports back false scaling
// https://github.com/Koenkk/zigbee2mqtt/issues/26215#issuecomment-3466490289
[15, "battery", tuya.valueConverter.raw],
],
},
whiteLabel: [
tuya.whitelabel("GIEX", "GX04", "Soil Moisture Sensor", ["_TZE284_nhgdf6qr"]),
tuya.whitelabel("GIEX", "GX06", "Soil Moisture Sensor", ["_TZE284_3urschql"]),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_noixx2uz"]),
model: "AR331",
vendor: "Tuya",
description: "Thermostatic radiator valve",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "1970" })],
exposes: [
e.battery().withUnit("%"),
e.child_lock(),
e
.climate()
.withPreset(["auto", "manual", "leave"])
.withSetpoint("current_heating_setpoint", 5, 40, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withSystemMode(["heat"], ea.STATE)
.withRunningState(["idle", "heat"], ea.STATE),
...tuya.exposes.scheduleAllDays(ea.STATE_SET, "HH:MM/C HH:MM/C HH:MM/C HH:MM/C"),
],
meta: {
tuyaDatapoints: [
[2, "preset", tuya.valueConverterBasic.lookup({ auto: tuya.enum(0), manual: tuya.enum(1), leave: tuya.enum(2) })],
[3, "running_state", tuya.valueConverterBasic.lookup({ idle: tuya.enum(0), heat: tuya.enum(1) })],
[
4,
"current_heating_setpoint",
{
from: (value) => value / 10,
to: (value, meta) => {
const currentPreset = meta.state.preset;
let newValue = value;
if (newValue < 5)
newValue = 5;
if (currentPreset === "leave" && newValue > 15)
newValue = 15;
return Math.round(newValue * 10);
},
},
],
[
5,
"local_temperature",
{
from: (value) => (value > 32767 ? value - 65536 : value) / 10,
to: (value) => Math.round(value * 10),
},
],
[6, "battery", tuya.valueConverter.raw],
[7, "child_lock", tuya.valueConverterBasic.lookup({ LOCK: false, UNLOCK: true })],
[28, "schedule_monday", trv603ScheduleConverter(1)],
[29, "schedule_tuesday", trv603ScheduleConverter(2)],
[30, "schedule_wednesday", trv603ScheduleConverter(3)],
[31, "schedule_thursday", trv603ScheduleConverter(4)],
[32, "schedule_friday", trv603ScheduleConverter(5)],
[33, "schedule_saturday", trv603ScheduleConverter(6)],
[34, "schedule_sunday", trv603ScheduleConverter(7)],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_4cgmagba"]),
model: "BHT-209-GCZB",
vendor: "Beca",
description: "Battery Zigbee thermostat with dry contact for boiler control",
extend: [tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true, timeStart: "1970" })],
exposes: [
e.binary("state", ea.STATE_SET, "ON", "OFF").withDescription("Turn the thermostat on/off"),
e.child_lock(),
e
.climate()
.withLocalTemperature(ea.STATE)
.withSetpoint("current_heating_setpoint", 5, 45, 0.5, ea.STATE_SET)
.withLocalTemperatureCalibration(-9, 9, 1, ea.STATE_SET)
.withSystemMode(["heat"], ea.STATE)
.withRunningState(["idle", "heat"], ea.STATE),
e.max_temperature_limit().withUnit("°C").withValueMin(35).withValueMax(45).withValueStep(1),
e
.deadzone_temperature()
.withValueMin(1)
.withValueMax(5)
.withValueStep(1)
.withDescription("The delta between local_temperature and current_heating_setpoint to trigger heating"),
e.binary("heating_mode", ea.STATE_SET, "ON", "OFF").withDescription("ON = heating, OFF = cooling. Must stay ON for boiler control"),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[16, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[18, "deadzone_temperature", tuya.valueConverter.raw],
[24, "local_temperature", tuya.valueConverter.divideBy10],
[27, "local_temperature_calibration", tuya.valueConverter.raw],
[34, "max_temperature_limit", tuya.valueConverter.raw],
[36, "running_state", tuya.valueConverterBasic.lookup({ idle: 1, heat: 0 })],
[40, "child_lock", tuya.valueConverter.lockUnlock],
[104, "heating_mode", tuya.valueConverterBasic.lookup({ ON: true, OFF: false })],
],
},
},
{
// AR331 Pro: 6 presets (auto/manual/holiday/eco/comfort/standby), 8-slot daily schedules,
// boost heating, frost protection, summer mode, override temperature, open-window detection.
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_nbv4tdaz"]),
model: "AR331Pro",
vendor: "Tuya",
description: "Thermostatic radiator valve",
fromZigbee: [fzLocal.ar331ProDp114Corrector],
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "1970" })],
exposes: [
e.battery().withUnit("%"),
e.binary("battery_status", ea.STATE, true, false).withDescription("Battery low warning"),
e.binary("preheat", ea.STATE, "ON", "OFF").withDescription("Preheat/boost active (read-only status; use boost_heating to control)"),
e.child_lock(),
e
.climate()
.withPreset(["auto", "manual", "holiday", "comfort", "eco", "standby"])
.withSetpoint("current_heating_setpoint", 5, 40, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withLocalTemperatureCalibration(-7, 7, 0.5, ea.STATE_SET)
.withRunningState(["idle", "heat"], ea.STATE),
e
.binary("summer_mode", ea.STATE_SET, "ON", "OFF")
.withDescription("Summer mode: disables heating, valve stays closed. Frost protection temperature remains active as safety net."),
e
.enum("heating_cooling_mode", ea.STATE_SET, ["heat", "cool"])
.withDescription("Heating or cooling mode. In cooling mode valve is closed, works as temperature and window/door sensor."),
e.eco_temperature().withValueMin(5).withValueMax(20).withValueStep(0.5),
e.comfort_temperature().withValueMin(5).withValueMax(40).withValueStep(0.5),
e.text("holiday_time", ea.STATE_SET).withDescription("Holiday start and end in format YYYY/MM/DD HH:MM | YYYY/MM/DD HH:MM"),
e
.numeric("frost_protection_temperature", ea.STATE_SET)
.withUnit("°C")
.withDescription("Frost protection: valve opens below this temperature")
.withValueMin(0)
.withValueMax(15)
.withValueStep(0.5),
e.binary("boost_heating", ea.STATE_SET, "ON", "OFF").withDescription("Boost heating: the device will enter boost heating mode."),
e
.numeric("boost_time", ea.STATE_SET)
.withUnit("min")
.withDescription("Boost duration in minutes")
.withValueMin(0)
.withValueMax(1440)
.withValueStep(30),
e.binary("window_detection", ea.STATE_SET, "ON", "OFF").withDescription("Enable open-window detection"),
e.binary("window_open", ea.STATE, "open", "closed").withDescription("Window is currently detected as"),
e
.numeric("window_temp", ea.STATE_SET)
.withUnit("°C")
.withDescription("Window detection: temperature drop threshold")
.withValueMin(5)
.withValueMax(40)
.withValueStep(0.5),
e
.numeric("window_delay", ea.STATE_SET)
.withUnit("min")
.withDescription("Window detection: delay before triggering (minutes)")
.withValueMin(0)
.withValueMax(1440)
.withValueStep(1),
e
.numeric("window_close_delay", ea.STATE_SET)
.withUnit("min")
.withDescription("Window detection: minimum open time before valve closes (minutes)")
.withValueMin(0)
.withValueMax(1440)
.withValueStep(1),
e.binary("valve_state", ea.STATE, "open", "close").withDescription("Current valve state (read-only)"),
e.numeric("fault_code", ea.STATE).withDescription("Fault code bitmap (bit 0: program_fault, bit 1: low_battery, bit 2: sensor_fault)"),
e.enum("screen_orientation", ea.STATE_SET, ["up", "right", "down", "left"]).withDescription("Display orientation"),
e
.numeric("display_brightness", ea.STATE_SET)
.withDescription("Display brightness (1–7)")
.withValueMin(1)
.withValueMax(7)
.withValueStep(1),
e.binary("override_active", ea.STATE_SET, "ON", "OFF").withDescription("Temporary manual override active"),
e
.numeric("override_temperature", ea.STATE_SET)
.withUnit("°C")
.withDescription("Temporary manual override temperature")
.withValueMin(5)
.withValueMax(40)
.withValueStep(0.5),
...tuya.exposes.scheduleAllDays(ea.STATE_SET, "00:00/16.0 06:00/20.5 09:00/18.0 12:00/21.0 14:00/18.0 17:00/21.0 22:00/16.0 23:00/16.0"),
],
meta: {
tuyaDatapoints: [
[1, "preheat", tuya.valueConverter.onOff],
[
2,
"preset",
tuya.valueConverterBasic.lookup({
auto: tuya.enum(0),
manual: tuya.enum(1),
holiday: tuya.enum(2),
eco: tuya.enum(3),
comfort: tuya.enum(4),
standby: tuya.enum(5),
}),
],
// work_state: index 0 = opened = heating, index 1 = closed = idle
[3, "running_state", tuya.valueConverterBasic.lookup({ heat: tuya.enum(0), idle: tuya.enum(1) })],
[
4,
"current_heating_setpoint",
{
from: (v) => v / 10,
to: (v, meta) => {
const preset = meta.state?.preset;
let val = v;
if (val < 5)
val = 5;
if (val > 40)
val = 40;
if (preset === "holiday" && val > 15)
val = 15;
if (preset === "eco" && val > 18)
val = 18;
if (preset === "comfort" && val < 18.5)
val = 18.5;
return Math.round(val * 10);
},
},
],
[5, "local_temperature", { from: (v) => (v > 32767 ? v - 65536 : v) / 10 }],
[6, "battery", tuya.valueConverter.raw],
[7, "child_lock", tuya.valueConverterBasic.lookup({ LOCK: false, UNLOCK: true })],
// DP 9/10: upper/lower_temp stored by device but not enforced — not mapped
[14, "window_detection", tuya.valueConverter.onOff],
[15, "window_open", tuya.valueConverterBasic.lookup({ open: tuya.enum(0), closed: tuya.enum(1) })],
[16, "window_temp", tuya.valueConverter.divideBy10],
[18, "display_brightness", tuya.valueConverter.raw],
// DP 19 factory_reset: not triggerable via Zigbee DP — not mapped
[35, "fault_code", tuya.valueConverter.raw],
// DP 47: same sign/wrap logic as divideBy10
[47, "local_temperature_calibration", tuya.valueConverter.divideBy10],
[49, "valve_state", tuya.valueConverterBasic.lookup({ open: tuya.enum(0), close: tuya.enum(1) })],
[101, "boost_heating", tuya.valueConverter.onOff],
// DP 102 boost_timestamp is in seconds (step = 1800); exposed as minutes
[102, "boost_time", { from: (v) => Math.round(v / 60), to: (v) => Math.round(v) * 60 }],
[103, "eco_temperature", tuya.valueConverter.divideBy10],
[104, "comfort_temperature", tuya.valueConverter.divideBy10],
// DP 105 window_timestamp: delay in seconds, exposed as minutes
[105, "window_delay", { from: (v) => Math.round(v / 60), to: (v) => Math.round(v) * 60 }],
[106, "holiday_time", ar331ProHolidayTimeConverter],
[107, "frost_protection_temperature", tuya.valueConverter.divideBy10],
// DP 108 window_stoptime: min open duration in seconds, exposed as minutes
[108, "window_close_delay", { from: (v) => Math.round(v / 60), to: (v) => Math.round(v) * 60 }],
[109, "heating_cooling_mode", tuya.valueConverterBasic.lookup({ heat: false, cool: true })],
[110, "battery_status", tuya.valueConverter.raw],
// DP 111: screen orientation as plain uint32 (degrees), NOT a Tuya enum
[111, "screen_orientation", tuya.valueConverterBasic.lookup({ up: 0, right: 90, down: 180, left: 270 })],
// DP 112 pro_mode intentionally not mapped (schedule type; error-prone over Zigbee)
[113, "summer_mode", tuya.valueConverter.onOff],
// DP 114: device echoes twice (uint32 BE datatype=2, then 2-byte LE raw datatype=0).
// Signed int16 underflow when knob turned below 5°C. Clamped here; fzLocal corrector writes back.
[
114,
"override_temperature",
{
from: (v) => {
let raw;
if (Buffer.isBuffer(v)) {
if (v.length < 2)
return null;
raw = v.readUInt16LE(0);
}
else {
raw = v;
}
if (raw > 32767)
raw -= 65536;
return Math.max(5, Math.min(40, raw / 10));
},
to: (v) => Math.round(v * 10),
},
],
// DP 115 override_active: device ignores external writes — to clear, cycle preset
[115, "override_active", tuya.valueConverter.onOff],
// Weekly schedule: 8 timeslots per day, 33-byte raw payload
// Uses thermostatScheduleDayMultiDPWithDayNumber — identical byte layout [dayNum, HH, MM, TH, TL] × 8
[28, "schedule_monday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(1, 8)],
[29, "schedule_tuesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(2, 8)],
[30, "schedule_wednesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(3, 8)],
[31, "schedule_thursday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(4, 8)],
[32, "schedule_friday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(5, 8)],
[33, "schedule_saturday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(6, 8)],
[34, "schedule_sunday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(7, 8)],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_nt4pquef"]),
model: "SGS02Z",
vendor: "Tuya",
description: "Soil sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.temperature(),
e.soil_moisture(),
tuya.exposes.temperatureUnit(),
e.enum("illuminance_level", ea.STATE, ["low-", "low", "nor", "high", "high+"]).withDescription("Illuminance level"),
e.battery(),
],
meta: {
tuyaDatapoints: [
[
2,
"illuminance_level",
tuya.valueConverterBasic.lookup({
"low-": 0,
low: 1,
nor: 2,
high: 3,
"high+": 4,
}),
],
[3, "soil_moisture", tuya.valueConverter.raw],
[5, "temperature", tuya.valueConverter.divideBy10],
[9, "temperature_unit", tuya.valueConverter.temperatureUnitEnum],
[15, "battery", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_2baujqot"]),
model: "TS0601_sos",
vendor: "Tuya",
description: "SOS panic button",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [e.action(["emergency"]), e.battery()],
meta: {
tuyaDatapoints: [
[23, "action", tuya.valueConverter.static("emergency")],
[3, "battery", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE200_ip2akl4w",
"_TZE200_1agwnems",
"_TZE200_la2c2uo9",
"_TZE200_579lguh2",
"_TZE200_vucankjx",
"_TZE200_4mh6tyyo",
"_TZE204_hlx9tnzb",
"_TZE204_n9ctkb6j",
"_TZE204_9qhuzgo0",
"_TZE200_9cxuhakf",
"_TZE200_a0syesf5",
"_TZE200_3p5ydos3",
"_TZE200_swaamsoy",
"_TZE200_ojzhk75b",
"_TZE200_w4cryh2i",
"_TZE200_dfxkcots",
"_TZE204_68utemio",
"_TZE200_9i9dt8is",
"_TZE200_ctq0k47x",
"_TZE200_ebwgzdqq",
"_TZE204_vevc4c6g",
"_TZE200_0nauxa0p",
"_TZE200_ykgar0ow",
"_TZE284_m1cvyneb",
"_TZE200_0hb4rdnp",
"_TZE200_gne0e6mk",
"_TZE284_68utemio",
"_TZE200_itp8dt7f",
"_TZE28C1000000_68utemio",
]),
model: "TS0601_dimmer_1_gang_1",
vendor: "Tuya",
description: "1 gang smart dimmer",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
// Some variants (notably ION Industries _TZE200_ykgar0ow / _TZE200_4mh6tyyo)
// emit standard genOnOff / genLevelCtrl attribute reports for local
// knob/rotation events once those clusters are bound to the coordinator
// and attribute reporting is configured. Without these there are no
// physical-knob state changes (see #10272, #25203). For variants whose
// firmware does not support standard reporting, the bind + configReport
// requests are harmless no-ops.
//
// maximumReportInterval is set to 0 (change-only reporting, no periodic
// heartbeat). The library default of 3600s causes this firmware to send
// a stale hourly report --- genOnOff.onOff always 1 and a frozen
// genLevelCtrl.currentLevel regardless of actual state --- which
// overwrites the correct state in the consumer once an hour. The
// event-driven on-change reports remain accurate, so suppressing the
// periodic path is sufficient and lossless.
version: "0.0.2",
fromZigbee: [fz.on_off, fz.brightness],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff", "genLevelCtrl"]);
await reporting.onOff(endpoint, { max: 0 });
await reporting.brightness(endpoint, { max: 0 });
},
exposes: (device, options) => {
const exps = [
tuya.exposes.lightBrightnessWithMinMax(),
tuya.exposes.countdown(),
e.power_on_behavior().withAccess(ea.STATE_SET),
];
if (!device ||
!["_TZE200_4mh6tyyo", "_TZE200_ykgar0ow", "_TZE200_0hb4rdnp", "_TZE200_gne0e6mk", "_TZE200_itp8dt7f"].includes(device.manufacturerName)) {
exps.push(tuya.exposes.lightType(), tuya.exposes.backlightModeOffNormalInverted().withAccess(ea.STATE_SET));
}
return exps;
},
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff, { skip: tuya.skip.stateOnAndBrightnessPresent }],
[2, "brightness", tuya.valueConverter.scale0_254to0_1000],
[3, "min_brightness", tuya.valueConverter.scale0_254to0_1000],
[4, "light_type", tuya.valueConverter.lightType],
[5, "max_brightness", tuya.valueConverter.scale0_254to0_1000],
[6, "countdown", tuya.valueConverter.countdown],
[14, "power_on_behavior", tuya.valueConverter.powerOnBehavior],
[21, "backlight_mode", tuya.valueConverter.backlightModeOffNormalInverted],
],
},
whiteLabel: [
{ vendor: "Lerlink", model: "X706U" },
{ vendor: "Moes", model: "ZS-EUD_1gang" },
{ vendor: "Larkkey", model: "ZSTY-SM-1DMZG-EU" },
{ vendor: "Earda", model: "EDM-1ZAA-EU" },
{ vendor: "Earda", model: "EDM-1ZAB-EU" },
{ vendor: "Earda", model: "EDM-1ZBA-EU" },
{ vendor: "Mercator Ikuü", model: "SSWD01" },
{ vendor: "Moes", model: "ZS-USD" },
{ vendor: "Moes", model: "EDM-1ZBB-EU" },
tuya.whitelabel("Moes", "ZS-SR-EUD-1", "Star ring smart dimmer switch 1 gang", ["_TZE204_hlx9tnzb"]),
tuya.whitelabel("Moes", "MS-105Z", "Smart Dimmer module", ["_TZE200_la2c2uo9"]),
tuya.whitelabel("Mercator Ikuü", "SSWM-DIMZ", "Switch Mechanism", ["_TZE200_9cxuhakf"]),
tuya.whitelabel("Zemismart", "ZN2S-US1-SD", "Single gang dimmer", ["_TZE204_68utemio", "_TZE284_68utemio"]),
tuya.whitelabel("Mercator Ikuü", "SSWRM-ZB", "Rotary dimmer mechanism", ["_TZE200_a0syesf5"]),
tuya.whitelabel("Lonsonho", "EDM-1ZBB-EU", "Smart Dimmer Switch", ["_TZE200_0nauxa0p"]),
tuya.whitelabel("ION Industries", "ID200W-ZIGB", "LED Zigbee Dimmer", ["_TZE200_ykgar0ow", "_TZE200_4mh6tyyo", "_TZE200_itp8dt7f"]),
tuya.whitelabel("ION Industries", "90.500.090", "Zigbee Dimmer Master/Slave set", ["_TZE200_0hb4rdnp"]),
tuya.whitelabel("ION Industries", "90.500.040", "LED Zigbee Dimmer", ["_TZE200_gne0e6mk"]),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_whpb9yts"]),
model: "TS0601_dimmer_1_gang_2",
vendor: "Tuya",
description: "1 gang smart dimmer",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.lightBrightness(),
tuya.exposes.countdown(),
tuya.exposes.lightType(),
e.power_on_behavior().withAccess(ea.STATE_SET),
tuya.exposes.backlightModeOffNormalInverted().withAccess(ea.STATE_SET),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff, { skip: tuya.skip.stateOnAndBrightnessPresent }],
[3, "brightness", tuya.valueConverter.scale0_254to0_1000],
[4, "light_type", tuya.valueConverter.lightType],
[5, "max_brightness", tuya.valueConverter.scale0_254to0_1000],
[6, "countdown", tuya.valueConverter.countdown],
[14, "power_on_behavior", tuya.valueConverter.powerOnBehavior],
[21, "backlight_mode", tuya.valueConverter.backlightModeOffNormalInverted],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_qzaing2g"]),
model: "TS0601_dimmer_1_gang_3",
vendor: "Tuya",
description: "1 gang smart dimmer",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [tuya.exposes.lightBrightness(), e.voltage(), e.current(), e.power(), e.child_lock(), tuya.exposes.backlightModeOffOn()],
meta: {
tuyaDatapoints: [
[16, "backlight_mode", tuya.valueConverter.onOff],
[21, "current", tuya.valueConverter.divideBy1000],
[22, "power", tuya.valueConverter.divideBy10],
[23, "voltage", tuya.valueConverter.divideBy10],
[101, "child_lock", tuya.valueConverter.lockUnlock],
[141, "state", tuya.valueConverter.onOff, { skip: tuya.skip.stateOnAndBrightnessPresent }],
[142, "brightness", tuya.valueConverter.scale0_254to0_1000],
// Doesn't seem to work
// https://github.com/Koenkk/zigbee2mqtt/issues/26526#issuecomment-2749559071
// [143, "min_brightness", tuya.valueConverter.scale0_254to0_1000],
// [144, "max_brightness", tuya.valueConverter.scale0_254to0_1000],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_fjjbhx9d", "_TZE200_e3oitdyu", "_TZE200_gwkapsoq", "_TZE204_zenj4lxv"]),
model: "TS0601_dimmer_2",
vendor: "Tuya",
description: "2 gang smart dimmer",
whiteLabel: [
{ vendor: "Moes", model: "ZS-EUD_2gang" },
{ vendor: "Moes", model: "MS-105B" }, // _TZE200_e3oitdyu
tuya.whitelabel("Moes", "ZS-SR-EUD-2", "Star ring smart dimmer switch 2 gangs", ["_TZE204_zenj4lxv"]),
],
extend: [
tuya.modernExtend.tuyaMagicPacket(),
m.deviceEndpoints({ endpoints: { l1: 1, l2: 1 } }),
tuya.modernExtend.dpLight({
state: {
dp: 1,
type: tuya.dataTypes.bool,
valueOn: ["ON", true],
valueOff: ["OFF", false],
skip: tuya.skip.stateOnAndBrightnessPresent,
},
brightness: {
dp: 2,
type: tuya.dataTypes.number,
scale: [0, 254, 0, 1000],
},
min: { dp: 3, type: tuya.dataTypes.number, scale: [0, 254, 0, 1000] },
max: { dp: 5, type: tuya.dataTypes.number, scale: [0, 254, 0, 1000] },
endpoint: "l1",
}),
tuya.modernExtend.dpNumeric({
name: "countdown",
dp: 6,
type: tuya.dataTypes.number,
expose: tuya.exposes.countdown(),
endpoint: "l1",
}),
tuya.modernExtend.dpLight({
state: {
dp: 7,
type: tuya.dataTypes.bool,
valueOn: ["ON", true],
valueOff: ["OFF", false],
skip: tuya.skip.stateOnAndBrightnessPresent,
},
brightness: {
dp: 8,
type: tuya.dataTypes.number,
scale: [0, 254, 0, 1000],
},
min: { dp: 9, type: tuya.dataTypes.number, scale: [0, 254, 0, 1000] },
max: { dp: 11, type: tuya.dataTypes.number, scale: [0, 254, 0, 1000] },
endpoint: "l2",
}),
tuya.modernExtend.dpNumeric({
name: "countdown",
dp: 12,
type: tuya.dataTypes.number,
expose: tuya.exposes.countdown(),
endpoint: "l2",
}),
tuya.modernExtend.dpPowerOnBehavior({
dp: 14,
type: tuya.dataTypes.enum,
}),
tuya.modernExtend.dpBacklightMode({
dp: 21,
type: tuya.dataTypes.enum,
}),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_vm1gyrso", "_TZE204_1v1dxkck", "_TZE204_znvwzxkq", "_TZE284_znvwzxkq", "_TZE200_vizxbhco"]),
model: "TS0601_dimmer_3",
vendor: "Tuya",
description: "3 gang smart dimmer",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.lightBrightnessWithMinMax().withEndpoint("l1"),
tuya.exposes.lightBrightnessWithMinMax().withEndpoint("l2"),
tuya.exposes.lightBrightnessWithMinMax().withEndpoint("l3"),
tuya.exposes.countdown().withEndpoint("l1"),
tuya.exposes.countdown().withEndpoint("l2"),
tuya.exposes.countdown().withEndpoint("l3"),
tuya.exposes.lightType().withEndpoint("l1"),
tuya.exposes.lightType().withEndpoint("l2"),
tuya.exposes.lightType().withEndpoint("l3"),
e.power_on_behavior().withAccess(ea.STATE_SET),
tuya.exposes.backlightModeOffNormalInverted().withAccess(ea.STATE_SET),
e.enum("backlight_color", ea.ALL, Object.keys(tuya.BacklightColorEnum)).withDescription("Backlight color"),
e
.numeric("backlight_brightness", ea.ALL)
.withValueMin(0)
.withValueMax(100)
.withUnit("%")
.withValueStep(1)
.withDescription("Backlight brightness"),
],
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff, { skip: tuya.skip.stateOnAndBrightnessPresent }],
[2, "brightness_l1", tuya.valueConverter.scale0_254to0_1000],
[3, "min_brightness_l1", tuya.valueConverter.scale0_254to0_1000],
[4, "light_type_l1", tuya.valueConverter.lightType],
[5, "max_brightness_l1", tuya.valueConverter.scale0_254to0_1000],
[6, "countdown_l1", tuya.valueConverter.countdown],
[7, "state_l2", tuya.valueConverter.onOff, { skip: tuya.skip.stateOnAndBrightnessPresent }],
[8, "brightness_l2", tuya.valueConverter.scale0_254to0_1000],
[9, "min_brightness_l2", tuya.valueConverter.scale0_254to0_1000],
[10, "light_type_l2", tuya.valueConverter.lightType],
[11, "max_brightness_l2", tuya.valueConverter.scale0_254to0_1000],
[12, "countdown_l2", tuya.valueConverter.countdown],
[14, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum],
[15, "state_l3", tuya.valueConverter.onOff, { skip: tuya.skip.stateOnAndBrightnessPresent }],
[16, "brightness_l3", tuya.valueConverter.scale0_254to0_1000],
[17, "min_brightness_l3", tuya.valueConverter.scale0_254to0_1000],
[18, "light_type_l3", tuya.valueConverter.lightType],
[19, "max_brightness_l3", tuya.valueConverter.scale0_254to0_1000],
[20, "countdown_l3", tuya.valueConverter.countdown],
[21, "backlight_mode", tuya.valueConverter.backlightModeOffNormalInverted],
[101, "backlight_color", tuya.valueConverterBasic.lookup(tuya.BacklightColorEnum)],
[103, "backlight_brightness", tuya.valueConverter.raw],
],
},
endpoint: (device) => {
return { l1: 1, l2: 1, l3: 1 };
},
whiteLabel: [
{ vendor: "Moes", model: "ZS-EUD_3gang" },
tuya.whitelabel("Moes", "ZS-SR-EUD-3", "Star ring smart dimmer switch 3 gangs", ["_TZE204_1v1dxkck"]),
tuya.whitelabel("Zemismart", "ZN2S-RS3E-DH", "3 gang dimmer", ["_TZE204_znvwzxkq", "_TZE284_znvwzxkq"]),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_bxoo2swd", "_TZE204_bxoo2swd", "_TZE200_tsxpl0d0"]),
model: "TS0601_dimmer_4",
vendor: "Tuya",
description: "2 gang smart dimmer module",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.lightBrightnessWithMinMax().withEndpoint("l1"),
tuya.exposes.lightBrightnessWithMinMax().withEndpoint("l2"),
tuya.exposes.countdown().withEndpoint("l1"),
tuya.exposes.countdown().withEndpoint("l2"),
tuya.exposes.lightType().withEndpoint("l1"),
tuya.exposes.lightType().withEndpoint("l2"),
e.enum("power_on_behavior", ea.STATE_SET, ["off", "on", "previous"]),
],
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff, { skip: tuya.skip.stateOnAndBrightnessPresent }],
[2, "brightness_l1", tuya.valueConverter.scale0_254to0_1000],
[3, "min_brightness_l1", tuya.valueConverter.scale0_254to0_1000],
[
4,
"light_type_l1",
tuya.valueConverterBasic.lookup({
led: tuya.enum(0),
incandescent: tuya.enum(1),
halogen: tuya.enum(2),
}),
],
[5, "max_brightness_l1", tuya.valueConverter.scale0_254to0_1000],
[6, "countdown_l1", tuya.valueConverter.countdown],
[7, "state_l2", tuya.valueConverter.onOff, { skip: tuya.skip.stateOnAndBrightnessPresent }],
[8, "brightness_l2", tuya.valueConverter.scale0_254to0_1000],
[9, "min_brightness_l2", tuya.valueConverter.scale0_254to0_1000],
[
10,
"light_type_l2",
tuya.valueConverterBasic.lookup({
led: tuya.enum(0),
incandescent: tuya.enum(1),
halogen: tuya.enum(2),
}),
],
[11, "max_brightness_l2", tuya.valueConverter.scale0_254to0_1000],
[12, "countdown_l2", tuya.valueConverter.countdown],
[
14,
"power_on_behavior",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
on: tuya.enum(1),
previous: tuya.enum(2),
}),
],
],
},
endpoint: (device) => {
return { l1: 1, l2: 1 };
},
whiteLabel: [
tuya.whitelabel("Moes", "ZM-105B-M", "2 gang smart dimmer module", ["_TZE200_bxoo2swd", "_TZE204_bxoo2swd"]),
tuya.whitelabel("KnockautX", "FMD2C018", "2 gang smart dimmer module", ["_TZE200_tsxpl0d0"]),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_dcnsggvz", "_TZE200_dcnsggv", "_TZE200_dcnsggvz"]),
model: "TS0601_dimmer_5",
vendor: "Tuya",
description: "1 gang smart dimmer module",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.lightBrightnessWithMinMax(),
e.enum("power_on_behavior", ea.STATE_SET, ["off", "on", "previous"]),
tuya.exposes.countdown(),
tuya.exposes.lightType(),
tuya.exposes.switchType(),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff, { skip: tuya.skip.stateOnAndBrightnessPresent }],
[2, "brightness", tuya.valueConverter.scale0_254to0_1000],
[3, "min_brightness", tuya.valueConverter.scale0_254to0_1000],
[
4,
"light_type",
tuya.valueConverterBasic.lookup({
led: tuya.enum(0),
incandescent: tuya.enum(1),
halogen: tuya.enum(2),
}),
],
[4, "light_type", tuya.valueConverter.lightType],
[5, "max_brightness", tuya.valueConverter.scale0_254to0_1000],
[6, "countdown", tuya.valueConverter.countdown],
[
14,
"power_on_behavior",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
on: tuya.enum(1),
previous: tuya.enum(2),
}),
],
[
57,
"switch_type",
tuya.valueConverterBasic.lookup({
toggle: tuya.enum(0),
state: tuya.enum(1),
momentary: tuya.enum(2),
}),
],
],
},
whiteLabel: [{ vendor: "Moes", model: "MS-105-M" }],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_p0gzbqct"]),
model: "TS0601_dimmer_knob",
vendor: "Tuya",
description: "Zigbee smart knob dimmer",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.lightBrightness().withMinBrightness().setAccess("min_brightness", ea.STATE_SET),
tuya.exposes.lightType(),
tuya.exposes.indicatorModeNoneRelayPos(),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff, { skip: tuya.skip.stateOnAndBrightnessPresent }],
[2, "brightness", tuya.valueConverter.scale0_254to0_1000],
[3, "min_brightness", tuya.valueConverter.scale0_254to0_1000],
[4, "light_type", tuya.valueConverter.lightType],
[21, "indicator_mode", tuya.valueConverterBasic.lookup({ none: 0, relay: 1, pos: 2 })],
],
},
whiteLabel: [
{ vendor: "Moes", model: "WS-SY-EURD" },
{ vendor: "Moes", model: "WS-SY-EURD-WH-MS" },
],
},
{
fingerprint: tuya.fingerprint("TS011F", ["_TZ3000_oiymh3qu"]),
model: "TS011F_socket_module",
vendor: "Tuya",
description: "Socket module",
extend: [tuya.modernExtend.tuyaBase(), tuya.modernExtend.tuyaOnOff()],
whiteLabel: [
{ vendor: "LoraTap", model: "RR400ZB" },
{ vendor: "LoraTap", model: "SP400ZB" },
],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff"]);
await reporting.onOff(endpoint);
},
},
{
fingerprint: tuya.fingerprint("TS011F", ["_TZ3000_wxtp7c5y", "_TYZB01_mtunwanm"]),
model: "TS011F_wall_outlet",
vendor: "Tuya",
description: "In-wall outlet",
extend: [tuya.modernExtend.tuyaBase(), tuya.modernExtend.tuyaOnOff({ childLock: true })],
whiteLabel: [
{ vendor: "Teekar", model: "SWP86-01OG" },
tuya.whitelabel("ClickSmart+", "CMA30035", "1 gang socket outlet", ["_TYZB01_mtunwanm"]),
{ vendor: "BSEED", model: "Zigbee Socket" },
],
},
{
fingerprint: tuya.fingerprint("TS0001", ["_TZ3210_fhx7lk3d"]),
model: "CMA30651",
vendor: "ClickSmart+",
description: "13A Smart Switched Fused Connection Unit",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
onOffCountdown: true,
}),
],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
},
},
{
fingerprint: tuya.fingerprint("TS0011", ["_TZ3000_uaa34g7v"]),
model: "CSP041",
vendor: "ClickSmart+",
description: "1 gang switch module without neutral wire",
extend: [m.onOff({ powerOnBehavior: false })],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
device.powerSource = "Mains (single phase)";
device.save();
},
},
{
fingerprint: tuya.fingerprint("TS0003", ["_TYZB01_u9kkqh5o"]),
model: "CSP043",
vendor: "ClickSmart+",
description: "1 gang switch module with neutral wire",
extend: [m.onOff({ powerOnBehavior: false })],
},
{
fingerprint: tuya.fingerprint("TS0012", ["_TZ3000_biakwrag"]),
model: "CSP042",
vendor: "ClickSmart+",
description: "2 gang switch module without neutral wire",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
switchType: false,
endpoints: ["l1", "l2"],
}),
],
endpoint: (device) => {
return { l1: 1, l2: 2 };
},
meta: { multiEndpoint: true, multiEndpointSkip: ["power_on_behavior"] },
},
{
fingerprint: tuya.fingerprint("TS110F", ["_TZ3000_estfrmup", "_TZ3000_ktuoyvt5"]),
model: "CSP051",
vendor: "ClickSmart+",
description: "1 gang smart dimmer switch module without neutral",
extend: [m.light()],
whiteLabel: [tuya.whitelabel("Lonsonho", "QS-Zigbee-D02-TRIAC-L", "1 gang smart dimmer switch module without neutral", ["_TZ3000_ktuoyvt5"])],
},
{
fingerprint: tuya.fingerprint("TS110F", ["_TZ3000_hexqj6ls"]),
model: "CSP052",
vendor: "ClickSmart+",
description: "2 gang smart dimmer switch module without neutral",
extend: [
m.deviceEndpoints({ endpoints: { l1: 1, l2: 2 } }),
m.light({
powerOnBehavior: false,
endpointNames: ["l1", "l2"],
configureReporting: true,
}),
],
meta: { multiEndpoint: true },
},
{
fingerprint: [...tuya.fingerprint("isltm67\u0000", ["_TYST11_pisltm67"]), ...tuya.fingerprint("TS0601", ["_TZE200_pisltm67"])],
model: "S-LUX-ZB",
vendor: "Tuya",
description: "Light sensor",
fromZigbee: [legacy.fromZigbee.SLUXZB],
toZigbee: [],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genBasic"]);
},
exposes: [e.battery(), e.illuminance(), e.enum("brightness_level", ea.STATE, ["LOW", "MEDIUM", "HIGH"])],
},
{
zigbeeModel: ["TS130F"],
model: "TS130F",
vendor: "Tuya",
description: "Curtain/blind switch",
fromZigbee: [
fz.cover_position_tilt,
tuya.fz.indicator_mode,
tuya.fz.cover_options,
tuya.fz.backlight_mode_off_on,
tuya.fz.switch_type_curtain,
],
toZigbee: [
tz.cover_state,
tz.cover_position_tilt,
tuya.tz.cover_calibration,
tuya.tz.cover_reversal,
tuya.tz.backlight_indicator_mode_2,
tuya.tz.backlight_indicator_mode_1,
tuya.tz.switch_type_curtain,
],
meta: { coverInverted: true },
extend: [
tuyaBase(),
tuya.clusters.addTuyaClosuresWindowCoveringCluster(),
m.deviceAddCustomCluster("closuresWindowCovering", {
name: "closuresWindowCovering",
ID: 0x0102,
attributes: {
currentPositionLiftPercentage: {
name: "currentPositionLiftPercentage",
ID: 0x0008,
type: zcl_1.DataType.UINT8,
report: true,
scene: true,
max: 100,
default: 0,
write: true,
},
},
commands: {},
commandsResponse: {},
}),
tuya.modernExtend.tuyaCoverSwitchType(), // TODO: only for some models..?
],
whiteLabel: [
tuya.whitelabel("BSEED", "EC-GL86ZPCRS31", "Curtain/blind switch", ["_TZ3000_bs93npae"]),
tuya.whitelabel("BSEED", "S-PC86ZPCS11B", "Curtain/blind switch", ["_TZ3000_dojqjapa"]),
tuya.whitelabel("Danor", "SK-Z802C-US", "Smart curtain/shutter switch", ["_TZ3000_8h7wgocw"]),
{ vendor: "LoraTap", model: "SC400" },
tuya.whitelabel("LoraTap", "SC500ZB", "Smart curtain/shutter switch", ["_TZ3000_e3vhyirx", "_TZ3000_femsaaua"]),
tuya.whitelabel("LoraTap", "SC500ZB-v4", "Smart curtain/shutter switch", ["_TZ3000_5iixzdo7"]),
tuya.whitelabel("Nous", "B4Z", "Curtain switch", ["_TZ3000_yruungrl"]),
tuya.whitelabel("Nous", "L12Z", "Smart Zigbee Curtain Module L12Z", ["_TZ3000_jwv3cwak"]),
tuya.whitelabel("Zemismart", "ZN-LC1E", "Smart curtain/shutter switch", ["_TZ3000_74hsp7qy"]),
tuya.whitelabel("Girier", "TS130F_GIRIER", "Smart curtain switch", ["_TZ3210_dwytrmda"]),
tuya.whitelabel("Zemismart", "TS130F_ZEMISMART", "Smart curtain wall switch", ["_TZ3000_vw8pawxa"]),
tuya.whitelabel("QA", "QACZ1", "Smart curtain switch", ["_TZ3210_xbpt8ewc"]),
],
exposes: (device) => {
const exps = [
e.cover_position(),
e.enum("moving", ea.STATE, ["UP", "STOP", "DOWN"]),
e.binary("motor_reversal", ea.ALL, "ON", "OFF"),
];
if (["_TZ3000_yruungrl", "_TZ3210_dwytrmda", "_TZ3210_xbpt8ewc", "_TZ3000_vw8pawxa"].includes(device.manufacturerName)) {
exps.push(e.binary("calibration", ea.ALL, "ON", "OFF"), e.numeric("calibration_time", ea.ALL).withUnit("s"));
}
else if (["_TZ3000_cet6ch1r", "_TZ3000_5iixzdo7"].includes(device.manufacturerName)) {
exps.push(e.binary("calibration_to_open", ea.ALL, "ON", "OFF"), e.binary("calibration_to_close", ea.ALL, "ON", "OFF"), e.numeric("calibration_time_to_open", ea.ALL).withUnit("s"), e.numeric("calibration_time_to_close", ea.ALL).withUnit("s"));
}
else {
exps.push(e.binary("calibration", ea.ALL, "ON", "OFF"), e.numeric("calibration_time", ea.STATE).withUnit("s"));
}
if (!["_TZ3000_e3vhyirx", "_TZ3000_5iixzdo7", "_TZ3000_yruungrl", "_TZ3210_dwytrmda", "_TZ3210_xbpt8ewc"].includes(device.manufacturerName)) {
exps.push(tuya.exposes.indicatorMode(), tuya.exposes.backlightModeOffOn());
}
if (["_TZ3000_5iixzdo7"].includes(device.manufacturerName)) {
exps.push(tuya.exposes.switchTypeCurtain());
}
else if (["_TZ3000_yruungrl"].includes(device.manufacturerName)) {
exps.push(e.enum("switch_type_curtain", ea.ALL, ["flip-switch", "sync-switch", "button-switch"]).withDescription("External switch type"));
}
return exps;
},
},
{
fingerprint: tuya.fingerprint("TS130F", ["_TZ3000_1dd0d5yi"]),
model: "MS-108ZR",
vendor: "Moes",
description: "Zigbee + RF curtain switch module",
ota: true,
extend: [tuyaBase(), tuya.clusters.addTuyaClosuresWindowCoveringCluster(), tuya.modernExtend.tuyaCoverSwitchType()],
meta: { coverInverted: true },
fromZigbee: [tuya.fz.cover_options, fz.cover_position_tilt],
toZigbee: [tz.cover_state, tuya.tz.moes_cover_calibration, tz.cover_position_tilt, tuya.tz.cover_reversal],
exposes: [
e.cover_position(),
e.numeric("calibration_time", ea.ALL).withValueMin(0).withValueMax(500).withUnit("s"),
e.enum("moving", ea.STATE, ["UP", "STOP", "DOWN"]),
e.binary("motor_reversal", ea.ALL, "ON", "OFF"),
],
},
{
fingerprint: tuya.fingerprint("TS130F", ["_TZE20C_xbexmf8h"]),
model: "TS130F_xbexmf8h",
vendor: "Tuya",
description: "Blind/curtain motor controller",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.cover().withPosition(),
e
.numeric("calibration_time", ea.STATE)
.withUnit("s")
.withValueStep(0.1)
.withDescription("Calibration time measured by motor")
.withCategory("diagnostic"),
e.binary("motor_reversal", ea.STATE_SET, "ON", "OFF").withDescription("Reverse motor direction").withCategory("config"),
e.binary("calibration", ea.STATE_SET, "ON", "OFF").withDescription("Calibration mode").withCategory("config"),
e.enum("switch_type", ea.STATE_SET, ["momentary", "toggle"]).withDescription("Type of the installed switch").withCategory("config"),
e.binary("backlight_mode", ea.STATE_SET, "ON", "OFF").withDescription("Switch backlight").withCategory("config"),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[2, "position", tuya.valueConverter.coverPosition],
[3, "calibration", tuya.valueConverter.onOffEnumOn0],
[8, "motor_reversal", tuya.valueConverter.onOffEnumOn1],
[10, "calibration_time", { from: (v) => v / 10 }],
[12, "switch_type", tuya.valueConverterBasic.lookup({ momentary: tuya.enum(0), toggle: tuya.enum(1) })], // needs confirmation
[14, "backlight_mode", tuya.valueConverter.onOffEnumOn0],
],
},
},
{
zigbeeModel: ["qnazj70", "kjintbl"],
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_oisqyl4o", "_TZ3000_uim07oem", "_TZE200_js3mgbjb", "_TZE200_7deq70b8", "_TZE204_ptaqh9tk"]),
model: "TS0601_switch",
vendor: "Tuya",
description: "1, 2, 3 or 4 gang switch",
exposes: [
e.switch().withEndpoint("l1").setAccess("state", ea.STATE_SET),
e.switch().withEndpoint("l2").setAccess("state", ea.STATE_SET),
e.switch().withEndpoint("l3").setAccess("state", ea.STATE_SET),
e.switch().withEndpoint("l4").setAccess("state", ea.STATE_SET),
],
fromZigbee: [legacy.fromZigbee.tuya_switch],
toZigbee: [legacy.toZigbee.tuya_switch_state],
meta: { multiEndpoint: true },
whiteLabel: [
{ vendor: "Norklmes", model: "MKS-CM-W5" },
{ vendor: "Somgoms", model: "ZSQB-SMB-ZB" },
{ vendor: "Moes", model: "WS-EUB1-ZG" },
{ vendor: "AVATTO", model: "ZGB-WS-EU" },
],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
if (device.getEndpoint(2))
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
if (device.getEndpoint(3))
await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ["genOnOff"]);
if (device.getEndpoint(4))
await reporting.bind(device.getEndpoint(4), coordinatorEndpoint, ["genOnOff"]);
},
endpoint: (device) => {
// Endpoint selection is made in tuya_switch_state
return { l1: 1, l2: 1, l3: 1, l4: 1 };
},
},
{
zigbeeModel: ["TS0301"],
model: "TS0301",
vendor: "Tuya",
description: "Cover",
extend: [m.battery(), m.windowCovering({ controls: ["lift"] })],
whiteLabel: [tuya.whitelabel("Yookee", "D10110_1", "Smart blind", ["_TZE200_9caxna4s"])],
},
{
fingerprint: tuya.fingerprint("TS0301", ["_TZE210_m6lwazh9"]),
model: "TS0301_cover_1",
vendor: "Tuya",
description: "Cover motor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
options: [exposes.options.invert_cover()],
exposes: [
e.battery(),
te.coverPosition(),
e.enum("reverse_direction", ea.STATE_SET, ["forward", "back"]).withDescription("Reverse the motor direction"),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[2, "position", tuya.valueConverter.coverPosition],
[3, "position", tuya.valueConverter.coverPosition],
[
5,
"reverse_direction",
tuya.valueConverterBasic.lookup({
forward: tuya.enum(0),
back: tuya.enum(1),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0301", ["_TZE210_xgzzuerd"]),
model: "TS0301_cover_2",
vendor: "Tuya",
description: "Cover motor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
te.coverPosition(),
te.motorState(),
te.clickControl(),
te.slowMode(),
te.favoritePosition(),
te.coverLimit(),
te.motorDirection(),
te.coverType(),
e.battery(),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[3, "motor_state", tuya.valueConverter.motorState], // work_state
[7, "slow_mode", tuya.valueConverter.onOffEnumOn1], // mode: morning, night
// DP9 = set, DP8 = report
[9, "position", tuya.valueConverter.coverPosition],
[8, "position", tuya.valueConverter.coverPosition],
[11, "motor_direction", tuya.valueConverter.tubularMotorDirection], // control_back_mode
[13, "battery", tuya.valueConverter.raw],
[15, "cover_type", tuya.valueConverter.coverType], // curtain_type
[16, "cover_limit", tuya.valueConverter.coverLimit], // border
[19, "favorite_position", tuya.valueConverter.raw], // position_best
[20, "click_control", tuya.valueConverter.clickControl],
],
},
},
{
fingerprint: tuya.fingerprint("TS0301", ["_TZE210_inpjmc0h", "_TZE210_yqwse3h5"]),
model: "TS0301_dual_rail",
vendor: "Tuya",
description: "Top-down bottom-up dual motor shade",
extend: [tuya.modernExtend.tuyaBase({ dp: true }), m.deviceEndpoints({ endpoints: { bottom: 1, top: 1 } })],
exposes: [
te.coverPosition().withEndpoint("bottom").withDescription("Bottom rail"),
te.coverPosition().withEndpoint("top").withDescription("Top rail"),
e.battery(),
],
meta: {
tuyaDatapoints: [
// Bottom rail - DP109 is control, DP101 is set position, DP102 is current position
[109, "state_bottom", tuya.valueConverterBasic.lookup({ OPEN: tuya.enum(0), CLOSE: tuya.enum(1), STOP: tuya.enum(2) })],
[101, "position_bottom", tuya.valueConverter.coverPositionInverted],
[102, "position_bottom", tuya.valueConverter.coverPositionInverted],
// Top rail - DP1 is control, DP2 is set position, DP3 is current position
// Note: State commands are reversed for Home Assistant compatibility with top-down operation
[1, "state_top", tuya.valueConverterBasic.lookup({ OPEN: tuya.enum(1), CLOSE: tuya.enum(0), STOP: tuya.enum(2) })],
[2, "position_top", tuya.valueConverter.coverPosition],
[3, "position_top", tuya.valueConverter.coverPosition],
// Battery
[13, "battery", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0301", ["_TZE200_eatmkx5j"]),
model: "TS0301_dual_rail_2",
vendor: "Tuya",
description: "Top-down bottom-up single control shade",
whiteLabel: [tuya.whitelabel("Yoolax", "Day-Night Shade", "Top-down bottom-up day/night shade", ["_TZE200_eatmkx5j"])],
extend: [m.deviceEndpoints({ endpoints: { bottom: 1, top: 1 } }), m.battery()],
fromZigbee: [fzLocal.TS0301_dual_rail_2],
toZigbee: [tzLocal.TS0301_dual_rail_2],
exposes: [
e.cover_position().withDescription("Unified position"),
e.cover_position().withEndpoint("bottom").withDescription("Bottom rail"),
e.cover_position().withEndpoint("top").withDescription("Top rail"),
],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["closuresWindowCovering"]);
await reporting.currentPositionLiftPercentage(endpoint);
},
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE200_aqnazj70",
"_TZE200_di3tfv5b",
"_TZE200_mexisfik",
"_TZE204_mexisfik",
"_TZE204_6wi2mope",
"_TZE200_6wi2mope",
"_TZE204_iik0pquw",
"_TZE204_aagrxlbd",
"_TZE204_f5efvtbv",
"_TZE284_f5efvtbv",
"_TZE204_lbhh5o6z",
"_TZE284_lbhh5o6z",
]),
model: "TS0601_switch_4_gang_1",
vendor: "Tuya",
description: "4 gang switch",
exposes: [
tuya.exposes.switch().withEndpoint("l1"),
tuya.exposes.switch().withEndpoint("l2"),
tuya.exposes.switch().withEndpoint("l3"),
tuya.exposes.switch().withEndpoint("l4"),
],
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
whiteLabel: [
tuya.whitelabel("ZYXH", "TY-04Z", "4 gang switch", ["_TZE204_iik0pquw"]),
{ vendor: "Norklmes", model: "MKS-CM-W5" },
{ vendor: "Somgoms", model: "ZSQB-SMB-ZB" },
{ vendor: "Moes", model: "WS-EUB1-ZG" },
{ vendor: "AVATTO", model: "ZGB-WS-EU" },
tuya.whitelabel("AVATTO", "WSMD-4", "4 gang switch", ["_TZE204_f5efvtbv", "_TZE284_f5efvtbv"]),
tuya.whitelabel("AVATTO", "ZWSMD-4", "4 gang switch", ["_TZE204_lbhh5o6z", "_TZE284_lbhh5o6z"]),
tuya.whitelabel("Tuya", "MG-ZG04W", "4 gang switch", ["_TZE204_mexisfik"]),
],
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
[4, "state_l4", tuya.valueConverter.onOff],
],
},
endpoint: (device) => {
return { l1: 1, l2: 1, l3: 1, l4: 1 };
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_jwsjbxjs", "_TZE200_leaqthqq"]),
model: "TS0601_switch_5_gang",
vendor: "Tuya",
description: "5 gang switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.switch().withEndpoint("l1"),
tuya.exposes.switch().withEndpoint("l2"),
tuya.exposes.switch().withEndpoint("l3"),
tuya.exposes.switch().withEndpoint("l4"),
tuya.exposes.switch().withEndpoint("l5"),
],
endpoint: (device) => {
return { l1: 1, l2: 1, l3: 1, l4: 1, l5: 1 };
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
[4, "state_l4", tuya.valueConverter.onOff],
[5, "state_l5", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE200_mwvfvw8g",
"_TZE200_wnp4d4va",
"_TZE200_cduqh1l0",
"_TZE204_cduqh1l0",
"_TZE200_emxxanvi",
"_TZE204_g4au0afs",
"_TZE204_w1wwxoja",
"_TZE204_lmgrbuwf",
"_TZE284_tdhnhhiy",
"_TZE204_wskr3up8",
"_TZE204_gxbdnfrh",
"_TZE284_g1enhdsi",
"_TZE284_r731zlxk",
]),
model: "TS0601_switch_6_gang",
vendor: "Tuya",
description: "6 gang switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff"]);
device.powerSource = "Mains (single phase)";
device.save();
},
exposes: [
tuya.exposes.switch().withEndpoint("l1"),
tuya.exposes.switch().withEndpoint("l2"),
tuya.exposes.switch().withEndpoint("l3"),
tuya.exposes.switch().withEndpoint("l4"),
tuya.exposes.switch().withEndpoint("l5"),
tuya.exposes.switch().withEndpoint("l6"),
tuya.exposes.powerOnBehavior(),
tuya.exposes.indicatorModeNoneRelayPos(),
],
endpoint: (device) => {
return { l1: 1, l2: 1, l3: 1, l4: 1, l5: 1, l6: 1 };
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
[4, "state_l4", tuya.valueConverter.onOff],
[5, "state_l5", tuya.valueConverter.onOff],
[6, "state_l6", tuya.valueConverter.onOff],
[
14,
"power_on_behavior",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
on: tuya.enum(1),
memory: tuya.enum(2),
}),
],
[
15,
"indicator_mode",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
relay: tuya.enum(1),
pos: tuya.enum(2),
}),
],
],
},
whiteLabel: [
tuya.whitelabel("Mercator Ikuü", "SSW06G", "6 Gang switch", ["_TZE200_wnp4d4va"]),
tuya.whitelabel("Nova Digital", "NTZB-04-W-B", "4 gang switch with 2 gang socket 4x4", ["_TZE204_lmgrbuwf"]),
tuya.whitelabel("Nova Digital", "SYZB-6W", "6 gang switch 4x4", ["_TZE284_tdhnhhiy"]),
tuya.whitelabel("Nova Digital", "FZB-6", "6 gang switch 4x4", ["_TZE204_wskr3up8"]),
tuya.whitelabel("Nova Digital", "SA-6", "Safira smart switch - 6 gang", ["_TZE204_gxbdnfrh"]),
tuya.whitelabel("Ekaza", "EKAT-T3074-6WZ", "6 gang switch", ["_TZE284_g1enhdsi"]),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_hyssaqjk"]),
model: "QZ-4x4-6 W/B",
vendor: "Nova Digital",
description: "Quartzo 6 gang switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.switch().withEndpoint("l1"),
tuya.exposes.switch().withEndpoint("l2"),
tuya.exposes.switch().withEndpoint("l3"),
tuya.exposes.switch().withEndpoint("l4"),
tuya.exposes.switch().withEndpoint("l5"),
tuya.exposes.switch().withEndpoint("l6"),
e.binary("backlight_switch", ea.STATE_SET, "ON", "OFF").withDescription("Turns the backlight on or off"),
e.power_on_behavior(["off", "on", "previous"]).withAccess(ea.STATE_SET),
],
endpoint: (device) => {
return { l1: 1, l2: 1, l3: 1, l4: 1, l5: 1, l6: 1 };
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
[4, "state_l4", tuya.valueConverter.onOff],
[5, "state_l5", tuya.valueConverter.onOff],
[6, "state_l6", tuya.valueConverter.onOff],
[14, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum],
[16, "backlight_switch", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_raz9qavg"]),
model: "KRC-103",
vendor: "Videosec",
description: "6 gang kinetic switch actuator",
extend: [tuya.modernExtend.tuyaBase({ dp: true }), m.deviceEndpoints({ endpoints: { l1: 1, l2: 1, l3: 1, l4: 1, l5: 1, l6: 1 } })],
exposes: [
e.switch().withEndpoint("l1"),
e.switch().withEndpoint("l2"),
e.switch().withEndpoint("l3"),
e.switch().withEndpoint("l4"),
e.switch().withEndpoint("l5"),
e.switch().withEndpoint("l6"),
],
meta: {
tuyaDatapoints: [
[19, "state_l1", tuya.valueConverter.onOff],
[20, "state_l2", tuya.valueConverter.onOff],
[21, "state_l3", tuya.valueConverter.onOff],
[22, "state_l4", tuya.valueConverter.onOff],
[23, "state_l5", tuya.valueConverter.onOff],
[24, "state_l6", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_rqhnxkqu"]),
model: "TO-6 W/B",
vendor: "Nova Digital",
description: "Topazio 6 gang Zigbee wall switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "1970" })],
exposes: [
tuya.exposes.switch().withEndpoint("l1"),
tuya.exposes.switch().withEndpoint("l2"),
tuya.exposes.switch().withEndpoint("l3"),
tuya.exposes.switch().withEndpoint("l4"),
tuya.exposes.switch().withEndpoint("l5"),
tuya.exposes.switch().withEndpoint("l6"),
tuya.exposes.backlightModeOffOn().withAccess(ea.STATE_SET).withDescription("Enables/disables the button backlight"),
e.enum("indicator_status", ea.STATE_SET, ["off", "relay", "invert"]).withDescription("Controls the LED indicator mode"),
e.power_on_behavior(["off", "on", "previous"]).withAccess(ea.STATE_SET),
e.binary("induction", ea.STATE_SET, "ON", "OFF").withDescription("Enables/disables capacitive induction for the buttons"),
e.enum("vibration_gear", ea.STATE_SET, ["off", "low", "medium", "high"]).withDescription("Controls the vibration feedback intensity"),
e.enum("mode_1", ea.STATE_SET, ["switch_1", "scene_1"]).withDescription("Button 1 mode"),
e.enum("mode_2", ea.STATE_SET, ["switch_2", "scene_2"]).withDescription("Button 2 mode"),
e.enum("mode_3", ea.STATE_SET, ["switch_3", "scene_3"]).withDescription("Button 3 mode"),
e.enum("mode_4", ea.STATE_SET, ["switch_4", "scene_4"]).withDescription("Button 4 mode"),
e.enum("mode_5", ea.STATE_SET, ["switch_5", "scene_5"]).withDescription("Button 5 mode"),
e.enum("mode_6", ea.STATE_SET, ["switch_6", "scene_6"]).withDescription("Button 6 mode"),
e.action(["scene_1", "scene_2", "scene_3", "scene_4", "scene_5", "scene_6"]),
e
.numeric("countdown_l1", ea.STATE_SET)
.withValueMin(0)
.withValueMax(43200)
.withValueStep(1)
.withUnit("s")
.withDescription("Countdown timer for channel 1"),
e
.numeric("countdown_l2", ea.STATE_SET)
.withValueMin(0)
.withValueMax(43200)
.withValueStep(1)
.withUnit("s")
.withDescription("Countdown timer for channel 2"),
e
.numeric("countdown_l3", ea.STATE_SET)
.withValueMin(0)
.withValueMax(43200)
.withValueStep(1)
.withUnit("s")
.withDescription("Countdown timer for channel 3"),
e
.numeric("countdown_l4", ea.STATE_SET)
.withValueMin(0)
.withValueMax(43200)
.withValueStep(1)
.withUnit("s")
.withDescription("Countdown timer for channel 4"),
e
.numeric("countdown_l5", ea.STATE_SET)
.withValueMin(0)
.withValueMax(43200)
.withValueStep(1)
.withUnit("s")
.withDescription("Countdown timer for channel 5"),
e
.numeric("countdown_l6", ea.STATE_SET)
.withValueMin(0)
.withValueMax(43200)
.withValueStep(1)
.withUnit("s")
.withDescription("Countdown timer for channel 6"),
e
.numeric("inching_l1", ea.STATE_SET)
.withValueMin(0)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Inching/pulse duration for channel 1"),
e
.numeric("inching_l2", ea.STATE_SET)
.withValueMin(0)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Inching/pulse duration for channel 2"),
e
.numeric("inching_l3", ea.STATE_SET)
.withValueMin(0)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Inching/pulse duration for channel 3"),
e
.numeric("inching_l4", ea.STATE_SET)
.withValueMin(0)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Inching/pulse duration for channel 4"),
e
.numeric("inching_l5", ea.STATE_SET)
.withValueMin(0)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Inching/pulse duration for channel 5"),
e
.numeric("inching_l6", ea.STATE_SET)
.withValueMin(0)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Inching/pulse duration for channel 6"),
],
endpoint: (device) => {
return { l1: 1, l2: 1, l3: 1, l4: 1, l5: 1, l6: 1 };
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "action", tuya.valueConverterBasic.lookup({ scene_1: tuya.enum(0) })],
[2, "action", tuya.valueConverterBasic.lookup({ scene_2: tuya.enum(0) })],
[3, "action", tuya.valueConverterBasic.lookup({ scene_3: tuya.enum(0) })],
[4, "action", tuya.valueConverterBasic.lookup({ scene_4: tuya.enum(0) })],
[5, "action", tuya.valueConverterBasic.lookup({ scene_5: tuya.enum(0) })],
[6, "action", tuya.valueConverterBasic.lookup({ scene_6: tuya.enum(0) })],
[18, "mode_1", tuya.valueConverterBasic.lookup({ switch_1: tuya.enum(0), scene_1: tuya.enum(1) })],
[19, "mode_2", tuya.valueConverterBasic.lookup({ switch_2: tuya.enum(0), scene_2: tuya.enum(1) })],
[20, "mode_3", tuya.valueConverterBasic.lookup({ switch_3: tuya.enum(0), scene_3: tuya.enum(1) })],
[21, "mode_4", tuya.valueConverterBasic.lookup({ switch_4: tuya.enum(0), scene_4: tuya.enum(1) })],
[22, "mode_5", tuya.valueConverterBasic.lookup({ switch_5: tuya.enum(0), scene_5: tuya.enum(1) })],
[23, "mode_6", tuya.valueConverterBasic.lookup({ switch_6: tuya.enum(0), scene_6: tuya.enum(1) })],
[24, "state_l1", tuya.valueConverter.onOff],
[25, "state_l2", tuya.valueConverter.onOff],
[26, "state_l3", tuya.valueConverter.onOff],
[27, "state_l4", tuya.valueConverter.onOff],
[28, "state_l5", tuya.valueConverter.onOff],
[29, "state_l6", tuya.valueConverter.onOff],
[30, "countdown_l1", tuya.valueConverter.raw],
[31, "countdown_l2", tuya.valueConverter.raw],
[32, "countdown_l3", tuya.valueConverter.raw],
[33, "countdown_l4", tuya.valueConverter.raw],
[34, "countdown_l5", tuya.valueConverter.raw],
[35, "countdown_l6", tuya.valueConverter.raw],
[36, "backlight_mode", tuya.valueConverter.onOff],
[
37,
"indicator_status",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
relay: tuya.enum(1),
invert: tuya.enum(2),
}),
],
[38, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum],
[103, "induction", tuya.valueConverter.onOff],
[
104,
"vibration_gear",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
low: tuya.enum(1),
medium: tuya.enum(2),
high: tuya.enum(3),
}),
],
[105, "inching_l1", tuya.valueConverter.raw],
[106, "inching_l2", tuya.valueConverter.raw],
[107, "inching_l3", tuya.valueConverter.raw],
[108, "inching_l4", tuya.valueConverter.raw],
[109, "inching_l5", tuya.valueConverter.raw],
[110, "inching_l6", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_3xnyj4ga"]),
model: "TO-WK-1W/B",
vendor: "Nova Digital",
description: "Topazio 1 gang switch with socket",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "1970" })],
exposes: [
e.switch().withEndpoint("l1"),
e.switch().withEndpoint("l2"),
e.enum("indicator_mode", ea.STATE_SET, ["none", "relay", "pos"]).withDescription("Controls the indicator LED mode"),
e
.power_on_behavior(["off", "on", "previous"])
.withAccess(ea.STATE_SET)
.withDescription("Controls the behavior when the device is powered on after power loss"),
e.binary("backlight_switch", ea.STATE_SET, "ON", "OFF").withDescription("Turns the button backlight on or off"),
e.binary("induction", ea.STATE_SET, "ON", "OFF").withDescription("Enables/disables capacitive induction for the button"),
e.enum("vibration", ea.STATE_SET, ["off", "low", "medium", "high"]).withDescription("Controls the vibration feedback intensity"),
e.energy().withDescription("Accumulated energy"),
e.current().withDescription("Current"),
e.power().withDescription("Power"),
e.voltage().withDescription("Voltage"),
e
.numeric("countdown_l1", ea.STATE_SET)
.withValueMin(0)
.withValueMax(43200)
.withValueStep(1)
.withUnit("s")
.withDescription("Countdown timer for channel 1"),
e
.numeric("countdown_l2", ea.STATE_SET)
.withValueMin(0)
.withValueMax(43200)
.withValueStep(1)
.withUnit("s")
.withDescription("Countdown timer for channel 2"),
e
.numeric("inching_l1", ea.STATE_SET)
.withValueMin(0)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Inching/pulse duration for channel 1"),
],
endpoint: () => {
return {
l1: 1,
l2: 1,
};
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[7, "countdown_l1", tuya.valueConverter.raw],
[8, "countdown_l2", tuya.valueConverter.raw],
[14, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum],
[
15,
"indicator_mode",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
relay: tuya.enum(1),
pos: tuya.enum(2),
}),
],
[16, "backlight_switch", tuya.valueConverter.onOff],
[19, "inching_l1", convLocal.novaDigitalToWkInching],
[20, "energy", tuya.valueConverter.divideBy1000],
[21, "current", tuya.valueConverter.divideBy1000],
[22, "power", tuya.valueConverter.divideBy10],
[23, "voltage", tuya.valueConverter.divideBy10],
[101, "induction", tuya.valueConverter.onOff],
[
102,
"vibration",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
low: tuya.enum(1),
medium: tuya.enum(2),
high: tuya.enum(3),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_exfilann"]),
model: "TO-WK-2W/B",
vendor: "Nova Digital",
description: "Topazio 2 gang switch with socket",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "1970" })],
exposes: [
e.switch().withEndpoint("l1"),
e.switch().withEndpoint("l2"),
e.switch().withEndpoint("l3"),
e
.numeric("countdown_l1", ea.STATE_SET)
.withValueMin(0)
.withValueMax(43200)
.withValueStep(1)
.withUnit("s")
.withDescription("Countdown timer for channel 1"),
e
.numeric("countdown_l2", ea.STATE_SET)
.withValueMin(0)
.withValueMax(43200)
.withValueStep(1)
.withUnit("s")
.withDescription("Countdown timer for channel 2"),
e
.numeric("countdown_l3", ea.STATE_SET)
.withValueMin(0)
.withValueMax(43200)
.withValueStep(1)
.withUnit("s")
.withDescription("Countdown timer for channel 3"),
e
.power_on_behavior(["off", "on", "previous"])
.withAccess(ea.STATE_SET)
.withDescription("Controls the behavior when the device is powered on after power loss"),
e.enum("indicator_mode", ea.STATE_SET, ["none", "relay", "pos"]).withDescription("Controls the indicator LED mode"),
e.binary("backlight_switch", ea.STATE_SET, "ON", "OFF").withDescription("Turns the button backlight on or off"),
e
.numeric("inching_l1", ea.STATE_SET)
.withValueMin(0)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Inching/pulse duration for channel 1"),
e
.numeric("inching_l2", ea.STATE_SET)
.withValueMin(0)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Inching/pulse duration for channel 2"),
e.energy().withDescription("Accumulated energy"),
e.current().withDescription("Current"),
e.power().withDescription("Power"),
e.voltage().withDescription("Voltage"),
e.binary("induction", ea.STATE_SET, "ON", "OFF").withDescription("Enables/disables capacitive induction for the button"),
e.enum("vibration", ea.STATE_SET, ["off", "low", "medium", "high"]).withDescription("Controls the vibration feedback intensity"),
],
endpoint: () => {
return {
l1: 1,
l2: 1,
l3: 1,
};
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
[7, "countdown_l1", tuya.valueConverter.raw],
[8, "countdown_l2", tuya.valueConverter.raw],
[9, "countdown_l3", tuya.valueConverter.raw],
[14, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum],
[
15,
"indicator_mode",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
relay: tuya.enum(1),
pos: tuya.enum(2),
}),
],
[16, "backlight_switch", tuya.valueConverter.onOff],
[19, null, convLocal.novaDigitalToWk2InchingFrom],
[19, "inching_l1", convLocal.novaDigitalToWk2InchingTo(1)],
[19, "inching_l2", convLocal.novaDigitalToWk2InchingTo(2)],
[20, "energy", tuya.valueConverter.divideBy1000],
[21, "current", tuya.valueConverter.divideBy1000],
[22, "power", tuya.valueConverter.divideBy10],
[23, "voltage", tuya.valueConverter.divideBy10],
[101, "induction", tuya.valueConverter.onOff],
[
102,
"vibration",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
low: tuya.enum(1),
medium: tuya.enum(2),
high: tuya.enum(3),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_5yah8qx4"]),
model: "TO-DM-W/B",
vendor: "Nova Digital",
description: "Topazio 1 gang Zigbee dimmer switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "1970" })],
exposes: [
e.light_brightness(),
e
.numeric("brightness_min", ea.STATE_SET)
.withValueMin(1)
.withValueMax(100)
.withValueStep(1)
.withUnit("%")
.withDescription("Minimum brightness limit"),
e
.numeric("brightness_max", ea.STATE_SET)
.withValueMin(1)
.withValueMax(100)
.withValueStep(1)
.withUnit("%")
.withDescription("Maximum brightness limit"),
e.enum("light_type", ea.STATE_SET, ["led", "incandescent", "halogen"]).withDescription("Type of connected light load"),
e
.power_on_behavior(["off", "on", "previous"])
.withAccess(ea.STATE_SET)
.withDescription("Controls the behavior when the device is powered on after power loss"),
e.enum("indicator_mode", ea.STATE_SET, ["none", "relay", "pos"]).withDescription("Controls the indicator LED mode"),
e.binary("backlight_switch", ea.STATE_SET, "ON", "OFF").withDescription("Turns the button backlight on or off"),
e
.numeric("countdown", ea.STATE_SET)
.withValueMin(0)
.withValueMax(86400)
.withValueStep(1)
.withUnit("s")
.withDescription("Countdown timer"),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[2, "brightness", convLocal.novaDigitalToDmBrightness],
[3, "brightness_min", convLocal.novaDigitalToDmBrightnessPercent],
[
4,
"light_type",
tuya.valueConverterBasic.lookup({
led: tuya.enum(0),
incandescent: tuya.enum(1),
halogen: tuya.enum(2),
}),
],
[5, "brightness_max", convLocal.novaDigitalToDmBrightnessPercent],
[6, "countdown", tuya.valueConverter.raw],
[14, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum],
[
21,
"indicator_mode",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
relay: tuya.enum(1),
pos: tuya.enum(2),
}),
],
[26, "backlight_switch", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_ojtqawav", "_TZE204_gbagoilo", "_TZE200_ojtqawav"]),
model: "TS0601_switch_1_gang",
vendor: "Tuya",
description: "1 gang switch",
exposes: [e.switch().setAccess("state", ea.STATE_SET)],
fromZigbee: [legacy.fromZigbee.tuya_switch],
toZigbee: [legacy.toZigbee.tuya_switch_state],
whiteLabel: [tuya.whitelabel("Shawader", "SMKG-1KNL-US/TZB-W", "1 gang switch", ["_TZE204_ojtqawav"])],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_gbagoilo", "_TZE284_xnwxmj8z"]),
model: "MG-ZG01W",
vendor: "Tuya",
description: "1 gang switch with power meter",
exposes: [
tuya.exposes.switch(),
tuya.exposes.countdown(),
e.voltage(),
e.current(),
e.power(),
e.power_on_behavior().withAccess(ea.STATE_SET),
tuya.exposes.backlightModeOffOn().withAccess(ea.STATE_SET),
],
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[7, "countdown", tuya.valueConverter.countdown],
[14, "power_on_behavior", tuya.valueConverter.powerOnBehavior],
[16, "backlight_mode", tuya.valueConverter.onOff],
[21, "current", tuya.valueConverter.divideBy1000],
[22, "power", tuya.valueConverter.divideBy10],
[23, "voltage", tuya.valueConverter.divideBy10],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE200_nkjintbl",
"_TZE200_ji1gn7rw",
"_TZE200_3t91nb6k",
"_TZE204_wvovwe9h",
"_TZE200_wvovwe9h",
"_TZE204_3t91nb6k",
]),
model: "TS0601_switch_2_gang",
vendor: "Tuya",
description: "2 gang switch",
exposes: [e.switch().withEndpoint("l1").setAccess("state", ea.STATE_SET), e.switch().withEndpoint("l2").setAccess("state", ea.STATE_SET)],
fromZigbee: [legacy.fromZigbee.tuya_switch],
toZigbee: [legacy.toZigbee.tuya_switch_state],
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
if (device.getEndpoint(2))
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
},
endpoint: (device) => {
// Endpoint selection is made in tuya_switch_state
return { l1: 1, l2: 1 };
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_nh9m9emk"]),
model: "MG-ZG02W",
vendor: "Tuya",
description: "2 gang switch",
exposes: [e.switch().withEndpoint("l1").setAccess("state", ea.STATE_SET), e.switch().withEndpoint("l2").setAccess("state", ea.STATE_SET)],
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
],
},
endpoint: (device) => {
return { l1: 1, l2: 1 };
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_kyfqmmyl", "_TZE200_2hf7x9n3", "_TZE204_atpwqgml", "_TZE200_bynnczcb", "_TZE200_atpwqgml"]),
model: "TS0601_switch_3_gang",
vendor: "Tuya",
description: "3 gang switch",
whiteLabel: [
{
vendor: "Nova Digital",
model: "WS-US-ZB",
description: "Interruptor touch Zigbee 3 Teclas",
},
],
exposes: [
e.switch().withEndpoint("l1").setAccess("state", ea.STATE_SET),
e.switch().withEndpoint("l2").setAccess("state", ea.STATE_SET),
e.switch().withEndpoint("l3").setAccess("state", ea.STATE_SET),
],
fromZigbee: [legacy.fromZigbee.tuya_switch],
toZigbee: [legacy.toZigbee.tuya_switch_state],
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
},
endpoint: (device) => {
// Endpoint selection is made in tuya_switch_state
return { l1: 1, l2: 1, l3: 1 };
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_2imwyigp", "_TZE200_2imwyigp", "_TZE200_go3tvswy"]),
model: "MG-ZG03W",
vendor: "Tuya",
description: "3 gang switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.switch().withEndpoint("l1").setAccess("state", ea.STATE_SET),
e.switch().withEndpoint("l2").setAccess("state", ea.STATE_SET),
e.switch().withEndpoint("l3").setAccess("state", ea.STATE_SET),
],
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
],
},
endpoint: (device) => {
return { l1: 1, l2: 1, l3: 1 };
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_oyti2ums"]),
model: "MG-GPO04ZSLP",
vendor: "Tuya",
description: "2 x socket + 1 x light with master switch and metering",
extend: [tuya.modernExtend.tuyaBase({ dp: true }), m.deviceEndpoints({ endpoints: { master: 1, light: 1, left: 1, right: 1 } })],
exposes: [
e.switch().withEndpoint("master").setAccess("state", ea.STATE_SET).withDescription("Master switch controlling all relays"),
e.switch().withEndpoint("light").setAccess("state", ea.STATE_SET).withDescription("Light relay"),
e.switch().withEndpoint("left").setAccess("state", ea.STATE_SET).withDescription("Left socket"),
e.switch().withEndpoint("right").setAccess("state", ea.STATE_SET).withDescription("Right socket"),
e.numeric("voltage", ea.STATE).withUnit("V").withDescription("Line voltage reported by the outlet"),
e.numeric("current", ea.STATE).withUnit("A").withDescription("Line current reported by the outlet"),
e.numeric("energy_wh", ea.STATE).withUnit("Wh").withDescription("Accumulated energy (raw Wh counter)"),
],
meta: {
tuyaDatapoints: [
[1, "state_right", tuya.valueConverter.onOff],
[2, "state_light", tuya.valueConverter.onOff],
[3, "state_left", tuya.valueConverter.onOff],
[13, "state_master", tuya.valueConverter.onOff],
[21, "current", tuya.valueConverter.divideBy1000],
[22, "energy_wh", tuya.valueConverter.raw],
[23, "voltage", tuya.valueConverter.divideBy10],
],
},
},
{
fingerprint: tuya.fingerprint("TS0215A", [
"_TZ3000_4fsgukof",
"_TZ3000_wr2ucaj9",
"_TZ3000_zsh6uat3",
"_TZ3000_tj4pwzzm",
"_TZ3000_2izubafb",
"_TZ3000_pkfazisv",
"_TZ3000_0dumfk2z",
"_TZ3000_ssp0maqm",
"_TZ3000_p3fph1go",
"_TZ3000_9r5jaajv",
"_TZ3000_nxdziqzc",
"_TZ3000_irwuzilv",
]),
model: "TS0215A_sos",
vendor: "Tuya",
description: "SOS button",
fromZigbee: [fz.command_emergency, fz.battery],
exposes: [e.battery(), e.battery_voltage(), e.action(["emergency"])],
toZigbee: [],
whiteLabel: [
tuya.whitelabel("Tuya", "BT400B", "Zigbee Panic Button", ["_TZ3000_0dumfk2z"]),
tuya.whitelabel("Woox", "R7052", "Smart SOS button", ["_TZ3000_ssp0maqm"]),
tuya.whitelabel("Nedis", "ZBPB10BK", "Smart Zigbee SOS / Panic Button", ["_TZ3000_irwuzilv"]),
],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genPowerCfg", "genTime", "genBasic", "ssIasAce", "ssIasZone"]);
await reporting.batteryPercentageRemaining(endpoint);
await reporting.batteryVoltage(endpoint);
},
},
{
fingerprint: tuya.fingerprint("TS0215A", [
"_TZ3000_p6ju8myv",
"_TZ3000_0zrccfgx",
"_TZ3000_fsiepnrh",
"_TZ3000_ug1vtuzn",
"_TZ3000_eo3dttwe",
"_TZ3000_jwcixnrz",
"_TZ3000_u2bbagu4",
"_TZ3000_8utxxtzr",
]),
model: "TS0215A_remote",
vendor: "Tuya",
description: "Security remote control",
fromZigbee: [fz.command_arm, fz.command_emergency, fz.battery],
exposes: [e.battery(), e.action(["disarm", "arm_day_zones", "arm_night_zones", "arm_all_zones", "exit_delay", "emergency"])],
toZigbee: [],
whiteLabel: [
{ vendor: "Woox", model: "R7054" },
{ vendor: "Nedis", model: "ZBRC10WT" },
],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genPowerCfg", "genTime", "genBasic", "ssIasAce", "ssIasZone"]);
},
},
{
fingerprint: tuya.fingerprint("TS0503A", ["_TZ3000_obacbukl"]),
model: "TS0503A",
vendor: "Tuya",
description: "Led strip controller",
extend: [tuya.modernExtend.tuyaLight({ color: true })],
},
{
zigbeeModel: ["TS0503A"],
model: "TYZS1L",
vendor: "Tuya",
description: "Led strip controller HSB",
extend: [tuyaLight()],
fromZigbee: [fz.on_off, tuya.fz.led_controller],
toZigbee: [tuya.tz.led_controller, tz.ignore_transition, tz.ignore_rate],
},
{
zigbeeModel: ["TS0502A"],
model: "TS0502A",
vendor: "Tuya",
description: "Light controller",
extend: [tuya.modernExtend.tuyaLight({ colorTemp: { range: [153, 500] } })],
whiteLabel: [
tuya.whitelabel("Lidl", "HG06492B/HG08130B", "Livarno Home E14 candle CCT", ["_TZ3000_oborybow"]),
tuya.whitelabel("Lidl", "HG06492A/HG08130A", "Livarno Lux GU10 spot CCT", ["_TZ3000_el5kt5im"]),
tuya.whitelabel("Lidl", "HG06492C/HG08130C/HG09154C", "Livarno Lux E27 bulb CCT", ["_TZ3000_49qchf10"]),
tuya.whitelabel("Lidl", "14147206L", "Livarno Lux ceiling light", ["_TZ3000_rylaozuc", "_TZ3000_5fkufhn1"]),
tuya.whitelabel("Lidl", "14153905L", "Livarno Home LED floor lamp", ["_TZ3000_8uaoilu9"]),
],
configure: (device, coordinatorEndpoint) => {
device.getEndpoint(1).saveClusterAttributeKeyValue("lightingColorCtrl", {
colorCapabilities: 16,
});
},
},
{
fingerprint: tuya.fingerprint("TS0503B", ["_TZB210_lmqquxus"]),
zigbeeModel: ["TS0502B"],
model: "TS0502B",
vendor: "Tuya",
description: "Light controller",
fromZigbee: [fzLocal.FUT035ZrfColorTemp],
whiteLabel: [
tuya.whitelabel("Mercator Ikuü", "SMI7040", "Ford Batten Light", ["_TZ3000_zw7wr5uo"]),
{
vendor: "Mercator Ikuü",
model: "SMD9300",
description: "Donovan Panel Light",
},
tuya.whitelabel("Aldi", "F122SB62H22A4.5W", "LIGHTWAY smart home LED-lamp - filament", ["_TZ3000_g1glzzfk"]),
tuya.whitelabel("Sibling", "Light-ZSLL", "Linear ceiling LED luminaire", ["_TZ3000_bumeauzp"]),
tuya.whitelabel("MiBoxer", "FUT035Z+", "Dual white LED controller", [
"_TZ3210_frm6149r",
"_TZ3210_jtifm80b",
"_TZ3210_xwqng7ol",
"_TZB210_lmqquxus",
"_TZB210_ue01a0s2",
]),
tuya.whitelabel("MiBoxer", "E2-ZR", "2 in 1 LED controller", ["_TZB210_ayx58ft5", "_TZB210_eiwanbeb"]),
tuya.whitelabel("MiBoxer", "PZ2", "2 in 1 LED controller", ["_TZB210_0bkzabht"]),
tuya.whitelabel("Lidl", "14156408L", "Livarno Lux smart LED ceiling light", ["_TZ3210_c2iwpxf1"]),
tuya.whitelabel("EcoDim", "ED-10032", "Zigbee LED filament lamp dimmable E27, bulb A60, Smokey 2000K-4000K", ["_TZ3210_09hzmirw"]),
tuya.whitelabel("Mercator Ikuü", "SMCL01-ZB", "Ikon ceiling light", ["_TZ3000_6dwfra5l"]),
tuya.whitelabel("LUUMR", "10024773", "Smart LED C35 matt E14 4,2 W", ["_TZ3210_claeh5ds"]),
],
extend: [
tuya.modernExtend.tuyaLight({
colorTemp: { range: [153, 500] },
configureReporting: true,
}),
],
configure: async (device, coordinatorEndpoint) => {
device.getEndpoint(1).saveClusterAttributeKeyValue("lightingColorCtrl", {
colorCapabilities: 16,
});
await tuya.configureMagicPacket(device, coordinatorEndpoint);
},
},
{
zigbeeModel: ["TS0504A"],
model: "TS0504A",
vendor: "Tuya",
description: "RGBW LED controller",
extend: [
tuya.modernExtend.tuyaLight({
colorTemp: { range: undefined },
color: true,
}),
],
},
{
fingerprint: tuya.fingerprint("TS0505A", ["_TZ3000_sosdczdl"]),
model: "TS0505A_led",
vendor: "Tuya",
description: "RGB+CCT LED",
extend: [tuyaLight({ colorTemp: { range: [153, 500] } })],
toZigbee: [tz.on_off, tuya.tz.led_control],
fromZigbee: [fz.on_off, tuya.fz.led_controller, fz.brightness],
},
{
zigbeeModel: ["TS0505A"],
model: "TS0505A",
vendor: "Tuya",
description: "RGB+CCT light controller",
extend: [
tuya.modernExtend.tuyaLight({
colorTemp: { range: undefined },
color: true,
}),
],
whiteLabel: [
tuya.whitelabel("Lidl", "HG06106B", "Livarno Lux E14 candle RGB", ["_TZ3000_odygigth"]),
tuya.whitelabel("Lidl", "HG06106A", "Livarno Lux GU10 spot RGB", ["_TZ3000_kdpxju99"]),
tuya.whitelabel("Lidl", "HG06106C", "Livarno Lux E27 bulb RGB", ["_TZ3000_dbou1ap4"]),
tuya.whitelabel("Lidl", "14148906L", "Livarno Lux mood light RGB+CCT", ["_TZ3000_9cpuaca6"]),
tuya.whitelabel("Lidl", "14149505L/14149506L_1", "Livarno Lux light bar RGB+CCT (black/white)", ["_TZ3000_gek6snaj"]),
tuya.whitelabel("Mycket", "MS-SP-LE27WRGB", "E27 RGBW bulb", ["_TZ3000_evag0pvn"]),
tuya.whitelabel("Lidl", "HG06104A", "Livarno Home RGB+CCT LED light strip 2m", ["_TZ3000_riwp3k79", "_TZ3000_riwp3k79"]),
],
configure: (device, coordinatorEndpoint) => {
device.getEndpoint(1).saveClusterAttributeKeyValue("lightingColorCtrl", {
colorCapabilities: 29,
});
},
},
{
zigbeeModel: ["TS0201", "SNTZ003", "TY0201"],
model: "TS0201",
vendor: "Tuya",
description: "Temperature & humidity sensor with display",
fromZigbee: [fzLocal.TS0201_battery, fz.temperature, fzLocal.TS0201_humidity],
toZigbee: [],
exposes: [e.battery(), e.temperature(), e.humidity(), e.battery_voltage()],
configure: tuya.configureMagicPacket,
ota: true,
whiteLabel: [
{ vendor: "BlitzWolf", model: "BW-IS4" },
tuya.whitelabel("Tuya", "TS0201_1", "Zigbee 3.0 temperature humidity sensor with display", ["_TZ3210_alxkwn0h"]),
tuya.whitelabel("Tuya", "ZTH01/ZTH02", "Temperature and humidity sensor", ["_TZ3000_0s1izerx"]),
tuya.whitelabel("Tuya", "ZY-ZTH02", "Temperature and humidity sensor - with or without display", [
"_TZ3000_v1w2k9dd",
"_TZ3000_rdhukkmi",
"Zbeacon",
]),
tuya.whitelabel("SEDEA", "eTH730", "Temperature and humidity sensor", ["_TZ3000_lqmvrwa2"]),
tuya.whitelabel("Moes", "ZSS-S01-TH", "Temperature and humidity sensor", ["_TZ3000_f2bw0b6k"]),
tuya.whitelabel("Danfoss", "014G2480", "Temperature and humidity sensor", ["_TZ3000_mxzo5rhf"]),
tuya.whitelabel("Tuya", "HS09", "Hanging temperature humidity sensor", ["_TZ3000_1twfmkcc"]),
tuya.whitelabel("Nedis", "ZBSC10WT", "Temperature and humidity sensor", ["_TZ3000_fie1dpkm"]),
tuya.whitelabel("Tuya", "TH09Z", "Temperature and humidity sensor", ["_TZ3000_isw9u95y", "_TZ3000_yupc0pb7"]),
tuya.whitelabel("Tuya", "ZTH05_1", "Temperature and humidity sensor", ["_TZ3000_bgsigers"]),
tuya.whitelabel("Tuya", "TS0201_2", "Temperature and humidity sensor", ["_TZ3000_yujem9ee"]),
],
},
{
fingerprint: tuya.fingerprint("TS0201", [
"_TZ3000_bguser20",
"_TZ3000_yd2e749y",
"_TZ3000_6uzkisv2",
"_TZ3000_xr3htd96",
"_TZ3000_fllyghyj",
"_TZ3000_saiqcn0y",
"_TZ3000_bjawzodf",
]),
model: "WSD500A",
vendor: "Tuya",
description: "Temperature & humidity sensor",
fromZigbee: [fzLocal.TS0201_battery, fz.temperature, fz.humidity],
toZigbee: [],
exposes: [e.battery(), e.temperature(), e.humidity(), e.battery_voltage()],
configure: tuya.configureMagicPacket,
whiteLabel: [tuya.whitelabel("Tuya", "TH02Z", "Temperature and humidity sensor", ["_TZ3000_fllyghyj", "_TZ3000_saiqcn0y"])],
meta: {
battery: {
// These sensors do send a Battery Percentage Remaining (0x0021)
// value, but is usually incorrect. For example, a coin battery tested
// with a load tester may show 80%, but report 2.5V / 1%. This voltage
// calculation matches what ZHA does by default.
// https://github.com/Koenkk/zigbee2mqtt/discussions/17337
// https://github.com/zigpy/zha-device-handlers/blob/c6ed94a52a469e72b32ece2a92d528060c7fd034/zhaquirks/__init__.py#L195-L228
voltageToPercentage: "3V_1500_2800",
},
},
},
{
fingerprint: [
...tuya.fingerprint("TS0201", ["_TZ3000_dowj6gyi", "_TZ3000_8ybe88nf", "_TZ3000_akqdg6g7"]),
{ manufacturerName: "_TZ3000_zl1kmjqx" },
],
model: "IH-K009",
vendor: "Tuya",
description: "Temperature & humidity sensor",
fromZigbee: [fzLocal.TS0201_battery, fz.temperature, fz.humidity],
extend: [m.battery()],
exposes: [e.temperature(), e.humidity(), e.battery_voltage()],
configure: tuya.configureMagicPacket,
whiteLabel: [tuya.whitelabel("Tuya", "RSH-HS06_1", "Temperature & humidity sensor", ["_TZ3000_zl1kmjqx"])],
},
{
fingerprint: tuya.fingerprint("SM0201", ["_TYZB01_cbiezpds", "_TYZB01_zqvwka4k"]),
model: "SM0201",
vendor: "Tuya",
description: "Temperature & humidity sensor with LED screen",
fromZigbee: [fz.battery, fz.temperature, fz.humidity],
toZigbee: [],
exposes: [e.battery(), e.temperature(), e.humidity(), e.battery_voltage()],
},
{
fingerprint: tuya.fingerprint("TS011F", [
"_TZ3000_3zofvcaa",
"_TZ3000_pvlvoxvt",
"_TZ3000_lqb7lcq9",
"_TZ3210_lqb7lcq9",
"_TZ3210_urjf5u18",
"_TZ3210_8n4dn1ne",
]),
model: "TS011F_2_gang_2_usb_wall",
vendor: "Tuya",
description: "2 gang 2 usb wall outlet",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
backlightModeLowMediumHigh: true,
endpoints: ["l1", "l2", "l3", "l4"],
childLock: true,
}),
],
endpoint: () => {
return { l1: 1, l2: 2, l3: 3, l4: 4 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
for (const endpointID of [1, 2, 3, 4]) {
const endpoint = device.getEndpoint(endpointID);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff"]);
await reporting.onOff(endpoint);
}
},
whiteLabel: [
tuya.whitelabel("Nova Digital", "NTS2-W-B", "2 gang 2 usb wall outlet 4x2", ["_TZ3000_lqb7lcq9"]),
tuya.whitelabel("Nova Digital", "SA-WK", "Safira Full Switch 1 gang + socket 20A + USB-A + USB-C 4x2", ["_TZ3210_lqb7lcq9"]),
tuya.whitelabel("AVATTO", "ZWOT12", "2 gang 2 usb wall outlet 4x2", ["_TZ3210_urjf5u18"]),
tuya.whitelabel("Coibeu", "ZB414", "2 gang 2 usb wall outlet 4x2", ["_TZ3210_8n4dn1ne"]),
],
},
{
fingerprint: tuya.fingerprint("TS011F", ["_TZ3000_bep7ccew"]),
model: "TS011F_2_gang_power",
vendor: "Tuya",
description: "2 gang socket with power monitoring and USB",
extend: [
tuyaMagicPacket(),
m.deviceEndpoints({
endpoints: { left: 1, right: 2 },
multiEndpointSkip: ["current", "voltage", "power", "energy"],
}),
m.onOff({ powerOnBehavior: false, endpointNames: ["left", "right"] }),
m.identify(),
m.electricityMeter(),
],
},
{
zigbeeModel: ["TS0041"],
model: "TS0041",
vendor: "Tuya",
description: "Wireless switch with 1 button",
extend: [tuyaBase()],
whiteLabel: [
{ vendor: "Smart9", model: "S9TSZGB" },
{ vendor: "Lonsonho", model: "TS0041" },
{ vendor: "Benexmart", model: "ZM-sui1" },
tuya.whitelabel("Tuya", "SH-SC07", "Button scene switch", ["_TZ3000_mrpevh8p", "_TZ3000_5bpeda8u", "_TZ3000_b4awzgct"]),
tuya.whitelabel("Tuya", "MINI-ZSB", "Smart button", ["_TZ3000_qgwcxxws"]),
tuya.whitelabel("Nous", "LZ4", "Wireless switch button", ["_TZ3000_6km7djcm"]),
tuya.whitelabel("Marmitek", "Push_LE", "Smart switch", ["_TZ3000_4upl1fcj"]),
tuya.whitelabel("Moes", "ZT-YK01", "Wireless switch with 1 button", ["_TZ3000_filhl5b7"]),
],
exposes: [e.battery(), e.action(["single", "double", "hold"])],
fromZigbee: [tuya.fz.on_off_action, fz.battery],
toZigbee: [],
configure: tuya.configureMagicPacket,
/*
* reporting.batteryPercentageRemaining removed as it was causing devices to fall of the network
* every 1 hour, with light flashing when it happened, extremely short battery life, 2 presses for
* action to register: https://github.com/Koenkk/zigbee2mqtt/issues/8072
* Initially wrapped in a try catch: https://github.com/Koenkk/zigbee2mqtt/issues/6313
*/
},
{
zigbeeModel: ["TS0041A"],
model: "TS0041A",
vendor: "Tuya",
description: "Wireless switch with 1 button",
whiteLabel: [
tuya.whitelabel("Cleverio", "SB100", "Wireless switch with 1 button", ["_TYZB01_4qw4rl1u"]),
tuya.whitelabel("Marmitek", "Push_ME", "Wireless switch with 1 button", ["_TYZB01_1xktopx6"]),
],
extend: [tuyaBase()],
exposes: [e.battery(), e.battery_voltage(), e.action(["single", "double", "hold"])],
fromZigbee: [tuya.fz.on_off_action, fz.battery],
toZigbee: [],
configure: tuya.configureMagicPacket,
/*
* reporting.batteryPercentageRemaining removed as it was causing devices to fall of the network
* every 1 hour, with light flashing when it happened, extremely short battery life, 2 presses for
* action to register: https://github.com/Koenkk/zigbee2mqtt/issues/8072
* Initially wrapped in a try catch: https://github.com/Koenkk/zigbee2mqtt/issues/6313
*/
},
{
zigbeeModel: ["TS0042"],
model: "TS0042",
vendor: "Tuya",
description: "Wireless switch with 2 buttons",
whiteLabel: [
{ vendor: "Smart9", model: "S9TSZGB" },
{ vendor: "Lonsonho", model: "TS0042" },
{ vendor: "ClickSmart+", model: "CSPGM2075PW" },
tuya.whitelabel("Marmitek", "Push_LO", "Smart switch", ["_TZ3000_dfgbtub0"]),
tuya.whitelabel("Moes", "ZT-YK02", "Wireless switch with 2 buttons", ["_TZ3000_cllghx1k"]),
],
extend: [tuyaBase()],
exposes: [e.battery(), e.action(["1_single", "1_double", "1_hold", "2_single", "2_double", "2_hold"])],
fromZigbee: [tuya.fz.on_off_action, fz.battery],
toZigbee: [],
configure: tuya.configureMagicPacket,
/*
* reporting.batteryPercentageRemaining removed as it was causing devices to fall of the network
* every 1 hour, with light flashing when it happened, extremely short battery life, 2 presses for
* action to register: https://github.com/Koenkk/zigbee2mqtt/issues/8072
* Initially wrapped in a try catch: https://github.com/Koenkk/zigbee2mqtt/issues/6313
*/
},
{
zigbeeModel: ["TS0043"],
model: "TS0043",
vendor: "Tuya",
description: "Wireless switch with 3 buttons",
whiteLabel: [
{ vendor: "Smart9", model: "S9TSZGB" },
{ vendor: "Lonsonho", model: "TS0043" },
{ vendor: "LoraTap", model: "SS600ZB" },
tuya.whitelabel("Moes", "ZT-YK03", "Wireless switch with 3 buttons", ["_TZ3000_1kmurvlx"]),
],
extend: [tuyaBase()],
exposes: [e.battery(), e.action(["1_single", "1_double", "1_hold", "2_single", "2_double", "2_hold", "3_single", "3_double", "3_hold"])],
fromZigbee: [tuya.fz.on_off_action, fz.battery],
toZigbee: [],
configure: tuya.configureMagicPacket,
/*
* reporting.batteryPercentageRemaining removed as it was causing devices to fall of the network
* every 1 hour, with light flashing when it happened, extremely short battery life, 2 presses for
* action to register: https://github.com/Koenkk/zigbee2mqtt/issues/8072
* Initially wrapped in a try catch: https://github.com/Koenkk/zigbee2mqtt/issues/6313
*/
},
{
zigbeeModel: ["TS0044"],
model: "TS0044",
vendor: "Tuya",
description: "Wireless switch with 4 buttons",
whiteLabel: [
{ vendor: "Lonsonho", model: "TS0044" },
{ vendor: "Haozee", model: "ESW-OZAA-EU" },
{ vendor: "Moes", model: "ZT-SY-EU-G-4S-WH-MS" },
{ vendor: "Nedis", model: "ZBWS40WT" },
{ vendor: "Nous", model: "C1", whiteLabelOf: "TS0044_2" },
tuya.whitelabel("Moes", "ZT-SR-EU4", "Star Ring 4 Gang Scene Switch", ["_TZ3000_a4xycprs"]),
tuya.whitelabel("Tuya", "TS0044_1", "Zigbee 4 button remote - 12 scene", ["_TZ3000_dziaict4", "_TZ3000_j61x9rxn"]),
tuya.whitelabel("iHseno", "_TZ3000_mh9px7cq", "Zigbee 4 button remote - 12 scene", ["_TZ3000_mh9px7cq"]),
tuya.whitelabel("iHseno", "TS0044_5tqxpine", "Zigbee 4 button wall switch", ["_TZ3000_5tqxpine"]),
tuya.whitelabel("Tuya", "TM-YKQ004", "Zigbee 4 button remote - 12 scene", ["_TZ3000_u3nv1jwk"]),
tuya.whitelabel("HOBEIAN", "ZG-101ZS", "Star Ring 4 Gang Scene Switch", ["_TZ3000_bgtzm4ny"]),
tuya.whitelabel("Moes", "XH-SY-04Z", "4 button portable remote control", ["_TZ3000_kfu8zapd"]),
tuya.whitelabel("LoraTap", "SS6400ZB", "4 button portable remote control", ["_TZ3000_ee8nrt2l"]),
tuya.whitelabel("Zemismart", "ZMR4_1", "4 button portable remote control (without dimmer)", ["_TZ3000_xwuveizv"]),
tuya.whitelabel("Tuya", "TS0044_2", "Wireless switch with 4 buttons", ["_TZ3000_zgyzgdua"]),
],
extend: [tuyaBase(), m.battery({ voltage: true, percentageReporting: false })],
fromZigbee: [tuya.fz.on_off_action],
exposes: [
e.action([
"1_single",
"1_double",
"1_hold",
"2_single",
"2_double",
"2_hold",
"3_single",
"3_double",
"3_hold",
"4_single",
"4_double",
"4_hold",
]),
],
/*
* reporting.batteryPercentageRemaining removed as it was causing devices to fall of the network
* every 1 hour, with light flashing when it happened, extremely short battery life, 2 presses for
* action to register: https://github.com/Koenkk/zigbee2mqtt/issues/8072
* Initially wrapped in a try catch: https://github.com/Koenkk/zigbee2mqtt/issues/6313
*/
},
{
fingerprint: tuya.fingerprint("TS004F", [
"_TZ3000_nuombroo",
"_TZ3000_xabckq1v",
"_TZ3000_czuyt8lz",
"_TZ3000_0ht8dnxj",
"_TZ3000_b3mgfu0d",
"_TZ3000_11pg3ima",
"_TZ3000_et7afzxz",
"_TZ3000_pftj0i7z",
"_TZ3000_xffhmvhv", // Nobø Connect SWS-IZ mode switch
]),
model: "TS004F",
vendor: "Tuya",
description: "Wireless switch with 4 buttons",
extend: [tuya.modernExtend.tuyaBase(), m.battery({ voltage: true }), m.identify({ isSleepy: true })],
exposes: [
e
.enum("operation_mode", ea.ALL, ["command", "event"])
.withDescription('Operation mode: "command" - for group control, "event" - for clicks'),
e.action([
"on",
"off",
"brightness_step_up",
"brightness_step_down",
"brightness_move_up",
"brightness_move_down",
"color_temperature_step_up",
"color_temperature_step_down",
"brightness_stop",
"1_single",
"1_double",
"1_hold",
"2_single",
"2_double",
"2_hold",
"3_single",
"3_double",
"3_hold",
"4_single",
"4_double",
"4_hold",
]),
],
fromZigbee: [
tuya.fz.on_off_action,
tuya.fz.operation_mode,
fz.command_on,
fz.command_off,
fz.command_step,
fz.command_move,
fz.command_stop,
fz.command_step_color_temperature,
],
whiteLabel: [
tuya.whitelabel("Zemismart", "ZMR4", "Wireless switch with 4 buttons", ["_TZ3000_11pg3ima", "_TZ3000_et7afzxz"]),
tuya.whitelabel("Moes", "TS004F_1", "Wireless switch with 4 buttons", ["_TZ3000_xabckq1v"]),
tuya.whitelabel("Nobø", "SWS-IZ", "Mode switch (Home/Sleep/Away/Vacation)", ["_TZ3000_xffhmvhv"]),
],
toZigbee: [tuya.tz.operation_mode],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await endpoint.read("genBasic", [0x0004, 0x000, 0x0001, 0x0005, 0x0007, 0xfffe]);
await endpoint.write("genOnOff", { tuyaOperationMode: 1 });
await endpoint.read("genOnOff", ["tuyaOperationMode"]);
try {
await endpoint.read(0xe001, [0xd011]);
}
catch {
/* do nothing */
}
for (const ep of [1, 2, 3, 4]) {
// Not all variants have all endpoints
// https://github.com/Koenkk/zigbee2mqtt/issues/15730#issuecomment-1364498358
if (device.getEndpoint(ep)) {
await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ["genOnOff"]);
}
}
},
},
{
fingerprint: tuya.fingerprint("TS004F", ["_TZ3000_r0o2dahu"]),
model: "TS004F_6_button",
vendor: "Tuya",
description: "Wireless switch with 6 buttons",
extend: [tuya.modernExtend.tuyaBase()],
exposes: [
e.battery(),
e
.enum("operation_mode", ea.ALL, ["command", "event"])
.withDescription('Operation mode: "command" - for group control, "event" - for clicks'),
e.action([
"on",
"off",
"brightness_step_up",
"brightness_step_down",
"brightness_move_up",
"brightness_move_down",
"1_single",
"1_double",
"1_hold",
"2_single",
"2_double",
"2_hold",
"3_single",
"3_double",
"3_hold",
"4_single",
"4_double",
"4_hold",
"5_single",
"5_double",
"5_hold",
"6_single",
"6_double",
"6_hold",
]),
],
fromZigbee: [fz.battery, tuya.fz.on_off_action, tuya.fz.operation_mode, fz.command_on, fz.command_off, fz.command_step, fz.command_move],
toZigbee: [tuya.tz.operation_mode],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await endpoint.read("genBasic", [0x0004, 0x000, 0x0001, 0x0005, 0x0007, 0xfffe]);
await endpoint.write("genOnOff", { tuyaOperationMode: 1 });
await endpoint.read("genOnOff", ["tuyaOperationMode"]);
try {
await endpoint.read(0xe001, [0xd011]);
}
catch {
/* do nothing */
}
await endpoint.read("genPowerCfg", ["batteryVoltage", "batteryPercentageRemaining"]);
await reporting.bind(endpoint, coordinatorEndpoint, ["genPowerCfg"]);
for (const ep of [1, 2, 3, 4, 5, 6]) {
// Not all variants have all endpoints
// https://github.com/Koenkk/zigbee2mqtt/issues/15730#issuecomment-1364498358
if (device.getEndpoint(ep)) {
await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ["genOnOff"]);
}
}
await reporting.batteryPercentageRemaining(endpoint);
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_xixlazkg"]),
model: "TS0601_thermostat_fancoil",
vendor: "Tuya",
description: "Fan coil unit (FCU) thermostat",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.child_lock(),
e
.climate()
.withSetpoint("current_heating_setpoint", 10, 35, 1, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withSystemMode(["heat", "cool", "dry", "fan_only", "emergency_heating", "auto"], ea.STATE_SET)
.withFanMode(["low", "medium", "high", "auto"], ea.STATE_SET)
.withPreset(["off", "on"])
.withLocalTemperatureCalibration(-9.9, 9.9, 0.1, ea.STATE_SET),
e.binary("battery_low", ea.STATE, true, false).withDescription("Indicates if the battery of this device is almost empty"),
e.numeric("error", ea.STATE).withCategory("diagnostic").withDescription("Device error code reported by the thermostat"),
],
meta: {
tuyaDatapoints: [
[1, "preset", tuya.valueConverterBasic.lookup({ off: false, on: true })],
[
2,
"system_mode",
tuya.valueConverterBasic.lookup({
fan_only: tuya.enum(0),
cool: tuya.enum(1),
dry: tuya.enum(2),
heat: tuya.enum(3),
auto: tuya.enum(4),
emergency_heating: tuya.enum(5),
}),
],
[16, "current_heating_setpoint", tuya.valueConverter.raw],
[24, "local_temperature", tuya.valueConverter.divideBy10],
[28, "fan_mode", tuya.valueConverterBasic.lookup({ low: tuya.enum(0), medium: tuya.enum(1), high: tuya.enum(2), auto: tuya.enum(3) })],
[35, "battery_low", tuya.valueConverter.trueFalse0],
[40, "child_lock", tuya.valueConverter.lockUnlock],
[44, "local_temperature_calibration", tuya.valueConverter.localTemperatureCalibration],
[45, "error", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_dzuqwsyg", "_TZE204_dzuqwsyg"]),
model: "BAC-002-ALZB",
vendor: "Tuya",
description: "FCU thermostat temperature controller",
extend: [tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true, timeStart: "1970" })],
options: [
e
.enum("control_sequence_of_operation", ea.SET, ["cooling_only", "cooling_and_heating"])
.withLabel("Device Configuration")
.withDescription("Report either cooling and fan or cooling, heating and fan capability."),
e
.binary("expose_device_state", ea.SET, true, false)
.withLabel("Expose device switch")
.withDescription("Expose a separate on/off switch, instead of including it in system mode."),
e
.binary("wake_before_power_transition", ea.SET, true, false)
.withLabel("Wake before Power Transition")
.withDescription("Send a wake-up command before turning the device on or/off, required for some firmware revisions."),
],
exposes: (device, options) => {
const system_modes = ["off", "cool", "heat", "fan_only"];
// Device can operate either in cooling or heating/cooling configuration
// For cooling only configurations remove 'heat' mode
if (options.control_sequence_of_operation === "cooling_only") {
system_modes.splice(2, 1);
}
const exposes = [
e
.climate()
.withLocalTemperature(ea.STATE)
.withSystemMode(system_modes, ea.STATE_SET)
.withFanMode(["low", "medium", "high", "auto"], ea.STATE_SET)
.withSetpoint("current_heating_setpoint", 5, 35, 1, ea.STATE_SET)
.withPreset(["auto", "manual"])
.withLocalTemperatureCalibration(-9, 9, 1, ea.STATE_SET),
e.child_lock(),
e.max_temperature().withValueMin(35).withValueMax(45),
e.numeric("deadzone_temperature", ea.STATE_SET).withUnit("°C").withValueMax(5).withValueMin(0),
e.text("schedule_text", ea.STATE_SET).withDescription(`Weekly schedule in the format "HH:MM/TT HH:MM/TT ...".
Example for 12 segments:
"06:00/20 11:30/21 13:30/22 17:30/23 06:00/24 12:00/23 14:30/22 17:30/21 06:00/19 12:30/20 14:30/21 18:30/20".
Each segment contains:
- HH:MM: Time in 24-hour format.
- TT: Temperature in °C.
Ensure all 12 segments are defined and separated by spaces.`),
];
if (options.expose_device_state === true) {
exposes.unshift(e.binary("state", ea.STATE_SET, "ON", "OFF").withDescription("Turn the thermostat ON or OFF"));
}
return exposes;
},
meta: {
publishDuplicateTransaction: true,
tuyaDatapoints: [
[
1,
"state",
{
to: async (v, meta) => {
if (meta.options.expose_device_state === true) {
await tuya.sendDataPointBool(meta.device.endpoints[0], 1, utils.getFromLookup(v, { on: true, off: false }), "dataRequest", 1);
}
},
from: (v, meta, options) => {
meta.state.system_mode = v === true ? (meta.state.system_mode_device ?? "cool") : "off";
if (options.expose_device_state === true)
return v === true ? "ON" : "OFF";
delete meta.state.state;
},
},
],
[
2,
"system_mode",
{
to: async (v, meta) => {
const ep = meta.device.endpoints[0];
if (v === "off") {
if (meta.options.wake_before_power_transition === true) {
await tuya.sendDataPointBool(ep, 1, true, "dataRequest", 1);
await utils.sleep(120);
}
await tuya.sendDataPointBool(ep, 1, false, "dataRequest", 1);
return;
}
if (meta.options.wake_before_power_transition === true) {
if (meta.state.system_mode === "off") {
await tuya.sendDataPointBool(ep, 1, true, "dataRequest", 1);
await utils.sleep(120);
}
}
await tuya.sendDataPointBool(ep, 1, true, "dataRequest", 1);
if (v === "cool")
await tuya.sendDataPointEnum(ep, 2, 0, "dataRequest", 1);
if (v === "heat")
await tuya.sendDataPointEnum(ep, 2, 1, "dataRequest", 1);
if (v === "fan_only")
await tuya.sendDataPointEnum(ep, 2, 2, "dataRequest", 1);
},
from: (v, meta) => {
const modes = ["cool", "heat", "fan_only"];
meta.state.system_mode_device = modes[v];
return modes[v];
},
},
],
[
4,
"preset",
{
to: async (v, meta) => {
const ep = meta.device.endpoints[0];
await tuya.sendDataPointBool(ep, 4, v === "manual");
},
from: (v, meta) => {
const preset = v ? "manual" : "auto";
meta.state.preset = preset;
return preset;
},
},
],
[16, "current_heating_setpoint", tuya.valueConverter.raw],
[19, "max_temperature", tuya.valueConverter.raw],
[24, "local_temperature", tuya.valueConverter.divideBy10],
[26, "deadzone_temperature", tuya.valueConverter.raw],
[27, "local_temperature_calibration", tuya.valueConverter.localTemperatureCalibration],
[
28,
"fan_mode",
tuya.valueConverterBasic.lookup({
low: tuya.enum(0),
medium: tuya.enum(1),
high: tuya.enum(2),
auto: tuya.enum(3),
}),
],
[40, "child_lock", tuya.valueConverter.lockUnlock],
[
101,
"schedule",
{
from: (v, meta) => {
const format = (data) => data.reduce((txt, val, i) => {
if (i % 3 === 0)
return `${txt}${i > 0 ? " " : ""}${val.toString().padStart(2, "0")}`;
if (i % 3 === 1)
return `${txt}:${val.toString().padStart(2, "0")}`;
return `${txt}/${val / 2}`;
}, "");
const weekdays = format(v.slice(0, 12));
const saturday = format(v.slice(12, 24));
const sunday = format(v.slice(24, 36));
const full = `${weekdays} ${saturday} ${sunday}`.trim();
meta.state.schedule_text = full;
return full;
},
},
],
[
202,
"schedule_text",
{
to: async (v, meta) => {
const regex = /((?<h>[01][0-9]|2[0-3]):(?<m>[0-5][0-9])\/(?<t>[0-3]?[0-9](\.[0,5])?))/gm;
const matches = [...v.matchAll(regex)];
if (matches.length !== 12)
return;
const result = [];
for (const m of matches) {
result.push(Number(m.groups?.h));
result.push(Number(m.groups?.m));
result.push(Number(m.groups?.t) * 2);
}
await tuya.sendDataPointRaw(meta.device.endpoints[0], 101, Buffer.from(result));
},
},
],
],
},
whiteLabel: [tuya.whitelabel("Tuya", "BAC-003", "FCU thermostat temperature controller", ["_TZE204_dzuqwsyg"])],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_hpkusvom"]),
model: "BAC-001",
vendor: "Tuya",
description: "Heating/cooling thermostat with fan control",
extend: [tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true, timeStart: "1970" })],
exposes: [
e
.climate()
.withSystemMode(["off", "cool", "heat", "fan_only"], ea.STATE_SET)
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withLocalTemperatureCalibration(-9, 9, 1, ea.STATE_SET)
.withFanMode(["low", "medium", "high", "auto"], ea.STATE_SET),
],
meta: {
tuyaDatapoints: [
[
1,
null,
{
from: (v, meta) => {
meta.state.power_state_device = v;
return { system_mode: bac001SystemMode(meta) };
},
},
],
[
2,
"system_mode",
{
to: async (v, meta) => {
const ep = meta.device.endpoints[0];
if (v === "off") {
await tuya.sendDataPointBool(ep, 1, false, "dataRequest", 1);
return;
}
await tuya.sendDataPointBool(ep, 1, true, "dataRequest", 1);
await tuya.sendDataPointEnum(ep, 2, utils.getFromLookup(v, { cool: 0, heat: 1, fan_only: 2 }), "dataRequest", 1);
},
from: (v, meta) => {
meta.state.system_mode_device = ["cool", "heat", "fan_only"][v];
return bac001SystemMode(meta);
},
},
],
[16, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[24, "local_temperature", tuya.valueConverter.divideBy10],
[
27,
"local_temperature_calibration",
{
// Negative values are reported as a 32 bit two's complement
from: (v) => (v > 0x7fffffff ? v - 0x100000000 : v),
to: (v) => (v < 0 ? 0x100000000 + v : v),
},
],
[
28,
"fan_mode",
tuya.valueConverterBasic.lookup({
low: tuya.enum(0),
medium: tuya.enum(1),
high: tuya.enum(2),
auto: tuya.enum(3),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_qq9mpfhw"]),
model: "TS0601_water_sensor",
vendor: "Tuya",
description: "Water leak sensor",
fromZigbee: [legacy.fromZigbee.tuya_water_leak],
exposes: [e.water_leak()],
toZigbee: [],
whiteLabel: [{ vendor: "NEO", model: "NAS-WS02B0" }],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_jthf7vb6"]),
model: "WLS-100z",
vendor: "Tuya",
description: "Water leak sensor",
fromZigbee: [fz.ignore_tuya_raw, legacy.fromZigbee.wls100z_water_leak],
extend: [tuya.modernExtend.tuyaBase({ bindBasicOnConfigure: true })],
exposes: [e.battery(), e.water_leak()],
},
{
fingerprint: tuya.fingerprint("TS0001", [
"_TZ3000_xkap8wtb",
"_TZ3000_qnejhcsu",
"_TZ3000_x3ewpzyr",
"_TZ3000_mkhkxx1p",
"_TZ3000_tgddllx4",
"_TZ3000_kqvb5akv",
"_TZ3000_q8r0bbvy",
"_TZ3000_g92baclx",
"_TZ3000_qlai3277",
"_TZ3000_qaabwu5c",
"_TZ3000_qorepo2x",
"_TZ3000_ikuxinvo",
"_TZ3000_hzlsaltw",
"_TZ3000_jsfzkftc",
"_TZ3000_0ghwhypc",
"_TZ3000_1adss9de",
"_TZ3000_x8mbwtsz",
"_TZ3000_iktiy8ue",
"_TZ3000_zojh9vz7",
"_TZ3000_gsat0axs",
]),
model: "TS0001_power",
description: "Switch with power monitoring",
vendor: "Tuya",
ota: true,
fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, tuya.fz.power_outage_memory, tuya.fz.switch_type],
toZigbee: [tz.on_off, tuya.tz.power_on_behavior_1, tuya.tz.switch_type],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
const endpoint = device.getEndpoint(1);
/*
https://github.com/Koenkk/zigbee2mqtt/issues/27090
INVALID_DATA_TYPE error for _TZ3000_xkap8wtb, appVersion:162 softwareBuildID: 0122052017
*/
if ((["_TZ3000_x3ewpzyr", "_TZ3000_xkap8wtb"].includes(device.manufacturerName) && [162, 100].includes(device.applicationVersion)) ||
(["_TZ3000_mkhkxx1p", "_TZ3000_kqvb5akv"].includes(device.manufacturerName) && [74].includes(device.applicationVersion)) ||
["_TZ3000_tgddllx4"].includes(device.manufacturerName)) {
logger_1.logger.warning(`Detected ${device.modelID} ${device.manufacturerName} switch with appVersion ${device.applicationVersion}. Skip reporting for haElectricalMeasurement and seMetering in favor of polling.`, NS);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff"]);
}
else {
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff", "haElectricalMeasurement", "seMetering"]);
await reporting.rmsVoltage(endpoint, { change: 5 });
await reporting.rmsCurrent(endpoint, { change: 50 });
await reporting.activePower(endpoint, { change: 10 });
await reporting.currentSummDelivered(endpoint);
}
endpoint.saveClusterAttributeKeyValue("haElectricalMeasurement", {
acCurrentDivisor: 1000,
acCurrentMultiplier: 1,
});
endpoint.saveClusterAttributeKeyValue("seMetering", {
divisor: 100,
multiplier: 1,
});
device.save();
},
exposes: [
e.switch(),
e.power(),
e.current(),
e.voltage(),
e.energy(),
tuya.exposes.switchType(),
e.enum("power_outage_memory", ea.ALL, ["on", "off", "restore"]).withDescription("Recover state after power outage"),
],
whiteLabel: [
tuya.whitelabel("Nous", "B2Z", "1 gang switch with power monitoring", ["_TZ3000_qlai3277"]),
tuya.whitelabel("Colorock", "CR-MNZ1", "1 gang switch 30A with power monitoring", ["_TZ3000_tgddllx4"]),
tuya.whitelabel("Nous", "L6Z", "Switch with power monitoring", ["_TZ3000_qaabwu5c", "_TZ3000_1adss9de"]),
tuya.whitelabel("Tuya", "XSH01A", "1 gang switch", ["_TZ3000_x3ewpzyr"]),
tuya.whitelabel("Moes", "ZM-104-M-16AM", "1 gang switch with power monitoring", ["_TZ3000_x8mbwtsz"]),
tuya.whitelabel("Nous", "B5Z", "1 gang switch with power monitoring", ["_TZ3000_iktiy8ue"]),
],
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.electricityMeasurementPoll({
electricalMeasurement: (device) => (["_TZ3000_x3ewpzyr", "_TZ3000_xkap8wtb"].includes(device.manufacturerName) && [162, 100].includes(device.applicationVersion)) ||
(["_TZ3000_mkhkxx1p", "_TZ3000_kqvb5akv"].includes(device.manufacturerName) && [74].includes(device.applicationVersion)) ||
["_TZ3000_tgddllx4"].includes(device.manufacturerName),
metering: (device) => (["_TZ3000_x3ewpzyr", "_TZ3000_xkap8wtb"].includes(device.manufacturerName) && [162, 100].includes(device.applicationVersion)) ||
(["_TZ3000_mkhkxx1p", "_TZ3000_kqvb5akv"].includes(device.manufacturerName) && [74].includes(device.applicationVersion)) ||
["_TZ3000_tgddllx4"].includes(device.manufacturerName),
}),
],
},
{
fingerprint: tuya.fingerprint("TS0002", ["_TZ3000_aaifmpuq", "_TZ3000_irrmjcgi", "_TZ3000_huvxrx4i", "_TZ3000_pxfjrzyj"]),
model: "TS0002_power",
vendor: "Tuya",
description: "2 gang switch with power monitoring",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
switchType: true,
endpoints: ["l1", "l2"],
electricalMeasurements: true,
onOffCountdown: true,
}),
],
endpoint: (device) => {
return { l1: 1, l2: 2 };
},
meta: {
multiEndpoint: true,
multiEndpointSkip: ["energy", "current", "voltage", "power"],
},
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await endpoint.read("genBasic", ["manufacturerName", "zclVersion", "appVersion", "modelId", "powerSource", 0xfffe]);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff", "haElectricalMeasurement", "seMetering"]);
await reporting.rmsVoltage(endpoint, { change: 5 });
await reporting.rmsCurrent(endpoint, { change: 50 });
await reporting.activePower(endpoint, { change: 10 });
await reporting.currentSummDelivered(endpoint);
endpoint.saveClusterAttributeKeyValue("haElectricalMeasurement", {
acCurrentDivisor: 1000,
acCurrentMultiplier: 1,
});
endpoint.saveClusterAttributeKeyValue("seMetering", {
divisor: 100,
multiplier: 1,
});
device.save();
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
},
whiteLabel: [
tuya.whitelabel("Tuya", "XSH01B", "2 gang switch module with power monitoring", ["_TZ3000_irrmjcgi"]),
tuya.whitelabel("Nous", "B3Z", "2 gang switch module with power monitoring", ["_TZ3000_aaifmpuq"]),
],
},
{
fingerprint: tuya.fingerprint("TS000F", ["_TZ3000_xkap8wtb"]),
model: "TS000F_power",
description: "Switch with power monitoring",
vendor: "Tuya",
extend: [tuya.modernExtend.tuyaBase()],
fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, tuya.fz.power_on_behavior_1, tuya.fz.switch_type],
toZigbee: [tz.on_off, tuya.tz.power_on_behavior_1, tuya.tz.switch_type],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff", "haElectricalMeasurement", "seMetering"]);
await reporting.rmsVoltage(endpoint, { change: 5 });
await reporting.rmsCurrent(endpoint, { change: 50 });
await reporting.currentSummDelivered(endpoint);
endpoint.saveClusterAttributeKeyValue("haElectricalMeasurement", {
acCurrentDivisor: 1000,
acCurrentMultiplier: 1,
});
endpoint.saveClusterAttributeKeyValue("seMetering", {
divisor: 100,
multiplier: 1,
});
device.save();
},
whiteLabel: [{ vendor: "Aubess", model: "WHD02" }],
exposes: [e.switch(), e.power(), e.current(), e.voltage(), e.energy(), e.power_on_behavior(), tuya.exposes.switchType()],
},
{
zigbeeModel: ["TS0001"],
model: "TS0001",
vendor: "Tuya",
description: "1 gang switch",
extend: [tuya.modernExtend.tuyaBase(), tuya.modernExtend.tuyaOnOff()],
whiteLabel: [
{
vendor: "CR Smart Home",
model: "TS0001",
description: "Valve control",
},
{ vendor: "Lonsonho", model: "X701" },
{ vendor: "Bandi", model: "BDS03G1" },
tuya.whitelabel("Nous", "B1Z", "1 gang switch", ["_TZ3000_ctftgjwb"]),
tuya.whitelabel("Tuya", "XMSJ", "Zigbee USB power switch", ["_TZ3000_8n7lqbm0"]),
tuya.whitelabel("Tuya", "ZG-001", "Smart home relay module", ["_TZ3000_g8n1n7lg"]),
tuya.whitelabel("Nova Digital", "SA-1", "Safira smart light switch - 1 gang", ["_TZ3000_udl7uyd2"]),
],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
},
},
{
fingerprint: tuya.fingerprint("TS0001", ["_TZ3000_myaaknbq", "_TZ3000_cpozgbrx", "_TZ3000_drc9tuqb"]),
model: "TS0001_switch_module_1",
vendor: "Tuya",
description: "1 gang switch module",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
indicatorMode: true,
backlightModeOffOn: true,
onOffCountdown: true,
}),
],
whiteLabel: [
tuya.whitelabel("PSMART", "T441", "1 gang switch module", ["_TZ3000_myaaknbq"]),
tuya.whitelabel("PSMART", "T461", "1 gang switch module", ["_TZ3000_cpozgbrx"]),
tuya.whitelabel("Mercator Ikuü", "SSWM10-ZB", "1 gang switch module", ["_TZ3000_drc9tuqb"]),
],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
},
},
{
fingerprint: tuya.fingerprint("TS0001", ["_TZ3000_gbshwgag", "_TZ3000_blhvsaqf"]),
model: "TS0001_switch_module_2",
vendor: "Tuya",
description: "1 gang switch with backlight",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
powerOnBehavior2: true,
backlightModeOffOn: true,
indicatorMode: true,
}),
],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
},
},
////////////////////////
// TS0002 DEFINITIONS //
////////////////////////
{
// TS0002 model with only on/off capability
zigbeeModel: ["ZG-301Z-2CH"],
fingerprint: tuya.fingerprint("TS0002", [
"_TZ3000_01gpyda5",
"_TZ3000_bvrlqyj7",
"_TZ3000_7ed9cqgi",
"_TZ3000_zmy4lslw",
"_TZ3000_ruxexjfz",
"_TZ3000_4xfqlgqo",
"_TZ3000_hojntt34",
"_TZ3000_eei0ubpy",
"_TZ3000_qaa59zqd",
"_TZ3000_lmlsduws",
"_TZ3000_fbjdkph9",
"_TZ3000_zbfya6h0",
"_TZ3000_hznzbl0x",
"_TZ3210_6smingw0",
]),
model: "TS0002_basic",
vendor: "Tuya",
version: "0.0.1",
description: "2 gang switch module",
whiteLabel: [
{ vendor: "OXT", model: "SWTZ22" },
tuya.whitelabel("Moes", "ZM-104B-M", "2 gang switch", ["_TZ3000_qaa59zqd"]),
tuya.whitelabel("pcblab.io", "RR620ZB", "2 gang Zigbee switch module", ["_TZ3000_4xfqlgqo"]),
tuya.whitelabel("Nous", "L13Z", "2 gang switch", ["_TZ3000_ruxexjfz", "_TZ3000_hojntt34"]),
tuya.whitelabel("Mercator Ikuü", "SSW02", "2 gang switch", ["_TZ3000_fbjdkph9"]),
tuya.whitelabel("Aubess", "TMZ02", "2 gang switch", ["_TZ3000_lmlsduws"]),
tuya.whitelabel("RSH", "TS0002_basic_2", "2 gang switch", ["_TZ3000_zbfya6h0"]),
tuya.whitelabel("EKAZA", "EKAC-T3092Z", "2 gang switch", ["_TZ3000_hznzbl0x"]),
tuya.whitelabel("Nova Digital", "NTZB-01", "1 switch and 1 socket with backlight", ["_TZ3210_6smingw0"]),
{
vendor: "HOBEIAN",
model: "ZG-301Z-2CH",
fingerprint: [{ modelID: "ZG-301Z-2CH" }],
},
],
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
switchType: true,
endpoints: ["l1", "l2"],
powerOutageMemory: (m) => m !== "_TZ3210_6smingw0",
onOffCountdown: (m) => m === "_TZ3000_hznzbl0x" || m === "_TZ3210_6smingw0",
indicatorMode: (m) => m === "_TZ3210_6smingw0",
backlightModeOffOn: (m) => m === "_TZ3210_6smingw0",
}),
],
endpoint: (device) => {
return { l1: 1, l2: 2 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
for (const endpoint of [device.getEndpoint(1), device.getEndpoint(2)]) {
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff"]);
await reporting.onOff(endpoint);
}
},
},
{
// TS0002 model with limited functionality available
fingerprint: tuya.fingerprint("TS0002", [
"_TZ3000_fisb3ajo",
"_TZ3000_5gey1ohx",
"_TZ3000_mtnpt6ws",
"_TZ3000_mufwv0ry",
"_TZ3000_54hjn4vs",
"_TZ3000_aa5t61rh",
"_TZ3000_in5qxhtt",
"_TZ3000_ogpla3lh",
"_TZ3000_i9w5mehz",
"_TZ3000_dershnvx",
]),
model: "TS0002_limited",
vendor: "Tuya",
description: "2 gang switch module",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
switchType: true,
onOffCountdown: true,
indicatorMode: true,
backlightModeOffOn: (m) => m !== "_TZ3000_dershnvx",
powerOnBehavior2: (m) => m === "_TZ3000_dershnvx" || m === "_TZ3000_mtnpt6ws",
endpoints: ["l1", "l2"],
}),
],
endpoint: (device) => {
return { l1: 1, l2: 2 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
},
whiteLabel: [
tuya.whitelabel("AVATTO", "ZWSM16-2", "2 gang switch module", ["_TZ3000_mtnpt6ws"]),
tuya.whitelabel("PSMART", "T442", "2 gang switch module", ["_TZ3000_mufwv0ry"]),
tuya.whitelabel("Lonsonho", "X702A", "2 gang switch with backlight", ["_TZ3000_54hjn4vs", "_TZ3000_aa5t61rh"]),
tuya.whitelabel("Homeetec", "37022463-1", "2 Gang switch with backlight", ["_TZ3000_in5qxhtt"]),
tuya.whitelabel("RoomsAI", "37022463-2", "2 Gang switch with backlight", ["_TZ3000_ogpla3lh"]),
tuya.whitelabel("Rely Electronics", "_TZ3000_dershnvx", "2 gang, no neutral, switch with backlight", ["_TZ3000_dershnvx"]),
],
},
{
// TS0002 2 gang switch module with all available features. This is the default for TS0002 devices.
model: "TS0002",
zigbeeModel: ["TS0002"],
vendor: "Tuya",
description: "2-Gang switch with backlight, countdown and inching",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
switchType: true,
powerOnBehavior2: true,
backlightModeOffOn: (m) => m !== "_TZ3000_criiahcg",
indicatorMode: true,
onOffCountdown: true,
inchingSwitch: true,
endpoints: ["l1", "l2"],
}),
tuya.clusters.addTuyaCommonPrivateCluster(),
],
endpoint: (device) => {
return { l1: 1, l2: 2 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
},
whiteLabel: [
tuya.whitelabel("Zemismart", "TB26-2", "2 Gang switch with backlight, countdown, inching", ["_TZ3000_ywubfuvt"]),
{ vendor: "Zemismart", model: "ZM-CSW002-D_switch" },
{ vendor: "Lonsonho", model: "X702" },
{ vendor: "AVATTO", model: "ZTS02" },
tuya.whitelabel("PSMART", "T462", "2 Gang switch with backlight, countdown, inching", ["_TZ3000_wnzoyohq"]),
tuya.whitelabel("Nova Digital", "FZB-2", "2-Gang switch with backlight, countdown and inching", ["_TZ3000_5ksufhqi"]),
tuya.whitelabel("iHseno", "_TZ3000_zxrfobzw", "2-gang touch switch", ["_TZ3000_zxrfobzw"]),
tuya.whitelabel("Moes", "ZM4LT2", "2-gang switch module", ["_TZ3000_criiahcg"]),
tuya.whitelabel("Tuya", "ZG-2002-RF", "Three mode Zigbee Switch", [
"_TZ3000_lugaswf8",
"_TZ3000_nuenzetq",
"_TZ3000_ruldv5dt",
"_TZ3210_nuenzetq",
]),
],
},
{
fingerprint: tuya.fingerprint("TS0002", ["_TZ3000_h1ipgkwn", "_TZ3000_tas0zemd", "_TZ3000_iwtv2jwo"]),
model: "_TZ3000_h1ipgkwn",
description: "2 channel USB switch",
vendor: "Tuya",
extend: [m.deviceEndpoints({ endpoints: { l1: 1, l2: 2 } }), m.onOff({ powerOnBehavior: true, endpointNames: ["l1", "l2"] })],
whiteLabel: [tuya.whitelabel("Hej", "BDS03G2", "2 gang switch", ["_TZ3000_tas0zemd"])],
},
////////////////////////
// TS0003 DEFINITIONS //
////////////////////////
{
fingerprint: tuya.fingerprint("TS0003", [
"_TZ3000_rhkfbfcv",
"_TZ3000_empogkya",
"_TZ3000_lubfc1t5",
"_TZ3000_lsunm46z",
"_TZ3000_v4l4b0lp",
"_TZ3000_uilitwsy",
"_TZ3000_66fekqhh",
"_TZ3000_ok0ggpk7",
"_TZ3210_ok0ggpk7",
"_TZ3000_aknpkt02",
"_TZ3000_nwidmc4n",
"_TZ3000_pfc7i3kt",
]),
model: "TS0003_switch_3_gang_with_backlight",
vendor: "Tuya",
description: "3-Gang switch with backlight",
extend: [
tuya.modernExtend.tuyaBase(),
m.deviceEndpoints({ endpoints: { l1: 1, l2: 2, l3: 3 } }),
tuya.modernExtend.tuyaOnOff({
onOffCountdown: (m) => m !== "_TZ3000_pfc7i3kt",
powerOnBehavior2: (m) => m !== "_TZ3000_nwidmc4n" && m !== "_TZ3210_ok0ggpk7",
indicatorMode: (m) => m !== "_TZ3000_nwidmc4n" && m !== "_TZ3000_pfc7i3kt",
inchingSwitch: (m) => m !== "_TZ3000_pfc7i3kt" && m !== "_TZ3210_ok0ggpk7",
backlightModeOffOn: (m) => m !== "_TZ3000_pfc7i3kt",
endpoints: ["l1", "l2", "l3"],
powerOutageMemory: (m) => m === "_TZ3000_nwidmc4n" || m === "_TZ3210_ok0ggpk7",
switchType: (m) => m === "_TZ3000_nwidmc4n" || m === "_TZ3000_pfc7i3kt",
}),
],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ["genOnOff"]);
},
whiteLabel: [
tuya.whitelabel("Moes", "MS-104CZ", "3 gang switch module", ["_TZ3000_pfc7i3kt"]),
tuya.whitelabel("Lonsonho", "X703A", "3 Gang switch with backlight", ["_TZ3000_rhkfbfcv"]),
tuya.whitelabel("Zemismart", "ZM-L03E-Z", "3 gang switch with neutral", ["_TZ3000_empogkya", "_TZ3000_lsunm46z"]),
tuya.whitelabel("Zemismart", "KES-606US-L3", "3 gang switch with neutral", ["_TZ3000_uilitwsy"]),
tuya.whitelabel("AVATTO", "ZWOT16-W2", "2 gang switch and 1 socket", ["_TZ3000_66fekqhh"]),
tuya.whitelabel("AVATTO", "ZBTS60-03", "3 gang switch module with backlight", ["_TZ3000_nwidmc4n"]),
tuya.whitelabel("Tuya", "M10Z", "2 gang switch with 20A power socket", ["_TZ3000_lubfc1t5"]),
tuya.whitelabel("Zemismart", "ZMO-606-S2", "Smart 2 gangs switch with outlet", ["_TZ3000_aknpkt02"]),
tuya.whitelabel("Nova Digital", "NTZB-02", "2 switches and 1 socket with backlight", ["_TZ3210_ok0ggpk7"]),
],
},
{
fingerprint: tuya.fingerprint("TS0003", ["_TZ3000_fawk5xjv", "_TZ3000_bvij6kod", "_TZ3000_aracgljk", "_TZ3210_fawk5xjv"]),
model: "NFZB-03",
vendor: "Nova Digital",
description: "3 gang switch with power-on behavior and indicator mode",
whiteLabel: [tuya.whitelabel("Nova Digital", "SA-3", "Safira smart light switch - 3 gang", ["_TZ3000_aracgljk"])],
extend: [
tuya.modernExtend.tuyaBase(),
m.deviceEndpoints({ endpoints: { l1: 1, l2: 2, l3: 3 } }),
tuya.modernExtend.tuyaOnOff({
endpoints: ["l1", "l2", "l3"],
powerOutageMemory: true,
switchType: true,
indicatorMode: true,
backlightModeOffOn: true,
inchingSwitch: true,
onOffCountdown: true,
}),
],
},
{
fingerprint: tuya.fingerprint("TS0003", ["_TZ3000_dyzkbcip"]),
model: "Mercurio-3",
vendor: "Ekaza",
description: "Smart 3-channel switch",
configure: tuya.configureMagicPacket,
extend: [
tuya.modernExtend.tuyaBase(),
m.deviceEndpoints({ endpoints: { l1: 1, l2: 2, l3: 3 } }),
tuya.modernExtend.tuyaOnOff({
powerOutageMemory: true,
indicatorMode: true,
onOffCountdown: true,
endpoints: ["l1", "l2", "l3"],
}),
],
},
{
zigbeeModel: ["TS0003"],
model: "TS0003",
vendor: "Tuya",
description: "3 gang switch",
extend: [
m.deviceEndpoints({ endpoints: { left: 1, center: 2, right: 3 } }),
m.onOff({
endpointNames: ["left", "center", "right"],
powerOnBehavior: false,
}),
],
whiteLabel: [
{ vendor: "BSEED", model: "TS0003", description: "Zigbee switch" },
tuya.whitelabel("Tuya", "TS0003_1", "3 gang switch", ["_TZ3000_ouwfc1qj"]),
tuya.whitelabel("Zemismart", "TB26-3", "3 gang switch", ["_TZ3000_eqsair32"]),
],
meta: { disableDefaultResponse: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
},
},
{
fingerprint: tuya.fingerprint("TS0003", [
"_TZ3000_4o16jdca",
"_TZ3000_odzoiovu",
"_TZ3000_hbic3ka3",
"_TZ3000_lvhy15ix",
"_TZ3000_mhhxxjrs",
"_TZ3000_iv4eq7eh",
"_TZ3000_mzcp0of6",
"_TZ3210_aksyshpw", // https://github.com/Koenkk/zigbee2mqtt/issues/32391
"_TZ3000_bu47m8pv",
]),
model: "TS0003_switch_module_2",
vendor: "Tuya",
description: "3 gang switch module",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
switchType: true,
onOffCountdown: true,
indicatorMode: true,
inchingSwitch: (m) => m === "_TZ3000_mzcp0of6",
endpoints: ["l1", "l2", "l3"],
}),
],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ["genOnOff"]);
},
whiteLabel: [
tuya.whitelabel("AVATTO", "ZWSM16-3", "3 gang switch module", ["_TZ3000_hbic3ka3", "_TZ3000_iv4eq7eh"]),
tuya.whitelabel("iHseno", "_TZ3000_mhhxxjrs", "3 gang switch module", ["_TZ3000_mhhxxjrs"]),
tuya.whitelabel("Moes", "ZM4LT3", "3-gang switch module", ["_TZ3000_mzcp0of6"]),
tuya.whitelabel("ARTDNA", "Z20-IK03F", "3 gang F style switch module", ["_TZ3000_bu47m8pv"]),
],
},
{
zigbeeModel: ["ZG-301Z-3CH"],
fingerprint: tuya.fingerprint("TS0003", ["_TZ3000_pf7swkqp"]),
model: "TS0003_switch_module_3",
vendor: "Tuya",
description: "3 gang switch module",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
switchType: true,
endpoints: ["l1", "l2", "l3"],
}),
],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ["genOnOff"]);
},
whiteLabel: [
tuya.whitelabel("RSH", "SB03-Zigbee", "3 gang switch module", ["_TZ3000_pf7swkqp"]),
{
vendor: "HOBEIAN",
model: "ZG-301Z-3CH",
fingerprint: [{ modelID: "ZG-301Z-3CH" }],
},
],
},
{
fingerprint: tuya.fingerprint("TS0003", ["_TZ3000_ju82pu2b"]),
model: "TW-03",
vendor: "Tuya",
description: "3-Gang switch",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
powerOnBehavior2: true,
endpoints: ["l1", "l2", "l3"],
}),
],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ["genOnOff"]);
},
},
{
fingerprint: tuya.fingerprint("TS0003", ["_TZ3000_vsasbzkf", "_TZ3000_nnwehhst"]),
model: "TS0003_switch_module_1",
vendor: "Tuya",
description: "3 gang switch module",
whiteLabel: [{ vendor: "OXT", model: "SWTZ23" }],
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
switchType: true,
backlightModeOffOn: true,
endpoints: ["l1", "l2", "l3"],
}),
],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ["genOnOff"]);
},
},
{
fingerprint: [{ modelID: "TS0003", manufacturerName: "_TZ3000_mw1pqqqt" }],
model: "_TZ3000_mw1pqqqt",
description: "3 channel USB switch",
vendor: "Tuya",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
switchType: false,
indicatorMode: false,
onOffCountdown: true,
endpoints: ["l1", "l2", "l3"],
}),
],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3 };
},
meta: { multiEndpoint: true },
},
{
fingerprint: tuya.fingerprint("TS0001", [
"_TZ3000_tqlv4ug4",
"_TZ3210_tqlv4ug4",
"_TZ3000_gjrubzje",
"_TZ3000_tygpxwqa",
"_TZ3000_4rbqgcuv",
"_TZ3000_veu2v775",
"_TZ3000_prits6g4",
"_TZ3000_xfxpoxe0",
"_TZ3210_9hbau615",
"_TZ3000_afgzktgb",
"_TZ3000_qamj2vnn",
"_TZ3000_n6fqajob",
]),
model: "TS0001_switch_module",
vendor: "Tuya",
description: "1 gang switch module",
whiteLabel: [
{ vendor: "OXT", model: "SWTZ21" },
tuya.whitelabel("Moes", "ZM-104-M", "1 gang switch module", ["_TZ3000_veu2v775"]),
tuya.whitelabel("AVATTO", "ZWSM16-1", "1 gang switch module", ["_TZ3000_4rbqgcuv"]),
tuya.whitelabel("AVATTO", "ZWSM16-DC-1", "Smart ZigBee Dry Contact Switch Module", ["_TZ3000_n6fqajob"]),
tuya.whitelabel("AVATTO", "ZBTS60-01", "1 gang switch module with backlight", ["_TZ3000_xfxpoxe0"]),
tuya.whitelabel("Moes", "ZM4LT1", "1-gang switch module", ["_TZ3000_afgzktgb"]),
tuya.whitelabel("Nous", "B6Z", "1 gang switch", ["_TZ3000_qamj2vnn"]),
],
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
switchType: true,
onOffCountdown: (m) => m !== "_TZ3000_xfxpoxe0",
indicatorMode: (m) => m !== "_TZ3000_xfxpoxe0",
powerOutageMemory: true,
backlightModeOffOn: (m) => m === "_TZ3000_xfxpoxe0",
inchingSwitch: (m) => m === "_TZ3000_afgzktgb",
}),
],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
},
},
{
fingerprint: tuya.fingerprint("TS0001", ["_TZ3000_pgq7ormg"]),
model: "_TZ3000_pgq7ormg",
vendor: "iHseno",
description: "1 gang switch module",
extend: [tuya.modernExtend.tuyaBase(), tuya.modernExtend.tuyaOnOff({ switchType: true, onOffCountdown: true, indicatorMode: true })],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
},
},
{
fingerprint: tuya.fingerprint("TS0004", ["_TZ3000_ltt60asa", "_TZ3000_mmkbptmx", "_TZ3000_liygxtcq"]),
model: "TS0004_switch_module",
vendor: "Tuya",
description: "4 gang switch module",
whiteLabel: [{ vendor: "OXT", model: "SWTZ27" }, tuya.whitelabel("RSH", "SB04-Zigbee", "4 gang switch", ["_TZ3000_liygxtcq"])],
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
switchType: true,
endpoints: ["l1", "l2", "l3", "l4"],
}),
],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3, l4: 4 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(4), coordinatorEndpoint, ["genOnOff"]);
},
},
{
zigbeeModel: [
"owvfni3\u0000",
"owvfni3",
"u1rkty3",
"aabybja", // Curtain motors
"mcdj3aq",
"mcdj3aq\u0000", // Tubular motors
],
fingerprint: [
...tuya.fingerprint("TS0601", [
"_TZE200_5zbp6j0u",
"_TZE200_nkoabg8w",
"_TZE200_xuzcvlku",
"_TZE200_4vobcgd3",
"_TZE284_4vobcgd3",
"_TZE200_nogaemzt",
"_TZE200_r0jdjrvi",
"_TZE200_pk0sfzvr",
"_TZE200_fdtjuw7u",
"_TZE200_zpzndjez",
"_TZE200_wmcdj3aq",
"_TZE200_cowvfni3",
"_TZE200_rddyvrci",
"_TZE200_nueqqe6k",
"_TZE200_bqcqqjpb",
"_TZE200_xaabybja",
"_TZE200_rmymn92d",
"_TZE200_feolm6rk",
"_TZE200_3i3exuay",
"_TZE200_tvrvdj6o",
"_TZE200_b2u1drdv",
"_TZE200_ol5jlkkr",
"_TZE204_guvc7pdy",
"_TZE200_zxxfv8wi",
"_TZE200_1fuxihti",
"_TZE284_1fuxihti",
"_TZE204_1fuxihti",
"_TZE204_57hjqelq",
"_TZE204_vvvtcehj",
"_TZE28C1000000_vvvtcehj",
"_TZE200_hojryzzd",
"_TZE204_m1wl5fvq",
"_TZE200_en3wvcbx",
"_TZE200_fctwhugx",
"_TZE200_hsgrhjpf",
"_TZE200_g5wdnuow",
"_TZE200_5sbebbzs",
"_TZE200_udank5zs",
"_TZE204_dpqsvdbi",
"_TZE200_zuz7f94z",
"_TZE200_nv6nxo0c",
"_TZE200_3ylew7b4",
"_TZE200_llm0epxg",
"_TZE200_n1aauwb4",
"_TZE200_xu4a5rhj",
"_TZE200_bjzrowv2",
"_TZE284_bjzrowv2",
"_TZE204_bjzrowv2",
"_TZE200_axgvo9jh",
"_TZE200_gaj531w3",
"_TZE284_gaj531w3",
"_TZE200_yia0p3tr",
"_TZE200_rsj5pu8y",
"_TZE200_yrugsphv",
"_TZE204_yrugsphv",
"_TZE204_nladmfvf",
"_TZE200_2odrmqwq",
"_TZE204_lh3arisb",
"_TZE284_udank5zs",
"_TZE200_7shyddj3",
"_TZE204_a2jcoyuk",
"_TZE204_ic7jtutb",
"_TZE204_odlldrxx",
"_TZE204_wzre8hu2",
"_TZE200_odlldrxx",
"_TZE200_m6lwazh9", // incomplete, see https://github.com/Koenkk/zigbee2mqtt/issues/31774
"_TZE204_zuq5xxib",
"_TZE200_swlgvdlh",
"_TZE200_2jwrgrro",
]),
...tuya.fingerprint("zo2pocs\u0000", ["_TYST11_fzo2pocs"]),
...tuya.fingerprint("dank5zs\u0000", ["_TYST11_udank5zs"]),
],
model: "TS0601_cover_1",
vendor: "Tuya",
description: "Curtain motor/roller blind motor/window pusher/tubular motor",
whiteLabel: [
{ vendor: "Yushun", model: "YS-MT750" },
tuya.whitelabel("Yushun", "YS-MT750L", "Curtain motor", ["_TZE200_bqcqqjpb", "_TZE200_gaj531w3", "_TZE284_gaj531w3"]),
{ vendor: "Zemismart", model: "ZM79E-DT" },
{ vendor: "Binthen", model: "BCM100D" },
{ vendor: "Binthen", model: "CV01A" },
{ vendor: "Zemismart", model: "M515EGB" },
{ vendor: "Oz Smart Things", model: "ZM85EL-1Z" },
{ vendor: "Tuya", model: "M515EGZT" },
{ vendor: "Tuya", model: "DT82LEMA-1.2N" },
{ vendor: "Tuya", model: "ZD82TN", description: "Curtain motor" },
{ vendor: "Larkkey", model: "ZSTY-SM-1SRZG-EU" },
{ vendor: "Zemismart", model: "AM43", description: "Roller blind motor" },
{
vendor: "Zemismart",
model: "M2805EGBZTN",
description: "Tubular motor",
},
{
vendor: "Zemismart",
model: "BCM500DS-TYZ",
description: "Curtain motor",
},
{ vendor: "A-OK", model: "AM25", description: "Tubular motor" },
{ vendor: "Alutech", model: "AM/R-Sm", description: "Tubular motor" },
tuya.whitelabel("Shenzhen Golden Security Technology", "GM46", "Curtain motor", ["_TZE204_guvc7pdy"]),
tuya.whitelabel("Roximo", "CRTZ01", "Curtain motor", ["_TZE204_57hjqelq", "_TZE204_vvvtcehj", "_TZE28C1000000_vvvtcehj"]),
{ vendor: "Quoya", model: "AT8510-TY" },
tuya.whitelabel("Somgoms", "ZSTY-SM-1DMZG-US-W_1", "Curtain switch", ["_TZE200_axgvo9jh"]),
tuya.whitelabel("HUARUI", "CMD900LE", "Lithium battery intelligent curtain opening and closing motor", ["_TZE200_zxxfv8wi"]),
tuya.whitelabel("Novato", "WPK", "Smart curtain track", ["_TZE204_lh3arisb"]),
tuya.whitelabel("Zemismart", "ZMS1-TYZ", "Smart curtain track", ["_TZE204_zuq5xxib"]),
tuya.whitelabel("Trublockout", "TB25-DC-10/25Z", "Zigbee + RG roller blind motor", ["_TZE200_m6lwazh9"]),
tuya.whitelabel("RINNconnect", "RINN WSCMQ20", "Curtain Controller", ["_TZE200_swlgvdlh"]),
tuya.whitelabel("RINNconnect", "RINN WSER40", "Roller Controller", ["_TZE200_pk0sfzvr"]),
],
fromZigbee: [legacy.fromZigbee.tuya_cover],
toZigbee: [legacy.toZigbee.tuya_cover_control, legacy.toZigbee.tuya_cover_options],
exposes: [
te.coverPosition(),
e
.composite("options", "options", ea.STATE_SET)
.withFeature(e.numeric("motor_speed", ea.STATE_SET).withValueMin(0).withValueMax(255).withDescription("Motor speed"))
.withFeature(e.binary("reverse_direction", ea.STATE_SET, true, false).withDescription("Reverse the motor direction")),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_xu4a5rhj"]),
model: "M3TYW-2.0-13",
vendor: "Tuya",
description: "Longsam M3 curtain motor",
fromZigbee: [legacy.fromZigbee.tuya_cover],
toZigbee: [tzLocal.invert_cover_percent_fix, legacy.toZigbee.tuya_cover_options],
exposes: [
te.coverPosition(),
e
.composite("options", "options", ea.STATE_SET)
.withFeature(e.numeric("motor_speed", ea.STATE_SET).withValueMin(0).withValueMax(255).withDescription("Motor speed"))
.withFeature(e.binary("reverse_direction", ea.STATE_SET, true, false).withDescription("Reverse the motor direction")),
],
options: [exposes.options.cover_position_percent_fix()],
},
{
fingerprint: [...tuya.fingerprint("TS0601", ["_TZE200_eegnwoyw"]), ...tuya.fingerprint("TS0105", ["_TZE600_ogyg1y6b"])],
model: "TS0601_cover_2",
vendor: "Tuya",
description: "Curtain motor or roller blind motor with fixed speed",
whiteLabel: [
tuya.whitelabel("Zemismart", "BCM100DB", "Curtain Motor", ["_TZE200_eegnwoyw"]),
tuya.whitelabel("Nova Digital", "ZBCMR-01", "Roller Blind Motor", ["_TZE600_ogyg1y6b"]),
],
fromZigbee: [legacy.fromZigbee.tuya_cover],
toZigbee: [legacy.toZigbee.tuya_cover_control],
exposes: [te.coverPosition()],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_cpbo62rn", "_TZE200_libht6ua", "_TZE284_libht6ua"]),
model: "TS0601_cover_6",
vendor: "Tuya",
description: "Cover motor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.text("work_state", ea.STATE),
te.coverPosition(),
e.battery(),
e.illuminance().withUnit("%"),
e.enum("opening_mode", ea.STATE_SET, ["tilt", "lift"]).withDescription("Opening mode"),
te.motorDirectionSide(),
e.enum("set_upper_limit", ea.STATE_SET, ["start", "stop"]).withDescription("Learning"),
e.enum("factory_reset", ea.STATE_SET, ["SET"]).withDescription("Remove limits"),
],
whiteLabel: [tuya.whitelabel("Tuya", "LY-108", "Cover", ["_TZE200_cpbo62rn"])],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[2, "position", tuya.valueConverter.coverPositionInverted],
[3, "position", tuya.valueConverter.coverPositionInverted],
[
4,
"opening_mode",
tuya.valueConverterBasic.lookup({
tilt: tuya.enum(0),
lift: tuya.enum(1),
}),
],
[
7,
"work_state",
tuya.valueConverterBasic.lookup({
standby: tuya.enum(0),
success: tuya.enum(1),
learning: tuya.enum(2),
}),
],
[13, "battery", tuya.valueConverter.raw],
[101, "motor_direction", tuya.valueConverter.motorDirectionSide],
[
102,
"set_upper_limit",
tuya.valueConverterBasic.lookup({
start: tuya.enum(1),
stop: tuya.enum(0),
}),
],
[104, "illuminance", tuya.valueConverter.raw],
[107, "factory_reset", tuya.valueConverter.setLimit],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_zvo63cmo"]),
model: "TS0601_cover_7",
vendor: "Tuya",
description: "Cover motor",
extend: [tuya.modernExtend.tuyaBase({ dp: true, respondToMcuVersionResponse: true })],
exposes: [te.coverPosition(), e.battery()],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[2, "position", tuya.valueConverter.coverPosition],
[3, "position", tuya.valueConverter.raw],
// motor_direction doesn't work: https://github.com/Koenkk/zigbee2mqtt/issues/18103
// [5, 'motor_direction', tuya.valueConverterBasic.lookup({'normal': tuya.enum(0), 'reversed': tuya.enum(1)})],
[101, "battery", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_rgeapp2c"]),
model: "_TZE200_rgeapp2c",
vendor: "Tuya",
description: "Semicom touch panel: 2 switches + 2 shutters",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
options: [exposes.options.invert_cover()],
exposes: [
// Expose Switches to Home Assistant
e.switch().withEndpoint("s1"),
e.switch().withEndpoint("s2"),
// Expose Shutters/Covers to Home Assistant
te.coverPosition().withEndpoint("c1"),
te.coverPosition().withEndpoint("c2"),
// Declare the state already published, so HA creates a sensor for it
e.enum("state", ea.STATE, ["OPEN", "STOP", "CLOSE"]).withEndpoint("c1"),
e.enum("state", ea.STATE, ["OPEN", "STOP", "CLOSE"]).withEndpoint("c2"),
],
endpoint: (device) => {
return { s1: 1, s2: 1, c1: 1, c2: 1 };
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
// --- LIGHTS / SWITCHES ---
[101, "state_s1", tuya.valueConverter.onOff],
[102, "state_s2", tuya.valueConverter.onOff],
// --- SHUTTERS / COVERS ---
// Cover 1 (DP 1 for state/control, DP 2 for position)
[1, "state_c1", tuya.valueConverter.coverAction],
[2, "position_c1", tuya.valueConverter.coverPosition],
// Cover 2 (DP 4 for state/control, DP 5 for position)
[4, "state_c2", tuya.valueConverter.coverAction],
[5, "position_c2", tuya.valueConverter.coverPosition],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_7lb6j8wg"]),
model: "_TZE204_7lb6j8wg",
vendor: "Tuya",
description: "Semicom touch panel: 3 shutters",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
options: [exposes.options.invert_cover()],
exposes: [
// Expose Shutters/Covers to Home Assistant
e.cover_position().withEndpoint("c1").setAccess("position", ea.STATE_SET),
e.cover_position().withEndpoint("c2").setAccess("position", ea.STATE_SET),
e.cover_position().withEndpoint("c3").setAccess("position", ea.STATE_SET),
// Declare the state already published, so HA creates a sensor for it
e.enum("state", ea.STATE, ["OPEN", "STOP", "CLOSE"]).withEndpoint("c1"),
e.enum("state", ea.STATE, ["OPEN", "STOP", "CLOSE"]).withEndpoint("c2"),
e.enum("state", ea.STATE, ["OPEN", "STOP", "CLOSE"]).withEndpoint("c3"),
],
endpoint: (device) => {
return { c1: 1, c2: 1, c3: 1 };
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
// --- SHUTTERS / COVERS ---
// Cover 1 (DP 1 for state/control, DP 2 for position)
[1, "state_c1", tuya.valueConverterBasic.lookup({ OPEN: tuya.enum(0), STOP: tuya.enum(1), CLOSE: tuya.enum(2) })],
[2, "position_c1", tuya.valueConverter.coverPosition],
// Cover 2 (DP 4 for state/control, DP 5 for position)
[4, "state_c2", tuya.valueConverterBasic.lookup({ OPEN: tuya.enum(0), STOP: tuya.enum(1), CLOSE: tuya.enum(2) })],
[5, "position_c2", tuya.valueConverter.coverPosition],
// Cover 3 (DP 7 for state/control, DP 8 for position)
[101, "state_c3", tuya.valueConverterBasic.lookup({ OPEN: tuya.enum(0), STOP: tuya.enum(1), CLOSE: tuya.enum(2) })],
[102, "position_c3", tuya.valueConverter.coverPosition],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_8eazvzo6"]),
model: "_TZE204_8eazvzo6",
vendor: "Tuya",
description: "Semicom touch panel: 6 switches",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
// Expose Switches to Home Assistant
e.switch().withEndpoint("s1"),
e.switch().withEndpoint("s2"),
e.switch().withEndpoint("s3"),
e.switch().withEndpoint("s4"),
e.switch().withEndpoint("s5"),
e.switch().withEndpoint("s6"),
],
endpoint: (device) => {
return { s1: 1, s2: 1, s3: 1, s4: 1, s5: 1, s6: 1 };
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
// --- LIGHTS / SWITCHES ---
[1, "state_s1", tuya.valueConverter.onOff],
[2, "state_s2", tuya.valueConverter.onOff],
[3, "state_s3", tuya.valueConverter.onOff],
[4, "state_s4", tuya.valueConverter.onOff],
[5, "state_s5", tuya.valueConverter.onOff],
[6, "state_s6", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE204_r0jdjrvi",
"_TZE200_g5xqosu7",
"_TZE204_g5xqosu7",
"_TZE284_fzo2pocs",
"_TZE200_9vpe3fl1",
"_TZE28C1000000_alh14edn",
]),
model: "TS0601_cover_8",
vendor: "Tuya",
description: "Cover motor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
options: [exposes.options.invert_cover()],
exposes: (device, options) => {
const exps = [
te.coverPosition(),
e.enum("reverse_direction", ea.STATE_SET, ["forward", "back"]).withDescription("Reverse the motor direction"),
];
if (device?.manufacturerName !== "_TZE28C1000000_alh14edn") {
exps.push(e.binary("motor_fault", ea.STATE, true, false).withDescription("Motor Fault"), e
.enum("upper_stroke_limit", ea.STATE_SET, ["SET", "RESET"])
.withDescription("Set or Reset the upper stroke limit")
.withCategory("config"), e
.enum("middle_stroke_limit", ea.STATE_SET, ["SET", "RESET"])
.withDescription("Set or Reset the middle stroke limit")
.withCategory("config"), e
.enum("lower_stroke_limit", ea.STATE_SET, ["SET", "RESET"])
.withDescription("Set or Reset the lower stroke limit")
.withCategory("config"), e
.enum("motor_working_mode", ea.STATE_SET, ["continuous", "intermittently"])
.withDescription("Motor operating mode")
.withCategory("config"));
}
return exps;
},
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[2, "position", tuya.valueConverter.coverPositionInverted],
[3, "position", tuya.valueConverter.coverPositionInverted],
[
5,
"reverse_direction",
tuya.valueConverterBasic.lookup({
forward: tuya.enum(0),
back: tuya.enum(1),
}),
],
[12, "motor_fault", tuya.valueConverter.trueFalse1],
[103, "upper_stroke_limit", tuya.valueConverterBasic.lookup({ SET: true, RESET: false })],
[104, "middle_stroke_limit", tuya.valueConverterBasic.lookup({ SET: true, RESET: false })],
[105, "lower_stroke_limit", tuya.valueConverterBasic.lookup({ SET: true, RESET: false })],
[106, "motor_working_mode", tuya.valueConverterBasic.lookup({ continuous: tuya.enum(0), intermittently: tuya.enum(1) })],
],
},
whiteLabel: [
tuya.whitelabel("Lilistore", "TS0601_lilistore", "Cover motor", ["_TZE204_r0jdjrvi"]),
tuya.whitelabel("Zemismart", "ZM90E-DT250N/A400", "Window opener", ["_TZE204_r0jdjrvi"]),
tuya.whitelabel("Nova Digital", "CMR-1", "Roller Blind Motor", ["_TZE200_9vpe3fl1"]),
tuya.whitelabel("Tuya", "TS0601_alh14edn", "Smart Blinds Controller", ["_TZE28C1000000_alh14edn"]),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_a8z0g46u", "_TZE204_a8z0g46u"]),
model: "YY-LT500",
vendor: "Tuya",
description: "Window opener",
options: [exposes.options.invert_cover()],
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [te.coverPosition()],
meta: {
tuyaDatapoints: [
[2, "state", tuya.valueConverter.coverAction],
[7, "position", tuya.valueConverter.coverPosition],
[8, "position", tuya.valueConverter.coverPosition],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_p2qzzazi"]),
model: "TS0601_cover_9",
vendor: "Tuya",
description: "Cover motor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [te.coverPosition(), te.motorDirection(), e.battery()],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[2, "position", tuya.valueConverter.coverPosition],
[3, "position", tuya.valueConverter.raw],
[5, "motor_direction", tuya.valueConverter.tubularMotorDirection],
[101, "battery", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_debczeci", "_TZE284_1lvln0x6", "_TZE204_debczeci"]),
model: "TS0601_human_presence",
vendor: "iHseno",
description: "Human presence sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true })],
exposes: [
e.presence().withDescription("Human presence detected"),
e.battery().withDescription("Battery percentage"),
e.enum("sensitivity", ea.STATE_SET, ["low", "middle", "high"]).withDescription("Sensor sensitivity"),
e.enum("delay_time", ea.STATE_SET, ["15s", "30s", "60s"]).withDescription("Delay time in seconds"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse0],
[4, "battery", tuya.valueConverter.raw],
[9, "sensitivity", tuya.valueConverterBasic.lookup({ low: 0, middle: 1, high: 2 })],
[10, "delay_time", tuya.valueConverterBasic.lookup({ "15s": 0, "30s": 1, "60s": 2 })],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE284_pzm3wab5",
"_TZE284_twybxdzl",
"_TZE284_hgeqeyuv",
"_TZE28C1000000_twybxdzl",
"_TZE28C1000000_hgeqeyuv",
"_TZE28C1000000_pzm3wab5",
]),
model: "ZF24",
vendor: "Tuya",
description: "Human presence sensor (millimeter wave radar)",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence(),
e.numeric("distance", ea.STATE).withUnit("m").withDescription("Object distance"),
e.illuminance(),
e.numeric("move_sensitivity", ea.STATE_SET).withValueMin(1).withValueMax(10).withValueStep(1).withDescription("Mobility sensitivity"),
e.numeric("presence_sensitivity", ea.STATE_SET).withValueMin(1).withValueMax(10).withValueStep(1).withDescription("Presence sensitivity"),
e
.numeric("presence_timeout", ea.STATE_SET)
.withValueMin(1)
.withValueMax(600)
.withValueStep(1)
.withUnit("s")
.withDescription("Presence state timeout time"),
e
.numeric("detection_distance_max", ea.STATE_SET)
.withValueMin(0.75)
.withValueMax(9.0)
.withValueStep(0.75)
.withUnit("m")
.withDescription("Maximum detection distance"),
e.binary("state", ea.STATE_SET, "ON", "OFF").withDescription("Function"),
e.binary("living_room", ea.STATE_SET, "ON", "OFF").withDescription("Living room"),
e.binary("bedroom", ea.STATE_SET, "ON", "OFF").withDescription("Bedroom"),
e.binary("bathroom", ea.STATE_SET, "ON", "OFF").withDescription("Bathroom"),
e.binary("sleep", ea.STATE_SET, "ON", "OFF").withDescription("Sleep"),
e.binary("radar_switch", ea.STATE_SET, "ON", "OFF").withDescription("Radar switch"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse1],
[2, "move_sensitivity", tuya.valueConverter.raw],
[4, "detection_distance_max", tuya.valueConverter.divideBy100],
[9, "distance", tuya.valueConverter.divideBy100],
[101, "presence_timeout", tuya.valueConverter.raw],
[102, "illuminance", tuya.valueConverter.raw],
[103, "presence_sensitivity", tuya.valueConverter.raw],
[104, "state", tuya.valueConverter.onOff],
[105, "living_room", tuya.valueConverter.onOff],
[106, "bedroom", tuya.valueConverter.onOff],
[107, "bathroom", tuya.valueConverter.onOff],
[108, "sleep", tuya.valueConverter.onOff],
[109, "radar_switch", tuya.valueConverter.onOff],
],
},
whiteLabel: [
tuya.whitelabel("Tuya", "ZT24", "Human presence sensor (millimeter wave radar)", ["_TZE284_hgeqeyuv", "_TZE28C1000000_hgeqeyuv"]),
tuya.whitelabel("Tuya", "ZX24", "Human presence sensor (millimeter wave radar)", ["_TZE284_pzm3wab5", "_TZE28C1000000_pzm3wab5"]),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE28C1000000_vosmoqsg", "_TZE28C1000000_ewn672ef"]),
model: "ZF24Pro",
vendor: "Tuya",
description: "Tuya Temperature & Humidity Human Presence Sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence(),
e.numeric("distance", ea.STATE).withUnit("m").withDescription("Object distance"),
e.illuminance(),
e.temperature(),
e.humidity(),
e.numeric("move_sensitivity", ea.STATE_SET).withValueMin(1).withValueMax(10).withValueStep(1).withDescription("Mobility sensitivity"),
e.numeric("presence_sensitivity", ea.STATE_SET).withValueMin(1).withValueMax(10).withValueStep(1).withDescription("Presence sensitivity"),
e
.numeric("presence_timeout", ea.STATE_SET)
.withValueMin(1)
.withValueMax(600)
.withValueStep(1)
.withUnit("s")
.withDescription("Presence state timeout time"),
e
.numeric("detection_distance_max", ea.STATE_SET)
.withValueMin(0.75)
.withValueMax(9.0)
.withValueStep(0.75)
.withUnit("m")
.withDescription("Maximum detection distance"),
e
.numeric("temperature_correction", ea.STATE_SET)
.withValueMin(-1.5)
.withValueMax(1.5)
.withValueStep(0.5)
.withUnit("℃")
.withDescription("temperature_correction"),
e
.numeric("humidity_correction", ea.STATE_SET)
.withValueMin(-10)
.withValueMax(10)
.withValueStep(1)
.withUnit("%")
.withDescription("humidity_correction"),
e.binary("state", ea.STATE_SET, "ON", "OFF").withDescription("Function"),
e.binary("living_room", ea.STATE_SET, "ON", "OFF").withDescription("Living room"),
e.binary("bedroom", ea.STATE_SET, "ON", "OFF").withDescription("Bedroom"),
e.binary("bathroom", ea.STATE_SET, "ON", "OFF").withDescription("Bathroom"),
e.binary("sleep", ea.STATE_SET, "ON", "OFF").withDescription("Sleep"),
e.binary("radar_switch", ea.STATE_SET, "ON", "OFF").withDescription("Radar switch"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse1],
[2, "move_sensitivity", tuya.valueConverter.raw],
[4, "detection_distance_max", tuya.valueConverter.divideBy100],
[9, "distance", tuya.valueConverter.divideBy100],
[22, "temperature", tuya.valueConverter.divideBy10],
[23, "humidity", tuya.valueConverter.raw],
[101, "presence_timeout", tuya.valueConverter.raw],
[102, "illuminance", tuya.valueConverter.raw],
[103, "presence_sensitivity", tuya.valueConverter.raw],
[104, "state", tuya.valueConverter.onOff],
[105, "living_room", tuya.valueConverter.onOff],
[106, "bedroom", tuya.valueConverter.onOff],
[107, "bathroom", tuya.valueConverter.onOff],
[108, "sleep", tuya.valueConverter.onOff],
[109, "radar_switch", tuya.valueConverter.onOff],
[110, "temperature_correction", tuya.valueConverter.divideBy10],
[111, "humidity_correction", tuya.valueConverter.raw],
],
},
whiteLabel: [tuya.whitelabel("Tuya", "ZT24Pro", "Tuya Temperature & Humidity Human Presence Sensor", ["_TZE28C1000000_ewn672ef"])],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE28C1000000_jlbsptkl"]),
model: "_TZE28C1000000_jlbsptkl",
vendor: "Tuya",
description: "Human sresence sensor 1-gang smart switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.enum("presence_state", ea.STATE, ["none", "presence"]).withDescription("Presence Status"),
e.switch(),
e
.numeric("countdown", ea.STATE_SET)
.withValueMin(0)
.withValueMax(43200)
.withValueStep(1)
.withUnit("s")
.withDescription("Switch 1 Countdown"),
e.enum("relay_status", ea.STATE_SET, ["off", "on", "memory"]).withDescription("Power-on Status"),
e.enum("light_mode", ea.STATE_SET, ["relay", "none", "pos"]).withDescription("Indicator Light Status"),
e
.numeric("delays_time", ea.STATE_SET)
.withValueMin(3)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Unoccupied Delay Time"),
e.numeric("sensitivity", ea.STATE_SET).withValueMin(1).withValueMax(10).withValueStep(1).withDescription("Trigger Sensitivity"),
e.enum("turn_on_light_for_person", ea.STATE_SET, ["none", "all"]).withDescription("Turn On Light When Human Detected"),
e.enum("turn_off_light_for_person", ea.STATE_SET, ["none", "all"]).withDescription("Turn Off Light When Human Not Detected"),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[7, "countdown", tuya.valueConverter.raw],
[
14,
"relay_status",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
on: tuya.enum(1),
memory: tuya.enum(2),
}),
],
[
15,
"light_mode",
tuya.valueConverterBasic.lookup({
relay: tuya.enum(0),
none: tuya.enum(1),
pos: tuya.enum(2),
}),
],
[
101,
"presence_state",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
presence: tuya.enum(1),
}),
],
[102, "delays_time", tuya.valueConverter.raw],
[
103,
"turn_on_light_for_person",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
all: tuya.enum(1),
}),
],
[104, "sensitivity", tuya.valueConverter.raw],
[
105,
"turn_off_light_for_person",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
all: tuya.enum(1),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE28C1000000_jaunkx9g"]),
model: "_TZE28C1000000_jaunkx9g",
vendor: "Tuya",
description: "Human presence sensor 2-gang smart switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.enum("presence_state", ea.STATE, ["none", "presence"]).withDescription("Presence Status"),
e.switch().withEndpoint("l1"),
e.switch().withEndpoint("l2"),
e
.numeric("countdown_1", ea.STATE_SET)
.withValueMin(0)
.withValueMax(43200)
.withValueStep(1)
.withUnit("s")
.withDescription("Switch 1 Countdown"),
e
.numeric("countdown_2", ea.STATE_SET)
.withValueMin(0)
.withValueMax(43200)
.withValueStep(1)
.withUnit("s")
.withDescription("Switch 2 Countdown"),
e.enum("relay_status", ea.STATE_SET, ["off", "on", "memory"]).withDescription("Power-on Status"),
e.enum("light_mode", ea.STATE_SET, ["relay", "none", "pos"]).withDescription("Indicator Light Status"),
e
.numeric("delays_time", ea.STATE_SET)
.withValueMin(3)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Unoccupied Delay Time"),
e.numeric("sensitivity", ea.STATE_SET).withValueMin(1).withValueMax(10).withValueStep(1).withDescription("Trigger Sensitivity"),
e
.enum("turn_on_light_for_person", ea.STATE_SET, ["none", "all", "on_ch1", "on_ch2"])
.withDescription("Turn On Light When Human Detected"),
e
.enum("turn_off_light_for_person", ea.STATE_SET, ["none", "all", "off_ch1", "off_ch2"])
.withDescription("Turn Off Light When Human Not Detected"),
],
meta: {
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[7, "countdown_1", tuya.valueConverter.raw],
[8, "countdown_2", tuya.valueConverter.raw],
[
14,
"relay_status",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
on: tuya.enum(1),
memory: tuya.enum(2),
}),
],
[
15,
"light_mode",
tuya.valueConverterBasic.lookup({
relay: tuya.enum(0),
none: tuya.enum(1),
pos: tuya.enum(2),
}),
],
[
101,
"presence_state",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
presence: tuya.enum(1),
}),
],
[102, "delays_time", tuya.valueConverter.raw],
[
103,
"turn_on_light_for_person",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
all: tuya.enum(1),
on_ch1: tuya.enum(2),
on_ch2: tuya.enum(3),
}),
],
[104, "sensitivity", tuya.valueConverter.raw],
[
105,
"turn_off_light_for_person",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
all: tuya.enum(1),
off_ch1: tuya.enum(2),
off_ch2: tuya.enum(3),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE28C1000000_usmqzgdm"]),
model: "_TZE28C1000000_usmqzgdm",
vendor: "Tuya",
description: "Human presence sensor 3-gang smart switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.enum("presence_state", ea.STATE, ["none", "presence"]).withDescription("Presence Status"),
e.switch().withEndpoint("l1"),
e.switch().withEndpoint("l2"),
e.switch().withEndpoint("l3"),
e
.numeric("countdown_1", ea.STATE_SET)
.withValueMin(0)
.withValueMax(43200)
.withValueStep(1)
.withUnit("s")
.withDescription("Switch 1 Countdown"),
e
.numeric("countdown_2", ea.STATE_SET)
.withValueMin(0)
.withValueMax(43200)
.withValueStep(1)
.withUnit("s")
.withDescription("Switch 2 Countdown"),
e
.numeric("countdown_3", ea.STATE_SET)
.withValueMin(0)
.withValueMax(43200)
.withValueStep(1)
.withUnit("s")
.withDescription("Switch 3 Countdown"),
e.enum("relay_status", ea.STATE_SET, ["off", "on", "memory"]).withDescription("Power-on Status"),
e.enum("light_mode", ea.STATE_SET, ["relay", "none", "pos"]).withDescription("Indicator Light Status"),
e
.numeric("delays_time", ea.STATE_SET)
.withValueMin(3)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Unoccupied Delay Time"),
e.numeric("sensitivity", ea.STATE_SET).withValueMin(1).withValueMax(10).withValueStep(1).withDescription("Trigger Sensitivity"),
e
.enum("turn_on_light_for_person", ea.STATE_SET, ["none", "all", "on_ch1", "on_ch2", "on_ch3", "on_1_2ch", "on_2_3ch", "on_1_3ch"])
.withDescription("Turn On Light When Human Detected"),
e
.enum("turn_off_light_for_person", ea.STATE_SET, [
"none",
"all",
"off_ch1",
"off_ch2",
"off_ch3",
"off_1_2ch",
"off_2_3ch",
"off_1_3ch",
])
.withDescription("Turn Off Light When Human Not Detected"),
],
meta: {
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
[7, "countdown_1", tuya.valueConverter.raw],
[8, "countdown_2", tuya.valueConverter.raw],
[9, "countdown_3", tuya.valueConverter.raw],
[
14,
"relay_status",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
on: tuya.enum(1),
memory: tuya.enum(2),
}),
],
[
15,
"light_mode",
tuya.valueConverterBasic.lookup({
relay: tuya.enum(0),
none: tuya.enum(1),
pos: tuya.enum(2),
}),
],
[
101,
"presence_state",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
presence: tuya.enum(1),
}),
],
[102, "delays_time", tuya.valueConverter.raw],
[
103,
"turn_on_light_for_person",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
all: tuya.enum(1),
on_ch1: tuya.enum(2),
on_ch2: tuya.enum(3),
on_ch3: tuya.enum(4),
on_1_2ch: tuya.enum(5),
on_2_3ch: tuya.enum(6),
on_1_3ch: tuya.enum(7),
}),
],
[104, "sensitivity", tuya.valueConverter.raw],
[
105,
"turn_off_light_for_person",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
all: tuya.enum(1),
off_ch1: tuya.enum(2),
off_ch2: tuya.enum(3),
off_ch3: tuya.enum(4),
off_1_2ch: tuya.enum(5),
off_2_3ch: tuya.enum(6),
off_1_3ch: tuya.enum(7),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_clm4gdw4", "_TZE200_2vfxweng", "_TZE200_gnw1rril", "_TZE204_ycke4deo", "_TZE284_clm4gdw4"]),
model: "TS0601_cover_10",
vendor: "Tuya",
description: "Cover motor",
extend: [tuya.modernExtend.tuyaBase({ dp: true, respondToMcuVersionResponse: true })],
exposes: [te.coverPosition(), te.motorDirection()],
meta: {
tuyaDatapoints: [
[
1,
"state",
tuya.valueConverterBasic.lookup({
OPEN: tuya.enum(2),
STOP: tuya.enum(1),
CLOSE: tuya.enum(0),
}),
],
[2, "position", tuya.valueConverter.coverPosition],
[3, "position", tuya.valueConverter.raw],
[5, "motor_direction", tuya.valueConverterBasic.lookup({ normal: false, reversed: true })], // maybe enum ?
],
},
whiteLabel: [tuya.whitelabel("Nova Digital", "ZC-GM42", "Roller blind motor", ["_TZE204_ycke4deo"])],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_ax8a8ahx"]),
model: "ZM79E-DT",
vendor: "Tervix",
description: "Pro Line Zigbee curtain motor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.text("work_state", ea.STATE),
te.coverPosition(),
e.enum("opening_mode", ea.STATE_SET, ["tilt", "lift"]).withDescription("Opening mode"),
te.motorDirectionSide(),
e.enum("set_upper_limit", ea.STATE_SET, ["start", "stop"]).withDescription("Learning"),
e.enum("factory_reset", ea.STATE_SET, ["SET"]).withDescription("Remove limits"),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[2, "position", tuya.valueConverter.coverPositionInverted],
[3, "position", tuya.valueConverter.coverPositionInverted],
[
4,
"opening_mode",
tuya.valueConverterBasic.lookup({
tilt: tuya.enum(0),
lift: tuya.enum(1),
}),
],
[
7,
"work_state",
tuya.valueConverterBasic.lookup({
standby: tuya.enum(0),
success: tuya.enum(1),
learning: tuya.enum(2),
}),
],
[101, "motor_direction", tuya.valueConverter.motorDirectionSide],
[
102,
"set_upper_limit",
tuya.valueConverterBasic.lookup({
start: tuya.enum(1),
stop: tuya.enum(0),
}),
],
[107, "factory_reset", tuya.valueConverter.setLimit],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_2rvvqjoa"]),
model: "BX82-TYZ1",
vendor: "Manhot",
description: "Cover motor",
extend: [tuya.modernExtend.tuyaBase({ dp: true, respondToMcuVersionResponse: true })],
exposes: [te.coverPosition(), te.motorDirection()],
meta: {
tuyaDatapoints: [
[
1,
"state",
tuya.valueConverterBasic.lookup({
OPEN: tuya.enum(2),
STOP: tuya.enum(1),
CLOSE: tuya.enum(0),
}),
],
[2, "position", tuya.valueConverter.coverPosition],
[3, "position", tuya.valueConverter.raw],
[5, "motor_direction", tuya.valueConverter.tubularMotorDirection],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_2gi1hy8s"]),
model: "MB60L-ZG-ZT-TY",
vendor: "Manhot",
description: "Smart blinds motor",
extend: [tuya.modernExtend.tuyaBase({ dp: true, respondToMcuVersionResponse: true })],
exposes: [
e.battery(),
te.coverPosition(),
e.enum("set_limits", ea.STATE_SET, ["up", "down", "reset"]),
te.motorDirection(),
e.binary("tilt_mode", ea.STATE_SET, "ON", "OFF").withDescription("Step movement"),
e.binary("child_lock", ea.STATE_SET, "ON", "OFF").withDescription("Child Lock"),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[9, "position", tuya.valueConverter.coverPositionInverted],
[11, "motor_direction", tuya.valueConverter.tubularMotorDirection],
[13, "battery", tuya.valueConverter.raw],
[
16,
"set_limits",
tuya.valueConverterBasic.lookup({
up: tuya.enum(0),
down: tuya.enum(1),
reset: tuya.enum(2),
}),
],
[101, "child_lock", tuya.valueConverter.onOff],
[103, "tilt_mode", tuya.valueConverter.onOff],
],
},
},
{
zigbeeModel: ["kud7u2l"],
fingerprint: tuya.fingerprint("TS0601", [
"_TZE200_ckud7u2l",
"_TZE200_ywdxldoj",
"_TZE200_do5qy8zo",
"_TZE200_cwnjrr72",
"_TZE200_pvvbommb",
"_TZE200_9sfg7gm0",
"_TZE200_2atgpdho",
"_TZE200_znlqjmih",
"_TZE284_znlqjmih",
"_TZE200_8thwkzxl",
"_TZE200_4eeyebrt",
"_TZE200_8whxpsiw",
"_TZE200_7fqkphoq",
"_TZE200_rufdtfyv" /* model: '07732L', vendor: 'Immax' */,
"_TZE200_lpwgshtl",
"_TZE200_rk1wojce" /* model: 'P5630S', vendor: 'Emos' */,
"_TZE200_rndg81sf" /* model: 'HY368', vendor: 'THALEOS' */,
"_TZE200_qjp4ynvi",
"_TZE200_xby0s3ta",
"_TZE200_cpmgn2cf",
]),
model: "TS0601_thermostat",
vendor: "Tuya",
description: "Radiator valve with thermostat",
whiteLabel: [
{ vendor: "Moes", model: "HY368" },
{ vendor: "Moes", model: "HY369RT" },
{ vendor: "SHOJZJ", model: "378RT" },
{ vendor: "Silvercrest", model: "TVR01" },
{ vendor: "Immax", model: "07732B" },
tuya.whitelabel("Immax", "07732L", "Radiator valve with thermostat", ["_TZE200_rufdtfyv"]),
{ vendor: "Evolveo", model: "Heat M30" },
tuya.whitelabel("Emos", "P5630S", "Radiator valve with thermostat", ["_TZE200_rk1wojce"]),
tuya.whitelabel("THALEOS", "HY368", "Radiator valve with thermostat", ["_TZE200_rndg81sf"]),
],
meta: {
tuyaThermostatPreset: legacy.thermostatPresets,
tuyaThermostatSystemMode: legacy.thermostatSystemModes3,
},
ota: true,
extend: [tuya.modernExtend.tuyaBase({ forceTimeUpdates: true, timeStart: "1970" })],
fromZigbee: [legacy.fromZigbee.tuya_thermostat, fz.ignore_tuya_set_time],
toZigbee: [
legacy.toZigbee.tuya_thermostat_child_lock,
legacy.toZigbee.tuya_thermostat_window_detection,
legacy.toZigbee.tuya_thermostat_valve_detection,
legacy.toZigbee.tuya_thermostat_current_heating_setpoint,
legacy.toZigbee.tuya_thermostat_auto_lock,
legacy.toZigbee.tuya_thermostat_calibration,
legacy.toZigbee.tuya_thermostat_min_temp,
legacy.toZigbee.tuya_thermostat_max_temp,
legacy.toZigbee.tuya_thermostat_boost_time,
legacy.toZigbee.tuya_thermostat_comfort_temp,
legacy.toZigbee.tuya_thermostat_eco_temp,
legacy.toZigbee.tuya_thermostat_force_to_mode,
legacy.toZigbee.tuya_thermostat_force,
legacy.toZigbee.tuya_thermostat_preset,
legacy.toZigbee.tuya_thermostat_window_detect,
legacy.toZigbee.tuya_thermostat_schedule,
legacy.toZigbee.tuya_thermostat_week,
legacy.toZigbee.tuya_thermostat_schedule_programming_mode,
legacy.toZigbee.tuya_thermostat_away_mode,
legacy.toZigbee.tuya_thermostat_away_preset,
],
exposes: [
e.child_lock(),
e.window_detection(),
e.binary("window_open", ea.STATE, true, false).withDescription("Window open?"),
e.battery_low(),
e.valve_detection(),
e.position(),
e
.climate()
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withSystemMode(["heat", "auto", "off"], ea.STATE_SET, "Mode of this device, in the `heat` mode the TS0601 will remain continuously heating, i.e. it does not regulate " +
"to the desired temperature. If you want TRV to properly regulate the temperature you need to use mode `auto` " +
"instead setting the desired temperature.")
.withLocalTemperatureCalibration(-9, 9, 0.5, ea.STATE_SET)
.withPreset(["schedule", "manual", "boost", "complex", "comfort", "eco", "away"])
.withRunningState(["idle", "heat"], ea.STATE),
e.auto_lock(),
e.away_mode(),
e.away_preset_days(),
e.boost_time(),
e.comfort_temperature(),
e.eco_temperature(),
e.force(),
e.max_temperature().withValueMin(16).withValueMax(70),
e.min_temperature(),
e.away_preset_temperature(),
e.week(),
e
.text("workdays_schedule", ea.STATE_SET)
.withDescription('Workdays schedule, 6 entries max, example: "00:20/5°C 01:20/5°C 6:59/15°C 18:00/5°C 20:00/5°C 23:30/5°C"'),
e
.text("holidays_schedule", ea.STATE_SET)
.withDescription('Holidays schedule, 6 entries max, example: "00:20/5°C 01:20/5°C 6:59/15°C 18:00/5°C 20:00/5°C 23:30/5°C"'),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_g2ki0ejr"]),
model: "BAB-1413_Pro",
vendor: "Tuya",
description: "Thermostat radiator valve",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "2000" })],
ota: true,
exposes: [
e.battery(),
e.child_lock(),
e
.numeric("max_temperature_limit", ea.STATE_SET)
.withUnit("°C")
.withDescription("Max temperature limit")
.withValueMin(15)
.withValueMax(45)
.withValueStep(0.5),
e
.numeric("min_temperature_limit", ea.STATE_SET)
.withUnit("°C")
.withDescription("Min temperature limit")
.withValueMin(5)
.withValueMax(15)
.withValueStep(0.5),
e.window_detection(),
e.open_window_temperature().withValueMin(5).withValueMax(30).withValueStep(0.5),
e.comfort_temperature().withValueMin(5).withValueMax(35),
e.eco_temperature().withValueMin(5).withValueMax(35).withValueStep(0.5),
e.holiday_temperature().withValueMin(5).withValueMax(35).withValueStep(0.5),
e
.climate()
.withPreset(["auto", "manual", "holiday", "comfort"])
.withLocalTemperatureCalibration(-9, 9, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withSetpoint("current_heating_setpoint", 5, 45, 0.5, ea.STATE_SET)
.withSystemMode(["off", "heat"], ea.STATE_SET, "Only for Homeassistant")
.withRunningState(["idle", "heat"], ea.STATE_SET),
tuya.exposes.frostProtection("When Anti-Freezing function is activated, the temperature in the house is kept " +
'at 8 °C, the device display "AF".press the pair button to cancel.'),
e
.numeric("boost_time", ea.STATE_SET)
.withUnit("min")
.withDescription("Boost running time. Minimum 0 - maximum 24 hours")
.withValueMin(0)
.withValueMax(1440)
.withValueStep(15)
.withCategory("config"),
e.numeric("boost_timeset_countdown", ea.STATE).withUnit("min").withDescription("Boost time remaining"),
...tuya.exposes.scheduleAllDays(ea.STATE_SET, "06:00/21.5 17:20/26 20:00/21 24:00/18").map((text) => text.withCategory("config")),
e.binary("valve", ea.STATE, "CLOSED", "OPEN"),
// e.enum('factory_reset', ea.STATE_SET, ['factory reset']).withLabel('Factory reset').withDescription('Reset all settings to factory ones'),
e.binary("factory_reset", ea.STATE_SET, "ON", "OFF").withDescription("Back to factory settings, USE WITH CAUTION"),
tuya.exposes.errorStatus(),
],
meta: {
tuyaDatapoints: [
[
49,
"running_state",
tuya.valueConverterBasic.lookup({
heat: tuya.enum(1),
idle: tuya.enum(0),
}),
],
[
49,
"system_mode",
tuya.valueConverterBasic.lookup({
heat: tuya.enum(1),
off: tuya.enum(0),
}),
],
[
2,
"preset",
tuya.valueConverterBasic.lookup({
comfort: tuya.enum(3),
auto: tuya.enum(0),
manual: tuya.enum(2),
holiday: tuya.enum(1),
}),
],
[4, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[5, "local_temperature", tuya.valueConverter.divideBy10],
[6, "battery", tuya.valueConverter.raw],
[7, "child_lock", tuya.valueConverter.lockUnlock],
[9, "max_temperature_limit", tuya.valueConverter.divideBy10],
[10, "min_temperature_limit", tuya.valueConverter.divideBy10],
[14, "window_detection", tuya.valueConverter.onOff],
[16, "open_window_temperature", tuya.valueConverter.divideBy10],
[17, "open_window_time", tuya.valueConverter.raw],
// [18, 'backlight', tuya.valueConverter.raw],
[19, "factory_reset", tuya.valueConverter.onOff],
[21, "holiday_temperature", tuya.valueConverter.raw],
[24, "comfort_temperature", tuya.valueConverter.divideBy10],
[25, "eco_temperature", tuya.valueConverter.divideBy10],
[17, "schedule_monday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(1)],
[18, "schedule_tuesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(2)],
[19, "schedule_wednesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(3)],
[20, "schedule_thursday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(4)],
[21, "schedule_friday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(5)],
[22, "schedule_saturday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(6)],
[23, "schedule_sunday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(7)],
// [35, 'error_status', tuya.valueConverter.raw],
[36, "frost_protection", tuya.valueConverter.onOff],
// [37, 'boost_time', tuya.valueConverter.raw],
// [38, 'boost_timeset_countdown', tuya.valueConverter.countdown],
// [39, 'Switch Scale', tuya.valueConverter.raw],
// Did not work properly from Smart life also
// [47, 'local_temperature_calibration', tuya.valueConverter.divideBy10],
// [48, 'valve_testing', tuya.valueConverter.raw],
[49, "valve", tuya.valueConverter.trueFalseEnum0],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_tbgecldg", "_TZE284_tbgecldg", "_TZE200_tbgecldg"]),
model: "PO-THCO-EAU",
vendor: "Powernity",
description: "Thermostat radiator valve",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "2000" })],
exposes: [
e.battery().withUnit("%"),
e.child_lock(),
e.comfort_temperature().withValueMin(0.5).withValueMax(29.5),
e.eco_temperature().withValueMin(0.5).withValueMax(29.5),
e.holiday_temperature().withValueMin(0.5).withValueMax(29.5),
e
.numeric("auto_temperature", ea.STATE_SET)
.withDescription("Auto settings temperature")
.withUnit("°C")
.withValueMin(0.5)
.withValueStep(0.5)
.withValueMax(29.5),
e
.climate()
.withPreset(["auto", "manual", "holiday"])
.withLocalTemperatureCalibration(-5.5, 5.5, 0.1, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withSetpoint("current_heating_setpoint", 0.5, 29.5, 0.5, ea.STATE_SET),
e.binary("boost_heating", ea.STATE_SET, "ON", "OFF").withDescription("Boost Heating: the device will enter the boost heating mode."),
e
.numeric("boost_time", ea.STATE_SET)
.withUnit("s")
.withDescription("Setting " +
"minimum 0 - maximum 900 seconds boost time. The boost function is activated. The remaining " +
"time for the function will be counted down in seconds ( 900 to 0 ).")
.withValueMin(0)
.withValueMax(900),
e.binary("window_open", ea.STATE, "OPEN", "CLOSE").withDescription("Window status CLOSE or OPEN "),
e.open_window_temperature().withValueMin(5).withValueMax(25),
e
.numeric("open_window_time", ea.STATE_SET)
.withDescription("In the setting time, when the range of indoor temperature changes reaches the set range, the window opening reminder will be displayed")
.withUnit("minutes")
.withValueMin(0)
.withValueMax(60)
.withValueStep(1),
tuya.exposes.errorStatus(),
],
meta: {
tuyaDatapoints: [
[
2,
"preset",
tuya.valueConverterBasic.lookup({
auto: tuya.enum(0),
manual: tuya.enum(1),
holiday: tuya.enum(2),
}),
],
[16, "current_heating_setpoint", tuya.valueConverter.divideBy2],
[24, "local_temperature", tuya.valueConverter.divideBy10],
[30, "child_lock", tuya.valueConverter.lockUnlock],
[34, "battery", tuya.valueConverterBasic.scale(0, 100, 50, 150)],
[101, "comfort_temperature", tuya.valueConverter.divideBy2],
[102, "eco_temperature", tuya.valueConverter.divideBy2],
[103, "holiday_temperature", tuya.valueConverter.divideBy2],
[104, "local_temperature_calibration", tuya.valueConverter.divideBy10],
[105, "auto_temperature", tuya.valueConverter.divideBy2],
[106, "boost_heating", tuya.valueConverter.onOff],
[107, "window_open", tuya.valueConverter.onOff],
[116, "open_window_temperature", tuya.valueConverter.divideBy2],
[117, "open_window_time", tuya.valueConverter.raw],
[118, "boost_time", tuya.valueConverter.countdown],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE200_68nvbio9",
"_TZE200_pw7mji0l",
"_TZE200_cf1sl3tj",
"_TZE200_nw1r9hp6",
"_TZE200_9p5xmj5r",
"_TZE200_eevqq1uv",
"_TZE204_ejh6owwz",
"_TZE200_ba69l9ol",
"_TZE200_68nvbi09",
"_TZE200_vexa5o82",
"_TZE200_sfqyhvpv",
"_TZE284_n73badib",
]),
model: "TS0601_cover_3",
vendor: "Tuya",
description: "Cover motor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
options: [exposes.options.invert_cover()],
exposes: [
e.battery(),
te.coverPosition(),
e.enum("reverse_direction", ea.STATE_SET, ["forward", "back"]).withDescription("Reverse the motor direction"),
te.coverLimit(),
te.clickControl(),
e.binary("motor_fault", ea.STATE, true, false),
],
whiteLabel: [
tuya.whitelabel("Zemismart", "ZM16EL-03/33", "Cover motor", ["_TZE200_68nvbio9", "_TZE200_68nvbi09"]),
tuya.whitelabel("Zemismart", "ZM25EL", "Cover motor", ["_TZE200_pw7mji0l"]),
tuya.whitelabel("Zemismart", "ZM85EL-2Z", "Roman Rod I type U curtains track", ["_TZE200_cf1sl3tj", "_TZE200_nw1r9hp6"]),
tuya.whitelabel("Hiladuo", "B09M3R35GC", "Motorized roller shade", ["_TZE200_9p5xmj5r"]),
tuya.whitelabel("Tuya", "MYQ-RM25-1.3/25-BZ", "Tubular roller blind motor", ["_TZE200_vexa5o82"]),
tuya.whitelabel("Shaman", "25EB-1/30-TYZ", "Motorized roller shade", ["_TZE200_sfqyhvpv"]),
tuya.whitelabel("Nova Digital", "ZBCMR-02", "Roller Blind Motor", ["_TZE284_n73badib"]),
],
meta: {
// All datapoints go in here
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[2, "position", tuya.valueConverter.coverPosition],
[3, "position", tuya.valueConverter.coverPosition],
[
5,
"reverse_direction",
tuya.valueConverterBasic.lookup({
forward: tuya.enum(0),
back: tuya.enum(1),
}),
],
[12, "motor_fault", tuya.valueConverter.trueFalse1],
[13, "battery", tuya.valueConverter.raw],
[16, "cover_limit", tuya.valueConverter.coverLimit],
[20, "click_control", tuya.valueConverter.clickControl],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_zah67ekd", "_TZE200_icka1clh"]),
model: "TS0601_cover_4",
vendor: "Tuya",
description: "Cover",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
te.coverPosition(),
te.motorDirection(),
e.numeric("motor_speed", ea.STATE_SET).withValueMin(0).withValueMax(255).withDescription("Motor speed").withUnit("rpm"),
e.enum("opening_mode", ea.STATE_SET, ["tilt", "lift"]).withDescription("Opening mode"),
e.enum("set_upper_limit", ea.SET, ["SET"]).withDescription("Set the upper limit, to reset limits use factory_reset"),
e.enum("set_bottom_limit", ea.SET, ["SET"]).withDescription("Set the bottom limit, to reset limits use factory_reset"),
e.binary("factory_reset", ea.SET, true, false).withDescription("Factory reset the device"),
],
whiteLabel: [tuya.whitelabel("Moes", "AM43-0.45/40-ES-EB", "Roller blind/shades drive motor", ["_TZE200_zah67ekd", "_TZE200_icka1clh"])],
configure: (device, coordinatorEndpoint) => {
device.powerSource = "Mains (single phase)";
device.save();
},
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[2, "position", tuya.valueConverter.coverPosition],
[3, "position", tuya.valueConverter.raw],
[5, "motor_direction", tuya.valueConverter.tubularMotorDirection],
[7, null, null], // work_state, not useful, ignore
[
101,
"opening_mode",
tuya.valueConverterBasic.lookup({
tilt: tuya.enum(0),
lift: tuya.enum(1),
}),
],
[102, "factory_reset", tuya.valueConverter.raw],
[103, "set_upper_limit", tuya.valueConverter.setLimit],
[104, "set_bottom_limit", tuya.valueConverter.setLimit],
[105, "motor_speed", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE200_sur6q7ko" /* model: '3012732', vendor: 'LSC Smart Connect' */,
"_TZE200_hue3yfsn" /* model: 'TV02-Zigbee', vendor: 'Tuya' */,
"_TZE200_e9ba97vf" /* model: 'TV01-ZB', vendor: 'Moes' */,
"_TZE200_husqqvux" /* model: 'TSL-TRV-TV01ZG', vendor: 'Tesla Smart' */,
"_TZE200_lnbfnyxd" /* model: 'TSL-TRV-TV01ZG', vendor: 'Tesla Smart' */,
"_TZE200_fsow0qsk" /* model: 'TSL-TRV-TV05ZG', vendor: 'Tesla Smart' */,
"_TZE200_lllliz3p" /* model: 'TV02-Zigbee', vendor: 'Tuya' */,
"_TZE200_mudxchsu" /* model: 'TV05-ZG curve', vendor: 'Tuya' */,
"_TZE200_7yoranx2" /* model: 'TV01-ZB', vendor: 'Moes' */,
"_TZE200_kds0pmmv",
"_TZE200_py4cm3he" /* model: 'TV06-Zigbee', vendor: 'Tuya' */,
"_TZE200_wsbfwodu" /* model: 'HA-08 THERMO', vendor: 'AlecoAir' */,
"_TZE200_x9axofse" /* model: 'ZTRV-ZX-TV02', vendor: 'Moes' */,
"_TZE200_lhzapfg9" /* model: 'ETT-8', vendor: 'EKF' */,
"_TZE200_k1tumq4t",
]),
model: "TV02-Zigbee",
vendor: "Tuya",
description: "Thermostat radiator valve",
whiteLabel: [
{ vendor: "Moes", model: "TV01-ZB" },
{ vendor: "AVATTO", model: "TRV06-1" },
{ vendor: "Tesla Smart", model: "TSL-TRV-TV01ZG" },
{ vendor: "Tesla Smart", model: "TSL-TRV-TV05ZG" },
{ vendor: "Unknown/id3.pl", model: "GTZ08" },
tuya.whitelabel("Moes", "ZTRV-ZX-TV01-MS", "Thermostat radiator valve", ["_TZE200_7yoranx2"]),
tuya.whitelabel("Moes", "TV01-ZB", "Thermostat radiator valve", ["_TZE200_e9ba97vf"]),
tuya.whitelabel("AlecoAir", "HA-08_THERMO", "Thermostat radiator valve", ["_TZE200_wsbfwodu"]),
tuya.whitelabel("GIEX", "TV06", "Thermostat radiator valve", ["_TZE200_py4cm3he"]),
tuya.whitelabel("Moes", "ZTRV-ZX-TV02", "Thermostat radiator valve", ["_TZE200_x9axofse"]),
tuya.whitelabel("EKF", "ETT-8", "Thermostat radiator valve", ["_TZE200_lhzapfg9"]),
],
ota: true,
extend: [tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true, timeStart: "1970" })],
exposes: [
e.battery_low(),
e.child_lock(),
e.open_window(),
e.open_window_temperature().withValueMin(5).withValueMax(30),
e.comfort_temperature().withValueMin(5).withValueMax(30),
e.eco_temperature().withValueMin(5).withValueMax(30),
e
.climate()
.withPreset(["auto", "manual", "holiday"])
.withLocalTemperatureCalibration(-5, 5, 0.1, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withSetpoint("current_heating_setpoint", 5, 30, 0.5, ea.STATE_SET)
.withSystemMode(["off", "heat"], ea.STATE_SET, "Only for Homeassistant"),
e
.binary("heating_stop", ea.STATE_SET, "ON", "OFF")
.withDescription("Battery life can be prolonged" +
" by switching the heating off. To achieve this, the valve is closed fully. To activate the " +
'heating stop, the device display "HS", press the pair button to cancel.'),
tuya.exposes.frostProtection("When Anti-Freezing function is activated, the temperature in the house is kept " +
'at 8 °C, the device display "AF".press the pair button to cancel.'),
e
.numeric("boost_timeset_countdown", ea.STATE_SET)
.withUnit("s")
.withDescription("Setting " +
"minimum 0 - maximum 465 seconds boost time. The boost (♨) function is activated. The remaining " +
"time for the function will be counted down in seconds ( 465 to 0 ).")
.withValueMin(0)
.withValueMax(465),
e.holiday_temperature().withValueMin(5).withValueMax(30),
e
.text("holiday_start_stop", ea.STATE_SET)
.withDescription("The holiday mode will automatically start " +
"at the set time starting point and run the holiday temperature. Can be defined in the following format: " +
"`startYear/startMonth/startDay startHours:startMinutes | endYear/endMonth/endDay endHours:endMinutes`. " +
'For example: `2022/10/01 16:30 | 2022/10/21 18:10`. After the end of holiday mode, it switches to "auto" ' +
"mode and uses schedule."),
e
.enum("working_day", ea.STATE_SET, ["mon_sun", "mon_fri+sat+sun", "separate"])
.withDescription("`mon_sun` " +
"- schedule for Monday used for each day (define it only for Monday). `mon_fri+sat+sun` - schedule for " +
"workdays used from Monday (define it only for Monday), Saturday and Sunday are defined separately. `separate` " +
"- schedule for each day is defined separately."),
e
.composite("schedule", "schedule", ea.SET)
.withFeature(e.enum("week_day", ea.SET, ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]))
.withFeature(e.text("schedule", ea.SET))
.withDescription('Schedule will work with "auto" preset. In this mode, the device executes ' +
"a preset week programming temperature time and temperature. Before using these properties, check `working_day` " +
"property. Each day can contain up to 10 segments. At least 1 segment should be defined. Different count of segments " +
"can be defined for each day, e.g., 3 segments for Monday, 5 segments for Thursday, etc. It should be defined in the " +
"following format: `hours:minutes/temperature`. Minutes can be only tens, i.e., 00, 10, 20, 30, 40, 50. Segments should " +
"be divided by space symbol. Each day should end with the last segment of 24:00. Examples: `04:00/20 08:30/22 10:10/18 " +
"18:40/24 22:50/19.5`; `06:00/21.5 17:20/26 24:00/18`. The temperature will be set from the beginning/start of one " +
"period and until the next period, e.g., `04:00/20 24:00/22` means that from 00:00 to 04:00 temperature will be 20 " +
"degrees and from 04:00 to 00:00 temperature will be 22 degrees."),
...tuya.exposes.scheduleAllDays(ea.STATE, "HH:MM/C"),
e.binary("online", ea.STATE_SET, "ON", "OFF").withDescription("The current data request from the device."),
tuya.exposes.errorStatus(),
],
meta: {
tuyaDatapoints: [
[2, "preset", tuya.valueConverter.tv02Preset()],
[8, "open_window", tuya.valueConverter.onOff],
[10, null, tuya.valueConverter.TV02FrostProtection],
[10, "frost_protection", tuya.valueConverter.TV02FrostProtection],
[16, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[24, "local_temperature", tuya.valueConverter.divideBy10],
[27, "local_temperature_calibration", tuya.valueConverter.divideBy10],
[
31,
"working_day",
tuya.valueConverterBasic.lookup({
mon_sun: tuya.enum(0),
"mon_fri+sat+sun": tuya.enum(1),
separate: tuya.enum(2),
}),
],
[32, "holiday_temperature", tuya.valueConverter.divideBy10],
[35, "battery_low", tuya.valueConverter.trueFalse0],
[40, "child_lock", tuya.valueConverter.lockUnlock],
[45, "error_status", tuya.valueConverter.raw],
[46, "holiday_start_stop", tuya.valueConverter.thermostatHolidayStartStop],
[101, "boost_timeset_countdown", tuya.valueConverter.raw],
[102, "open_window_temperature", tuya.valueConverter.divideBy10],
[104, "comfort_temperature", tuya.valueConverter.divideBy10],
[105, "eco_temperature", tuya.valueConverter.divideBy10],
[106, "schedule", tuya.valueConverter.thermostatScheduleDaySingleDP],
[107, null, tuya.valueConverter.TV02SystemMode],
[107, "system_mode", tuya.valueConverter.TV02SystemMode],
[107, "heating_stop", tuya.valueConverter.TV02SystemMode],
[115, "online", tuya.valueConverter.onOffNotStrict],
[108, "schedule_monday", tuya.valueConverter.thermostatScheduleDaySingleDP],
[112, "schedule_tuesday", tuya.valueConverter.thermostatScheduleDaySingleDP],
[109, "schedule_wednesday", tuya.valueConverter.thermostatScheduleDaySingleDP],
[113, "schedule_thursday", tuya.valueConverter.thermostatScheduleDaySingleDP],
[110, "schedule_friday", tuya.valueConverter.thermostatScheduleDaySingleDP],
[114, "schedule_saturday", tuya.valueConverter.thermostatScheduleDaySingleDP],
[111, "schedule_sunday", tuya.valueConverter.thermostatScheduleDaySingleDP],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_0hg58wyk" /* model: 'S366', vendor: 'Cloud Even' */]),
model: "TS0601_thermostat_2",
vendor: "Tuya",
description: "Thermostat radiator valve",
whiteLabel: [{ vendor: "S366", model: "Cloud Even" }],
extend: [tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true, timeStart: "1970" })],
meta: {
tuyaDatapoints: [
[1, "system_mode", tuya.valueConverterBasic.lookup({ heat: true, off: false })],
[
2,
"preset",
tuya.valueConverterBasic.lookup({
manual: tuya.enum(0),
holiday: tuya.enum(1),
program: tuya.enum(2),
}),
],
[3, null, null], // TODO: Unknown DP
[8, "open_window", tuya.valueConverter.onOff],
[10, "frost_protection", tuya.valueConverter.onOff],
[16, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[24, "local_temperature", tuya.valueConverter.divideBy10],
[27, "local_temperature_calibration", tuya.valueConverter.divideBy10],
[35, "battery_low", tuya.valueConverter.trueFalse0],
[40, "child_lock", tuya.valueConverter.lockUnlock],
[45, "error_status", tuya.valueConverter.raw],
[101, "schedule_monday", tuya.valueConverter.thermostatScheduleDayMultiDP],
[102, "schedule_tuesday", tuya.valueConverter.thermostatScheduleDayMultiDP],
[103, "schedule_wednesday", tuya.valueConverter.thermostatScheduleDayMultiDP],
[104, "schedule_thursday", tuya.valueConverter.thermostatScheduleDayMultiDP],
[105, "schedule_friday", tuya.valueConverter.thermostatScheduleDayMultiDP],
[106, "schedule_saturday", tuya.valueConverter.thermostatScheduleDayMultiDP],
[107, "schedule_sunday", tuya.valueConverter.thermostatScheduleDayMultiDP],
],
},
exposes: [
e.battery_low(),
e.child_lock(),
e.open_window(),
tuya.exposes.frostProtection(),
tuya.exposes.errorStatus(),
e
.climate()
.withSystemMode(["off", "heat"], ea.STATE_SET)
.withPreset(["manual", "holiday", "program"])
.withLocalTemperatureCalibration(-5, 5, 0.1, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withSetpoint("current_heating_setpoint", 5, 30, 0.5, ea.STATE_SET),
...tuya.exposes.scheduleAllDays(ea.STATE_SET, "HH:MM/C HH:MM/C HH:MM/C HH:MM/C"),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_hodyryli"]),
model: "ZT08",
vendor: "Tuya",
description: "Weather station with clock, internal/external temperature and humidity",
fromZigbee: [tuya.fz.datapoints, fzLocal.zt08ClockSync],
toZigbee: [tuya.tz.datapoints],
configure: tuya.configureMagicPacket,
exposes: [
e.temperature(),
e.humidity(),
exposes.numeric("external_temperature", ea.STATE).withUnit("°C").withDescription("External probe temperature"),
tuya.exposes.batteryState(),
e.enum("time_format", ea.STATE, ["12h", "24h"]).withDescription("Clock time format on LCD"),
],
meta: {
tuyaDatapoints: [
[1, "temperature", tuya.valueConverter.divideBy10],
[2, "humidity", tuya.valueConverter.raw],
[3, "battery_state", tuya.valueConverter.batteryState],
[17, "time_format", tuya.valueConverter.onOff],
[38, "external_temperature", tuya.valueConverter.divideBy10],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE200_bvu2wnxz" /* model: 'ME167', vendor: 'AVATTO' */,
"_TZE200_6rdj8dzm" /* model: 'ME167', vendor: 'AVATTO' */,
"_TZE200_9xfjixap" /* model: 'ME167', vendor: 'AVATTO' */,
"_TZE200_jkfbph7l" /* model: 'ME167', vendor: 'AVATTO' */,
"_TZE200_rxntag7i" /* model: 'ME168', vendor: 'AVATTO' */,
"_TZE200_4utwozi2" /* model: 'ME167', vendor: 'AVATTO' */,
"_TZE200_yqgbrdyo",
"_TZE284_p3dbf6qs",
"_TZE200_rxq4iti9",
"_TZE204_ogx8u5z6",
"_TZE200_4utwoz2",
"_TZE284_ogx8u5z6",
"_TZE284_o3x45p96" /* model: 'TRV06', vendor: 'AVATTO' */,
"_TZE284_c6wv4xyo" /* model: 'AVATTO_TRV06', vendor: 'AVATTO' */,
"_TZE204_o3x45p96" /* model: 'AVATTO_TRV06', vendor: 'AVATTO' */,
"_TZE200_ow09xlxm" /* model: 'TRV06-AT', vendor: 'THALEOS' */,
"_TZE284_rv6iuyxb" /* model: 'TRV06-AT', vendor: 'THALEOS' */,
"_TZE200_rv6iuyxb" /* model: 'TRV06-AT', vendor: 'THALEOS' */,
"_TZE200_suxywabt" /* model: 'TRV06-AT', vendor: 'THALEOS' */,
"_TZE200_d3z1ukqw" /* model: 'Eco-4160', vendor: 'Echos' */,
]),
model: "TS0601_thermostat_3",
vendor: "Tuya",
description: "Thermostatic radiator valve",
whiteLabel: [
tuya.whitelabel("AVATTO", "ME167", "Thermostatic radiator valve", [
"_TZE200_bvu2wnxz",
"_TZE200_6rdj8dzm",
"_TZE200_9xfjixap",
"_TZE200_jkfbph7l",
"_TZE200_4utwoz2",
"_TZE200_4utwozi2",
]),
tuya.whitelabel("AVATTO", "ME168_1", "Thermostatic radiator valve", ["_TZE200_rxntag7i"]),
tuya.whitelabel("AVATTO", "TRV06_1", "Thermostatic radiator valve", ["_TZE284_o3x45p96"]),
tuya.whitelabel("EARU", "TRV06", "Smart thermostat module", ["_TZE200_yqgbrdyo", "_TZE200_rxq4iti9"]),
tuya.whitelabel("AVATTO", "AVATTO_TRV06", "Thermostatic radiator valve", ["_TZE284_c6wv4xyo", "_TZE204_o3x45p96"]),
tuya.whitelabel("THALEOS", "TRV06-AT", "Thermostatic radiator valve", [
"_TZE200_ow09xlxm",
"_TZE284_rv6iuyxb",
"_TZE200_rv6iuyxb",
"_TZE200_suxywabt",
]),
tuya.whitelabel("Echos", "Eco-4160", "Thermostatic radiator valve", ["_TZE200_d3z1ukqw"]),
],
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "2000" })],
exposes: [
e.child_lock(),
e.battery_low(),
e
.climate()
.withSetpoint("current_heating_setpoint", 5, 35, 1, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withSystemMode(["auto", "heat", "off"], ea.STATE_SET)
.withRunningState(["idle", "heat"], ea.STATE)
.withPiHeatingDemand()
.withLocalTemperatureCalibration(-20, 20, 1, ea.STATE_SET),
...tuya.exposes.scheduleAllDays(ea.STATE_SET, "HH:MM/C HH:MM/C HH:MM/C HH:MM/C HH:MM/C HH:MM/C"),
e
.binary("scale_protection", ea.STATE_SET, "ON", "OFF")
.withDescription("If the heat sink is not fully opened within " +
"two weeks or is not used for a long time, the valve will be blocked due to silting up and the heat sink will not be " +
"able to be used. To ensure normal use of the heat sink, the controller will automatically open the valve fully every " +
'two weeks. It will run for 30 seconds per time with the screen displaying "Ad", then return to its normal working state ' +
"again."),
e
.binary("frost_protection", ea.STATE_SET, "ON", "OFF")
.withDescription("When the room temperature is lower than 5 °C, the valve opens; when the temperature rises to 8 °C, the valve closes."),
e.numeric("error", ea.STATE).withDescription('If NTC is damaged, "Er" will be on the TRV display.'),
],
meta: {
tuyaDatapoints: [
[
2,
"system_mode",
tuya.valueConverterBasic.lookup({
auto: tuya.enum(0),
heat: tuya.enum(1),
off: tuya.enum(2),
}),
],
[
3,
"running_state",
tuya.valueConverterBasic.lookup({
heat: tuya.enum(0),
idle: tuya.enum(1),
}),
],
[4, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[5, "local_temperature", tuya.valueConverter.divideBy10],
[7, "child_lock", tuya.valueConverter.lockUnlock],
[28, "schedule_monday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(1, 6)],
[29, "schedule_tuesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(2, 6)],
[30, "schedule_wednesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(3, 6)],
[31, "schedule_thursday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(4, 6)],
[32, "schedule_friday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(5, 6)],
[33, "schedule_saturday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(6, 6)],
[34, "schedule_sunday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(7, 6)],
[35, null, tuya.valueConverter.errorOrBatteryLow],
[36, "frost_protection", tuya.valueConverter.onOff],
[39, "scale_protection", tuya.valueConverter.onOff],
[47, "local_temperature_calibration", tuya.valueConverter.raw],
[101, "pi_heating_demand", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_pcdmj88b", "_TZE284_pcdmj88b"]),
model: "TS0601_thermostat_4",
vendor: "Tuya",
description: "Thermostatic radiator valve",
extend: [tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true, timeStart: "1970" })],
exposes: [
e.child_lock(),
e.battery(),
e.battery_low(),
e
.climate()
.withSetpoint("current_heating_setpoint", 5, 45, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withPreset(["schedule", "holiday", "manual", "comfort", "eco"])
.withSystemMode(["off", "heat"], ea.STATE)
.withLocalTemperatureCalibration(-12, 12, 1, ea.STATE_SET),
...tuya.exposes.scheduleAllDays(ea.STATE_SET, "HH:MM/C HH:MM/C HH:MM/C HH:MM/C HH:MM/C HH:MM/C"),
e.holiday_temperature().withValueMin(5).withValueMax(45),
e.comfort_temperature().withValueMin(5).withValueMax(45),
e.eco_temperature().withValueMin(5).withValueMax(45),
e
.binary("scale_protection", ea.STATE_SET, "ON", "OFF")
.withDescription("If the heat sink is not fully opened within " +
"two weeks or is not used for a long time, the valve will be blocked due to silting up and the heat sink will not be " +
"able to be used. To ensure normal use of the heat sink, the controller will automatically open the valve fully every " +
'two weeks. It will run for 30 seconds per time with the screen displaying "Ad", then return to its normal working state ' +
"again."),
e
.binary("frost_protection", ea.STATE_SET, "ON", "OFF")
.withDescription("When the room temperature is lower than 5 °C, the valve opens; when the temperature rises to 8 °C, the valve closes."),
e.numeric("error", ea.STATE).withDescription('If NTC is damaged, "Er" will be on the TRV display.'),
e.binary("boost_heating", ea.STATE_SET, "ON", "OFF").withDescription("Boost Heating: the device will enter the boost heating mode."),
],
meta: {
tuyaDatapoints: [
[
2,
"preset",
tuya.valueConverterBasic.lookup({
schedule: tuya.enum(0),
holiday: tuya.enum(1),
manual: tuya.enum(2),
comfort: tuya.enum(3),
eco: tuya.enum(4),
}),
],
[4, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[5, "local_temperature", tuya.valueConverter.divideBy10],
[6, "battery", tuya.valueConverter.raw],
[7, "child_lock", tuya.valueConverter.lockUnlock],
[21, "holiday_temperature", tuya.valueConverter.divideBy10],
[24, "comfort_temperature", tuya.valueConverter.divideBy10],
[25, "eco_temperature", tuya.valueConverter.divideBy10],
[28, "schedule_monday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(1)],
[29, "schedule_tuesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(2)],
[30, "schedule_wednesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(3)],
[31, "schedule_thursday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(4)],
[32, "schedule_friday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(5)],
[33, "schedule_saturday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(6)],
[34, "schedule_sunday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(7)],
[35, "fault_alarm", tuya.valueConverter.errorOrBatteryLow],
[36, "frost_protection", tuya.valueConverter.onOff],
[37, "boost_heating", tuya.valueConverter.onOff],
[39, "scale_protection", tuya.valueConverter.onOff],
[47, "local_temperature_calibration", tuya.valueConverter.raw],
[
49,
"system_mode",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
heat: tuya.enum(1),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_m5r5nlxc"]),
model: "THAH202001",
vendor: "THALEOS",
description: "Thermostatic radiator valve",
extend: [tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true, timeStart: "1970" })],
exposes: [
e.battery(),
e.battery_low(),
e
.climate()
.withSetpoint("current_heating_setpoint", 5, 30, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
// DP2 is a single enum encoding both the system mode and the active preset.
.withPreset(["manual", "eco", "away", "holiday"])
.withSystemMode(["off", "heat"], ea.STATE_SET)
.withRunningState(["idle", "heat"], ea.STATE)
.withLocalTemperatureCalibration(-9.5, 9.5, 0.5, ea.STATE_SET),
e.binary("window_detection", ea.STATE_SET, "ON", "OFF").withDescription("Enable open window detection").withCategory("config"),
e.binary("window_open", ea.STATE, "OPEN", "CLOSE").withDescription("Open window detected (heating paused)").withCategory("diagnostic"),
e.binary("frost_protection", ea.STATE_SET, "ON", "OFF").withDescription("Anti-freeze protection").withCategory("config"),
e
.binary("scale_protection", ea.STATE_SET, "ON", "OFF")
.withDescription("Anti-scale protection: periodically opens the valve fully to prevent it seizing up")
.withCategory("config"),
e.eco_temperature().withValueMin(5).withValueMax(30).withValueStep(0.5).withCategory("config"),
e
.numeric("away_temperature", ea.STATE_SET)
.withUnit("°C")
.withValueMin(5)
.withValueMax(30)
.withValueStep(0.5)
.withDescription("Temperature used in away preset")
.withCategory("config"),
e.holiday_temperature().withValueMin(5).withValueMax(30).withValueStep(0.5).withCategory("config"),
e.numeric("operating_time", ea.STATE).withUnit("h").withDescription("Total operating time").withCategory("diagnostic"),
e
.numeric("scale_protection_remaining_time", ea.STATE)
.withUnit("h")
.withDescription("Time remaining before the next anti-scale cycle")
.withCategory("diagnostic"),
e.numeric("error", ea.STATE).withDescription("Device error code").withCategory("diagnostic"),
],
meta: {
tuyaDatapoints: [
// DP2 combines system_mode and preset. The first entry (key null) handles reads;
// the "preset"/"system_mode" entries handle writes (matched by key name).
[
2,
null,
{
from: (v) => {
const lookup = {
0: { system_mode: "heat", preset: "manual" },
1: { system_mode: "heat", preset: "eco" },
2: { system_mode: "heat", preset: "away" },
3: { system_mode: "heat", preset: "holiday" },
4: { system_mode: "off" },
};
return lookup[v] ?? { system_mode: "heat", preset: "manual" };
},
},
],
[
2,
"preset",
tuya.valueConverterBasic.lookup({
manual: tuya.enum(0),
eco: tuya.enum(1),
away: tuya.enum(2),
holiday: tuya.enum(3),
}),
],
[
2,
"system_mode",
tuya.valueConverterBasic.lookup({
heat: tuya.enum(0),
off: tuya.enum(4),
}),
],
[
3,
"running_state",
tuya.valueConverterBasic.lookup({
heat: tuya.enum(0),
idle: tuya.enum(1),
}),
],
[4, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[5, "local_temperature", tuya.valueConverter.divideBy10],
[6, "battery", tuya.valueConverter.raw],
[14, "window_detection", tuya.valueConverter.onOff],
[
15,
"window_open",
tuya.valueConverterBasic.lookup({
OPEN: tuya.enum(0),
CLOSE: tuya.enum(1),
}),
],
[21, "holiday_temperature", tuya.valueConverter.divideBy10],
[24, "eco_temperature", tuya.valueConverter.divideBy10],
[25, "away_temperature", tuya.valueConverter.divideBy10],
[35, null, tuya.valueConverter.errorOrBatteryLow],
[36, "frost_protection", tuya.valueConverter.onOff],
[39, "scale_protection", tuya.valueConverter.onOff],
[47, "local_temperature_calibration", tuya.valueConverter.divideBy10],
[101, "operating_time", tuya.valueConverter.divideBy10],
[102, "scale_protection_remaining_time", tuya.valueConverter.divideBy10],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE200_p3dbf6qs" /* model: 'ME167_1', vendor: 'AVATTO' */,
"_TZE200_hvaxb2tc" /* model: 'TRV06_1b', vendor: 'AVATTO' */,
]),
model: "TS0601_thermostat_5",
vendor: "Tuya",
description: "Thermostatic radiator valve",
whiteLabel: [
tuya.whitelabel("AVATTO", "ME167_1", "Thermostatic radiator valve", ["_TZE200_p3dbf6qs"]),
tuya.whitelabel("AVATTO", "TRV06_1b", "Thermostatic radiator valve", ["_TZE200_hvaxb2tc"]),
],
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "2000" })],
exposes: [
e.child_lock(),
e.battery_low(),
e
.climate()
.withSetpoint("current_heating_setpoint", 5, 35, 1, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withSystemMode(["auto", "heat", "off"], ea.STATE_SET)
.withRunningState(["idle", "heat"], ea.STATE)
.withPiHeatingDemand()
.withLocalTemperatureCalibration(-20, 20, 1, ea.STATE_SET),
...tuya.exposes.scheduleAllDays(ea.STATE_SET, "HH:MM/C HH:MM/C HH:MM/C HH:MM/C HH:MM/C HH:MM/C"),
e
.binary("scale_protection", ea.STATE_SET, "ON", "OFF")
.withDescription("If the heat sink is not fully opened within " +
"two weeks or is not used for a long time, the valve will be blocked due to silting up and the heat sink will not be " +
"able to be used. To ensure normal use of the heat sink, the controller will automatically open the valve fully every " +
'two weeks. It will run for 30 seconds per time with the screen displaying "Ad", then return to its normal working state ' +
"again."),
e
.binary("frost_protection", ea.STATE_SET, "ON", "OFF")
.withDescription("When the room temperature is lower than 5 °C, the valve opens; when the temperature rises to 8 °C, the valve closes."),
e.numeric("error", ea.STATE).withDescription('If NTC is damaged, "Er" will be on the TRV display.'),
],
meta: {
tuyaDatapoints: [
[
2,
"system_mode",
tuya.valueConverterBasic.lookup({
auto: tuya.enum(0),
heat: tuya.enum(1),
off: tuya.enum(2),
}),
],
[
3,
"running_state",
tuya.valueConverterBasic.lookup({
heat: tuya.enum(0),
idle: tuya.enum(1),
}),
],
[4, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[5, "local_temperature", tuya.valueConverter.divideBy10],
[7, "child_lock", tuya.valueConverter.lockUnlock],
[28, "schedule_wednesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(1, 6)],
[29, "schedule_thursday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(2, 6)],
[30, "schedule_friday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(3, 6)],
[31, "schedule_saturday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(4, 6)],
[32, "schedule_sunday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(5, 6)],
[33, "schedule_monday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(6, 6)],
[34, "schedule_tuesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(7, 6)],
[35, null, tuya.valueConverter.errorOrBatteryLow],
[36, "frost_protection", tuya.valueConverter.onOff],
[39, "scale_protection", tuya.valueConverter.onOff],
[47, "local_temperature_calibration", tuya.valueConverter.raw],
[101, "pi_heating_demand", tuya.valueConverter.raw],
],
},
},
{
fingerprint: [...tuya.fingerprint("v90ladg\u0000", ["_TYST11_wv90ladg"]), ...tuya.fingerprint("TS0601", ["_TZE200_wv90ladg"])],
model: "HT-08",
vendor: "ETOP",
description: "Wall-mount thermostat",
fromZigbee: [legacy.fromZigbee.tuya_thermostat_weekly_schedule_2, legacy.fromZigbee.etop_thermostat, fz.ignore_tuya_set_time],
toZigbee: [
legacy.toZigbee.etop_thermostat_system_mode,
legacy.toZigbee.etop_thermostat_away_mode,
legacy.toZigbee.tuya_thermostat_child_lock,
legacy.toZigbee.tuya_thermostat_current_heating_setpoint,
legacy.toZigbee.tuya_thermostat_weekly_schedule,
],
extend: [tuya.modernExtend.tuyaBase({ timeStart: "2000" })],
meta: {
thermostat: {
weeklyScheduleMaxTransitions: 4,
weeklyScheduleSupportedModes: [1], // bits: 0-heat present, 1-cool present (dec: 1-heat,2-cool,3-heat+cool)
weeklyScheduleFirstDayDpId: 101,
},
},
exposes: [
e.child_lock(),
e.away_mode(),
e
.climate()
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withSystemMode(["off", "heat", "auto"], ea.STATE_SET)
.withRunningState(["idle", "heat"], ea.STATE),
],
},
{
fingerprint: [...tuya.fingerprint("dpplnsn\u0000", ["_TYST11_2dpplnsn"]), ...tuya.fingerprint("TS0601", ["_TZE200_2dpplnsn"])],
model: "HT-10",
vendor: "ETOP",
description: "Radiator valve",
fromZigbee: [legacy.fromZigbee.tuya_thermostat_weekly_schedule_2, legacy.fromZigbee.etop_thermostat, fz.ignore_tuya_set_time],
toZigbee: [
legacy.toZigbee.etop_thermostat_system_mode,
legacy.toZigbee.etop_thermostat_away_mode,
legacy.toZigbee.tuya_thermostat_child_lock,
legacy.toZigbee.tuya_thermostat_current_heating_setpoint,
legacy.toZigbee.tuya_thermostat_weekly_schedule,
],
extend: [tuya.modernExtend.tuyaBase({ timeStart: "2000" })],
meta: {
timeout: 20000, // TRV wakes up every 10sec
thermostat: {
weeklyScheduleMaxTransitions: 4,
weeklyScheduleSupportedModes: [1], // bits: 0-heat present, 1-cool present (dec: 1-heat,2-cool,3-heat+cool)
weeklyScheduleFirstDayDpId: 101,
},
},
exposes: [
e.battery_low(),
e.child_lock(),
e.away_mode(),
e
.climate()
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withSystemMode(["off", "heat", "auto"], ea.STATE_SET)
.withRunningState(["idle", "heat"], ea.STATE),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_a4bpgplm", "_TZE200_dv8abrrz", "_TZE200_z1tyspqw", "_TZE200_bvrlmajk"]),
model: "TS0601_thermostat_1",
vendor: "Tuya",
description: "Thermostatic radiator valve",
whiteLabel: [
tuya.whitelabel("id3", "GTZ06", "Thermostatic radiator valve", ["_TZE200_z1tyspqw"]),
tuya.whitelabel("AVATTO", "TRV07", "Thermostatic radiator valve", ["_TZE200_bvrlmajk"]),
],
extend: [tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true, timeStart: "1970" })],
exposes: [
e.battery(),
e.child_lock(),
e.max_temperature(),
e.min_temperature(),
e.position(),
e.window_detection(),
e.binary("window", ea.STATE, "CLOSE", "OPEN").withDescription("Window status closed or open "),
e.binary("alarm_switch", ea.STATE, "ON", "OFF").withDescription("Thermostat in error state"),
e
.climate()
.withLocalTemperature(ea.STATE)
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withLocalTemperatureCalibration(-30, 30, 0.1, ea.STATE_SET)
.withPreset(["auto", "manual", "off", "on"], "MANUAL MODE ☝ - In this mode, the device executes manual temperature setting. " +
'When the set temperature is lower than the "minimum temperature", the valve is closed (forced closed). ' +
"AUTO MODE ⏱ - In this mode, the device executes a preset week programming temperature time and temperature. " +
"ON - In this mode, the thermostat stays open " +
"OFF - In this mode, the thermostat stays closed")
.withSystemMode(["auto", "heat", "off"], ea.STATE)
.withRunningState(["idle", "heat"], ea.STATE),
e.enum("screen_orientation", ea.STATE_SET, ["up", "down"]).withDescription("How do you look at the display (up and down supported only)"),
e
.enum("display_brightness", ea.STATE_SET, ["high", "middle", "low"])
.withDescription("How strong the LED diplay is light up (high is default)"),
e
.enum("mode", ea.STATE_SET, ["comfort", "eco"])
.withDescription("Hysteresis - comfort > switches off/on exactly at reached " +
"temperature with valve smooth from 0 to 100%, eco > 0.5 degrees above or below, valve either 0 or 100%"),
e
.numeric("switch_deviation_eco", ea.STATE_SET)
.withValueMin(0.5)
.withValueMax(5.0)
.withValueStep(0.1)
.withUnit("°C")
.withDescription("Switch deviation (energy-saving mode only)"),
...tuya.exposes.scheduleAllDays(ea.STATE_SET, "HH:MM/C HH:MM/C HH:MM/C HH:MM/C"),
e
.binary("boost_heating", ea.STATE_SET, "ON", "OFF")
.withDescription("Boost Heating: press and hold " +
" for 3 seconds, " +
"the device will enter the boost heating mode, and the ▷╵◁ will flash. The countdown will be displayed in the APP"),
e.numeric("boost_time", ea.STATE_SET).withUnit("min").withDescription("Countdown in minutes").withValueMin(0).withValueMax(1000),
],
meta: {
tuyaDatapoints: [
[1, null, tuya.valueConverter.thermostatSystemModeAndPreset(null)],
[1, "system_mode", tuya.valueConverter.thermostatSystemModeAndPreset("system_mode")],
[1, "preset", tuya.valueConverter.thermostatSystemModeAndPreset("preset")],
[2, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[3, "local_temperature", tuya.valueConverter.divideBy10],
[4, "boost_heating", tuya.valueConverter.onOff],
[5, "boost_time", tuya.valueConverter.countdown],
[6, "running_state", tuya.valueConverterBasic.lookup({ heat: 1, idle: 0 })],
[7, "window", tuya.valueConverterBasic.lookup({ OPEN: 1, CLOSE: 0 })],
[8, "window_detection", tuya.valueConverter.onOff],
[12, "child_lock", tuya.valueConverter.lockUnlock],
[13, "battery", tuya.valueConverter.raw],
[14, "alarm_switch", tuya.valueConverter.onOff],
[15, "min_temperature", tuya.valueConverter.divideBy10],
[16, "max_temperature", tuya.valueConverter.divideBy10],
[17, "schedule_monday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(1)],
[18, "schedule_tuesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(2)],
[19, "schedule_wednesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(3)],
[20, "schedule_thursday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(4)],
[21, "schedule_friday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(5)],
[22, "schedule_saturday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(6)],
[23, "schedule_sunday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(7)],
[101, "local_temperature_calibration", tuya.valueConverter.divideBy10],
[102, "position", tuya.valueConverter.divideBy10],
[116, "screen_orientation", tuya.valueConverterBasic.lookup({ up: tuya.enum(0), down: tuya.enum(2) })],
[152, "display_brightness", tuya.valueConverterBasic.lookup({ high: tuya.enum(0), middle: tuya.enum(1), low: tuya.enum(2) })],
[153, "mode", tuya.valueConverterBasic.lookup({ comfort: tuya.enum(0), eco: tuya.enum(1) })],
[154, "switch_deviation_eco", tuya.valueConverter.divideBy10],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_rtrmfadk", "_TZE204_cvcu2p6e"]),
model: "TRV601",
vendor: "Tuya",
whiteLabel: [
tuya.whitelabel("Sber", "SBDV-00185", "Thermostatic radiator valve", ["_TZE204_cvcu2p6e"]),
tuya.whitelabel("Moes", "TRV801_1", "Thermostatic radiator valve", ["_TZE204_rtrmfadk"]),
],
description: "Thermostatic radiator valve",
extend: [tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true, timeStart: "1970" })],
exposes: [
e.battery(),
e.child_lock(),
e.max_temperature(),
e.min_temperature(),
e.position(),
e.window_detection(),
e.binary("window", ea.STATE, "OPEN", "CLOSE").withDescription("Window status closed or open "),
e.binary("alarm_switch", ea.STATE, "ON", "OFF").withDescription("Thermostat in error state"),
e
.climate()
.withLocalTemperature(ea.STATE)
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withLocalTemperatureCalibration(-30, 30, 0.1, ea.STATE_SET)
.withPreset(["auto", "manual", "off", "on"], "MANUAL MODE ☝ - In this mode, the device executes manual temperature setting. " +
'When the set temperature is lower than the "minimum temperature", the valve is closed (forced closed). ' +
"AUTO MODE ⏱ - In this mode, the device executes a preset week programming temperature time and temperature. " +
"ON - In this mode, the thermostat stays open " +
"OFF - In this mode, the thermostat stays closed")
.withSystemMode(["auto", "heat", "off"], ea.STATE)
.withRunningState(["idle", "heat"], ea.STATE),
...tuya.exposes.scheduleAllDays(ea.STATE_SET, "HH:MM/C HH:MM/C HH:MM/C HH:MM/C"),
e.enum("screen_orientation", ea.STATE_SET, ["up", "down"]).withDescription("How do you look at the display (up and down supported only)"),
e
.enum("display_brightness", ea.STATE_SET, ["high", "middle", "low"])
.withDescription("How strong the LED diplay is light up (high is default)"),
e
.enum("mode", ea.STATE_SET, ["comfort", "eco"])
.withDescription("Hysteresis - comfort > switches off/on exactly at reached " +
"temperature with valve smooth from 0 to 100%, eco > 0.5 degrees above or below, valve either 0 or 100%"),
e
.numeric("switch_deviation_eco", ea.STATE_SET)
.withValueMin(0.5)
.withValueMax(5.0)
.withValueStep(0.1)
.withUnit("°C")
.withDescription("Switch deviation (energy-saving mode only)"),
],
meta: {
tuyaDatapoints: [
[1, null, tuya.valueConverter.thermostatSystemModeAndPreset(null)],
[1, "system_mode", tuya.valueConverter.thermostatSystemModeAndPreset("system_mode")],
[1, "preset", tuya.valueConverter.thermostatSystemModeAndPreset("preset")],
[2, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[3, "local_temperature", tuya.valueConverter.divideBy10],
[6, "running_state", tuya.valueConverterBasic.lookup({ heat: 1, idle: 0 })],
[7, "window", tuya.valueConverterBasic.lookup({ OPEN: 1, CLOSE: 0 })],
[8, "window_detection", tuya.valueConverter.onOff],
[12, "child_lock", tuya.valueConverter.lockUnlock],
[13, "battery", tuya.valueConverter.raw],
[14, "alarm_switch", tuya.valueConverter.onOff],
[15, "min_temperature", tuya.valueConverter.divideBy10],
[16, "max_temperature", tuya.valueConverter.divideBy10],
[17, "schedule_monday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(1)],
[18, "schedule_tuesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(2)],
[19, "schedule_wednesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(3)],
[20, "schedule_thursday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(4)],
[21, "schedule_friday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(5)],
[22, "schedule_saturday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(6)],
[23, "schedule_sunday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(7)],
[101, "local_temperature_calibration", tuya.valueConverter.divideBy10],
[108, "position", tuya.valueConverter.divideBy10],
[111, "display_brightness", tuya.valueConverterBasic.lookup({ high: tuya.enum(0), middle: tuya.enum(1), low: tuya.enum(2) })],
[113, "screen_orientation", tuya.valueConverterBasic.lookup({ up: tuya.enum(0), down: tuya.enum(2) })],
[114, "mode", tuya.valueConverterBasic.lookup({ comfort: tuya.enum(0), eco: tuya.enum(1) })],
[115, "switch_deviation_eco", tuya.valueConverter.divideBy10],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_9mjy74mp", "_TZE200_rtrmfadk", "_TZE200_9mjy74mp"]),
model: "TRV602",
vendor: "Tuya",
description: "Thermostatic radiator valve.",
whiteLabel: [tuya.whitelabel("Moes", "TRV801", "Thermostatic radiator valve", ["_TZE204_9mjy74mp", "_TZE200_9mjy74mp"])],
extend: [tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true, timeStart: "1970" })],
exposes: [
e.battery(),
e.child_lock(),
e.max_temperature(),
e.min_temperature(),
e.position(),
e.window_detection(),
e.binary("window", ea.STATE, "OPEN", "CLOSE").withDescription("Window status closed or open "),
e.binary("alarm_switch", ea.STATE, "ON", "OFF").withDescription("Thermostat in error state"),
e
.climate()
.withLocalTemperature(ea.STATE)
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withLocalTemperatureCalibration(-30, 30, 0.1, ea.STATE_SET)
.withPreset(["auto", "manual", "off", "on"], "MANUAL MODE ☝ - In this mode, the device executes manual temperature setting. " +
'When the set temperature is lower than the "minimum temperature", the valve is closed (forced closed). ' +
"AUTO MODE ⏱ - In this mode, the device executes a preset week programming temperature time and temperature. " +
"ON - In this mode, the thermostat stays open " +
"OFF - In this mode, the thermostat stays closed")
.withSystemMode(["auto", "heat", "off"], ea.STATE)
.withRunningState(["idle", "heat"], ea.STATE),
...tuya.exposes.scheduleAllDays(ea.STATE_SET, "HH:MM/C HH:MM/C HH:MM/C HH:MM/C"),
e.enum("display_brightness", ea.STATE_SET, ["high", "medium", "low"]).withDescription("Display brightness"),
e.enum("screen_orientation", ea.STATE_SET, ["up", "right", "down", "left"]).withDescription("Screen orientation"),
e
.enum("mode", ea.STATE_SET, ["comfort", "eco"])
.withDescription("Hysteresis - comfort > switches off/on exactly at reached " +
"temperature with valve smooth from 0 to 100%, eco > 0.5 degrees above or below, valve either 0 or 100%"),
],
meta: {
tuyaDatapoints: [
[1, null, tuya.valueConverter.thermostatSystemModeAndPreset(null)],
[1, "system_mode", tuya.valueConverter.thermostatSystemModeAndPreset("system_mode")],
[1, "preset", tuya.valueConverter.thermostatSystemModeAndPreset("preset")],
[2, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[3, "local_temperature", tuya.valueConverter.divideBy10],
[6, "running_state", tuya.valueConverterBasic.lookup({ heat: 1, idle: 0 })],
[7, "window", tuya.valueConverterBasic.lookup({ OPEN: 1, CLOSE: 0 })],
[8, "window_detection", tuya.valueConverter.onOff],
[12, "child_lock", tuya.valueConverter.lockUnlock],
[13, "battery", tuya.valueConverter.raw],
[14, "alarm_switch", tuya.valueConverter.onOff],
[15, "min_temperature", tuya.valueConverter.divideBy10],
[16, "max_temperature", tuya.valueConverter.divideBy10],
[17, "schedule_monday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(1)],
[18, "schedule_tuesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(2)],
[19, "schedule_wednesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(3)],
[20, "schedule_thursday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(4)],
[21, "schedule_friday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(5)],
[22, "schedule_saturday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(6)],
[23, "schedule_sunday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(7)],
[101, "local_temperature_calibration", tuya.valueConverter.divideBy10],
[108, "position", tuya.valueConverter.divideBy10],
[
111,
"display_brightness",
tuya.valueConverterBasic.lookup({
high: tuya.enum(0),
medium: tuya.enum(1),
low: tuya.enum(2),
}),
],
[
113,
"screen_orientation",
tuya.valueConverterBasic.lookup({
up: tuya.enum(0),
right: tuya.enum(1),
down: tuya.enum(2),
left: tuya.enum(3),
}),
],
[
114,
"mode",
tuya.valueConverterBasic.lookup({
comfort: tuya.enum(0),
eco: tuya.enum(1),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_qyr2m29i", "_TZE204_ltwbm23f", "_TZE284_ltwbm23f"]),
model: "TRV602Z",
vendor: "Tuya",
description: "Thermostatic radiator valve.",
whiteLabel: [tuya.whitelabel("Moes", "TRV801Z", "Thermostatic radiator valve", ["_TZE204_qyr2m29i", "_TZE284_ltwbm23f"])],
// _TZE204_qyr2m29i requires 2000
// https://github.com/Koenkk/zigbee2mqtt/issues/30054
extend: [tuyaBase({ dp: true, timeStart: "2000" })],
exposes: [
e.battery(),
e.child_lock(),
e.max_temperature(),
e.min_temperature(),
e.position(),
e.window_detection(),
e
.numeric("boost_timeset_countdown", ea.STATE_SET)
.withUnit("m")
.withDescription("Setting " +
"minimum 0 - maximum 120 minutes boost time. The boost function is activated. The remaining " +
"time for the function will be counted down in minutes ( 120 to 0 ).")
.withValueMin(0)
.withValueMax(120),
e.binary("frost_protection", ea.STATE_SET, "ON", "OFF").withDescription("Antifreeze function"),
e.binary("window", ea.STATE, "OPEN", "CLOSE").withDescription("Window status closed or open "),
e
.climate()
.withLocalTemperature(ea.STATE)
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withLocalTemperatureCalibration(-10, 10, 0.1, ea.STATE_SET)
.withPreset(["off", "antifrost", "eco", "comfort", "auto", "on"])
.withRunningState(["idle", "heat"], ea.STATE)
.withSystemMode(["auto", "heat", "off"], ea.STATE),
...tuya.exposes.scheduleAllDays(ea.STATE_SET, "HH:MM/C HH:MM/C HH:MM/C HH:MM/C HH:MM/C HH:MM/C"),
e.comfort_temperature().withValueMin(5).withValueMax(30).withDescription("Comfort mode temperature"),
e.eco_temperature().withValueMin(5).withValueMax(30).withDescription("Eco mode temperature"),
e.holiday_temperature().withValueMin(5).withValueMax(30).withDescription("Antifreeze mode temperature"),
e.enum("display_brightness", ea.STATE_SET, ["high", "medium", "low"]).withDescription("Display brightness"),
e.enum("screen_orientation", ea.STATE_SET, ["up", "down"]).withDescription("Screen orientation"),
e
.enum("hysteresis", ea.STATE_SET, ["comfort", "eco"])
.withDescription("Hysteresis - comfort > switches off/on exactly at reached " +
"temperature with valve smooth from 0 to 100%, eco > 0.5 degrees above or below, valve either 0 or 100%"),
e.enum("motor_thrust", ea.STATE_SET, ["strong", "middle", "weak"]),
],
meta: {
tuyaDatapoints: [
[
2,
null,
tuya.valueConverter.thermostatSystemModeAndPresetMap({
fromMap: {
0: { deviceMode: "off", systemMode: "off", preset: "off" },
1: { deviceMode: "antifrost", systemMode: "auto", preset: "antifrost" },
2: { deviceMode: "eco", systemMode: "auto", preset: "eco" },
3: { deviceMode: "comfort", systemMode: "auto", preset: "comfort" },
4: { deviceMode: "auto", systemMode: "auto", preset: "auto" },
5: { deviceMode: "on", systemMode: "heat", preset: "on" },
},
}),
],
[
2,
"preset",
tuya.valueConverter.thermostatSystemModeAndPresetMap({
toMap: {
off: new tuya.Enum(0),
antifrost: new tuya.Enum(1),
eco: new tuya.Enum(2),
comfort: new tuya.Enum(3),
auto: new tuya.Enum(4),
on: new tuya.Enum(5),
},
}),
],
[
2,
"system_mode",
tuya.valueConverter.thermostatSystemModeAndPresetMap({
toMap: {
off: new tuya.Enum(0),
auto: new tuya.Enum(4),
heat: new tuya.Enum(5),
},
}),
],
[3, "running_state", tuya.valueConverterBasic.lookup({ heat: 1, idle: 0 })],
[4, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[5, "local_temperature", tuya.valueConverter.divideBy10],
[6, "battery", tuya.valueConverter.raw],
[
7,
"child_lock",
tuya.valueConverterBasic.lookup({
LOCK: true,
UNLOCK: false,
}),
],
[9, "max_temperature", tuya.valueConverter.divideBy10],
[10, "min_temperature", tuya.valueConverter.divideBy10],
[
14,
"window_detection",
tuya.valueConverterBasic.lookup({
ON: true,
OFF: false,
}),
],
[
15,
"window",
tuya.valueConverterBasic.lookup({
CLOSE: tuya.enum(0),
OPEN: tuya.enum(1),
}),
],
[47, "local_temperature_calibration", tuya.valueConverter.divideBy10],
[102, "schedule_monday", tuya.valueConverter.thermostatScheduleDayMultiDP_TRV602Z_WithDayNumber(1)],
[103, "schedule_tuesday", tuya.valueConverter.thermostatScheduleDayMultiDP_TRV602Z_WithDayNumber(2)],
[104, "schedule_wednesday", tuya.valueConverter.thermostatScheduleDayMultiDP_TRV602Z_WithDayNumber(3)],
[105, "schedule_thursday", tuya.valueConverter.thermostatScheduleDayMultiDP_TRV602Z_WithDayNumber(4)],
[106, "schedule_friday", tuya.valueConverter.thermostatScheduleDayMultiDP_TRV602Z_WithDayNumber(5)],
[107, "schedule_saturday", tuya.valueConverter.thermostatScheduleDayMultiDP_TRV602Z_WithDayNumber(6)],
[108, "schedule_sunday", tuya.valueConverter.thermostatScheduleDayMultiDP_TRV602Z_WithDayNumber(7)],
[
110,
"motor_thrust",
tuya.valueConverterBasic.lookup({
strong: tuya.enum(0),
middle: tuya.enum(1),
weak: tuya.enum(2),
}),
],
[
111,
"display_brightness",
tuya.valueConverterBasic.lookup({
high: tuya.enum(0),
medium: tuya.enum(1),
low: tuya.enum(2),
}),
],
[
113,
"screen_orientation",
tuya.valueConverterBasic.lookup({
up: tuya.enum(0),
down: tuya.enum(1),
}),
],
[114, "position", tuya.valueConverter.divideBy10],
[118, "boost_timeset_countdown", tuya.valueConverter.raw],
[119, "comfort_temperature", tuya.valueConverter.divideBy10],
[120, "eco_temperature", tuya.valueConverter.divideBy10],
[121, "holiday_temperature", tuya.valueConverter.divideBy10],
[122, "frost_protection", tuya.valueConverter.onOff],
[
127,
"hysteresis",
tuya.valueConverterBasic.lookup({
comfort: tuya.enum(0),
eco: tuya.enum(1),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_xalsoe3m"]),
model: "ZHT-002",
vendor: "Moes",
description: "ZHT series thermostat",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "2000" })],
exposes: [
e.binary("state", ea.STATE_SET, "ON", "OFF").withDescription("Turn the thermostat ON/OFF"),
e.child_lock(),
e.binary("system_mode", ea.STATE_SET, "Auto", "Manual").withDescription("Manual = Manual or Schedule = Auto"),
e.eco_mode(),
e.temperature_sensor_select(["IN", "AL", "OU"]).withLabel("Sensor").withDescription("Choose which sensor to use. Default: AL"),
e.enum("valve_state", ea.STATE, ["close", "open"]).withDescription("State of the valve"),
e.min_temperature().withValueMin(0).withValueMax(20),
e.max_temperature().withValueMin(20).withValueMax(50),
e
.climate()
.withLocalTemperature(ea.STATE)
.withSetpoint("current_heating_setpoint", 0, 50, 1, ea.STATE_SET)
.withLocalTemperatureCalibration(-9, 9, 1, ea.STATE_SET),
e
.numeric("max_temperature_limit", ea.STATE_SET)
.withDescription("Max temperature limit")
.withValueMin(25)
.withValueMax(70)
.withValueStep(1),
e
.numeric("deadzone_temperature", ea.STATE_SET)
.withValueMax(5)
.withValueMin(1)
.withValueStep(1)
.withPreset("default", 1, "Default value")
.withDescription("The difference between local temp and set temp that triggers heating"),
(() => {
const groups = [
{ name: "W", start: 0 },
{ name: "S", start: 16 },
{ name: "U", start: 32 },
];
let composite = e
.composite("programming_mode", "programming_mode", ea.STATE_SET)
.withDescription("Schedule: W=Weekdays, S=Saturday, U=Sunday. 4 slots each with hour(h), minute(m), temperature(t).");
for (const group of groups) {
for (let slot = 0; slot < 4; slot++) {
const offset = group.start + slot * 4;
const label = `${group.name}${slot + 1}`;
composite = composite.withFeature(e.numeric(offset.toString(), ea.STATE_SET).withValueMin(0).withValueMax(23).withDescription(`${label}h`));
composite = composite.withFeature(e
.numeric((offset + 1).toString(), ea.STATE_SET)
.withValueMin(0)
.withValueMax(59)
.withDescription(`${label}m`));
composite = composite.withFeature(e
.numeric((offset + 3).toString(), ea.STATE_SET)
.withValueMin(5)
.withValueMax(45)
.withUnit("°C")
.withDescription(`${label}t`));
}
}
return composite;
})(),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[
2,
"system_mode",
tuya.valueConverterBasic.lookup({
auto: tuya.enum(0),
manual: tuya.enum(1),
}),
],
[16, "local_temperature", tuya.valueConverter.divideBy10],
[18, "min_temperature", tuya.valueConverter.raw],
[19, "local_temperature_calibration", tuya.valueConverter.localTemperatureCalibration],
[
32,
"sensor",
tuya.valueConverterBasic.lookup({
IN: tuya.enum(0),
AL: tuya.enum(2),
OU: tuya.enum(1),
}),
],
[34, "max_temperature", tuya.valueConverter.raw],
[39, "child_lock", tuya.valueConverter.lockUnlock],
[40, "eco_mode", tuya.valueConverter.onOff],
[
47,
"valve_state",
tuya.valueConverterBasic.lookup({
closed: tuya.enum(0),
open: tuya.enum(1),
}),
],
[50, "current_heating_setpoint", tuya.valueConverter.raw],
[68, "programming_mode", zht002ProgrammingModeConverter],
[101, "max_temperature_limit", tuya.valueConverter.raw],
[102, "deadzone_temperature", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_ymldrmzx"]),
model: "TRV603-WZ",
vendor: "Tuya",
description: "Thermostatic radiator valve.",
extend: [tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true, timeStart: "1970" })],
exposes: [
e.battery(),
e.child_lock(),
e.window_detection_bool(),
e.window_open(),
e
.climate()
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE) // local_temperature
.withLocalTemperatureCalibration(-7, 7, 0.5, ea.STATE_SET) // local_temperature_calibration
.withSystemMode(["auto", "off"], ea.STATE_SET) // system_mode
.withPreset(["schedule", "manual", "holiday"]), //preset
e.comfort_temperature().withValueMin(15).withValueMax(35),
e.eco_temperature().withValueMin(5).withValueMax(22),
e.holiday_temperature().withValueMin(5).withValueMax(35),
e.text("holiday_time", ea.STATE_SET).withDescription("Holiday start and end time in format YYYY/MM/DD HH:MM | YYYY/MM/DD HH:MM"),
e.binary("boost_heating", ea.STATE_SET, "ON", "OFF"),
e.numeric("boost_time", ea.STATE_SET).withUnit("min").withDescription("Countdown in minutes").withValueMin(0).withValueMax(30),
e
.binary("frost_protection", ea.STATE_SET, "ON", "OFF")
.withDescription("When the room temperature is lower than 5 °C, the valve opens; when the temperature rises to 8 °C, the valve closes."),
...["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"].map((day) => e.text(`schedule_${day}`, ea.STATE_SET).withDescription("Schedule in 8 segments format: HH:MM/TT.T HH:MM/TT.T ...")),
e.enum("screen_orientation", ea.STATE_SET, ["up", "right", "down", "left"]).withDescription("Screen orientation"),
e.numeric("fault_code", ea.STATE).withDescription("Raw fault code"),
],
meta: {
tuyaDatapoints: [
[
2,
"preset",
tuya.valueConverterBasic.lookup({
schedule: tuya.enum(0),
holiday: tuya.enum(1), // only possible to set during holiday_time
manual: tuya.enum(2),
}),
],
// when system_mode is 'off', current_heating_setpoint is set to -5000... hence limits
[4, "current_heating_setpoint", tuya.valueConverterBasic.divideByWithLimits(10, 0, 35)],
[5, "local_temperature", tuya.valueConverter.divideBy10],
[6, "battery", tuya.valueConverter.raw],
[
7,
"child_lock",
tuya.valueConverterBasic.lookup({
LOCK: true,
UNLOCK: false,
}),
],
[14, "window_detection", tuya.valueConverter.raw],
[15, "window_open", tuya.valueConverter.trueFalseEnum1],
[21, "holiday_temperature", tuya.valueConverter.divideBy10],
[36, "frost_protection", tuya.valueConverter.onOff],
[39, "anti_scale", tuya.valueConverter.raw],
[47, "local_temperature_calibration", tuya.valueConverter.divideBy10], // duplicate/legacy
[49, "valve_status", tuya.valueConverter.raw],
[101, "boost_heating", tuya.valueConverter.onOff],
[102, "boost_time", tuya.valueConverter.countdown],
[103, "schedule_monday", tuya.valueConverter.thermostatScheduleDayMultiDP_TRV603WZ],
[104, "schedule_tuesday", tuya.valueConverter.thermostatScheduleDayMultiDP_TRV603WZ],
[105, "schedule_wednesday", tuya.valueConverter.thermostatScheduleDayMultiDP_TRV603WZ],
[106, "schedule_thursday", tuya.valueConverter.thermostatScheduleDayMultiDP_TRV603WZ],
[107, "schedule_friday", tuya.valueConverter.thermostatScheduleDayMultiDP_TRV603WZ],
[108, "schedule_saturday", tuya.valueConverter.thermostatScheduleDayMultiDP_TRV603WZ],
[109, "schedule_sunday", tuya.valueConverter.thermostatScheduleDayMultiDP_TRV603WZ],
[110, "holiday_time", tuya.valueConverter.thermostatHolidayStartStopUnixTS],
[
111,
"screen_orientation",
tuya.valueConverterBasic.lookup({
up: tuya.enum(0),
right: tuya.enum(1),
down: tuya.enum(2),
left: tuya.enum(3),
}),
],
[112, "antifrost_temperature", tuya.valueConverter.divideBy10],
[
113,
"system_mode",
tuya.valueConverterBasic.lookup({
auto: false,
off: true,
}),
],
[114, "local_temperature_calibration", tuya.valueConverter.divideBy10],
[115, "programming_mode", tuya.valueConverter.raw],
[116, "eco_temperature", tuya.valueConverter.divideBy10],
[117, "comfort_temperature", tuya.valueConverter.divideBy10],
[118, "fault_code", tuya.valueConverter.raw],
],
},
},
{
zigbeeModel: ["TS0121"],
model: "TS0121_plug",
description: "10A UK or 16A EU smart plug",
whiteLabel: [
{ vendor: "BlitzWolf", model: "BW-SHP13" },
{ vendor: "Connecte", model: "4500990" },
{ vendor: "Connecte", model: "4500991" },
{ vendor: "Connecte", model: "4500992" },
{ vendor: "Connecte", model: "4500993" },
],
vendor: "Tuya",
fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, tuya.fz.power_outage_memory, tuya.fz.indicator_mode],
toZigbee: [tz.on_off, tuya.tz.power_on_behavior_1, tuya.tz.backlight_indicator_mode_1],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff", "haElectricalMeasurement", "seMetering"]);
endpoint.saveClusterAttributeKeyValue("seMetering", {
divisor: 100,
multiplier: 1,
});
endpoint.saveClusterAttributeKeyValue("haElectricalMeasurement", {
acVoltageMultiplier: 1,
acVoltageDivisor: 1,
acCurrentMultiplier: 1,
acCurrentDivisor: 1000,
acPowerMultiplier: 1,
acPowerDivisor: 1,
});
try {
await reporting.currentSummDelivered(endpoint);
await reporting.rmsVoltage(endpoint, { change: 5 });
await reporting.rmsCurrent(endpoint, { change: 50 });
await reporting.activePower(endpoint, { change: 10 });
}
catch {
/* fails for some https://github.com/Koenkk/zigbee2mqtt/issues/11179
and https://github.com/Koenkk/zigbee2mqtt/issues/16864 */
}
await endpoint.read("genOnOff", ["onOff", "moesStartUpOnOff", "tuyaBacklightMode"]);
},
// This device doesn't support reporting correctly.
// https://github.com/Koenkk/zigbee-herdsman-converters/pull/1270
exposes: [
e.switch(),
e.power(),
e.current(),
e.voltage(),
e.energy(),
e.enum("power_outage_memory", ea.ALL, ["on", "off", "restore"]).withDescription("Recover state after power outage"),
e.enum("indicator_mode", ea.ALL, ["off", "off/on", "on/off"]).withDescription("LED indicator mode"),
],
extend: [tuya.modernExtend.electricityMeasurementPoll(), tuya.modernExtend.tuyaBase()],
},
{
fingerprint: tuya.fingerprint("TS0111", ["_TYZB01_ymcdbl3u"]),
model: "TS0111_valve",
vendor: "Tuya",
whiteLabel: [{ vendor: "Tuya", model: "SM-AW713Z" }],
description: "Smart water/gas valve",
extend: [tuya.modernExtend.tuyaBase(), tuya.modernExtend.tuyaOnOff({ indicatorMode: true })],
},
{
// Note: below you will find the TS011F_plug_2 and TS011F_plug_3. These are identified via a fingerprint and
// thus preferred above the TS011F_plug_1 if the fingerprint matches
zigbeeModel: ["TS011F"],
// These report an applicationVersion caught by the version-only fingerprints of TS011F_plug_3
// (metering by polling), but they do support attribute reporting. A modelID + manufacturerName
// fingerprint outranks those `priority: -1` entries and keeps them on this definition.
fingerprint: tuya.fingerprint("TS011F", ["_TZ3008_tary5dvv", "_TZ3210_iooniers", "_TZ3008_iooniers", "_TZ3008_xvfd3nkp", "_TZ3008_qziabvzj"]),
model: "TS011F_plug_1",
description: "Smart plug (with power monitoring)",
vendor: "Tuya",
version: "0.0.1",
whiteLabel: [
{ vendor: "LELLKI", model: "TS011F_plug" },
{ vendor: "BlitzWolf", model: "BW-SHP15" },
{ vendor: "BlitzWolf", model: "BW-SHP13" },
{ vendor: "MatSee Plus", model: "PJ-ZSW01" },
{ vendor: "MODEMIX", model: "MOD037" },
{ vendor: "MODEMIX", model: "MOD048" },
{ vendor: "Coswall", model: "CS-AJ-DE2U-ZG-11" },
{ vendor: "Aubess", model: "TS011F_plug_1" },
{ vendor: "NEO Coolcam", model: "PLUG-001SPB2" },
{ vendor: "Haozee", model: "HT-SP-ZB-01" },
tuya.whitelabel("BSEED", "TS011F_plug_1_2", "Wall-mounted electrical EU/FR/UK socket with power monitoring", [
"_TZ3000_4ux0ondb",
"_TZ3000_b28wrpvx",
"_TZ3210_4ux0ondb",
"_TZ3000_2uollq9d",
"_TZ3210_2uollq9d",
"_TZ3210_zifx0xoj",
"_TZ3000_ko6v90pg",
]),
tuya.whitelabel("BSEED", "_TZ3210_5ct6e7ye", "Wall-mounted electrical EU/FR/UK socket with power monitoring and USB", [
"_TZ3210_5ct6e7ye",
]),
tuya.whitelabel("Nous", "A1Z", "Smart plug (with power monitoring)", ["_TZ3000_2putqrmw", "_TZ3210_2putqrmw", "_TZ3000_ksw8qtmt"]),
tuya.whitelabel("Moes", "Moes_plug", "Smart plug (with power monitoring)", ["_TZ3000_yujkchbz"]),
tuya.whitelabel("Moes", "ZK-EU", "Smart wallsocket (with power monitoring)", ["_TZ3000_ss98ec5d"]),
tuya.whitelabel("Elivco", "LSPA9", "Smart plug (with power monitoring)", ["_TZ3000_okaz9tjs"]),
tuya.whitelabel("PSMART", "T440", "Smart wallsocket (with power monitoring)", ["_TZ3000_y4ona9me"]),
tuya.whitelabel("Nous", "A6Z", "Outdoor smart socket", ["_TZ3000_266azbg3"]),
tuya.whitelabel("Nedis", "ZBPO130FWT", "Outdoor smart plug (with power monitoring)", ["_TZ3000_3ias4w4o"]),
tuya.whitelabel("Nous", "A9Z", "Smart Zigbee Socket", ["_TZ3210_ddigca5n"]),
tuya.whitelabel("Girier", "JR-ZPM01", "Smart Plug", ["_TZ3000_ww6drja5"]),
tuya.whitelabel("Nous", "A7Z", "Smart Zigbee Socket", ["_TZ3210_rwmitwj4", "_TZ3008_reatplte", "_TZ3008_1a8m8wd6"]),
tuya.whitelabel("Zbeacon", "TS011F_plug_1_1", "Smart plug (with power monitoring)", ["Zbeacon"]),
tuya.whitelabel("AOYAN", "TS011F_AOYAN", "Smart plug with power monitoring", ["AOYAN "]),
tuya.whitelabel("NEO", "NAS-WR01B", "Smart plug (with electrical measurements)", ["_TZ3000_gjnozsaz"]),
tuya.whitelabel("GreenSun", "HSC-ZW-EU", "Outdoor Smart Plug (with power monitoring)", ["_TZ3000_cicwjqth"]),
tuya.whitelabel("Nous", "A10Z", "Smart Zigbee Socket", ["_TZ3210_jlf1nepw"]),
tuya.whitelabel("MatSee Plus", "PJ-MINI-ZSW01", "Smart Socket Switch Module (with power monitoring)", [
"_TZ3000_cjrngdr3",
"_TZ3210_cjrngdr3",
]),
tuya.whitelabel("BlitzWolf", "BW-SHP13", "Smart plug (with power monitoring)", ["_TZ3000_amdymr7l"]),
tuya.whitelabel("NEO Coolcam", "PLUG-001SPB2", "Smart plug (with power monitoring)", ["_TZ3000_zloso4jk"]),
tuya.whitelabel("Haozee", "HT-SP-ZB-01", "Smart plug (with power monitoring)", ["_TZ3210_w0qqde0g"]),
tuya.whitelabel("EIGHTREE", "EIGHTREE Socket", "Smart plug (with power monitoring)", [
"_TZ3008_tary5dvv",
"_TZ3210_iooniers",
"_TZ3008_iooniers",
"_TZ3008_xvfd3nkp",
"_TZ3008_qziabvzj",
]),
],
ota: true,
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
electricalMeasurements: true,
electricalMeasurementsFzConverter: tuya.fz.TS011F_electrical_measurement,
// Conditional features
powerOutageMemory: (manufacturerName) => manufacturerName !== "_TZ3000_cicwjqth",
indicatorMode: (manufacturerName) => manufacturerName !== "_TZ3000_ww6drja5",
childLock: (manufacturerName) => manufacturerName !== "_TZ3000_cicwjqth",
onOffCountdown: (manufacturerName) => manufacturerName !== "_TZ3000_cicwjqth",
switchTypeButton: (manufacturerName) => !["_TZ3000_w0qqde0g", "_TZ3000_typdpbpg"].includes(manufacturerName),
}),
m.identify(),
],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff", "haElectricalMeasurement", "seMetering"]);
if (!["_TZ3000_okaz9tjs"].includes(device.manufacturerName)) {
// https://github.com/Koenkk/zigbee2mqtt/issues/29034
await reporting.currentSummDelivered(endpoint);
await reporting.rmsVoltage(endpoint, { change: 5 });
await reporting.rmsCurrent(endpoint, { change: 50 });
}
if (!["_TZ3000_0zfrhq4i", "_TZ3000_okaz9tjs", "_TZ3000_typdpbpg", "_TZ3000_ww6drja5", "Zbeacon", "_TZ3000_cicwjqth"].includes(device.manufacturerName)) {
// Gives INVALID_DATA_TYPE error for _TZ3000_0zfrhq4i (as well as a few others in issue 20028)
// https://github.com/Koenkk/zigbee2mqtt/discussions/19680#discussioncomment-7667035
// Don't do this for `_TZ3000_gjnozsaz` (was previously in the list, but removed after:
// https://github.com/Koenkk/zigbee2mqtt/issues/28729#issuecomment-3370261334
await reporting.activePower(endpoint, { change: 10 });
}
// Unlike the plugs that made #12817 necessary, these report their current and metering
// scale correctly (e.g. seMetering.divisor is 10, not the hardcoded 100 below), so read
// it from the device instead. Limited to these manufacturerNames to keep the hardcoded
// path (and the #12817 workaround) unchanged for every other TS011F.
const readMeteringScale = ["_TZ3008_tary5dvv", "_TZ3210_iooniers", "_TZ3008_iooniers", "_TZ3008_xvfd3nkp", "_TZ3008_qziabvzj"].includes(device.manufacturerName);
const acCurrentDivisor = ["_TZ3000_typdpbpg"].includes(device.manufacturerName) ? 2000 : 1000;
endpoint.saveClusterAttributeKeyValue("haElectricalMeasurement", {
acCurrentDivisor,
acCurrentMultiplier: 1,
});
// Some TS011F plugs (e.g. _TZ3008_reatplte) report voltage/power/current scaled by a non-default
// factor and expose the correct multipliers/divisors via the haElectricalMeasurement cluster.
// Try to read each attribute individually because some devices return UNSUPPORTED_ATTRIBUTE for a
// subset, and reading them in bulk would cause one failure to abort the rest. Successful reads are
// cached by zigbee-herdsman and override the hardcoded values saved above.
for (const attr of [
"acVoltageMultiplier",
"acVoltageDivisor",
"acPowerMultiplier",
"acPowerDivisor",
// "acCurrentMultiplier",
// "acCurrentDivisor",
// Some devices report wrong divisor. Zbeacon TS011F v1.0.5 gives 1, but it's actually 1000
]) {
try {
await endpoint.read("haElectricalMeasurement", [attr]);
}
catch {
/* Not all Tuya plugs expose all multiplier/divisor attributes; fall back to defaults. */
}
}
if (readMeteringScale) {
for (const attr of ["acCurrentMultiplier", "acCurrentDivisor"]) {
try {
await endpoint.read("haElectricalMeasurement", [attr]);
}
catch {
/* Fall back to the hardcoded value saved above. */
}
}
}
endpoint.saveClusterAttributeKeyValue("seMetering", {
divisor: 100,
multiplier: 1,
});
utils.attachOutputCluster(device, "genOta");
device.save();
},
},
{
fingerprint: tuya.fingerprint("TS011F", [
"_TZ3000_hyfvrar3",
"_TZ3000_v1pdxuqq",
"_TZ3000_8a833yls",
"_TZ3000_bfn1w0mm",
"_TZ3000_nzkqcvvs",
"_TZ3000_rtcrrvia",
"_TZ3000_ysiog9xi",
"_TZ3000_o1jzcxou",
"_TZ3210_nhqka112", // https://github.com/Koenkk/zigbee2mqtt/issues/30889
"_TZ3000_uyrhiafs",
]),
model: "TS011F_plug_2",
description: "Smart plug (without power monitoring)",
vendor: "Tuya",
whiteLabel: [
tuya.whitelabel("BSEED", "_TZ3000_o1jzcxou", "Wall-mounted electrical EU/FR/UK socket", ["_TZ3000_o1jzcxou"]),
tuya.whitelabel("BSEED", "S-PC86ZEUSK1B", "Wall-mounted electrical EU socket", ["_TZ3000_uyrhiafs"]),
],
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
powerOutageMemory: true,
indicatorMode: true,
childLock: true,
onOffCountdown: true,
}),
],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff"]);
},
},
{
zigbeeModel: ["SM0212"],
model: "SM0212",
vendor: "Tuya",
description: "Gas sensor",
extend: [m.iasZoneAlarm({ zoneType: "gas", zoneAttributes: ["alarm_1", "alarm_2", "tamper", "battery_low"] })],
},
{
fingerprint: [
{ modelID: "TS011F", applicationVersion: 192, manufacturerName: "_TZ3000_2uollq9d", priority: -1 },
{ modelID: "TS011F", applicationVersion: 160, priority: -1 },
{ modelID: "TS011F", applicationVersion: 100, priority: -1 },
{ modelID: "TS011F", applicationVersion: 69, priority: -1 },
{ modelID: "TS011F", applicationVersion: 68, priority: -1 },
{ modelID: "TS011F", applicationVersion: 67, manufacturerName: "_TZ3000_9ni6xxld", priority: -1 },
{ modelID: "TS011F", applicationVersion: 65, priority: -1 },
{ modelID: "TS011F", applicationVersion: 64, priority: -1 },
{ modelID: "TS011F", softwareBuildID: "1.0.5\u0000", manufacturerName: "_TZ3000_cehuw1lw", priority: -1 },
],
model: "TS011F_plug_3",
description: "Smart plug (with power monitoring by polling)",
vendor: "Tuya",
whiteLabel: [
{ vendor: "VIKEFON", model: "TS011F" },
{ vendor: "BlitzWolf", model: "BW-SHP15" },
{ vendor: "AVATTO", model: "MIUCOT10Z" },
{ vendor: "NEO", model: "PLUG-001SPB2" },
tuya.whitelabel("BSEED", "TS011F_plug_3_1", "Wall-mounted electrical EU/FR/UK socket with power monitoring", ["_TZ3000_2uollq9d"]),
tuya.whitelabel("Nous", "A6Z_polling", "Outdoor smart socket", ["_TZ3000_266azbg3"]),
],
ota: true,
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
electricalMeasurements: true,
powerOutageMemory: true,
onOffCountdown: true,
indicatorMode: true,
childLock: true,
}),
tuya.modernExtend.electricityMeasurementPoll({
metering: (device) => [100, 160, 192].includes(device.applicationVersion) || ["1.0.5\u0000"].includes(device.softwareBuildID), // polling for energy
}),
],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
const endpoint = device.getEndpoint(1);
endpoint.saveClusterAttributeKeyValue("haElectricalMeasurement", {
acCurrentDivisor: 1000,
acCurrentMultiplier: 1,
});
endpoint.saveClusterAttributeKeyValue("seMetering", {
divisor: 100,
multiplier: 1,
});
utils.attachOutputCluster(device, "genOta");
device.save();
},
},
{
fingerprint: tuya.fingerprint("TS011F", ["_TZ3000_in5s3wn1", "_TZ3000_wbloefbf"]),
model: "TS011F_switch_5_gang",
description: "2 gang 2 usb 1 wall ac outlet",
whiteLabel: [{ vendor: "Milfra", model: "M11Z" }],
vendor: "Tuya",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
powerOutageMemory: true,
childLock: true,
endpoints: ["l1", "l2", "l3", "l4", "l5"],
}),
],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3, l4: 4, l5: 5 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(4), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(5), coordinatorEndpoint, ["genOnOff"]);
},
},
{
fingerprint: tuya.fingerprint("TS011F", ["_TZ3000_dlug3kbc", "_TZ3000_9tg32trw"]),
model: "TS011F_3_gang",
description: "3 gang wall ac outlet",
vendor: "Tuya",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
powerOutageMemory: true,
childLock: true,
onOffCountdown: true,
endpoints: ["l1", "l2", "l3"],
}),
],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
for (const ep of [1, 2, 3]) {
await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ["genOnOff"]);
}
},
whiteLabel: [tuya.whitelabel("Mifra", "KS-604S", "2 gang wall outlet, with USB", ["_TZ3000_9tg32trw"])],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_0zaf1cr8", "_TZE204_ntcy3xu1", "_TZE284_0zaf1cr8"]),
model: "TS0601_smoke_1",
vendor: "Tuya",
description: "Smoke sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [e.smoke(), e.tamper(), e.battery_low()],
meta: {
tuyaDatapoints: [
[1, "smoke", tuya.valueConverter.trueFalse0],
[4, "tamper", tuya.valueConverter.raw],
[14, "battery_low", tuya.valueConverter.trueFalse0],
],
},
whiteLabel: [tuya.whitelabel("Nous", "E8", "Smoke sensor", ["_TZE200_0zaf1cr8", "_TZE284_0zaf1cr8"])],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_ntcy3xu1"]),
model: "TS0601_smoke_6",
vendor: "Tuya",
description: "Smoke sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [e.smoke(), e.tamper(), tuya.exposes.batteryState()],
meta: {
tuyaDatapoints: [
[1, "smoke", tuya.valueConverter.trueFalse0],
[4, "tamper", tuya.valueConverter.raw],
[14, "battery_state", tuya.valueConverter.batteryState],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_ai4rqhky", "_TZE284_ai4rqhky"]),
model: "HS2SA-1",
vendor: "Heiman",
description: "Photoelectric Smoke Alarm",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.smoke(),
e.battery(),
tuya.exposes.batteryState(),
e.binary("silence", ea.STATE_SET, "ON", "OFF"),
e.enum("self_test", ea.STATE, ["checking", "check_success", "check_failure"]),
],
meta: {
tuyaDatapoints: [
[1, "smoke", tuya.valueConverter.trueFalse0],
[9, "self_test", tuya.valueConverterBasic.lookup({ checking: 0, check_success: 1, check_failure: 2 })],
[14, "battery_state", tuya.valueConverter.batteryState],
[15, "battery", tuya.valueConverter.raw],
[16, "silence", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE200_m9skfctm",
"_TZE200_rccxox8p",
"_TZE284_rccxox8p",
"_TZE2841000000_rccxox8p",
"_TZE284_qvzsq3s2",
]),
model: "PA-44Z",
vendor: "Tuya",
description: "Photoelectric smoke detector",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.smoke(),
e.battery(),
tuya.exposes.silence(),
e.test(),
e.numeric("smoke_concentration", ea.STATE).withUnit("ppm").withDescription("Parts per million of smoke detected"),
e.binary("device_fault", ea.STATE, true, false).withDescription("Indicates a fault with the device"),
],
meta: {
tuyaDatapoints: [
[1, "smoke", tuya.valueConverter.trueFalse0],
[2, "smoke_concentration", tuya.valueConverter.divideBy10],
[11, "device_fault", tuya.valueConverter.raw],
[15, "battery", tuya.valueConverter.raw],
[16, "silence", tuya.valueConverter.raw],
[101, "test", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_ux5v4dbd"]),
vendor: "Tuya",
model: "TS0601_smoke_3",
description: "Photoelectric smoke detector",
whiteLabel: [{ vendor: "KnockautX", model: "SMOAL024" }],
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [e.smoke(), tuya.exposes.batteryState()],
meta: {
tuyaDatapoints: [
/**
* According to the Vendor "KnockautX / Brelag AG" DP 16 "muffling"
* is supported as well. But it was not possible to verify this using
* SMOLA024 devices - therefore it is not included in the device definition.
*
* Data Transfer Type: Send and Report
* Data Type: Bool
* muffling: 16,
*/
[1, "smoke", tuya.valueConverter.trueFalse0],
[14, "battery_state", tuya.valueConverter.batteryState],
],
},
},
{
zigbeeModel: ["5p1vj8r"],
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_t5p1vj8r", "_TZE200_uebojraa", "_TZE200_vzekyi4c", "_TZE200_yh7aoahi", "_TZE200_dq1mfjug"]),
model: "TS0601_smoke_4",
vendor: "Tuya",
description: "Smoke sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [e.smoke(), e.battery(), tuya.exposes.batteryState()],
meta: {
tuyaDatapoints: [
[1, "smoke", tuya.valueConverter.trueFalse0],
[14, "battery_state", tuya.valueConverter.batteryState],
[15, "battery", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_ytibqbra"]),
model: "TS0601_smoke_5",
vendor: "Tuya",
description: "Smoke sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.smoke(),
e.tamper(),
e.battery(),
tuya.exposes.faultAlarm(),
tuya.exposes.silence(),
e.binary("alarm", ea.STATE_SET, "ON", "OFF").withDescription("Enable the alarm"),
],
meta: {
tuyaDatapoints: [
[1, "smoke", tuya.valueConverter.trueFalse0],
[4, "tamper", tuya.valueConverter.raw],
[11, "fault_alarm", tuya.valueConverter.trueFalse1],
[15, "battery", tuya.valueConverter.raw],
[16, "silence", tuya.valueConverter.raw],
[17, "alarm", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_o9ofysmo", "_TZE284_xc3vwx5a"]),
model: "ZS-301Z",
vendor: "Arteco",
description: "Soil moisture sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e
.enum("battery_state", ea.STATE, ["low", "middle", "high"])
.withDescription("low: 1-25%, middle: 26-50%, high: 51-100%")
.withCategory("diagnostic"),
e.temperature(),
e.soil_moisture(),
e.humidity(),
e.illuminance(),
e
.numeric("humidity_calibration", ea.STATE_SET)
.withUnit("%")
.withDescription("Adjust humidity")
.withValueMin(-30)
.withValueMax(30)
.withValueStep(1),
e
.numeric("report_interval", ea.STATE_SET)
.withUnit("s")
.withDescription("Report interval")
.withValueMin(30)
.withValueMax(1200)
.withValueStep(30),
],
meta: {
tuyaDatapoints: [
[3, "soil_moisture", tuya.valueConverter.raw],
[5, "temperature", tuya.valueConverter.divideBy10],
[14, "battery_state", tuya.valueConverterBasic.lookup({ low: tuya.enum(0), middle: tuya.enum(1), high: tuya.enum(2) })],
[101, "humidity", tuya.valueConverter.raw],
[102, "illuminance", tuya.valueConverter.raw],
[103, "humidity_calibration", tuya.valueConverter.raw],
[104, "report_interval", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_e2bedvo9", "_TZE200_dnz6yvl2", "_TZE284_e2bedvo9"]),
model: "ZSS-QY-SSD-A-EN",
vendor: "Tuya",
description: "Smart smoke alarm",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.smoke(),
tuya.exposes.faultAlarm(),
tuya.exposes.batteryState(),
e.battery(),
tuya.exposes.silence(),
tuya.exposes.selfTest(),
e.numeric("smoke_concentration", ea.STATE).withUnit("ppm").withDescription("Parts per million of smoke detected"),
],
meta: {
tuyaDatapoints: [
[1, "smoke", tuya.valueConverter.trueFalse0],
[2, "smoke_concentration", tuya.valueConverter.divideBy10],
[11, "fault_alarm", tuya.valueConverter.trueFalse1],
[14, "battery_state", tuya.valueConverter.batteryState],
[15, "battery", tuya.valueConverter.raw],
[16, "silence", tuya.valueConverter.raw],
[17, "self_test", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_6ycgarab", "_TZE284_aoah6bv8"]),
model: "TS0601_smoke_co",
vendor: "Tuya",
description: "Dual smoke CO sensor",
whiteLabel: [tuya.whitelabel("Moes", "JKD-513COM-Z", "Dual smoke & CO alarm", ["_TZE284_aoah6bv8"])],
exposes: [
e.smoke(),
e
.enum("smoke_state", ea.STATE, ["alarm", "none", "detecting", "unknown"])
.withLabel("Smoke sensor status")
.withDescription("Possible states: alarm, none, detecting, unknown"),
e.enum("alarm_volume", ea.STATE_SET, ["mute", "low", "medium", "high"]).withDescription("Alarm volume"),
e.battery(),
tuya.exposes.silence(),
e.binary("alarm_switch", ea.STATE_SET, true, false).withDescription("Disable the alarm"),
e.carbon_monoxide(),
],
meta: {
tuyaDatapoints: [
[
1,
null,
{
from: (v) => {
const lookup = { alarm: tuya.enum(0), none: tuya.enum(1), detecting: tuya.enum(2), unknown: tuya.enum(3) };
const smokeState = Object.entries(lookup).find((i) => i[1].valueOf() === v)[0];
return {
smoke: smokeState === "alarm",
smoke_state: smokeState,
};
},
},
],
[
5,
"alarm_volume",
tuya.valueConverterBasic.lookup({
low: tuya.enum(0),
medium: tuya.enum(1),
high: tuya.enum(2),
mute: tuya.enum(3),
}),
],
[15, "battery", tuya.valueConverter.raw],
[16, "silence", tuya.valueConverter.raw],
[17, "alarm_switch", tuya.valueConverter.raw],
[18, "carbon_monoxide", tuya.valueConverter.trueFalseEnum0],
],
},
extend: [
tuya.modernExtend.tuyaBase({
dp: true,
bindBasicOnConfigure: true,
queryOnDeviceAnnounce: true,
queryOnConfigure: true,
forceTimeUpdates: true,
}),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_5d3vhjro"]),
model: "SA12IZL",
vendor: "Tuya",
description: "Smart smoke alarm",
meta: { timeout: 30000, disableDefaultResponse: true },
fromZigbee: [legacy.fromZigbee.SA12IZL],
toZigbee: [legacy.toZigbee.SA12IZL_silence_siren, legacy.toZigbee.SA12IZL_alarm],
exposes: [
e.battery(),
e.binary("smoke", ea.STATE, true, false).withDescription("Smoke alarm status"),
e.enum("battery_level", ea.STATE, ["low", "middle", "high"]).withDescription("Battery level state"),
e.binary("alarm", ea.STATE_SET, true, false).withDescription("Enable the alarm"),
e.binary("silence_siren", ea.STATE_SET, true, false).withDescription("Silence the siren"),
],
extend: [tuya.modernExtend.tuyaBase()],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_cjbofhxw", "_TZE284_cjbofhxw"]),
model: "PJ-MGW1203",
vendor: "Tuya",
description: "Clamp meter",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [e.current(), e.power(), e.voltage(), e.energy()],
meta: {
tuyaDatapoints: [
[18, "current", tuya.valueConverter.divideBy1000],
[19, "power", tuya.valueConverter.divideBy10],
[20, "voltage", tuya.valueConverter.divideBy10],
[101, "energy", tuya.valueConverter.divideBy1000],
],
},
whiteLabel: [tuya.whitelabel("Tuya", "PJ-1203-W", "Electricity energy monitor", ["_TZE284_cjbofhxw"])],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_bkkmqmyo", "_TZE200_eaac7dkw", "_TZE204_bkkmqmyo"]),
model: "TS0601_din_1",
vendor: "Tuya",
description: "Zigbee DIN energy meter",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.switch(),
te.circuitBreakerFaults(),
e.ac_frequency(),
e.energy(),
e.power(),
e.power_factor().withUnit("%"),
e.voltage(),
e.current(),
e.produced_energy(),
e.power_reactive(),
e.numeric("energy_reactive", ea.STATE).withUnit("kVArh").withDescription("Sum of reactive energy"),
e.numeric("total_energy", ea.STATE).withUnit("kWh").withDescription("Total consumed and produced energy"),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[6, null, tuya.valueConverter.phaseVariant1], // voltage and current
[16, "state", tuya.valueConverter.onOff],
[101, "total_energy", tuya.valueConverter.divideBy100], // total energy produced + consumed
[102, "produced_energy", tuya.valueConverter.divideBy100],
[103, "power", tuya.valueConverter.raw],
[105, "ac_frequency", tuya.valueConverter.divideBy100],
[109, "energy_reactive", tuya.valueConverter.divideBy100], // reactive energy in VArh
[110, "power_reactive", tuya.valueConverter.raw], // reactive power
[111, "power_factor", tuya.valueConverter.divideBy10],
[9, "faults", tuya.valueConverter.circuitBreakerFaults],
// Ignored for now; we don't know what the values mean
[17, null, null], // Alarm set1 - value seems garbage "AAAAAAAAAAAAAABkAAEOAACqAAAAAAAKAAAAAAAA"
[18, null, null], // 18 - Alarm set2 - value seems garbage "AAUAZAAFAB4APAAAAAAAAAA="
],
},
whiteLabel: [
{ vendor: "Tuya", model: "RC-MCB" },
tuya.whitelabel("Hiking", "DDS238-2", "Single phase DIN-rail energy meter with switch function", [
"_TZE200_bkkmqmyo",
"_TZE204_bkkmqmyo",
]),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_m64smti7"]),
model: "RMDZB-1PNL63",
vendor: "TNCE",
description: "Zigbee DIN single phase energy meter",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.switch(),
e.energy(),
te.circuitBreakerFaults(),
tuya.exposes.voltageWithPhase("a"),
tuya.exposes.powerWithPhase("a"),
tuya.exposes.currentWithPhase("a"),
e.temperature(),
e
.binary("over_current_breaker", ea.STATE_SET, "ON", "OFF")
.withDescription("OFF - alarm only, ON - relay will turn off when threshold reached"),
e
.numeric("over_current_threshold", ea.STATE_SET)
.withUnit("A")
.withDescription("Setup the value on the device")
.withValueMin(1)
.withValueMax(63),
e
.binary("over_voltage_breaker", ea.STATE_SET, "ON", "OFF")
.withDescription("OFF - alarm only, ON - relay will turn off when threshold reached"),
e
.numeric("over_voltage_threshold", ea.STATE_SET)
.withUnit("V")
.withDescription("Setup value on the device")
.withValueMin(250)
.withValueMax(300),
e
.binary("under_voltage_breaker", ea.STATE_SET, "ON", "OFF")
.withDescription("OFF - alarm only, ON - relay will turn off when threshold reached"),
e
.numeric("under_voltage_threshold", ea.STATE_SET)
.withUnit("V")
.withDescription("Setup value on the device")
.withValueMin(150)
.withValueMax(200),
e
.binary("high_temperature_breaker", ea.STATE_SET, "ON", "OFF")
.withDescription("OFF - alarm only, ON - relay will turn off when threshold reached"),
e
.numeric("high_temperature_threshold", ea.STATE_SET)
.withUnit("°C")
.withDescription("Setup value on the device")
.withValueMin(40)
.withValueMax(100),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[6, null, tuya.valueConverter.phaseVariant2WithPhase("a")],
[9, "faults", tuya.valueConverter.circuitBreakerFaults],
[16, "state", tuya.valueConverter.onOff],
[17, null, tuya.valueConverter.threshold_2],
[17, "high_temperature_threshold", tuya.valueConverter.threshold_2],
[17, "high_temperature_breaker", tuya.valueConverter.threshold_2],
[18, null, tuya.valueConverter.threshold_3],
[18, "over_current_threshold", tuya.valueConverter.threshold_3],
[18, "over_current_breaker", tuya.valueConverter.threshold_3],
[18, "over_voltage_threshold", tuya.valueConverter.threshold_3],
[18, "over_voltage_breaker", tuya.valueConverter.threshold_3],
[18, "under_voltage_threshold", tuya.valueConverter.threshold_3],
[18, "under_voltage_breaker", tuya.valueConverter.threshold_3],
[103, "temperature", tuya.valueConverter.raw],
// Ignored for now; we don't know what the values mean
[11, null, null], // Switch prepayment
[12, null, null], // Energy reset
[13, null, null], // Balance enertgy
[14, null, null], // Charge energy
[105, null, null], // Countdown
[106, null, null], // Cycle time
],
},
whiteLabel: [{ vendor: "TNCE", model: "RMDZB-1PNL63" }],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_wbhaespm", "_TZE200_wbhaespm", "_TZE284_wbhaespm"]),
model: "STB3L-125-ZJ",
vendor: "SUTON",
description: "Zigbee DIN RCBO energy meter",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.switch(),
e.energy(),
te.circuitBreakerFaults(),
tuya.exposes.voltageWithPhase("a"),
tuya.exposes.voltageWithPhase("b"),
tuya.exposes.voltageWithPhase("c"),
tuya.exposes.powerWithPhase("a"),
tuya.exposes.powerWithPhase("b"),
tuya.exposes.powerWithPhase("c"),
tuya.exposes.currentWithPhase("a"),
tuya.exposes.currentWithPhase("b"),
tuya.exposes.currentWithPhase("c"),
e.temperature(),
e.binary("leakage_test", ea.STATE_SET, "ON", "OFF").withDescription("Turn ON to perform a leagage test"),
e
.binary("over_current_breaker", ea.STATE_SET, "ON", "OFF")
.withDescription("OFF - alarm only, ON - relay will turn off when threshold reached"),
e
.numeric("over_current_threshold", ea.STATE_SET)
.withUnit("A")
.withDescription("Setup the value on the device")
.withValueMin(1)
.withValueMax(63),
e
.binary("over_voltage_breaker", ea.STATE_SET, "ON", "OFF")
.withDescription("OFF - alarm only, ON - relay will turn off when threshold reached"),
e
.numeric("over_voltage_threshold", ea.STATE_SET)
.withUnit("V")
.withDescription("Setup value on the device")
.withValueMin(250)
.withValueMax(300),
e
.binary("under_voltage_breaker", ea.STATE_SET, "ON", "OFF")
.withDescription("OFF - alarm only, ON - relay will turn off when threshold reached"),
e
.numeric("under_voltage_threshold", ea.STATE_SET)
.withUnit("V")
.withDescription("Setup value on the device")
.withValueMin(150)
.withValueMax(200),
e
.binary("insufficient_balance_breaker", ea.STATE_SET, "ON", "OFF")
.withDescription("OFF - alarm only, ON - relay will turn off when threshold reached"),
e
.numeric("insufficient_balance_threshold", ea.STATE_SET)
.withUnit("kWh")
.withDescription("Setup value on the device")
.withValueMin(1)
.withValueMax(65535),
e
.binary("overload_breaker", ea.STATE_SET, "ON", "OFF")
.withDescription("OFF - alarm only, ON - relay will turn off when threshold reached"),
e
.numeric("overload_threshold", ea.STATE_SET)
.withUnit("kW")
.withDescription("Setup value on the device")
.withValueMin(1)
.withValueMax(25),
e
.binary("leakage_breaker", ea.STATE_SET, "ON", "OFF")
.withDescription("OFF - alarm only, ON - relay will turn off when threshold reached"),
e
.numeric("leakage_threshold", ea.STATE_SET)
.withUnit("mA")
.withDescription("Setup value on the device")
.withValueMin(10)
.withValueMax(90),
e
.binary("high_temperature_breaker", ea.STATE_SET, "ON", "OFF")
.withDescription("OFF - alarm only, ON - relay will turn off when threshold reached"),
e
.numeric("high_temperature_threshold", ea.STATE_SET)
.withUnit("°C")
.withDescription("Setup value on the device")
.withValueMin(40)
.withValueMax(100),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[6, null, tuya.valueConverter.phaseVariant2WithPhase("a")],
[7, null, tuya.valueConverter.phaseVariant2WithPhase("b")],
[8, null, tuya.valueConverter.phaseVariant2WithPhase("c")],
[9, "faults", tuya.valueConverter.circuitBreakerFaults],
[16, "state", tuya.valueConverter.onOff],
[17, null, tuya.valueConverter.threshold_2],
[17, "overload_breaker", tuya.valueConverter.threshold_2],
[17, "overload_threshold", tuya.valueConverter.threshold_2],
[17, "leakage_threshold", tuya.valueConverter.threshold_2],
[17, "leakage_breaker", tuya.valueConverter.threshold_2],
[17, "high_temperature_threshold", tuya.valueConverter.threshold_2],
[17, "high_temperature_breaker", tuya.valueConverter.threshold_2],
[18, null, tuya.valueConverter.threshold_3],
[18, "over_current_threshold", tuya.valueConverter.threshold_3],
[18, "over_current_breaker", tuya.valueConverter.threshold_3],
[18, "over_voltage_threshold", tuya.valueConverter.threshold_3],
[18, "over_voltage_breaker", tuya.valueConverter.threshold_3],
[18, "under_voltage_threshold", tuya.valueConverter.threshold_3],
[18, "under_voltage_breaker", tuya.valueConverter.threshold_3],
[18, "insufficient_balance_threshold", tuya.valueConverter.threshold_3],
[18, "insufficient_balance_breaker", tuya.valueConverter.threshold_3],
[21, "leakage_test", tuya.valueConverter.onOff], // Leakage test
[102, "temperature", tuya.valueConverter.divideBy10],
// Ignored for now; we don't know what the values mean
[12, null, null], // Clear energy`
[13, null, null],
[14, null, null], // Leakage current
[15, null, null],
],
},
whiteLabel: [{ vendor: "SUTON", model: "STB3L-125/ZJ" }],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_lsanae15", "_TZE204_lsanae15"]),
model: "TS0601_din_2",
vendor: "Tuya",
description: "Zigbee DIN energy meter",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.switch(),
e.energy(),
e.power(),
e.voltage(),
e.current(),
e
.enum("fault", ea.STATE, [
"clear",
"over_current_threshold",
"over_power_threshold",
"over_voltage threshold",
"wrong_frequency_threshold",
])
.withDescription("Fault status of the device (clear = nothing)"),
e.enum("threshold_1", ea.STATE, ["not_set", "over_current_threshold", "over_voltage_threshold"]).withDescription("State of threshold_1"),
e
.binary("threshold_1_protection", ea.STATE, "ON", "OFF")
.withDescription("OFF - alarm only, ON - relay will be off when threshold reached"),
e
.numeric("threshold_1_value", ea.STATE)
.withDescription("Can be in Volt or Ampere depending on threshold setting. Setup the value on the device"),
e.enum("threshold_2", ea.STATE, ["not_set", "over_current_threshold", "over_voltage_threshold"]).withDescription("State of threshold_2"),
e
.binary("threshold_2_protection", ea.STATE, "ON", "OFF")
.withDescription("OFF - alarm only, ON - relay will be off when threshold reached"),
e.numeric("threshold_2_value", ea.STATE).withDescription("Setup value on the device"),
e.binary("clear_fault", ea.STATE_SET, "ON", "OFF").withDescription("Turn ON to clear last the fault"),
e.text("meter_id", ea.STATE).withDescription("Meter ID (ID of device)"),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[3, null, null], // Monthly, but sends data only after request
[4, null, null], // Dayly, but sends data only after request
[6, null, tuya.valueConverter.phaseVariant2], // voltage and current
[
10,
"fault",
tuya.valueConverterBasic.lookup({
clear: 0,
over_current_threshold: 1,
over_power_threshold: 2,
over_voltage_threshold: 4,
wrong_frequency_threshold: 8,
}),
],
[11, null, null], // Frozen - strange function, in native app - nothing is clear
[16, "state", tuya.valueConverter.onOff],
[17, null, tuya.valueConverter.threshold], // It's settable, but can't write converter
[18, "meter_id", tuya.valueConverter.raw],
[20, "clear_fault", tuya.valueConverter.onOff], // Clear fault
[21, null, null], // Forward Energy T1 - don't know what this
[22, null, null], // Forward Energy T2 - don't know what this
[23, null, null], // Forward Energy T3 - don't know what this
[24, null, null], // Forward Energy T4 - don't know what this
],
},
whiteLabel: [tuya.whitelabel("MatSee Plus", "DAC2161C", "Smart Zigbee energy meter 80A din rail", ["_TZE200_lsanae15", "_TZE204_lsanae15"])],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_rhblgy0z", "_TZE204_rhblgy0z"]),
model: "TS0601_din_3",
vendor: "Tuya",
description: "Zigbee DIN energy meter",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
whiteLabel: [{ vendor: "XOCA", model: "DAC2161C" }],
exposes: [
tuya.exposes.switch(),
e.energy(),
e.produced_energy(),
e.power(),
e.voltage(),
e.current(),
e
.enum("fault", ea.STATE, [
"clear",
"over_current_threshold",
"over_power_threshold",
"over_voltage threshold",
"wrong_frequency_threshold",
])
.withDescription("Fault status of the device (clear = nothing)"),
e.enum("threshold_1", ea.STATE, ["not_set", "over_current_threshold", "over_voltage_threshold"]).withDescription("State of threshold_1"),
e
.binary("threshold_1_protection", ea.STATE, "ON", "OFF")
.withDescription("OFF - alarm only, ON - relay will be off when threshold reached"),
e
.numeric("threshold_1_value", ea.STATE)
.withDescription("Can be in Volt or Ampere depending on threshold setting. Setup the value on the device"),
e.enum("threshold_2", ea.STATE, ["not_set", "over_current_threshold", "over_voltage_threshold"]).withDescription("State of threshold_2"),
e
.binary("threshold_2_protection", ea.STATE, "ON", "OFF")
.withDescription("OFF - alarm only, ON - relay will be off when threshold reached"),
e.numeric("threshold_2_value", ea.STATE).withDescription("Setup value on the device"),
e.binary("clear_fault", ea.STATE_SET, "ON", "OFF").withDescription("Turn ON to clear last the fault"),
e.text("meter_id", ea.STATE).withDescription("Meter ID (ID of device)"),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[2, "produced_energy", tuya.valueConverter.divideBy100],
[3, null, null], // Monthly, but sends data only after request
[4, null, null], // Dayly, but sends data only after request
[6, null, tuya.valueConverter.phaseVariant2], // voltage and current
[
10,
"fault",
tuya.valueConverterBasic.lookup({
clear: 0,
over_current_threshold: 1,
over_power_threshold: 2,
over_voltage_threshold: 4,
wrong_frequency_threshold: 8,
}),
],
[11, null, null], // Frozen - strange function, in native app - nothing is clear
[16, "state", tuya.valueConverter.onOff],
[17, null, tuya.valueConverter.threshold], // It's settable, but can't write converter
[18, "meter_id", tuya.valueConverter.raw],
[20, "clear_fault", tuya.valueConverter.onOff], // Clear fault
[21, null, null], // Forward Energy T1 - don't know what this
[22, null, null], // Forward Energy T2 - don't know what this
[23, null, null], // Forward Energy T3 - don't know what this
[24, null, null], // Forward Energy T4 - don't know what this
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_byzdayie", "_TZE200_fsb6zw01", "_TZE200_ewxhg6o9"]),
model: "TS0601_din",
vendor: "Tuya",
description: "Zigbee smart energy meter DDS238-2 Zigbee",
fromZigbee: [legacy.fromZigbee.tuya_dinrail_switch],
toZigbee: [legacy.toZigbee.tuya_switch_state],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff"]);
},
exposes: [e.switch().setAccess("state", ea.STATE_SET), e.voltage(), e.power(), e.current(), e.energy()],
},
{
fingerprint: tuya.fingerprint("TS1101", ["_TZ3000_xfs39dbf"]),
model: "TS1101_dimmer_module_1ch",
vendor: "Tuya",
description: "Zigbee dimmer module 1 channel",
extend: [tuyaLight({ minBrightness: "attribute" })],
},
{
fingerprint: tuya.fingerprint("TS1101", ["_TZ3000_7ysdnebc"]),
model: "TS1101_dimmer_module_2ch",
vendor: "Tuya",
description: "Zigbee dimmer module 2 channel",
whiteLabel: [{ vendor: "OXT", model: "SWTZ25" }],
extend: [
m.deviceEndpoints({ endpoints: { l1: 1, l2: 2 } }),
tuyaLight({
minBrightness: "attribute",
endpointNames: ["l1", "l2"],
configureReporting: true,
}),
],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
},
},
{
zigbeeModel: ["RH3001"],
fingerprint: [
{
type: "EndDevice",
manufacturerID: 4098,
applicationVersion: 66,
endpoints: [
{
ID: 1,
profileID: 260,
deviceID: 1026,
inputClusters: [0, 10, 1, 1280],
outputClusters: [25],
},
],
},
],
model: "SNTZ007",
vendor: "Tuya",
description: "Rechargeable Zigbee contact sensor",
fromZigbee: [fz.ias_contact_alarm_1, fz.battery],
toZigbee: [],
exposes: [e.contact(), e.battery_low(), e.tamper(), e.battery()],
whiteLabel: [{ vendor: "BlitzWolf", model: "BW-IS2" }],
},
{
zigbeeModel: ["RH3040"],
model: "RH3040",
vendor: "Tuya",
description: "PIR sensor",
fromZigbee: [fz.battery, fz.ias_occupancy_alarm_1],
toZigbee: [],
whiteLabel: [
{ vendor: "Samotech", model: "SM301Z" },
{ vendor: "Nedis", model: "ZBSM10WT" },
],
exposes: [e.battery(), e.occupancy(), e.battery_low(), e.tamper()],
},
{
zigbeeModel: ["TS0115"],
model: "TS0115",
vendor: "Tuya",
description: "Multiprise with 4 AC outlets and 2 USB super charging ports (10A or 16A)",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
endpoints: ["l1", "l2", "l3", "l4", "l5"],
}),
],
whiteLabel: [{ vendor: "UseeLink", model: "SM-SO306E/K/M" }],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3, l4: 4, l5: 7 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(4), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(7), coordinatorEndpoint, ["genOnOff"]);
await device.getEndpoint(1).read("genOnOff", ["onOff", "moesStartUpOnOff"]);
await device.getEndpoint(2).read("genOnOff", ["onOff"]);
await device.getEndpoint(3).read("genOnOff", ["onOff"]);
await device.getEndpoint(4).read("genOnOff", ["onOff"]);
await device.getEndpoint(7).read("genOnOff", ["onOff"]);
},
},
{
zigbeeModel: ["RH3052"],
model: "TT001ZAV20",
vendor: "Tuya",
description: "Temperature & humidity sensor",
fromZigbee: [fz.humidity, fz.temperature, fz.battery],
toZigbee: [],
exposes: [e.humidity(), e.temperature(), e.battery()],
},
{
fingerprint: tuya.fingerprint("TS0011", ["_TZ3000_l8fsgo6p"]),
zigbeeModel: ["TS0011", "ZG-302Z1"],
model: "TS0011",
vendor: "Tuya",
description: "Smart light switch - 1 gang",
extend: [tuya.modernExtend.tuyaBase(), tuya.modernExtend.tuyaOnOff({ backlightModeOffNormalInverted: true })],
whiteLabel: [
{ vendor: "Vrey", model: "VR-X712U-0013" },
{ vendor: "TUYATEC", model: "GDKES-01TZXD" },
{
vendor: "Lonsonho",
model: "QS-Zigbee-S05-L",
description: "1 gang smart switch module without neutral wire",
},
{ vendor: "Mercator Ikuü", model: "SSW01" },
tuya.whitelabel("Nous", "LZ3", "Smart water/gas valve", ["_TZ3000_abjodzas"]),
],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
// Reports itself as battery which is not correct: https://github.com/Koenkk/zigbee2mqtt/issues/6190
device.powerSource = "Mains (single phase)";
device.save();
},
},
{
fingerprint: tuya.fingerprint("TS0011", ["_TZ3000_qmi1cfuq", "_TZ3000_txpirhfq", "_TZ3000_ji4araar", "_TZ3000_tw4ztbp4"]),
model: "TS0011_switch_module",
vendor: "Tuya",
description: "1 gang switch module - (without neutral)",
extend: [tuya.modernExtend.tuyaBase(), tuya.modernExtend.tuyaOnOff({ switchType: true, onOffCountdown: true })],
whiteLabel: [
{ vendor: "AVATTO", model: "1gang N-ZLWSM01" },
{ vendor: "SMATRUL", model: "TMZ02L-16A-W" },
{ vendor: "Aubess", model: "TMZ02L-16A-B" },
tuya.whitelabel("HOMMYN", "RLZBNN01", "1-gang switch module (without Neutral)", ["_TZ3000_tw4ztbp4"]),
],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
device.powerSource = "Mains (single phase)";
device.save();
},
},
{
zigbeeModel: ["TS0012", "ZG-302Z2"],
model: "TS0012",
vendor: "Tuya",
description: "Smart light switch - 2 gang",
whiteLabel: [
{ vendor: "Vrey", model: "VR-X712U-0013" },
{ vendor: "TUYATEC", model: "GDKES-02TZXD" },
{ vendor: "Earda", model: "ESW-2ZAA-EU" },
{ vendor: "Moes", model: "ZS-US2-BK-MS" },
tuya.whitelabel("Moes", "ZS-EUB_2gang", "Smart light switch - 2 gang", ["_TZ3000_18ejxno0"]),
],
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
backlightModeOffNormalInverted: true,
endpoints: ["left", "right"],
}),
],
endpoint: (device) => {
return { left: 1, right: 2 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
device.powerSource = "Mains (single phase)";
device.save();
},
},
{
fingerprint: tuya.fingerprint("TS0012", ["_TZ3000_jl7qyupf", "_TZ3000_nPGIPl5D", "_TZ3000_kpatq5pq", "_TZ3000_ljhbw1c9", "_TZ3000_4zf0crgo"]),
model: "TS0012_switch_module",
vendor: "Tuya",
description: "2 gang switch module - (without neutral)",
whiteLabel: [
{ vendor: "AVATTO", model: "2gang N-ZLWSM01" },
tuya.whitelabel("AVATTO", "LZWSM16-2", "2 gang switch module - (without neutral)", ["_TZ3000_kpatq5pq", "_TZ3000_ljhbw1c9"]),
],
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
switchType: true,
onOffCountdown: true,
endpoints: ["left", "right"],
}),
m.forcePowerSource({ powerSource: "Mains (single phase)" }),
],
endpoint: (device) => {
return { left: 1, right: 2 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
device.powerSource = "Mains (single phase)";
device.save();
},
},
{
zigbeeModel: ["TS0013", "ZG-302Z3"],
model: "TS0013",
vendor: "Tuya",
description: "Smart light switch - 3 gang without neutral wire",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
backlightModeOffNormalInverted: true,
endpoints: ["left", "center", "right"],
}),
],
endpoint: (device) => {
return { left: 1, center: 2, right: 3 };
},
whiteLabel: [{ vendor: "TUYATEC", model: "GDKES-03TZXD" }],
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
try {
for (const ID of [1, 2, 3]) {
const endpoint = device.getEndpoint(ID);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff"]);
}
}
catch {
// Fails for some: https://github.com/Koenkk/zigbee2mqtt/issues/4872
}
device.powerSource = "Mains (single phase)";
device.save();
},
},
{
fingerprint: tuya.fingerprint("TS0013", ["_TZ3000_ypgri8yz", "_TZ3000_sznawwyw", "_TZ3000_avotanj3", "_TZ3000_t7ugva7q"]),
model: "TS0013_switch_module",
vendor: "Tuya",
description: "3 gang switch module - (without neutral)",
whiteLabel: [
{ vendor: "AVATTO", model: "3gang N-ZLWSM01" },
tuya.whitelabel("AVATTO", "LZWSM16-3", "3 gang switch module - (without neutral)", ["_TZ3000_sznawwyw", "_TZ3000_avotanj3"]),
tuya.whitelabel("Girier", "ZB08", "3 Channel Switch Module-L - (No Neutral Wire)", ["_TZ3000_ypgri8yz", "_TZ3000_t7ugva7q"]),
],
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
switchType: true,
onOffCountdown: true,
endpoints: ["left", "center", "right"],
}),
m.forcePowerSource({ powerSource: "Mains (single phase)" }),
],
endpoint: (device) => {
return { left: 1, center: 2, right: 3 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
try {
for (const ID of [1, 2, 3]) {
const endpoint = device.getEndpoint(ID);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff"]);
}
}
catch {
// Fails for some: https://github.com/Koenkk/zigbee2mqtt/issues/4872
}
device.powerSource = "Mains (single phase)";
device.save();
},
},
{
zigbeeModel: ["AY-601ZL", "AY-801ZL"],
model: "AY-601ZL",
vendor: "AOYAN",
description: "1 gang switch module - without neutral wire",
whiteLabel: [
{
vendor: "AOYAN",
model: "AY-801ZL",
description: "1 gang switch module - without neutral wire",
fingerprint: [{ modelID: "AY-801ZL" }],
},
],
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
backlightModeOffNormalInverted: true,
onOffCountdown: true,
}),
m.forcePowerSource({ powerSource: "Mains (single phase)" }),
],
},
{
zigbeeModel: ["AY-602ZL", "AY-802ZL"],
model: "AY-602ZL",
vendor: "AOYAN",
description: "2 gang switch module - without neutral wire",
whiteLabel: [
{
vendor: "AOYAN",
model: "AY-802ZL",
description: "2 gang switch module - without neutral wire",
fingerprint: [{ modelID: "AY-802ZL" }],
},
],
extend: [
tuya.modernExtend.tuyaBase(),
m.deviceEndpoints({ endpoints: { left: 1, right: 2 } }),
tuya.modernExtend.tuyaOnOff({
backlightModeOffNormalInverted: true,
onOffCountdown: true,
endpoints: ["left", "right"],
}),
m.forcePowerSource({ powerSource: "Mains (single phase)" }),
],
},
{
zigbeeModel: ["AY-603ZL", "AY-803ZL"],
model: "AY-603ZL",
vendor: "AOYAN",
description: "3 gang switch module - without neutral wire",
whiteLabel: [
{
vendor: "AOYAN",
model: "AY-803ZL",
description: "3 gang switch module - without neutral wire",
fingerprint: [{ modelID: "AY-803ZL" }],
},
],
extend: [
tuya.modernExtend.tuyaBase(),
m.deviceEndpoints({ endpoints: { left: 1, center: 2, right: 3 } }),
tuya.modernExtend.tuyaOnOff({
backlightModeOffNormalInverted: true,
onOffCountdown: true,
endpoints: ["left", "center", "right"],
}),
m.forcePowerSource({ powerSource: "Mains (single phase)" }),
],
},
{
zigbeeModel: ["TS0014"],
model: "TS0014",
vendor: "Tuya",
description: "Smart light switch - 4 gang without neutral wire",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
backlightModeLowMediumHigh: true,
endpoints: ["l1", "l2", "l3", "l4"],
}),
],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3, l4: 4 };
},
whiteLabel: [
{ vendor: "TUYATEC", model: "GDKES-04TZXD" },
tuya.whitelabel("Vizo", "VZ-221S", "Smart light switch - 1 gang without neutral wire", ["_TZ3210_qjvi92wz"]),
tuya.whitelabel("Vizo", "VZ-222S", "Smart light switch - 2 gang without neutral wire", ["_TZ3210_w3hl6rao"]),
tuya.whitelabel("Vizo", "VZ-223S", "Smart light switch - 3 gang without neutral wire", ["_TZ3210_z4hgsevd"]),
{ vendor: "MakeGood", model: "MG-ZG04W/B/G" },
{ vendor: "Mercator Ikuü", model: "SSW04" },
],
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
try {
for (const ID of [1, 2, 3, 4]) {
const endpoint = device.getEndpoint(ID);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff"]);
}
}
catch {
// Fails for some: https://github.com/Koenkk/zigbee2mqtt/issues/4872
}
device.powerSource = "Mains (single phase)";
device.save();
},
},
{
zigbeeModel: ["gq8b1uv"],
model: "gq8b1uv",
vendor: "Tuya",
description: "Zigbee smart dimmer",
fromZigbee: [legacy.fromZigbee.tuya_dimmer],
toZigbee: [legacy.toZigbee.tuya_dimmer_state, legacy.toZigbee.tuya_dimmer_level],
exposes: [e.light_brightness().setAccess("state", ea.STATE_SET).setAccess("brightness", ea.STATE_SET)],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff", "genLevelCtrl"]);
},
},
{
zigbeeModel: ["HY0017", "005f0c3b"],
model: "U86KCJ-ZP",
vendor: "Tuya",
description: "Smart 6 key scene wall switch",
fromZigbee: [fzLocal.scenes_recall_scene_65029],
exposes: [e.action(["scene_1", "scene_2", "scene_3", "scene_4", "scene_5", "scene_6"])],
toZigbee: [],
},
{
zigbeeModel: ["TS0026"],
model: "TS0026",
vendor: "Tuya",
description: "6 button scene wall switch",
fromZigbee: [fzLocal.scenes_recall_scene_65029, fzLocal.scene_recall],
exposes: [e.action(["scene_1", "scene_2", "scene_3", "scene_4", "scene_5", "scene_6"])],
toZigbee: [],
},
{
zigbeeModel: ["q9mpfhw"],
model: "SNTZ009",
vendor: "Tuya",
description: "Water leak sensor",
fromZigbee: [legacy.fromZigbee.tuya_water_leak],
exposes: [e.water_leak()],
toZigbee: [],
},
{
zigbeeModel: ["TS0004"],
model: "TS0004",
vendor: "Tuya",
description: "Smart light switch - 4 gang with neutral wire",
extend: [
tuya.modernExtend.tuyaBase(),
m.deviceEndpoints({ endpoints: { l1: 1, l2: 2, l3: 3, l4: 4 } }),
tuya.modernExtend.tuyaOnOff({
powerOnBehavior2: true,
backlightModeOffOn: true,
endpoints: ["l1", "l2", "l3", "l4"],
switchType: (m) => m === "_TZ3000_r9e2w7dn" || m === "_TZ3000_excgg5kb",
}),
],
whiteLabel: [
tuya.whitelabel("Tuya", "DS-111", "Smart light switch - 4 gang with neutral wire", ["_TZ3000_mdj7kra9"]),
tuya.whitelabel("MHCOZY", "TYWB 4ch-RF", "4 channel relay", ["_TZ3000_u3oupgdy", "_TZ3000_imaccztn", "_TZ3210_imaccztn"]),
tuya.whitelabel("AVATTO", "TS0004_1", "Smart light switch - 4 gang with neutral wire", ["_TZ3000_nivavasg", "_TZ3000_gexniqbq"]),
tuya.whitelabel("Nova Digital", "SA-4", "Safira smart light switch - 4 gang", ["_TZ3000_iymfxdis"]),
tuya.whitelabel("AVATTO", "ZBTS60-04", "4 gang switch module with backlight", ["_TZ3000_r9e2w7dn"]),
],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(4), coordinatorEndpoint, ["genOnOff"]);
},
},
{
fingerprint: tuya.fingerprint("TS0004", ["_TZ3000_ncb6mkx8"]),
model: "TS0004_fan_light_switch",
vendor: "Tuya",
description: "Fan and light controller with 3 speeds",
extend: [tuyaBase()],
fromZigbee: [fz.on_off, fzLocal.TS0004_backlight_mode],
toZigbee: [tz.on_off, tzLocal.TS0004_backlight_mode],
exposes: [
e.switch().withEndpoint("lights").withDescription("Light control"),
e.switch().withEndpoint("low").withDescription("Fan speed: low"),
e.switch().withEndpoint("medium").withDescription("Fan speed: medium"),
e.switch().withEndpoint("high").withDescription("Fan speed: high"),
e
.enum("backlight_mode", ea.ALL, ["red_when_on", "pink_when_on", "red_on_blue_off", "pink_on_blue_off"])
.withDescription("Backlight color mode"),
],
endpoint: (device) => {
return { lights: 1, high: 2, low: 3, medium: 4 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
for (const ID of [1, 2, 3, 4]) {
await reporting.bind(device.getEndpoint(ID), coordinatorEndpoint, ["genOnOff"]);
}
},
},
{
fingerprint: tuya.fingerprint("TS0726", ["_TZ3002_l8bfzlcd", "_TZ3000_ovbvmhiq"]),
model: "TS0726_1_gang",
vendor: "Tuya",
description: "1 gang switch with neutral wire",
extend: [
tuya.modernExtend.tuyaBase(),
m.enumLookup({
name: "indicator_mode",
lookup: { none: 0, relay: 1, pos: 2 },
cluster: "genOnOff",
attribute: { ID: 0x8001, type: zcl_1.DataType.ENUM8 },
description: "Mode of the indicator light",
}),
],
fromZigbee: [fz.on_off, tuya.fz.power_on_behavior_2, fzLocal.TS0726_action],
toZigbee: [tz.on_off, tuya.tz.power_on_behavior_2, tzLocal.TS0726_switch_mode],
exposes: [e.switch(), e.power_on_behavior(), e.enum("switch_mode", ea.STATE_SET, ["switch", "scene"]), e.action(["scene_1"])],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
},
},
{
fingerprint: tuya.fingerprint("TS0726", ["_TZ3002_1s0vfmtv", "_TZ3002_gdwja9a7", "_TZ3002_u7d3nes3", "_TZ3000_icoxotza"]),
model: "TS0726_2_gang",
vendor: "Tuya",
description: "2 gang switch with neutral wire",
extend: [
tuya.modernExtend.tuyaBase(),
m.enumLookup({
name: "indicator_mode",
lookup: { none: 0, relay: 1, pos: 2 },
cluster: "genOnOff",
attribute: { ID: 0x8001, type: zcl_1.DataType.ENUM8 },
description: "Mode of the indicator light",
}),
],
fromZigbee: [fz.on_off, tuya.fz.power_on_behavior_2, fzLocal.TS0726_action],
toZigbee: [tz.on_off, tuya.tz.power_on_behavior_2, tzLocal.TS0726_switch_mode],
exposes: [
...[1, 2].map((ep) => e.switch().withEndpoint(`l${ep}`)),
...[1, 2].map((ep) => e.power_on_behavior().withEndpoint(`l${ep}`)),
...[1, 2].map((ep) => e.enum("switch_mode", ea.STATE_SET, ["switch", "scene"]).withEndpoint(`l${ep}`)),
e.action(["scene_1", "scene_2"]),
],
endpoint: (device) => {
return { l1: 1, l2: 2 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
for (const ep of [1, 2]) {
await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ["genOnOff"]);
}
},
},
{
fingerprint: tuya.fingerprint("TS0726", ["_TZ3002_9vcekkp1", "_TZ3000_m4ah6bcz"]),
model: "TS0726_multi_1_gang",
vendor: "Tuya",
description: "Multi 1 gang switch with backlight",
extend: [tuya.modernExtend.tuyaBase()],
fromZigbee: [fz.on_off, tuya.fz.power_on_behavior_2, fzLocal.TS0726_action],
toZigbee: [tz.on_off, tuya.tz.power_on_behavior_2, tzLocal.TS0726_switch_mode, tuya.tz.backlight_indicator_mode_2],
exposes: [
e.switch(),
e.power_on_behavior(),
e.enum("switch_mode", ea.STATE_SET, ["switch", "scene"]),
tuya.exposes.backlightModeOffOn().withAccess(ea.STATE_SET),
e.action(["scene_1"]),
],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
for (const ep of [1]) {
await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ["genOnOff"]);
}
},
},
{
fingerprint: tuya.fingerprint("TS0726", ["_TZ3000_m3pafcnk", "_TZ3002_m3pafcnk"]),
model: "TS0726_multi_3_gang",
vendor: "Tuya",
description: "multi 3 gang switch with backlight",
extend: [tuya.modernExtend.tuyaBase()],
fromZigbee: [fz.on_off, tuya.fz.power_on_behavior_2, fzLocal.TS0726_action],
toZigbee: [tz.on_off, tuya.tz.power_on_behavior_2, tzLocal.TS0726_switch_mode, tuya.tz.backlight_indicator_mode_2],
exposes: [
...[1, 2, 3].map((ep) => e.switch().withEndpoint(`l${ep}`)),
...[1, 2, 3].map((ep) => e.power_on_behavior().withEndpoint(`l${ep}`)),
...[1, 2, 3].map((ep) => e.enum("switch_mode", ea.STATE_SET, ["switch", "scene"]).withEndpoint(`l${ep}`)),
tuya.exposes.backlightModeOffOn().withAccess(ea.STATE_SET),
e.action(["scene_1", "scene_2", "scene_3"]),
],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
for (const ep of [1, 2, 3]) {
await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ["genOnOff"]);
}
},
},
{
fingerprint: tuya.fingerprint("TS0726", ["_TZ3002_aewsvjcu", "_TZ3002_phu8ygaw"]),
model: "TS0726_multi_4_gang",
vendor: "Tuya",
description: "Multi 4 gang switch with backlight",
extend: [tuya.modernExtend.tuyaBase()],
fromZigbee: [fz.on_off, tuya.fz.power_on_behavior_2, fzLocal.TS0726_action],
toZigbee: [tz.on_off, tuya.tz.power_on_behavior_2, tzLocal.TS0726_switch_mode, tuya.tz.backlight_indicator_mode_2],
exposes: [
...[1, 2, 3, 4].map((ep) => e.switch().withEndpoint(`l${ep}`)),
...[1, 2, 3, 4].map((ep) => e.power_on_behavior().withEndpoint(`l${ep}`)),
...[1, 2, 3, 4].map((ep) => e.enum("switch_mode", ea.STATE_SET, ["switch", "scene"]).withEndpoint(`l${ep}`)),
tuya.exposes.backlightModeOffOn().withAccess(ea.STATE_SET),
e.action(["scene_1", "scene_2", "scene_3", "scene_4"]),
],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3, l4: 4 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
for (const ep of [1, 2, 3, 4]) {
await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ["genOnOff"]);
}
},
},
{
fingerprint: tuya.fingerprint("TS0726", ["_TZ3000_qhyadm57"]),
model: "TS0726_4_gang_switch_and_2_scene",
vendor: "Tuya",
description: "Multi 4 gang switch and 2 scene with backlight",
extend: [tuya.modernExtend.tuyaBase()],
fromZigbee: [fz.on_off, tuya.fz.power_on_behavior_2, fzLocal.TS0726_action],
toZigbee: [tz.on_off, tuya.tz.power_on_behavior_2, tzLocal.TS0726_switch_mode, tuya.tz.backlight_indicator_mode_2],
exposes: [
...[1, 2, 3, 4].map((ep) => e.switch().withEndpoint(`l${ep}`)),
...[1, 2, 3, 4].map((ep) => e.power_on_behavior().withEndpoint(`l${ep}`)),
...[1, 2, 3, 4].map((ep) => e.enum("switch_mode", ea.STATE_SET, ["switch", "scene"]).withEndpoint(`l${ep}`)),
tuya.exposes.backlightModeOffOn().withAccess(ea.STATE_SET),
e.action(["scene_1", "scene_2", "scene_3", "scene_4", "scene_5", "scene_6"]),
],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3, l4: 4, l5: 5, l6: 6 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
for (const ep of [1, 2, 3, 4, 5, 6]) {
await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ["genOnOff"]);
}
},
},
{
// TS0726_3_gang_scene_switch is usually preferred
fingerprint: tuya.fingerprint("TS0726", ["_TZ3000_lcjsewlo", "_TZ3000_kfkqkjqe"]),
model: "TS0726_3_gang",
vendor: "Tuya",
description: "3 gang switch with neutral wire",
extend: [tuya.modernExtend.tuyaBase()],
fromZigbee: [fz.on_off, tuya.fz.power_on_behavior_2, fzLocal.TS0726_action],
toZigbee: [tz.on_off, tuya.tz.power_on_behavior_2, tzLocal.TS0726_switch_mode],
exposes: [
...[1, 2, 3].map((ep) => e.switch().withEndpoint(`l${ep}`)),
...[1, 2, 3].map((ep) => e.power_on_behavior().withEndpoint(`l${ep}`)),
...[1, 2, 3].map((ep) => e.enum("switch_mode", ea.STATE_SET, ["switch", "scene"]).withEndpoint(`l${ep}`)),
e.action(["scene_1", "scene_2", "scene_3"]),
],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
for (const ep of [1, 2, 3]) {
await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ["genOnOff"]);
}
},
},
{
// TS0726_4_gang_scene_switch is usually preferred
fingerprint: tuya.fingerprint("TS0726", ["_TZ3000_wsspgtcd", "_TZ3000_s678wazd", "_TZ3002_uu4uircb", "_TZ3002_yptomml1", "_TZ3002_pw4ad2xa"]),
model: "TS0726_4_gang",
vendor: "Tuya",
description: "4 gang switch with neutral wire",
extend: [tuya.modernExtend.tuyaBase()],
fromZigbee: [fz.on_off, tuya.fz.power_on_behavior_2, fzLocal.TS0726_action],
toZigbee: [tz.on_off, tuya.tz.power_on_behavior_2, tzLocal.TS0726_switch_mode],
exposes: [
...[1, 2, 3, 4].map((ep) => e.switch().withEndpoint(`l${ep}`)),
...[1, 2, 3, 4].map((ep) => e.power_on_behavior().withEndpoint(`l${ep}`)),
...[1, 2, 3, 4].map((ep) => e.enum("switch_mode", ea.STATE_SET, ["switch", "scene"]).withEndpoint(`l${ep}`)),
e.action(["scene_1", "scene_2", "scene_3", "scene_4"]),
],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3, l4: 4 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
for (const ep of [1, 2, 3, 4]) {
await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ["genOnOff"]);
}
},
},
{
fingerprint: tuya.fingerprint("TS0726", ["_TZ3002_sal078g8", "_TZ3002_sfh0jtz0", "_TZ3000_cumqn2av", "_TZ300A_fhbcipep"]),
model: "TS0726_switch_4g_2s",
vendor: "Tuya",
description: "COSWALL smart switch (4 gang + 2 scene)",
fromZigbee: [fzLocal.TS0726_action],
exposes: [e.action(["scene_1", "scene_2", "scene_3", "scene_4", "scene_5", "scene_6"])],
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
switchMode: true,
powerOnBehavior2: true,
backlightModeOffOn: true,
endpoints: ["l1", "l2", "l3", "l4"],
}),
],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3, l4: 4, l5: 5, l6: 6 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
for (const ep of [1, 2, 3, 4, 5, 6]) {
await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ["genOnOff"]);
}
},
},
{
zigbeeModel: ["TS0006"],
model: "TS0006",
vendor: "Tuya",
description: "6 gang switch module with neutral wire",
extend: [tuya.modernExtend.tuyaBase(), tuya.modernExtend.tuyaOnOff()],
exposes: [
e.switch().withEndpoint("l1"),
e.switch().withEndpoint("l2"),
e.switch().withEndpoint("l3"),
e.switch().withEndpoint("l4"),
e.switch().withEndpoint("l5"),
e.switch().withEndpoint("l6"),
],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3, l4: 4, l5: 5, l6: 6 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(4), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(5), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(6), coordinatorEndpoint, ["genOnOff"]);
},
whiteLabel: [tuya.whitelabel("AVATTO", "TS0006_1", "4 gang switch module with neutral wire and socket", ["_TZ3000_cvis4qmw"])],
},
{
zigbeeModel: ["HY0080"],
model: "U86KWF-ZPSJ",
vendor: "Tuya",
description: "Environment controller",
fromZigbee: [fz.thermostat, fz.fan],
toZigbee: [
tz.thermostat_local_temperature,
tz.thermostat_local_temperature_calibration,
tz.thermostat_occupancy,
tz.thermostat_occupied_heating_setpoint,
tz.thermostat_unoccupied_heating_setpoint,
tz.thermostat_occupied_cooling_setpoint,
tz.thermostat_unoccupied_cooling_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_relay_status_log,
tz.thermostat_temperature_setpoint_hold,
tz.thermostat_temperature_setpoint_hold_duration,
tz.fan_mode,
],
exposes: [
e
.climate()
.withSetpoint("occupied_heating_setpoint", 5, 30, 0.5)
.withLocalTemperature()
.withSystemMode(["off", "auto", "heat"], ea.ALL)
.withRunningState(["idle", "heat", "cool"], ea.STATE)
.withLocalTemperatureCalibration(-30, 30, 0.1, ea.ALL)
.withPiHeatingDemand(),
],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(9);
await reporting.bind(endpoint, coordinatorEndpoint, ["hvacThermostat", "hvacFanCtrl"]);
await reporting.thermostatTemperature(endpoint);
await reporting.thermostatSystemMode(endpoint);
await reporting.thermostatOccupiedHeatingSetpoint(endpoint);
await reporting.thermostatUnoccupiedHeatingSetpoint(endpoint);
await reporting.thermostatOccupiedCoolingSetpoint(endpoint);
await reporting.thermostatUnoccupiedCoolingSetpoint(endpoint);
await reporting.fanMode(endpoint);
},
},
{
zigbeeModel: ["6dfgetq"],
model: "D3-DPWK-TY",
vendor: "Tuya",
description: "HVAC controller",
exposes: [
e
.climate()
.withSetpoint("current_heating_setpoint", 5, 30, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withSystemMode(["off", "auto", "heat"], ea.STATE_SET)
.withRunningState(["idle", "heat", "cool"], ea.STATE),
],
fromZigbee: [legacy.fromZigbee.tuya_thermostat, legacy.fromZigbee.tuya_dimmer],
meta: {
tuyaThermostatSystemMode: legacy.thermostatSystemModes2,
tuyaThermostatPreset: legacy.thermostatPresets,
},
toZigbee: [
legacy.toZigbee.tuya_thermostat_current_heating_setpoint,
legacy.toZigbee.tuya_thermostat_system_mode,
legacy.toZigbee.tuya_thermostat_fan_mode,
legacy.toZigbee.tuya_dimmer_state,
],
},
{
zigbeeModel: ["E220-KR4N0Z0-HA", "JZ-ZB-004"],
model: "E220-KR4N0Z0-HA",
vendor: "Tuya",
description: "Multiprise with 4 AC outlets and 2 USB super charging ports (16A)",
extend: [tuya.modernExtend.tuyaBase(), tuya.modernExtend.tuyaOnOff()],
fromZigbee: [fz.on_off_skip_duplicate_transaction],
exposes: [e.switch().withEndpoint("l1"), e.switch().withEndpoint("l2"), e.switch().withEndpoint("l3"), e.switch().withEndpoint("l4")],
whiteLabel: [{ vendor: "LELLKI", model: "WP33-EU" }],
meta: { multiEndpoint: true },
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3, l4: 4 };
},
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(4), coordinatorEndpoint, ["genOnOff"]);
},
},
{
zigbeeModel: ["TS0216"],
model: "TS0216",
vendor: "Tuya",
description: "Sound and flash siren",
fromZigbee: [tuya.fz.ts0216_siren, fz.battery],
exposes: [
e.battery(),
e.binary("alarm", ea.STATE_SET, true, false),
e.numeric("volume", ea.ALL).withValueMin(0).withValueMax(100).withDescription("Volume of siren"),
],
whiteLabel: [tuya.whitelabel("Hejhome", "GKZ-SA141", "Sound and flash siren", ["_TYZB01_sbpc1zrb"])],
toZigbee: [tuya.tz.ts0216_alarm, tuya.tz.ts0216_duration, tuya.tz.ts0216_volume],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genPowerCfg"]);
// Device advertises itself as Router but is an EndDevice
device.type = "EndDevice";
device.save();
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_znzs7yaw"]),
model: "HY08WE",
vendor: "Tuya",
description: "Wall-mount thermostat",
fromZigbee: [legacy.fromZigbee.hy_thermostat],
toZigbee: [legacy.toZigbee.hy_thermostat],
extend: [tuya.modernExtend.tuyaBase({ timeStart: "2000" })],
exposes: [
e
.climate()
.withSetpoint("current_heating_setpoint", 5, 30, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withSystemMode(["off", "auto", "heat"], ea.STATE_SET)
.withRunningState(["idle", "heat"], ea.STATE),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_g9a3awaj"]),
model: "ZWT07",
vendor: "Tuya",
description: "Wall-mount thermostat",
extend: [tuya.modernExtend.tuyaBase({ dp: true, respondToMcuVersionResponse: true, timeStart: "2000" })],
exposes: [
e
.climate()
.withSetpoint("current_heating_setpoint", 5, 60, 0.5, ea.STATE_SET)
.withSystemMode(["off", "heat"], ea.STATE_SET)
.withRunningState(["idle", "heat"], ea.STATE)
.withPreset(["manual", "program"])
.withLocalTemperature(),
e.binary("frost", ea.STATE_SET, "ON", "OFF").withDescription("Antifreeze function"),
],
meta: {
tuyaDatapoints: [
[1, "system_mode", tuya.valueConverterBasic.lookup({ heat: true, off: false })],
[
2,
"preset",
tuya.valueConverterBasic.lookup({
manual: tuya.enum(1),
program: tuya.enum(0),
}),
],
[36, "running_state", tuya.valueConverterBasic.lookup({ heat: 1, idle: 0 })],
[16, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[24, "local_temperature", tuya.valueConverter.divideBy10],
[10, "frost", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_6kijc7nd", "_TZE204_6kijc7nd"]),
model: "_TZE284_6kijc7nd",
vendor: "Tervix",
description: "Tervix Zigbee thermostat",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
// Weekly Schedule (Datapoint 48) - Placeholder
// The schedule is a raw 168-byte array (7 days x 24 bytes/day):
// - Each day has up to 6 slots (4 bytes each): [Hour (1B), Minute (1B), Temp x10 (2B)]
// - Empty slots: encoded as 00:00 time and 0°C temp (0x0000).
// To implement:
// - Converter 'from': Decode raw data into structured schedule (e.g., [{day, slots: [{time, temp}]}]).
// - Converter 'to': Encode user input back into raw 168-byte buffer format.
// Each timer can set the time in the 1st and 2nd bytes, and the temperature in the 3rd and 4th bytes.
// There are 6 timers. The default time is 06:00 20°C; 08:00 15°C; 11:30 15°C; 13:30 15°C; 17:00 22°C; 22:00 15°C.
// Each segment occupies 4 bytes. If the temperature contains decimals, the data must be expanded 10 times.
// If it does not contain decimals, it will be transmitted normally.
// For example, if it is 06:00 35.0, it will be transmitted as 06 00 01 5e. 6*4*3=72 bytes
// Climate Control
e
.climate()
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withSystemMode(["off", "heat"], ea.STATE_SET)
.withRunningState(["idle", "heat"], ea.STATE),
// Expose local temperature as a sensor
e.numeric("local_temperature", ea.STATE).withUnit("°C").withDescription("Current temperature measured by the thermostat."),
// Modes and Schedules
e.enum("mode", ea.STATE_SET, ["manual", "program"]),
e.enum("run_mode", ea.STATE_SET, ["heat_mode", "cool_mode"]).withDescription("Operation mode of the thermostat (heat or cool)."),
e.enum("week_program_periods", ea.STATE, ["periods_4"]).withDescription("Number of program periods per week (read-only)."),
// Features
e
.binary("factory_reset", ea.STATE_SET, "ON", "OFF")
.withDescription("WARNING: Restores the device to factory settings. All configurations will be lost."),
e.binary("child_lock", ea.STATE_SET, "ON", "OFF").withDescription("Enables or disables the child lock feature."),
e.enum("window_state", ea.STATE, ["open", "close"]).withDescription("Indicates whether the window is open or closed."),
e.enum("working_status", ea.STATE, ["Keeping Warm", "Working"]).withDescription("Current working status of the thermostat."),
e.binary("window_check", ea.STATE_SET, "ON", "OFF").withDescription("Checks whether the window is open or closed."),
e.binary("frost_protection", ea.STATE_SET, "ON", "OFF").withDescription("Enables frost protection mode."),
e.enum("sensor_choose", ea.STATE_SET, ["in", "out"]).withDescription("Selects between internal or external temperature sensors."),
e.binary("humidity_control", ea.STATE_SET, "ON", "OFF").withDescription("Controls the humidity protection feature."),
e.numeric("humidity", ea.STATE).withUnit("%").withDescription("Displays the current relative humidity level in percentage."),
// Weekly Schedule
e
.text("week_schedule", ea.STATE_SET)
.withDescription('Weekly schedule: structured format like - Monday: [{"time":"06:30","temp":20.0},...].'),
e
.numeric("window_open_detection_time", ea.STATE_SET)
.withUnit("min")
.withValueMin(2)
.withValueMax(30)
.withValueStep(1)
.withDescription("Window open detection threshold in minutes."),
e
.numeric("window_open_detection_temp", ea.STATE_SET)
.withUnit("°C")
.withValueMin(2)
.withValueMax(4)
.withDescription("Temperature threshold for window open detection."),
e
.numeric("window_open_delay_time", ea.STATE_SET)
.withUnit("min")
.withValueMin(10)
.withValueMax(60)
.withValueStep(1)
.withDescription("Delay time for triggering window open detection (minutes)."),
// Temperature and Control Adjustments
e
.numeric("upper_temp", ea.STATE_SET)
.withUnit("°C")
.withValueMin(35)
.withValueMax(95)
.withValueStep(0.5)
.withDescription("Set the upper temperature limit"),
e
.numeric("temperature_correction", ea.STATE_SET)
.withUnit("°C")
.withValueMin(-9)
.withValueMax(9)
.withValueStep(1)
.withDescription("Temperature calibration adjustment."),
e
.numeric("switch_sensitivity", ea.STATE_SET)
.withUnit("°C")
.withValueMin(0.5)
.withValueMax(5.0)
.withValueStep(0.5)
.withDescription("Temperature difference threshold to trigger switching."),
e
.numeric("floor_temp_protection", ea.STATE_SET)
.withUnit("°C")
.withValueMin(5.0)
.withValueMax(60.0)
.withValueStep(0.5)
.withDescription("Maximum allowed floor temperature for protection."),
e
.numeric("floor_low_protection", ea.STATE_SET)
.withUnit("°C")
.withValueMin(10.0)
.withValueMax(30.0)
.withValueStep(0.5)
.withDescription("Minimum allowed floor temperature for protection."),
],
meta: {
tuyaDatapoints: [
[1, "system_mode", tuya.valueConverterBasic.lookup({ off: 0, heat: 1 })],
[2, "mode", tuya.valueConverterBasic.lookup({ manual: 0, program: 1 })],
[3, "working_status", tuya.valueConverterBasic.lookup({ "Keeping Warm": 0, Working: 1 })],
[8, "window_check", tuya.valueConverter.onOff],
[10, "frost_protection", tuya.valueConverter.onOff],
[16, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[19, "upper_temp", tuya.valueConverter.divideBy10],
[24, "local_temperature", tuya.valueConverter.divideBy10],
[25, "window_state", tuya.valueConverterBasic.lookup({ open: 1, close: 0 })],
[27, "temperature_correction", tuya.valueConverter.raw],
[34, "humidity", tuya.valueConverter.raw],
[39, "factory_reset", tuya.valueConverter.onOff],
[40, "child_lock", tuya.valueConverter.onOff],
[43, "sensor_choose", tuya.valueConverterBasic.lookup({ in: 0, out: 1 })],
[48, "week_schedule", tuya.valueConverter.raw],
[58, "run_mode", tuya.valueConverterBasic.lookup({ heat_mode: 1, cool_mode: 2 })],
[61, "week_program_periods", tuya.valueConverterBasic.lookup({ periods_4: "periods_4" })],
[101, "switch_sensitivity", tuya.valueConverter.divideBy10],
[102, "floor_temp_protection", tuya.valueConverter.divideBy10],
[103, "floor_low_protection", tuya.valueConverter.divideBy10],
[104, "window_open_detection_time", tuya.valueConverter.raw],
[105, "window_open_detection_temp", tuya.valueConverter.raw],
[106, "window_open_delay_time", tuya.valueConverter.raw],
[107, "humidity_control", tuya.valueConverter.onOff],
[108, "upper_humidity_limit", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_2ekuz3dz"]),
model: "X5H-GB-B",
vendor: "Tuya",
description: "Wall-mount thermostat",
fromZigbee: [legacy.fromZigbee.x5h_thermostat],
toZigbee: [legacy.toZigbee.x5h_thermostat],
whiteLabel: [
{ vendor: "Beok", model: "TGR85-ZB" },
{ vendor: "AVATTO", model: "ZWT-100-16A" },
],
exposes: [
e
.climate()
.withSetpoint("current_heating_setpoint", 5, 60, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withLocalTemperatureCalibration(-9.9, 9.9, 0.1, ea.STATE_SET)
.withSystemMode(["off", "heat"], ea.STATE_SET)
.withRunningState(["idle", "heat"], ea.STATE)
.withPreset(["manual", "program"]),
e.temperature_sensor_select(["internal", "external", "both"]),
e
.text("schedule", ea.STATE_SET)
.withDescription("There are 8 periods in the schedule in total. " +
"6 for workdays and 2 for holidays. It should be set in the following format for each of the periods: " +
"`hours:minutes/temperature`. All periods should be set at once and delimited by the space symbol. " +
"For example: `06:00/20.5 08:00/15 11:30/15 13:30/15 17:00/22 22:00/15 06:00/20 22:00/15`. " +
"The thermostat doesn't report the schedule by itself even if you change it manually from device"),
e.child_lock(),
e.week(),
e.enum("brightness_state", ea.STATE_SET, ["off", "low", "medium", "high"]).withDescription("Screen brightness"),
e.binary("sound", ea.STATE_SET, "ON", "OFF").withDescription("Switches beep sound when interacting with thermostat"),
e.binary("frost_protection", ea.STATE_SET, "ON", "OFF").withDescription("Antifreeze function"),
e.binary("factory_reset", ea.STATE_SET, "ON", "OFF").withDescription("Resets all settings to default. Doesn't unpair device."),
e
.numeric("heating_temp_limit", ea.STATE_SET)
.withUnit("°C")
.withValueMax(60)
.withValueMin(5)
.withValueStep(1)
.withPreset("default", 35, "Default value")
.withDescription("Heating temperature limit"),
e
.numeric("deadzone_temperature", ea.STATE_SET)
.withUnit("°C")
.withValueMax(9.5)
.withValueMin(0.5)
.withValueStep(0.5)
.withPreset("default", 1, "Default value")
.withDescription("The delta between local_temperature and current_heating_setpoint to trigger Heat"),
e
.numeric("upper_temp", ea.STATE_SET)
.withUnit("°C")
.withValueMax(95)
.withValueMin(35)
.withValueStep(1)
.withPreset("default", 60, "Default value"),
],
extend: [tuya.modernExtend.tuyaBase({ timeStart: "2000" })],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_agcxaw3f"]),
model: "BOT-R15W",
vendor: "Tuya",
description: "Beok wall thermostat (battery powered)",
extend: [
tuya.modernExtend.tuyaBase({
dp: true,
respondToMcuVersionResponse: true,
timeStart: "1970",
}),
],
exposes: [
e.child_lock(),
e
.climate()
.withSystemMode(["off", "heat"], ea.STATE_SET)
.withPreset(["manual", "auto", "mixed", "away"])
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withRunningState(["idle", "heat"], ea.STATE)
.withLocalTemperature(ea.STATE)
.withLocalTemperatureCalibration(-9.9, 9.9, 0.1, ea.STATE_SET),
e.binary("frost_protection", ea.STATE_SET, "ON", "OFF").withDescription("Antifreeze function"),
e
.max_temperature_limit()
.withUnit("°C")
.withValueMin(15)
.withValueMax(90)
.withValueStep(0.5)
.withPreset("default", 60, "Default value")
.withDescription("Maximum upper temperature"),
e
.numeric("temperature_delta", ea.STATE_SET)
.withUnit("°C")
.withValueMax(10)
.withValueMin(0.5)
.withValueStep(0.5)
.withPreset("default", 1, "Default value")
.withDescription("Delta between local temp and setpoint to trigger heat"),
e.binary("factory_reset", ea.STATE_SET, "ON", "OFF").withDescription("Full factory reset, use with caution!"),
e.battery(),
],
meta: {
tuyaDatapoints: [
[1, "system_mode", tuya.valueConverterBasic.lookup({ heat: true, off: false })],
[2, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[3, "local_temperature", tuya.valueConverter.divideBy10],
[4, "preset", tuya.valueConverterBasic.lookup({ manual: tuya.enum(0), auto: tuya.enum(1), mixed: tuya.enum(2), away: tuya.enum(3) })],
[9, "child_lock", tuya.valueConverter.lockUnlock],
[15, "max_temperature_limit", tuya.valueConverter.divideBy10],
[19, "local_temperature_calibration", tuya.valueConverter.divideBy10],
[101, "running_state", tuya.valueConverterBasic.lookup({ heat: tuya.enum(1), idle: tuya.enum(0) })],
[102, "frost_protection", tuya.valueConverter.onOff],
[103, "factory_reset", tuya.valueConverter.onOff],
[107, "temperature_delta", tuya.valueConverter.divideBy10],
[113, "battery", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE200_viy9ihs7",
"_TZE204_lzriup1j",
"_TZE204_xnbkhhdr",
"_TZE284_xnbkhhdr",
"_TZE204_oh8y8pv8",
"_TZE204_gops3slb",
"_TZE284_gops3slb",
"_TZE284_zjhoqbrd",
"_TZE204_zjhoqbrd",
]),
model: "ZWT198/ZWT100-BH",
vendor: "Tuya",
description: "Wall thermostat",
// Don't enable mcuVersionResponse
// https://github.com/Koenkk/zigbee2mqtt/issues/28455#issuecomment-3603696676
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "1970" })],
whiteLabel: [tuya.whitelabel("AVATTO", "WT-100-BH", "Wall thermostat", ["_TZE204_gops3slb", "_TZE284_gops3slb"])],
exposes: [
e.binary("factory_reset", ea.STATE_SET, "ON", "OFF").withDescription("Full factory reset, use with caution!"),
e.child_lock(),
e.temperature_sensor_select(["internal", "external", "both"]),
e
.climate()
.withSystemMode(["off", "heat"], ea.STATE_SET)
.withPreset(["manual", "auto", "temporary_manual"])
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withRunningState(["idle", "heat"], ea.STATE)
.withLocalTemperature(ea.STATE)
.withLocalTemperatureCalibration(-9.9, 9.9, 0.1, ea.STATE_SET),
e.binary("frost_protection", ea.STATE_SET, "ON", "OFF").withDescription("Antifreeze function"),
e
.max_temperature_limit()
.withUnit("°C")
.withValueMin(15)
.withValueMax(90)
.withValueStep(0.5)
.withPreset("default", 60, "Default value")
.withDescription("Maximum upper temperature"),
e
.numeric("deadzone_temperature", ea.STATE_SET)
.withUnit("°C")
.withValueMax(10)
.withValueMin(0.1)
.withValueStep(0.1)
.withPreset("default", 1, "Default value")
.withDescription("The delta between local_temperature (5<t<35)and current_heating_setpoint to trigger Heat"),
e.enum("backlight_mode", ea.STATE_SET, ["off", "low", "medium", "high"]).withDescription("Intensity of the backlight"),
e.enum("working_day", ea.STATE_SET, ["disabled", "6-1", "5-2", "7"]).withDescription("Workday setting"),
e.text("schedule_weekday", ea.STATE_SET).withDescription("Workdays (6 times `hh:mm/cc.c°C`)"),
e.text("schedule_holiday", ea.STATE_SET).withDescription("Holidays (2 times `hh:mm/cc.c°C)`"),
// ============== exposes for found, but not functional datapoints:
/*
e.min_temperature_limit()
.withValueMin(5)
.withValueMax(15)
.withValueStep(0.5)
.withPreset('default', 10, 'Default value')
.withDescription('dp16 is listed in Tuya, but no communication from device'),
e.binary('dp105', ea.STATE_SET, 'ON', 'OFF')
.withDescription('dp105 is not listed in Tuya, but device sends datapoint, binary: true/false'),
e.binary('dp111', ea.STATE_SET, 'ON', 'OFF')
.withDescription('dp111 is not listed in Tuya, but device sends datapoint, binary: true/false'),
*/
],
meta: {
tuyaDatapoints: [
[1, "system_mode", tuya.valueConverterBasic.lookup({ heat: true, off: false })],
[2, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[3, "local_temperature", tuya.valueConverter.divideBy10],
[
4,
"preset",
tuya.valueConverterBasic.lookup((_, device) => {
// https://github.com/Koenkk/zigbee2mqtt/issues/21353#issuecomment-1938328429
// https://github.com/Koenkk/zigbee2mqtt/issues/28035
if (device.manufacturerName === "_TZE204_lzriup1j" || device.manufacturerName === "_TZE204_gops3slb") {
return {
auto: tuya.enum(1),
manual: tuya.enum(0),
temporary_manual: tuya.enum(2),
};
}
return {
auto: tuya.enum(0),
manual: tuya.enum(1),
temporary_manual: tuya.enum(2),
};
}),
],
[9, "child_lock", tuya.valueConverter.lockUnlock],
[11, "faultalarm", tuya.valueConverter.raw],
[15, "max_temperature_limit", tuya.valueConverter.divideBy10],
[19, "local_temperature_calibration", tuya.valueConverter.divideBy10],
[
101,
"running_state",
tuya.valueConverterBasic.lookup({
heat: tuya.enum(1),
idle: tuya.enum(0),
}),
],
[102, "frost_protection", tuya.valueConverter.onOff],
[103, "factory_reset", tuya.valueConverter.onOff],
[
104,
"working_day",
tuya.valueConverterBasic.lookup((_, device) => {
// https://github.com/Koenkk/zigbee2mqtt/issues/23979
if (device.manufacturerName === "_TZE200_viy9ihs7") {
return {
disabled: tuya.enum(0),
"6-1": tuya.enum(1),
"5-2": tuya.enum(2),
"7": tuya.enum(3),
};
}
return {
disabled: tuya.enum(0),
"6-1": tuya.enum(2),
"5-2": tuya.enum(1),
"7": tuya.enum(3),
};
}),
],
[
106,
"sensor",
tuya.valueConverterBasic.lookup({
internal: tuya.enum(0),
external: tuya.enum(1),
both: tuya.enum(2),
}),
],
[107, "deadzone_temperature", tuya.valueConverter.divideBy10],
[109, null, tuya.valueConverter.ZWT198_schedule],
[109, "schedule_weekday", tuya.valueConverter.ZWT198_schedule],
[109, "schedule_holiday", tuya.valueConverter.ZWT198_schedule],
[110, "backlight_mode", tuya.valueConverter.backlightModeOffLowMediumHigh],
// ============== found but not functional datapoints:
// [16, 'min_temperature_limit', tuya.valueConverter.divideBy10], // datapoint listed in Tuya, but no communication from device
// [105, 'dp105', tuya.valueConverter.onOff], // not listed in Tuya, but device sends datapoint
// [111, 'dp111', tuya.valueConverter.onOff], // not listed in Tuya, but device sends datapoint
// These are the schedule values in bytes, 8 periods in total (4 bytes per period).
// For each period:
// 1st byte: hour
// 2nd byte: minute
// 3rd, 4th bytes: temperature multiplied by 10
// On the device last 2 periods are ignored if schedule_mode is 7day. When schedule_mode is disabled,
// scheduling can't be configured at all on the device.
// For example, if schedule_mode is weekday/sat+sun and this byte array is received:
// [6,10,1,144,8,10,0,170,11,40,0,170,12,40,0,170,17,10,0,230,22,10,0,170,8,5,0,200,23,0,0,160]
// Then the schedule is:
// Mon-Fri: 6:10 --> 40C, 8:10 --> 17C, 11:40 --> 17C, 12:40 --> 17C, 17:10 --> 23C, 22:10 --> 17C
// Sat-Sun: 8:05 --> 20C, 23:00 --> 16C
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_mwomyz5n", "_TZE204_cvub6xbb", "_TZE284_cvub6xbb"]),
model: "TGM50-ZB",
vendor: "Tuya",
description: "Beok wall thermostat",
extend: [tuya.modernExtend.tuyaBase({ dp: true, respondToMcuVersionResponse: true, timeStart: "1970" })],
exposes: [
e.child_lock(),
e.temperature_sensor_select(["internal", "external", "both"]),
e
.climate()
.withSystemMode(["off", "heat"], ea.STATE_SET)
.withPreset(["manual", "auto", "eco"])
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withRunningState(["idle", "heat"], ea.STATE)
.withLocalTemperature(ea.STATE)
.withLocalTemperatureCalibration(-9.9, 9.9, 0.1, ea.STATE_SET),
e.enum("backlight_mode", ea.STATE_SET, ["off", "always_low", "always_mid", "always_high"]).withDescription("Intensity of the backlight"),
e.binary("frost_protection", ea.STATE_SET, "ON", "OFF").withDescription("Antifreeze function"),
e
.max_temperature_limit()
.withUnit("°C")
.withValueMin(15)
.withValueMax(90)
.withValueStep(0.5)
.withPreset("default", 60, "Default value")
.withDescription("Maximum upper temperature"),
e
.numeric("temperature_delta", ea.STATE_SET)
.withUnit("°C")
.withValueMax(10)
.withValueMin(0.5)
.withValueStep(0.5)
.withPreset("default", 1, "Default value")
.withDescription("The delta between local_temperature and current_heating_setpoint to trigger Heat"),
e.binary("factory_reset", ea.STATE_SET, "ON", "OFF").withDescription("Full factory reset, use with caution!"),
],
meta: {
tuyaDatapoints: [
[1, "system_mode", tuya.valueConverterBasic.lookup({ heat: true, off: false })],
[2, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[3, "local_temperature", tuya.valueConverter.divideBy10],
[
4,
"preset",
tuya.valueConverterBasic.lookup({
manual: tuya.enum(0),
auto: tuya.enum(1),
eco: tuya.enum(3),
}),
],
[9, "child_lock", tuya.valueConverter.lockUnlock],
[15, "max_temperature_limit", tuya.valueConverter.divideBy10],
[19, "local_temperature_calibration", tuya.valueConverter.divideBy10],
[
101,
"running_state",
tuya.valueConverterBasic.lookup({
heat: tuya.enum(1),
idle: tuya.enum(0),
}),
],
[102, "frost_protection", tuya.valueConverter.onOff],
[103, "factory_reset", tuya.valueConverter.onOff],
[
106,
"sensor",
tuya.valueConverterBasic.lookup({
internal: tuya.enum(0),
external: tuya.enum(1),
both: tuya.enum(2),
}),
],
[107, "temperature_delta", tuya.valueConverter.divideBy10],
[
110,
"backlight_mode",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
always_low: tuya.enum(1),
always_mid: tuya.enum(2),
always_high: tuya.enum(3),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0222", ["_TZ3000_kky16aay", "_TZE204_myd45weu", "_TZ3000_ceplrhnu"]),
model: "TS0222_temperature_humidity",
vendor: "Tuya",
description: "Temperature & humidity sensor",
fromZigbee: [fzLocal.TS0222_humidity, fz.battery, fz.temperature],
toZigbee: [],
configure: tuya.configureMagicPacket,
exposes: [e.battery(), e.temperature(), e.humidity()],
whiteLabel: [tuya.whitelabel("Tuya", "QT-07S", "Soil sensor", ["_TZE204_myd45weu"])],
extend: [m.illuminance()],
},
{
fingerprint: tuya.fingerprint("TS0222", ["_TZ3000_8uxxzz4b", "_TZ3000_9kbbfeho", "_TZ3000_l6rsaipj"]),
model: "TS0222_light",
vendor: "Tuya",
description: "Light sensor",
fromZigbee: [fz.battery],
toZigbee: [],
configure: tuya.configureMagicPacket,
whiteLabel: [tuya.whitelabel("Moes", "ZSS-QT-LS-C", "Light sensor", ["_TZ3000_9kbbfeho"])],
exposes: [e.battery()],
extend: [m.illuminance()],
},
{
fingerprint: tuya.fingerprint("TS0222", ["_TZ3000_t9qqxn70"]),
model: "THE01860A",
vendor: "Tuya",
description: "Temp & humidity flower sensor with illuminance",
fromZigbee: [fz.humidity, fz.battery, fz.temperature],
toZigbee: [],
configure: tuya.configureMagicPacket,
exposes: [e.battery(), e.temperature(), e.humidity()],
extend: [m.illuminance()],
},
{
zigbeeModel: ["ZG-106Z"],
fingerprint: tuya.fingerprint("TS0222", [
"_TYZB01_4mdqxxnn",
"_TYZB01_m6ec2pgj",
"_TZ3000_do6txrcw",
"_TZ3000_7kscdesh",
"_TZ3000_hy6ncvmw",
"_TZ3000_7y90pany",
]),
model: "TS0222",
vendor: "Tuya",
description: "Light intensity sensor",
fromZigbee: [fz.battery, legacy.fromZigbee.TS0222],
toZigbee: [],
whiteLabel: [{ vendor: "HOBEIAN", model: "ZG-106Z", fingerprint: [{ modelID: "ZG-106Z" }] }],
exposes: [e.battery()],
configure: tuya.configureMagicPacket,
extend: [m.illuminance()],
},
{
fingerprint: tuya.fingerprint("TS0222", ["_TZ3000_j6adk9id"]),
model: "TS0222_solar_light",
vendor: "Tuya",
description: "Solar light sensor",
configure: tuya.configureMagicPacket,
extend: [m.illuminance(), m.battery()],
},
{
zigbeeModel: ["TS0210"],
model: "TS0210",
vendor: "Tuya",
description: "Vibration sensor",
whiteLabel: [
tuya.whitelabel("Niceboy", "ORBIS Vibration Sensor", "Vibration sensor", ["_TYZB01_821siati"]),
tuya.whitelabel("iHseno", "_TZ3000_lzdjjfss", "Vibration sensor", ["_TZ3000_lzdjjfss"]),
tuya.whitelabel("ONENUO", "TS0210_5oy7cysk", "Vibration sensor", ["'_TZ32101000000_5oy7cysk'"]),
tuya.whitelabel("EKAZA", "EKVZ-T1016", "Vibration sensor", ["_TZ3210_kjafhwd2"]),
],
fromZigbee: [fz.battery, fz.ias_vibration_alarm_1_with_timeout],
toZigbee: [tzLocal.TS0210_sensitivity],
exposes: [
e.battery(),
e.battery_voltage(),
e.vibration(),
e
.numeric("sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(50)
.withDescription("Sensitivity of the sensor (0 = highest sensitivity, 50 = lowest sensitivity). " +
"Press button on the device right before changing this"),
],
},
{
fingerprint: tuya.fingerprint("TS011F", ["_TZ3000_8bxrzyxz", "_TZ3210_vbfp8eyv"]),
model: "TS011F_din_smart_relay",
description: "Din smart relay (with power monitoring)",
vendor: "Tuya",
extend: [tuyaBase()],
fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, tuya.fz.power_outage_memory, tuya.fz.relay_din_led_indicator],
toZigbee: [tz.on_off, tuya.tz.power_on_behavior_1, tuya.tz.relay_din_led_indicator],
whiteLabel: [{ vendor: "MatSee Plus", model: "ATMS1602Z" }],
ota: true,
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff", "haElectricalMeasurement", "seMetering"]);
await reporting.rmsVoltage(endpoint, { change: 5 });
await reporting.rmsCurrent(endpoint, { change: 50 });
await reporting.activePower(endpoint, { change: 10 });
await reporting.currentSummDelivered(endpoint);
endpoint.saveClusterAttributeKeyValue("haElectricalMeasurement", {
acCurrentDivisor: 1000,
acCurrentMultiplier: 1,
});
endpoint.saveClusterAttributeKeyValue("seMetering", {
divisor: 100,
multiplier: 1,
});
device.save();
},
exposes: [
e.switch(),
e.power(),
e.current(),
e.voltage(),
e.energy(),
e.enum("power_outage_memory", ea.ALL, ["on", "off", "restore"]).withDescription("Recover state after power outage"),
e.enum("indicator_mode", ea.STATE_SET, ["off", "on_off", "off_on"]).withDescription("Relay LED indicator mode"),
],
},
{
fingerprint: tuya.fingerprint("TS011F", ["_TZ3000_qeuvnohg", "_TZ3000_6l1pjfqe", "_TZ3000_2iiimqs9", "_TZ3000_viqwamhn", "_TZ3000_ky0fq4ho"]),
model: "TS011F_din_smart_relay_polling",
description: "Din smart relay (with power monitoring via polling)",
vendor: "Tuya",
// Device reports weird temperature value
// https://github.com/Koenkk/zigbee2mqtt/issues/32125
fromZigbee: [
fz.on_off,
fz.electrical_measurement,
fz.metering,
tuya.fz.power_outage_memory,
tuya.fz.relay_din_led_indicator,
fz.ignore_temperature_report,
],
toZigbee: [tz.on_off, tuya.tz.power_on_behavior_1, tuya.tz.relay_din_led_indicator],
whiteLabel: [
tuya.whitelabel("Tongou", "TO-Q-SY1-JZT", "Din smart relay (with power monitoring via polling)", ["_TZ3000_qeuvnohg"]),
tuya.whitelabel("TOMZN", "TOB9Z-63M", "Din smart relay (with power monitoring via polling)", ["_TZ3000_6l1pjfqe"]),
tuya.whitelabel("Nous", "DZ", "DIN Switch (with power monitoring via polling)", ["_TZ3000_2iiimqs9"]),
],
ota: true,
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff", "haElectricalMeasurement", "seMetering"]);
await reporting.rmsVoltage(endpoint, { change: 5 });
await reporting.rmsCurrent(endpoint, { change: 50 });
await reporting.activePower(endpoint, { change: 10 });
await reporting.currentSummDelivered(endpoint);
endpoint.saveClusterAttributeKeyValue("haElectricalMeasurement", {
acCurrentDivisor: 1000,
acCurrentMultiplier: 1,
});
endpoint.saveClusterAttributeKeyValue("seMetering", {
divisor: 100,
multiplier: 1,
});
device.save();
},
exposes: [
e.switch(),
e.power(),
e.current(),
e.voltage(),
e.energy(),
e.enum("power_outage_memory", ea.ALL, ["on", "off", "restore"]).withDescription("Recover state after power outage"),
e.enum("indicator_mode", ea.STATE_SET, ["off", "on_off", "off_on"]).withDescription("Relay LED indicator mode"),
],
extend: [tuyaBase(), tuya.modernExtend.electricityMeasurementPoll()],
},
{
fingerprint: [...tuya.fingerprint("TS011F", ["_TZ3000_7issjl2q"]), ...tuya.fingerprint("TS0011", ["_TZ3000_gzvniqjb"])],
model: "ATMS1601Z",
description: "Din smart relay (without power monitoring)",
vendor: "Tuya",
extend: [tuyaBase()],
fromZigbee: [fz.on_off, tuya.fz.power_outage_memory, tuya.fz.relay_din_led_indicator],
toZigbee: [tz.on_off, tuya.tz.power_on_behavior_1, tuya.tz.relay_din_led_indicator],
whiteLabel: [tuya.whitelabel("Tongou", "TO-Q-SY1-ZT", "Din smart relay (without power monitoring)", ["_TZ3000_gzvniqjb"])],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff"]);
device.save();
},
exposes: [
e.switch(),
e.enum("power_outage_memory", ea.ALL, ["on", "off", "restore"]).withDescription("Recover state after power outage"),
e.enum("indicator_mode", ea.STATE_SET, ["off", "on_off", "off_on"]).withDescription("Relay LED indicator mode"),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_nklqjk62", "_TZE204_nklqjk62", "_TZE204_jktmrpoj", "_TZE284_nklqjk62"]),
model: "TS0601_garage_door_opener",
vendor: "Tuya",
description: "Garage door opener",
whiteLabel: [
tuya.whitelabel("MatSee Plus", "PJ-ZGD01", "Garage door opener", ["_TZE204_nklqjk62", "_TZE284_nklqjk62"]),
tuya.whitelabel("Moes", "ZM-102-M", "Garage door opener", ["_TZE204_jktmrpoj"]),
],
extend: [
tuyaMagicPacket(),
dpBinary({
name: "trigger",
dp: 1,
type: tuya.dataTypes.bool,
valueOn: [true, true],
valueOff: [false, false],
description: "Request door to close (= false) or open (= true), will not pulse output if contact shows door is already in requested state",
}),
dpNumeric({
name: "countdown",
dp: 2,
type: tuya.dataTypes.number,
description: "Countdown to trigger door movement after a certain time, will pulse output in all cases",
unit: "s",
valueMin: 0,
valueMax: 43200,
}),
dpBinary({
name: "garage_door_contact",
dp: 3,
type: tuya.dataTypes.bool,
valueOn: [true, false],
valueOff: [false, true],
description: "Indicates if the garage door contact is closed (= true) or open (= false)",
readOnly: true,
}),
dpNumeric({
name: "run_time",
dp: 4,
type: tuya.dataTypes.number,
description: "Configure the time to wait for the door contact status to change before triggering a run time alarm",
unit: "s",
valueMin: 0,
valueMax: 120,
}),
dpNumeric({
name: "open_alarm_time",
dp: 5,
type: tuya.dataTypes.number,
description: "Configure the amount of time the door may be open before an open time alarm is triggered",
unit: "s",
valueMin: 0,
valueMax: 86400,
}),
dpEnumLookup({
name: "status",
dp: 12,
type: tuya.dataTypes.enum,
description: "Indicates run time alarm, door open alarm or normal status, will not return to normal until door is triggered again",
lookup: { "Open Time Alarm": 0, "Run Time Alarm": 1, Normal: 2 },
readOnly: true,
}),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_wfxuhoea", "_TZE204_wfxuhoea"]),
model: "GDC311ZBQ1",
vendor: "Tuya",
description: "LoraTap garage door opener with wireless sensor",
fromZigbee: [legacy.fromZigbee.matsee_garage_door_opener],
toZigbee: [legacy.toZigbee.matsee_garage_door_opener, legacy.toZigbee.tuya_data_point_test],
whiteLabel: [{ vendor: "LoraTap", model: "GDC311ZBQ1" }],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genBasic"]);
},
exposes: [
e.binary("trigger", ea.STATE_SET, true, false).withDescription("Trigger the door movement"),
e
.binary("garage_door_contact", ea.STATE, false, true)
.withDescription("Indicates if the garage door contact is closed (= true) or open (= false)"),
],
},
{
fingerprint: tuya.fingerprint("TS0603", ["_TZE608_c75zqghm", "_TZE608_fmemczv1", "_TZE608_xkr8gep3", "_TZE608_lapuuoke"]),
model: "TS0603",
vendor: "Tuya",
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.raw],
[3, "garage_door_contact", tuya.valueConverter.trueFalseInvert],
[12, null, null],
],
},
description: "Garage door opener",
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genBasic"]);
},
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.binary("state", ea.STATE_SET, true, false).withDescription("Trigger the door movement"),
e
.binary("garage_door_contact", ea.STATE, true, false)
.withDescription("Indicates if the garage door contact is closed (= true) or open (= false)"),
],
},
{
fingerprint: tuya.fingerprint("TS0201", ["_TZ3000_qaaysllp"]),
model: "LCZ030",
vendor: "Tuya",
description: "Temperature & humidity & illuminance sensor with display",
fromZigbee: [fz.battery, fz.temperature, fz.humidity, fzLocal.ts0201_temperature_humidity_alarm],
toZigbee: [tzLocal.ts0201_temperature_humidity_alarm],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
// Enables reporting of measurement state changes
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(endpoint, coordinatorEndpoint, [
"genBasic",
"genPowerCfg",
"msTemperatureMeasurement",
"msRelativeHumidity",
"manuSpecificTuya2",
]);
},
exposes: [
e.temperature(),
e.humidity(),
e.battery(),
e
.numeric("alarm_temperature_max", ea.STATE_SET)
.withUnit("°C")
.withDescription("Alarm temperature max")
.withValueMin(-20)
.withValueMax(80),
e
.numeric("alarm_temperature_min", ea.STATE_SET)
.withUnit("°C")
.withDescription("Alarm temperature min")
.withValueMin(-20)
.withValueMax(80),
e.numeric("alarm_humidity_max", ea.STATE_SET).withUnit("%").withDescription("Alarm humidity max").withValueMin(0).withValueMax(100),
e.numeric("alarm_humidity_min", ea.STATE_SET).withUnit("%").withDescription("Alarm humidity min").withValueMin(0).withValueMax(100),
e.enum("alarm_humidity", ea.STATE, ["below_min_humdity", "over_humidity", "off"]).withDescription("Alarm humidity status"),
e.enum("alarm_temperature", ea.STATE, ["below_min_temperature", "over_temperature", "off"]).withDescription("Alarm temperature status"),
],
extend: [tuya.modernExtend.tuyaBase(), m.illuminance()],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_auin8mzr"]),
model: "TS0601_motion_sensor",
vendor: "Tuya",
description: "Human presence sensor AIR",
fromZigbee: [legacy.fromZigbee.tuya_motion_sensor],
toZigbee: [legacy.toZigbee.tuya_motion_sensor],
exposes: [
e.occupancy(),
e.enum("o_sensitivity", ea.STATE_SET, Object.values(legacy.msLookups.OSensitivity)).withDescription("O-Sensitivity mode"),
e.enum("v_sensitivity", ea.STATE_SET, Object.values(legacy.msLookups.VSensitivity)).withDescription("V-Sensitivity mode"),
e.enum("led_status", ea.STATE_SET, ["ON", "OFF"]).withDescription("Led status switch"),
e.numeric("vacancy_delay", ea.STATE_SET).withUnit("sec").withDescription("Vacancy delay").withValueMin(0).withValueMax(1000),
e.numeric("light_on_luminance_prefer", ea.STATE_SET).withDescription("Light-On luminance prefer").withValueMin(0).withValueMax(10000),
e.numeric("light_off_luminance_prefer", ea.STATE_SET).withDescription("Light-Off luminance prefer").withValueMin(0).withValueMax(10000),
e.enum("mode", ea.STATE_SET, Object.values(legacy.msLookups.Mode)).withDescription("Working mode"),
e.numeric("luminance_level", ea.STATE).withDescription("Luminance level"),
e.numeric("reference_luminance", ea.STATE).withDescription("Reference luminance"),
e.numeric("vacant_confirm_time", ea.STATE).withDescription("Vacant confirm time"),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_lu01t0zl", "_TZE200_vrfecyku", "_TZE200_ypprdwsl", "_TZE200_jkbljri7"]),
model: "MIR-HE200-TY",
vendor: "Tuya",
description: "Human presence sensor with fall function",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await tuya.sendDataPointEnum(endpoint, legacy.dataPoints.trsfTumbleSwitch, 0);
},
exposes: [
e.illuminance(),
e.presence(),
e.occupancy(),
e.numeric("motion_speed", ea.STATE).withDescription("Speed of movement"),
e
.enum("motion_direction", ea.STATE, ["standing_still", "moving_forward", "moving_backward"])
.withDescription("direction of movement from the point of view of the radar"),
e
.numeric("radar_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(1)
.withDescription("Sensitivity of the radar"),
e
.enum("radar_scene", ea.STATE_SET, ["default", "area", "toilet", "bedroom", "parlour", "office", "hotel"])
.withDescription("Presets for sensitivity for presence and movement"),
e.enum("tumble_switch", ea.STATE_SET, ["ON", "OFF"]).withDescription("Tumble status switch"),
e
.numeric("fall_sensitivity", ea.STATE_SET)
.withValueMin(1)
.withValueMax(10)
.withValueStep(1)
.withDescription("Fall sensitivity of the radar"),
e
.numeric("tumble_alarm_time", ea.STATE_SET)
.withValueMin(1)
.withValueMax(5)
.withValueStep(1)
.withUnit("min")
.withDescription("Tumble alarm time"),
e.enum("fall_down_status", ea.STATE, ["none", "maybe_fall", "fall"]).withDescription("Fall down status"),
e.text("static_dwell_alarm", ea.STATE).withDescription("Static dwell alarm"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse1],
[2, "radar_sensitivity", tuya.valueConverter.raw],
[102, "occupancy", tuya.valueConverter.trueFalse1],
[12, "illuminance", tuya.valueConverter.raw],
[103, "illuminance", tuya.valueConverter.raw],
[105, "tumble_switch", tuya.valueConverter.plus1],
[106, "tumble_alarm_time", tuya.valueConverter.raw],
[
112,
"radar_scene",
tuya.valueConverterBasic.lookup({
default: 0,
area: 1,
toilet: 2,
bedroom: 3,
parlour: 4,
office: 5,
hotel: 6,
}),
],
[
114,
"motion_direction",
tuya.valueConverterBasic.lookup({
standing_still: 0,
moving_forward: 1,
moving_backward: 2,
}),
],
[115, "motion_speed", tuya.valueConverter.raw],
[116, "fall_down_status", tuya.valueConverterBasic.lookup({ none: 0, maybe_fall: 1, fall: 2 })],
[117, "static_dwell_alarm", tuya.valueConverter.raw],
[118, "fall_sensitivity", tuya.valueConverter.raw],
// Below are ignored
[104, null, null], // detection_flag_code
[107, null, null], // radar_check_end_code
[108, null, null], // radar_check_start_code
[109, null, null], // hw_version_code
[110, null, null], // sw_version_code
[111, null, null], // radar_id_code
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_bvfld3xc"]),
model: "ZY-ZHPS01-24G",
vendor: "Tuya",
description: "24GHz mmWave human presence sensor",
extend: [
tuya.modernExtend.tuyaBase({ dp: true }),
m.poll({
key: "dis_current",
optionKey: "measurement_poll_interval",
defaultIntervalSeconds: 60,
option: exposes.options.measurement_poll_interval(),
poll: async (device) => {
await device.getEndpoint(1).command("manuSpecificTuya", "dataQuery", {});
},
}),
],
exposes: [
e.presence(),
e.illuminance(),
e.numeric("dis_current", ea.STATE).withUnit("cm").withDescription("Current detection distance"),
e.numeric("presence_delay", ea.STATE_SET).withUnit("s").withValueMin(1).withValueMax(600).withDescription("Presence delay"),
e.numeric("movesensitivity", ea.STATE_SET).withValueMin(0).withValueMax(10).withDescription("Move sensitivity"),
e.numeric("breathsensitivity", ea.STATE_SET).withValueMin(0).withValueMax(10).withDescription("Breath sensitivity"),
e
.numeric("movedistance_max", ea.STATE_SET)
.withUnit("cm")
.withValueMin(0)
.withValueMax(600)
.withDescription("Move detection max distance"),
e
.numeric("movedistance_min", ea.STATE_SET)
.withUnit("cm")
.withValueMin(0)
.withValueMax(600)
.withDescription("Move detection min distance"),
e
.numeric("breathdistance_max", ea.STATE_SET)
.withUnit("cm")
.withValueMin(0)
.withValueMax(600)
.withDescription("Breath detection max distance"),
e
.numeric("breathdistance_min", ea.STATE_SET)
.withUnit("cm")
.withValueMin(0)
.withValueMax(600)
.withDescription("Breath detection min distance"),
e.enum("self_learning", ea.STATE_SET, ["start", "stop"]).withDescription("Self learning mode"),
e.binary("restore_factory_setting", ea.STATE_SET, true, false).withDescription("Factory reset (behavior depends on firmware)"),
],
meta: {
tuyaDatapoints: [
[
101,
"presence",
{
from: (v) => {
// DP101 is inverted: [0]=presence detected, [1]=no presence
if (Buffer.isBuffer(v))
return v[0] === 0;
return v === 0;
},
},
],
[12, "illuminance", tuya.valueConverter.raw],
// DP102 requires periodic dataQuery to maintain reporting (see disCurrentPoll)
[102, "dis_current", { from: (v) => (Buffer.isBuffer(v) ? v.readUInt32BE(0) : v) }],
[103, "presence_delay", tuya.valueConverter.raw],
[105, "movesensitivity", tuya.valueConverter.raw],
[107, "breathsensitivity", tuya.valueConverter.raw],
[109, "movedistance_max", tuya.valueConverter.raw],
[110, "movedistance_min", tuya.valueConverter.raw],
[111, "breathdistance_max", tuya.valueConverter.raw],
[112, "breathdistance_min", tuya.valueConverter.raw],
[116, "self_learning", tuya.valueConverterBasic.lookup({ start: tuya.enum(0), stop: tuya.enum(1) })],
[117, "restore_factory_setting", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0045", ["_TZ3000_qfhhb5y4"]),
model: "TS0045",
vendor: "Tuya",
description: "Wireless switch with 5 buttons",
extend: [tuyaBase()],
fromZigbee: [tuya.fz.on_off_action, fz.battery],
exposes: [
e.battery(),
e.action([
"1_single",
"1_double",
"1_hold",
"2_single",
"2_double",
"2_hold",
"3_single",
"3_double",
"3_hold",
"4_single",
"4_double",
"4_hold",
"5_single",
"5_double",
"5_hold",
]),
],
toZigbee: [],
configure: tuya.configureMagicPacket,
},
{
zigbeeModel: ["TS0046"],
model: "TS0046",
vendor: "Tuya",
description: "Wireless switch with 6 buttons",
whiteLabel: [{ vendor: "LoraTap", model: "SS9600ZB" }],
extend: [tuyaBase()],
fromZigbee: [tuya.fz.on_off_action, fz.battery],
exposes: [
e.battery(),
e.action([
"1_single",
"1_double",
"1_hold",
"2_single",
"2_double",
"2_hold",
"3_single",
"3_double",
"3_hold",
"4_single",
"4_double",
"4_hold",
"5_single",
"5_double",
"5_hold",
"6_single",
"6_double",
"6_hold",
]),
],
toZigbee: [],
configure: tuya.configureMagicPacket,
},
{
fingerprint: tuya.fingerprint("TS004F", ["_TZ3000_g9g2xnch", "_TZ3000_pcqjmcud"]),
model: "YSR-MINI-Z",
vendor: "Tuya",
description: "2 in 1 dimming remote control and scene control",
exposes: [
e.battery(),
e.action([
"on",
"off",
"brightness_move_up",
"brightness_step_up",
"brightness_step_down",
"brightness_move_down",
"brightness_stop",
"color_temperature_step_down",
"color_temperature_step_up",
"1_single",
"1_double",
"1_hold",
"2_single",
"2_double",
"2_hold",
"3_single",
"3_double",
"3_hold",
"4_single",
"4_double",
"4_hold",
]),
e
.enum("operation_mode", ea.ALL, ["command", "event"])
.withDescription('Operation mode: "command" - for group control, "event" - for clicks'),
],
fromZigbee: [
fz.battery,
fz.command_on,
fz.command_off,
fz.command_step,
fz.command_move,
fz.command_stop,
fz.command_step_color_temperature,
tuya.fz.on_off_action,
tuya.fz.operation_mode,
],
toZigbee: [tuya.tz.operation_mode],
extend: [tuya.modernExtend.tuyaBase({ forceTimeUpdates: true })],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await endpoint.write("genOnOff", { tuyaOperationMode: 1 });
await endpoint.read("genOnOff", ["tuyaOperationMode"]);
try {
await endpoint.read(0xe001, [0xd011]);
}
catch {
/* do nothing */
}
await endpoint.read("genPowerCfg", ["batteryVoltage", "batteryPercentageRemaining"]);
await reporting.bind(endpoint, coordinatorEndpoint, ["genPowerCfg"]);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff"]);
await reporting.batteryPercentageRemaining(endpoint);
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_hkdl5fmv"]),
model: "TS0601_rcbo",
vendor: "Tuya",
whiteLabel: [
{ vendor: "HOCH", model: "ZJSBL7-100Z" },
{ vendor: "WDYK", model: "ZJSBL7-100Z" },
],
description: "DIN mount RCBO with smart energy metering",
fromZigbee: [legacy.fromZigbee.hoch_din],
toZigbee: [legacy.toZigbee.hoch_din],
exposes: [
e.text("meter_number", ea.STATE).withDescription("Meter number"),
e.binary("state", ea.STATE_SET, "ON", "OFF").withDescription("State"),
e.text("alarm", ea.STATE).withDescription("Alarm text"),
e.binary("trip", ea.STATE_SET, "trip", "clear").withDescription("Trip"),
e.binary("child_lock", ea.STATE_SET, "ON", "OFF").withDescription("Child lock"),
e.enum("power_on_behavior", ea.STATE_SET, ["off", "on", "previous"]).withDescription("Power on behavior"),
e.numeric("countdown_timer", ea.STATE_SET).withValueMin(0).withValueMax(86400).withUnit("s").withDescription("Countdown timer"),
e.numeric("voltage_rms", ea.STATE).withUnit("V").withDescription("Voltage RMS"),
e.numeric("current", ea.STATE).withUnit("A").withDescription("Current"),
e.numeric("current_average", ea.STATE).withUnit("A").withDescription("Current average"),
e.power(),
e.voltage(),
e.energy(),
e.temperature(),
e.numeric("power_l1", ea.STATE).withUnit("W").withDescription("Instantaneous measured power on phase 1"),
e.numeric("power_l2", ea.STATE).withUnit("W").withDescription("Instantaneous measured power on phase 2"),
e.numeric("power_l3", ea.STATE).withUnit("W").withDescription("Instantaneous measured power on phase 3"),
e.numeric("energy_consumed", ea.STATE).withUnit("kWh").withDescription("Consumed energy"),
e.enum("clear_device_data", ea.SET, [""]).withDescription("Clear device data"),
],
},
{
zigbeeModel: ["ZG-101ZD"],
fingerprint: tuya.fingerprint("TS004F", [
"_TZ3000_4fjiwweb",
"_TZ3000_uri7ongn",
"_TZ3000_ixla93vd",
"_TZ3000_qja6nq5z",
"_TZ3000_402vrq2i",
"_TZ3000_abrsvsou",
"_TZ3000_gwkzibhs",
"_TZ3000_ugi8ky6u",
"_TZ3000_1fqpj6qz",
]),
model: "ERS-10TZBVK-AA",
vendor: "Tuya",
description: "Smart knob",
extend: [tuya.modernExtend.tuyaBase()],
fromZigbee: [
fz.command_step,
fz.command_toggle,
fz.command_move_hue,
fz.command_step_color_temperature,
fzLocal.command_stop_move_raw,
tuya.fz.multi_action,
tuya.fz.operation_mode,
fz.battery,
],
whiteLabel: [
tuya.whitelabel("Tuya", "ZG-101Z_D_1", "Smart knob", ["_TZ3000_402vrq2i"]),
tuya.whitelabel("Moes", "ZG-101ZD", "Smart knob", ["_TZ3000_gwkzibhs"]),
tuya.whitelabel("Moes", "SYT-ZB01", "Smart knob", ["_TZ3000_1fqpj6qz"]),
tuya.whitelabel("Immax", "07768L", "NEO Smart rotary knob", ["_TZ3000_ugi8ky6u"]),
],
toZigbee: [tuya.tz.operation_mode],
exposes: [
e.action([
"toggle",
"brightness_step_up",
"brightness_step_down",
"color_temperature_step_up",
"color_temperature_step_down",
"saturation_move",
"hue_move",
"hue_stop",
"single",
"double",
"hold",
"rotate_left",
"rotate_right",
]),
e.numeric("action_brightness_delta", ea.STATE).withValueMin(-255).withValueMax(255),
e.numeric("action_step_size", ea.STATE).withValueMin(0).withValueMax(255),
e.numeric("action_color_temperature_delta", ea.STATE).withValueMin(-65535).withValueMax(65535),
e.numeric("action_transition_time", ea.STATE).withUnit("s"),
e.numeric("action_rate", ea.STATE).withValueMin(0).withValueMax(255),
e.battery(),
e
.enum("operation_mode", ea.ALL, ["command", "event"])
.withDescription('Operation mode: "command" - for group control, "event" - for clicks'),
],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await endpoint.read("genBasic", [0x0004, 0x000, 0x0001, 0x0005, 0x0007, 0xfffe]);
await endpoint.write("genOnOff", { tuyaOperationMode: 1 });
await endpoint.read("genOnOff", ["tuyaOperationMode"]);
try {
await endpoint.read(0xe001, [0xd011]);
}
catch {
/* do nothing */
}
await endpoint.read("genPowerCfg", ["batteryVoltage", "batteryPercentageRemaining"]);
await reporting.bind(endpoint, coordinatorEndpoint, ["genPowerCfg"]);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff"]);
await reporting.batteryPercentageRemaining(endpoint);
},
},
{
// Only the ones with applicationVersion 145 should be detected as this, e.g. applicationVersion 66 should be detected as ERS-10TZBVK-AA.
// https://github.com/Koenkk/zigbee2mqtt/issues/25053
fingerprint: [
{
modelID: "TS004F",
manufacturerName: "_TZ3000_abrsvsou",
applicationVersion: 145,
priority: 1,
},
],
model: "ZG-101Z/D",
vendor: "Tuya",
description: "Smart knob",
fromZigbee: [tuya.fz.multi_action, fz.battery, tuya.fz.operation_mode],
exposes: [
e.action(["rotate_left", "rotate_right"]),
e
.enum("operation_mode", ea.ALL, ["command", "event"])
.withDescription('Operation mode: "command" - for group control, "event" - for clicks'),
],
extend: [tuyaBase(), m.battery(), tuya.modernExtend.tuyaMagicPacket()],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_kzm5w4iz"]),
model: "TS0601_vibration_sensor",
vendor: "Tuya",
description: "Smart vibration sensor",
fromZigbee: [legacy.fromZigbee.tuya_smart_vibration_sensor],
toZigbee: [],
exposes: [e.contact(), e.battery(), e.vibration()],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_yi4jtqq1", "_TZE200_khx7nnka", "_TZE204_khx7nnka"]),
model: "XFY-CGQ-ZIGB",
vendor: "Tuya",
description: "Illuminance sensor",
fromZigbee: [legacy.fromZigbee.tuya_illuminance_sensor],
toZigbee: [],
exposes: [e.illuminance(), e.brightness_state()],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_kltffuzl", "_TZE200_fwoorn8y"]),
model: "TM001-ZA/TM081",
vendor: "Tuya",
description: "Door and window sensor",
fromZigbee: [legacy.fromZigbee.tm081],
toZigbee: [],
exposes: [e.contact(), e.battery()],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_2m38mh6k"]),
model: "SS9600ZB",
vendor: "Tuya",
description: "6 gang remote",
exposes: [
e.battery(),
e.action([
"1_single",
"1_double",
"1_hold",
"2_single",
"2_double",
"2_hold",
"3_single",
"3_double",
"3_hold",
"4_single",
"4_double",
"4_hold",
"5_single",
"5_double",
"5_hold",
"6_single",
"6_double",
"6_hold",
]),
],
fromZigbee: [legacy.fromZigbee.tuya_remote],
toZigbee: [],
},
{
zigbeeModel: ["TS0052"],
model: "TS0052",
vendor: "Tuya",
description: "Zigbee dimmer module 1 channel",
extend: [
tuyaLight({
powerOnBehavior: true,
configureReporting: true,
switchType: true,
minBrightness: "attribute",
}),
],
whiteLabel: [tuya.whitelabel("Tuya", "FS-05R", "Mini dimmable switch 1 channel", ["_TZ3000_mgusv51k"])],
},
{
fingerprint: tuya.fingerprint("TS0052", ["_TZ3000_zjtxnoft", "_TZ3000_kvwrdf47", "_TZ3000_sfibawtr"]),
model: "TS0052_2",
vendor: "Tuya",
description: "Zigbee dimmer module 2 channel",
extend: [
m.deviceEndpoints({ endpoints: { l1: 1, l2: 2 } }),
tuyaLight({
powerOnBehavior: true,
configureReporting: true,
switchType: true,
minBrightness: "attribute",
endpointNames: ["l1", "l2"],
}),
],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
},
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE200_ikvncluo",
"_TZE200_lyetpprm",
"_TZE200_jva8ink8",
"_TZE204_xpq2rzhq",
"_TZE200_holel4dk",
"_TZE200_xpq2rzhq",
"_TZE200_wukb7rhc",
"_TZE204_xsm7l9xa",
"_TZE204_ztc6ggyl",
"_TZE200_ztc6ggyl",
"_TZE200_sgpeacqp",
"_TZE204_fwondbzy",
"_TZE284_fwondbzy",
"_TZE284_xpq2rzhq",
]),
model: "TS0601_smart_human_presence_sensor_1",
vendor: "Tuya",
description: "Smart Human presence sensor",
fromZigbee: [legacy.fz.tuya_smart_human_presense_sensor],
toZigbee: [legacy.tz.tuya_smart_human_presense_sensor],
whiteLabel: [
tuya.whitelabel("Tuya", "ZY-M100-L", "Ceiling human breathe sensor", ["_TZE204_ztc6ggyl"]),
tuya.whitelabel("Moes", "ZSS-QY-HP", "Human presence sensor", ["_TZE204_fwondbzy", "_TZE284_fwondbzy", "_TZE284_xpq2rzhq"]),
],
exposes: [
e.illuminance(),
e.presence(),
e.numeric("target_distance", ea.STATE).withDescription("Distance to target").withUnit("m"),
e.numeric("radar_sensitivity", ea.STATE_SET).withValueMin(0).withValueMax(9).withValueStep(1).withDescription("sensitivity of the radar"),
e
.numeric("minimum_range", ea.STATE_SET)
.withValueMin(0)
.withValueMax(9.5)
.withValueStep(0.15)
.withDescription("Minimum range")
.withUnit("m"),
e
.numeric("maximum_range", ea.STATE_SET)
.withValueMin(0)
.withValueMax(9.5)
.withValueStep(0.15)
.withDescription("Maximum range")
.withUnit("m"),
e
.numeric("detection_delay", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(0.1)
.withDescription("Detection delay")
.withUnit("s"),
e.numeric("fading_time", ea.STATE_SET).withValueMin(0).withValueMax(1500).withValueStep(1).withDescription("Fading time").withUnit("s"),
// e.text('cli', ea.STATE).withDescription('not recognize'),
e
.enum("self_test", ea.STATE, Object.values(legacy.tuyaHPSCheckingResult))
.withDescription("Self_test, possible results: checking, check_success, check_failure, others, comm_fault, radar_fault."),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_atuj3i0w"]),
model: "M8Pro",
vendor: "Tuya",
description: "4 gang switch with LCD",
extend: [
tuyaWeatherForecast({
includeCurrentWeather: true,
numberOfForecastDays: 3,
correctForNegativeValues: true,
weatherConditionMap: tuya.M8ProTuyaWeatherCondition,
}),
tuyaBase({ dp: true, timeStart: "1970" }),
m.deviceEndpoints({ endpoints: { l1: 1, l2: 1, l3: 1, l4: 1 } }),
],
exposes: [
tuya.exposes.switch().withEndpoint("l1"),
tuya.exposes.switch().withEndpoint("l2"),
tuya.exposes.switch().withEndpoint("l3"),
tuya.exposes.switch().withEndpoint("l4"),
e.text("name", ea.STATE_SET).withEndpoint("l1").withDescription("Name for Switch 1"),
e.text("name", ea.STATE_SET).withEndpoint("l2").withDescription("Name for Switch 2"),
e.text("name", ea.STATE_SET).withEndpoint("l3").withDescription("Name for Switch 3"),
e.text("name", ea.STATE_SET).withEndpoint("l4").withDescription("Name for Switch 4"),
e.text("scene_name", ea.STATE_SET).withEndpoint("l1").withDescription("Name for Scene 1"),
e.text("scene_name", ea.STATE_SET).withEndpoint("l2").withDescription("Name for Scene 2"),
e.text("scene_name", ea.STATE_SET).withEndpoint("l3").withDescription("Name for Scene 3"),
e.text("scene_name", ea.STATE_SET).withEndpoint("l4").withDescription("Name for Scene 4"),
e.enum("mode", ea.STATE_SET, ["switch_1", "scene_1", "smart_light_1"]).withEndpoint("l1").withDescription("Switch1 mode"),
e.enum("mode", ea.STATE_SET, ["switch_1", "scene_1", "smart_light_1"]).withEndpoint("l2").withDescription("Switch2 mode"),
e.enum("mode", ea.STATE_SET, ["switch_1", "scene_1", "smart_light_1"]).withEndpoint("l3").withDescription("Switch3 mode"),
e.enum("mode", ea.STATE_SET, ["switch_1", "scene_1", "smart_light_1"]).withEndpoint("l4").withDescription("Switch4 mode"),
e.action(["scene_1", "scene_2", "scene_3", "scene_4"]),
e.binary("backlight", ea.ALL, "ON", "OFF").withDescription("Backlight"),
e.enum("indicator_switch", ea.STATE_SET, ["status", "switch_position", "off"]).withDescription("Indicator switch"),
e.binary("backlight_switch", ea.ALL, "ON", "OFF").withDescription("Backlight switch"),
e.numeric("temperature_1", ea.STATE_SET).withValueMin(-65).withValueMax(99).withDescription("Temperature"),
e.enum("condition_1", ea.STATE_SET, Object.keys(tuya.M8ProTuyaWeatherCondition)).withDescription("Weather condition"),
],
meta: {
tuyaDatapoints: [
[24, "state_l1", tuya.valueConverter.onOff],
[25, "state_l2", tuya.valueConverter.onOff],
[26, "state_l3", tuya.valueConverter.onOff],
[27, "state_l4", tuya.valueConverter.onOff],
[103, "name_l1", convLocal.name],
[104, "name_l2", convLocal.name],
[105, "name_l3", convLocal.name],
[106, "name_l4", convLocal.name],
[107, "scene_name_l1", convLocal.name],
[108, "scene_name_l2", convLocal.name],
[109, "scene_name_l3", convLocal.name],
[110, "scene_name_l4", convLocal.name],
[18, "mode_l1", tuya.valueConverterBasic.lookup({ switch_1: tuya.enum(0), scene_1: tuya.enum(1), smart_light_1: tuya.enum(2) })],
[19, "mode_l2", tuya.valueConverterBasic.lookup({ switch_1: tuya.enum(0), scene_1: tuya.enum(1), smart_light_1: tuya.enum(2) })],
[20, "mode_l3", tuya.valueConverterBasic.lookup({ switch_1: tuya.enum(0), scene_1: tuya.enum(1), smart_light_1: tuya.enum(2) })],
[21, "mode_l4", tuya.valueConverterBasic.lookup({ switch_1: tuya.enum(0), scene_1: tuya.enum(1), smart_light_1: tuya.enum(2) })],
[1, "action", tuya.valueConverter.static("scene_1")],
[2, "action", tuya.valueConverter.static("scene_2")],
[3, "action", tuya.valueConverter.static("scene_3")],
[4, "action", tuya.valueConverter.static("scene_4")],
[101, "backlight", tuya.valueConverter.onOff],
[36, "backlight_switch", tuya.valueConverter.onOff],
[37, "indicator_switch", tuya.valueConverterBasic.lookup({ status: tuya.enum(0), switch_position: tuya.enum(1), off: tuya.enum(2) })],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_bmdsp6bs"]),
model: "Y1_IN",
vendor: "Tuya",
description: "Smart human presence sensor 24G",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence(),
e.illuminance(),
e.numeric("keep_sensitivity", ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(1).withDescription("Keep sensitivity"),
e.numeric("trigger_sensitivity", ea.STATE_SET).withValueMin(1).withValueMax(20).withValueStep(1).withDescription("Trigger sensitivity"),
e
.numeric("fading_time", ea.STATE_SET)
.withValueMin(1)
.withValueMax(60)
.withValueStep(1)
.withDescription("Presence timeout")
.withUnit("s"),
e.numeric("target_distance", ea.STATE).withDescription("Distance to target").withUnit("m"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse1],
[102, "fading_time", tuya.valueConverter.raw],
[103, "illuminance", tuya.valueConverter.raw],
[110, "keep_sensitivity", tuya.valueConverter.raw],
[114, "trigger_sensitivity", tuya.valueConverter.raw],
[182, "target_distance", tuya.valueConverter.divideBy10],
[183, null, null],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_sxm7l9xa", "_TZE204_e5m9c5hl"]),
model: "ZY-M100-S_1",
vendor: "Tuya",
description: "Mini human breathe sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
whiteLabel: [tuya.whitelabel("Wenzhi", "WZ-M100-W", "Human presence sensor", ["_TZE204_e5m9c5hl"])],
exposes: [
e.illuminance(),
e.presence(),
e.numeric("target_distance", ea.STATE).withDescription("Distance to target").withUnit("m"),
e.numeric("radar_sensitivity", ea.STATE_SET).withValueMin(0).withValueMax(9).withValueStep(1).withDescription("sensitivity of the radar"),
e
.numeric("minimum_range", ea.STATE_SET)
.withValueMin(0)
.withValueMax(9.5)
.withValueStep(0.15)
.withDescription("Minimum range")
.withUnit("m"),
e
.numeric("maximum_range", ea.STATE_SET)
.withValueMin(0)
.withValueMax(9.5)
.withValueStep(0.15)
.withDescription("Maximum range")
.withUnit("m"),
e
.numeric("detection_delay", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(0.1)
.withDescription("Detection delay")
.withUnit("s"),
e.numeric("fading_time", ea.STATE_SET).withValueMin(0.5).withValueMax(1500).withValueStep(1).withDescription("Fading time").withUnit("s"),
],
meta: {
tuyaDatapoints: [
[104, "illuminance", tuya.valueConverter.raw],
[105, "presence", tuya.valueConverter.trueFalse1],
[106, "radar_sensitivity", tuya.valueConverter.raw],
[107, "maximum_range", tuya.valueConverter.divideBy100],
[108, "minimum_range", tuya.valueConverter.divideBy100],
[109, "target_distance", tuya.valueConverter.divideBy100],
[110, "fading_time", tuya.valueConverter.divideBy10],
[111, "detection_delay", tuya.valueConverter.divideBy10],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_qasjif9e", "_TZE200_qasjif9e", "_TZE204_ztqnh5cg", "_TZE204_iadro9bf", "_TZE284_iadro9bf"]),
model: "ZY-M100-S_2",
vendor: "Tuya",
description: "Mini human breathe sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.illuminance(),
e.presence(),
e.numeric("target_distance", ea.STATE).withDescription("Distance to target").withUnit("m"),
e.numeric("radar_sensitivity", ea.STATE_SET).withValueMin(0).withValueMax(9).withValueStep(1).withDescription("sensitivity of the radar"),
e
.numeric("minimum_range", ea.STATE_SET)
.withValueMin(0)
.withValueMax(9.5)
.withValueStep(0.15)
.withDescription("Minimum range")
.withUnit("m"),
e
.numeric("maximum_range", ea.STATE_SET)
.withValueMin(0)
.withValueMax(9.5)
.withValueStep(0.15)
.withDescription("Maximum range")
.withUnit("m"),
e
.numeric("detection_delay", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(0.1)
.withDescription("Detection delay")
.withUnit("s"),
e.numeric("fading_time", ea.STATE_SET).withValueMin(0.5).withValueMax(1500).withValueStep(1).withDescription("Fading time").withUnit("s"),
],
meta: {
tuyaDatapoints: [
[
1,
"presence",
{
from: (v, meta, options) => {
if (meta.device.manufacturerName === "_TZE284_iadro9bf") {
return tuya.valueConverter.trueFalse0.from(v);
}
return tuya.valueConverter.trueFalse1.from(v);
},
},
],
[9, "target_distance", tuya.valueConverter.divideBy100],
[12, "illuminance", tuya.valueConverter.raw], // _TZE284_iadro9bf
[104, "illuminance", tuya.valueConverter.raw],
[2, "radar_sensitivity", tuya.valueConverter.raw],
[4, "maximum_range", tuya.valueConverter.divideBy100],
[3, "minimum_range", tuya.valueConverter.divideBy100],
[102, "fading_time", tuya.valueConverter.divideBy10],
[101, "detection_delay", tuya.valueConverter.divideBy10],
],
},
whiteLabel: [tuya.whitelabel("iHseno", "TY_24G_Sensor_V2", "Human presence sensor 24G", ["_TZE204_ztqnh5cg"])],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_laokfqwu"]),
model: "WZ-M100",
vendor: "Wenzhi",
description: "Human presence sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.illuminance(),
e.presence(),
e
.numeric("target_distance", ea.STATE)
.withValueMin(0)
.withValueMax(10)
.withValueStep(0.01)
.withDescription("Distance to target")
.withUnit("m"),
e.numeric("sensitivity", ea.STATE_SET).withValueMin(1).withValueMax(9).withValueStep(1).withDescription("sensitivity of the radar"),
e
.numeric("minimum_range", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10.0)
.withValueStep(0.1)
.withDescription("minimum detection range")
.withUnit("m"),
e
.numeric("maximum_range", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10.0)
.withValueStep(0.1)
.withDescription("maximum detection range")
.withUnit("m"),
e
.numeric("interval_time", ea.STATE_SET)
.withValueMin(1)
.withValueMax(3600)
.withValueStep(1)
.withDescription("interval_time")
.withUnit("s"),
e
.numeric("detection_delay", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10.0)
.withValueStep(0.1)
.withDescription("detection delay")
.withUnit("s"),
e
.numeric("fading_time", ea.STATE_SET)
.withValueMax(1500)
.withValueMin(5)
.withValueStep(5)
.withDescription("presence timeout")
.withUnit("s"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse1],
[2, "sensitivity", tuya.valueConverter.raw],
[3, "minimum_range", tuya.valueConverter.divideBy100],
[4, "maximum_range", tuya.valueConverter.divideBy100],
[9, "target_distance", tuya.valueConverter.divideBy100],
[103, "illuminance", tuya.valueConverter.raw],
[104, "interval_time", tuya.valueConverter.raw],
[105, "detection_delay", tuya.valueConverter.divideBy10],
[106, "fading_time", tuya.valueConverter.divideBy10],
],
},
},
{
zigbeeModel: ["ZG-205ZL"],
fingerprint: [
{ modelID: "TS0225", manufacturerName: "_TZE200_hl0ss9oa" },
{ modelID: "CK-BL702-MWS-01(7016)", manufacturerName: "ZGAF-205L" },
{ modelID: "TS0225", manufacturerName: "_TZE200_y4mdop0b" },
],
model: "ZG-205ZL",
vendor: "Tuya",
description: "24Ghz/5.8GHz human presence sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence(),
e.enum("motion_state", ea.STATE, ["none", "large", "small", "static"]).withDescription("Motion state"),
e.illuminance(),
e
.numeric("fading_time", ea.STATE_SET)
.withValueMin(0)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Presence keep time"),
e
.numeric("large_motion_detection_distance", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(0.01)
.withUnit("m")
.withDescription("Large motion detection distance"),
e
.numeric("large_motion_detection_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(1)
.withDescription("Large motion detection sensitivity"),
e
.numeric("small_motion_detection_distance", ea.STATE_SET)
.withValueMin(0)
.withValueMax(6)
.withValueStep(0.01)
.withUnit("m")
.withDescription("Small motion detection distance"),
e
.numeric("small_motion_detection_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(1)
.withDescription("Small motion detection sensitivity"),
e
.numeric("static_detection_distance", ea.STATE_SET)
.withValueMin(0)
.withValueMax(6)
.withValueStep(0.01)
.withUnit("m")
.withDescription("Static detection distance"),
e
.numeric("static_detection_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(1)
.withDescription("Static detection sensitivity"),
e.enum("mode", ea.STATE_SET, ["off", "arm", "alarm", "doorbell"]).withDescription("Working mode"),
e.enum("alarm_volume", ea.STATE_SET, ["mute", "low", "medium", "high"]).withDescription("Alarm volume"),
e.numeric("alarm_time", ea.STATE_SET).withValueMin(1).withValueMax(60).withValueStep(1).withUnit("m").withDescription("Alarm time"),
e.binary("light_mode", ea.STATE_SET, "ON", "OFF").withDescription("LED indicator mode"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse1],
[20, "illuminance", tuya.valueConverter.raw],
[
11,
"motion_state",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
large: tuya.enum(1),
small: tuya.enum(2),
static: tuya.enum(3),
far: tuya.enum(4),
near: tuya.enum(5),
}),
],
[12, "fading_time", tuya.valueConverter.raw],
[13, "large_motion_detection_distance", tuya.valueConverter.divideBy100],
[15, "large_motion_detection_sensitivity", tuya.valueConverter.raw],
[14, "small_motion_detection_distance", tuya.valueConverter.divideBy100],
[16, "small_motion_detection_sensitivity", tuya.valueConverter.raw],
[103, "static_detection_distance", tuya.valueConverter.divideBy100],
[104, "static_detection_sensitivity", tuya.valueConverter.raw],
[
105,
"mode",
tuya.valueConverterBasic.lookup({
arm: tuya.enum(0),
off: tuya.enum(1),
alarm: tuya.enum(2),
doorbell: tuya.enum(3),
}),
],
[
102,
"alarm_volume",
tuya.valueConverterBasic.lookup({
low: tuya.enum(0),
medium: tuya.enum(1),
high: tuya.enum(2),
mute: tuya.enum(3),
}),
],
[101, "alarm_time", tuya.valueConverter.raw],
[24, "light_mode", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_whkgqxse"]),
model: "JM-TRH-ZGB-V1",
vendor: "Tuya",
description: "Temperature & humidity sensor with clock",
fromZigbee: [legacy.fromZigbee.nous_lcd_temperature_humidity_sensor, fz.ignore_tuya_set_time],
toZigbee: [legacy.toZigbee.nous_lcd_temperature_humidity_sensor],
extend: [tuya.modernExtend.tuyaBase({ forceTimeUpdates: true, bindBasicOnConfigure: true, timeStart: "1970" })],
exposes: [
e.temperature(),
e.humidity(),
e.battery(),
e
.numeric("temperature_report_interval", ea.STATE_SET)
.withUnit("min")
.withValueMin(5)
.withValueMax(60)
.withValueStep(5)
.withDescription("Temperature Report interval"),
e.enum("temperature_unit_convert", ea.STATE_SET, ["celsius", "fahrenheit"]).withDescription("Current display unit"),
e.enum("temperature_alarm", ea.STATE, ["canceled", "lower_alarm", "upper_alarm"]).withDescription("Temperature alarm status"),
e.numeric("max_temperature", ea.STATE_SET).withUnit("°C").withValueMin(-20).withValueMax(60).withDescription("Alarm temperature max"),
e.numeric("min_temperature", ea.STATE_SET).withUnit("°C").withValueMin(-20).withValueMax(60).withDescription("Alarm temperature min"),
e.enum("humidity_alarm", ea.STATE, ["canceled", "lower_alarm", "upper_alarm"]).withDescription("Humidity alarm status"),
e.numeric("max_humidity", ea.STATE_SET).withUnit("%").withValueMin(0).withValueMax(100).withDescription("Alarm humidity max"),
e.numeric("min_humidity", ea.STATE_SET).withUnit("%").withValueMin(0).withValueMax(100).withDescription("Alarm humidity min"),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_zyrdrmno", "_TZE200_osmxri8y"]),
model: "ZB-Sm",
vendor: "Tuya",
description: "Tubular motor",
fromZigbee: [legacy.fromZigbee.zb_sm_cover],
toZigbee: [legacy.toZigbee.zb_sm_cover],
extend: [tuya.modernExtend.tuyaBase()],
exposes: [
te.coverPosition(),
e.enum("goto_positon", ea.SET, ["25", "50", "75", "FAVORITE"]),
te.motorState(),
e.numeric("active_power", ea.STATE).withDescription("Active power").withUnit("mWt"),
e.numeric("cycle_count", ea.STATE).withDescription("Cycle count"),
e.numeric("cycle_time", ea.STATE).withDescription("Cycle time").withUnit("ms"),
e.enum("top_limit", ea.STATE_SET, ["SET", "CLEAR"]).withDescription("Setup or clear top limit"),
e.enum("bottom_limit", ea.STATE_SET, ["SET", "CLEAR"]).withDescription("Setup or clear bottom limit"),
e.numeric("favorite_position", ea.STATE_SET).withValueMin(0).withValueMax(100).withDescription("Favorite position of this cover"),
e.binary("reverse_direction", ea.STATE_SET, true, false).withDescription("Inverts the cover direction"),
e.text("motor_type", ea.STATE),
e.enum("report", ea.SET, [""]),
],
},
{
// These models are without battery. Add battery variants to WMUN ZS05.
zigbeeModel: ["TS1201"],
model: "ZS06",
vendor: "Tuya",
fingerprint: tuya.fingerprint("TS1201", [
"_TZ3290_rlkmy85q4pzoxobl",
"_TZ3290_gnl5a6a5xvql7c2a",
"_TZ3290_jxvzqatwgsaqzx1u",
"_TZ3290_lypnqvlem5eq1ree",
"_TZ3290_yac64inudpovoaba",
"_TZ3290_uc8lwbi2",
"_TZ3290_8xzb2ghn",
"_TZ3290_s6ezpa3j",
"_TZ3290_acv1iuslxi3shaaj",
]),
description: "Universal smart IR remote control",
extend: [zosung.zosungExtend.addZosungIRTransmitCluster(), zosung.zosungExtend.addZosungIRControlCluster()],
fromZigbee: [
fzZosung.zosung_send_ir_code_00,
fzZosung.zosung_send_ir_code_01,
fzZosung.zosung_send_ir_code_02,
fzZosung.zosung_send_ir_code_03,
fzZosung.zosung_send_ir_code_04,
fzZosung.zosung_send_ir_code_05,
],
toZigbee: [tzZosung.zosung_ir_code_to_send, tzZosung.zosung_learn_ir_code],
exposes: [ez.learn_ir_code(), ez.learned_ir_code(), ez.learned_ir_timings(), ez.ir_code_to_send(), ez.ir_emitter()],
whiteLabel: [
tuya.whitelabel("Tuya", "UFO-R4Z", "Universal smart IR remote control", ["_TZ3290_rlkmy85q4pzoxobl", "_TZ3290_gnl5a6a5xvql7c2a"]),
tuya.whitelabel("QA", "QAIRZPRO", "Infrared hub pro", ["_TZ3290_jxvzqatwgsaqzx1u", "_TZ3290_lypnqvlem5eq1ree"]),
tuya.whitelabel("QA", "QAIRZM2", "Zigbee smart IR remote control", ["_TZ3290_yac64inudpovoaba"]),
tuya.whitelabel("Zemismart", "ZM-18-USB", "Universal smart IR remote control", ["_TZ3290_uc8lwbi2"]),
tuya.whitelabel("Zemismart", "ZXMIR-02", "Universal smart IR remote control", ["_TZ3290_8xzb2ghn"]),
tuya.whitelabel("Ekaza", "EKAT-T304Z", "Universal smart IR remote control", ["_TZ3290_s6ezpa3j"]),
tuya.whitelabel("Aubess", "ZXZIR-02", "Universal smart IR remote control", ["_TZ3290_acv1iuslxi3shaaj"]),
],
},
{
fingerprint: tuya.fingerprint("TS0201", ["_TZ3000_itnrsufe"]),
model: "KCTW1Z",
vendor: "Tuya",
description: "Temperature & humidity sensor with LCD",
fromZigbee: [fz.temperature, fzLocal.humidity10, fzLocal.temperature_unit, fz.battery, fz.ignore_tuya_set_time],
toZigbee: [tzLocal.temperature_unit],
extend: [tuya.modernExtend.tuyaBase({ forceTimeUpdates: true })],
exposes: [
e.temperature(),
e.humidity(),
e.battery(),
e.battery_voltage(),
e.enum("temperature_unit", ea.STATE_SET, ["celsius", "fahrenheit"]).withDescription("Current display unit"),
],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genPowerCfg", "msTemperatureMeasurement", "msRelativeHumidity"]);
await endpoint.read("genPowerCfg", ["batteryVoltage", "batteryPercentageRemaining"]);
await reporting.batteryPercentageRemaining(endpoint);
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_0u3bj3rc", "_TZE200_v6ossqfy", "_TZE200_mx6u6l4y"]),
model: "TS0601_human_presence_sensor",
vendor: "Tuya",
description: "Human presence sensor Zigbee",
fromZigbee: [legacy.fromZigbee.hpsz],
toZigbee: [legacy.toZigbee.hpsz],
extend: [tuya.modernExtend.tuyaBase({ forceTimeUpdates: true })],
exposes: [
e.presence(),
e.numeric("duration_of_attendance", ea.STATE).withUnit("min").withDescription("Shows the presence duration in minutes"),
e.numeric("duration_of_absence", ea.STATE).withUnit("min").withDescription("Shows the duration of the absence in minutes"),
e.binary("led_state", ea.STATE_SET, true, false).withDescription("Turns the onboard LED on or off"),
],
},
{
zigbeeModel: ["ZG-227Z", "ZG-227ZL", "AY201Z"],
fingerprint: tuya.fingerprint("TS0601", [
"_TZE200_qoy0ekbd",
"_TZE200_znbl8dj5",
"_TZE200_a8sdabtg",
"_TZE200_dikkika5",
"_TZE200_vs0skpuc",
"_TZE200_3xfjp0ag",
"_TZE200_ehhrv2e3",
"_TZE200_lhqtjwax",
"_TZE200_y8wkaq6w",
]),
model: "ZG-227ZL",
vendor: "Tuya",
description: "Temperature & humidity LCD sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.temperature(),
e.humidity(),
tuya.exposes.temperatureUnit(),
tuya.exposes.temperatureCalibration(),
tuya.exposes.humidityCalibration(),
e.battery(),
],
whiteLabel: [
{
model: "ZG-227Z",
vendor: "HOBEIAN",
description: "Temperature & humidity sensor",
fingerprint: [
{ modelID: "ZG-227Z" },
{ manufacturerName: "_TZE200_a8sdabtg" },
{ manufacturerName: "_TZE200_vs0skpuc" },
{ manufacturerName: "_TZE200_ehhrv2e3" },
],
},
tuya.whitelabel("KOJIMA", "KOJIMA-THS-ZG-LCD", "Temperature and humidity sensor", ["_TZE200_dikkika5", "_TZE200_y8wkaq6w"]),
{
model: "AY201Z",
vendor: "AOYAN",
description: "Temperature & humidity LCD sensor",
fingerprint: [
{ modelID: "AY201Z" },
{ manufacturerName: "_TZE200_qoy0ekbd" },
{ manufacturerName: "_TZE200_znbl8dj5" },
{ manufacturerName: "_TZE200_a8sdabtg" },
{ manufacturerName: "_TZE200_dikkika5" },
{ manufacturerName: "_TZE200_vs0skpuc" },
{ manufacturerName: "_TZE200_3xfjp0ag" },
{ manufacturerName: "_TZE200_ehhrv2e3" },
{ manufacturerName: "_TZE200_lhqtjwax" },
{ manufacturerName: "_TZE200_y8wkaq6w" },
],
},
],
meta: {
tuyaDatapoints: [
[1, "temperature", tuya.valueConverter.divideBy10],
[2, "humidity", tuya.valueConverter.raw],
[4, "battery", tuya.valueConverter.raw],
[9, "temperature_unit", tuya.valueConverter.temperatureUnit],
[23, "temperature_calibration", tuya.valueConverter.divideBy10],
[24, "humidity_calibration", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_j7sgd8po"]),
model: "S8",
vendor: "SODA",
description: "S8 premium window handle",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.battery(),
e.battery_low(),
e.binary("vacation", ea.STATE_SET, "ON", "OFF").withDescription("Vacation mode"),
e.enum("alarm", ea.STATE, ["ALARM", "IDLE"]).withDescription("Alarm"),
e.binary("alarm_switch", ea.STATE_SET, "ON", "OFF").withDescription("Alarm enable"),
e.binary("handlesound", ea.STATE_SET, "ON", "OFF").withDescription("Handle closed sound"),
e.enum("opening_mode", ea.STATE, ["closed", "tilted"]).withDescription("Window tilt"),
e.temperature(),
e.humidity(),
e.binary("keysound", ea.STATE_SET, "ON", "OFF").withDescription("Key beep sound"),
e.enum("sensitivity", ea.STATE_SET, ["off", "low", "medium", "high", "max"]).withDescription("Sensitivity of the alarm sensor"),
e.enum("position", ea.STATE, ["up", "right", "down", "left"]),
e.enum("button_left", ea.STATE, ["released", "pressed"]),
e.enum("button_right", ea.STATE, ["released", "pressed"]),
e
.numeric("duration", ea.STATE_SET)
.withValueMin(0)
.withValueMax(300)
.withValueStep(1)
.withUnit("sec")
.withDescription("Duration of the alarm")
.withPreset("default", 180, "Default value"),
e
.numeric("update_frequency", ea.STATE_SET)
.withUnit("min")
.withDescription("Update frequency")
.withValueMin(0)
.withValueMax(700)
.withPreset("default", 20, "Default value"),
e.enum("calibrate", ea.STATE_SET, ["clear", "execute"]),
],
meta: {
tuyaDatapoints: [
[3, "battery", tuya.valueConverter.raw],
[8, "temperature", tuya.valueConverter.divideBy10],
[101, "humidity", tuya.valueConverter.raw],
[
102,
"alarm",
tuya.valueConverterBasic.lookup({
IDLE: tuya.enum(0),
ALARM: tuya.enum(1),
}),
],
[
103,
"opening_mode",
tuya.valueConverterBasic.lookup({
closed: tuya.enum(0),
tilted: tuya.enum(1),
}),
],
[
104,
"position",
tuya.valueConverterBasic.lookup({
left: tuya.enum(4),
up: tuya.enum(1),
down: tuya.enum(2),
right: tuya.enum(3),
}),
],
[
105,
"button_left",
tuya.valueConverterBasic.lookup({
released: tuya.enum(0),
pressed: tuya.enum(1),
}),
],
[
106,
"button_right",
tuya.valueConverterBasic.lookup({
released: tuya.enum(0),
pressed: tuya.enum(1),
}),
],
[
107,
"vacation",
tuya.valueConverterBasic.lookup({
OFF: tuya.enum(0),
ON: tuya.enum(1),
}),
],
[
108,
"sensitivity",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
low: tuya.enum(1),
medium: tuya.enum(2),
high: tuya.enum(3),
max: tuya.enum(4),
}),
],
[
109,
"alarm_switch",
tuya.valueConverterBasic.lookup({
OFF: tuya.enum(0),
ON: tuya.enum(1),
}),
],
[110, "update_frequency", tuya.valueConverter.raw],
[
111,
"keysound",
tuya.valueConverterBasic.lookup({
OFF: tuya.enum(0),
ON: tuya.enum(1),
}),
],
[112, "battery_low", tuya.valueConverter.trueFalse0],
[113, "duration", tuya.valueConverter.raw],
[
114,
"handlesound",
tuya.valueConverterBasic.lookup({
OFF: tuya.enum(0),
ON: tuya.enum(1),
}),
],
[
120,
"calibrate",
tuya.valueConverterBasic.lookup({
clear: tuya.enum(0),
execute: tuya.enum(1),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_ysm4dsb1"]),
model: "RSH-HS06",
vendor: "Tuya",
description: "Temperature and humidity sensor",
extend: [
tuya.modernExtend.tuyaBase({
dp: true,
queryOnDeviceAnnounce: true,
queryOnConfigure: true,
respondToMcuVersionResponse: true,
}),
],
exposes: [
e.temperature(),
e.humidity(),
tuya.exposes.temperatureUnit(),
tuya.exposes.temperatureCalibration(),
tuya.exposes.humidityCalibration(),
e.battery(),
],
meta: {
tuyaDatapoints: [
[1, "temperature", tuya.valueConverter.divideBy10],
[2, "humidity", tuya.valueConverter.raw],
[4, "battery", tuya.valueConverter.raw],
[9, "temperature_unit", tuya.valueConverter.temperatureUnit],
[23, "temperature_calibration", tuya.valueConverter.divideBy10],
[24, "humidity_calibration", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_n8dljorx"]),
model: "ZG-102Z",
vendor: "Tuya",
description: "Door sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [e.contact(), e.battery()],
meta: {
tuyaDatapoints: [
[1, "contact", tuya.valueConverter.inverse],
[2, "battery", tuya.valueConverter.raw],
],
},
},
{
zigbeeModel: ["ZG-102ZL"],
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_pay2byax", "_TZE200_ijey4q29", "_TZE200_ykglasuj", "_TZE200_kf2hbko4"]),
model: "ZG-102ZL",
vendor: "Tuya",
description: "Luminance door sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.contact(),
e.illuminance().withUnit("lx"),
e.battery(),
e
.numeric("illuminance_interval", ea.STATE_SET)
.withValueMin(1)
.withValueMax(720)
.withValueStep(1)
.withUnit("minutes")
.withDescription("Brightness acquisition interval (refresh and update only while active)"),
],
meta: {
tuyaDatapoints: [
[1, "contact", tuya.valueConverter.inverse],
[101, "illuminance", tuya.valueConverter.raw],
[2, "battery", tuya.valueConverter.raw],
[102, "illuminance_interval", tuya.valueConverter.raw],
],
},
},
{
fingerprint: [
...tuya.fingerprint("TS0601", ["_TZE200_f1pvdgoh", "_TZE200_me6wtiqs", "_TZE200_ghynnvos"]),
...tuya.fingerprint("\u0000B", ["_TZE200_f1pvdgoh"]),
],
model: "TS0601_pir",
vendor: "Tuya",
description: "Haozee PIR sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true, respondToMcuVersionResponse: true })],
exposes: (device, options) => {
const exposes = [e.occupancy(), e.illuminance()];
// https://github.com/Koenkk/zigbee2mqtt.io/pull/4018
if (device.manufacturerName !== "_TZE200_ghynnvos") {
exposes.push(e.battery());
}
return exposes;
},
whiteLabel: [
tuya.whitelabel("Immax", "07527L", "NEO Smart PIR sensor 2v1 Zigbee 3.0", ["_TZE200_me6wtiqs"]),
tuya.whitelabel("Conecto", "COZIGPMS", "PIR Motion Sensor", ["_TZE200_ghynnvos"]),
],
meta: {
tuyaDatapoints: [
[1, "occupancy", tuya.valueConverter.trueFalse0],
[4, "battery", tuya.valueConverter.raw],
[101, "illuminance", tuya.valueConverter.raw],
],
},
},
{
zigbeeModel: ["ZG-225Z"],
fingerprint: [...tuya.fingerprint("TS0601", ["_TZE200_8isdky6j"]), ...tuya.fingerprint("TS0225", ["_TZE200_p6fuhvez", "_TZE200_aj0oxo1i"])],
model: "ZG-225Z",
vendor: "Tuya",
description: "Gas sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.gas(),
tuya.exposes.gasValue().withUnit("ppm"),
e.enum("sensitivity", ea.STATE_SET, ["low", "medium", "high"]).withDescription("Gas sensor sensitivity"),
e.enum("ring", ea.STATE_SET, ["ring1", "ring2"]).withDescription("Ring"),
],
meta: {
tuyaDatapoints: [
[1, "gas", tuya.valueConverter.trueFalse0],
[2, "gas_value", tuya.valueConverter.raw],
[
101,
"sensitivity",
tuya.valueConverterBasic.lookup({
low: tuya.enum(0),
medium: tuya.enum(1),
high: tuya.enum(2),
}),
],
[
6,
"ring",
tuya.valueConverterBasic.lookup({
ring1: tuya.enum(0),
ring2: tuya.enum(1),
}),
],
],
},
},
{
zigbeeModel: ["ZG-204ZL"],
fingerprint: tuya.fingerprint("TS0601", [
"_TZE200_3towulqd",
"_TZE200_1ibpyhdc",
"_TZE200_bh3n6gk8",
"_TZE200_ttcovulf",
"_TZE200_gjldowol",
"_TZE200_s6hzw8g2",
"_TZE200_jxyhl4eq",
"_TZE200_qxyh4r7g",
"_TZE200_na5qlzow",
]),
model: "ZG-204ZL",
vendor: "Tuya",
description: "Luminance motion sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.occupancy(),
e.illuminance().withUnit("lx"),
e.battery(),
e
.enum("sensitivity", ea.STATE_SET, ["low", "medium", "high"])
.withDescription("PIR sensor sensitivity (refresh and update only while active)"),
e
.enum("keep_time", ea.STATE_SET, ["10", "30", "60", "120"])
.withDescription("PIR keep time in seconds (refresh and update only while active)"),
e
.numeric("illuminance_interval", ea.STATE_SET)
.withValueMin(1)
.withValueMax(720)
.withValueStep(1)
.withUnit("minutes")
.withDescription("Brightness acquisition interval (refresh and update only while active)"),
],
meta: {
tuyaDatapoints: [
[1, "occupancy", tuya.valueConverter.trueFalse0],
[4, "battery", tuya.valueConverter.raw],
[
9,
"sensitivity",
tuya.valueConverterBasic.lookup({
low: tuya.enum(0),
medium: tuya.enum(1),
high: tuya.enum(2),
}),
],
[
10,
"keep_time",
tuya.valueConverterBasic.lookup({
"10": tuya.enum(0),
"30": tuya.enum(1),
"60": tuya.enum(2),
"120": tuya.enum(3),
}),
],
[12, "illuminance", tuya.valueConverter.raw],
[101, "illuminance", tuya.valueConverter.raw], // For _TZE200_s6hzw8g2
[102, "illuminance_interval", tuya.valueConverter.raw],
],
},
whiteLabel: [tuya.whitelabel("Nedis", "ZBSM20WT", "Nedis motion sensor", ["_TZE200_s6hzw8g2"])],
},
{
fingerprint: [
{ modelID: "AY-204Z", manufacturerName: "AOYAN" },
{ modelID: "AY-204Z", manufacturerName: "AOYAN " },
{ modelID: "AY-204Z", manufacturerName: "AOYAN " },
],
model: "AY-204Z",
vendor: "AOYAN",
description: "PIR motion sensor",
extend: [
tuya.modernExtend.tuyaBase({ dp: true }),
m.iasZoneAlarm({
zoneType: "occupancy",
zoneAttributes: ["alarm_1", "tamper", "battery_low"],
}),
m.battery(),
],
exposes: [
e
.enum("sensitivity", ea.STATE_SET, ["low", "medium", "high"])
.withDescription("PIR sensor sensitivity (refresh and update only while active)"),
e
.enum("keep_time", ea.STATE_SET, ["10", "30", "60", "120"])
.withDescription("PIR keep time in seconds (refresh and update only while active)"),
],
meta: {
tuyaDatapoints: [
[
9,
"sensitivity",
tuya.valueConverterBasic.lookup({
low: tuya.enum(0),
medium: tuya.enum(1),
high: tuya.enum(2),
}),
],
[
10,
"keep_time",
tuya.valueConverterBasic.lookup({
"10": tuya.enum(0),
"30": tuya.enum(1),
"60": tuya.enum(2),
"120": tuya.enum(3),
}),
],
],
},
},
{
fingerprint: [
...tuya.fingerprint("TS0225", ["_TZE200_2aaelwxk", "_TZE200_crq3r3la"]),
...tuya.fingerprint("CK-BL702-MWS-01(7016)", ["HOBEIAN", "_TZE200_crq3r3la"]),
],
model: "ZG-205Z/A",
vendor: "Tuya",
description: "5.8Ghz/24Ghz Human presence sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence(),
e.enum("motion_state", ea.STATE, ["none", "small", "medium", "large", "far", "near"]).withDescription("State of the motion"),
e.numeric("target_distance", ea.STATE).withDescription("Distance to target").withUnit("m"),
e.illuminance().withUnit("lx"),
e
.numeric("large_motion_detection_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(1)
.withDescription("Motion detection sensitivity"),
e
.numeric("large_motion_detection_distance", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(0.01)
.withUnit("m")
.withDescription("Motion detection distance"),
e
.numeric("fading_time", ea.STATE_SET)
.withValueMin(0)
.withValueMax(28800)
.withValueStep(1)
.withUnit("s")
.withDescription("For how much time presence should stay true after detecting it"),
e
.numeric("medium_motion_detection_distance", ea.STATE_SET)
.withValueMin(0)
.withValueMax(6)
.withValueStep(0.01)
.withUnit("m")
.withDescription("Medium motion detection distance"),
e
.numeric("medium_motion_detection_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(1)
.withDescription("Medium motion detection sensitivity"),
e.binary("indicator", ea.STATE_SET, "ON", "OFF").withDescription("LED Indicator"),
e
.numeric("small_detection_distance", ea.STATE_SET)
.withValueMin(0)
.withValueMax(6)
.withValueStep(0.01)
.withUnit("m")
.withDescription("Small detection distance"),
e
.numeric("small_detection_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(1)
.withDescription("Small detection sensitivity"),
e
.numeric("minimum_range", ea.STATE_SET)
.withValueMin(0)
.withValueMax(6)
.withValueStep(0.01)
.withUnit("m")
.withDescription("Minimum range"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse1],
[2, "large_motion_detection_sensitivity", tuya.valueConverter.raw],
[4, "large_motion_detection_distance", tuya.valueConverter.divideBy100],
[
101,
"motion_state",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
large: tuya.enum(1),
medium: tuya.enum(2),
small: tuya.enum(3),
far: tuya.enum(4),
near: tuya.enum(5),
}),
],
[102, "fading_time", tuya.valueConverter.raw],
[104, "medium_motion_detection_distance", tuya.valueConverter.divideBy100],
[105, "medium_motion_detection_sensitivity", tuya.valueConverter.raw],
[106, "illuminance", tuya.valueConverter.raw],
[107, "indicator", tuya.valueConverter.onOff],
[108, "small_detection_distance", tuya.valueConverter.divideBy100],
[109, "small_detection_sensitivity", tuya.valueConverter.raw],
[122, "target_distance", tuya.valueConverter.divideBy100],
[123, "minimum_range", tuya.valueConverter.divideBy100],
// Not exposed DPs/untested
// [103, 'motion_false_detection', tuya.valueConverter.raw],
// [113, 'breathe_false_detection', tuya.valueConverter.raw],
// [3, 'mov_minimum_distance', tuya.valueConverter.raw],
// [110, 'micro_minimum_distance', tuya.valueConverter.raw],
// [111, 'motionless_minimum_distance', tuya.valueConverter.raw],
// [112, 'reset_setting', tuya.valueConverter.raw],
// [114, 'time', tuya.valueConverter.raw],
// [115, 'alarm_time', tuya.valueConverter.raw],
// [116, 'alarm_volume', tuya.valueConverterBasic.lookup(
// {'low': tuya.enum(0), 'medium': tuya.enum(1), 'high': tuya.enum(2), 'mute': tuya.enum(3)})],
// [117, 'working_mode', tuya.valueConverterBasic.lookup(
// {'arm': tuya.enum(0), 'off': tuya.enum(1), 'alarm': tuya.enum(2), 'doorbell': tuya.enum(3)})],
// [118, 'auto1', tuya.valueConverter.raw],
// [119, 'auto2', tuya.valueConverter.raw],
// [120, 'auto3', tuya.valueConverter.raw],
],
},
},
{
zigbeeModel: ["ZG-204ZM", "AY205Z"],
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_2aaelwxk", "_TZE200_kb5noeto", "_TZE200_tyffvoij", "_TZE200_yflzeeqj"]),
model: "ZG-204ZM",
vendor: "Tuya",
description: "PIR 24Ghz human presence sensor",
extend: [
tuya.modernExtend.tuyaBase({ dp: true }),
// Besides dp, also uses standard illuminance cluster
// https://github.com/Koenkk/zigbee-herdsman-converters/issues/10897
m.illuminance({ reporting: false }),
],
exposes: [
e.presence(),
e.enum("motion_state", ea.STATE, ["none", "large", "small", "static"]).withDescription("Motion state"),
e.battery(),
e
.numeric("fading_time", ea.STATE_SET)
.withValueMin(0)
.withValueMax(28800)
.withValueStep(1)
.withUnit("s")
.withDescription("Presence keep time"),
e
.numeric("static_detection_distance", ea.STATE_SET)
.withValueMin(0)
.withValueMax(6)
.withValueStep(0.01)
.withUnit("m")
.withDescription("Static detection distance"),
e
.numeric("static_detection_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(1)
.withDescription("Static detection sensitivity"),
e.binary("indicator", ea.STATE_SET, "ON", "OFF").withDescription("LED indicator mode"),
e
.enum("motion_detection_mode", ea.STATE_SET, ["only_pir", "pir_and_radar", "only_radar"])
.withDescription("Motion detection mode (Firmware version>=0122052017)"),
e
.numeric("motion_detection_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(1)
.withDescription("Motion detection sensitivity (Firmware version>=0122052017)"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse1],
[2, "static_detection_sensitivity", tuya.valueConverter.raw],
[4, "static_detection_distance", tuya.valueConverter.divideBy100],
[
101,
"motion_state",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
large: tuya.enum(1),
small: tuya.enum(2),
static: tuya.enum(3),
}),
],
[102, "fading_time", tuya.valueConverter.raw],
[106, "illuminance", tuya.valueConverter.raw],
[107, "indicator", tuya.valueConverter.onOff],
[121, "battery", tuya.valueConverter.raw],
[
122,
"motion_detection_mode",
tuya.valueConverterBasic.lookup({
only_pir: tuya.enum(0),
pir_and_radar: tuya.enum(1),
only_radar: tuya.enum(2),
}),
],
[123, "motion_detection_sensitivity", tuya.valueConverter.raw],
],
},
whiteLabel: [
{
model: "AY205Z",
vendor: "AOYAN",
description: "PIR 24Ghz human presence sensor",
fingerprint: [{ modelID: "AY205Z", manufacturerName: "AOYAN" }, { manufacturerName: "AOYAN" }],
},
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_bw4ayyeh", "_TZE2841000000_bw4ayyeh"]),
model: "ZD24_Presence_Sensor",
vendor: "Tuya",
description: "PIR 24GHz human presence sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence(),
e.illuminance(),
e.battery(),
e.enum("motion_state", ea.STATE, ["none", "static", "small", "large"]).withDescription("Human body motion state"),
e.numeric("distance", ea.STATE).withUnit("m").withDescription("Target distance"),
e.binary("init", ea.STATE_SET, "ON", "OFF").withDescription("Initialize"),
e
.numeric("fading_time", ea.STATE_SET)
.withValueMin(10)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Presence keep time"),
e
.numeric("motion_detection_sensitivity", ea.STATE_SET)
.withValueMin(1)
.withValueMax(10)
.withValueStep(1)
.withDescription("Motion detection sensitivity"),
e
.numeric("static_detection_sensitivity", ea.STATE_SET)
.withValueMin(1)
.withValueMax(10)
.withValueStep(1)
.withDescription("Static detection sensitivity"),
e.enum("motion_detection_mode", ea.STATE_SET, ["pir_and_radar", "only_radar", "pir_or_radar"]).withDescription("Motion detection mode"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse1],
[4, "distance", tuya.valueConverter.raw],
[
11,
"motion_state",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
static: tuya.enum(1),
small: tuya.enum(2),
large: tuya.enum(3),
}),
],
[12, "fading_time", tuya.valueConverter.raw],
[15, "motion_detection_sensitivity", tuya.valueConverter.raw],
[16, "static_detection_sensitivity", tuya.valueConverter.raw],
[20, "illuminance", tuya.valueConverter.raw],
[81, "battery", tuya.valueConverter.raw],
[101, "init", tuya.valueConverter.onOff],
[102, "motion_detection_mode", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_juzago6i"]),
model: "TS0601-PIR-Sensor",
vendor: "Tuya",
description: "PIR 24GHz human presence sensor",
extend: [m.battery(), tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence(),
e.illuminance(),
e.numeric("detection_distance", ea.STATE_SET).withValueMin(1).withValueMax(6).withUnit("m").withDescription("Maximum detection distance"),
e
.numeric("fading_time", ea.STATE_SET)
.withValueMin(10)
.withValueMax(180)
.withValueStep(10)
.withUnit("s")
.withDescription("Presence keep time"),
e.enum("last_time", ea.STATE, ["pir", "none"]).withDescription("Last trigger type"),
e.numeric("static_detection_sensitivity", ea.STATE_SET).withValueMin(1).withValueMax(10).withDescription("Static detection sensitivity"),
e.numeric("motion_detection_sensitivity", ea.STATE_SET).withValueMin(1).withValueMax(10).withDescription("Motion detection sensitivity"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse0],
[4, "battery", tuya.valueConverter.raw],
[12, "illuminance", tuya.valueConverter.raw],
[101, "detection_distance", tuya.valueConverter.raw],
[103, "fading_time", tuya.valueConverter.raw],
[104, "last_time", tuya.valueConverterBasic.lookup({ pir: 0, none: 1 })],
[105, "static_detection_sensitivity", tuya.valueConverter.raw],
[106, "motion_detection_sensitivity", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS110E", ["_TZ3210_zxbtub8r", "_TZ3210_cyuyd5az"]),
model: "TS110E_1gang_1",
vendor: "Tuya",
description: "1 channel dimmer",
extend: [tuyaBase(), m.light({ powerOnBehavior: false, configureReporting: true, effect: false }), tuya.modernExtend.tuyaMagicPacket()],
fromZigbee: [tuya.fz.power_on_behavior_1, tuya.fz.TS110E_switch_type, tuya.fz.TS110E, fz.on_off],
toZigbee: [tuya.tz.TS110E_light_onoff_brightness, tuya.tz.power_on_behavior_1, tuya.tz.TS110E_options],
exposes: [e.power_on_behavior(), tuya.exposes.switchType(), e.min_brightness(), e.max_brightness()],
configure: tuya.configureMagicPacket,
},
{
fingerprint: tuya.fingerprint("TS110E", ["_TZ3210_ngqk6jia", "_TZ3210_weaqkhab", "_TZ3210_k1msuvg6", "_TZ3210_o235agwx"]),
model: "TS110E_1gang_2",
vendor: "Tuya",
description: "1 channel dimmer",
whiteLabel: [
tuya.whitelabel("Lonsonho", "QS-Zigbee-D02-TRIAC-L_1", "1 channel dimmer", ["_TZ3210_weaqkhab"]),
tuya.whitelabel("Lonsonho", "QS-Zigbee-D02-TRIAC-LN_1", "1 channel dimmer", ["_TZ3210_ngqk6jia"]),
],
ota: true,
fromZigbee: [tuya.fz.TS110E, tuya.fz.TS110E_light_type, tuya.fz.power_on_behavior_1, fz.on_off],
toZigbee: [
tuya.tz.TS110E_onoff_brightness,
tuya.tz.TS110E_options,
tuya.tz.power_on_behavior_1,
tz.light_brightness_move,
tzLocal.ts110eCountdown,
],
exposes: (device, options) => {
// https://github.com/Koenkk/zigbee2mqtt/issues/26791#issuecomment-2765734859
const countdownValueStep = device.manufacturerName === "_TZ3210_ngqk6jia" ? 30 : 1;
return [
e.light_brightness().withMinBrightness().withMaxBrightness(),
tuya.exposes.lightType().withAccess(ea.ALL),
e.power_on_behavior().withAccess(ea.ALL),
tuya.exposes.switchType(),
tuya.exposes.countdown().withValueStep(countdownValueStep),
];
},
extend: [tuyaBase()],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff", "genLevelCtrl"]);
await reporting.onOff(endpoint);
},
},
{
fingerprint: tuya.fingerprint("TS110E", ["_TZ3210_ysfo0wla"]),
model: "EKAC-T3095Z",
vendor: "Ekaza",
description: "1 channel dimmer",
extend: [tuyaBase()],
fromZigbee: [tuya.fz.TS110E, tuya.fz.power_on_behavior_1, fz.on_off],
toZigbee: [tuya.tz.TS110E_onoff_brightness, tuya.tz.TS110E_options, tuya.tz.power_on_behavior_1, tz.light_brightness_move],
exposes: [e.light_brightness().withMinBrightness().withMaxBrightness(), e.power_on_behavior().withAccess(ea.ALL)],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff", "genLevelCtrl"]);
await reporting.onOff(endpoint);
},
},
{
fingerprint: tuya.fingerprint("TS110E", ["_TZE200_ubgdwsnr"]),
model: "EKAC-T3096Z",
vendor: "Ekaza",
description: "2 channel dimmer",
extend: [tuya.modernExtend.tuyaBase({ dp: true }), m.deviceEndpoints({ endpoints: { l1: 1, l2: 2 } })],
exposes: [e.light_brightness().withEndpoint("l1"), e.light_brightness().withEndpoint("l2")],
meta: {
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "brightness_l1", tuya.valueConverter.scale0_254to0_1000],
[7, "state_l2", tuya.valueConverter.onOff],
[8, "brightness_l2", tuya.valueConverter.scale0_254to0_1000],
],
},
},
{
fingerprint: tuya.fingerprint("TS110E", ["_TZ3210_wdexaypg"]),
model: "TS110E_2gang_1",
vendor: "Tuya",
description: "2 channel dimmer",
extend: [
tuyaBase(),
m.deviceEndpoints({ endpoints: { l1: 1, l2: 2 } }),
m.light({
powerOnBehavior: false,
endpointNames: ["l1", "l2"],
configureReporting: true,
}),
],
fromZigbee: [tuya.fz.power_on_behavior_1, tuya.fz.TS110E_switch_type, tuya.fz.TS110E],
toZigbee: [tuya.tz.TS110E_light_onoff_brightness, tuya.tz.power_on_behavior_1, tuya.tz.TS110E_options],
configure: tuya.configureMagicPacket,
exposes: [
e.min_brightness().withEndpoint("l1"),
e.max_brightness().withEndpoint("l1"),
e.min_brightness().withEndpoint("l2"),
e.max_brightness().withEndpoint("l2"),
e.power_on_behavior(),
tuya.exposes.switchType().withEndpoint("l1"),
tuya.exposes.switchType().withEndpoint("l2"),
],
},
{
fingerprint: tuya.fingerprint("TS110E", ["_TZ3210_pagajpog", "_TZ3210_4ubylghk", "_TZ3210_vfwhhldz", "_TZ3210_3mpwqzuu", "_TZ3210_mt5xjoy6"]),
model: "TS110E_2gang_2",
vendor: "Tuya",
description: "2 channel dimmer",
whiteLabel: [tuya.whitelabel("Nedis", "ZBWD20RD", "SmartLife Triac Dimmer", ["_TZ3210_mt5xjoy6"])],
extend: [tuyaBase()],
fromZigbee: [tuya.fz.TS110E, tuya.fz.TS110E_switch_type, tuya.fz.power_on_behavior_1, fz.on_off],
toZigbee: [tuya.tz.TS110E_onoff_brightness, tuya.tz.TS110E_options, tuya.tz.power_on_behavior_1, tz.light_brightness_move],
meta: { multiEndpoint: true },
exposes: [
e.power_on_behavior(),
e.light_brightness().withMinBrightness().withMaxBrightness().withEndpoint("l1"),
e.light_brightness().withMinBrightness().withMaxBrightness().withEndpoint("l2"),
tuya.exposes.switchType().withEndpoint("l1"),
tuya.exposes.switchType().withEndpoint("l2"),
],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff", "genLevelCtrl"]);
await reporting.onOff(endpoint);
},
endpoint: (device) => {
return { l1: 1, l2: 2 };
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_nslr42tt"]),
model: "TS0601_3_phase_clamp_meter",
vendor: "Tuya",
description: "3-phase clamp power meter",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
whiteLabel: [
{ vendor: "MatSee Plus", model: "PC321-Z-TY" },
{ vendor: "OWON", model: "PC321-Z-TY" },
],
exposes: (device) => {
const baseExposes = [
e.ac_frequency(),
e.temperature(),
e.current(),
e.power(),
e.energy(),
tuya.exposes.energyWithPhase("a"),
tuya.exposes.energyWithPhase("b"),
tuya.exposes.energyWithPhase("c"),
tuya.exposes.voltageWithPhase("a"),
tuya.exposes.voltageWithPhase("b"),
tuya.exposes.voltageWithPhase("c"),
tuya.exposes.powerWithPhase("a"),
tuya.exposes.powerWithPhase("b"),
tuya.exposes.powerWithPhase("c"),
tuya.exposes.currentWithPhase("a"),
tuya.exposes.currentWithPhase("b"),
tuya.exposes.currentWithPhase("c"),
tuya.exposes.powerFactorWithPhase("a"),
tuya.exposes.powerFactorWithPhase("b"),
tuya.exposes.powerFactorWithPhase("c"),
];
if (device.applicationVersion >= 132) {
baseExposes.push(e.numeric("ac_frequency_high_precision", ea.STATE).withUnit("Hz").withValueMin(0).withValueMax(100));
}
return baseExposes;
},
meta: {
multiEndpointSkip: ["power_factor", "power_factor_phase_b", "power_factor_phase_c", "energy"],
tuyaDatapoints: [
[132, "ac_frequency", tuya.valueConverter.raw],
[133, "temperature", tuya.valueConverter.divideBy10],
[1, "energy", tuya.valueConverter.divideBy100],
[101, "energy_a", tuya.valueConverter.divideBy1000],
[111, "energy_b", tuya.valueConverter.divideBy1000],
[121, "energy_c", tuya.valueConverter.divideBy1000],
[131, "current", tuya.valueConverter.divideBy1000],
[9, "power", tuya.valueConverter.power],
[102, "power_factor_a", tuya.valueConverter.raw],
[112, "power_factor_b", tuya.valueConverter.raw],
[122, "power_factor_c", tuya.valueConverter.raw],
[6, null, tuya.valueConverter.phaseVariant2WithPhase("a")],
[7, null, tuya.valueConverter.phaseVariant2WithPhase("b")],
[8, null, tuya.valueConverter.phaseVariant2WithPhase("c")],
[134, "device_status", tuya.valueConverter.raw],
[135, "ac_frequency_high_precision", tuya.valueConverter.divideBy100],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_x8fp01wi", "_TZE204_x8fp01wi"]),
model: "TS0601_3_phase_clamp_meter_relay",
vendor: "Tuya",
description: "3-phase clamp power meter with relay",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
whiteLabel: [{ vendor: "Wenzhou Taiye Electric", model: "TAC7361C BI" }],
exposes: [
e.switch().setAccess("state", ea.STATE_SET),
e.power(),
e.energy(),
e.produced_energy(),
tuya.exposes.voltageWithPhase("a"),
tuya.exposes.voltageWithPhase("b"),
tuya.exposes.voltageWithPhase("c"),
tuya.exposes.powerWithPhase("a"),
tuya.exposes.powerWithPhase("b"),
tuya.exposes.powerWithPhase("c"),
tuya.exposes.currentWithPhase("a"),
tuya.exposes.currentWithPhase("b"),
tuya.exposes.currentWithPhase("c"),
],
meta: {
tuyaDatapoints: [
[16, "state", tuya.valueConverter.onOff],
[1, "energy", tuya.valueConverter.divideBy100],
[2, "produced_energy", tuya.valueConverter.divideBy100],
[9, "power", tuya.valueConverter.power],
[6, null, tuya.valueConverter.phaseVariant2WithPhase("a")],
[7, null, tuya.valueConverter.phaseVariant2WithPhase("b")],
[8, null, tuya.valueConverter.phaseVariant2WithPhase("c")],
],
},
},
{
zigbeeModel: ["TS0049"],
model: "TS0049",
vendor: "Tuya",
description: "Water valve",
extend: [tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true })],
exposes: [
tuya.exposes.errorStatus(),
tuya.exposes.switch(),
tuya.exposes.batteryState(),
tuya.exposes.countdown().withValueMin(0).withValueMax(255).withUnit("minutes").withDescription("Max on time in minutes"),
],
meta: {
tuyaSendCommand: "sendData",
tuyaDatapoints: [
[26, "error_status", tuya.valueConverter.raw],
[101, "state", tuya.valueConverter.onOff],
[111, "countdown", tuya.valueConverter.raw],
[115, "battery_state", tuya.valueConverter.batteryState],
],
},
},
{
fingerprint: tuya.fingerprint("TS0049", ["_TZ3290_ixd9mvv4"]),
model: "LYAI-14",
vendor: "Tuya",
description: "Solar intelligent timed water valve controller",
extend: [m.battery(), m.onOff({ powerOnBehavior: false }), tuya.modernExtend.tuyaBase({ dp: true })],
// Countdown is not working properly yet - device ignores it and uses the default (10 minutes)
// Countdown can be set using the Tuya app/hub and it will persist
fromZigbee: [fz.battery, fz.on_off /*tuya.fz.on_off_countdown,*/],
toZigbee: [tz.on_off /*tuya.tz.on_off_countdown,*/],
exposes: [
tuya.exposes.countdown().withAccess(ea.ALL),
e.numeric("error_status", ea.STATE).withDescription("Device error code"),
e.enum("rain_sensor_status", ea.STATE, ["rain", "no_rain"]).withDescription("Rain sensor state"),
e.binary("rain_sensor_switch", ea.STATE_SET, "ON", "OFF").withDescription("Enable/disable rain sensor"),
e
.enum("battery_state", ea.STATE, ["no_charge", "charging", "charged"])
.withDescription("Battery charging state")
.withCategory("diagnostic"),
],
meta: {
tuyaSendCommand: "sendData",
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[4, "error_status", tuya.valueConverter.raw],
[11, "countdown", tuya.valueConverter.countdown],
[101, "rain_sensor_status", tuya.valueConverterBasic.lookup({ rain: tuya.enum(0), no_rain: tuya.enum(1) })],
[102, "rain_sensor_switch", tuya.valueConverter.onOff],
[103, "battery_state", tuya.valueConverterBasic.lookup({ no_charge: tuya.enum(0), charging: tuya.enum(1), charged: tuya.enum(2) })],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_eaet5qt5", "_TZE284_fhvpaltk"]),
model: "TS0601_water_switch",
vendor: "Tuya",
description: "Dual water valve",
fromZigbee: [fz.battery],
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.enum("valve_status", ea.STATE, ["manual", "auto", "idle"]).withDescription("Valve 1 status (manual, auto, idle)").withEndpoint("l1"),
e.enum("valve_status", ea.STATE, ["manual", "auto", "idle"]).withDescription("Valve 2 status (manual, auto, idle)").withEndpoint("l2"),
e.switch().withEndpoint("l1").withDescription("Valve 1 on/off").withLabel("Valve 1"),
e.switch().withEndpoint("l2").withDescription("Valve 2 on/off").withLabel("Valve 2"),
e
.numeric("countdown", ea.STATE_SET)
.withUnit("min")
.withDescription("Valve 1 countdown in minutes")
.withValueMin(0)
.withValueMax(1440)
.withEndpoint("l1"),
e
.numeric("countdown", ea.STATE_SET)
.withUnit("min")
.withDescription("Valve 2 countdown in minutes")
.withValueMin(0)
.withValueMax(1440)
.withEndpoint("l2"),
e.numeric("valve_duration", ea.STATE).withUnit("s").withDescription("Valve 1 irrigation last duration in seconds").withEndpoint("l1"),
e.numeric("valve_duration", ea.STATE).withUnit("s").withDescription("Valve 2 irrigation last duration in seconds").withEndpoint("l2"),
e.battery(),
],
meta: {
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff], // Valve 1 on/off
[2, "state_l2", tuya.valueConverter.onOff], // Valve 2 on/off
[13, "countdown_l1", tuya.valueConverter.raw], // Valve 1 countdown
[14, "countdown_l2", tuya.valueConverter.raw], // Valve 2 countdown
[25, "valve_duration_l1", tuya.valueConverter.raw], // Valve 1 duration
[26, "valve_duration_l2", tuya.valueConverter.raw], // Valve 2 duration
[104, "valve_status_l1", tuya.valueConverterBasic.lookup({ manual: 0, auto: 1, idle: 2 })], // Valve 1 status
[105, "valve_status_l2", tuya.valueConverterBasic.lookup({ manual: 0, auto: 1, idle: 2 })], // Valve 2 status
[59, "battery", tuya.valueConverter.raw],
],
multiEndpoint: true, // Enable multi-endpoint support
},
endpoint: (device) => {
return {
l1: 1, // Valve 1 uses endpoint 1
l2: 1, // Valve 2 also uses endpoint 1
};
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_r32ctezx", "_TZE204_r32ctezx", "_TZE28C1000000_z5jz7wpo"]),
model: "TS0601_fan_switch",
vendor: "Tuya",
description: "Fan switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.switch(),
e.power_on_behavior(["off", "on"]).withAccess(ea.STATE_SET),
tuya.exposes.countdown().withValueMin(0).withValueMax(43200).withUnit("s").withDescription("Max ON time in seconds"),
e.numeric("fan_speed", ea.STATE_SET).withValueMin(1).withValueMax(5).withValueStep(1).withDescription("Speed off the fan"),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[2, "countdown", tuya.valueConverter.countdown],
[
3,
"fan_speed",
tuya.valueConverterBasic.lookup({
"1": tuya.enum(0),
"2": tuya.enum(1),
"3": tuya.enum(2),
"4": tuya.enum(3),
"5": tuya.enum(4),
}),
],
[
11,
"power_on_behavior",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
on: tuya.enum(1),
}),
],
],
},
whiteLabel: [{ vendor: "Lerlink", model: "T2-Z67/T2-W67" }],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_hmqzfqml", "_TZE200_qanl25yu"]),
model: "TS0601_fan_and_light_switch",
vendor: "Tuya",
description: "Fan & light switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.binary("status_indication", ea.STATE_SET, "ON", "OFF").withDescription("Light switch"),
tuya.exposes.switch(),
e.power_on_behavior(["OFF", "ON"]).withAccess(ea.STATE_SET),
e.enum("fan_speed", ea.STATE_SET, ["minimum", "medium", "maximum"]).withDescription("Speed of the fan"),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[
101,
"fan_speed",
tuya.valueConverterBasic.lookup({
minimum: tuya.enum(0),
medium: tuya.enum(1),
maximum: tuya.enum(2),
}),
],
[
11,
"power_on_behavior",
tuya.valueConverterBasic.lookup({
OFF: tuya.enum(0),
ON: tuya.enum(1),
}),
],
[5, "status_indication", tuya.valueConverter.onOff],
],
},
whiteLabel: [{ vendor: "Liwokit", model: "Fan+Light-01" }],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_bql5khqx", "_TZE204_2jnoy8dj"]),
model: "TS0601_fan_dimmer_and_light_switch",
vendor: "Tuya",
description: "Fan Dimmer and 1 Gang Light Switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
exposes
.numeric("fan_speed", ea.STATE_SET)
.withValueMin(0)
.withValueMax(100)
.withValueStep(1)
.withUnit("%")
.withDescription("Speed of the fan %"),
e.power_on_behavior(["off", "on", "previous"]).withAccess(ea.STATE_SET),
e.binary("light_switch", ea.STATE_SET, "ON", "OFF").withDescription("Light switch"),
e.enum("indicator", ea.ALL, ["off", "off/on", "on"]).withDescription("LED indicator mode").withAccess(ea.STATE_SET),
exposes.binary("child_lock", ea.STATE_SET, "ON", "OFF").withDescription("Child Lock"),
exposes
.numeric("minimum_speed", ea.STATE_SET)
.withValueMin(0)
.withValueMax(100)
.withValueStep(1)
.withUnit("%")
.withDescription("Vitesse minimale du ventilateur"),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[4, "fan_speed", tuya.valueConverter.raw],
[5, "light_switch", tuya.valueConverter.onOff],
[11, "power_on_behavior", tuya.valueConverterBasic.lookup({ off: tuya.enum(0), on: tuya.enum(1), previous: tuya.enum(2) })],
[12, "indicator", tuya.valueConverterBasic.lookup({ off: tuya.enum(0), "off/on": tuya.enum(1), on: tuya.enum(2) })],
[13, "backlight", tuya.valueConverter.onOff],
[104, "child_lock", tuya.valueConverter.onOff],
[105, "minimum_speed", tuya.valueConverter.raw],
],
},
whiteLabel: [
{ vendor: "Coswall", model: "X99-G-kbFan-1g-ZG-LN-11" },
{ vendor: "Zemismart", model: "ZN2S-RS1E-FL / ZN2S-US1U-FL" },
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_lawxy9e2", "_TZE204_lawxy9e2"]),
model: "TS0601_fan_5_levels_and_light_switch",
vendor: "Tuya",
description: "Fan with 5 levels & light switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.binary("status_indication", ea.STATE_SET, "ON", "OFF").withDescription("Light switch"),
tuya.exposes.switch(),
e.power_on_behavior(["OFF", "ON"]).withAccess(ea.STATE_SET).withDescription("Fan On Off"),
e.numeric("fan_speed", ea.STATE_SET).withValueMin(1).withValueMax(5).withValueStep(1).withDescription("Speed off the fan"),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[
3,
"fan_speed",
tuya.valueConverterBasic.lookup({
"1": tuya.enum(0),
"2": tuya.enum(1),
"3": tuya.enum(2),
"4": tuya.enum(3),
"5": tuya.enum(4),
}, "5"),
],
[
11,
"power_on_behavior",
tuya.valueConverterBasic.lookup({
OFF: tuya.enum(0),
ON: tuya.enum(1),
}),
],
[5, "status_indication", tuya.valueConverter.onOff],
],
},
whiteLabel: [{ vendor: "Liwokit", model: "Fan+Light-01" }],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_ikul00sx"]),
model: "TS0601_fan_5_levels_and_light_5_levels",
vendor: "Tuya",
description: "Fan with 5 levels and light with 5 levels",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.light_brightness(),
e.binary("fan_state", ea.STATE_SET, "ON", "OFF").withDescription("Fan on/off"),
e.numeric("fan_speed", ea.STATE_SET).withValueMin(1).withValueMax(5).withValueStep(1).withDescription("Fan speed (1=slowest, 5=fastest)"),
],
meta: {
tuyaDatapoints: [
[1, "fan_state", tuya.valueConverter.onOff],
[
6,
"fan_speed",
tuya.valueConverterBasic.lookup({
1: 300,
2: 410,
3: 520,
4: 650,
5: 1000,
}),
],
[104, "state", tuya.valueConverter.onOff],
[
105,
"brightness",
{
to: (value) => {
// Map 0-254 to nearest device level
if (value <= 25)
return 300; // 0-10%
if (value <= 76)
return 410; // 11-30%
if (value <= 127)
return 520; // 31-50%
if (value <= 178)
return 650; // 51-70%
return 1000; // 71-100%
},
from: (value) => {
// Map device level back to 0-254
if (value <= 300)
return 51;
if (value <= 410)
return 102;
if (value <= 520)
return 153;
if (value <= 650)
return 203;
return 254;
},
},
],
],
},
},
{
zigbeeModel: ["TS0224"],
model: "TS0224",
vendor: "Tuya",
description: "Smart light & sound siren",
fromZigbee: [],
toZigbee: [tz.warning, tzLocal.TS0224],
extend: [m.iasWarningMaxDuration()],
exposes: [
e.warning(),
e.binary("light", ea.STATE_SET, "ON", "OFF").withDescription("Turn the light of the alarm ON/OFF"),
e.enum("volume", ea.STATE_SET, ["mute", "low", "medium", "high"]).withDescription("Volume of the alarm"),
],
},
{
fingerprint: tuya.fingerprint("TS0041", ["_TZ3000_fa9mlvja"]),
model: "IH-K663",
vendor: "Tuya",
description: "Smart button",
extend: [tuyaBase()],
exposes: [e.battery(), e.battery_voltage(), e.action(["single", "double"])],
fromZigbee: [tuya.fz.on_off_action, fz.battery],
toZigbee: [],
configure: tuya.configureMagicPacket,
},
{
fingerprint: tuya.fingerprint("TS011F", [
"_TZ3000_cayepv1a",
"_TZ3000_lepzuhto",
"_TZ3000_qystbcjg",
"_TZ3000_zrm3oxsh",
"_TZ3000_303avxxt",
"_TZ3000_ibefeicf",
"_TZ3000_zjchz7pd",
"_TZ3000_zv6x8bt2",
"_TZ3000_yi0n4xfd",
]),
model: "TS011F_with_threshold",
description: "Din rail switch with power monitoring and threshold settings",
vendor: "Tuya",
ota: true,
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
electricalMeasurements: true,
electricalMeasurementsFzConverter: tuya.fz.TS011F_electrical_measurement,
powerOutageMemory: true,
indicatorMode: true,
onOffCountdown: true,
childLock: true,
}),
],
fromZigbee: [fz.temperature, fzLocal.TS011F_threshold],
toZigbee: [tzLocal.TS011F_threshold],
exposes: (device, options) => {
const exposes = [];
if (!["_TZ3000_303avxxt", "_TZ3000_ibefeicf", "_TZ3000_zjchz7pd"].includes(device.manufacturerName)) {
exposes.push(e.temperature(), e
.numeric("temperature_threshold", ea.STATE_SET)
.withValueMin(40)
.withValueMax(100)
.withValueStep(1)
.withUnit("°C")
.withDescription("High temperature threshold"), e.binary("temperature_breaker", ea.STATE_SET, "ON", "OFF").withDescription("High temperature breaker"));
}
exposes.push(e
.numeric("power_threshold", ea.STATE_SET)
.withValueMin(1)
.withValueMax(26)
.withValueStep(1)
.withUnit("kW")
.withDescription("High power threshold"), e.binary("power_breaker", ea.STATE_SET, "ON", "OFF").withDescription("High power breaker"), e
.numeric("over_current_threshold", ea.STATE_SET)
.withValueMin(1)
.withValueMax(65)
.withValueStep(1)
.withUnit("A")
.withDescription("Over-current threshold"), e.binary("over_current_breaker", ea.STATE_SET, "ON", "OFF").withDescription("Over-current breaker"), e
.numeric("over_voltage_threshold", ea.STATE_SET)
// Min 90 (not 220) so thresholds for 100-127V grids are accepted too;
// the device firmware itself takes values in this range.
.withValueMin(90)
.withValueMax(265)
.withValueStep(1)
.withUnit("V")
.withDescription("Over-voltage threshold"), e.binary("over_voltage_breaker", ea.STATE_SET, "ON", "OFF").withDescription("Over-voltage breaker"), e
.numeric("under_voltage_threshold", ea.STATE_SET)
.withValueMin(75)
.withValueMax(240)
.withValueStep(1)
.withUnit("V")
.withDescription("Under-voltage threshold"), e.binary("under_voltage_breaker", ea.STATE_SET, "ON", "OFF").withDescription("Under-voltage breaker"));
return exposes;
},
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
const endpoint = device.getEndpoint(1);
await endpoint.command("genBasic", "tuyaSetup", {});
await reporting.bind(endpoint, coordinatorEndpoint, ["msTemperatureMeasurement"]);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff", "haElectricalMeasurement", "seMetering"]);
await reporting.rmsVoltage(endpoint, { change: 5 });
await reporting.rmsCurrent(endpoint, { change: 50 });
await reporting.activePower(endpoint, { change: 10 });
await reporting.currentSummDelivered(endpoint);
endpoint.saveClusterAttributeKeyValue("haElectricalMeasurement", {
acCurrentDivisor: 1000,
acCurrentMultiplier: 1,
});
endpoint.saveClusterAttributeKeyValue("seMetering", {
divisor: 100,
multiplier: 1,
});
device.save();
},
whiteLabel: [
tuya.whitelabel("Tongou", "TO-Q-SY2-163JZT", "Smart circuit breaker", ["_TZ3000_cayepv1a", "_TZ3000_yi0n4xfd"]),
tuya.whitelabel("EARU", "EAKCB-T-M-Z", "Smart circuit breaker", ["_TZ3000_lepzuhto"]),
tuya.whitelabel("EARU", "EAYCB-Z-2P", "Smart circuit breaker with leakage protection", ["_TZ3000_zrm3oxsh"]),
tuya.whitelabel("UNSH", "SMKG-1KNL-EU-Z", "Smart circuit Breaker", ["_TZ3000_qystbcjg"]),
tuya.whitelabel("Tomzn", "TOB9Z-VAP", "Smart circuit breaker", ["_TZ3000_303avxxt", "_TZ3000_ibefeicf"]),
tuya.whitelabel("Immax", "07573L", "Smart circuit breaker", ["_TZ3000_zjchz7pd"]),
tuya.whitelabel("Moes", "A5", "Smart circuit breaker", ["_TZ3000_zv6x8bt2"]),
],
},
{
fingerprint: tuya.fingerprint("TS000F", ["_TZ3000_hdc8bbha"]),
model: "QS-Zigbee-SEC01-U",
vendor: "Tuya",
description: "Zigbee 3.0 smart light switch module 1 gang",
version: "0.0.1",
extend: [tuya.modernExtend.tuyaBase(), tuya.modernExtend.tuyaOnOff({ switchType: true })],
configure: async (device, coordinatorEndpoint) => {
const ep = device.getEndpoint(1);
await reporting.bind(ep, coordinatorEndpoint, ["genOnOff"]);
await reporting.onOff(ep);
device.powerSource = "Mains (single phase)";
// Device advertises itself as Router but is an EndDevice
device.type = "EndDevice";
device.save();
},
},
{
fingerprint: tuya.fingerprint("TS000F", ["_TZ3218_hdc8bbha"]),
model: "QS-Zigbee-SEC01-DC",
vendor: "Tuya",
description: "Mini 1 Gang Zigbee Switch Module",
version: "0.0.1",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({ powerOutageMemory: true }),
m.enumLookup({
name: "switch_type",
lookup: { momentary: 0, toggle: 1, state: 2 },
cluster: "genOnOff",
attribute: { ID: 0x8001, type: zcl_1.DataType.ENUM8 },
zigbeeCommandOptions: { manufacturerCode: 0x1141 },
description: "External switch type",
}),
],
configure: async (device, coordinatorEndpoint) => {
const ep = device.getEndpoint(1);
await reporting.bind(ep, coordinatorEndpoint, ["genOnOff"]);
await reporting.onOff(ep);
device.powerSource = "Mains (single phase)";
// Device advertises itself as Router but is an EndDevice
device.type = "EndDevice";
device.save();
},
},
{
fingerprint: tuya.fingerprint("TS000F", ["_TZ3000_m8f3z8ju"]),
model: "QS-Zigbee-SEC02-U",
vendor: "Tuya",
description: "Zigbee 3.0 smart light switch module 2 gang",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
switchType: true,
endpoints: ["l1", "l2"],
}),
],
endpoint: (device) => {
return { l1: 1, l2: 2 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
},
},
{
fingerprint: tuya.fingerprint("TS0001", [
"_TZ3000_bmqxalil",
"_TZ3000_w1tcofu8",
"_TZ3000_ma3mhpx2",
"_TZ3000_wijoqjk1",
"_TZ3000_5rpu3r0d",
"_TZ3000_bbebkwjk",
]),
model: "TS0001_switch_1_gang",
vendor: "Tuya",
description: "1-Gang switch with backlight",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
powerOnBehavior2: true,
backlightModeOffOn: (m) => m !== "_TZ3000_5rpu3r0d",
switchType: (m) => m === "_TZ3000_5rpu3r0d",
onOffCountdown: (m) => m === "_TZ3000_5rpu3r0d",
indicatorMode: (m) => ["_TZ3000_5rpu3r0d", "_TZ3000_bbebkwjk"].includes(m),
}),
],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
},
whiteLabel: [
tuya.whitelabel("Homeetec", "Homeetec_37022454", "1 Gang switch with backlight", ["_TZ3000_bmqxalil"]),
tuya.whitelabel("RoomsAI", "RoomsAI_37022454", "1 Gang switch with backlight", ["_TZ3000_w1tcofu8"]),
tuya.whitelabel("Rely Electronics", "_TZ3000_5rpu3r0d", "1 gang, no neutral, switch with backlight", ["_TZ3000_5rpu3r0d"]),
],
},
{
fingerprint: tuya.fingerprint("TS0001", ["_TZ3000_65ajyxua", "_TZ3000_qq9ahj6z"]),
model: "FZB-1",
vendor: "Nova Digital",
description: "1-Gang switch with power-on behavior and indicator mode",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
powerOnBehavior2: true,
backlightModeOffOn: true,
indicatorMode: true,
inchingSwitch: true,
}),
],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
},
whiteLabel: [tuya.whitelabel("iHseno", "_TZ3000_qq9ahj6z", "1-gang touch switch", ["_TZ3000_qq9ahj6z"])],
},
{
fingerprint: tuya.fingerprint("TS0003", ["_TZ3000_pv4puuxi", "_TZ3000_avky2mvc", "_TZ3000_785olaiq", "_TZ3000_qxcnwv26"]),
model: "TS0003_switch_3_gang",
vendor: "Tuya",
description: "3-Gang switch with backlight",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
powerOnBehavior2: true,
backlightModeOffOn: true,
endpoints: ["left", "center", "right"],
}),
],
endpoint: (device) => {
return { left: 1, center: 2, right: 3 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ["genOnOff"]);
},
whiteLabel: [
tuya.whitelabel("Homeetec", "37022474_1", "3 Gang switch with backlight", ["_TZ3000_pv4puuxi"]),
tuya.whitelabel("RoomsAI", "37022474_2", "3 Gang switch with backlight", ["_TZ3000_avky2mvc"]),
tuya.whitelabel("Nova Digital", "WS-US-ZB", "3 Gang switch with backlight", ["_TZ3000_785olaiq"]),
tuya.whitelabel("Nova Digital", "FZB-3", "3 Gang physical switch with backlight", ["_TZ3000_qxcnwv26"]),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_hewlydpz"]),
model: "TS0601_switch_4_gang_2",
vendor: "Tuya",
description: "4 gang switch with backlight",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.switch().withEndpoint("l1"),
tuya.exposes.switch().withEndpoint("l2"),
tuya.exposes.switch().withEndpoint("l3"),
tuya.exposes.switch().withEndpoint("l4"),
tuya.exposes.backlightModeOffOn(),
],
endpoint: (device) => {
return { l1: 1, l2: 1, l3: 1, l4: 1 };
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
[4, "state_l4", tuya.valueConverter.onOff],
[7, "backlight_mode", tuya.valueConverter.onOff],
],
},
whiteLabel: [tuya.whitelabel("Homeetec", "37022714", "4 Gang switch with backlight", ["_TZE200_hewlydpz"])],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_p6vz3wzt", "_TZE204_p6vz3wzt", "_TZE284_b7kbnl6q", "_TZE284_uqfph8ah", "_TZE284_waa352qv"]),
model: "TS0601_cover_5",
vendor: "Tuya",
description: "Curtain/blind switch",
options: [exposes.options.invert_cover()],
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: (device, options) => {
const exps = [
te.coverPosition(),
e.enum("calibration", ea.STATE_SET, ["START", "END"]).withDescription("Calibration"),
e.binary("backlight_mode", ea.STATE_SET, "ON", "OFF").withDescription("Backlight"),
e.enum("motor_steering", ea.STATE_SET, ["FORWARD", "BACKWARD"]).withDescription("Motor Steering"),
];
if (!device || device.manufacturerName === "_TZE284_uqfph8ah") {
exps.push(e
.numeric("quick_calibration", ea.STATE_SET)
.withValueMin(1)
.withValueMax(120)
.withUnit("s")
.withDescription("Set quick calibration"));
exps.push(e.enum("indicator_mode", ea.STATE_SET, ["relay", "pos", "none"]).withDescription("LED indicator mode"));
}
else {
exps.push(e.binary("child_lock", ea.STATE_SET, "ON", "OFF").withDescription("Child Lock"));
}
return exps;
},
meta: {
tuyaDatapoints: [
[
1,
"state",
tuya.valueConverterBasic.lookup((_, device) => {
if (device.manufacturerName === "_TZE284_waa352qv") {
return {
STOP: tuya.enum(0),
CLOSE: tuya.enum(1),
OPEN: tuya.enum(2),
};
}
return {
OPEN: tuya.enum(0),
STOP: tuya.enum(1),
CLOSE: tuya.enum(2),
};
}),
],
[2, "position", tuya.valueConverter.coverPosition],
[
3,
"calibration",
tuya.valueConverterBasic.lookup({
START: tuya.enum(0),
END: tuya.enum(1),
}),
],
[7, "backlight_mode", tuya.valueConverter.onOff],
[
8,
"motor_steering",
tuya.valueConverterBasic.lookup({
FORWARD: tuya.enum(0),
BACKWARD: tuya.enum(1),
}),
],
[10, "quick_calibration", tuya.valueConverter.raw],
[
14,
"indicator_mode",
tuya.valueConverterBasic.lookup({
relay: tuya.enum(0),
pos: tuya.enum(1),
none: tuya.enum(2),
}),
],
[103, "child_lock", tuya.valueConverter.onOff],
],
},
whiteLabel: [
tuya.whitelabel("Homeetec", "37022483", "Curtain/blind switch", ["_TZE200_p6vz3wzt"]),
tuya.whitelabel("BSEED", "_TZE284_uqfph8ah", "Curtain/blind switch", ["_TZE284_uqfph8ah"]),
tuya.whitelabel("Moes", "ZRS-USC-WH", "Smart curtain switch", ["_TZE284_b7kbnl6q"]),
],
},
{
zigbeeModel: ["TS030F"],
model: "TS030F",
vendor: "Tuya",
description: "Smart blind controller",
extend: [tuyaBase()],
fromZigbee: [fz.cover_position_tilt, tuya.fz.cover_options_2],
toZigbee: [tz.cover_position_tilt, tz.cover_state, tzLocal.TS030F_border, tuya.tz.moes_cover_calibration, tuya.tz.cover_reversal],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genPowerCfg", "closuresWindowCovering"]);
await reporting.batteryPercentageRemaining(endpoint);
await reporting.currentPositionLiftPercentage(endpoint);
},
whiteLabel: [tuya.whitelabel("Lidl", "HG09648", "Livarno roller blinds", ["_TZB000_42ha4rsc"])],
exposes: [
e.cover_position(),
e.enum("border", ea.SET, ["up", "down", "up_delete", "down_delete"]),
e.numeric("calibration_time", ea.ALL).withValueMin(0).withValueMax(100),
e
.binary("motor_reversal", ea.ALL, "ON", "OFF")
.withDescription("Reverse the motor, resets all endpoints! Also the upper border after hardware initialisation. Be careful!" +
"After this you have to turn off and turn on the roller so that it can drive into the uppest position."),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_jhkttplm"]),
model: "TS0601_cover_with_1_switch",
vendor: "Tuya",
description: "Curtain/blind switch with 1 Gang switch",
options: [exposes.options.invert_cover()],
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
te.coverPosition(),
tuya.exposes.switch().withEndpoint("l1"),
e.enum("calibration", ea.STATE_SET, ["START", "END"]).withDescription("Calibration"),
e.binary("backlight_mode", ea.STATE_SET, "ON", "OFF").withDescription("Backlight"),
e.enum("motor_steering", ea.STATE_SET, ["FORWARD", "BACKWARD"]).withDescription("Motor Steering"),
e.binary("child_lock", ea.STATE_SET, "ON", "OFF").withDescription("Child Lock"),
],
endpoint: (device) => {
return { l1: 1 };
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[2, "position", tuya.valueConverter.coverPosition],
[
3,
"calibration",
tuya.valueConverterBasic.lookup({
START: tuya.enum(0),
END: tuya.enum(1),
}),
],
[7, "backlight_mode", tuya.valueConverter.onOff],
[
8,
"motor_steering",
tuya.valueConverterBasic.lookup({
FORWARD: tuya.enum(0),
BACKWARD: tuya.enum(1),
}),
],
[101, "state_l1", tuya.valueConverter.onOff],
[103, "child_lock", tuya.valueConverter.onOff],
],
},
whiteLabel: [tuya.whitelabel("Homeetec", "37022493", "Curtain/blind switch with 1 Gang switch", ["_TZE200_jhkttplm"])],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_5nldle7w"]),
model: "TS0601_cover_with_2_switch",
vendor: "Tuya",
description: "Curtain/blind switch with 2 Gang switch",
options: [exposes.options.invert_cover()],
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
te.coverPosition(),
tuya.exposes.switch().withEndpoint("l1"),
tuya.exposes.switch().withEndpoint("l2"),
e.enum("calibration", ea.STATE_SET, ["START", "END"]).withDescription("Calibration"),
e.binary("backlight_mode", ea.STATE_SET, "ON", "OFF").withDescription("Backlight"),
e.enum("motor_steering", ea.STATE_SET, ["FORWARD", "BACKWARD"]).withDescription("Motor Steering"),
e.binary("child_lock", ea.STATE_SET, "ON", "OFF").withDescription("Child Lock"),
],
endpoint: (device) => {
return { l1: 1, l2: 1 };
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[2, "position", tuya.valueConverter.coverPosition],
[
3,
"calibration",
tuya.valueConverterBasic.lookup({
START: tuya.enum(0),
END: tuya.enum(1),
}),
],
[7, "backlight_mode", tuya.valueConverter.onOff],
[
8,
"motor_steering",
tuya.valueConverterBasic.lookup({
FORWARD: tuya.enum(0),
BACKWARD: tuya.enum(1),
}),
],
[101, "state_l2", tuya.valueConverter.onOff],
[102, "state_l1", tuya.valueConverter.onOff],
[103, "child_lock", tuya.valueConverter.onOff],
],
},
whiteLabel: [tuya.whitelabel("Homeetec", "37022173", "Curtain/blind switch with 2 Gang switch", ["_TZE200_5nldle7w"])],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_bcusnqt8"]),
model: "SPM01",
vendor: "Tuya",
description: "Smart energy monitor for 1P+N system",
ota: true,
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.voltage(),
e.power(),
e.current(),
// Change the description according to the specifications of the device
e.energy().withDescription("Total forward active energy"),
e.produced_energy().withDescription("Total reverse active energy"),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[2, "produced_energy", tuya.valueConverter.divideBy100],
[6, null, tuya.valueConverter.phaseVariant4],
// [9,'',tuya.valueConverter.raw] // Unknown / datatype=5 (bitmap)
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_ves1ycwx", "_TZE284_ves1ycwx", "_TZE200_ves1ycwx"]),
model: "SPM02",
vendor: "Tuya",
description: "Smart energy monitor for 3P+N system",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.voltageWithPhase("X"),
tuya.exposes.voltageWithPhase("Y"),
tuya.exposes.voltageWithPhase("Z"),
tuya.exposes.powerWithPhase("X"),
tuya.exposes.powerWithPhase("Y"),
tuya.exposes.powerWithPhase("Z"),
tuya.exposes.currentWithPhase("X"),
tuya.exposes.currentWithPhase("Y"),
tuya.exposes.currentWithPhase("Z"),
// Change the description according to the specifications of the device
e.energy().withDescription("Total forward active energy"),
e.produced_energy().withDescription("Total reverse active energy"),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[2, "produced_energy", tuya.valueConverter.divideBy100],
[6, null, tuya.valueConverter.phaseVariant2WithPhase("X")],
[7, null, tuya.valueConverter.phaseVariant2WithPhase("Y")],
[8, null, tuya.valueConverter.phaseVariant2WithPhase("Z")],
// [9,'',tuya.valueConverter.raw] // Unknown / datatype=5 (bitmap)
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_qhlxve78", "_TZE284_qhlxve78", "_TZE204_qhlxve78"]),
model: "SPM01V2",
vendor: "Tuya",
description: "Smart energy monitor for 1P+N system",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.voltage(),
e.power(),
e.current(),
e.energy().withDescription("Total forward active energy"),
e.produced_energy().withDescription("Total reverse active energy"),
e.power_factor().withUnit("%"),
e.ac_frequency(),
e
.numeric("data_report_duration", ea.SET)
.withValueMin(30)
.withValueMax(3600)
.withDescription("WARNING: You must update device firmware to V3.1.3 before changing this setting! Use Tuya gateway/app to update firmware. Data report duration set (Threshold value range 30~3600 seconds)"),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[2, "produced_energy", tuya.valueConverter.divideBy100],
// [6, null, tuya.valueConverter.phaseVariant3],
[15, "power_factor", tuya.valueConverter.raw],
// [16, 'clear_energy', tuya.valueConverter.onOff],
[
18,
"data_report_duration",
{
to: (v) => {
const value = Math.max(30, Math.min(3600, Math.round(v)));
const byte1 = (value >> 8) & 0xff;
const byte2 = value & 0xff;
return [
// Unknown what these bytes mean, possibly configures other settings of the device
0x01,
0x01,
0x00,
0x3c,
0x03,
0x01,
0x00,
0xfd,
0x04,
0x00,
0x00,
0xb4,
0x07,
0x01,
0x00,
0x00,
0x08,
0x01,
// Report duration
byte1,
byte2,
// Unknown what these bytes mean, possibly configures other settings of the device
0x09,
0x00,
0x00,
0x00,
];
},
},
],
[101, "ac_frequency", tuya.valueConverter.divideBy100],
[102, "voltage", tuya.valueConverter.divideBy10],
[103, "current", tuya.valueConverter.divideBy1000],
[104, "power", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_v9hkz2yn", "_TZE284_v9hkz2yn", "_TZE200_v9hkz2yn"]),
model: "SPM02V2",
vendor: "Tuya",
description: "Smart energy monitor for 3P+N system",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.voltageWithPhase("a"),
tuya.exposes.voltageWithPhase("b"),
tuya.exposes.voltageWithPhase("c"),
tuya.exposes.powerWithPhase("a"),
tuya.exposes.powerWithPhase("b"),
tuya.exposes.powerWithPhase("c"),
tuya.exposes.currentWithPhase("a"),
tuya.exposes.currentWithPhase("b"),
tuya.exposes.currentWithPhase("c"),
e.energy().withDescription("Total forward active energy"),
e.produced_energy().withDescription("Total reverse active energy"),
e.power_factor().withUnit("%"),
e.power(),
e.ac_frequency(),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[2, "produced_energy", tuya.valueConverter.divideBy100],
// [6, null, tuya.valueConverter.phaseVariant2WithPhase('X')],
// [7, null, tuya.valueConverter.phaseVariant2WithPhase('Y')],
// [8, null, tuya.valueConverter.phaseVariant2WithPhase('Z')],
[15, "power_factor", tuya.valueConverter.raw],
[101, "ac_frequency", tuya.valueConverter.divideBy100],
[102, "voltage_a", tuya.valueConverter.divideBy10],
[103, "current_a", tuya.valueConverter.divideBy1000],
[104, "power_a", tuya.valueConverter.raw],
[105, "voltage_b", tuya.valueConverter.divideBy10],
[106, "current_b", tuya.valueConverter.divideBy1000],
[107, "power_b", tuya.valueConverter.raw],
[108, "voltage_c", tuya.valueConverter.divideBy10],
[109, "current_c", tuya.valueConverter.divideBy1000],
[110, "power_c", tuya.valueConverter.raw],
[111, "power", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_dhotiauw"]),
model: "2CT",
vendor: "Tuya",
description: "Bidirectional energy meter with 80A current clamp",
extend: [tuyaBase({ dp: true })],
exposes: [
e.ac_frequency(),
e.voltage(),
e.power(),
tuya.exposes.powerWithPhase("a"),
tuya.exposes.powerWithPhase("b"),
tuya.exposes.currentWithPhase("a"),
tuya.exposes.currentWithPhase("b"),
tuya.exposes.powerFactorWithPhase("a"),
tuya.exposes.powerFactorWithPhase("b"),
tuya.exposes.energyWithPhase("a"),
tuya.exposes.energyWithPhase("b"),
e
.numeric("update_frequency", ea.STATE_SET)
.withUnit("s")
.withDescription("Update frequency")
.withValueMin(3)
.withValueMax(60)
.withPreset("default", 10, "Default value"),
e.numeric("timestamp_a", ea.STATE).withDescription("Timestamp at power measure (phase a)"),
e.numeric("timestamp_b", ea.STATE).withDescription("Timestamp at power measure (phase b)"),
e
.numeric("calibration_ac_frequency", ea.STATE_SET)
.withDescription("Calibration AC frequency")
.withValueMin(0.5)
.withValueMax(1.5)
.withValueStep(0.01)
.withPreset("default", 1.0, "Default value"),
e
.numeric("calibration_voltage", ea.STATE_SET)
.withDescription("Calibration voltage")
.withValueMin(0.5)
.withValueMax(1.5)
.withValueStep(0.01)
.withPreset("default", 1.0, "Default value"),
e
.numeric("calibration_power_a", ea.STATE_SET)
.withDescription("Calibration power A")
.withValueMin(0.5)
.withValueMax(1.5)
.withValueStep(0.01)
.withPreset("default", 1.0, "Default value"),
e
.numeric("calibration_current_a", ea.STATE_SET)
.withDescription("Calibration current A")
.withValueMin(0.5)
.withValueMax(1.5)
.withValueStep(0.01)
.withPreset("default", 1.0, "Default value"),
e
.numeric("calibration_power_b", ea.STATE_SET)
.withDescription("Calibration power B")
.withValueMin(0.5)
.withValueMax(1.5)
.withValueStep(0.01)
.withPreset("default", 1.0, "Default value"),
e
.numeric("calibration_current_b", ea.STATE_SET)
.withDescription("Calibration current B")
.withValueMin(0.5)
.withValueMax(1.5)
.withValueStep(0.01)
.withPreset("default", 1.0, "Default value"),
],
meta: {
multiEndpointSkip: ["power_factor", "power_factor_phase_a", "power_factor_phase_b", "energy"],
tuyaDatapoints: [
[105, "ac_frequency", tuya.valueConverter.divideBy100],
[103, "power_a", tuya.valueConverter.divideBy10],
[104, "power_b", tuya.valueConverter.divideBy10],
[19, "power", tuya.valueConverter.divideBy10],
[20, "voltage", tuya.valueConverter.divideBy10],
[101, "current_a", tuya.valueConverter.divideBy1000],
[102, "current_b", tuya.valueConverter.divideBy1000],
[120, "power_factor_a", tuya.valueConverter.raw],
[121, "power_factor_b", tuya.valueConverter.raw],
[115, "energy_a", tuya.valueConverter.divideBy100],
[117, "energy_b", tuya.valueConverter.divideBy100],
[122, "update_frequency", tuya.valueConverter.raw],
[123, "calibration_voltage", tuya.valueConverter.divideBy1000],
[125, "calibration_current_a", tuya.valueConverter.divideBy1000],
[126, "calibration_power_a", tuya.valueConverter.divideBy1000],
[130, "calibration_current_b", tuya.valueConverter.divideBy1000],
[131, "calibration_power_b", tuya.valueConverter.divideBy1000],
[124, "calibration_ac_frequency", tuya.valueConverter.divideBy1000],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_iwn0gpzz", "_TZE204_iwn0gpzz", "_TZE284_iwn0gpzz"]),
model: "SPM01V2.5",
vendor: "Tuya",
description: "Smart energy monitor for 1P+N system",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.voltage(),
e.power(),
e.current(),
e.energy().withDescription("Total forward active energy"),
e.produced_energy().withDescription("Total reverse active energy"),
e.power_factor().withUnit("%"),
e.ac_frequency(),
e
.numeric("data_report_duration", ea.SET)
.withValueMin(5)
.withValueMax(3600)
.withDescription("WARNING: You must update device firmware to V3.2.2 before changing this setting! Use Tuya gateway/app to update firmware. Data report duration set (Threshold value range 30~3600 seconds)"),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[
18,
"data_report_duration",
{
to: (v) => {
const value = Math.max(5, Math.min(3600, Math.round(v)));
const byte1 = (value >> 8) & 0xff;
const byte2 = value & 0xff;
return [
// Other settings of the device
0x01,
0x01,
0x00,
0x3c,
0x03,
0x01,
0x00,
0xfd,
0x04,
0x00,
0x00,
0xb4,
0x07,
0x01,
0x00,
0x00,
0x08,
0x01,
// Report duration
byte1,
byte2,
// Only modify the report duration
0x09,
0x00,
0x00,
0x00,
];
},
},
],
[23, "produced_energy", tuya.valueConverter.divideBy100],
[32, "ac_frequency", tuya.valueConverter.divideBy100],
[50, "power_factor", tuya.valueConverter.raw],
[102, "voltage", tuya.valueConverter.divideBy10],
[103, "current", tuya.valueConverter.divideBy1000],
[104, "power", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_ny94onlb", "_TZE200_ny94onlb", "_TZE284_ny94onlb"]),
model: "SPM02V2.5",
vendor: "Tuya",
description: "Smart energy monitor for 3P+N system",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.voltageWithPhase("a"),
tuya.exposes.voltageWithPhase("b"),
tuya.exposes.voltageWithPhase("c"),
tuya.exposes.powerWithPhase("a"),
tuya.exposes.powerWithPhase("b"),
tuya.exposes.powerWithPhase("c"),
tuya.exposes.currentWithPhase("a"),
tuya.exposes.currentWithPhase("b"),
tuya.exposes.currentWithPhase("c"),
e.energy().withDescription("Total forward active energy"),
e.produced_energy().withDescription("Total reverse active energy"),
e.power_factor().withUnit("%").withDescription("Total power factor"),
e.power(),
e.ac_frequency(),
e.numeric("data_report_duration", ea.SET).withValueMin(30).withValueMax(3600),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[
18,
"data_report_duration",
{
to: (v) => {
const value = Math.max(30, Math.min(3600, Math.round(v)));
const byte1 = (value >> 8) & 0xff;
const byte2 = value & 0xff;
return [
// Other settings of the device
0x01,
0x01,
0x00,
0x3c,
0x02,
0x00,
0x00,
0x0a,
0x03,
0x01,
0x00,
0xfd,
0x04,
0x00,
0x00,
0xb4,
0x05,
0x01,
0x00,
0x00,
0x07,
0x01,
0x00,
0x00,
0x08,
0x01,
// Report duration
byte1,
byte2,
// Only modify the report duration
0x09,
0x00,
0x00,
0x00,
];
},
},
],
[23, "produced_energy", tuya.valueConverter.divideBy100],
[29, "power", tuya.valueConverter.raw],
[32, "ac_frequency", tuya.valueConverter.divideBy100],
[50, "power_factor", tuya.valueConverter.raw],
[102, "voltage_a", tuya.valueConverter.divideBy10],
[103, "current_a", tuya.valueConverter.divideBy1000],
[104, "power_a", tuya.valueConverter.raw],
[105, "voltage_b", tuya.valueConverter.divideBy10],
[106, "current_b", tuya.valueConverter.divideBy1000],
[107, "power_b", tuya.valueConverter.raw],
[108, "voltage_c", tuya.valueConverter.divideBy10],
[109, "current_c", tuya.valueConverter.divideBy1000],
[110, "power_c", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_dikb3dp6", "_TZE204_dikb3dp6", "_TZE284_dikb3dp6"]),
model: "SPM02V3",
vendor: "Tuya",
description: "Smart energy monitor for 3P+N system",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.voltageWithPhase("a"),
tuya.exposes.voltageWithPhase("b"),
tuya.exposes.voltageWithPhase("c"),
tuya.exposes.powerWithPhase("a"),
tuya.exposes.powerWithPhase("b"),
tuya.exposes.powerWithPhase("c"),
tuya.exposes.currentWithPhase("a"),
tuya.exposes.currentWithPhase("b"),
tuya.exposes.currentWithPhase("c"),
e.energy().withDescription("Total forward active energy"),
e.produced_energy().withDescription("Total reverse active energy"),
e.power_factor().withUnit("%").withDescription("Total power factor"),
e.power().withDescription("Total active power"),
e.ac_frequency(),
tuya.exposes.energyWithPhase("a"),
tuya.exposes.energyWithPhase("b"),
tuya.exposes.energyWithPhase("c"),
tuya.exposes.energyProducedWithPhase("a"),
tuya.exposes.energyProducedWithPhase("b"),
tuya.exposes.energyProducedWithPhase("c"),
tuya.exposes.powerFactorWithPhase("a"),
tuya.exposes.powerFactorWithPhase("b"),
tuya.exposes.powerFactorWithPhase("c"),
e
.numeric("update_frequency", ea.STATE_SET)
.withUnit("s")
.withDescription("Update frequency")
.withValueMin(5)
.withValueMax(3600)
.withPreset("default", 30, "Default value"),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[23, "produced_energy", tuya.valueConverter.divideBy100],
[29, "power", tuya.valueConverter.raw],
[32, "ac_frequency", tuya.valueConverter.divideBy100],
[50, "power_factor", tuya.valueConverter.raw],
[102, "update_frequency", tuya.valueConverterBasic.divideBy(1)],
[103, "voltage_a", tuya.valueConverter.divideBy10],
[104, "current_a", tuya.valueConverter.divideBy1000],
[105, "power_a", tuya.valueConverter.raw],
[108, "power_factor_a", tuya.valueConverter.raw],
[109, "energy_a", tuya.valueConverter.divideBy100],
[110, "energy_produced_a", tuya.valueConverter.divideBy100],
[112, "voltage_b", tuya.valueConverter.divideBy10],
[113, "current_b", tuya.valueConverter.divideBy1000],
[114, "power_b", tuya.valueConverter.raw],
[117, "power_factor_b", tuya.valueConverter.raw],
[118, "energy_b", tuya.valueConverter.divideBy100],
[119, "energy_produced_b", tuya.valueConverter.divideBy100],
[121, "voltage_c", tuya.valueConverter.divideBy10],
[122, "current_c", tuya.valueConverter.divideBy1000],
[123, "power_c", tuya.valueConverter.raw],
[126, "power_factor_c", tuya.valueConverter.raw],
[127, "energy_c", tuya.valueConverter.divideBy100],
[128, "energy_produced_c", tuya.valueConverter.divideBy100],
],
},
},
{
// incomplete / incorrect: https://github.com/Koenkk/zigbee-herdsman-converters/pull/12355
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_ugekduaj", "_TZE200_ugekduaj"]),
model: "SDM01",
vendor: "Tuya",
description: "Smart energy monitor for 3P+N system",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.voltageWithPhase("a"),
tuya.exposes.voltageWithPhase("b"),
tuya.exposes.voltageWithPhase("c"),
tuya.exposes.powerWithPhase("a"),
tuya.exposes.powerWithPhase("b"),
tuya.exposes.powerWithPhase("c"),
tuya.exposes.currentWithPhase("a"),
tuya.exposes.currentWithPhase("b"),
tuya.exposes.currentWithPhase("c"),
e.energy().withDescription("Total forward active energy"),
e.produced_energy().withDescription("Total reverse active energy"),
e.power_factor().withUnit("%").withDescription("Total power factor"),
e.power().withDescription("Total active power"),
e.ac_frequency(),
e.numeric("data_report_duration", ea.SET).withValueMin(30).withValueMax(3600),
tuya.exposes.energyWithPhase("a"),
tuya.exposes.energyWithPhase("b"),
tuya.exposes.energyWithPhase("c"),
tuya.exposes.energyProducedWithPhase("a"),
tuya.exposes.energyProducedWithPhase("b"),
tuya.exposes.energyProducedWithPhase("c"),
tuya.exposes.powerFactorWithPhase("a"),
tuya.exposes.powerFactorWithPhase("b"),
tuya.exposes.powerFactorWithPhase("c"),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[2, "produced_energy", tuya.valueConverter.divideBy100],
[15, "power_factor", tuya.valueConverter.raw],
[
18,
"data_report_duration",
{
to: (v) => {
const value = Math.max(30, Math.min(3600, Math.round(v))) * 2;
// The reporting logic of this device is: for example, if 30 is input, it will report twice within 30 seconds,
// Which means reporting once every 15 seconds. Therefore, the input data needs to be multiplied by 2.
const byte1 = (value >> 8) & 0xff;
const byte2 = value & 0xff;
return [
// Other settings of the device
0x01,
0x01,
0x00,
0x3c,
0x02,
0x00,
0x00,
0x0a,
0x03,
0x01,
0x00,
0xfd,
0x04,
0x00,
0x00,
0xb4,
0x05,
0x01,
0x00,
0x00,
0x07,
0x01,
0x00,
0x00,
0x08,
0x01,
// Report duration
byte1,
byte2,
// Only modify the report duration
0x09,
0x00,
0x00,
0x00,
];
},
},
],
[101, "ac_frequency", tuya.valueConverter.divideBy100],
[102, "voltage_a", tuya.valueConverter.divideBy10],
[103, "current_a", tuya.valueConverter.divideBy1000],
[104, "power_a", tuya.valueConverter.raw],
[105, "voltage_b", tuya.valueConverter.divideBy10],
[106, "current_b", tuya.valueConverter.divideBy1000],
[107, "power_b", tuya.valueConverter.raw],
[108, "voltage_c", tuya.valueConverter.divideBy10],
[109, "current_c", tuya.valueConverter.divideBy1000],
[110, "power_c", tuya.valueConverter.raw],
[111, "power", tuya.valueConverter.raw],
[112, "energy_a", tuya.valueConverter.divideBy100],
[114, "energy_b", tuya.valueConverter.divideBy100],
[116, "energy_c", tuya.valueConverter.divideBy100],
[113, "energy_produced_a", tuya.valueConverter.divideBy100],
[115, "energy_produced_b", tuya.valueConverter.divideBy100],
[117, "energy_produced_c", tuya.valueConverter.divideBy100],
[118, "power_factor_a", tuya.valueConverter.raw],
[119, "power_factor_b", tuya.valueConverter.raw],
[120, "power_factor_c", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_gomuk3dc", "_TZE284_gomuk3dc", "_TZE200_gomuk3dc"]),
model: "SDM01V1.5",
vendor: "Tuya",
description: "Smart energy monitor for 3P+N system",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.voltageWithPhase("a"),
tuya.exposes.voltageWithPhase("b"),
tuya.exposes.voltageWithPhase("c"),
tuya.exposes.powerWithPhase("a"),
tuya.exposes.powerWithPhase("b"),
tuya.exposes.powerWithPhase("c"),
tuya.exposes.currentWithPhase("a"),
tuya.exposes.currentWithPhase("b"),
tuya.exposes.currentWithPhase("c"),
e.energy().withDescription("Total forward active energy"),
e.produced_energy().withDescription("Total reverse active energy"),
e.power_factor().withUnit("%").withDescription("Total power factor"),
e.power().withDescription("Total active power"),
e.ac_frequency(),
tuya.exposes.energyWithPhase("a"),
tuya.exposes.energyWithPhase("b"),
tuya.exposes.energyWithPhase("c"),
tuya.exposes.energyProducedWithPhase("a"),
tuya.exposes.energyProducedWithPhase("b"),
tuya.exposes.energyProducedWithPhase("c"),
tuya.exposes.powerFactorWithPhase("a"),
tuya.exposes.powerFactorWithPhase("b"),
tuya.exposes.powerFactorWithPhase("c"),
e
.numeric("update_frequency", ea.STATE_SET)
.withUnit("s")
.withDescription("Update frequency")
.withValueMin(5)
.withValueMax(3600)
.withPreset("default", 30, "Default value"),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[23, "produced_energy", tuya.valueConverter.divideBy100],
[29, "power", tuya.valueConverter.raw],
[32, "ac_frequency", tuya.valueConverter.divideBy100],
[50, "power_factor", tuya.valueConverter.raw],
[102, "update_frequency", tuya.valueConverterBasic.divideBy(1)],
[103, "voltage_a", tuya.valueConverter.divideBy10],
[104, "current_a", tuya.valueConverter.divideBy1000],
[105, "power_a", tuya.valueConverter.raw],
[108, "power_factor_a", tuya.valueConverter.raw],
[109, "energy_a", tuya.valueConverter.divideBy100],
[110, "energy_produced_a", tuya.valueConverter.divideBy100],
[112, "voltage_b", tuya.valueConverter.divideBy10],
[113, "current_b", tuya.valueConverter.divideBy1000],
[114, "power_b", tuya.valueConverter.raw],
[117, "power_factor_b", tuya.valueConverter.raw],
[118, "energy_b", tuya.valueConverter.divideBy100],
[119, "energy_produced_b", tuya.valueConverter.divideBy100],
[121, "voltage_c", tuya.valueConverter.divideBy10],
[122, "current_c", tuya.valueConverter.divideBy1000],
[123, "power_c", tuya.valueConverter.raw],
[126, "power_factor_c", tuya.valueConverter.raw],
[127, "energy_c", tuya.valueConverter.divideBy100],
[128, "energy_produced_c", tuya.valueConverter.divideBy100],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_78ioiaml", "_TZE284_78ioiaml", "_TZE200_78ioiaml"]),
model: "SDM02V1",
vendor: "Tuya",
description: "Smart energy monitor for 2P+N system",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.voltageWithPhase("l1"),
tuya.exposes.voltageWithPhase("l2"),
tuya.exposes.powerWithPhase("l1"),
tuya.exposes.powerWithPhase("l2"),
tuya.exposes.currentWithPhase("l1"),
tuya.exposes.currentWithPhase("l2"),
e.energy().withDescription("Total forward active energy"),
e.produced_energy().withDescription("Total reverse active energy"),
e.power_factor().withUnit("%").withDescription("Total power factor"),
e.power().withDescription("Total active power"),
e.ac_frequency(),
tuya.exposes.energyWithPhase("l1"),
tuya.exposes.energyWithPhase("l2"),
tuya.exposes.energyProducedWithPhase("l1"),
tuya.exposes.energyProducedWithPhase("l2"),
tuya.exposes.powerFactorWithPhase("l1"),
tuya.exposes.powerFactorWithPhase("l2"),
e
.numeric("update_frequency", ea.STATE_SET)
.withUnit("s")
.withDescription("Update frequency")
.withValueMin(5)
.withValueMax(3600)
.withPreset("default", 30, "Default value"),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[23, "produced_energy", tuya.valueConverter.divideBy100],
[29, "power", tuya.valueConverter.raw],
[32, "ac_frequency", tuya.valueConverter.divideBy100],
[50, "power_factor", tuya.valueConverter.raw],
[102, "update_frequency", tuya.valueConverterBasic.divideBy(1)],
[103, "voltage_l1", tuya.valueConverter.divideBy10],
[104, "current_l1", tuya.valueConverter.divideBy1000],
[105, "power_l1", tuya.valueConverter.raw],
[108, "power_factor_l1", tuya.valueConverter.raw],
[109, "energy_l1", tuya.valueConverter.divideBy100],
[110, "energy_produced_l1", tuya.valueConverter.divideBy100],
[112, "voltage_l2", tuya.valueConverter.divideBy10],
[113, "current_l2", tuya.valueConverter.divideBy1000],
[114, "power_l2", tuya.valueConverter.raw],
[117, "power_factor_l2", tuya.valueConverter.raw],
[118, "energy_l2", tuya.valueConverter.divideBy100],
[119, "energy_produced_l2", tuya.valueConverter.divideBy100],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_d2zfgtij", "_TZE284_d2zfgtij", "_TZE200_d2zfgtij"]),
model: "SPM01V1-GT",
vendor: "Tuya",
description: "Smart energy monitor for 1P+N system",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.voltage(),
e.power(),
e.current(),
e.power_factor().withUnit("%"),
e.ac_frequency(),
e.energy().withDescription("Total forward active energy"),
e.produced_energy().withDescription("Total reverse active energy"),
// e.binary('clear_energy', ea.SET, 'ON', 'OFF').withDescription('Switch to OFF to clear energy'),
e.binary("device_locating", ea.SET, "ON", "OFF").withDescription("Locate device"),
e
.numeric("update_frequency", ea.STATE_SET)
.withUnit("s")
.withDescription("Update frequency")
.withValueMin(5)
.withValueMax(3600)
.withPreset("default", 30, "Default value"),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[23, "produced_energy", tuya.valueConverter.divideBy100],
[6, null, tuya.valueConverter.phaseVariant3],
[32, "ac_frequency", tuya.valueConverter.divideBy100],
[50, "power_factor", tuya.valueConverter.raw],
// [12, 'clear_energy', tuya.valueConverter.onOff],
[101, "device_locating", tuya.valueConverter.onOff],
[102, "update_frequency", tuya.valueConverterBasic.divideBy(1)],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_wjk6rurm", "_TZE284_wjk6rurm", "_TZE200_wjk6rurm"]),
model: "SPM02V1-GT",
vendor: "Tuya",
description: "Smart energy monitor for 3P+N system",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.voltageWithPhase("a"),
tuya.exposes.voltageWithPhase("b"),
tuya.exposes.voltageWithPhase("c"),
tuya.exposes.powerWithPhase("a"),
tuya.exposes.powerWithPhase("b"),
tuya.exposes.powerWithPhase("c"),
tuya.exposes.currentWithPhase("a"),
tuya.exposes.currentWithPhase("b"),
tuya.exposes.currentWithPhase("c"),
e.power_factor().withUnit("%").withDescription("Total power factor"),
e.power().withDescription("Total active power"),
e.energy().withDescription("Total forward active energy"),
e.produced_energy().withDescription("Total reverse active energy"),
tuya.exposes.energyWithPhase("a"),
tuya.exposes.energyWithPhase("b"),
tuya.exposes.energyWithPhase("c"),
tuya.exposes.energyProducedWithPhase("a"),
tuya.exposes.energyProducedWithPhase("b"),
tuya.exposes.energyProducedWithPhase("c"),
tuya.exposes.powerFactorWithPhase("a"),
tuya.exposes.powerFactorWithPhase("b"),
tuya.exposes.powerFactorWithPhase("c"),
e
.numeric("update_frequency", ea.STATE_SET)
.withUnit("s")
.withDescription("Update frequency")
.withValueMin(30)
.withValueMax(3600)
.withPreset("default", 10, "Default value"),
// e.binary('clear_energy', ea.SET, 'ON', 'OFF').withDescription('Switch to OFF to clear energy'),
e.binary("device_locating", ea.SET, "ON", "OFF").withDescription("Locate device"),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[23, "produced_energy", tuya.valueConverter.divideBy100],
[6, null, tuya.valueConverter.phaseVariant2WithPhase("a")],
[7, null, tuya.valueConverter.phaseVariant2WithPhase("b")],
[8, null, tuya.valueConverter.phaseVariant2WithPhase("c")],
[29, "power", tuya.valueConverter.raw],
[32, "ac_frequency", tuya.valueConverter.divideBy100],
[50, "power_factor", tuya.valueConverter.raw],
// [12, 'clear_energy', tuya.valueConverter.onOff],
[101, "device_locating", tuya.valueConverter.onOff],
[102, "update_frequency", tuya.valueConverterBasic.divideBy(1)],
[108, "power_factor_a", tuya.valueConverter.raw],
[53, "energy_a", tuya.valueConverter.divideBy100],
[57, "energy_produced_a", tuya.valueConverter.divideBy100],
[117, "power_factor_b", tuya.valueConverter.raw],
[54, "energy_b", tuya.valueConverter.divideBy100],
[58, "energy_produced_b", tuya.valueConverter.divideBy100],
[126, "power_factor_c", tuya.valueConverter.raw],
[55, "energy_c", tuya.valueConverter.divideBy100],
[59, "energy_produced_c", tuya.valueConverter.divideBy100],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_s4sa1mcx", "_TZE284_s4sa1mcx", "_TZE200_s4sa1mcx"]),
model: "SDM01V1-GT",
vendor: "Tuya",
description: "Smart energy monitor for 3P+N system",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.voltageWithPhase("a"),
tuya.exposes.voltageWithPhase("b"),
tuya.exposes.voltageWithPhase("c"),
tuya.exposes.powerWithPhase("a"),
tuya.exposes.powerWithPhase("b"),
tuya.exposes.powerWithPhase("c"),
tuya.exposes.currentWithPhase("a"),
tuya.exposes.currentWithPhase("b"),
tuya.exposes.currentWithPhase("c"),
e.energy().withDescription("Total forward active energy"),
e.produced_energy().withDescription("Total reverse active energy"),
e.power_factor().withUnit("%").withDescription("Total power factor"),
e.power().withDescription("Total active power"),
e.ac_frequency(),
// e.binary('clear_energy', ea.SET, 'ON', 'OFF').withDescription('Switch to OFF to clear energy'),
e.binary("device_locating", ea.SET, "ON", "OFF").withDescription("Locate device"),
tuya.exposes.energyWithPhase("a"),
tuya.exposes.energyWithPhase("b"),
tuya.exposes.energyWithPhase("c"),
tuya.exposes.energyProducedWithPhase("a"),
tuya.exposes.energyProducedWithPhase("b"),
tuya.exposes.energyProducedWithPhase("c"),
tuya.exposes.powerFactorWithPhase("a"),
tuya.exposes.powerFactorWithPhase("b"),
tuya.exposes.powerFactorWithPhase("c"),
e
.numeric("update_frequency", ea.STATE_SET)
.withUnit("s")
.withDescription("Update frequency")
.withValueMin(5)
.withValueMax(3600)
.withPreset("default", 30, "Default value"),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[23, "produced_energy", tuya.valueConverter.divideBy100],
[6, null, tuya.valueConverter.phaseVariant2WithPhase("a")],
[7, null, tuya.valueConverter.phaseVariant2WithPhase("b")],
[8, null, tuya.valueConverter.phaseVariant2WithPhase("c")],
[29, "power", tuya.valueConverter.raw],
[32, "ac_frequency", tuya.valueConverter.divideBy100],
[50, "power_factor", tuya.valueConverter.raw],
// [12, 'clear_energy', tuya.valueConverter.onOff],
[101, "device_locating", tuya.valueConverter.onOff],
[102, "update_frequency", tuya.valueConverterBasic.divideBy(1)],
[108, "power_factor_a", tuya.valueConverter.raw],
[53, "energy_a", tuya.valueConverter.divideBy100],
[57, "energy_produced_a", tuya.valueConverter.divideBy100],
[117, "power_factor_b", tuya.valueConverter.raw],
[54, "energy_b", tuya.valueConverter.divideBy100],
[58, "energy_produced_b", tuya.valueConverter.divideBy100],
[126, "power_factor_c", tuya.valueConverter.raw],
[55, "energy_c", tuya.valueConverter.divideBy100],
[59, "energy_produced_c", tuya.valueConverter.divideBy100],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_x8diwkqb", "_TZE284_x8diwkqb", "_TZE200_x8diwkqb"]),
model: "SDM02V1-GT",
vendor: "Tuya",
description: "Smart energy monitor for 2P+N system",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.voltageWithPhase("l1"),
tuya.exposes.voltageWithPhase("l2"),
tuya.exposes.powerWithPhase("l1"),
tuya.exposes.powerWithPhase("l2"),
tuya.exposes.currentWithPhase("l1"),
tuya.exposes.currentWithPhase("l2"),
e.energy().withDescription("Total forward active energy"),
e.produced_energy().withDescription("Total reverse active energy"),
e.power_factor().withUnit("%").withDescription("Total power factor"),
e.power().withDescription("Total active power"),
e.ac_frequency(),
// e.binary('clear_energy', ea.SET, 'ON', 'OFF').withDescription('Switch to OFF to clear energy'),
e.binary("device_locating", ea.SET, "ON", "OFF").withDescription("Locate device"),
tuya.exposes.energyWithPhase("l1"),
tuya.exposes.energyWithPhase("l2"),
tuya.exposes.energyProducedWithPhase("l1"),
tuya.exposes.energyProducedWithPhase("l2"),
tuya.exposes.powerFactorWithPhase("l1"),
tuya.exposes.powerFactorWithPhase("l2"),
e
.numeric("update_frequency", ea.STATE_SET)
.withUnit("s")
.withDescription("Update frequency")
.withValueMin(5)
.withValueMax(3600)
.withPreset("default", 30, "Default value"),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[6, null, tuya.valueConverter.phaseVariant2WithPhase("l1")],
[7, null, tuya.valueConverter.phaseVariant2WithPhase("l2")],
// [12, 'clear_energy', tuya.valueConverter.onOff],
[23, "produced_energy", tuya.valueConverter.divideBy100],
[29, "power", tuya.valueConverter.raw],
[32, "ac_frequency", tuya.valueConverter.divideBy100],
[50, "power_factor", tuya.valueConverter.raw],
[53, "energy_l1", tuya.valueConverter.divideBy100],
[54, "energy_l2", tuya.valueConverter.divideBy100],
[57, "energy_produced_l1", tuya.valueConverter.divideBy100],
[58, "energy_produced_l2", tuya.valueConverter.divideBy100],
[101, "device_locating", tuya.valueConverter.onOff],
[102, "update_frequency", tuya.valueConverterBasic.divideBy(1)],
[108, "power_factor_l1", tuya.valueConverter.raw],
[117, "power_factor_l2", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_ac0fhfiq", "_TZE284_ac0fhfiq"]),
model: "TS0601_bidirectional_energy meter",
vendor: "Tuya",
description: "Bidirectional energy meter with 150A Current Clamp",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.energy(),
e.produced_energy(),
e.power(),
e.voltage(),
e.current(),
e.enum("energy_flow", ea.STATE, ["consuming", "producing"]).withDescription("Direction of energy"),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[2, "produced_energy", tuya.valueConverter.divideBy100],
[6, null, tuya.valueConverter.phaseVariant3],
[102, "energy_flow", tuya.valueConverterBasic.lookup({ consuming: 0, producing: 1 })],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE200_vmcgja59",
"_TZE204_dvosyycn",
"_TZE284_dvosyycn",
"_TZE200_wktrysab",
"_TZE204_wktrysab",
"_TZE204_nvxorhcj",
"_TZE204_tdhnhhiy",
"_TZE204_ad2jkxwh",
"_TZE204_72bewjky",
"_TZE284_kow4ok3t",
]),
model: "TS0601_switch_8",
vendor: "ZYXH",
description: "8 gang switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.switch().withEndpoint("l1"),
tuya.exposes.switch().withEndpoint("l2"),
tuya.exposes.switch().withEndpoint("l3"),
tuya.exposes.switch().withEndpoint("l4"),
tuya.exposes.switch().withEndpoint("l5"),
tuya.exposes.switch().withEndpoint("l6"),
tuya.exposes.switch().withEndpoint("l7"),
tuya.exposes.switch().withEndpoint("l8"),
],
endpoint: (device) => {
return { l1: 1, l2: 1, l3: 1, l4: 1, l5: 1, l6: 1, l7: 1, l8: 1 };
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
[4, "state_l4", tuya.valueConverter.onOff],
[5, "state_l5", tuya.valueConverter.onOff],
[6, "state_l6", tuya.valueConverter.onOff],
[0x65, "state_l7", tuya.valueConverter.onOff],
[0x66, "state_l8", tuya.valueConverter.onOff],
],
},
whiteLabel: [tuya.whitelabel("Nova Digital", "ZTS-8W-B", "8 Gang Switch", ["_TZE204_nvxorhcj"])],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_adlblwab"]),
model: "TS0601_switch_8_2",
vendor: "Tuya",
description: "8 gang switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.switch().withEndpoint("l1"),
tuya.exposes.switch().withEndpoint("l2"),
tuya.exposes.switch().withEndpoint("l3"),
tuya.exposes.switch().withEndpoint("l4"),
tuya.exposes.switch().withEndpoint("l5"),
tuya.exposes.switch().withEndpoint("l6"),
tuya.exposes.switch().withEndpoint("l7"),
tuya.exposes.switch().withEndpoint("l8"),
tuya.exposes.countdown().withEndpoint("l1"),
tuya.exposes.countdown().withEndpoint("l2"),
tuya.exposes.countdown().withEndpoint("l3"),
tuya.exposes.countdown().withEndpoint("l4"),
tuya.exposes.countdown().withEndpoint("l5"),
tuya.exposes.countdown().withEndpoint("l6"),
tuya.exposes.countdown().withEndpoint("l7"),
tuya.exposes.countdown().withEndpoint("l8"),
e.power_on_behavior().withAccess(ea.STATE_SET),
],
endpoint: (device) => {
return { l1: 1, l2: 1, l3: 1, l4: 1, l5: 1, l6: 1, l7: 1, l8: 1 };
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
[4, "state_l4", tuya.valueConverter.onOff],
[5, "state_l5", tuya.valueConverter.onOff],
[6, "state_l6", tuya.valueConverter.onOff],
[7, "state_l7", tuya.valueConverter.onOff],
[8, "state_l8", tuya.valueConverter.onOff],
[9, "countdown_l1", tuya.valueConverter.countdown],
[10, "countdown_l2", tuya.valueConverter.countdown],
[11, "countdown_l3", tuya.valueConverter.countdown],
[12, "countdown_l4", tuya.valueConverter.countdown],
[13, "countdown_l5", tuya.valueConverter.countdown],
[14, "countdown_l6", tuya.valueConverter.countdown],
[15, "countdown_l7", tuya.valueConverter.countdown],
[16, "countdown_l8", tuya.valueConverter.countdown],
[27, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_7sjncirf", "TZE204_7sjncirf"]),
model: "TS0601_switch_10",
vendor: "Tuya",
description: "10 gang switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [...Array.from({ length: 10 }, (_, i) => tuya.exposes.switch().withEndpoint(`l${i + 1}`))],
endpoint: (device) => {
return {
l1: 1,
l2: 1,
l3: 1,
l4: 1,
l5: 1,
l6: 1,
l7: 1,
l8: 1,
l9: 1,
l10: 1,
l11: 1,
l12: 1,
};
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
[4, "state_l4", tuya.valueConverter.onOff],
[5, "state_l5", tuya.valueConverter.onOff],
[6, "state_l6", tuya.valueConverter.onOff],
[0x65, "state_l7", tuya.valueConverter.onOff],
[0x66, "state_l8", tuya.valueConverter.onOff],
[0x67, "state_l9", tuya.valueConverter.onOff],
[0x68, "state_l10", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_dqolcpcp", "_TZE284_dqolcpcp"]),
model: "TS0601_switch_12",
vendor: "ZYXH",
description: "12 gang switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [...Array.from({ length: 12 }, (_, i) => tuya.exposes.switch().withEndpoint(`l${i + 1}`))],
endpoint: (device) => {
return {
l1: 1,
l2: 1,
l3: 1,
l4: 1,
l5: 1,
l6: 1,
l7: 1,
l8: 1,
l9: 1,
l10: 1,
l11: 1,
l12: 1,
};
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
[4, "state_l4", tuya.valueConverter.onOff],
[5, "state_l5", tuya.valueConverter.onOff],
[6, "state_l6", tuya.valueConverter.onOff],
[0x65, "state_l7", tuya.valueConverter.onOff],
[0x66, "state_l8", tuya.valueConverter.onOff],
[0x67, "state_l9", tuya.valueConverter.onOff],
[0x68, "state_l10", tuya.valueConverter.onOff],
[0x69, "state_l11", tuya.valueConverter.onOff],
[0x6a, "state_l12", tuya.valueConverter.onOff],
],
},
},
// TS011F
{
fingerprint: tuya.fingerprint("TS011F", ["_TZ3000_8fdayfch", "_TZ3000_1hwjutgo", "_TZ3000_lnggrqqi", "_TZ3000_tvuarksa"]),
model: "TS011F_1",
vendor: "Tuya",
description: "Switch",
extend: [tuya.modernExtend.tuyaBase(), tuya.modernExtend.tuyaOnOff()],
whiteLabel: [
{ vendor: "Mumubiz", model: "ZJSB9-80Z" },
tuya.whitelabel("KTNNKG", "ZB1248-10A", "Relay switch", ["_TZ3000_8fdayfch"]),
tuya.whitelabel("UseeLink", "SM-AZ713", "Smart water/gas valve", ["_TZ3000_tvuarksa"]),
],
},
{
fingerprint: tuya.fingerprint("TS011F", ["_TZ3000_rqbjepe8", "_TZ3000_uwkja6z1"]),
model: "TS011F_4",
description: "2 gang plug",
vendor: "Tuya",
ota: true,
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
electricalMeasurements: true,
powerOutageMemory: true,
indicatorMode: true,
childLock: true,
endpoints: ["l1", "l2"],
}),
tuya.modernExtend.electricityMeasurementPoll(),
],
endpoint: (device) => {
return { l1: 1, l2: 2 };
},
meta: {
multiEndpoint: true,
multiEndpointSkip: ["energy", "current", "voltage", "power"],
},
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
const endpoint = device.getEndpoint(1);
endpoint.saveClusterAttributeKeyValue("haElectricalMeasurement", {
acCurrentDivisor: 1000,
acCurrentMultiplier: 1,
});
endpoint.saveClusterAttributeKeyValue("seMetering", {
divisor: 100,
multiplier: 1,
});
device.save();
},
whiteLabel: [tuya.whitelabel("Nous", "A4Z", "2 gang outdoor plug", ["_TZ3000_rqbjepe8", "_TZ3000_uwkja6z1"])],
},
{
fingerprint: tuya.fingerprint("TS011F", ["_TZ3000_cfnprab5", "_TZ3000_o005nuxx", "_TZ3000_gdyjfvgm", "_TZ3000_pl5v1yyy", "_TZ3000_djgzdba9"]),
model: "TS011F_5",
description: "Power strip 5 gang",
vendor: "Tuya",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
powerOutageMemory: true,
childLock: true,
endpoints: ["l1", "l2", "l3", "l4", "l5"],
}),
],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3, l4: 4, l5: 5 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
for (const ID of [1, 2, 3, 4, 5]) {
await reporting.bind(device.getEndpoint(ID), coordinatorEndpoint, ["genOnOff"]);
}
},
whiteLabel: [
tuya.whitelabel("UseeLink", "SM-0306E-2W", "4 gang switch, with USB", ["_TZ3000_cfnprab5"]),
tuya.whitelabel("UseeLink", "SM-O301-AZ", "AU 4 plug 10A power board + USB", ["_TZ3000_o005nuxx"]),
tuya.whitelabel("Lotus", "Ref 2117", "4 gang plug + 4 USB", ["_TZ3000_djgzdba9"]),
],
},
{
zigbeeModel: ["SM0202"],
model: "SM0202",
vendor: "Tuya",
description: "Motion sensor",
fromZigbee: [fz.ias_occupancy_alarm_1_with_timeout, fz.battery],
toZigbee: [],
exposes: [e.occupancy(), e.battery_low(), e.battery(), e.battery_voltage()],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genPowerCfg"]);
await reporting.batteryPercentageRemaining(endpoint);
},
whiteLabel: [
tuya.whitelabel("Cleverio", "SS200", "Motion sensor", ["_TYZB01_z2umiwvq"]),
tuya.whitelabel("Marmitek", "SM0202_1", "Motion sensor", ["_TYZB01_yr95mpib"]),
],
},
{
fingerprint: tuya.fingerprint("TS0001", ["_TZ3210_dse8ogfy", "_TZ3210_j4pdtz9v", "_TZ3210_7vgttna6", "_TZ3210_a04acm9s", "_TZ3210_cm9mbpr1"]),
model: "TS0001_fingerbot",
vendor: "Tuya",
description: "Zigbee fingerbot plus",
whiteLabel: [tuya.whitelabel("Adaprox", "TS0001_fingerbot_1", "Zigbee fingerbot plus", ["_TZ3210_dse8ogfy"])],
fromZigbee: [fz.on_off],
toZigbee: [tz.on_off],
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.switch(),
e.battery(),
e.enum("mode", ea.STATE_SET, ["click", "switch", "program"]).withDescription("Working mode"),
e.numeric("lower", ea.STATE_SET).withValueMin(50).withValueMax(100).withValueStep(1).withUnit("%").withDescription("Down movement limit"),
e.numeric("upper", ea.STATE_SET).withValueMin(0).withValueMax(50).withValueStep(1).withUnit("%").withDescription("Up movement limit"),
e.numeric("delay", ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(1).withUnit("s").withDescription("Sustain time"),
e.binary("reverse", ea.STATE_SET, "ON", "OFF").withDescription("Reverse"),
e.binary("touch", ea.STATE_SET, "ON", "OFF").withDescription("Touch control"),
],
configure: async (device, coordinatorEndpoint) => {
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
},
meta: {
tuyaSendCommand: "sendData",
tuyaDatapoints: [
[
0x65,
"mode",
tuya.valueConverterBasic.lookup({
click: tuya.enum(0),
switch: tuya.enum(1),
program: tuya.enum(2),
}),
],
[0x66, "lower", tuya.valueConverter.raw],
[0x67, "delay", tuya.valueConverter.raw],
[
0x68,
"reverse",
tuya.valueConverterBasic.lookup({
ON: tuya.enum(1),
OFF: tuya.enum(0),
}),
],
[0x69, "battery", tuya.valueConverter.raw],
[0x6a, "upper", tuya.valueConverter.raw],
[0x6b, "touch", tuya.valueConverterBasic.lookup({ ON: true, OFF: false })],
// ? [0x6c, '', tuya.valueConverter.onOff],
[0x6d, "program", tuya.valueConverter.raw],
// ? [0x70, '', tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_8eazvzo6"]),
model: "SWS6TZ-WHITE",
vendor: "Tuya",
description: "6 gang wall switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.switch().withEndpoint("l1"),
e.switch().withEndpoint("l2"),
e.switch().withEndpoint("l3"),
e.switch().withEndpoint("l4"),
e.switch().withEndpoint("l5"),
e.switch().withEndpoint("l6"),
e.current(),
e.power(),
e.voltage(),
],
endpoint: (device) => {
return { l1: 1, l2: 1, l3: 1, l4: 1, l5: 1, l6: 1 };
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
[4, "state_l4", tuya.valueConverter.onOff],
[5, "state_l5", tuya.valueConverter.onOff],
[6, "state_l6", tuya.valueConverter.onOff],
[21, "current", tuya.valueConverter.divideBy1000],
[22, "power", tuya.valueConverter.divideBy10],
[23, "voltage", tuya.valueConverter.divideBy10],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_0j5jma9b", "_TZE200_h2rctifa"]),
model: "ZS-TYG3-SM-61Z",
vendor: "Tuya",
description: "Smart switch (4 gang + 2 scene) with backlight and neutral wire",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.switch().withDescription("All Switches"),
tuya.exposes.switch().withEndpoint("l1"),
tuya.exposes.switch().withEndpoint("l2"),
tuya.exposes.switch().withEndpoint("l3"),
tuya.exposes.switch().withEndpoint("l4"),
tuya.exposes.switch().withEndpoint("l5"),
tuya.exposes.switch().withEndpoint("l6"),
tuya.exposes.backlightModeOffOn().withAccess(ea.STATE_SET),
tuya.exposes.countdown().withEndpoint("l1"),
tuya.exposes.countdown().withEndpoint("l2"),
tuya.exposes.countdown().withEndpoint("l3"),
tuya.exposes.countdown().withEndpoint("l4"),
tuya.exposes.countdown().withEndpoint("l5"),
tuya.exposes.countdown().withEndpoint("l6"),
e.power_on_behavior().withAccess(ea.STATE_SET),
],
endpoint: (device) => {
return {
l1: 1,
l2: 1,
l3: 1,
l4: 1,
l5: 1,
l6: 1,
state: 1,
backlight: 1,
};
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
[4, "state_l4", tuya.valueConverter.onOff],
[5, "state_l5", tuya.valueConverter.onOff],
[6, "state_l6", tuya.valueConverter.onOff],
[7, "countdown_l1", tuya.valueConverter.countdown],
[8, "countdown_l2", tuya.valueConverter.countdown],
[9, "countdown_l3", tuya.valueConverter.countdown],
[10, "countdown_l4", tuya.valueConverter.countdown],
[11, "countdown_l5", tuya.valueConverter.countdown],
[12, "countdown_l6", tuya.valueConverter.countdown],
[13, "state", tuya.valueConverter.onOff],
[14, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum],
[16, "backlight_mode", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_wunufsil"]),
model: "ZS-TYG3-SM-21Z",
vendor: "Tuya",
description: "2 gang smart switch with backlight and neutral wire",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.switch().withDescription("All Switches"),
tuya.exposes.switch().withEndpoint("l1"),
tuya.exposes.switch().withEndpoint("l2"),
tuya.exposes.backlightModeOffOn().withAccess(ea.STATE_SET),
tuya.exposes.countdown().withEndpoint("l1"),
tuya.exposes.countdown().withEndpoint("l2"),
e.power_on_behavior().withAccess(ea.STATE_SET),
],
endpoint: (device) => {
return { l1: 1, l2: 1, state: 1, backlight: 1 };
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[7, "countdown_l1", tuya.valueConverter.countdown],
[8, "countdown_l2", tuya.valueConverter.countdown],
[13, "state", tuya.valueConverter.onOff],
[14, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum],
[16, "backlight_mode", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_vhy3iakz"]),
model: "ZS-TYG3-SM-31Z",
vendor: "Tuya",
description: "3 gang smart switch with backlight and neutral wire",
exposes: [
tuya.exposes.switch().withDescription("All Switches"),
tuya.exposes.switch().withEndpoint("l1"),
tuya.exposes.switch().withEndpoint("l2"),
tuya.exposes.switch().withEndpoint("l3"),
tuya.exposes.backlightModeOffOn().withAccess(ea.STATE_SET),
tuya.exposes.countdown().withEndpoint("l1"),
tuya.exposes.countdown().withEndpoint("l2"),
tuya.exposes.countdown().withEndpoint("l3"),
e.power_on_behavior(["off", "on", "previous"]).withAccess(ea.STATE_SET),
],
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
endpoint: (device) => {
return { l1: 1, l2: 1, l3: 1, state: 1, backlight: 1 };
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
[7, "countdown_l1", tuya.valueConverter.countdown],
[8, "countdown_l2", tuya.valueConverter.countdown],
[9, "countdown_l3", tuya.valueConverter.countdown],
[13, "state", tuya.valueConverter.onOff],
[14, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum],
[16, "backlight_mode", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_k6jhsr0q", "_TZE204_unsxl4ir"]),
model: "ZS-TYG3-SM-41Z",
vendor: "Tuya",
description: "4 gang smart switch with backlight and neutral wire",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.switch().withDescription("All Switches"),
tuya.exposes.switch().withEndpoint("l1"),
tuya.exposes.switch().withEndpoint("l2"),
tuya.exposes.switch().withEndpoint("l3"),
tuya.exposes.switch().withEndpoint("l4"),
tuya.exposes.backlightModeOffOn().withAccess(ea.STATE_SET),
tuya.exposes.countdown().withEndpoint("l1"),
tuya.exposes.countdown().withEndpoint("l2"),
tuya.exposes.countdown().withEndpoint("l3"),
tuya.exposes.countdown().withEndpoint("l4"),
e.power_on_behavior(["off", "on", "previous"]).withAccess(ea.STATE_SET),
],
endpoint: (device) => {
return { l1: 1, l2: 1, l3: 1, l4: 1, state: 1, backlight: 1 };
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
[4, "state_l4", tuya.valueConverter.onOff],
[7, "countdown_l1", tuya.valueConverter.countdown],
[8, "countdown_l2", tuya.valueConverter.countdown],
[9, "countdown_l3", tuya.valueConverter.countdown],
[10, "countdown_l4", tuya.valueConverter.countdown],
[13, "state", tuya.valueConverter.onOff],
[14, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum],
[16, "backlight_mode", tuya.valueConverter.onOff],
],
},
whiteLabel: [tuya.whitelabel("Nova Digital", "FZB-4", "Interruptor de 4 canais com backlight e neutro", ["TZE204_unsxl4ir"])],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_nvodulvi", "_TZE284_nvodulvi"]),
model: "M9-zigbee-SL",
vendor: "Tuya",
description: "Smart Switch (4 gang + 4 scene) with neutral wire and motion sensing",
fromZigbee: [fz.ias_occupancy_only_alarm_2, tuya.fz.indicator_mode],
toZigbee: [tuya.tz.power_on_behavior_1, tuya.tz.backlight_indicator_mode_1],
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
...[1, 2, 3, 4, 5, 6, 7, 8].map((i) => tuya.exposes.switch().withEndpoint(`l${i}`)),
...[1, 2, 3, 4, 5, 6, 7, 8].map((i) => e.power_on_behavior().withAccess(ea.STATE_SET).withEndpoint(`l${i}`)),
...[1, 2, 3, 4, 5, 6, 7, 8].map((i) => tuya.exposes.switchMode().withEndpoint(`l${i}`)),
...[1, 2, 3, 4, 5, 6, 7, 8].map((i) => tuya.exposes.lightMode().withEndpoint(`l${i}`)),
tuya.exposes.backlightModeOffOn().withAccess(ea.STATE_SET),
e.action(["scene_1", "scene_2", "scene_3", "scene_4", "scene_5", "scene_6", "scene_7", "scene_8"]),
e.presence(),
new exposes.Numeric("delay", ea.STATE_SET).withUnit("sec").withDescription("light off delay").withValueMin(0).withValueMax(1000),
],
endpoint: (device) => {
return {
l1: 1,
l2: 1,
l3: 1,
l4: 1,
l5: 1,
l6: 1,
state: 1,
backlight: 1,
l7: 1,
l8: 1,
};
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
[4, "state_l4", tuya.valueConverter.onOff],
[5, "state_l5", tuya.valueConverter.onOff],
[6, "state_l6", tuya.valueConverter.onOff],
[16, "backlight_mode", tuya.valueConverter.onOff],
[29, "power_on_behavior_l1", tuya.valueConverter.powerOnBehaviorEnum],
[30, "power_on_behavior_l2", tuya.valueConverter.powerOnBehaviorEnum],
[31, "power_on_behavior_l3", tuya.valueConverter.powerOnBehaviorEnum],
[32, "power_on_behavior_l4", tuya.valueConverter.powerOnBehaviorEnum],
[33, "power_on_behavior_l5", tuya.valueConverter.powerOnBehaviorEnum],
[34, "power_on_behavior_l6", tuya.valueConverter.powerOnBehaviorEnum],
[101, "action", tuya.valueConverter.static("scene_1")],
[102, "action", tuya.valueConverter.static("scene_2")],
[103, "action", tuya.valueConverter.static("scene_3")],
[104, "action", tuya.valueConverter.static("scene_4")],
[105, "presence", tuya.valueConverter.raw],
[106, "delay", tuya.valueConverter.raw],
[108, "action", tuya.valueConverter.static("scene_5")],
[109, "action", tuya.valueConverter.static("scene_6")],
[110, "action", tuya.valueConverter.static("scene_7")],
[111, "action", tuya.valueConverter.static("scene_8")],
[112, "state_l7", tuya.valueConverter.onOff],
[113, "state_l8", tuya.valueConverter.onOff],
[114, "switch_mode_l1", tuya.valueConverter.switchMode],
[115, "switch_mode_l2", tuya.valueConverter.switchMode],
[116, "switch_mode_l3", tuya.valueConverter.switchMode],
[117, "switch_mode_l4", tuya.valueConverter.switchMode],
[118, "switch_mode_l5", tuya.valueConverter.switchMode],
[119, "switch_mode_l6", tuya.valueConverter.switchMode],
[120, "switch_mode_l7", tuya.valueConverter.switchMode],
[121, "switch_mode_l8", tuya.valueConverter.switchMode],
[122, "light_mode_l1", tuya.valueConverter.lightMode],
[123, "light_mode_l2", tuya.valueConverter.lightMode],
[124, "light_mode_l3", tuya.valueConverter.lightMode],
[125, "light_mode_l4", tuya.valueConverter.lightMode],
[126, "light_mode_l5", tuya.valueConverter.lightMode],
[127, "light_mode_l6", tuya.valueConverter.lightMode],
[128, "light_mode_l7", tuya.valueConverter.lightMode],
[129, "light_mode_l8", tuya.valueConverter.lightMode],
[130, "power_on_behavior_l7", tuya.valueConverter.powerOnBehaviorEnum],
[131, "power_on_behavior_l8", tuya.valueConverter.powerOnBehaviorEnum],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_yrwmnya3"]),
model: "M9-zigbee-SL-2",
vendor: "Tuya",
description: "Smart switch (4 gang + 4 scene) with neutral wire and motion sensing",
extend: [tuya.modernExtend.tuyaBase({ dp: true }), m.deviceEndpoints({ endpoints: { l1: 1, l2: 1, l3: 1, l4: 1, state: 1, backlight: 1 } })],
fromZigbee: [fz.ias_occupancy_only_alarm_2, tuya.fz.indicator_mode],
toZigbee: [tuya.tz.power_on_behavior_1, tuya.tz.backlight_indicator_mode_1],
exposes: [
...[1, 2, 3, 4].map((i) => tuya.exposes.switch().withEndpoint(`l${i}`)),
...[0, 1, 2, 3, 4].map((i) => e.power_on_behavior().withAccess(ea.STATE_SET).withEndpoint(`l${i}`)),
...[1, 2, 3, 4].map((i) => tuya.exposes.switchMode().withEndpoint(`l${i}`)),
tuya.exposes.backlightModeOffOn().withAccess(ea.STATE_SET),
e.action(["scene_0", "scene_1", "scene_2", "scene_3", "scene_4", "scene_5", "scene_6", "scene_7", "scene_8"]),
e.presence(),
e.numeric("delay", ea.STATE_SET).withUnit("s").withDescription("Light off delay").withValueMin(0).withValueMax(1000),
],
meta: {
tuyaDatapoints: [
[1, "action", tuya.valueConverter.static("scene_1")],
[2, "action", tuya.valueConverter.static("scene_2")],
[3, "action", tuya.valueConverter.static("scene_3")],
[4, "action", tuya.valueConverter.static("scene_4")],
[5, "action", tuya.valueConverter.static("scene_5")],
[6, "action", tuya.valueConverter.static("scene_6")],
[7, "action", tuya.valueConverter.static("scene_7")],
[8, "action", tuya.valueConverter.static("scene_8")],
[17, "action", tuya.valueConverter.static("scene_0")],
[18, "switch_mode_l1", tuya.valueConverter.switchMode],
[19, "switch_mode_l2", tuya.valueConverter.switchMode],
[20, "switch_mode_l3", tuya.valueConverter.switchMode],
[21, "switch_mode_l4", tuya.valueConverter.switchMode],
[24, "state_l1", tuya.valueConverter.onOff],
[25, "state_l2", tuya.valueConverter.onOff],
[26, "state_l3", tuya.valueConverter.onOff],
[27, "state_l4", tuya.valueConverter.onOff],
[36, "backlight_mode", tuya.valueConverter.onOff],
[38, "power_on_behavior_l0", tuya.valueConverter.powerOnBehaviorEnum],
[39, "power_on_behavior_l1", tuya.valueConverter.powerOnBehaviorEnum],
[40, "power_on_behavior_l2", tuya.valueConverter.powerOnBehaviorEnum],
[41, "power_on_behavior_l3", tuya.valueConverter.powerOnBehaviorEnum],
[42, "power_on_behavior_l4", tuya.valueConverter.powerOnBehaviorEnum],
[101, "presence", tuya.valueConverter.raw],
[102, "delay", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_w2vunxzm"]),
model: "_TZE204_w2vunxzm",
vendor: "Tuya",
description: "Air pressure sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.pressure(),
e.temperature(),
e
.numeric("display_brightness", ea.STATE_SET)
.withValueMin(0)
.withValueMax(8)
.withValueStep(1)
.withDescription("Display brightness level (0 = off)"),
],
meta: {
tuyaDatapoints: [
[101, "pressure", tuya.valueConverter.divideBy10],
[8, "temperature", tuya.valueConverter.divideBy100],
[102, "display_brightness", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_sooucan5", "_TZE204_oqtpvx51"]),
model: "YXZBRB58",
vendor: "Tuya",
description: "Smart human presence sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.illuminance(),
e.presence(),
e.numeric("target_distance", ea.STATE).withDescription("Distance to target").withUnit("m"),
e.numeric("radar_sensitivity", ea.STATE_SET).withValueMin(0).withValueMax(9).withValueStep(1).withDescription("Sensitivity of the radar"),
e
.numeric("minimum_range", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(0.1)
.withDescription("Minimum range")
.withUnit("m"),
e
.numeric("maximum_range", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(0.1)
.withDescription("Maximum range")
.withUnit("m"),
e
.numeric("detection_delay", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(0.1)
.withDescription("Detection delay")
.withUnit("s"),
e.numeric("fading_time", ea.STATE_SET).withValueMin(0).withValueMax(1500).withValueStep(1).withDescription("Fading time").withUnit("s"),
e
.enum("radar_scene", ea.STATE_SET, ["default", "bathroom", "bedroom", "sleeping", "unknown"])
.withDescription("Presets for sensitivity for presence and movement"),
],
meta: {
tuyaDatapoints: [
[0x01, "presence", tuya.valueConverter.trueFalse1],
[0x02, "radar_sensitivity", tuya.valueConverter.raw],
[0x03, "minimum_range", tuya.valueConverter.divideBy100],
[0x04, "maximum_range", tuya.valueConverter.divideBy100],
[0x65, "illuminance", tuya.valueConverter.raw],
[0x66, "detection_delay", tuya.valueConverter.divideBy10],
[0x67, "fading_time", tuya.valueConverter.divideBy10],
[
0x68,
"radar_scene",
tuya.valueConverterBasic.lookup({
default: tuya.enum(0),
bathroom: tuya.enum(1),
bedroom: tuya.enum(2),
sleeping: tuya.enum(3),
unknown: tuya.enum(4),
}),
],
[0x69, "target_distance", tuya.valueConverter.divideBy100],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_86nbew0j", "_TZE200_io0zdqh1", "_TZE200_drs6j6m5", "_TZE200_ywe90lt0", "_TZE200_qyss8gjy"]),
model: "TS0601_light",
vendor: "Tuya",
description: "Light",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [tuya.exposes.lightBrightness(), e.power_on_behavior().withAccess(ea.STATE_SET)],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff, { skip: tuya.skip.stateOnAndBrightnessPresent }],
[2, "power_on_behavior", tuya.valueConverter.powerOnBehavior],
[3, "brightness", tuya.valueConverter.scale0_254to0_1000],
],
},
whiteLabel: [
tuya.whitelabel("Ltech", "TY-12-100-400-W1Z", "12W 100-400mA Zigbee CC Dimmable LED driver", ["_TZE200_86nbew0j"]),
tuya.whitelabel("Ltech", "TY-75-24-G2Z2", "150W 24V Zigbee CV tunable white LED driver", ["_TZE200_io0zdqh1"]),
tuya.whitelabel("Lifud", "LF-AAZ012-0400-42", "Zigbee dimmable LED driver 4-40W 220-240Vac", ["_TZE200_drs6j6m5"]),
tuya.whitelabel("Lifud", "LF-GAZ150A6250-24", "Lifud Zigbee LED Driver CCT 150W 24V", ["_TZE200_ywe90lt0"]),
tuya.whitelabel("Lifud", "LF-GAZ150B6250-24", "Lifud Zigbee LED Driver 150W 24V", ["_TZE204_drs6j6m5"]),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_k7mfgaen", "_TZE204_fncxk3ob", "_TZE284_fncxk3ob"]),
model: "YXZBSL",
vendor: "Tuya",
description: "Smart siren",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.binary("alarm", ea.STATE_SET, "ON", "OFF").withDescription("Turn the light of the alarm ON/OFF"),
e.enum("type", ea.STATE_SET, ["sound", "light", "sound+light", "normal"]).withDescription("Alarm type"),
e.enum("volume", ea.STATE_SET, ["mute", "low", "middle", "high"]).withDescription("Volume of the alarm"),
e
.enum("ringtone", ea.STATE_SET, [
"melody1",
"melody2",
"melody3",
"melody4",
"melody5",
"melody6",
"melody7",
"melody8",
"door",
"water",
"temperature",
"entered",
"left",
])
.withDescription("Ringtone of the alarm"),
e.enum("power_type", ea.STATE, ["battery", "cable"]).withDescription("Power type"),
e
.numeric("duration", ea.STATE_SET)
.withValueMin(1)
.withValueMax(60)
.withValueStep(1)
.withUnit("min")
.withDescription("Duration of the alarm"),
e.enum("battery_level", ea.STATE, ["low", "middle", "high"]).withDescription("Battery level state"),
e.battery(),
],
meta: {
tuyaDatapoints: [
[
1,
"type",
tuya.valueConverterBasic.lookup({
sound: tuya.enum(0),
light: tuya.enum(1),
"sound+light": tuya.enum(2),
normal: tuya.enum(3),
}),
],
[
5,
"volume",
tuya.valueConverterBasic.lookup({
low: tuya.enum(0),
middle: tuya.enum(1),
high: tuya.enum(2),
mute: tuya.enum(3),
}),
],
[6, "power_type", tuya.valueConverterBasic.lookup({ cable: false, battery: true })],
[7, "duration", tuya.valueConverter.raw],
[13, "alarm", tuya.valueConverter.onOff],
[
14,
"battery_level",
tuya.valueConverterBasic.lookup({
low: tuya.enum(0),
middle: tuya.enum(1),
high: tuya.enum(2),
}),
],
[15, "battery", tuya.valueConverter.raw],
[
21,
"ringtone",
tuya.valueConverterBasic.lookup({
melody1: tuya.enum(0),
melody2: tuya.enum(1),
melody3: tuya.enum(2),
melody4: tuya.enum(3),
melody5: tuya.enum(4),
melody6: tuya.enum(5),
melody7: tuya.enum(6),
melody8: tuya.enum(7),
door: tuya.enum(8),
water: tuya.enum(9),
temperature: tuya.enum(10),
entered: tuya.enum(11),
left: tuya.enum(12),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_vmcgja59", "_TZE284_vmcgja59"]),
model: "ZYXH_switch_24",
vendor: "ZYXH",
description: "24 gang switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [...Array.from(Array(24).keys()).map((ep) => tuya.exposes.switch().withEndpoint(`l${ep + 1}`))],
endpoint: (device) => {
return {
l1: 1,
l2: 1,
l3: 1,
l4: 1,
l5: 1,
l6: 1,
l7: 1,
l8: 1,
l9: 1,
l10: 1,
l11: 1,
l12: 1,
l13: 1,
l14: 1,
l15: 1,
l16: 1,
l17: 1,
l18: 1,
l19: 1,
l20: 1,
l21: 1,
l22: 1,
l23: 1,
l24: 1,
};
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
[4, "state_l4", tuya.valueConverter.onOff],
[5, "state_l5", tuya.valueConverter.onOff],
[6, "state_l6", tuya.valueConverter.onOff],
[0x65, "state_l7", tuya.valueConverter.onOff],
[0x66, "state_l8", tuya.valueConverter.onOff],
[0x67, "state_l9", tuya.valueConverter.onOff],
[0x68, "state_l10", tuya.valueConverter.onOff],
[0x69, "state_l11", tuya.valueConverter.onOff],
[0x6a, "state_l12", tuya.valueConverter.onOff],
[0x6b, "state_l13", tuya.valueConverter.onOff],
[0x6c, "state_l14", tuya.valueConverter.onOff],
[0x6d, "state_l15", tuya.valueConverter.onOff],
[0x6e, "state_l16", tuya.valueConverter.onOff],
[0x6f, "state_l17", tuya.valueConverter.onOff],
[0x70, "state_l18", tuya.valueConverter.onOff],
[0x71, "state_l19", tuya.valueConverter.onOff],
[0x72, "state_l20", tuya.valueConverter.onOff],
[0x73, "state_l21", tuya.valueConverter.onOff],
[0x74, "state_l22", tuya.valueConverter.onOff],
[0x75, "state_l23", tuya.valueConverter.onOff],
[0x76, "state_l24", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_ijxvkhd0"]),
model: "ZY-M100-24G",
vendor: "Tuya",
description: "24G MmWave radar human presence motion sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.enum("state", ea.STATE, ["none", "presence", "move"]).withDescription("Presence state"),
e.presence(),
e.numeric("distance", ea.STATE).withUnit("m").withDescription("Target distance"),
e.illuminance(),
e
.numeric("motion_sensitivity", ea.STATE_SET)
.withValueMin(1)
.withValueMax(10)
.withValueStep(1)
.withCategory("config")
.withDescription("Motion sensitivity"),
e
.numeric("presence_sensitivity", ea.STATE_SET)
.withValueMin(1)
.withValueMax(10)
.withValueStep(1)
.withCategory("config")
.withDescription("Presence sensitivity"),
e
.numeric("max_range", ea.STATE_SET)
.withValueMin(1.5)
.withValueMax(5.5)
.withValueStep(1)
.withUnit("m")
.withCategory("config")
.withDescription("Maximum range"),
e
.numeric("presence_timeout", ea.STATE_SET)
.withValueMin(1)
.withValueMax(1500)
.withValueStep(1)
.withUnit("s")
.withDescription("Presence timeout"),
],
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[112, "presence", tuya.valueConverter.trueFalse1],
[106, "motion_sensitivity", tuya.valueConverter.divideBy10FromOnly],
[111, "presence_sensitivity", tuya.valueConverter.divideBy10FromOnly],
[107, "max_range", tuya.valueConverter.divideBy100],
[109, "distance", tuya.valueConverter.divideBy100],
[110, "presence_timeout", tuya.valueConverter.raw],
[104, "illuminance", tuya.valueConverter.raw],
[102, "illuminance_treshold_max", tuya.valueConverter.raw],
[103, "illuminance_treshold_min", tuya.valueConverter.raw],
[105, "state", tuya.valueConverterBasic.lookup({ none: 0, presence: 1, move: 2 })],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_7gclukjs"]),
model: "ZY-M100-24GV2",
vendor: "Tuya",
description: "24G MmWave radar human presence motion sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.enum("state", ea.STATE, ["none", "presence", "move"]).withDescription("Presence state sensor"),
e.presence().withDescription("Occupancy"),
e.numeric("distance", ea.STATE).withUnit("m").withDescription("Target distance"),
e.illuminance().withDescription("Illuminance sensor"),
e.numeric("move_sensitivity", ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(1).withDescription("Motion Sensitivity"),
e.numeric("presence_sensitivity", ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(1).withDescription("Presence Sensitivity"),
e
.numeric("detection_distance_min", ea.STATE_SET)
.withValueMin(0)
.withValueMax(8.25)
.withValueStep(0.75)
.withUnit("m")
.withDescription("Minimum range"),
e
.numeric("detection_distance_max", ea.STATE_SET)
.withValueMin(0.75)
.withValueMax(9.0)
.withValueStep(0.75)
.withUnit("m")
.withDescription("Maximum range"),
e
.numeric("presence_timeout", ea.STATE_SET)
.withValueMin(1)
.withValueMax(1500)
.withValueStep(1)
.withUnit("s")
.withDescription("Fade time"),
],
meta: {
tuyaDatapoints: [
[104, "presence", tuya.valueConverter.trueFalse1],
[2, "move_sensitivity", tuya.valueConverter.raw],
[102, "presence_sensitivity", tuya.valueConverter.raw],
[3, "detection_distance_min", tuya.valueConverter.divideBy100],
[4, "detection_distance_max", tuya.valueConverter.divideBy100],
[9, "distance", tuya.valueConverter.divideBy10],
[105, "presence_timeout", tuya.valueConverter.raw],
[103, "illuminance", tuya.valueConverter.raw],
[1, "state", tuya.valueConverterBasic.lookup({ none: 0, presence: 1, move: 2 })],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_ya4ft0w4", "_TZE200_ya4ft0w4", "_TZE204_gkfbdvyx", "_TZE200_gkfbdvyx"]),
model: "ZY-M100-24GV3",
vendor: "Tuya",
description: "24G MmWave radar human presence motion sensor (added distance switch)",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: (device, options) => {
const exps = [
e.enum("state", ea.STATE, ["none", "presence", "move"]).withDescription("Presence state sensor"),
e.presence().withDescription("Occupancy"),
e.numeric("distance", ea.STATE).withUnit("m").withDescription("Target distance"),
e.binary("find_switch", ea.STATE_SET, "ON", "OFF").withDescription("distance switch"),
e.illuminance().withDescription("Illuminance sensor"),
e.numeric("move_sensitivity", ea.STATE_SET).withValueMin(1).withValueMax(10).withValueStep(1).withDescription("Motion Sensitivity"),
e
.numeric("presence_sensitivity", ea.STATE_SET)
.withValueMin(1)
.withValueMax(10)
.withValueStep(1)
.withDescription("Presence Sensitivity"),
e
.numeric("presence_timeout", ea.STATE_SET)
.withValueMin(1)
.withValueMax(15000)
.withValueStep(1)
.withUnit("s")
.withDescription("Fade time"),
];
if (!device || device.manufacturerName === "_TZE204_gkfbdvyx" || device.manufacturerName === "_TZE200_gkfbdvyx") {
exps.push(e
.numeric("detection_distance_min", ea.STATE_SET)
.withValueMin(0)
.withValueMax(6)
.withValueStep(0.5)
.withUnit("m")
.withDescription("Minimum range"));
exps.push(e
.numeric("detection_distance_max", ea.STATE_SET)
.withValueMin(0.5)
.withValueMax(9.0)
.withValueStep(0.5)
.withUnit("m")
.withDescription("Maximum range"));
}
else {
exps.push(e
.numeric("detection_distance_min", ea.STATE_SET)
.withValueMin(0)
.withValueMax(8.25)
.withValueStep(0.75)
.withUnit("m")
.withDescription("Minimum range"));
exps.push(e
.numeric("detection_distance_max", ea.STATE_SET)
.withValueMin(0.75)
.withValueMax(9.0)
.withValueStep(0.75)
.withUnit("m")
.withDescription("Maximum range"));
}
return exps;
},
meta: {
tuyaDatapoints: [
[
1,
null,
{
from: (v, meta) => {
if (v === 0) {
return {
state: "none",
presence: false,
};
}
if (v === 1) {
return {
state: "presence",
presence: true,
};
}
if (v === 2) {
return {
state: "move",
presence: true,
};
}
return {
state: "none",
presence: false,
};
},
},
],
// Firmware periodically reports sensitivity as 0 even when a valid value is stored;
// dropping the 0 on read keeps the entity at its last good state, prevents Z2M from
// caching/writing-back 0 to the device, and silences the mqtt.number validation flood
// (range 1.0-10.0). See zigbee2mqtt#24049, #26672, #27357. Replaces the silent loss
// introduced by the min(0)->min(1) tightening in PR #8674.
[2, "move_sensitivity", { from: (v) => (v === 0 ? undefined : v), to: (v) => v }],
[3, "detection_distance_min", tuya.valueConverter.divideBy100],
[4, "detection_distance_max", tuya.valueConverter.divideBy100],
[9, "distance", tuya.valueConverter.divideBy10],
[101, "find_switch", tuya.valueConverter.onOff],
[102, "presence_sensitivity", { from: (v) => (v === 0 ? undefined : v), to: (v) => v }],
[103, "illuminance", tuya.valueConverter.raw],
[105, "presence_timeout", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_e9ajs4ft"]),
model: "CTL-R1-TY-Zigbee",
vendor: "Tuya",
description: "24G radar human presence motion sensor.",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.illuminance().withUnit("lx"),
e.presence(),
e
.numeric("presence_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(100)
.withValueStep(1)
.withUnit("%")
.withDescription("Presence sensitivity"),
e
.numeric("detection_range", ea.STATE_SET)
.withValueMin(1.5)
.withValueMax(4.5)
.withValueStep(0.1)
.withUnit("m")
.withDescription("Detection range"),
e
.numeric("detection_delay", ea.STATE_SET)
.withValueMin(1)
.withValueMax(600)
.withValueStep(1)
.withUnit("s")
.withDescription("Presence detection delay"),
e
.numeric("illuminance_treshold_max", ea.STATE_SET)
.withValueMin(0)
.withValueMax(2000)
.withValueStep(1)
.withUnit("lx")
.withDescription("The max illuminance threshold to turn on the light"),
e
.numeric("illuminance_treshold_min", ea.STATE_SET)
.withValueMin(0)
.withValueMax(2000)
.withValueStep(1)
.withUnit("lx")
.withDescription("The min illuminance threshold to turn on the light"),
e
.binary("presence_illuminance_switch", ea.STATE_SET, true, false)
.withDescription(`Whether to enable 'light_switch' illumination is between min/max threshold`),
e
.binary("light_switch", ea.STATE, "ON", "OFF")
.withDescription("This state will determine the light on/off based on the lighting threshold and presence sensing"),
e.binary("light_linkage", ea.STATE_SET, true, false).withDescription("Light linkage"),
e
.enum("detection_method", ea.STATE_SET, ["only_move", "exist_move"])
.withDescription(`When 'only_move' is used, presence will only be triggered when there is movement`),
e.enum("indicator_light", ea.STATE_SET, ["presence", "off", "on"]).withDescription("Controls when the indicator light is turned on"),
e
.binary("identify", ea.STATE_SET, true, false)
.withDescription("After turning on, the indicator light quickly flashes, used to locate devices"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse1],
[2, "presence_sensitivity", tuya.valueConverter.raw],
[4, "detection_range", tuya.valueConverter.divideBy10],
[101, "illuminance", tuya.valueConverter.raw],
[102, "illuminance_treshold_max", tuya.valueConverter.raw],
[103, "illuminance_treshold_min", tuya.valueConverter.raw],
[104, "detection_delay", tuya.valueConverter.raw],
[109, "presence_illuminance_switch", tuya.valueConverter.trueFalseEnum1],
[105, "light_switch", tuya.valueConverter.onOff],
[106, "light_linkage", tuya.valueConverter.trueFalseEnum1],
[
107,
"indicator_light",
tuya.valueConverterBasic.lookup({
presence: tuya.enum(0),
off: tuya.enum(1),
on: tuya.enum(2),
}),
],
[
108,
"detection_method",
tuya.valueConverterBasic.lookup({
only_move: tuya.enum(0),
exist_move: tuya.enum(1),
}),
],
[113, "find_switch", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE204_sbyx0lm6",
"_TZE204_clrdrnya",
"_TZE204_dtzziy1e",
"_TZE204_iaeejhvf",
"_TZE204_mtoaryre",
"_TZE200_mp902om5",
"_TZE204_pfayrzcw",
"_TZE284_4qznlkbu",
"_TZE200_clrdrnya",
"_TZE200_sbyx0lm6",
]),
model: "MTG075-ZB-RL",
vendor: "Tuya",
description: "2.4G/5.8G human presence sensor with relay",
whiteLabel: [
tuya.whitelabel("Tuya", "MTG275-ZB-RL", "2.4G/5.8G MmWave radar human presence motion sensor", ["_TZE204_dtzziy1e"]),
tuya.whitelabel("Tuya", "MTG035-ZB-RL", "Human presence sensor with relay", ["_TZE204_pfayrzcw"]),
tuya.whitelabel("Tuya", "MTG235-ZB-RL", "24G Human presence sensor with relay", ["_TZE204_clrdrnya", "_TZE200_clrdrnya"]),
tuya.whitelabel("QA", "QASZ24R", "mmWave 24 Ghz sensor with relay", ["_TZE284_4qznlkbu", "_TZE204_clrdrnya"]),
],
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence(),
e.illuminance(),
e.numeric("target_distance", ea.STATE).withDescription("Distance to target").withUnit("m"),
e
.numeric("radar_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(9)
.withValueStep(1)
.withDescription("Detection threshold for the strength of object energy"),
e
.numeric("detection_range", ea.STATE_SET)
.withValueMin(0)
.withValueMax(8)
.withValueStep(0.1)
.withUnit("m")
.withDescription("Maximum distance detected by the sensor"),
e
.numeric("shield_range", ea.STATE_SET)
.withValueMin(0)
.withValueMax(8)
.withValueStep(0.1)
.withUnit("m")
.withDescription("Nearest distance detected by the sensor"),
e
.numeric("entry_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(9)
.withValueStep(1)
.withDescription("Sensitivity threshold triggered for the first time when the target enters the detection range"),
e
.numeric("entry_distance_indentation", ea.STATE_SET)
.withValueMin(0)
.withValueMax(8)
.withValueStep(0.1)
.withUnit("m")
.withDescription("Indent the distance inward based on the detection distance"),
e
.numeric("entry_filter_time", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(0.1)
.withUnit("s")
.withDescription("Sensitivity threshold triggered for the first time when the target enters the detection range "),
e
.numeric("departure_delay", ea.STATE_SET)
.withValueMin(0)
.withValueMax(600)
.withValueStep(1)
.withUnit("s")
.withDescription("Confirmation time after the target disappears"),
e
.numeric("block_time", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(0.1)
.withUnit("s")
.withDescription("Time for the target to be detected again after switching from manned(occupy) to unmanned(unoccupy) mode"),
e.binary("breaker_status", ea.STATE_SET, "ON", "OFF").withDescription("Remotely control the breaker in standard mode"),
e
.enum("breaker_mode", ea.STATE_SET, ["standard", "local"])
.withDescription("Breaker mode: standard is remotely controlled, local is automatic"),
e
.numeric("illuminance_threshold", ea.STATE_SET)
.withValueMin(0)
.withValueMax(420)
.withValueStep(0.1)
.withUnit("lx")
.withDescription("Illumination threshold for local (automatic) switching mode operation"),
e.enum("status_indication", ea.STATE_SET, ["OFF", "ON"]).withDescription("Indicator light will flash when human presence is detected"),
e
.enum("sensor", ea.STATE_SET, ["on", "off", "occupied", "unoccupied"])
.withDescription("The radar sensor can be set in four states: on, off, occupied and unoccupied. For example, if set to occupied, " +
"it will continue to maintain presence regardless of whether someone is present or not. If set to unoccupied, the unoccupied " +
"state will be maintained permanently."),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse1],
[2, "radar_sensitivity", tuya.valueConverter.raw],
[3, "shield_range", tuya.valueConverter.divideBy100],
[4, "detection_range", tuya.valueConverter.divideBy100],
[6, "equipment_status", tuya.valueConverter.raw],
[9, "target_distance", tuya.valueConverter.divideBy100],
[101, "entry_filter_time", tuya.valueConverter.divideBy10],
[102, "departure_delay", tuya.valueConverter.raw],
[103, "cline", tuya.valueConverter.raw],
[104, "illuminance", tuya.valueConverter.divideBy10],
[105, "entry_sensitivity", tuya.valueConverter.raw],
[106, "entry_distance_indentation", tuya.valueConverter.divideBy100],
[
107,
"breaker_mode",
tuya.valueConverterBasic.lookup({
standard: tuya.enum(0),
local: tuya.enum(1),
}),
],
[
108,
"breaker_status",
tuya.valueConverterBasic.lookup({
OFF: tuya.enum(0),
ON: tuya.enum(1),
}),
],
[
109,
"status_indication",
tuya.valueConverterBasic.lookup({
OFF: tuya.enum(0),
ON: tuya.enum(1),
}),
],
[110, "illuminance_threshold", tuya.valueConverter.divideBy10],
[
111,
"breaker_polarity",
tuya.valueConverterBasic.lookup({
NC: tuya.enum(0),
NO: tuya.enum(1),
}),
],
[112, "block_time", tuya.valueConverter.divideBy10],
[113, "parameter_setting_result", tuya.valueConverter.raw],
[114, "factory_parameters", tuya.valueConverter.raw],
[
115,
"sensor",
tuya.valueConverterBasic.lookup({
on: tuya.enum(0),
off: tuya.enum(1),
occupied: tuya.enum(2),
unoccupied: tuya.enum(3),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_aai5grix", "_TZE204_aai5grix"]),
model: "MTD285-ZB",
vendor: "Wenzhi",
description: "24GHz mmWave human presence sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence(),
e.enum("state", ea.STATE, ["none", "presence", "move"]).withDescription("Presence state"),
e.illuminance().withDescription("Measured illuminance"),
e
.numeric("min_distance", ea.STATE_SET)
.withValueMin(0)
.withValueMax(8.4)
.withValueStep(0.1)
.withUnit("m")
.withDescription("Minimum detection distance"),
e
.numeric("max_distance", ea.STATE_SET)
.withValueMin(0)
.withValueMax(8.4)
.withValueStep(0.1)
.withUnit("m")
.withDescription("Maximum detection distance"),
e.numeric("target_distance", ea.STATE).withValueMin(0).withValueMax(9.9).withUnit("m").withDescription("Target distance"),
...Array.from({ length: 11 }, (_, i) => e
.enum(`gate_enable_${String(i + 1).padStart(2, "0")}`, ea.STATE_SET, ["disable", "enable"])
.withDescription(`Distance gate ${i + 1} enable`)),
e.numeric("configuration_gate", ea.STATE_SET).withValueMin(1).withValueMax(11).withDescription("Select gate for threshold config"),
e.numeric("move_threshold", ea.STATE_SET).withValueMin(0).withValueMax(99).withDescription("Configure motion detection threshold"),
e.numeric("presence_threshold", ea.STATE_SET).withValueMin(0).withValueMax(99).withDescription("Configure presence detection threshold"),
e.numeric("nearest_target_gate", ea.STATE).withValueMin(0).withValueMax(11).withDescription("Nearest active gate"),
e
.numeric("target_countdown", ea.STATE)
.withValueMin(0)
.withValueMax(3600)
.withUnit("s")
.withDescription("Target timeout countdown (debug only)"),
e
.numeric("target_velocity", ea.STATE)
.withValueMin(-9.99)
.withValueMax(9.99)
.withUnit("m/s")
.withDescription("Target speed with direction"),
e
.binary("debug_switch", ea.STATE_SET, "on", "off") // change to toggle
.withDescription("Debug mode toggle"),
e
.enum("led_mode", ea.STATE_SET, ["silence", "status"]) // same as in the app
.withDescription("LED indicator mode"),
e.numeric("delay_time", ea.STATE_SET).withValueMin(5).withValueMax(3600).withUnit("s").withDescription("Departure delay time"),
e
.numeric("block_time", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(0.5)
.withUnit("s")
.withDescription("Block time after unoccupancy"), // same as in the app
e
.enum("judge_logic", ea.STATE_SET, ["large_move", "small_move", "custom_move"]) // same as in the app
.withDescription("Presence detection algorithm"),
e
.enum("start_noise_collect", ea.SET, ["start"]) // only button to start noise collection
.withDescription("Start environmental background noise collection and auto adjust thresholds"),
e
.enum("noise_collect_status", ea.STATE, ["start", "ongoing", "complete"]) // status of noise collection
.withDescription("Environmental background noise collection status"),
e.enum("device_control", ea.STATE_SET, ["no_action", "restart", "reset_param"]).withDescription("Device control commands"),
e.enum("presence_sensitivity", ea.STATE_SET, ["high", "medium", "low", "custom"]).withDescription("Presence sensitivity"),
e.enum("move_sensitivity", ea.STATE_SET, ["high", "medium", "low", "custom"]).withDescription("Motion sensitivity"),
e
.enum("scene_mode", ea.STATE_SET, [
"custom", // more fitting and same as in the app
"toilet",
"kitchen",
"corridor",
"bedroom",
"living_room",
"meeting_room", // same as in the app
])
.withDescription("Scene mode preset"),
e.binary("illuminance_report", ea.STATE_SET, "on", "off").withDescription("Illuminance reporting toggle"),
e.binary("move_detect", ea.STATE_SET, "on", "off").withDescription("Motion detection toggle"),
e.binary("distance_report", ea.STATE_SET, "on", "off").withDescription("Distance reporting toggle"),
e.binary("speed_report", ea.STATE_SET, "on", "off").withDescription("Speed reporting toggle"),
],
meta: {
tuyaDatapoints: [
[
1,
null,
{
from: (v) => ({
presence: v !== 0,
state: v === 0 ? "none" : v === 1 ? "presence" : "move",
}),
},
],
[3, "min_distance", tuya.valueConverter.divideBy10],
[4, "max_distance", tuya.valueConverter.divideBy10],
[9, "target_distance", tuya.valueConverter.divideBy10],
[101, "gate_enable_01", tuya.valueConverterBasic.lookup({ disable: tuya.enum(0), enable: tuya.enum(1) })],
[102, "gate_enable_02", tuya.valueConverterBasic.lookup({ disable: tuya.enum(0), enable: tuya.enum(1) })],
[103, "gate_enable_03", tuya.valueConverterBasic.lookup({ disable: tuya.enum(0), enable: tuya.enum(1) })],
[104, "gate_enable_04", tuya.valueConverterBasic.lookup({ disable: tuya.enum(0), enable: tuya.enum(1) })],
[105, "gate_enable_05", tuya.valueConverterBasic.lookup({ disable: tuya.enum(0), enable: tuya.enum(1) })],
[106, "gate_enable_06", tuya.valueConverterBasic.lookup({ disable: tuya.enum(0), enable: tuya.enum(1) })],
[107, "gate_enable_07", tuya.valueConverterBasic.lookup({ disable: tuya.enum(0), enable: tuya.enum(1) })],
[108, "gate_enable_08", tuya.valueConverterBasic.lookup({ disable: tuya.enum(0), enable: tuya.enum(1) })],
[109, "gate_enable_09", tuya.valueConverterBasic.lookup({ disable: tuya.enum(0), enable: tuya.enum(1) })],
[110, "gate_enable_10", tuya.valueConverterBasic.lookup({ disable: tuya.enum(0), enable: tuya.enum(1) })],
[111, "gate_enable_11", tuya.valueConverterBasic.lookup({ disable: tuya.enum(0), enable: tuya.enum(1) })],
[112, "configuration_gate", tuya.valueConverter.raw],
[113, "move_threshold", tuya.valueConverter.raw],
[114, "presence_threshold", tuya.valueConverter.raw],
[115, "nearest_target_gate", tuya.valueConverter.raw],
[116, "target_countdown", tuya.valueConverter.raw],
[117, "target_velocity", tuya.valueConverter.divideBy100],
[118, "debug_switch", tuya.valueConverterBasic.lookup({ off: tuya.enum(0), on: tuya.enum(1) })],
[119, "led_mode", tuya.valueConverterBasic.lookup({ silence: tuya.enum(0), status: tuya.enum(1) })],
[120, "delay_time", tuya.valueConverter.raw],
[121, "block_time", tuya.valueConverter.divideBy10],
[
122,
"judge_logic",
tuya.valueConverterBasic.lookup({ large_move: tuya.enum(0), small_move: tuya.enum(1), custom_move: tuya.enum(2) }),
],
[123, "noise_collect_status", tuya.valueConverterBasic.lookup({ start: tuya.enum(0), ongoing: tuya.enum(1), complete: tuya.enum(2) })], // status reporting, this line MUST above button to work properly
[
123,
"start_noise_collect", // for button, this line MUST below status reporting
{
to: () => tuya.enum(0), // send 0 to start noise collection
},
],
[124, "device_control", tuya.valueConverterBasic.lookup({ no_action: tuya.enum(0), restart: tuya.enum(1), reset_param: tuya.enum(2) })],
[125, "illuminance", tuya.valueConverter.raw],
[
126,
"presence_sensitivity",
tuya.valueConverterBasic.lookup({ high: tuya.enum(0), medium: tuya.enum(1), low: tuya.enum(2), custom: tuya.enum(3) }),
],
[
127,
"move_sensitivity",
tuya.valueConverterBasic.lookup({ high: tuya.enum(0), medium: tuya.enum(1), low: tuya.enum(2), custom: tuya.enum(3) }),
],
[
128,
"scene_mode",
tuya.valueConverterBasic.lookup({
custom: tuya.enum(0), // more fitting and same as in the app
toilet: tuya.enum(1),
kitchen: tuya.enum(2),
corridor: tuya.enum(3),
bedroom: tuya.enum(4),
living_room: tuya.enum(5),
meeting_room: tuya.enum(6), // same as in the app
}),
],
[129, "illuminance_report", tuya.valueConverterBasic.lookup({ off: tuya.enum(0), on: tuya.enum(1) })],
[130, "move_detect", tuya.valueConverterBasic.lookup({ off: tuya.enum(0), on: tuya.enum(1) })],
[131, "distance_report", tuya.valueConverterBasic.lookup({ off: tuya.enum(0), on: tuya.enum(1) })],
[132, "speed_report", tuya.valueConverterBasic.lookup({ off: tuya.enum(0), on: tuya.enum(1) })],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_81yrt3lo", "_TZE284_81yrt3lo", "_TZE28C1000000_81yrt3lo"]),
model: "PJ-1203A",
vendor: "Tuya",
description: "Bidirectional energy meter with 80A current clamp",
fromZigbee: [fzLocal.PJ1203A_strict_fz_datapoints, fzLocal.PJ1203A_sync_time_increase_seq],
toZigbee: [tuya.tz.datapoints],
extend: [tuya.modernExtend.tuyaBase()],
options: [
e
.binary("late_energy_flow_a", ea.SET, true, false)
.withDescription("Delay channel A publication until the next energy flow update (default false)."),
e
.binary("late_energy_flow_b", ea.SET, true, false)
.withDescription("Delay channel B publication until the next energy flow update (default false)."),
e
.binary("signed_power_a", ea.SET, true, false)
.withDescription("Report energy flow direction for channel A using signed power (default false)."),
e
.binary("signed_power_b", ea.SET, true, false)
.withDescription("Report energy flow direction for channel B using signed power (default false)."),
e.binary("invert_energy_flow_a", ea.SET, true, false).withDescription("Report energy flow direction inverted for channel A."),
e.binary("invert_energy_flow_b", ea.SET, true, false).withDescription("Report energy flow direction inverted for channel B."),
e
.binary("single_zero_remove", ea.SET, true, false)
.withDescription("If true then single-zero power or current values will be disgarded. The default is false."),
],
exposes: [
e.ac_frequency(),
e.voltage(),
tuya.exposes.powerWithPhase("a"),
tuya.exposes.powerWithPhase("b"),
tuya.exposes.powerWithPhase("ab"),
tuya.exposes.currentWithPhase("a"),
tuya.exposes.currentWithPhase("b"),
tuya.exposes.powerFactorWithPhase("a"),
tuya.exposes.powerFactorWithPhase("b"),
tuya.exposes.energyFlowWithPhase("a", ["sign"]),
tuya.exposes.energyFlowWithPhase("b", ["sign"]),
tuya.exposes.energyWithPhase("a"),
tuya.exposes.energyWithPhase("b"),
tuya.exposes.energyProducedWithPhase("a"),
tuya.exposes.energyProducedWithPhase("b"),
e
.numeric("update_frequency", ea.STATE_SET)
.withUnit("s")
.withDescription("Update frequency")
.withValueMin(3)
.withValueMax(60)
.withPreset("default", 10, "Default value"),
// Timestamp a and b are basically equivalent to last_seen
// but they indicate when the unsigned value of power_a and power_b
// were received. They can be several seconds in the past if
// the publication was delayed because of the late_energy_flow options.
e.text("timestamp_a", ea.STATE).withDescription("Timestamp at power measure (phase a)"),
e.text("timestamp_b", ea.STATE).withDescription("Timestamp at power measure (phase b)"),
],
meta: {
multiEndpointSkip: ["power_factor", "power_factor_phase_b", "power_factor_phase_c", "energy"],
tuyaDatapoints: [
[111, "ac_frequency", tuya.valueConverter.divideBy100],
[112, "voltage", tuya.valueConverter.divideBy10],
[101, null, convLocal.powerPJ1203A("a")], // power_a
[105, null, convLocal.powerPJ1203A("b")], // power_b
[113, null, convLocal.currentPJ1203A("a")], // current_a
[114, null, convLocal.currentPJ1203A("b")], // current_b
[110, null, convLocal.powerFactorPJ1203A("a")], // power_factor_a
[121, null, convLocal.powerFactorPJ1203A("b")], // power_factor_b
[102, null, convLocal.energyFlowPJ1203A("a")], // energy_flow_a or the sign of power_a
[104, null, convLocal.energyFlowPJ1203A("b")], // energy_flow_b or the sign of power_b
[115, null, convLocal.powerAbPJ1203A()],
[106, null, convLocal.energyForwardPJ1203A("a")],
[108, null, convLocal.energyForwardPJ1203A("b")],
[107, null, convLocal.energyReversePJ1203A("a")],
[109, null, convLocal.energyReversePJ1203A("b")],
[129, "update_frequency", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_rks0sgb7"]),
model: "PC311-Z-TY",
vendor: "Tuya",
description: "Bidirectional energy meter with 80A current clamp",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.ac_frequency(),
e.voltage(),
e.power(),
e.current(),
e.energy(),
e.energy_produced(),
tuya.exposes.powerWithPhase("a"),
tuya.exposes.powerWithPhase("b"),
tuya.exposes.currentWithPhase("a"),
tuya.exposes.currentWithPhase("b"),
tuya.exposes.powerFactorWithPhase("a"),
tuya.exposes.powerFactorWithPhase("b"),
tuya.exposes.energyWithPhase("a"),
tuya.exposes.energyWithPhase("b"),
tuya.exposes.energyProducedWithPhase("a"),
tuya.exposes.energyProducedWithPhase("b"),
],
meta: {
multiEndpointSkip: ["power_factor", "power_factor_phase_b", "power_factor_phase_c", "energy"],
tuyaDatapoints: [
[113, "ac_frequency", tuya.valueConverter.raw],
[108, "power_a", tuya.valueConverter.raw],
[111, "power_b", tuya.valueConverter.raw],
[9, "power", tuya.valueConverter.raw],
[106, "voltage", tuya.valueConverter.divideBy10],
[107, "current_a", tuya.valueConverter.divideBy1000],
[110, "current_b", tuya.valueConverter.divideBy1000],
[105, "current", tuya.valueConverter.divideBy1000],
[109, "power_factor_a", tuya.valueConverter.raw],
[112, "power_factor_b", tuya.valueConverter.raw],
[1, "energy", tuya.valueConverter.divideBy100],
[101, "energy_a", tuya.valueConverter.divideBy100],
[103, "energy_b", tuya.valueConverter.divideBy100],
[102, "energy_produced_a", tuya.valueConverter.divideBy100],
[104, "energy_produced_b", tuya.valueConverter.divideBy100],
[2, "energy_produced", tuya.valueConverter.divideBy100],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_mpbki2zm", "_TZE204_qujphad5"]),
model: "TYBAC-006",
vendor: "Tuya",
description: "Wall-mount thermostat for 2-pipe fan-coil unit",
extend: [tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true, timeStart: "1970" })],
options: [
e
.enum("control_sequence_of_operation", ea.SET, ["cooling_only", "cooling_and_heating_4-pipes"])
.withDescription("Operating environment of the thermostat"),
e.binary("expose_device_state", ea.SET, true, false).withDescription("Expose device power state as a separate property when enabled."),
],
exposes: (device, options) => {
const system_modes = ["off", "cool", "heat", "fan_only"];
// Device can operate either in 2-pipe or 4-pipe configuration
// For 2-pipe configurations remove 'heat' mode
switch (options?.control_sequence_of_operation) {
case "cooling_only":
system_modes.splice(2, 1);
break;
}
const exposes = [
e
.climate()
.withLocalTemperature(ea.STATE)
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withSystemMode(["off", "cool", "heat", "fan_only"], ea.STATE_SET)
.withFanMode(["low", "medium", "high", "auto"], ea.STATE_SET)
.withLocalTemperatureCalibration(-9, 9, 0.5, ea.STATE_SET),
e.child_lock(),
e.min_temperature().withValueMin(5).withValueMax(15),
e.max_temperature().withValueMin(15).withValueMax(45),
e.binary("eco_mode", ea.STATE_SET, "ON", "OFF").withDescription("ECO mode ON/OFF"),
e.max_temperature_limit().withDescription("ECO Heating energy-saving temperature (default: 20 ºC)").withValueMin(15).withValueMax(30),
e.min_temperature_limit().withDescription("ECO Cooling energy-saving temperature (default: 26 ºC)").withValueMin(15).withValueMax(30),
e.deadzone_temperature().withValueMin(0).withValueMax(5).withValueStep(1),
e.binary("valve", ea.STATE, "OPEN", "CLOSE").withDescription("3-Way Valve State"),
e.binary("manual_mode", ea.STATE_SET, "ON", "OFF").withDescription("Manual = ON or Schedule = OFF"),
...tuya.exposes.scheduleAllDays(ea.STATE_SET, "HH:MM/C HH:MM/C HH:MM/C HH:MM/C HH:MM/C HH:MM/C"),
];
if (options?.expose_device_state === true) {
exposes.unshift(e.binary("state", ea.STATE_SET, "ON", "OFF").withDescription("Turn the thermostat ON or OFF"));
}
return exposes;
},
meta: {
publishDuplicateTransaction: true,
tuyaDatapoints: [
[
1,
"state",
{
to: async (v, meta) => {
if (meta.options?.expose_device_state === true) {
await tuya.sendDataPointBool(meta.device.endpoints[0], 1, utils.getFromLookup(v, { on: true, off: false }), "dataRequest", 1);
}
},
from: (v, meta, options) => {
meta.state.system_mode = v === true ? (meta.state.system_mode_device ?? "cool") : "off";
if (options?.expose_device_state === true) {
return v === true ? "ON" : "OFF";
}
delete meta.state.state;
},
},
],
[
2,
"system_mode",
{
// Extend system_mode to support 'off' in addition to 'cool', 'heat' and 'fan_only'
to: async (v, meta) => {
const entity = meta.device.endpoints[0];
// Power State
await tuya.sendDataPointBool(entity, 1, v !== "off", "dataRequest", 1);
switch (v) {
case "cool":
await tuya.sendDataPointEnum(entity, 2, 0, "dataRequest", 1);
break;
case "heat":
await tuya.sendDataPointEnum(entity, 2, 1, "dataRequest", 1);
break;
case "fan_only":
await tuya.sendDataPointEnum(entity, 2, 2, "dataRequest", 1);
break;
}
},
from: (v, meta) => {
const modes = ["cool", "heat", "fan_only"];
meta.state.system_mode_device = modes[v];
return modes[v];
},
},
],
[4, "eco_mode", tuya.valueConverter.onOff],
[16, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[19, "max_temperature", tuya.valueConverter.divideBy10],
[24, "local_temperature", tuya.valueConverter.divideBy10],
[26, "min_temperature", tuya.valueConverter.divideBy10],
[27, "local_temperature_calibration", tuya.valueConverter.localTemperatureCalibration],
[
28,
"fan_mode",
tuya.valueConverterBasic.lookup({
low: tuya.enum(0),
medium: tuya.enum(1),
high: tuya.enum(2),
auto: tuya.enum(3),
}),
],
[36, "valve", tuya.valueConverterBasic.lookup({ OPEN: 0, CLOSE: 1 })],
[40, "child_lock", tuya.valueConverter.lockUnlock],
[103, "deadzone_temperature", tuya.valueConverter.raw],
[104, "min_temperature_limit", tuya.valueConverter.divideBy10],
[105, "max_temperature_limit", tuya.valueConverter.divideBy10],
[106, "schedule_sunday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(7)],
[107, "schedule_saturday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(6)],
[108, "schedule_friday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(5)],
[109, "schedule_thursday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(4)],
[110, "schedule_wednesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(3)],
[111, "schedule_tuesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(2)],
[112, "schedule_monday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(1)],
[101, "manual_mode", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_mhxn2jso"]),
model: "rtsc11r",
vendor: "Tuya",
description: "5.8G human presence sensor with relay",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence(),
e.illuminance().withUnit("lx"),
e
.numeric("detection_delay", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(0.1)
.withDescription("Detection delay")
.withUnit("s"),
e
.numeric("detection_distance", ea.STATE)
.withValueMin(0)
.withValueMax(1000)
.withValueStep(1)
.withDescription("Distance of detected person")
.withUnit("cm"),
e.numeric("sensitivity", ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(1).withDescription("Detection sensitivity"),
e
.numeric("keep_time", ea.STATE_SET)
.withValueMin(5)
.withValueMax(3600)
.withValueStep(1)
.withDescription("Detection keep time")
.withUnit("s"),
e
.numeric("minimum_range", ea.STATE_SET)
.withValueMin(0)
.withValueMax(1000)
.withValueStep(50)
.withDescription("Minimum detection range")
.withUnit("m"),
e
.numeric("maximum_range", ea.STATE_SET)
.withValueMin(50)
.withValueMax(1000)
.withValueStep(50)
.withDescription("Maximum detection range")
.withUnit("m"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse1],
[12, "detection_delay", tuya.valueConverter.divideBy10],
[19, "detection_distance", tuya.valueConverter.raw],
[20, "illuminance", tuya.valueConverter.raw],
[101, "sensitivity", tuya.valueConverter.divideBy10],
[102, "keep_time", tuya.valueConverter.raw],
[111, "minimum_range", tuya.valueConverter.divideBy100],
[112, "maximum_range", tuya.valueConverter.divideBy100],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_kyhbrfyl"]),
model: "NAS-PS09B2",
vendor: "NEO",
description: "Human presence sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.occupancy(),
e.enum("human_motion_state", ea.STATE, ["none", "small", "large"]).withDescription("Human Motion State"),
e
.numeric("departure_delay", ea.STATE_SET)
.withUnit("s")
.withValueMin(3)
.withValueMax(600)
.withValueStep(1)
.withDescription("Presence Time"),
e
.numeric("radar_range", ea.STATE_SET)
.withUnit("cm")
.withValueMin(150)
.withValueMax(600)
.withValueStep(75)
.withDescription("Motion Range Detection"),
e
.numeric("radar_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(7)
.withValueStep(1)
.withDescription("Motion Detection Sensitivity"),
e
.numeric("presence_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(7)
.withValueStep(1)
.withDescription("Motionless Detection Sensitivity"),
e
.numeric("dis_current", ea.STATE)
.withUnit("cm")
.withValueMin(0)
.withValueMax(1000)
.withValueStep(1)
.withLabel("Current distance")
.withDescription("Current Distance of Detected Motion"),
],
meta: {
tuyaDatapoints: [
[1, "occupancy", tuya.valueConverter.trueFalse1],
[11, "human_motion_state", tuya.valueConverterBasic.lookup({ none: 0, small: 1, large: 2 })],
[12, "departure_delay", tuya.valueConverter.raw],
[13, "radar_range", tuya.valueConverter.raw],
[15, "radar_sensitivity", tuya.valueConverter.raw],
[16, "presence_sensitivity", tuya.valueConverter.raw],
[19, "dis_current", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_v1jqz5cy", "_TZE200_d9mzkhoq"]),
model: "BLE-YL01",
vendor: "Tuya",
description: "Smart WiFi Zigbee chlorine meter",
whiteLabel: [
tuya.whitelabel("Tuya", "YK-S03", "Smart pH and Chlorine Tester for Swimming Pool", ["_TZE200_d9mzkhoq"]),
{ model: "YY-1099L", vendor: "Tuya" },
],
// Don't query too often. Values are not always updated. https://github.com/Koenkk/zigbee2mqtt/issues/18704
extend: [
tuya.modernExtend.tuyaBase({
dp: true,
queryOnDeviceAnnounce: true,
// https://github.com/Koenkk/zigbee2mqtt/issues/23946#issuecomment-2941182834
queryIntervalSeconds: 10 * 60,
respondToMcuVersionResponse: true,
}),
m.poll({
key: "backlight",
defaultIntervalSeconds: 30 * 60,
poll: async (device) => {
// Set backlight ever 30 mins, otherwise device stops updating.
// https://github.com/Koenkk/zigbee2mqtt/issues/23946#issuecomment-3176121660
const ep = device.getEndpoint(1);
await tuya.sendDataPointValue(ep, 105, 0, "dataRequest", 1);
await utils.sleep(2000);
await tuya.sendDataPointValue(ep, 105, 0, "dataRequest", 0);
},
}),
],
exposes: [
e.numeric("tds", ea.STATE).withUnit("ppm").withDescription("Total Dissolved Solids"),
e.temperature(),
e.battery(),
e
.numeric("ph", ea.STATE)
.withUnit("pH")
.withDescription("pH value, if the pH value is lower than 6.5, it means that the water quality " +
"is too acidic and has impurities, and it is necessary to add disinfectant water for disinfection"),
e.numeric("ec", ea.STATE).withUnit("µS/cm").withDescription("Electrical conductivity"),
e
.numeric("orp", ea.STATE)
.withUnit("mV")
.withDescription("Oxidation Reduction Potential value. If the ORP value is above 850mv, it " +
"means that the disinfectant has been added too much, and it is necessary to add water or change the water for neutralization. " +
"If the ORP value is below 487mv, it means that too little disinfectant has been added and the pool needs to be disinfected again"),
e
.numeric("free_chlorine", ea.STATE)
.withUnit("mg/L")
.withDescription("Free chlorine value. The water in the swimming pool should " +
"be between 6.5-8ph and ORP should be between 487-840mv, and the chlorine value will be displayed normally. Chlorine will not " +
"be displayed if either value is out of range"),
e.numeric("ph_max", ea.STATE_SET).withUnit("pH").withDescription("pH maximal value").withValueMin(0).withValueMax(140),
e.numeric("ph_min", ea.STATE_SET).withUnit("pH").withDescription("pH minimal value").withValueMin(0).withValueMax(140),
e
.numeric("ec_max", ea.STATE_SET)
.withUnit("µS/cm")
.withDescription("Electrical Conductivity maximal value")
.withValueMin(0)
.withValueMax(20000),
e
.numeric("ec_min", ea.STATE_SET)
.withUnit("µS/cm")
.withDescription("Electrical Conductivity minimal value")
.withValueMin(0)
.withValueMax(20000),
e
.numeric("orp_max", ea.STATE_SET)
.withUnit("mV")
.withDescription("Oxidation Reduction Potential maximal value")
.withValueMin(0)
.withValueMax(1200),
e
.numeric("orp_min", ea.STATE_SET)
.withUnit("mV")
.withDescription("Oxidation Reduction Potential minimal value")
.withValueMin(0)
.withValueMax(1200),
e
.numeric("free_chlorine_max", ea.STATE_SET)
.withUnit("mg/L")
.withDescription("Free Chlorine maximal value")
.withValueMin(0)
.withValueMax(40),
e
.numeric("free_chlorine_min", ea.STATE_SET)
.withUnit("mg/L")
.withDescription("Free Chlorine minimal value")
.withValueMin(0)
.withValueMax(40),
e.numeric("salinity", ea.STATE).withUnit("ppm").withDescription("Salt value"),
e.numeric("backlightvalue", ea.STATE_SET).withUnit("gg").withDescription("Backlight Value").withValueMin(0).withValueMax(1),
],
meta: {
// All datapoints go in here
tuyaDatapoints: [
[1, "tds", tuya.valueConverter.raw],
[2, "temperature", tuya.valueConverter.divideBy10],
[7, "battery", tuya.valueConverter.raw],
[
10,
"ph",
{
from: (v) => {
if (v > 99) {
return v / 100;
}
return v / 10;
},
},
],
[11, "ec", tuya.valueConverter.raw],
[101, "orp", tuya.valueConverter.raw],
[102, "free_chlorine", tuya.valueConverter.divideBy10],
[105, "backlightvalue", tuya.valueConverter.raw],
[106, "ph_max", tuya.valueConverter.raw],
[107, "ph_min", tuya.valueConverter.raw],
[108, "ec_max", tuya.valueConverter.raw],
[109, "ec_min", tuya.valueConverter.raw],
[110, "orp_max", tuya.valueConverter.raw],
[111, "orp_min", tuya.valueConverter.raw],
[112, "free_chlorine_max", tuya.valueConverter.raw],
[113, "free_chlorine_min", tuya.valueConverter.raw],
[117, "salinity", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_mgxy2d9f"]),
model: "SP02-ZB001",
vendor: "iAlarm",
description: "Infrared motion sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [e.tamper(), e.battery(), e.occupancy()],
meta: {
tuyaDatapoints: [
[1, "occupancy", tuya.valueConverter.trueFalse0],
[4, "battery", tuya.valueConverter.raw],
[5, "tamper", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0201", ["_TZE200_iq4ygaai", "_TZE200_01fvxamo"]),
model: "THS317-ET-TY",
vendor: "Tuya",
description: "Temperature sensor with probe",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [e.temperature(), e.battery()],
whiteLabel: [tuya.whitelabel("OWON", "THS317-ET-EY", "Temperature sensor with probe", ["_TZE200_01fvxamo"])],
meta: {
tuyaDatapoints: [
[1, "temperature", tuya.valueConverter.divideBy10],
[4, "battery", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_iuk8kupi", "_TZE204_iuk8kupi"]),
model: "DCR-RQJ",
vendor: "Tuya",
description: "Carbon monoxide sensor gas leak detector",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [e.gas(), tuya.exposes.gasValue().withUnit("LEL %"), e.carbon_monoxide(), e.co()],
meta: {
tuyaDatapoints: [
[1, "gas", tuya.valueConverter.trueFalseEnum0],
[2, "gas_value", tuya.valueConverter.divideBy1000],
[18, "carbon_monoxide", tuya.valueConverter.trueFalseEnum0],
[19, "co", tuya.valueConverter.divideBy100],
],
},
},
{
zigbeeModel: ["ZG-101Z"],
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_nojsjtj2", "_TZE200_vrcfo4i0"]),
model: "ZG-101Z",
vendor: "Loginovo",
description: "SOS button",
extend: [
tuya.modernExtend.combineActions([
tuya.modernExtend.dpAction({ dp: 26, lookup: { sos: 0 } }),
tuya.modernExtend.dpAction({ dp: 29, lookup: { emergency: 0 } }),
]),
m.iasZoneAlarm({ zoneType: "generic", zoneAttributes: ["battery_low"] }),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_muvkrjr5"]),
model: "SZR07U",
vendor: "Tuya",
description: "24GHz millimeter wave radar",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence(),
e
.numeric("detection_range", ea.STATE_SET)
.withValueMin(1.5)
.withValueMax(6)
.withValueStep(0.75)
.withUnit("m")
.withDescription("Maximum range"),
e
.numeric("radar_sensitivity", ea.STATE_SET)
.withValueMin(68)
.withValueMax(90)
.withValueStep(1)
.withDescription("Sensitivity of the radar"),
e
.numeric("target_distance", ea.STATE)
.withValueMin(0)
.withValueMax(1000)
.withValueStep(1)
.withDescription("Distance of detected target")
.withUnit("cm"),
e.binary("indicator", ea.STATE_SET, "ON", "OFF").withDescription("LED indicator"),
e.numeric("fading_time", ea.STATE_SET).withValueMin(3).withValueMax(1799).withValueStep(1).withDescription("Fading time").withUnit("s"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse1],
[13, "detection_range", tuya.valueConverter.divideBy100],
[16, "radar_sensitivity", tuya.valueConverter.raw],
[19, "target_distance", tuya.valueConverter.raw],
[101, "indicator", tuya.valueConverter.onOff],
[102, null, null], // toggle to enable presence notifications in app is ignored
[103, "fading_time", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0004", ["_TZ3000_5ajpkyq6", "_TZ3000_knoj8lpk", "_TZ3000_3n2minvf", "_TZ3000_tyg4yiat", "_TZ3210_wts1g2oh"]),
model: "TS0004_switch_module_2",
vendor: "Tuya",
description: "4 gang switch module",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
switchType: true,
onOffCountdown: true,
indicatorMode: true,
inchingSwitch: (m) => m === "_TZ3000_tyg4yiat",
endpoints: ["l1", "l2", "l3", "l4"],
}),
],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3, l4: 4 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(4), coordinatorEndpoint, ["genOnOff"]);
},
whiteLabel: [
tuya.whitelabel("AVATTO", "ZWSM16-4", "4 gang switch module", ["_TZ3000_5ajpkyq6", "_TZ3000_3n2minvf"]),
tuya.whitelabel("Moes", "ZM4LT4", "4-gang switch module", ["_TZ3000_tyg4yiat"]),
tuya.whitelabel("iHseno", "_TZ3000_knoj8lpk", "4 gang switch module", ["_TZ3000_knoj8lpk"]),
],
},
{
fingerprint: tuya.fingerprint("TS0004", ["_TZ3210_iymfxdis"]),
model: "SA-4W",
vendor: "Nova Digital",
description: "Safira smart light switch - 4 gang",
extend: [
tuya.modernExtend.tuyaBase(),
m.deviceEndpoints({ endpoints: { l1: 1, l2: 2, l3: 3, l4: 4 } }),
tuya.modernExtend.tuyaOnOff({
backlightModeOffOn: true,
powerOnBehavior3: true,
endpoints: ["l1", "l2", "l3", "l4"],
}),
],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
for (const endpointId of [1, 2, 3, 4]) {
const endpoint = device.getEndpoint(endpointId);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff"]);
await reporting.onOff(endpoint);
}
},
},
{
fingerprint: tuya.fingerprint("TS1002", ["_TZ3000_etufnltx"]),
model: "F00XN00-04-1",
vendor: "FORIA",
description: "Dimmer 4 scenes",
extend: [
tuya.modernExtend.tuyaMagicPacket(),
m.battery({ voltage: true }),
tuya.modernExtend.combineActions([
m.actionEnumLookup({
actionLookup: { scene_1: 1, scene_2: 2, scene_3: 3, scene_4: 4 },
cluster: "genOnOff",
commands: ["commandTuyaAction"],
attribute: "data",
parse: (msg, attr) => msg.data[attr][1],
}),
m.commandsOnOff(),
m.commandsLevelCtrl({
commands: ["brightness_move_up", "brightness_move_down", "brightness_stop"],
}),
]),
],
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE204_kobbcyum",
"_TZE284_kobbcyum",
"_TZE284_hecsejsb",
"_TZE284_432zhuwe",
"_TZE204_432zhuwe",
"_TZE284_s5vuaadg",
"_TZE284_tuhfx7tf",
"_TZE204_tuhfx7tf",
]),
model: "TOWSMR1",
vendor: "Tongou",
description: "Single-phase multifunction RCBO (DIN Module)",
extend: [
tuya.modernExtend.tuyaBase({
dp: true,
queryOnConfigure: true,
queryIntervalSeconds: 10,
respondToMcuVersionResponse: true,
}),
],
whiteLabel: [
tuya.whitelabel("Tongou", "TOWSMR1-40A-AC", "Single-phase multifunction RCBO (DIN Module)", ["_TZE284_432zhuwe", "_TZE204_432zhuwe"]),
tuya.whitelabel("Tongou", "TOWSMR1-40A-A", "Single-phase multifunction RCBO (DIN Module)", ["_TZE284_s5vuaadg"]),
tuya.whitelabel("Tongou", "TOWSMR1-20A-AC", "Single-phase multifunction RCBO (DIN Module)", ["_TZE284_tuhfx7tf", "_TZE204_tuhfx7tf"]),
],
exposes: (device, options) => {
const exps = [
tuya.exposes.switch(),
e.temperature(),
e.current(),
e.power(),
e.voltage(),
e.energy(),
e
.enum("event", ea.STATE, [
"normal",
"over_current_trip",
"over_power_trip",
"high_temp_trip",
"over_voltage_trip",
"under_voltage_trip",
"over_current_alarm",
"over_power_alarm",
"high_temp_alarm",
"over_voltage_alarm",
"under_voltage_alarm",
"remote_on",
"remote_off",
"manual_on",
"manual_off",
"leakage_trip",
"leakage_alarm",
"restore_default",
"automatic_closing",
"electricity_shortage",
"electricity_shortage_alarm",
"timing_switch_On",
"timing_switch_off",
])
.withDescription("Last event of the device"),
e.enum("over_voltage_setting", ea.STATE_SET, ["Ignore", "Alarm", "Trip"]).withDescription("Over voltage setting"),
e
.numeric("over_voltage_threshold", ea.STATE_SET)
.withUnit("V")
.withDescription("Setup value on the device")
.withValueMin(240)
.withValueMax(295),
e.enum("under_voltage_setting", ea.STATE_SET, ["Ignore", "Alarm", "Trip"]).withDescription("Under voltage setting"),
e
.numeric("under_voltage_threshold", ea.STATE_SET)
.withUnit("V")
.withDescription("Setup value on the device")
.withValueMin(145)
.withValueMax(220),
e.enum("temperature_setting", ea.STATE_SET, ["Ignore", "Alarm", "Trip"]).withDescription("Temperature setting"),
e
.numeric("temperature_threshold", ea.STATE_SET)
.withUnit("°C")
.withDescription("Setup value on the device")
.withValueMin(-25)
.withValueMax(100),
e.enum("over_power_setting", ea.STATE_SET, ["Ignore", "Alarm", "Trip"]).withDescription("Over power setting"),
e
.numeric("over_power_threshold", ea.STATE_SET)
.withUnit("W")
.withDescription("Setup value on the device")
.withValueStep(10)
.withValueMin(5)
.withValueMax(25000),
e
.binary("auto_reclosing", ea.STATE_SET, "ON", "OFF")
.withLabel("Auto reclosing")
.withDescription("When the circuit breaker trips due to voltage protection, it will automatically close when the circuit voltage returns to normal"),
e.binary("restore_default", ea.STATE_SET, "ON", "OFF").withDescription("Turn ON to restore default settings"),
e
.binary("overcurrent_recloser", ea.STATE_SET, "ON", "OFF")
.withLabel("Overcurrent Recloser")
.withDescription("When the circuit breaker trips due to overcurrent protection, it will automatically close when the circuit voltage returns to normal"),
e
.binary("leakage_recloser", ea.STATE_SET, "ON", "OFF")
.withLabel("Leakage Recloser")
.withDescription("When the circuit breaker trips due to leakage protection, it will automatically close when the circuit voltage returns to normal"),
e
.binary("overpower_recloser", ea.STATE_SET, "ON", "OFF")
.withLabel("Overpower Recloser")
.withDescription("When the circuit breaker trips due to overpower protection, it will automatically close when the circuit voltage returns to normal"),
];
if (!device || device.manufacturerName === "_TZE284_432zhuwe" || device.manufacturerName === "_TZE204_432zhuwe") {
exps.push(te.leakageCurrent(), e.enum("over_current_setting", ea.STATE_SET, ["Ignore", "Alarm", "Trip"]).withDescription("Over current setting"), e
.numeric("over_current_threshold", ea.STATE_SET)
.withUnit("A")
.withDescription("Setup the value on the device")
.withValueMin(1)
.withValueMax(40), e.enum("leakage_setting", ea.STATE_SET, ["Ignore", "Alarm", "Trip"]).withDescription("Leakage setting"), e
.numeric("leakage_threshold", ea.STATE_SET)
.withUnit("mA")
.withDescription("Setup value on the device")
.withValueStep(5)
.withValueMin(30)
.withValueMax(100));
}
else if (device.manufacturerName === "_TZE284_s5vuaadg") {
exps.push(e.enum("over_current_setting", ea.STATE_SET, ["Ignore", "Alarm", "Trip"]).withDescription("Over current setting"), e
.numeric("over_current_threshold", ea.STATE_SET)
.withUnit("A")
.withDescription("Setup the value on the device")
.withValueMin(1)
.withValueMax(40));
}
else if (device.manufacturerName === "_TZE284_tuhfx7tf" || device.manufacturerName === "_TZE204_tuhfx7tf") {
exps.push(te.leakageCurrent(), e.enum("over_current_setting", ea.STATE_SET, ["Ignore", "Alarm", "Trip"]).withDescription("Over current setting"), e
.numeric("over_current_threshold", ea.STATE_SET)
.withUnit("A")
.withDescription("Setup the value on the device")
.withValueMin(1)
.withValueMax(20), e.enum("leakage_setting", ea.STATE_SET, ["Ignore", "Alarm", "Trip"]).withDescription("Leakage setting"), e
.numeric("leakage_threshold", ea.STATE_SET)
.withUnit("mA")
.withDescription("Setup value on the device")
.withValueStep(5)
.withValueMin(30)
.withValueMax(100));
}
return exps;
},
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[6, null, tuya.valueConverter.phaseVariant2], // voltage and current
[15, "leakage_current", tuya.valueConverter.raw],
[16, "state", tuya.valueConverter.onOff],
[
110,
"event",
tuya.valueConverterBasic.lookup({
normal: 0,
over_current_trip: 1,
over_power_trip: 2,
high_temp_trip: 3,
over_voltage_trip: 4,
under_voltage_trip: 5,
over_current_alarm: 6,
over_power_alarm: 7,
high_temp_alarm: 8,
over_voltage_alarm: 9,
under_voltage_alarm: 10,
remote_on: 11,
remote_off: 12,
manual_on: 13,
manual_off: 14,
leakage_trip: 15,
leakage_alarm: 16,
restore_default: 17,
automatic_closing: 18,
electricity_shortage: 19,
electricity_shortage_alarm: 20,
timing_switch_on: 21,
timing_switch_off: 22,
}),
],
[
102,
"over_voltage_setting",
tuya.valueConverterBasic.lookup({
Ignore: tuya.enum(0),
Alarm: tuya.enum(1),
Trip: tuya.enum(2),
}),
],
[
103,
"under_voltage_setting",
tuya.valueConverterBasic.lookup({
Ignore: tuya.enum(0),
Alarm: tuya.enum(1),
Trip: tuya.enum(2),
}),
],
[
104,
"over_current_setting",
tuya.valueConverterBasic.lookup({
Ignore: tuya.enum(0),
Alarm: tuya.enum(1),
Trip: tuya.enum(2),
}),
],
[
105,
"over_power_setting",
tuya.valueConverterBasic.lookup({
Ignore: tuya.enum(0),
Alarm: tuya.enum(1),
Trip: tuya.enum(2),
}),
],
[
107,
"temperature_setting",
tuya.valueConverterBasic.lookup({
Ignore: tuya.enum(0),
Alarm: tuya.enum(1),
Trip: tuya.enum(2),
}),
],
[
108,
"leakage_setting",
tuya.valueConverterBasic.lookup({
Ignore: tuya.enum(0),
Alarm: tuya.enum(1),
Trip: tuya.enum(2),
}),
],
[112, "auto_reclosing", tuya.valueConverter.onOff],
[113, "restore_default", tuya.valueConverter.onOff],
[114, "over_current_threshold", tuya.valueConverter.raw],
[115, "over_voltage_threshold", tuya.valueConverter.raw],
[116, "under_voltage_threshold", tuya.valueConverter.raw],
[117, "leakage_threshold", tuya.valueConverter.raw],
[118, "temperature_threshold", tuya.valueConverter.divideBy10],
[119, "over_power_threshold", tuya.valueConverter.raw],
[131, "temperature", tuya.valueConverter.divideBy10],
// The ones below might only work for _TZE284_432zhuwe
// https://github.com/Koenkk/zigbee-herdsman-converters/pull/9747
[143, "overcurrent_recloser", tuya.valueConverter.onOff],
[144, "leakage_recloser", tuya.valueConverter.onOff],
[145, "overpower_recloser", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: [{ modelID: "TS0601", manufacturerName: "_TZE284_6ocnqlhn" }],
model: "TO-Q-SYS-JZT",
vendor: "Tongou",
description: "Din rail smart meter",
extend: [
tuya.modernExtend.tuyaBase({
dp: true,
queryOnConfigure: true,
queryIntervalSeconds: 10,
respondToMcuVersionResponse: true,
}),
],
exposes: [
tuya.exposes.switch(),
e.power(),
e.current(),
e.voltage(),
e.energy(),
e.ac_frequency(),
e.power_factor().withUnit("%"),
e.numeric("temperature", ea.STATE).withUnit("°C").withDescription("Current temperature"),
e
.enum("event", ea.STATE, [
"normal",
"over_current_trip",
"over_power_trip",
"high_temp_trip",
"over_voltage_trip",
"under_voltage_trip",
"over_current_alarm",
"over_power_alarm",
"high_temp_alarm",
"over_voltage_alarm",
"under_voltage_alarm",
"remote_on",
"remote_off",
"manual_on",
"manual_off",
"leakage_trip",
"leakage_alarm",
"restore_default",
"automatic_closing",
"electricity_shortage",
"electricity_shortage_alarm",
"timing_switch_On",
"timing_switch_off",
])
.withDescription("Last event of the device"),
e.enum("control_mode", ea.STATE_SET, ["local_lock", "local_mode", "remote_mode", "full_control"]).withDescription("Device control mode"),
e.enum("over_current_setting", ea.STATE_SET, ["Ignore", "Alarm", "Trip"]).withDescription("Over current setting"),
e
.numeric("over_current_threshold", ea.STATE_SET)
.withUnit("A")
.withDescription("Setup the value on the device")
.withValueMin(1)
.withValueMax(50),
e.enum("over_voltage_setting", ea.STATE_SET, ["Ignore", "Alarm", "Trip"]).withDescription("Over voltage setting"),
e
.numeric("over_voltage_threshold", ea.STATE_SET)
.withUnit("V")
.withDescription("Setup value on the device")
.withValueMin(240)
.withValueMax(295),
e.enum("under_voltage_setting", ea.STATE_SET, ["Ignore", "Alarm", "Trip"]).withDescription("Under voltage setting"),
e
.numeric("under_voltage_threshold", ea.STATE_SET)
.withUnit("V")
.withDescription("Setup value on the device")
.withValueMin(90)
.withValueMax(220),
e.enum("temperature_setting", ea.STATE_SET, ["Ignore", "Alarm", "Trip"]).withDescription("Temperature setting"),
e
.numeric("temperature_threshold", ea.STATE_SET)
.withUnit("°C")
.withDescription("Setup value on the device")
.withValueMin(-25)
.withValueMax(80),
e.enum("over_power_setting", ea.STATE_SET, ["Ignore", "Alarm", "Trip"]).withDescription("Over power setting"),
e
.numeric("over_power_threshold", ea.STATE_SET)
.withUnit("W")
.withDescription("Setup value on the device")
.withValueMin(1000)
.withValueMax(26000),
e.numeric("test1", ea.STATE), // ?
e.numeric("test5", ea.STATE), // ?
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[6, null, tuya.valueConverter.phaseVariant2],
[13, "test1", tuya.valueConverter.raw], // ?
[15, "leakage_current", tuya.valueConverter.raw],
[16, "state", tuya.valueConverter.onOff],
[32, "ac_frequency", tuya.valueConverter.raw],
[50, "power_factor", tuya.valueConverter.raw],
[
102,
"over_voltage_setting",
tuya.valueConverterBasic.lookup({
Ignore: tuya.enum(0),
Alarm: tuya.enum(1),
Trip: tuya.enum(2),
}),
],
[
103,
"under_voltage_setting",
tuya.valueConverterBasic.lookup({
Ignore: tuya.enum(0),
Alarm: tuya.enum(1),
Trip: tuya.enum(2),
}),
],
[
104,
"over_current_setting",
tuya.valueConverterBasic.lookup({
Ignore: tuya.enum(0),
Alarm: tuya.enum(1),
Trip: tuya.enum(2),
}),
],
[
105,
"over_power_setting",
tuya.valueConverterBasic.lookup({
Ignore: tuya.enum(0),
Alarm: tuya.enum(1),
Trip: tuya.enum(2),
}),
],
[
107,
"temperature_setting",
tuya.valueConverterBasic.lookup({
Ignore: tuya.enum(0),
Alarm: tuya.enum(1),
Trip: tuya.enum(2),
}),
],
[
108,
"control_mode",
tuya.valueConverterBasic.lookup({
local_lock: tuya.enum(0),
local_mode: tuya.enum(1),
remote_mode: tuya.enum(2),
full_control: tuya.enum(3),
}),
],
[
110,
"event",
tuya.valueConverterBasic.lookup({
normal: 0,
over_current_trip: 1,
over_power_trip: 2,
high_temp_trip: 3,
over_voltage_trip: 4,
under_voltage_trip: 5,
over_current_alarm: 6,
over_power_alarm: 7,
high_temp_alarm: 8,
over_voltage_alarm: 9,
under_voltage_alarm: 10,
remote_on: 11,
remote_off: 12,
manual_on: 13,
manual_off: 14,
leakage_trip: 15,
leakage_alarm: 16,
restore_default: 17,
automatic_closing: 18,
electricity_shortage: 19,
electricity_shortage_alarm: 20,
timing_switch_on: 21,
timing_switch_off: 22,
}),
],
[114, "over_current_threshold", tuya.valueConverter.raw],
[115, "over_voltage_threshold", tuya.valueConverter.raw],
[116, "under_voltage_threshold", tuya.valueConverter.raw],
[118, "temperature_threshold", tuya.valueConverter.divideBy10],
[119, "over_power_threshold", tuya.valueConverter.raw],
[125, "test5", tuya.valueConverter.raw], // ?
[131, "temperature", tuya.valueConverter.divideBy10],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_l6llgoxq"]),
model: "EA4161C-BI",
vendor: "Tuya",
description: "Single-phase multifunction energy meter (DIN Module)",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [e.current(), e.power(), e.voltage(), e.energy(), e.text("meter_id", ea.STATE).withDescription("Meter ID (ID of device)")],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[3, null, null], // Monthly, but sends data only after request
[4, null, null], // Dayly, but sends data only after request
[6, null, tuya.valueConverter.phaseVariant2], // voltage and current
[
10,
"fault",
tuya.valueConverterBasic.lookup({
clear: 0,
over_current_threshold: 1,
over_power_threshold: 2,
over_voltage_threshold: 4,
wrong_frequency_threshold: 8,
}),
],
[11, null, null], // Frozen - strange function, in native app - nothing is clear
[16, "state", tuya.valueConverter.onOff],
[17, null, tuya.valueConverter.threshold], // It's settable, but can't write converter
[18, "meter_id", tuya.valueConverter.raw],
[20, "clear_fault", tuya.valueConverter.onOff], // Clear fault
[21, null, null], // Forward Energy T1 - don't know what this
[22, null, null], // Forward Energy T2 - don't know what this
[23, null, null], // Forward Energy T3 - don't know what this
[24, null, null], // Forward Energy T4 - don't know what this
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZ3000_kkerjand"]),
model: "SZT06 V2.0",
vendor: "Tuya",
description: "Smart mini temperature and humidity sensor",
extend: [m.temperature(), m.humidity(), m.identify({ isSleepy: true }), m.battery({ voltage: true })],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_pl31aqf5", "_TZE200_xpvamyfz", "_TZE284_xpvamyfz"]),
model: "ZR360CDB",
vendor: "Zorro Alert",
description: "Multifunctional CO2 detector",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
whiteLabel: [tuya.whitelabel("Nous", "E10", "Multifunctional CO2 detector", ["_TZE200_xpvamyfz", "_TZE284_xpvamyfz"])],
exposes: [
e.humidity(),
e.temperature(),
e.co2(),
e.enum("alarm_ringtone", ea.STATE_SET, ["melody_1", "melody_2", "OFF"]).withDescription("Ringtone of the alarm"),
e.numeric("backlight_mode", ea.STATE_SET).withValueMin(1).withValueMax(3).withValueStep(1).withDescription("Backlight"),
tuya.exposes.batteryState(),
e.enum("air_quality", ea.STATE_GET, ["excellent", "moderate", "poor"]),
],
meta: {
tuyaDatapoints: [
[
1,
"air_quality",
tuya.valueConverterBasic.lookup({
excellent: tuya.enum(0),
moderate: tuya.enum(1),
poor: tuya.enum(2),
}),
],
[2, "co2", tuya.valueConverter.raw],
[
5,
"alarm_ringtone",
tuya.valueConverterBasic.lookup({
melody_1: tuya.enum(0),
melody_2: tuya.enum(1),
OFF: tuya.enum(2),
}),
],
[14, "battery_state", tuya.valueConverter.batteryState],
[17, "backlight_mode", tuya.valueConverter.raw],
[18, "temperature", tuya.valueConverter.raw],
[19, "humidity", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS110E", ["_TZ3210_guijtl8k", "_TZ3210_hquixjeg"]),
model: "QS-Zigbee-D04",
vendor: "LEDRON",
description: "0-10v dimmer",
fromZigbee: [tuya.fz.TS110E, fz.on_off],
extend: [tuyaBase()],
toZigbee: [tuya.tz.TS110E_onoff_brightness, tuya.tz.TS110E_options, tz.light_brightness_move],
whiteLabel: [tuya.whitelabel("Ledron", "QS-Zigbee-D06-DC", "Dimmer 12-36v", ["_TZ3210_hquixjeg"])],
exposes: [e.light_brightness().withMinBrightness().withMaxBrightness()],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff", "genLevelCtrl"]);
await reporting.onOff(endpoint);
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_edl8pz1k", "_TZE204_edl8pz1k", "_TZE204_6a4vxfnv"]),
model: "TS0601_floor_thermostat",
vendor: "Tuya",
description: "Zigbee thermostat for electric floors",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "1970" })],
exposes: [
e
.climate()
.withSystemMode(["off", "heat"], ea.STATE_SET)
.withPreset(["manual", "auto"])
.withRunningState(["idle", "heat"], ea.STATE)
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withDescription("Floor temperature")
.withLocalTemperatureCalibration(-9, 9, 0.1, ea.STATE_SET)
.withDescription("Calibration floor temperature sensor"),
e.deadzone_temperature().withValueMin(0).withValueMax(5).withValueStep(1).withDescription("Floor temperature"),
e.child_lock(),
...tuya.exposes.scheduleAllDays(ea.STATE_SET, "HH:MM/C HH:MM/C HH:MM/C HH:MM/C"),
],
meta: {
tuyaDatapoints: [
[1, "system_mode", tuya.valueConverterBasic.lookup({ heat: true, off: false })],
[2, "preset", tuya.valueConverter.tv02Preset()],
[16, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[24, "device_temperature", tuya.valueConverter.divideBy10],
[27, "local_temperature_calibration", tuya.valueConverter.raw],
[
36,
"running_state",
tuya.valueConverterBasic.lookup({
heat: tuya.enum(0),
idle: tuya.enum(1),
}),
],
[40, "child_lock", tuya.valueConverter.lockUnlock],
[102, "local_temperature", tuya.valueConverter.divideBy10],
[103, "deadzone_temperature", tuya.valueConverter.raw],
[110, "schedule_monday", tuya.valueConverter.thermostatScheduleDayMultiDP],
[109, "schedule_tuesday", tuya.valueConverter.thermostatScheduleDayMultiDP],
[108, "schedule_wednesday", tuya.valueConverter.thermostatScheduleDayMultiDP],
[107, "schedule_thursday", tuya.valueConverter.thermostatScheduleDayMultiDP],
[106, "schedule_friday", tuya.valueConverter.thermostatScheduleDayMultiDP],
[105, "schedule_saturday", tuya.valueConverter.thermostatScheduleDayMultiDP],
[101, "schedule_sunday", tuya.valueConverter.thermostatScheduleDayMultiDP],
],
},
whiteLabel: [{ vendor: "ELECTSMART", model: "EST-120Z" }],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_khah2lkr"]),
model: "TE-1Z",
vendor: "Tuya",
description: "Floor heating thermostat",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "2000" })],
exposes: [
// e.binary('system_mode', ea.STATE_SET, 'ON', 'OFF')
// .withDescription('Turn system on or standby mode'),
e.binary("state", ea.STATE_SET, "ON", "OFF").withDescription("Turn system on or standby mode"),
e
.climate()
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withRunningState(["idle", "heat"], ea.STATE)
// you can change preset, but can't make auto back remotely so I would set this readonly
.withPreset(["auto", "manual", "mixed"])
.withLocalTemperatureCalibration(-9, 9, 1, ea.STATE_SET)
.withLocalTemperature(ea.STATE),
e.child_lock(),
// you can change it to IN remotely but can not set it back, so I set it read only
e
.enum("sensor_mode", ea.STATE, ["IN", "OU", "AL"])
.withDescription("IN - internal sensor, no heat protection. OU - external sensor, no heat protection. AL - internal sensor for room temperature, external for heat protection"),
e
.binary("high_temperature_protection_state", ea.STATE_SET, "ON", "OFF")
.withDescription("If temperature hit the HIGH temperature limit, it " +
"will turn off heating until it drops for amount of deadzone/hysteresis " +
"degrees"),
e.binary("low_temperature_protection_state", ea.STATE_SET, "ON", "OFF"),
e
.numeric("high_temperature_protection_setting", ea.STATE_SET)
.withUnit("°C")
.withValueMin(20)
.withValueMax(70)
.withDescription("Alarm temperature max")
.withPreset("default", 45, "Default value"),
e
.numeric("low_temperature_protection_setting", ea.STATE_SET)
.withUnit("°C")
.withValueMin(1)
.withValueMax(10)
.withDescription("Alarm temperature min")
.withPreset("default", 5, "Default value"),
e.numeric("temperature_sensor", ea.STATE).withUnit("°C").withDescription("Floor temperature from external sensor"),
e
.deadzone_temperature()
.withValueMin(1)
.withValueMax(9)
.withValueStep(1)
.withUnit("°C")
.withDescription("Hysteresis")
.withPreset("default", 1, "Default value"),
e.max_temperature().withValueMin(20).withValueMax(70).withPreset("default", 35, "Default value"),
// @todo not tested
tuya.exposes.errorStatus(),
],
meta: {
tuyaDatapoints: [
// internal sensor temperature
[16, "local_temperature", tuya.valueConverter.divideBy10],
[50, "current_heating_setpoint", tuya.valueConverter.divideBy10],
// data type 1
[102, "running_state", tuya.valueConverterBasic.lookup({ idle: false, heat: true })],
[103, "temperature_sensor", tuya.valueConverter.divideBy10],
// can be changed by setting 112 below 20, data type 1
[106, "high_temperature_protection_state", tuya.valueConverter.onOff],
// can be changed by setting 113 over 10, data type 1
[107, "low_temperature_protection_state", tuya.valueConverter.onOff],
// range -9 to +9, data type 2, affects shown room temperature (even tho sensors detect its 19, you can make it show 21 by setting this to 2)
[109, "local_temperature_calibration", tuya.valueConverter.divideBy10],
// according to manual settable between 0.5 and 2.5 degree.
// staring with 5 as 0.5 degree, and 25 as 2.5 degree (data type 2)
[110, "temperature_return_difference", tuya.valueConverter.raw],
// range 1-9. How far should temperature drop to turn back heating, if high temp protection kicked in
[111, "deadzone_temperature", tuya.valueConverter.raw],
// High temperature protection
// range 20-70, trying to turn below 20 keeps this datapoint at 20 but turns 106 to 0
[112, "high_temperature_protection_setting", tuya.valueConverter.raw],
// range 1-10, trying to turn over 10 keeps this datapoint at 10, but turns 107 to 0
[113, "low_temperature_protection_setting", tuya.valueConverter.raw],
[114, "max_temperature", tuya.valueConverter.raw],
// choose_sensor
// 0: device sensor. Switches "higsyht_temperature_protection_state" off
// 1: external sensor / high temperature protection off
// 2: internal for room + external for high temperature protection. Switches "hight_temperature_protection_state" on
[116, "sensor_mode", tuya.valueConverterBasic.lookup({ IN: 0, OU: 1, AL: 2 })],
// once every 24h it provides an array of numbers, maybe device fingerprint or something
// [119, 'unknown_119', tuya.valueConverter.raw],
// [120, 'unknown_120', tuya.valueConverter.raw],
// [121, 'unknown_121', tuya.valueConverter.raw],
// [122, 'unknown_122', tuya.valueConverter.raw],
// device state
// 0: standby mode - displays temperature but will not turn heating. Manual configuration is accessible only in this state
// 1: fully functional, can turn on heating
[125, "system_mode", tuya.valueConverter.onOff],
[125, "state", tuya.valueConverter.onOff],
// manual_mode
// 1: automatically turns heating when protection levels hit
// 2: you can turn off heating if it heats. You can set up target temperature
// 3: you can set up target temperature and it will automatically try to maintain it
[128, "preset", tuya.valueConverterBasic.lookup({ auto: 1, manual: 0, mixed: 3 })],
[129, "child_lock", tuya.valueConverter.lockUnlock],
// data type 5. Was [0] but went [16] when high temperature protection went into alert mode
[130, "error_status", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_tagezcph"]),
model: "PRO-900Z",
vendor: "ElectSmart",
description: "Thermostat for electric floor",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "2000" })],
exposes: [
e.binary("child_lock", ea.STATE_SET, "ON", "OFF").withLabel("Child lock"),
e.binary("eco_mode", ea.STATE_SET, "OFF", "ON").withLabel("ECO mode").withDescription("Default: Off"),
e
.numeric("eco_temperature", ea.STATE_SET)
.withValueMin(5)
.withValueMax(30)
.withValueStep(1)
.withUnit("°C")
.withDescription("Max temperature in ECO mode. Default: 20"),
e
.climate()
.withSystemMode(["off", "heat"], ea.STATE_SET)
.withPreset(["manual", "auto"])
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withLocalTemperatureCalibration(-9, 9, 1, ea.STATE_SET)
.withDescription("Default: -3")
.withRunningState(["idle", "heat"], ea.STATE),
e
.numeric("deadzone_temperature", ea.STATE_SET)
.withValueMin(1)
.withValueMax(5)
.withValueStep(1)
.withUnit("°C")
.withDescription("Hysteresis. Default: 1"),
e.numeric("min_temperature", ea.STATE_SET).withValueMin(5).withValueMax(15).withValueStep(1).withUnit("°C").withDescription("Default: 5"),
e
.numeric("max_temperature", ea.STATE_SET)
.withValueMin(15)
.withValueMax(45)
.withValueStep(1)
.withUnit("°C")
.withDescription("Default: 35"),
e
.numeric("min_temperature_limit", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(1)
.withUnit("°C")
.withLabel("Low temperature protection")
.withDescription("Default: 0"),
e
.numeric("max_temperature_limit", ea.STATE_SET)
.withValueMin(25)
.withValueMax(70)
.withValueStep(1)
.withUnit("°C")
.withLabel("High temperature protection")
.withDescription("Default: 45"),
e.temperature_sensor_select(["IN", "OU", "AL"]).withLabel("Sensor").withDescription("Choose which sensor to use. Default: AL"),
e
.numeric("external_temperature_input", ea.STATE)
.withLabel("Floor temperature")
.withUnit("°C")
.withDescription("Temperature from floor sensor"),
e
.numeric("brightness", ea.STATE_SET)
.withValueMin(0)
.withValueMax(8)
.withValueStep(1)
.withLabel("Screen brightness 06:00 - 22:00")
.withDescription("0 - on for 10 seconds. Default: 6"),
e
.numeric("display_brightness", ea.STATE_SET)
.withValueMin(0)
.withValueMax(8)
.withValueStep(1)
.withLabel("Screen brightness 22:00 - 06:00")
.withDescription("0 - on for 10 seconds. Default: 3"),
e
.text("schedule_monday", ea.STATE_SET)
.withLabel("Schedule for monday")
.withDescription("Default: 06:00/20.0 11:30/20.0 13:30/20.0 17:30/20.0"),
e
.text("schedule_tuesday", ea.STATE_SET)
.withLabel("Schedule for tuesday")
.withDescription("Default: 06:00/20.0 11:30/20.0 13:30/20.0 17:30/20.0"),
e
.text("schedule_wednesday", ea.STATE_SET)
.withLabel("Schedule for wednesday")
.withDescription("Default: 06:00/20.0 11:30/20.0 13:30/20.0 17:30/20.0"),
e
.text("schedule_thursday", ea.STATE_SET)
.withLabel("Schedule for thursday")
.withDescription("Default: 06:00/20.0 11:30/20.0 13:30/20.0 17:30/20.0"),
e
.text("schedule_friday", ea.STATE_SET)
.withLabel("Schedule for friday")
.withDescription("Default: 06:00/20.0 11:30/20.0 13:30/20.0 17:30/20.0"),
e
.text("schedule_saturday", ea.STATE_SET)
.withLabel("Schedule for saturday")
.withDescription("Default: 06:00/20.0 11:30/20.0 13:30/20.0 17:30/20.0"),
e
.text("schedule_sunday", ea.STATE_SET)
.withLabel("Schedule for sunday")
.withDescription("Default: 06:00/20.0 11:30/20.0 13:30/20.0 17:30/20.0"),
e.enum("factory_reset", ea.STATE_SET, ["factory reset"]).withLabel("Factory reset").withDescription("Reset all settings to factory ones"),
],
meta: {
tuyaDatapoints: [
[1, "system_mode", tuya.valueConverterBasic.lookup({ off: false, heat: true })],
[
2,
"preset",
tuya.valueConverterBasic.lookup({
auto: tuya.enum(0),
manual: tuya.enum(1),
}),
],
[16, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[19, "max_temperature", tuya.valueConverter.divideBy10],
[24, "local_temperature", tuya.valueConverter.divideBy10],
[26, "min_temperature", tuya.valueConverter.divideBy10],
[27, "local_temperature_calibration", tuya.valueConverter.raw],
[28, "factory_reset", tuya.valueConverterBasic.lookup({ factory_reset: true })],
[
36,
"running_state",
tuya.valueConverterBasic.lookup({
idle: tuya.enum(1),
heat: tuya.enum(0),
}),
],
[39, "child_lock", tuya.valueConverterBasic.lookup({ ON: true, OFF: false })],
[40, "eco_mode", tuya.valueConverterBasic.lookup({ ON: true, OFF: false })],
[
43,
"sensor",
tuya.valueConverterBasic.lookup({
IN: tuya.enum(0),
OU: tuya.enum(2),
AL: tuya.enum(1),
}),
],
[102, "external_temperature_input", tuya.valueConverter.divideBy10],
[103, "deadzone_temperature", tuya.valueConverter.raw],
[104, "max_temperature_limit", tuya.valueConverter.divideBy10],
[101, "schedule_monday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(1)],
[105, "schedule_tuesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(2)],
[106, "schedule_wednesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(3)],
[107, "schedule_thursday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(4)],
[108, "schedule_friday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(5)],
[109, "schedule_saturday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(6)],
[110, "schedule_sunday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(7)],
[111, "min_temperature_limit", tuya.valueConverter.divideBy10],
[112, "eco_temperature", tuya.valueConverter.divideBy10],
[113, "brightness", tuya.valueConverter.raw],
[114, "display_brightness", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_dsagrkvg", "_TZE284_zm8zpwas", "_TZE284_sdvbnmj5"]),
model: "ZPV-01",
vendor: "Novato",
description: "Battery powered smart valve",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.switch().setAccess("state", ea.STATE_SET),
e.enum("valve_state", ea.STATE, ["Closed", "Unknown", "Open"]).withDescription("Valve Status"),
e.battery(),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[
8,
"valve_state",
tuya.valueConverterBasic.lookup({
unknown: tuya.enum(0),
open: tuya.enum(1),
closed: tuya.enum(2),
}),
],
[101, "battery", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS000F", ["_TZ3218_7fiyo3kv"]),
model: "TYZGTH1CH-D1RF",
vendor: "Mumubiz",
description: "Smart switch with temperature/humidity sensor",
meta: {
tuyaSendCommand: "sendData",
},
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaMagicPacket(),
tuya.modernExtend.tuyaOnOff({
powerOutageMemory: true,
switchType: false,
}),
tuya.modernExtend.dpChildLock({ dp: 0x6f }),
tuya.modernExtend.dpTemperature({ dp: 0x66 }),
tuya.modernExtend.dpHumidity({ dp: 0x67 }),
tuya.modernExtend.dpNumeric({
dp: 0x6c,
name: "temperature_calibration",
type: tuya.dataTypes.number,
valueMin: -10,
valueMax: 10,
valueStep: 0.1,
unit: "°C",
scale: 10,
description: "Temperature calibration",
}),
tuya.modernExtend.dpNumeric({
dp: 0x6d,
name: "humidity_calibration",
type: tuya.dataTypes.number,
valueMin: -10,
valueMax: 10,
unit: "%",
description: "Humidity calibration",
}),
tuya.modernExtend.dpNumeric({
dp: 0x71,
name: "temperature_sensitivity",
type: tuya.dataTypes.number,
valueMin: 0.1,
valueMax: 1,
valueStep: 0.1,
unit: "°C",
scale: 10,
description: "Temperature sensitivity",
}),
tuya.modernExtend.dpNumeric({
dp: 0x70,
name: "humidity_sensitivity",
type: tuya.dataTypes.number,
valueMin: 1,
valueMax: 10,
unit: "%",
description: "Humidity sensitivity",
}),
tuya.modernExtend.dpBinary({
name: "manual_mode",
dp: 0x65,
type: tuya.dataTypes.enum,
valueOn: ["ON", 1],
valueOff: ["OFF", 0],
description: "Manual mode, ON = auto settings disabled, OFF = auto settings enabled",
}),
tuya.modernExtend.dpTHZBSettings(),
],
},
{
fingerprint: tuya.fingerprint("TS000F", ["_TZ3218_ya5d6wth"]),
model: "TYZGTH4CH-D1RF",
vendor: "Mumubiz",
description: "4 channel changeover contact with temperature and humidity sensing",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
powerOnBehavior2: true,
onOffCountdown: true,
inchingSwitch: true,
endpoints: ["l1", "l2", "l3", "l4"],
}),
tuya.clusters.addTuyaCommonPrivateCluster(),
tuya.modernExtend.dpTemperature({ dp: 102, scale: 10 }),
tuya.modernExtend.dpHumidity({ dp: 103 }),
],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3, l4: 4 };
},
exposes: [],
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
for (const ep of [1, 2, 3, 4]) {
await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ["genOnOff"]);
}
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_tre6haif"]),
model: "TS0601_pir_solar",
vendor: "Tuya",
description: "Solar PIR occupancy sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
meta: {
tuyaDatapoints: [
[1, "occupancy", tuya.valueConverter.trueFalse0],
[4, "battery", tuya.valueConverter.raw],
[9, "pir_sensitivity", tuya.valueConverterBasic.lookup({ high: 0, low: 1 })],
[101, "alarm_time", tuya.valueConverter.raw],
[102, "alarm_mode", tuya.valueConverter.alarmMode],
],
},
exposes: [
e.occupancy(),
e.enum("pir_sensitivity", ea.STATE_SET, ["high", "low"]).withDescription("PIR sensitivity (0=high, 1=low)"),
te.alarmTime(),
te.alarmMode(),
e.battery(),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_uxllnywp"]),
model: "RT_ZCZ03Z",
vendor: "Tuya",
description: "Human presence sensor 24G",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.illuminance(),
e.presence(),
e
.numeric("detection_distance_max", ea.STATE_SET)
.withValueMin(0)
.withValueMax(840)
.withValueStep(1)
.withDescription("Max detection distance")
.withUnit("cm"),
e
.numeric("detection_distance_min", ea.STATE_SET)
.withValueMin(0)
.withValueMax(840)
.withValueStep(1)
.withDescription("Min detection distance")
.withUnit("cm"),
e.numeric("target_distance", ea.STATE).withDescription("Distance to target").withUnit("cm"),
e.numeric("fading_time", ea.STATE_SET).withValueMin(1).withValueMax(59).withValueStep(1).withDescription("Delay time").withUnit("s"),
e.numeric("presence_sensitivity", ea.STATE_SET).withValueMin(1).withValueMax(10).withValueStep(1).withDescription("Presence sensitivity"),
e.binary("indicator", ea.STATE_SET, "ON", "OFF").withDescription("LED Indicator"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverterBasic.trueFalse(4)],
[101, "target_distance", tuya.valueConverter.raw],
[102, "illuminance", tuya.valueConverter.raw],
[103, "fading_time", tuya.valueConverter.raw],
[104, "indicator", tuya.valueConverter.onOff],
[107, "detection_distance_max", tuya.valueConverter.raw],
[108, "detection_distance_min", tuya.valueConverter.raw],
[111, "presence_sensitivity", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE284_q22avxbv",
"_TZE204_q22avxbv",
"_TZE204_mrffaamu",
"_TZE204_tzreobvu",
"_TZE284_mrffaamu",
"_TZE284_tzreobvu",
"_TZE284_9xstqowh",
"_TZE284_kv1nvirl",
]),
model: "TOQCB2-80",
vendor: "Tongou",
description: "Smart circuit breaker",
// Important: respondToMcuVersionResponse should be false otherwise there is an avalanche of commandMcuVersionResponse messages every second.
// queryIntervalSeconds: is doing a pooling to update the device's parameters, now defined to update data every 3 minutes.
extend: [tuya.modernExtend.tuyaBase({ dp: true, queryIntervalSeconds: 3 * 60 })],
whiteLabel: [tuya.whitelabel("Tongou", "TOQCB2-80-2P", "Smart circuit breaker (2P)", ["_TZE284_mrffaamu", "_TZE204_mrffaamu"])],
exposes: [
tuya.exposes.switch(),
e.energy(),
e.power(),
e.voltage(),
e.current(),
e.temperature(),
tuya.exposes.voltageWithPhase("a"),
tuya.exposes.voltageWithPhase("b"),
tuya.exposes.voltageWithPhase("c"),
tuya.exposes.powerWithPhase("a"),
tuya.exposes.powerWithPhase("b"),
tuya.exposes.powerWithPhase("c"),
tuya.exposes.currentWithPhase("a"),
tuya.exposes.currentWithPhase("b"),
tuya.exposes.currentWithPhase("c"),
e
.enum("last_event", ea.STATE, [
"normal",
"trip_over_current",
"trip_over_power",
"trip_over_temperature",
"trip_voltage_1",
"trip_voltage_2",
"alarm_over_current",
"alarm_over_power",
"alarm_over_temperature",
"alarm_voltage_1",
"alarm_voltage_2",
"remote_on",
"remote_off",
"manual_on",
"manual_off",
"value_15",
"value_16",
"factory_reset",
])
.withDescription("Last event"),
e.enum("over_current_setting", ea.STATE_SET, ["closed", "alarm", "trip"]).withDescription("Action if threshold value is reached"),
e
.numeric("current_threshold", ea.STATE_SET)
.withValueMin(1)
.withValueMax(63)
.withValueStep(1)
.withUnit("A")
.withDescription("Current threshold setting"),
e.enum("under_voltage_setting", ea.STATE_SET, ["closed", "alarm", "trip"]).withDescription("Action if threshold value is reached"),
e
.numeric("under_voltage_threshold", ea.STATE_SET)
.withValueMin(145)
.withValueMax(220)
.withValueStep(1)
.withUnit("V")
.withDescription("Under voltage threshold setting"),
e.enum("over_voltage_setting", ea.STATE_SET, ["closed", "alarm", "trip"]).withDescription("Action if threshold value is reached"),
e
.numeric("over_voltage_threshold", ea.STATE_SET)
.withValueMin(245)
.withValueMax(295)
.withValueStep(1)
.withUnit("V")
.withDescription("Over-voltage threshold setting"),
e.enum("over_power_setting", ea.STATE_SET, ["closed", "alarm", "trip"]).withDescription("Action if threshold value is reached"),
e
.numeric("over_power_threshold", ea.STATE_SET)
.withValueMin(200)
.withValueMax(20000)
.withValueStep(100)
.withUnit("W")
.withDescription("Over-power threshold setting"),
e.enum("temperature_setting", ea.STATE_SET, ["closed", "alarm", "trip"]).withDescription("Action if threshold value is reached"),
e
.numeric("temperature_threshold", ea.STATE_SET)
.withValueMin(-40)
.withValueMax(100)
.withValueStep(1)
.withUnit("°C")
.withDescription("Temperature threshold setting"),
e.binary("clear_fault", ea.STATE_SET, "ON", "OFF").withDescription("Recover from an incident"),
e.binary("factory_reset", ea.STATE_SET, "ON", "OFF").withDescription("Back to factory settings, USE WITH CAUTION"),
e
.binary("auto_reclosing", ea.STATE_SET, "ON", "OFF")
.withLabel("Auto-Reclosing")
.withDescription("When the circuit breaker trips due to voltage protection, it will automatically close when the circuit voItage returns to normal.(Note: For safety reasons, this function only applies to trips caused by voltage)"),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
//[6, null, tuya.valueConverter.phaseVariant2],
[3, null, null], // Monthly, but sends data only after request
[4, null, null], // Dayly, but sends data only after request
[6, null, tuya.valueConverter.phaseVariant2WithPhase("a")],
[7, null, tuya.valueConverter.phaseVariant2WithPhase("b")],
[8, null, tuya.valueConverter.phaseVariant2WithPhase("c")],
[16, "state", tuya.valueConverter.onOff],
[
102,
"over_voltage_setting",
tuya.valueConverterBasic.lookup({
closed: tuya.enum(0),
alarm: tuya.enum(1),
trip: tuya.enum(2),
}),
],
[
103,
"under_voltage_setting",
tuya.valueConverterBasic.lookup({
closed: tuya.enum(0),
alarm: tuya.enum(1),
trip: tuya.enum(2),
}),
],
[
104,
"over_current_setting",
tuya.valueConverterBasic.lookup({
closed: tuya.enum(0),
alarm: tuya.enum(1),
trip: tuya.enum(2),
}),
],
[
105,
"over_power_setting",
tuya.valueConverterBasic.lookup({
closed: tuya.enum(0),
alarm: tuya.enum(1),
trip: tuya.enum(2),
}),
],
[
107,
"temperature_setting",
tuya.valueConverterBasic.lookup({
closed: tuya.enum(0),
alarm: tuya.enum(1),
trip: tuya.enum(2),
}),
],
//109, 'online_state, unknown, I have not seen any message from this DP],
[
110,
"last_event",
tuya.valueConverterBasic.lookup({
normal: tuya.enum(0),
trip_over_current: tuya.enum(1),
trip_over_power: tuya.enum(2),
trip_over_temperature: tuya.enum(3),
trip_voltage_1: tuya.enum(4),
trip_voltage_2: tuya.enum(5),
alarm_over_current: tuya.enum(6),
alarm_over_power: tuya.enum(7),
alarm_over_temperature: tuya.enum(8),
alarm_voltage_1: tuya.enum(9),
alarm_voltage_2: tuya.enum(10),
remote_on: tuya.enum(11),
remote_off: tuya.enum(12),
manual_on: tuya.enum(13),
manual_off: tuya.enum(14),
value_15: tuya.enum(15),
value_16: tuya.enum(16),
factory_reset: tuya.enum(17),
}),
],
[112, "clear_fault", tuya.valueConverter.onOff],
[113, "factory_reset", tuya.valueConverter.onOff],
[114, "current_threshold", tuya.valueConverter.raw],
[115, "over_voltage_threshold", tuya.valueConverter.raw],
[116, "under_voltage_threshold", tuya.valueConverter.raw],
[118, "temperature_threshold", tuya.valueConverter.divideBy10],
[119, "over_power_threshold", tuya.valueConverter.raw],
//[125, 'forward_electricity', tuya.valueConverter.divideBy100],
[131, "temperature", tuya.valueConverter.divideBy10],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_nbkshs6k"]),
model: "ZY-M100-S_3",
vendor: "Tuya",
description: "Human presence detector",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence(),
e.illuminance(),
e
.enum("sensitivity", ea.STATE_SET, ["low", "medium", "high"])
.withDescription("PIR sensor sensitivity (refresh and update only while active)"),
e.enum("keep_time", ea.STATE_SET, ["30", "60", "120"]).withDescription("PIR keep time in seconds (refresh and update only while active)"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverterBasic.lookup({ True: 0, False: 1 })],
[
9,
"sensitivity",
tuya.valueConverterBasic.lookup({
low: tuya.enum(0),
medium: tuya.enum(1),
high: tuya.enum(2),
}),
],
[
10,
"keep_time",
tuya.valueConverterBasic.lookup({
"30": tuya.enum(0),
"60": tuya.enum(1),
"120": tuya.enum(2),
}),
],
[12, "illuminance", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_dapwryy7"]),
model: "ZG-205Z",
vendor: "Tuya",
description: "5.8 GHz human presence sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence(),
e
.enum("presence_state", ea.STATE, ["none", "presence", "peaceful", "small_movement", "large_movement"])
.withDescription("The presence state"),
e
.numeric("target_distance", ea.STATE)
.withValueMin(0)
.withValueMax(10)
.withValueStep(0.01)
.withUnit("m")
.withDescription("Target distance"),
e.illuminance(),
e.binary("indicator", ea.STATE_SET, "ON", "OFF").withDescription("LED Indicator"),
e
.numeric("none_delay_time", ea.STATE_SET)
.withValueMin(0)
.withValueMax(28800)
.withValueStep(1)
.withUnit("Sec")
.withDescription("Hold delay time"),
e
.numeric("move_detection_max", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(0.01)
.withUnit("m")
.withDescription("Move detection max distance"),
e
.numeric("move_detection_min", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(0.01)
.withUnit("m")
.withDescription("Move detection min distance"),
e
.numeric("small_move_detection_max", ea.STATE_SET)
.withValueMin(0)
.withValueMax(6)
.withValueStep(0.01)
.withUnit("m")
.withDescription("Small move detection max distance"),
e
.numeric("small_move_detection_min", ea.STATE_SET)
.withValueMin(0)
.withValueMax(6)
.withValueStep(0.01)
.withUnit("m")
.withDescription("Small move detection min distance"),
e
.numeric("breath_detection_max", ea.STATE_SET)
.withValueMin(0)
.withValueMax(6)
.withValueStep(0.01)
.withUnit("m")
.withDescription("Breath detection max distance"),
e
.numeric("breath_detection_min", ea.STATE_SET)
.withValueMin(0)
.withValueMax(6)
.withValueStep(0.01)
.withUnit("m")
.withDescription("Breath detection min distance"),
e.numeric("move_sensitivity", ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(1).withDescription("Move sensitivity"),
e.numeric("breath_sensitivity", ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(1).withDescription("Breath sensitivity"),
e
.numeric("small_move_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(1)
.withDescription("Small Move sensitivity"),
],
meta: {
tuyaDatapoints: [
[
1,
null,
{
from: (v) => {
const lookup = {
none: tuya.enum(0),
presence: tuya.enum(1),
peaceful: tuya.enum(2),
small_movement: tuya.enum(3),
large_movement: tuya.enum(4),
};
const presenceState = Object.entries(lookup).find((i) => i[1].valueOf() === v)[0];
return {
presence: presenceState !== "none",
presence_state: presenceState,
};
},
},
],
[101, "target_distance", tuya.valueConverter.divideBy100],
[102, "illuminance", tuya.valueConverter.raw],
[103, "none_delay_time", tuya.valueConverter.raw],
[104, "indicator", tuya.valueConverter.onOff],
[107, "move_detection_max", tuya.valueConverter.divideBy100],
[108, "move_detection_min", tuya.valueConverter.divideBy100],
[109, "breath_detection_max", tuya.valueConverter.divideBy100],
[110, "breath_detection_min", tuya.valueConverter.divideBy100],
[114, "small_move_detection_max", tuya.valueConverter.divideBy100],
[115, "small_move_detection_min", tuya.valueConverter.divideBy100],
[116, "move_sensitivity", tuya.valueConverter.raw],
[117, "small_move_sensitivity", tuya.valueConverter.raw],
[118, "breath_sensitivity", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_ncti2pro", "_TZE204_l8xiyymq", "_TZE284_l8xiyymq", "_TZE284_zeldawjv"]),
model: "PN6",
vendor: "ZSVIOT",
description: "6-way controller",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.switch(),
tuya.exposes.switchMode2().withEndpoint("l1_l2").withLabel("1-2 channels"),
tuya.exposes.switch().withEndpoint("l1"),
tuya.exposes.switch().withEndpoint("l2"),
tuya.exposes.switchMode2().withEndpoint("l3_l4").withLabel("3-4 channels"),
tuya.exposes.switch().withEndpoint("l3"),
tuya.exposes.switch().withEndpoint("l4"),
tuya.exposes.switchMode2().withEndpoint("l5_l6").withLabel("5-6 channels"),
tuya.exposes.switch().withEndpoint("l5"),
tuya.exposes.switch().withEndpoint("l6"),
tuya.exposes.switchType(),
e.power_on_behavior(["off", "on", "previous"]).withAccess(ea.STATE_SET),
],
endpoint: (device) => {
return {
l1: 1,
l2: 1,
l3: 1,
l4: 1,
l5: 1,
l6: 1,
state: 1,
l1_l2: 1,
l3_l4: 1,
l5_l6: 1,
};
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
[4, "state_l4", tuya.valueConverter.onOff],
[5, "state_l5", tuya.valueConverter.onOff],
[6, "state_l6", tuya.valueConverter.onOff],
[13, "state", tuya.valueConverter.onOff],
[14, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum],
[107, "switch_type", tuya.valueConverter.switchType],
[113, "switch_mode_l1_l2", tuya.valueConverter.switchMode2],
[114, "switch_mode_l3_l4", tuya.valueConverter.switchMode2],
[115, "switch_mode_l5_l6", tuya.valueConverter.switchMode2],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_zqq3cipq", "_TZE284_zqq3cipq"]),
model: "PN16",
vendor: "ZSVIOT",
description: "16-way controller",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.power_on_behavior(["off", "on", "previous"]).withAccess(ea.STATE_SET),
tuya.exposes.switch().withEndpoint("all"),
tuya.exposes.switchType().withEndpoint("l1_l8").withLabel("Switch type 1-8 channels"),
tuya.exposes.switch().withEndpoint("l1"),
tuya.exposes.switch().withEndpoint("l2"),
tuya.exposes.switch().withEndpoint("l3"),
tuya.exposes.switch().withEndpoint("l4"),
tuya.exposes.switch().withEndpoint("l5"),
tuya.exposes.switch().withEndpoint("l6"),
tuya.exposes.switch().withEndpoint("l7"),
tuya.exposes.switch().withEndpoint("l8"),
tuya.exposes.switchType().withEndpoint("l9_l16").withLabel("Switch type 9-16 channels"),
tuya.exposes.switch().withEndpoint("l9"),
tuya.exposes.switch().withEndpoint("l10"),
tuya.exposes.switchMode2().withEndpoint("l11_l12").withLabel("Mode 11-12 channels"),
tuya.exposes.switch().withEndpoint("l11"),
tuya.exposes.switch().withEndpoint("l12"),
tuya.exposes.switchMode2().withEndpoint("l13_l14").withLabel("Mode 13-14 channels"),
tuya.exposes.switch().withEndpoint("l13"),
tuya.exposes.switch().withEndpoint("l14"),
tuya.exposes.switchMode2().withEndpoint("l15_l16").withLabel("Mode 15-16 channels"),
tuya.exposes.switch().withEndpoint("l15"),
tuya.exposes.switch().withEndpoint("l16"),
],
endpoint: (device) => {
return {
all: 1,
l1: 1,
l2: 1,
l3: 1,
l4: 1,
l5: 1,
l6: 1,
l7: 1,
l8: 1,
l9: 1,
l10: 1,
l11: 1,
l12: 1,
l13: 1,
l14: 1,
l15: 1,
l16: 1,
l1_l8: 1,
l9_l16: 1,
l11_l12: 1,
l13_l14: 1,
l15_l16: 1,
};
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_all", tuya.valueConverter.onOff],
[101, "state_l1", tuya.valueConverter.onOff],
[102, "state_l2", tuya.valueConverter.onOff],
[103, "state_l3", tuya.valueConverter.onOff],
[104, "state_l4", tuya.valueConverter.onOff],
[105, "state_l5", tuya.valueConverter.onOff],
[106, "state_l6", tuya.valueConverter.onOff],
[107, "state_l7", tuya.valueConverter.onOff],
[108, "state_l8", tuya.valueConverter.onOff],
[109, "state_l9", tuya.valueConverter.onOff],
[110, "state_l10", tuya.valueConverter.onOff],
[111, "state_l11", tuya.valueConverter.onOff],
[112, "state_l12", tuya.valueConverter.onOff],
[113, "state_l13", tuya.valueConverter.onOff],
[114, "state_l14", tuya.valueConverter.onOff],
[115, "state_l15", tuya.valueConverter.onOff],
[116, "state_l16", tuya.valueConverter.onOff],
[117, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum],
[118, "switch_type_l1_l8", tuya.valueConverter.switchType],
[119, "switch_type_l9_l16", tuya.valueConverter.switchType],
[120, "switch_mode_l11_l12", tuya.valueConverter.switchMode2],
[121, "switch_mode_l13_l14", tuya.valueConverter.switchMode2],
[122, "switch_mode_l15_l16", tuya.valueConverter.switchMode2],
],
},
},
{
zigbeeModel: ["TS0105"],
model: "TS0105",
vendor: "Tuya",
description: "3 gang switch",
extend: [tuyaBase({ dp: true }), m.deviceEndpoints({ endpoints: { l1: 1, l2: 1, l3: 1 } })],
exposes: [
e.switch().withEndpoint("l1").setAccess("state", ea.STATE_SET),
e.switch().withEndpoint("l2").setAccess("state", ea.STATE_SET),
e.switch().withEndpoint("l3").setAccess("state", ea.STATE_SET),
],
meta: {
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
],
},
},
{
zigbeeModel: ["ZG-103Z"],
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_iba1ckek", "_TZE200_hggxgsjj", "_TZE200_yjryxpot", "_TZE200_afycb3cg"]),
model: "ZG-103Z",
vendor: "Tuya",
description: "Vibration sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.vibration(),
e.tilt(),
e.numeric("x", ea.STATE).withValueMin(0).withValueMax(256).withValueStep(1).withDescription("X coordinate"),
e.numeric("y", ea.STATE).withValueMin(0).withValueMax(256).withValueStep(1).withDescription("Y coordinate"),
e.numeric("z", ea.STATE).withValueMin(0).withValueMax(256).withValueStep(1).withDescription("Z coordinate"),
e.battery(),
e.enum("sensitivity", ea.STATE_SET, ["low", "middle", "high"]).withDescription("Vibration detection sensitivity"),
],
meta: {
tuyaDatapoints: [
[1, "vibration", tuya.valueConverter.trueFalseEnum1],
[7, "tilt", tuya.valueConverter.trueFalseEnum1],
[101, "x", tuya.valueConverter.raw],
[102, "y", tuya.valueConverter.raw],
[103, "z", tuya.valueConverter.raw],
[
104,
"sensitivity",
tuya.valueConverterBasic.lookup({
low: tuya.enum(0),
middle: tuya.enum(1),
high: tuya.enum(2),
}),
],
[105, "battery", tuya.valueConverter.raw],
],
},
},
{
zigbeeModel: ["ZG-302ZM"],
fingerprint: tuya.fingerprint("TS0601", [
"_TZE200_kccdzaeo",
"_TZE200_s7rsrtbg",
"_TZE200_tmszbtzq",
"_TZE200_bfmfhxra",
"_TZE200_ahpcyzth",
"_TZE200_kijxnb8q",
]),
model: "ZG-302ZM",
vendor: "HOBEIAN",
description: "Motion sensing switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence(),
e.binary("switch1", ea.STATE_SET, "ON", "OFF").withDescription("Switch1"),
e.binary("switch2", ea.STATE_SET, "ON", "OFF").withDescription("Switch2"),
e.binary("switch3", ea.STATE_SET, "ON", "OFF").withDescription("Switch3"),
e
.numeric("distance", ea.STATE_SET)
.withValueMin(0)
.withValueMax(6)
.withValueStep(0.1)
.withUnit("m")
.withDescription("detection distance"),
e.numeric("sensitivity", ea.STATE_SET).withValueMin(0).withValueMax(19).withValueStep(1).withDescription("detection sensitivity"),
e.binary("backlight", ea.STATE_SET, "ON", "OFF").withDescription("backlight"),
e
.numeric("trigger_hold", ea.STATE_SET)
.withValueMin(5)
.withValueMax(28800)
.withValueStep(1)
.withUnit("s")
.withDescription("Trigger hold(second)"),
tuya.exposes.powerOutageMemory(),
e
.enum("auto_on", ea.STATE_SET, ["off", "all", "ch1", "ch2", "ch3", "ch1_2", "ch2_3", "ch1_3"])
.withDescription("Someone turn on the light"),
e
.enum("auto_off", ea.STATE_SET, ["off", "all", "ch1", "ch2", "ch3", "ch1_2", "ch2_3", "ch1_3"])
.withDescription("No one turns off the lights"),
e.enum("trigger_switch", ea.STATE_SET, ["ch1", "ch2", "ch3"]).withDescription("Switch state reversal"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse1],
[2, "sensitivity", tuya.valueConverter.raw],
[4, "distance", tuya.valueConverter.divideBy100],
[101, "switch1", tuya.valueConverter.onOff],
[102, "switch2", tuya.valueConverter.onOff],
[103, "switch3", tuya.valueConverter.onOff],
[111, "backlight", tuya.valueConverter.onOff],
[114, "trigger_hold", tuya.valueConverter.raw],
[
112,
"power_outage_memory",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
on: tuya.enum(1),
restore: tuya.enum(2),
}),
],
[
113,
"auto_on",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
all: tuya.enum(1),
ch1: tuya.enum(2),
ch2: tuya.enum(3),
ch3: tuya.enum(4),
ch1_2: tuya.enum(5),
ch2_3: tuya.enum(6),
ch1_3: tuya.enum(7),
}),
],
[
115,
"auto_off",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
all: tuya.enum(1),
ch1: tuya.enum(2),
ch2: tuya.enum(3),
ch3: tuya.enum(4),
ch1_2: tuya.enum(5),
ch2_3: tuya.enum(6),
ch1_3: tuya.enum(7),
}),
],
[
108,
"trigger_switch",
tuya.valueConverterBasic.lookup({
ch1: tuya.enum(0),
ch2: tuya.enum(1),
ch3: tuya.enum(2),
}),
],
],
},
},
{
zigbeeModel: ["ZG-302ZL"],
fingerprint: tuya.fingerprint("TS0601", [
"_TZE200_khzbklyh",
"_TZE200_df04ghrb",
"_TZE200_toeldckg",
"_TZE200_cqtamhh5",
"_TZE200_xlnzk169",
"_TZE200_llvwkkde",
]),
model: "ZG-302ZL",
vendor: "HOBEIAN",
description: "Motion sensing switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence(),
e.binary("switch_1", ea.STATE_SET, "ON", "OFF").withDescription("Switch2"),
e.binary("switch_2", ea.STATE_SET, "ON", "OFF").withDescription("Switch2"),
e.binary("switch_3", ea.STATE_SET, "ON", "OFF").withDescription("Switch3"),
e.numeric("sensitivity", ea.STATE_SET).withValueMin(0).withValueMax(19).withValueStep(1).withDescription("detection sensitivity"),
e.binary("backlight", ea.STATE_SET, "ON", "OFF").withDescription("backlight"),
e
.numeric("trigger_hold", ea.STATE_SET)
.withValueMin(5)
.withValueMax(28800)
.withValueStep(1)
.withUnit("s")
.withDescription("Trigger hold(second)"),
tuya.exposes.powerOutageMemory(),
e
.enum("auto_on", ea.STATE_SET, ["off", "all", "ch1", "ch2", "ch3", "ch1_and_ch2", "ch2_and_ch3", "ch1_and_ch3"])
.withDescription("When somebody passes in front of their sensors, the lights turn"),
e
.enum("auto_off", ea.STATE_SET, ["off", "all", "ch1", "ch2", "ch3", "ch1_and_ch2", "ch2_and_ch3", "ch1_and_ch3"])
.withDescription("No one turns off the lights"),
],
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[101, "presence", tuya.valueConverter.trueFalse1],
[102, "sensitivity", tuya.valueConverter.raw],
[1, "switch_1", tuya.valueConverter.onOff],
[2, "switch_2", tuya.valueConverter.onOff],
[3, "switch_3", tuya.valueConverter.onOff],
[16, "backlight", tuya.valueConverter.onOff],
[103, "trigger_hold", tuya.valueConverter.raw],
[
14,
"power_outage_memory",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
on: tuya.enum(1),
restore: tuya.enum(2),
}),
],
[
104,
"auto_on",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
all: tuya.enum(1),
ch1: tuya.enum(1),
ch2: tuya.enum(2),
ch3: tuya.enum(3),
ch1_and_ch2: tuya.enum(4),
ch2_and_ch3: tuya.enum(5),
ch1_and_ch3: tuya.enum(6),
}),
],
[
105,
"auto_off",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
all: tuya.enum(1),
ch1: tuya.enum(1),
ch2: tuya.enum(2),
ch3: tuya.enum(3),
ch1_and_ch2: tuya.enum(4),
ch2_and_ch3: tuya.enum(5),
ch1_and_ch3: tuya.enum(6),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_wqashyqo"]),
model: "ZG-303Z",
vendor: "HOBEIAN",
description: "Soil moisture sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.enum("water_warning", ea.STATE, ["none", "alarm"]).withDescription("Water shortage warning"),
e.temperature(),
e.humidity(),
e.soil_moisture(),
tuya.exposes.temperatureUnit(),
tuya.exposes.temperatureCalibration(),
tuya.exposes.humidityCalibration(),
tuya.exposes.soilCalibration(),
tuya.exposes.temperatureSampling(),
tuya.exposes.soilSampling(),
tuya.exposes.soilWarning(),
e.battery(),
],
meta: {
tuyaDatapoints: [
[
1,
"water_warning",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
alarm: tuya.enum(1),
}),
],
[103, "temperature", tuya.valueConverter.divideBy10],
[109, "humidity", tuya.valueConverter.raw],
[107, "soil_moisture", tuya.valueConverter.raw],
[108, "battery", tuya.valueConverter.raw],
[106, "temperature_unit", tuya.valueConverter.temperatureUnit],
[104, "temperature_calibration", tuya.valueConverter.divideBy10],
[105, "humidity_calibration", tuya.valueConverter.raw],
[102, "soil_calibration", tuya.valueConverter.raw],
[111, "temperature_sampling", tuya.valueConverter.raw],
[112, "soil_sampling", tuya.valueConverter.raw],
[110, "soil_warning", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_fhvdgeuh", "_TZE200_abatw3kj", "_TZE204_4bjixefp"]),
model: "TS0601_din_4",
vendor: "Tuya",
description: "Din rail switch with power monitoring and threshold settings",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
configure: async (device, coordinatorEndpoint) => {
// Required to get the device to start reporting
await device.getEndpoint(1).command("manuSpecificTuya", "dataQuery", {});
},
whiteLabel: [tuya.whitelabel("RTX", "TS0601_RTX_DIN", "Din rail switch", ["_TZE200_abatw3kj"])],
exposes: [
e.switch().setAccess("state", ea.STATE_SET),
e.power(),
e.current(),
e.voltage(),
e.energy(),
e.power_on_behavior().withAccess(ea.STATE_SET),
e.numeric("temperature", ea.STATE).withUnit("°C").withDescription("Current temperature"),
e.numeric("leakage", ea.STATE).withUnit("mA").withDescription("Current leakage"),
te.circuitBreakerFaults(),
e.numeric("reclosing_allowed_times", ea.STATE_SET).withValueMin(0).withValueMax(30).withDescription("Reclosing tries"),
e.binary("reclosing_enable", ea.STATE_SET, "ON", "OFF").withLabel("Auto reclosing"),
e.numeric("timer", ea.STATE_SET).withValueMin(0).withValueMax(86400).withUnit("s"),
e.binary("clear_energy", ea.STATE_SET, "ON", "OFF").withLabel("Clear energy"),
e.text("status", ea.STATE).withDescription("Status"),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100], // Total forward energy
[6, null, tuya.valueConverter.phaseVariant2], // Phase A voltage and current
[9, "faults", tuya.valueConverter.circuitBreakerFaults],
// [11, 'switch_prepayment', tuya.valueConverter.raw], // no expose
[12, "clear_energy", tuya.valueConverter.raw],
// [14, 'charge_energy', tuya.valueConverter.raw], // no expose
[15, "leakage", tuya.valueConverter.raw],
[16, "state", tuya.valueConverter.onOff],
[102, "reclosing_allowed_times", tuya.valueConverter.raw],
[103, "temperature", tuya.valueConverter.raw],
[104, "reclosing_enable", tuya.valueConverter.onOff],
[105, "timer", tuya.valueConverter.raw],
// [106, 'cycle_schedule', tuya.valueConverter.raw], // no expose
// [107, 'reclose_recover_seconds', tuya.valueConverter.raw], // no expose
// [108, 'random_timing', tuya.valueConverter.raw], // no expose
// [109, 'switch_inching', tuya.valueConverter.raw], // no expose
// [119, 'power_on_delay_power_on_time', tuya.valueConverter.raw], // no expose
// [124, 'overcurrent_event_threshold_time', tuya.valueConverter.raw], // no expose
// [125, 'time_threshold_of_lost_flow_event', tuya.valueConverter.raw], // no expose
[
127,
"status",
tuya.valueConverterBasic.lookup({
standby: 0,
active: 1,
}),
],
[134, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_hcxvyxa5"]),
model: "ZA03",
vendor: "Tuya",
description: "Siren alarm",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.binary("alarm", ea.STATE_SET, "ON", "OFF").withDescription("Sound the alarm"),
e.enum("volume", ea.STATE_SET, ["low", "medium", "high", "mute"]),
e.enum("ringtone", ea.STATE_SET, [
"ringtone 1",
"ringtone 2",
"ringtone 3",
"ringtone 4",
"ringtone 5",
"ringtone 6",
"ringtone 7",
"ringtone 8",
"ringtone 9",
"ringtone 10",
"ringtone 11",
"ringtone 12",
"ringtone 13",
"ringtone 14",
"ringtone 15",
"ringtone 16",
"ringtone 17",
"ringtone 18",
"ringtone 19",
"ringtone 20",
"ringtone 21",
"ringtone 22",
"ringtone 23",
"ringtone 24",
"ringtone 25",
"ringtone 26",
"ringtone 27",
"ringtone 28",
"ringtone 29",
"ringtone 30",
"ringtone 31",
"ringtone 32",
]),
e
.numeric("duration", ea.STATE_SET)
.withValueMin(1)
.withValueMax(380)
.withValueStep(1)
.withUnit("s")
.withDescription("How long the alarm sounds for when triggered"),
],
meta: {
tuyaDatapoints: [
[
5,
"volume",
tuya.valueConverterBasic.lookup({
low: tuya.enum(0),
medium: tuya.enum(1),
high: tuya.enum(2),
mute: tuya.enum(3),
}),
],
[7, "duration", tuya.valueConverter.raw],
[13, "alarm", tuya.valueConverter.onOff],
[
21,
"ringtone",
tuya.valueConverterBasic.lookup({
"ringtone 1": tuya.enum(0),
"ringtone 2": tuya.enum(1),
"ringtone 3": tuya.enum(2),
"ringtone 4": tuya.enum(3),
"ringtone 5": tuya.enum(4),
"ringtone 6": tuya.enum(5),
"ringtone 7": tuya.enum(6),
"ringtone 8": tuya.enum(7),
"ringtone 9": tuya.enum(8),
"ringtone 10": tuya.enum(9),
"ringtone 11": tuya.enum(10),
"ringtone 12": tuya.enum(11),
"ringtone 13": tuya.enum(12),
"ringtone 14": tuya.enum(13),
"ringtone 15": tuya.enum(14),
"ringtone 16": tuya.enum(15),
"ringtone 17": tuya.enum(16),
"ringtone 18": tuya.enum(17),
"ringtone 19": tuya.enum(18),
"ringtone 20": tuya.enum(19),
"ringtone 21": tuya.enum(20),
"ringtone 22": tuya.enum(21),
"ringtone 23": tuya.enum(22),
"ringtone 24": tuya.enum(23),
"ringtone 25": tuya.enum(24),
"ringtone 26": tuya.enum(25),
"ringtone 27": tuya.enum(26),
"ringtone 28": tuya.enum(27),
"ringtone 29": tuya.enum(28),
"ringtone 30": tuya.enum(29),
"ringtone 31": tuya.enum(30),
"ringtone 32": tuya.enum(31),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_ex3rcdha", "_TZE204_lbbg34rj"]),
model: "ZY_HPS01",
vendor: "Tuya",
description: "mmWave radar 5.8GHz",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
whiteLabel: [tuya.whitelabel("Nova Digital", "ZTS-MM", "mmWave radar 5.8GHz", ["_TZE204_lbbg34rj"])],
exposes: [
e.illuminance().withUnit("lx"),
e.occupancy(),
e
.numeric("presence_timeout", ea.STATE_SET)
.withValueMin(0)
.withValueMax(180)
.withValueStep(1)
.withDescription("Presence timeout")
.withUnit("s"),
e.numeric("move_sensitivity", ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(1).withDescription("sensitivity of the radar"),
e
.numeric("move_minimum_range", ea.STATE_SET)
.withValueMin(0)
.withValueMax(600)
.withValueStep(10)
.withDescription("Movement minimum range")
.withUnit("cm"),
e
.numeric("move_maximum_range", ea.STATE_SET)
.withValueMin(0)
.withValueMax(600)
.withValueStep(10)
.withDescription("Movement maximum range")
.withUnit("cm"),
e
.numeric("breath_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(1)
.withDescription("Breath sensitivity of the radar"),
e
.numeric("breath_minimum_range", ea.STATE_SET)
.withValueMin(0)
.withValueMax(600)
.withValueStep(10)
.withDescription("Breath minimum range")
.withUnit("cm"),
e
.numeric("breath_maximum_range", ea.STATE_SET)
.withValueMin(0)
.withValueMax(600)
.withValueStep(10)
.withDescription("Breath maximum range")
.withUnit("cm"),
],
meta: {
tuyaDatapoints: [
[12, "illuminance", tuya.valueConverter.raw],
[101, "occupancy", tuya.valueConverter.trueFalse0],
[104, "presence_timeout", tuya.valueConverter.raw],
[105, "move_sensitivity", tuya.valueConverter.raw],
[107, "breath_sensitivity", tuya.valueConverter.raw],
[109, "move_maximum_range", tuya.valueConverter.raw],
[110, "move_minimum_range", tuya.valueConverter.raw],
[111, "breath_maximum_range", tuya.valueConverter.raw],
[112, "breath_minimum_range", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_ppuj1vem"]),
model: "ZPIR-10",
vendor: "Tuya",
description: "Treatlife human presence sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [e.occupancy(), e.battery(), e.illuminance()],
meta: {
tuyaDatapoints: [
[1, "occupancy", tuya.valueConverter.trueFalse0],
[4, "battery", tuya.valueConverter.raw],
[101, "illuminance", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_ha0vwoew"]),
model: "TS0601_thermostat_thermosphere",
vendor: "Tuya",
description: "ThermoSphere thermostat",
extend: [tuyaBase({ dp: true, timeStart: "1970" })],
exposes: [
e
.climate()
.withSystemMode(["off", "auto"], ea.STATE_SET, "Whether the thermostat is turned on or off")
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE),
e
.enum("sensor_mode", ea.STATE_SET, ["room_temperature", "floor_temperature", "room_with_floor_limit"])
.withDescription("What type of sensor are you using to measure the temperature of the floor?"),
e
.binary("adaptive_start", ea.STATE_SET, "ON", "OFF")
.withDescription("Preheat the room to the desired temperature before the scheduled start time."),
e.max_temperature_limit().withDescription("Maximum temperature (default: 35 ºC)").withValueMin(5).withValueMax(35).withValueStep(0.5),
e
.min_temperature_limit()
.withDescription("Minimum temperature limit for frost protection. Turns the thermostat on regardless of setpoint if the temperature drops below this.")
.withValueMin(1)
.withValueMax(5),
e
.enum("boost", ea.STATE_SET, ["ON", "OFF"])
.withDescription("Override the schedule and boost at the current temperature until turned off"),
e
.numeric("display_brightness", ea.STATE_SET)
.withDescription("Brightness of the display when in use")
.withValueMin(0)
.withValueMax(100)
.withValueStep(1),
e
.numeric("holiday_start_stop", ea.STATE_SET)
.withDescription("Set the number of days of holiday, this will start immediately.")
.withValueMax(99)
.withValueMin(0),
e.holiday_temperature().withValueMin(5).withValueMax(35),
e.binary("frost_protection", ea.STATE_SET, "ON", "OFF").withDescription("Turning on will keep heating at the minimum temperature limit"),
e
.numeric("switch_delay", ea.STATE_SET)
.withDescription("How long to wait between making a change and it taking effect")
.withValueMin(10)
.withValueMax(90)
.withValueStep(10)
.withUnit("s"),
e
.numeric("power_rating", ea.STATE_SET)
.withDescription("How much power is the underfloor heating rated to. Entering a value will allow the Thermostat to record a value of power usage that can be checked under settings on the physical Thermostat")
.withUnit("W")
.withValueMin(0)
.withValueMax(4500)
.withValueStep(100),
e
.binary("open_window_active", ea.STATE_SET, "ON", "OFF")
.withDescription("When active the heating will cut off if an Open Window is detected"),
e
.numeric("open_window_sensing_time", ea.STATE_SET)
.withDescription("The duration that the drop in temperature needs to occur over")
.withUnit("minutes")
.withValueMin(1)
.withValueMax(30)
.withValueStep(1),
e
.numeric("open_window_drop_limit", ea.STATE_SET)
.withDescription("The drop in ambient room temperature that will trigger an open window warning")
.withUnit("C")
.withValueMin(2)
.withValueMax(4)
.withValueStep(1),
e
.numeric("open_window_off_time", ea.STATE_SET)
.withDescription("The length of time the drop in temperature must be consistent for to turn the heating off")
.withUnit("minutes")
.withValueMin(10)
.withValueMax(60)
.withValueStep(5),
],
meta: {
tuyaDatapoints: [
[1, "system_mode", tuya.valueConverterBasic.lookup({ off: false, auto: true })],
[2, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[
4,
"boost",
tuya.valueConverterBasic.lookup({
OFF: tuya.enum(1),
ON: tuya.enum(2),
}),
],
[18, "open_window_active", tuya.valueConverterBasic.lookup({ OFF: false, ON: true })],
[40, "open_window_sensing_time", tuya.valueConverterBasic.divideBy(1)],
[45, "open_window_drop_limit", tuya.valueConverter.divideBy10],
[47, "open_window_off_time", tuya.valueConverterBasic.divideBy(1)],
[37, "adaptive_start", tuya.valueConverterBasic.lookup({ OFF: false, ON: true })],
[38, "local_temperature", tuya.valueConverter.divideBy10],
[39, "max_temperature_limit", tuya.valueConverter.divideBy10],
[41, "holiday_start_stop", tuya.valueConverterBasic.divideBy(1)], //divideBy1 required to force the format. Raw does not work
[42, "holiday_temperature", tuya.valueConverter.divideBy10],
[
43,
"sensor_mode",
tuya.valueConverterBasic.lookup({
room_temperature: tuya.enum(0),
floor_temperature: tuya.enum(1),
room_with_floor_limit: tuya.enum(2),
}),
],
//[48, 'temp_tolerance', tuya.valueConverter.raw],
[50, "power_rating", tuya.valueConverterBasic.divideBy(1)],
[52, "frost_protection", tuya.valueConverterBasic.lookup({ OFF: false, ON: true })],
[53, "min_temperature_limit", tuya.valueConverter.divideBy10],
[54, "switch_delay", tuya.valueConverterBasic.divideBy(1)],
[55, "display_brightness", tuya.valueConverterBasic.divideBy(1)], //divideBy1 required to force the format. Raw does not work
//[16, 'schedule', tuya.valueConverter.Raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_madl8ejv"]),
model: "SAS936RHB-7-Z03",
vendor: "Saswell",
description: "Wireless temperature sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e
.climate()
.withLocalTemperature(ea.STATE)
.withSetpoint("current_heating_setpoint", 5, 30, 0.5, ea.STATE_SET)
.withSystemMode(["off", "heat"], ea.STATE_SET),
e.binary("heating_demand", ea.STATE, "ON", "OFF").withDescription("Valve demand (0=off, 1=heating)"),
e
.binary("temporary_leaving", ea.STATE_SET, "ON", "OFF")
.withDescription("Temporary leaving mode (checkmark on display, disables heating)"),
e.child_lock(),
],
meta: {
tuyaDatapoints: [
[102, "local_temperature", tuya.valueConverter.divideBy10],
[103, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[
101,
"system_mode",
{
to: async (v, meta) => {
await tuya.sendDataPointBool(meta.device.endpoints[0], 101, v === "heat", "dataRequest", 1);
},
from: (v) => (v === true ? "heat" : "off"),
},
],
[
3,
"heating_demand",
{
from: (v) => {
const value = Array.isArray(v) ? v[v.length - 1] : v;
return value === 1 ? "ON" : "OFF";
},
to: (v) => (v === "ON" ? 1 : 0),
},
],
[106, "temporary_leaving", tuya.valueConverter.onOff],
[40, "child_lock", tuya.valueConverter.lockUnlock],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_pbo8cj0z", "_TZE200_eo6xhfbo"]),
model: "TS0601_GTZ10",
vendor: "Tuya",
description: "Thermostat radiator valve",
extend: [tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true, timeStart: "1970" })],
ota: true,
exposes: [
e.battery(),
e.child_lock(),
e.window_detection(),
e.window_open(),
e.open_window_temperature().withValueMin(5).withValueMax(30),
e.max_temperature().withValueMin(15).withValueMax(45),
e.min_temperature().withValueMin(5).withValueMax(15),
e
.climate()
.withPreset(["manual", "auto", "holiday", "comfort", "eco", "off"])
.withSetpoint("current_heating_setpoint", 5, 40, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withLocalTemperatureCalibration(-9, 9, 0.5, ea.STATE_SET)
.withSystemMode(["off", "heat", "auto"], ea.STATE_SET, "Only for Homeassistant")
.withRunningState(["idle", "heat"], ea.STATE),
...tuya.exposes.scheduleAllDays(ea.STATE_SET, "HH:MM/C HH:MM/C HH:MM/C HH:MM/C"),
tuya.exposes
.errorStatus()
.withDescription("Error status. Possible codes: E1 - builtin sensor error, E2 - external sensor error, E3 - valve not installed, E4 - motor error."),
e
.binary("frost_protection", ea.STATE_SET, "ON", "OFF")
.withDescription("When the room temperature is lower than 5 °C, the valve opens; when the temperature rises to 8 °C, the valve closes."),
e.binary("boost_heating", ea.STATE_SET, "ON", "OFF").withDescription("Boost Heating: the device will enter the boost heating mode."),
e
.numeric("boost_timeset_countdown", ea.STATE_SET)
.withUnit("m")
.withDescription("Setting " +
"minimum 0 - maximum 90 minutes boost time. The boost function is activated. The remaining " +
"time for the function will be counted down in minutes ( 90 to 0 ).")
.withValueMin(0)
.withValueMax(90),
e.binary("switch_type", ea.STATE_SET, "ON", "OFF").withDescription("Enables/disables valve switch"),
e
.numeric("position", ea.STATE_SET)
.withUnit("%")
.withValueMin(0)
.withValueMax(100)
.withDescription("Position. To set the value you need to turn on valve switch"),
e.enum("screen_orientation", ea.STATE_SET, ["up", "right", "down", "left"]).withDescription("Screen orientation"),
],
meta: {
tuyaDatapoints: [
[
2,
null,
tuya.valueConverter.thermostatSystemModeAndPresetMap({
fromMap: {
0: {
deviceMode: "manual",
systemMode: "heat",
preset: "manual",
},
1: { deviceMode: "auto", systemMode: "auto", preset: "auto" },
2: {
deviceMode: "holiday",
systemMode: "heat",
preset: "holiday",
},
3: {
deviceMode: "comfort",
systemMode: "heat",
preset: "comfort",
},
4: { deviceMode: "eco", systemMode: "heat", preset: "eco" },
5: { deviceMode: "off", systemMode: "off", preset: "off" },
},
}),
],
[
2,
"preset",
tuya.valueConverter.thermostatSystemModeAndPresetMap({
toMap: {
manual: new tuya.Enum(0),
auto: new tuya.Enum(1),
holiday: new tuya.Enum(2),
comfort: new tuya.Enum(3),
eco: new tuya.Enum(4),
off: new tuya.Enum(5),
},
}),
],
[
2,
"system_mode",
tuya.valueConverter.thermostatSystemModeAndPresetMap({
toMap: {
heat: new tuya.Enum(0),
auto: new tuya.Enum(1),
off: new tuya.Enum(5),
},
}),
],
[4, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[5, "local_temperature", tuya.valueConverter.divideBy10],
[6, "battery", tuya.valueConverter.raw],
[7, "child_lock", tuya.valueConverter.lockUnlock],
[9, "max_temperature", tuya.valueConverter.divideBy10],
[10, "min_temperature", tuya.valueConverter.divideBy10],
[14, "window_detection", tuya.valueConverter.onOff],
[15, "window_open", tuya.valueConverter.onOff],
[16, "open_window_temperature", tuya.valueConverter.divideBy10],
[28, "schedule_monday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(1)],
[29, "schedule_tuesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(2)],
[30, "schedule_wednesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(3)],
[31, "schedule_thursday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(4)],
[32, "schedule_friday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(5)],
[33, "schedule_saturday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(6)],
[34, "schedule_sunday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(7)],
[35, "error_status", tuya.valueConverter.raw],
[36, "frost_protection", tuya.valueConverter.onOff],
[37, "boost_heating", tuya.valueConverter.onOff],
[38, "boost_timeset_countdown", tuya.valueConverter.raw],
[47, "local_temperature_calibration", tuya.valueConverter.divideBy10],
[
49, //valve_state
"running_state",
tuya.valueConverterBasic.lookup({
idle: tuya.enum(0),
heat: tuya.enum(1),
closing: tuya.enum(2),
opening: tuya.enum(3),
}),
],
[113, "switch_type", tuya.valueConverter.onOff], // valve_switch
[115, "position", tuya.valueConverter.raw], // valve_opening
[
119,
"screen_orientation",
tuya.valueConverterBasic.lookup({
up: tuya.enum(0),
down: tuya.enum(1),
left: tuya.enum(2),
right: tuya.enum(3),
}),
],
// Exposed but not used DPs
[1, "preheat", tuya.valueConverter.raw],
[3, "working_status", tuya.valueConverter.raw],
[8, "temperature_scale", tuya.valueConverter.raw],
[11, "max_temperature_f", tuya.valueConverter.divideBy10],
[12, "min_temperature_f", tuya.valueConverter.divideBy10],
[17, "open_window_time", tuya.valueConverter.raw],
[18, "backlight", tuya.valueConverter.raw],
[39, "switch_scale", tuya.valueConverter.raw],
[40, "local_temperature_f", tuya.valueConverter.divideBy10],
[41, "current_heating_setpoint_f", tuya.valueConverter.divideBy10],
[46, "open_window_temperature_f", tuya.valueConverter.divideBy10],
[48, "valve_testing", tuya.valueConverter.raw],
[112, "local_temperature_calibration_f", tuya.valueConverter.divideBy10],
[114, "valve_control_type", tuya.valueConverterBasic.lookup({ PID: 0, ONOFF: 1 })],
[116, "frost_temperature", tuya.valueConverter.raw],
[117, "frost_temperature_f", tuya.valueConverter.raw],
[118, "auto_time", tuya.valueConverter.raw],
[120, "multi_control_link", tuya.valueConverter.raw],
[121, "holiday_time", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_kyyu8rbj", "_TZE284_mxujdmxo"]),
model: "ME201WZ",
vendor: "Tuya",
description: "Water level sensor",
whiteLabel: [tuya.whitelabel("Tuya", "ME202WZ", "Water level sensor", ["_TZE284_mxujdmxo"])],
extend: [
m.forcePowerSource({ powerSource: "Mains (single phase)" }),
tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true, queryOnConfigure: true }),
],
version: "0.0.1",
exposes: (device, options) => [
te.liquidState(),
te.liquidLevelPercent(),
te.liquidDepth(),
te.powerSupplyVoltage(),
te.liquidMaximalPercent(),
te.liquidMinimalPercent(),
...(!device || ["_TZE284_kyyu8rbj"].includes(device.manufacturerName)
? [te.liquidDepthMax(), te.liquidInstallationHeight()]
: [te.liquidDepthMax().withValueMax(10)]),
te.relaySwitch(),
te.pumpMode(),
te.autoPumpControl(),
te.version(),
],
meta: {
tuyaDatapoints: [
[1, "liquid_state", tvc.level],
[2, "liquid_depth", tvc.divideBy100],
[5, "voltage", tvc.divideBy10], // power_level
[7, "max_set", tvc.raw],
[8, "min_set", tvc.raw],
[19, "installation_height", tvc.divideBy100],
[21, "liquid_depth_max", tvc.divideBy100],
[22, "liquid_level_percent", tvc.raw],
[24, "relay_switch", tvc.onOff],
[101, "pump_mode", tvc.pumpMode],
[102, "pump_control", tvc.pumpControl], // auto_pump_control
[103, "version", tuya.valueConverter.raw],
],
},
},
{
fingerprint: [{ modelID: "TS0601", manufacturerName: "_TZE200_lvkk0hdg" }],
model: "TLC2206",
vendor: "Tuya",
description: "Water level sensor",
extend: [
m.forcePowerSource({ powerSource: "Mains (single phase)" }),
tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true, queryOnConfigure: true }),
],
version: "0.0.1",
exposes: [
te.liquidState(),
te.liquidLevelPercent(),
te.liquidDepth(),
te.liquidMinimalPercent(),
te.liquidMaximalPercent(),
te.liquidDepthMax().withValueMax(2).withValueStep(0.001),
te.liquidInstallationHeight().withValueMax(3).withValueStep(0.001),
],
meta: {
tuyaDatapoints: [
[1, "liquid_state", tvc.level],
[2, "liquid_depth", tvc.divideBy100],
[7, "max_set", tvc.raw],
[8, "min_set", tvc.raw],
[19, "installation_height", tvc.divideBy1000],
[21, "liquid_depth_max", tvc.divideBy1000],
[22, "liquid_level_percent", tvc.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_7yyuo8sr"]),
model: "872WZ",
vendor: "Tuya",
description: "Water level sensor",
extend: [
m.forcePowerSource({ powerSource: "Mains (single phase)" }),
tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true, queryOnConfigure: true }),
],
version: "0.0.1",
exposes: [
te.liquidState(),
te.liquidLevelPercent(),
te.liquidDepth(),
te.liquidMaximalPercent(),
te.liquidMinimalPercent(),
te.liquidDepthMax().withValueMin(0),
te.liquidInstallationHeight().withValueMin(0),
te.silentMode(),
te.alarmDuration(),
// te.autoPumpControl() ?
// te.hydrate ?
// te.water drainage ?
],
meta: {
tuyaDatapoints: [
[1, "liquid_state", tvc.level],
[2, "liquid_depth", tvc.divideBy100],
[7, "max_set", tvc.raw],
[8, "min_set", tvc.raw],
[9, "silent_mode", tvc.onOff],
[19, "installation_height", tvc.divideBy100],
[21, "liquid_depth_max", tvc.divideBy100],
[22, "liquid_level_percent", tvc.raw],
[27, "alarm_time", tvc.raw],
[101, "pump_control", tvc.pumpControl],
[102, "hydrate", tvc.raw],
[103, "water_drainage", tvc.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_q12rv9gj"]),
model: "HHST001",
vendor: "HeatHUB",
description: "Fan coil thermostat",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.binary("state", ea.STATE_SET, "ON", "OFF").withDescription("Turn the thermostat ON/OFF"),
e
.climate()
.withLocalTemperature(ea.STATE)
.withSystemMode(["cool", "heat", "fan_only"], ea.STATE_SET)
.withSetpoint("current_heating_setpoint", 5, 45, 0.5, ea.STATE_SET)
.withFanMode(["auto", "high", "medium", "low", "off"], ea.STATE_SET)
.withLocalTemperatureCalibration(-9, 9, 0.1, ea.STATE_SET),
e
.numeric("deadzone_temperature", ea.STATE_SET)
.withValueMax(5)
.withValueMin(1)
.withValueStep(1)
.withPreset("default", 1, "Default value")
.withDescription("The difference between the local temperature that triggers heating and the set temperature"),
e.min_temperature().withValueMin(5).withValueMax(15),
e.max_temperature().withValueMin(35).withValueMax(45),
e.child_lock(),
e.humidity(),
e.binary("manual_mode", ea.STATE_SET, "Auto", "Manual").withDescription("Manual = Manual or Schedule = Auto"),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[
2,
"system_mode",
tuya.valueConverterBasic.lookup({
cool: tuya.enum(0),
heat: tuya.enum(1),
fan_only: tuya.enum(2),
}),
],
[16, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[19, "max_temperature", tuya.valueConverter.divideBy10],
[18, "max_temperature_f", tuya.valueConverter.raw],
[20, "min_temperature_f", tuya.valueConverter.raw],
[21, "local_temperature_f", tuya.valueConverter.raw],
[24, "local_temperature", tuya.valueConverter.divideBy10],
[26, "min_temperature", tuya.valueConverter.divideBy10],
[102, "local_temperature_calibration", tuya.valueConverter.divideBy10],
[103, "local_temperature_calibration_f", tuya.valueConverter.raw],
[104, "deadzone_temperature", tuya.valueConverter.raw],
[105, "deadzone_temperature_f", tuya.valueConverter.raw],
[
28,
"fan_mode",
tuya.valueConverterBasic.lookup({
auto: tuya.enum(0),
high: tuya.enum(1),
medium: tuya.enum(2),
low: tuya.enum(3),
off: tuya.enum(4),
}),
],
[40, "child_lock", tuya.valueConverter.lockUnlock],
[44, "current_heating_setpoint_f", tuya.valueConverter.raw],
[46, "temperature_scale", tuya.valueConverter.raw],
[
101,
"manual_mode",
tuya.valueConverterBasic.lookup({
Auto: tuya.enum(0),
Manual: tuya.enum(1),
Tempoary: tuya.enum(2),
}),
],
[113, "humidity", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_8zizsafo", "_TZE284_iilebqoo"]),
model: "GX03",
vendor: "GIEX",
description: "GIEX 2-zone watering timer",
extend: [tuya.modernExtend.tuyaBase({ dp: true, queryOnConfigure: true })],
exposes: [
exposes.binary("valve_1", ea.STATE_SET, "ON", "OFF").withDescription("State of the valve 1"),
e.enum("state_1", ea.STATE, ["Manual", "Auto", "Closed"]).withDescription("State of the valve 1"),
e
.numeric("timer_1", ea.SET)
.withValueMin(1)
.withValueMax(1440)
.withValueStep(1)
.withUnit("min")
.withDescription("Timer for the valve 1 operation"),
e.numeric("countdown_1", ea.STATE).withUnit("min").withCategory("diagnostic").withDescription("Time remaining for the open valve 1"),
e.numeric("last_duration_1", ea.STATE).withUnit("s").withCategory("diagnostic").withDescription("Last open duration for the valve 1"),
e.binary("valve_2", ea.STATE_SET, "ON", "OFF").withDescription("State of the valve 2"),
e.enum("state_2", ea.STATE, ["Manual", "Auto", "Closed"]).withDescription("State of the valve 2"),
e
.numeric("timer_2", ea.SET)
.withValueMin(1)
.withValueMax(1440)
.withValueStep(1)
.withUnit("min")
.withDescription("Timer for the valve 2 operation"),
e.numeric("countdown_2", ea.STATE).withUnit("min").withCategory("diagnostic").withDescription("Time remaining for the open valve 2"),
e.numeric("last_duration_2", ea.STATE).withUnit("s").withCategory("diagnostic").withDescription("Last open duration for the valve 2"),
e.battery(),
],
meta: {
tuyaDatapoints: [
[1, "valve_1", tuya.valueConverter.onOff],
[104, "state_1", tuya.valueConverter.GX03ValveState(1)],
[13, "countdown_1", tuya.valueConverter.countdown],
[13, "timer_1", tuya.valueConverter.raw],
[25, "last_duration_1", tuya.valueConverter.raw],
[2, "valve_2", tuya.valueConverter.onOff],
[105, "state_2", tuya.valueConverter.GX03ValveState(2)],
[14, "countdown_2", tuya.valueConverter.countdown],
[14, "timer_2", tuya.valueConverter.raw],
[26, "last_duration_2", tuya.valueConverter.raw],
[59, "battery", tuya.valueConverter.raw],
],
},
whiteLabel: [tuya.whitelabel("Nova Digital", "ZVL-DUAL", "Water Valve with 2 zones", ["_TZE284_iilebqoo"])],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_eaulras5"]),
model: "PJ3201A",
vendor: "Dongguan Pinjia Technology Co.,LTD.",
description: "Human Presence Sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence().withDescription("Indicates whether the device detected presence. Will be true also when movement (occupancy) is detected."),
e
.occupancy()
.withDescription("Indicates whether the device detected movement. Will be true when movement. Can remain true even if the target left the detection range. In this case presence will be reset to false reliably."),
e.numeric("closest_target_distance", ea.STATE).withDescription("the target distance away from the sensor").withUnit("m"),
e
.numeric("movement_timeout", ea.STATE_SET)
.withValueMin(0)
.withValueMax(43200)
.withValueStep(1)
.withDescription("Timeout until the movement (occupancy) is reset when no further movement is detected. (Occupancy -> None)")
.withUnit("s"),
e
.numeric("idle_timeout", ea.STATE_SET)
.withValueMin(0)
.withValueMax(43200)
.withValueStep(1)
.withDescription("Timeout until the presence is reset when no further presence is detected (Presence -> None)")
.withUnit("s"),
e.illuminance(),
e
.numeric("far_movement_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(1)
.withDescription("the moving detecting sensitivity 1 meter away"),
e
.numeric("near_movement_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(1)
.withDescription("the moving detecting sensitivity within 1 meter"),
e
.numeric("near_presence_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(1)
.withDescription("the presence detecting sensitivity within 1 meter"),
e
.numeric("far_presence_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(1)
.withDescription("the presence detecting sensitivity 1 meter away"),
e
.numeric("closest_detection_distance", ea.STATE_SET)
.withValueMin(0.1)
.withValueMax(7)
.withValueStep(0.1)
.withDescription("The closest distance that can be detected")
.withUnit("m"),
e
.numeric("largest_movement_detection_distance", ea.STATE_SET)
.withValueMin(0.1)
.withValueMax(7)
.withValueStep(0.1)
.withDescription("The farthest distance that can be detected (moving)")
.withUnit("m"),
e
.numeric("largest_presence_detection_distance", ea.STATE_SET)
.withValueMin(0.1)
.withValueMax(7)
.withValueStep(0.1)
.withDescription("The farthest distance that can be detected (present)")
.withUnit("m"),
e.binary("restore_factory", ea.STATE_SET, "ON", "OFF").withDescription("restore_factory"),
e.binary("led_indicator", ea.STATE_SET, "ON", "OFF").withDescription("turn on or off the led "),
],
meta: {
tuyaDatapoints: [
[104, "presence", tuya.valueConverter.trueFalse1],
[112, "occupancy", tuya.valueConverter.trueFalseInvert],
[9, "closest_target_distance", tuya.valueConverter.divideBy100],
[101, "movement_timeout", tuya.valueConverter.raw],
[102, "idle_timeout", tuya.valueConverter.raw],
[103, "illuminance", tuya.valueConverter.divideBy10],
[105, "far_movement_sensitivity", tuya.valueConverter.raw],
[110, "near_movement_sensitivity", tuya.valueConverter.raw],
[109, "near_presence_sensitivity", tuya.valueConverter.raw],
[111, "far_presence_sensitivity", tuya.valueConverter.raw],
[3, "closest_detection_distance", tuya.valueConverter.divideBy100],
[4, "largest_movement_detection_distance", tuya.valueConverter.divideBy100],
[108, "largest_presence_detection_distance", tuya.valueConverter.divideBy100],
[106, "restore_factory", tuya.valueConverterBasic.lookup({ ON: false, OFF: true })],
[107, "led_indicator", tuya.valueConverterBasic.lookup({ ON: false, OFF: true })],
],
},
},
{
fingerprint: tuya.fingerprint("TS0225", ["_TZ321C_fkzihax8", "_TZ321C_4slreunp"]),
model: "MTD085-ZB",
vendor: "LeapMMW",
description: "Human presence sensor",
extend: [tuyaBase({ dp: true })],
fromZigbee: [fz.ias_occupancy_alarm_1, fz.ias_occupancy_alarm_1_report],
exposes: [
e.occupancy(),
e.illuminance(),
e.numeric("target_distance", ea.STATE).withDescription("Distance to target").withUnit("m"),
e
.numeric("radar_sensitivity", ea.STATE_SET)
.withValueMin(10)
.withValueMax(100)
.withValueStep(10)
.withUnit("%")
.withDescription("Detection threshold for the strength of object energy"),
e
.numeric("detection_range", ea.STATE_SET)
.withValueMin(0)
.withValueMax(8)
.withValueStep(0.1)
.withUnit("m")
.withDescription("Maximum distance detected by the sensor"),
e
.numeric("shield_range", ea.STATE_SET)
.withValueMin(0)
.withValueMax(8)
.withValueStep(0.1)
.withUnit("m")
.withDescription("Nearest distance detected by the sensor"),
e
.numeric("entry_sensitivity", ea.STATE_SET)
.withValueMin(10)
.withValueMax(100)
.withValueStep(10)
.withUnit("%")
.withDescription("Sensitivity threshold triggered for the first time when the target enters the detection range"),
e
.numeric("entry_distance", ea.STATE_SET)
.withValueMin(0)
.withValueMax(8)
.withValueStep(0.1)
.withUnit("m")
.withDescription("Detection distance when unoccupied"),
e
.numeric("entry_filter_time", ea.STATE_SET)
.withValueMin(0)
.withValueMax(0.5)
.withValueStep(0.05)
.withUnit("s")
.withDescription("Sensitivity threshold triggered for the first time when the target enters the detection range "),
e
.numeric("departure_delay", ea.STATE_SET)
.withValueMin(5)
.withValueMax(7200)
.withValueStep(1)
.withUnit("s")
.withDescription("Confirmation time after the target disappears"),
e
.numeric("block_time", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(0.1)
.withUnit("s")
.withDescription("Time for the target to be detected again after switching from manned(occupy) to unmanned(unoccupy) mode"),
e.enum("status_indication", ea.STATE_SET, ["OFF", "ON"]).withDescription("Indicator light will turn on when human presence is detected"),
e
.enum("sensor", ea.STATE_SET, ["on", "occupied", "unoccupied"])
.withDescription("The radar sensor can be set in three states: on, occupied and unoccupied. For example, if set to occupied, " +
"it will continue to maintain presence regardless of whether someone is present or not. If set to unoccupied, the unoccupied " +
"state will be maintained permanently."),
e
.enum("scene_preset", ea.STATE_SET, [
"Custom",
"Toilet",
"Kitchen",
"Hallway",
"Bedroom",
"Livingroom",
"Meetingroom",
"Factory default",
])
.withDescription("Presets"),
e
.enum("distance_report_mode", ea.STATE_SET, ["Normal", "Occupancy detection"])
.withDescription("Indicator light will turn on when human presence is detected"),
e
.enum("debug_mode", ea.STATE_SET, ["OFF", "ON"])
.withDescription("In debug mode, radar will report more information, can be used to identify interference"),
e.numeric("debug_distance", ea.STATE).withDescription("Real time distance to target").withUnit("m"),
e.numeric("debug_countdown", ea.STATE).withDescription("Time before the target disappears").withUnit("s"),
],
meta: {
tuyaSendCommand: "sendData",
tuyaDatapoints: [
// [1, 'presence', tuya.valueConverter.trueFalse1],
[101, "entry_sensitivity", tuya.valueConverter.raw],
[102, "entry_distance", tuya.valueConverter.divideBy100],
[103, "departure_delay", tuya.valueConverter.raw],
[104, "entry_filter_time", tuya.valueConverter.divideBy100],
[105, "block_time", tuya.valueConverter.divideBy10],
[107, "illuminance", tuya.valueConverter.divideBy10],
[
108,
"debug_mode",
tuya.valueConverterBasic.lookup({
OFF: tuya.enum(0),
ON: tuya.enum(1),
}),
],
[109, "debug_distance", tuya.valueConverter.divideBy100],
[110, "debug_countdown", tuya.valueConverter.raw],
[
111,
"scene_preset",
tuya.valueConverterBasic.lookup({
Custom: tuya.enum(0),
Toilet: tuya.enum(1),
Kitchen: tuya.enum(2),
Hallway: tuya.enum(3),
Bedroom: tuya.enum(4),
Livingroom: tuya.enum(5),
Meetingroom: tuya.enum(6),
Default: tuya.enum(7),
}),
],
[
112,
"sensor",
tuya.valueConverterBasic.lookup({
on: tuya.enum(0),
occupied: tuya.enum(1),
unoccupied: tuya.enum(2),
}),
],
[113, "cline", tuya.valueConverter.raw],
[
114,
"status_indication",
tuya.valueConverterBasic.lookup({
OFF: tuya.enum(0),
ON: tuya.enum(1),
}),
],
[115, "radar_sensitivity", tuya.valueConverter.raw],
[116, "shield_range", tuya.valueConverter.divideBy100],
[117, "detection_range", tuya.valueConverter.divideBy100],
[118, "equipment_status", tuya.valueConverter.raw],
[119, "target_distance", tuya.valueConverter.divideBy100],
[
120,
"distance_report_mode",
tuya.valueConverterBasic.lookup({
Normal: tuya.enum(0),
"Occupancy detection": tuya.enum(1),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_d6i25bwg", "_TZE204_3q3maeoo"]),
model: "PO-BOCO-ELEC",
vendor: "Powernity / Xanlite",
description: "Pilot wire heating module",
extend: [tuyaBase({ dp: true, timeStart: "1970" })],
exposes: [
e
.climate()
.withPreset(["manual", "auto", "holiday"])
.withLocalTemperature(ea.STATE)
.withSetpoint("occupied_heating_setpoint", 0, 20, 1, ea.STATE_SET)
.withLocalTemperatureCalibration(-5.5, 5.5, 0.5, ea.STATE_SET)
.withSystemMode(["off", "heat", "auto"], ea.STATE_SET, "Only for Homeassistant"),
e.enum("auto_mode", ea.STATE_SET, ["off", "antifrost", "eco", "comfort_-2", "comfort_-1", "comfort"]).withDescription("Auto Mode"),
e.enum("manual_mode", ea.STATE_SET, ["off", "antifrost", "eco", "comfort_-2", "comfort_-1", "comfort"]).withDescription("Manual Mode"),
tuya.exposes.frostProtection(),
// e.text("antifrost_mode", ea.STATE),
e.humidity(),
e.child_lock(),
e.window_detection(),
e
.enum("window_opening_mode", ea.STATE_SET, ["off", "antifrost", "eco", "comfort_-2", "comfort_-1", "comfort"])
.withDescription("Window Opening Mode"),
e
.numeric("window_opening_mode_duration", ea.STATE_SET)
.withUnit("min")
.withDescription("Duration of the window opening mode")
.withValueMin(1)
.withValueMax(90),
tuya.exposes.temperatureUnit(),
e
.binary("boost_heating", ea.STATE_SET, "ON", "OFF")
.withDescription("Boost Heating: the device will enter the boost heating mode.")
.withCategory("config"),
e
.numeric("boost_timeset_countdown", ea.STATE)
.withUnit("s")
.withDescription("The remaining " + "time for the function will be counted down in seconds ( 900 to 0 ) when the boost heating is activated.")
.withValueMin(0)
.withValueMax(900),
e
.numeric("power_rating", ea.STATE_SET)
.withDescription("How much power is the heater rated to. Entering a value will allow the Thermostat to record a value of power usage that can be checked under settings on the physical Thermostat")
.withUnit("kWh")
.withValueMin(10)
.withValueMax(10000)
.withValueStep(10)
.withCategory("config"),
e.numeric("energy_consumed", ea.STATE).withUnit("kWh").withDescription("Consumed energy"),
e
.enum("mode_switching", ea.STATE_SET, ["four_modes", "six_modes"])
.withDescription("Choose the number of mode your heater supports.")
.withCategory("config"),
e
.text("schedule_monday", ea.STATE_SET)
.withDescription("Schedule for the auto mode. Can be defined in the following format: " +
"`mode / hours:minutes / mode`. " +
"For example: `comfort / 06:00 / eco / 12:00 / off`. " +
"Note: Minutes can only be set by multiple of 5. Your limited to 9 modes max."),
e
.text("schedule_tuesday", ea.STATE_SET)
.withDescription("Schedule for the auto mode. Can be defined in the following format: " +
"`mode / hours:minutes / mode`. " +
"For example: `comfort / 06:00 / eco / 12:00 / off`. " +
"Note: Minutes can only be set by multiple of 5. Your limited to 9 modes max."),
e
.text("schedule_wednesday", ea.STATE_SET)
.withDescription("Schedule for the auto mode. Can be defined in the following format: " +
"`mode / hours:minutes / mode`. " +
"For example: `comfort / 06:00 / eco / 12:00 / off`. " +
"Note: Minutes can only be set by multiple of 5. Your limited to 9 modes max."),
e
.text("schedule_thursday", ea.STATE_SET)
.withDescription("Schedule for the auto mode. Can be defined in the following format: " +
"`mode / hours:minutes / mode`. " +
"For example: `comfort / 06:00 / eco / 12:00 / off`. " +
"Note: Minutes can only be set by multiple of 5. Your limited to 9 modes max."),
e
.text("schedule_friday", ea.STATE_SET)
.withDescription("Schedule for the auto mode. Can be defined in the following format: " +
"`mode / hours:minutes / mode`. " +
"For example: `comfort / 06:00 / eco / 12:00 / off`. " +
"Note: Minutes can only be set by multiple of 5. Your limited to 9 modes max."),
e
.text("schedule_saturday", ea.STATE_SET)
.withDescription("Schedule for the auto mode. Can be defined in the following format: " +
"`mode / hours:minutes / mode`. " +
"For example: `comfort / 06:00 / eco / 12:00 / off`. " +
"Note: Minutes can only be set by multiple of 5. Your limited to 9 modes max."),
e
.text("schedule_sunday", ea.STATE_SET)
.withDescription("Schedule for the auto mode. Can be defined in the following format: " +
"`mode / hours:minutes / mode`. " +
"For example: `comfort / 06:00 / eco / 12:00 / off`. " +
"Note: Minutes can only be set by multiple of 5. Your limited to 9 modes max."),
e
.text("holiday_start_stop", ea.STATE_SET)
.withDescription("The holiday mode will automatically start " +
"at the set time starting point and run the holiday mode. Can be defined in the following format: " +
"`startYear/startMonth/startDay startHours:startMinutes | endYear/endMonth/endDay endHours:endMinutes | mode`. " +
"For example: `2024/12/12 09:00 | 2024/12/14 10:00 | comfort`" +
"Note: You cannot set an interval superior at 255 hours. The end date minutes will be ignore."),
e.binary("reset_consumption", ea.STATE_SET, "ON", "OFF").withDescription("Reset energy consumption to zero").withCategory("config"),
],
meta: {
tuyaDatapoints: [
[
null,
"occupied_heating_setpoint",
{
to: async (v, meta) => {
const entity = meta.device.endpoints[0];
const mode = meta.state.mode_switching;
let temp = v;
if (meta.state.temperature_unit === "fahrenheit")
temp = ((temp - 32) * 5) / 9;
await tuya.sendDataPointEnum(entity, 2, 1, "dataRequest", 1); // manual
if (temp === 0) {
await tuya.sendDataPointEnum(entity, 127, 5, "dataRequest", 3); //off
}
else if (temp < 16) {
await tuya.sendDataPointEnum(entity, 127, 4, "dataRequest", 3); //antifrost
}
else if (temp >= 19) {
await tuya.sendDataPointEnum(entity, 127, 0, "dataRequest", 1); //comfort
}
else if (mode === 1) {
if (temp === 17) {
await tuya.sendDataPointEnum(entity, 127, 2, "dataRequest", 1); // comfort-2
}
else if (temp === 18) {
await tuya.sendDataPointEnum(entity, 127, 1, "dataRequest", 1); // comfort-1
}
}
else {
await tuya.sendDataPointEnum(entity, 127, 3, "dataRequest", 1); // eco
}
return v;
},
},
],
[
2,
"preset",
tuya.valueConverterBasic.lookup({
auto: tuya.enum(0),
manual: tuya.enum(1),
holiday: tuya.enum(2),
}),
],
[
null,
"system_mode",
{
// Extend system_mode to support 'off' in addition to 'heat' and 'auto'
to: async (v, meta) => {
const entity = meta.device.endpoints[0];
let temp = meta.state.occupied_heating_setpoint;
if (meta.state.temperature_unit === "fahrenheit")
temp = ((temp - 32) * 5) / 9;
const mode = meta.state.mode_switching;
switch (v) {
case "off":
await tuya.sendDataPointEnum(entity, 2, 1, "dataRequest", 1); // manual
await tuya.sendDataPointEnum(entity, 127, 5, "dataRequest", 1); // off
break;
case "heat":
await tuya.sendDataPointEnum(entity, 2, 1, "dataRequest", 1); // manual
if (temp === 0) {
await tuya.sendDataPointEnum(entity, 127, 5, "dataRequest", 3); //off
}
else if (temp < 16) {
await tuya.sendDataPointEnum(entity, 127, 4, "dataRequest", 3); //antifrost
}
else if (temp >= 19) {
await tuya.sendDataPointEnum(entity, 127, 0, "dataRequest", 1); //comfort
}
else if (mode === 1) {
if (temp === 17) {
await tuya.sendDataPointEnum(entity, 127, 2, "dataRequest", 1); // comfort-2
}
else if (temp === 18) {
await tuya.sendDataPointEnum(entity, 127, 1, "dataRequest", 1); // comfort-1
}
}
else {
await tuya.sendDataPointEnum(entity, 127, 3, "dataRequest", 1); // eco
}
break;
case "auto":
await tuya.sendDataPointEnum(entity, 2, 0, "dataRequest", 1); // auto
break;
}
},
},
],
[8, "humidity", tuya.valueConverter.raw],
[11, "energy_consumed", tuya.valueConverter.raw],
[16, "local_temperature", tuya.valueConverter.divideBy10],
[39, "child_lock", tuya.valueConverter.lockUnlock],
[46, "temperature_unit", tuya.valueConverter.temperatureUnitEnum],
[101, "local_temperature_calibration", tuya.valueConverter.divideBy10],
[108, "boost_heating", tuya.valueConverter.onOff],
[114, "schedule_monday", tuya.valueConverter.PO_BOCO_ELEC_schedule(1)],
[115, "schedule_tuesday", tuya.valueConverter.PO_BOCO_ELEC_schedule(2)],
[116, "schedule_wednesday", tuya.valueConverter.PO_BOCO_ELEC_schedule(3)],
[117, "schedule_thursday", tuya.valueConverter.PO_BOCO_ELEC_schedule(4)],
[118, "schedule_friday", tuya.valueConverter.PO_BOCO_ELEC_schedule(5)],
[119, "schedule_saturday", tuya.valueConverter.PO_BOCO_ELEC_schedule(6)],
[120, "schedule_sunday", tuya.valueConverter.PO_BOCO_ELEC_schedule(7)],
[123, "boost_timeset_countdown", tuya.valueConverter.raw],
// [125, "temperature_antifreeze", tuya.valueConverterBasic.raw],
[
126,
"auto_mode",
tuya.valueConverterBasic.lookup({
off: tuya.enum(5),
antifrost: tuya.enum(4),
eco: tuya.enum(3),
"comfort_-2": tuya.enum(2),
"comfort_-1": tuya.enum(1),
comfort: tuya.enum(0),
}),
],
[
127,
"manual_mode",
tuya.valueConverterBasic.lookup({
off: tuya.enum(5),
antifrost: tuya.enum(4),
eco: tuya.enum(3),
"comfort_-2": tuya.enum(2),
"comfort_-1": tuya.enum(1),
comfort: tuya.enum(0),
}),
],
[136, "frost_protection", tuya.valueConverter.onOff],
// [137, "antifrost", tuya.valueConverterBasic.raw],
[138, "window_detection", tuya.valueConverter.onOff],
[
139,
"window_opening_mode",
tuya.valueConverterBasic.lookup({
off: tuya.enum(5),
antifrost: tuya.enum(4),
eco: tuya.enum(3),
"comfort_-2": tuya.enum(2),
"comfort_-1": tuya.enum(1),
comfort: tuya.enum(0),
}),
],
[140, "window_opening_mode_duration", tuya.valueConverter.raw],
[141, "reset_consumption", tuya.valueConverter.onOff],
[142, "power_rating", tuya.valueConverter.raw],
[143, "holiday_start_stop", tuya.valueConverter.PO_BOCO_ELEC_holiday],
[
144,
"mode_switching",
tuya.valueConverterBasic.lookup({
four_modes: tuya.enum(0),
six_modes: tuya.enum(1),
}),
],
// [145, "weekly temperature replication function", ],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_eekpf0ft", "_TZE284_eekpf0ft"]),
model: "TR-M3Z",
vendor: "Tuya",
description: "Thermostatic radiator valve actuator",
extend: [tuyaBase({ dp: true, timeStart: "1970" })],
exposes: [
e.child_lock(),
e.battery(),
e.battery_low(),
e
.climate()
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withPreset(["manual", "schedule", "eco", "comfort", "frost_protection", "holiday", "off"])
.withSystemMode(["off", "heat"], ea.STATE)
.withRunningState(["idle", "heat"], ea.STATE)
.withLocalTemperatureCalibration(-9.5, 9.5, 0.5, ea.STATE_SET),
...tuya.exposes.scheduleAllDays(ea.STATE_SET, "HH:MM/C HH:MM/C HH:MM/C HH:MM/C HH:MM/C HH:MM/C"),
e.eco_temperature().withValueMin(5).withValueMax(35).withValueStep(0.5),
e.comfort_temperature().withValueMin(5).withValueMax(35).withValueStep(0.5),
e.holiday_temperature().withValueMin(5).withValueMax(35).withValueStep(0.5),
e
.binary("window_detection", ea.STATE_SET, "ON", "OFF")
.withDescription("Startup: when room temperature decreases by 3°C within 5 minutes, stop heating. " +
"Close: when room temperature rises by 3 degrees / 48 minutes later / manual (three ways). " +
"After the window opening mode is turned on, one of these three conditions can trigger to exit the window opening mode."),
e.binary("window_open", ea.STATE, "OPEN", "CLOSE").withDescription("Window status CLOSE or OPEN "),
e
.binary("scale_protection", ea.STATE_SET, "ON", "OFF")
.withDescription("If the heat sink is not fully opened within " +
"two weeks or is not used for a long time, the valve will be blocked due to silting up and the heat sink will not be " +
"able to be used. To ensure normal use of the heat sink, the controller will automatically open the valve fully every " +
'two weeks. It will run for 30 seconds per time with the screen displaying "Ad", then return to its normal working state ' +
"again."),
e
.binary("frost_protection", ea.STATE_SET, "ON", "OFF")
.withDescription("When the room temperature is lower than 5 °C, the valve opens; when the temperature rises to 8 °C, the valve closes."),
e
.numeric("frost_protection_temperature", ea.STATE_SET)
.withUnit("°C")
.withValueMin(5)
.withValueMax(35)
.withValueStep(0.5)
.withDescription(""),
e
.numeric("temperature_accuracy", ea.STATE_SET)
.withUnit("°C")
.withValueMin(0.5)
.withValueMax(5)
.withValueStep(0.5)
.withDescription("The difference required between local temperature and set point to trigger the valve."),
e.numeric("error", ea.STATE).withDescription('If NTC is damaged, "Er" will be on the TRV display.'),
],
meta: {
tuyaDatapoints: [
[
2,
"preset",
tuya.valueConverterBasic.lookup({
manual: tuya.enum(0),
schedule: tuya.enum(1),
eco: tuya.enum(2),
comfort: tuya.enum(3),
frost_protection: tuya.enum(4),
holiday: tuya.enum(5),
off: tuya.enum(6),
}),
],
[
3,
"running_state",
tuya.valueConverterBasic.lookup({
idle: tuya.enum(0),
heat: tuya.enum(1),
}),
],
[4, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[5, "local_temperature", tuya.valueConverter.divideBy10],
[6, "battery", tuya.valueConverter.raw],
[7, "child_lock", tuya.valueConverter.lockUnlock],
[14, "window_detection", tuya.valueConverter.onOff],
[
15,
"window_open",
tuya.valueConverterBasic.lookup({
CLOSE: tuya.enum(0),
OPEN: tuya.enum(1),
}),
],
[21, "holiday_temperature", tuya.valueConverter.divideBy10],
[28, "schedule_monday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(1, 6)],
[29, "schedule_tuesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(2, 6)],
[30, "schedule_wednesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(3, 6)],
[31, "schedule_thursday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(4, 6)],
[32, "schedule_friday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(5, 6)],
[33, "schedule_saturday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(6, 6)],
[34, "schedule_sunday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(7, 6)],
[35, "fault_alarm", tuya.valueConverter.errorOrBatteryLow],
[36, "frost_protection", tuya.valueConverter.onOff],
[39, "scale_protection", tuya.valueConverter.onOff],
[47, "local_temperature_calibration", tuya.valueConverter.divideBy10],
[101, "system_mode", tuya.valueConverterBasic.lookup({ off: false, heat: true })],
[102, "temperature_accuracy", tuya.valueConverter.divideBy10],
[103, "eco_temperature", tuya.valueConverter.divideBy10],
[104, "comfort_temperature", tuya.valueConverter.divideBy10],
[105, "frost_protection_temperature", tuya.valueConverter.divideBy10],
],
},
},
{
// incomplete. see https://github.com/Koenkk/zigbee-herdsman-converters/pull/11271
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_cgr0rhza"]),
model: "TS0601_thermostat_6",
vendor: "Tuya",
description: "Thermostatic radiator valve",
extend: [tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true })],
exposes: [
e.child_lock(),
e.battery(),
e.open_window(),
e.window_detection(),
e.battery_low(),
e
.climate()
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withPreset(["manual", "schedule", "eco", "comfort", "antifrost", "off"])
.withSystemMode(["off", "heat"], ea.STATE)
.withLocalTemperatureCalibration(-12, 12, 1, ea.STATE_SET),
...tuya.exposes.scheduleAllDays(ea.STATE_SET, "HH:MM/C HH:MM/C HH:MM/C HH:MM/C HH:MM/C HH:MM/C"),
e.comfort_temperature().withValueMin(5).withValueMax(35),
e.eco_temperature().withValueMin(5).withValueMax(35),
e
.binary("scale_protection", ea.STATE_SET, "ON", "OFF")
.withDescription("If the heat sink is not fully opened within " +
"two weeks or is not used for a long time, the valve will be blocked due to silting up and the heat sink will not be " +
"able to be used. To ensure normal use of the heat sink, the controller will automatically open the valve fully every " +
'two weeks. It will run for 30 seconds per time with the screen displaying "Ad", then return to its normal working state ' +
"again."),
e
.binary("frost_protection", ea.STATE_SET, "ON", "OFF")
.withDescription("When the room temperature is lower than 5 °C, the valve opens; when the temperature rises to 8 °C, the valve closes."),
e.numeric("error", ea.STATE).withDescription('If NTC is damaged, "Er" will be on the TRV display.'),
],
meta: {
tuyaDatapoints: [
[
2,
"preset",
tuya.valueConverterBasic.lookup({
manual: tuya.enum(0),
schedule: tuya.enum(1),
eco: tuya.enum(2),
comfort: tuya.enum(3),
antifrost: tuya.enum(4),
off: tuya.enum(5),
}),
],
[4, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[5, "local_temperature", tuya.valueConverter.divideBy10],
[6, "battery", tuya.valueConverter.raw],
[7, "child_lock", tuya.valueConverter.lockUnlock],
[8, "window_detection", tuya.valueConverter.onOff],
[14, "open_window", tuya.valueConverter.onOff],
[24, "comfort_temperature", tuya.valueConverter.divideBy10],
[25, "eco_temperature", tuya.valueConverter.divideBy10],
[28, "schedule_monday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(1)],
[29, "schedule_tuesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(2)],
[30, "schedule_wednesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(3)],
[31, "schedule_thursday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(4)],
[32, "schedule_friday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(5)],
[33, "schedule_saturday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(6)],
[34, "schedule_sunday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(7)],
[35, "fault_alarm", tuya.valueConverter.errorOrBatteryLow],
[36, "frost_protection", tuya.valueConverter.onOff],
[39, "scale_protection", tuya.valueConverter.onOff],
[47, "local_temperature_calibration", tuya.valueConverter.raw],
[
49,
"system_mode",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
heat: tuya.enum(1),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_wem3gxyx"]),
model: "AE-940K",
vendor: "ACMELEC",
description: "Compatible with Daikin vrv system",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "2000" })],
exposes: [
e.binary("state", ea.STATE_SET, "ON", "OFF").withDescription("Turn the thermostat ON/OFF"),
e
.climate()
.withSystemMode(["cool", "heat", "fan_only", "dry"], ea.STATE_SET)
.withSetpoint("current_heating_setpoint", 16, 32, 1, ea.STATE_SET)
.withFanMode(["low", "medium", "high", "auto"], ea.STATE_SET)
.withLocalTemperature(ea.STATE),
e.child_lock(),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[
2,
"system_mode",
tuya.valueConverterBasic.lookup({
cool: tuya.enum(0),
heat: tuya.enum(1),
fan_only: tuya.enum(2),
dry: tuya.enum(3),
}),
],
[16, "current_heating_setpoint", tuya.valueConverter.raw],
[
28,
"fan_mode",
tuya.valueConverterBasic.lookup({
low: tuya.enum(0),
medium: tuya.enum(1),
high: tuya.enum(2),
auto: tuya.enum(3),
}),
],
[40, "child_lock", tuya.valueConverter.lockUnlock],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_mul9abs3"]),
model: "AE-720K",
vendor: "ACMELEC",
description: "Compatible with Daikin vrv system",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "2000" })],
toZigbee: [tzLocal.acmelec_ae720k_state_double_on],
exposes: [
// Same wording as AE-940K doc: state is binary and write-only
e.binary("state", ea.STATE_SET, "ON", "OFF").withDescription("Turn the thermostat ON/OFF"),
e
.climate()
.withSystemMode(["cool", "heat", "fan_only", "dry"], ea.STATE_SET)
.withSetpoint("current_heating_setpoint", 16, 32, 1, ea.STATE_SET)
.withFanMode(["low", "medium", "high", "auto"], ea.STATE_SET)
.withLocalTemperature(ea.STATE),
e.child_lock(),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[
2,
"system_mode",
tuya.valueConverterBasic.lookup({
cool: tuya.enum(0),
heat: tuya.enum(1),
fan_only: tuya.enum(2),
dry: tuya.enum(3),
}),
],
[16, "current_heating_setpoint", tuya.valueConverter.raw],
[
28,
"fan_mode",
tuya.valueConverterBasic.lookup({
low: tuya.enum(0),
medium: tuya.enum(1),
high: tuya.enum(2),
auto: tuya.enum(3),
}),
],
[
24,
"local_temperature",
{
from: (v) => (typeof v === "number" && v > 60 ? v / 10 : v), // supports both 257->25.7 and 26->26
to: (v) => v, // not used (read-only)
},
],
[40, "child_lock", tuya.valueConverter.lockUnlock],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_mul9abs3"]),
model: "AE-669K",
vendor: "ACMELEC",
description: "Compatible with Mitsubishi Electric vrf system",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.binary("state", ea.STATE_SET, "ON", "OFF").withDescription("Turn the thermostat ON/OFF"),
e
.climate()
.withSystemMode(["cool", "heat", "fan_only", "dry"], ea.STATE_SET)
.withSetpoint("current_heating_setpoint", 16, 32, 1, ea.STATE_SET)
.withFanMode(["low", "medium", "high", "auto"], ea.STATE_SET)
.withLocalTemperature(ea.STATE),
e.child_lock(),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[
2,
"system_mode",
tuya.valueConverterBasic.lookup({
cool: tuya.enum(0),
heat: tuya.enum(1),
fan_only: tuya.enum(2),
dry: tuya.enum(3),
}),
],
[16, "current_heating_setpoint", tuya.valueConverter.raw],
[
28,
"fan_mode",
tuya.valueConverterBasic.lookup({
low: tuya.enum(0),
medium: tuya.enum(1),
high: tuya.enum(2),
auto: tuya.enum(3),
}),
],
[40, "child_lock", tuya.valueConverter.lockUnlock],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_a14rjslz"]),
model: "ATMS10013Z3",
vendor: "Ourtop",
description: "Zigbee 3 Phase Meter",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
whiteLabel: [{ vendor: "Ourtop", model: "ATMS100133Z" }],
exposes: [
e.energy(),
tuya.exposes.voltageWithPhase("a"),
tuya.exposes.voltageWithPhase("b"),
tuya.exposes.voltageWithPhase("c"),
tuya.exposes.powerWithPhase("a"),
tuya.exposes.powerWithPhase("b"),
tuya.exposes.powerWithPhase("c"),
tuya.exposes.currentWithPhase("a"),
tuya.exposes.currentWithPhase("b"),
tuya.exposes.currentWithPhase("c"),
e.produced_energy(),
e.numeric("total_energy", ea.STATE).withUnit("kWh").withDescription("Total consumed and produced energy"),
e.power(),
e.power_reactive(),
e.power_factor().withUnit("%"),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100], // "Forward Active Energy", 7685 -> 76.85kW
[6, null, tuya.valueConverter.phaseVariant2WithPhase("a")], // "Phase A", CJMAAEMAAAI=
[7, null, tuya.valueConverter.phaseVariant2WithPhase("b")], // "Phase B"
[8, null, tuya.valueConverter.phaseVariant2WithPhase("c")], // "Phase C"
[23, "produced_energy", tuya.valueConverter.divideBy100], // "Reverse Energy", 0 W*h -> 0.0 kW*h
[24, "total_energy", tuya.valueConverter.divideBy100], // total energy produced + consumed , 7692 W*h -> 76.92kW*h
[29, "power", tuya.valueConverter.power], // "Total Active Power", 1740 W
[30, "power_reactive", tuya.valueConverter.power], // "Total Reactive Power", 985 Var
[50, "power_factor", tuya.valueConverter.raw], // "Overall Power Factor" 88 -> 88 %
],
},
},
{
zigbeeModel: ["TS0901"],
model: "TS0901",
vendor: "Tuya",
description: "Battery powered light",
extend: [m.battery(), m.light()],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_atdqo4nj"]),
model: "ZG-WK-DA-Wh-Zigbee",
vendor: "Tuya",
description: "Wall thermostat with humidity sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.humidity(),
e.child_lock(),
//e.temperature_sensor_select(['internal', 'external', 'both']),
e
.climate()
.withSystemMode(["off", "heat"], ea.STATE_SET)
.withPreset(["manual", "auto"])
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withRunningState(["idle", "heat"], ea.STATE)
.withLocalTemperature(ea.STATE)
.withLocalTemperatureCalibration(-9, 9, 1, ea.STATE_SET),
],
meta: {
tuyaDatapoints: [
[1, "system_mode", tuya.valueConverterBasic.lookup({ heat: true, off: false })],
[2, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[3, "local_temperature", tuya.valueConverter.divideBy10],
[4, "preset", tuya.valueConverterBasic.lookup({ manual: 0, auto: 1 })],
[9, "child_lock", tuya.valueConverter.lockUnlock],
//[11, 'unknown', null],
[19, "local_temperature_calibration", tuya.valueConverterBasic.divideBy(1)],
[102, "running_state", tuya.valueConverterBasic.lookup({ heat: 0, idle: 1 })],
],
},
},
{
fingerprint: tuya.fingerprint("TS0003", ["_TZ3000_g9chy2ib"]),
model: "TS0003_3_gang_switch",
vendor: "Tuya",
description: "3 gang switch with backlight",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
powerOnBehavior2: true,
backlightModeOffOn: true,
onOffCountdown: true,
endpoints: ["l1", "l2", "l3"],
}),
],
fromZigbee: [],
toZigbee: [],
endpoint: (device) => ({ l1: 1, l2: 2, l3: 3 }),
meta: {
multiEndpoint: true,
},
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
for (const ep of [1, 2, 3]) {
await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ["genOnOff"]);
}
},
},
{
fingerprint: tuya.fingerprint("TS0001", ["_TZ3000_qvmiyxuk"]),
model: "TS0001_1_gang_switch",
vendor: "Tuya",
description: "1 gang switch with backlight",
fromZigbee: [],
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
powerOnBehavior2: true,
backlightModeOffOn: true,
onOffCountdown: true,
}),
],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
},
},
{
fingerprint: tuya.fingerprint("TS0726", ["_TZ3000_5kxl9esg", "_TZ3002_jn2x20tg", "_TZ300A_rncj86af", "_TZ3002_xkxgfxsg"]),
model: "TS0726_1_gang_scene_switch",
vendor: "Tuya",
description: "1 gang switch with scene and backlight",
whiteLabel: [
tuya.whitelabel("BSEED", "EC-GL86ZPCS11", "1 gang switch with scene and backlight", ["_TZ3002_jn2x20tg"]),
tuya.whitelabel("BSEED", "EC-SL-FK86ZPCS11", "1 gang switch with scene and backlight (Neutral line optional)", ["_TZ3002_xkxgfxsg"]),
],
fromZigbee: [fzLocal.TS0726_action],
exposes: [e.action(["scene_1"])],
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
switchMode: true,
powerOnBehavior2: true,
backlightModeOffOn: true,
indicatorModeNoneRelayPos: true,
onOffCountdown: true,
}),
],
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
},
},
{
fingerprint: tuya.fingerprint("TS0726", [
"_TZ3000_ezqbvrqz",
"_TZ3002_ymv5vytn",
"_TZ3002_6ahhkwyh",
"_TZ3002_zjuvw9zf",
"_TZ3002_a4kvf6zd",
"_TZ300A_ohjmifiz",
"_TZ3002_tlsvxhxc",
]),
model: "TS0726_2_gang_scene_switch",
vendor: "Tuya",
description: "2 gang switch with scene and backlight",
whiteLabel: [
tuya.whitelabel("BSEED", "EC-GL86ZPCS21", "2 gang switch with scene and backlight", ["_TZ3002_zjuvw9zf"]),
tuya.whitelabel("BSEED", "EC-SL-FK86ZPCS21", "2 gang switch with scene and backlight (Neutral line optional)", ["_TZ3002_tlsvxhxc"]),
],
fromZigbee: [fzLocal.TS0726_action],
exposes: [e.action(["scene_1", "scene_2"])],
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
switchMode: true,
powerOnBehavior2: true,
backlightModeOffOn: true,
indicatorModeNoneRelayPos: true,
onOffCountdown: true,
endpoints: ["l1", "l2"],
}),
],
endpoint: (device) => ({ l1: 1, l2: 2 }),
meta: {
multiEndpoint: true,
},
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
for (const ep of [1, 2]) {
await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ["genOnOff"]);
}
},
},
{
fingerprint: tuya.fingerprint("TS0726", [
"_TZ3000_noru9tix",
"_TZ3002_rbnycsav",
"_TZ3002_kq3kqwjt",
"_TZ3002_ybtqbyk3",
"_TZ3002_iedhxgyi",
"_TZ3002_vsom92pp",
"_TZ300A_vqrs45nj",
"_TZ3000_cziew6eu",
"_TZ3000_r2fgo9ks",
]),
model: "TS0726_3_gang_scene_switch",
vendor: "Tuya",
description: "3 gang switch with scene and backlight",
whiteLabel: [
tuya.whitelabel("BSEED", "EC-GL86ZPCS31", "3 gang switch with scene and backlight", ["_TZ3002_iedhxgyi"]),
tuya.whitelabel("BSEED", "EC-SL-FK86ZPCS31", "3 gang switch with scene and backlight (Neutral line optional)", [
"_TZ3002_vsom92pp",
"_TZ3000_r2fgo9ks",
]),
tuya.whitelabel("Zemismart", "KES-606US-L3-EESS", "3 gang switch with neutral", ["_TZ3000_cziew6eu"]),
],
fromZigbee: [fzLocal.TS0726_action],
exposes: [e.action(["scene_1", "scene_2", "scene_3"])],
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
switchMode: true,
powerOnBehavior2: true,
backlightModeOffOn: true,
indicatorModeNoneRelayPos: true,
onOffCountdown: true,
endpoints: ["l1", "l2", "l3"],
}),
],
endpoint: (device) => ({ l1: 1, l2: 2, l3: 3 }),
meta: {
multiEndpoint: true,
},
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
for (const ep of [1, 2, 3]) {
await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ["genOnOff"]);
}
},
},
{
fingerprint: tuya.fingerprint("TS0726", [
"_TZ3000_rsylfthg",
"_TZ3002_umdkr64x",
"_TZ3002_hkaktryd",
"_TZ3002_pzao9ls1",
"_TZ300A_vkflnsl0",
"_TZ3002_eda6eitk",
"_TZ3000_hurauima",
"_TZ3002_tdf2m4ch",
]),
model: "TS0726_4_gang_scene_switch",
vendor: "Tuya",
description: "4 gang switch with scene and backlight",
whiteLabel: [
tuya.whitelabel("BSEED", "EC-GL86ZPCS41", "4 gang switch with scene and backlight", ["_TZ3002_pzao9ls1"]),
tuya.whitelabel("BSEED", "EC-SL-FK86ZPCS41", "4 gang switch with scene and backlight (Neutral line optional)", ["_TZ3002_eda6eitk"]),
],
fromZigbee: [fzLocal.TS0726_action],
exposes: [e.action(["scene_1", "scene_2", "scene_3", "scene_4"])],
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
switchMode: true,
powerOnBehavior2: true,
backlightModeOffOn: (m) => m !== "_TZ3002_tdf2m4ch",
indicatorModeNoneRelayPos: true,
onOffCountdown: (m) => m !== "_TZ3002_tdf2m4ch",
endpoints: ["l1", "l2", "l3", "l4"],
}),
],
endpoint: (device) => ({ l1: 1, l2: 2, l3: 3, l4: 4 }),
meta: {
multiEndpoint: true,
},
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
for (const ep of [1, 2, 3, 4]) {
await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ["genOnOff"]);
}
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_oc7xqqbs"]),
model: "HY-SZLUMPIR",
vendor: "Homcloud",
description: "PIR sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true, respondToMcuVersionResponse: true })],
exposes: [e.occupancy(), e.illuminance(), e.battery()],
meta: {
tuyaDatapoints: [
[1, "occupancy", tuya.valueConverter.trueFalse0],
[4, "battery", tuya.valueConverter.raw],
[101, "illuminance", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_1drr8tab"]),
model: "XZ-AKT101",
vendor: "Tuya",
description: "Intelligent air conditioning wall switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
ota: true,
exposes: [
e.binary("air_switch", ea.STATE_SET, true, false),
e.child_lock(),
e
.climate()
.withLocalTemperature(ea.STATE)
.withSetpoint("current_heating_setpoint", 20, 30, 0.5, ea.STATE_SET)
.withSystemMode(["cool", "heat", "fan_only"], ea.STATE_SET, "Only for Homeassistant")
.withFanMode(["low", "medium", "high", "auto"]),
e
.numeric("boost_time", ea.STATE_SET)
.withUnit("H")
.withDescription("Boost running time. Minimum 0 - maximum 24 hours")
.withValueMin(0)
.withValueMax(24)
.withValueStep(0.5)
.withCategory("config"),
],
meta: {
tuyaDatapoints: [
[1, "air_switch", tuya.valueConverter.raw],
[
2,
"system_mode",
tuya.valueConverterBasic.lookup({
heat: tuya.enum(1),
cool: tuya.enum(0),
fan_only: tuya.enum(2),
}),
],
[
28,
"fan_mode",
tuya.valueConverterBasic.lookup({
low: tuya.enum(0),
medium: tuya.enum(1),
high: tuya.enum(2),
auto: tuya.enum(3),
}),
],
[16, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[24, "local_temperature", tuya.valueConverter.divideBy10],
[40, "child_lock", tuya.valueConverter.lockUnlock],
[101, "boost_time", tuya.valueConverter.divideBy10],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_jcwbwckh"]),
model: "ZBN-JT-63",
vendor: "Tuya",
description: "Din rail switch with power monitoring",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
tuya.exposes.switch(),
e.numeric("power", ea.STATE).withDescription("power").withUnit("W").withDescription("Instantaneous measured power"),
e.numeric("current", ea.STATE).withDescription("current").withUnit("A").withDescription("Instantaneous measured electrical current"),
e.numeric("voltage", ea.STATE).withDescription("voltage").withUnit("V").withDescription("Measured electrical potential value"),
e.numeric("energy", ea.STATE).withDescription("energy").withUnit("kWh").withDescription("Consumed energy"),
e.enum("power_outage_memory", ea.STATE_SET, ["on", "off", "restore"]).withDescription("Recover state after power outage"),
e
.enum("fault", ea.STATE, [
"clear",
"short_circuit_alarm",
"surge_alarm",
"overload_alarm",
"leakagecurr_alarm",
"temp_dif_fault",
"fire_alarm",
"high_power_alarm",
"self_test_alarm",
"ov_cr",
"unbalance_alarm",
"ov_vol",
"undervoltage_alarm",
"miss_phase_alarm",
"outage_alarm",
"magnetism_alarm",
"credit_alarm",
"no_balance_alarm",
])
.withDescription("Fault status of the device (clear = nothing)"),
e.text("meter_id", ea.STATE).withDescription("Meter ID (ID of device)"),
],
meta: {
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[3, null, null],
[4, null, null],
[6, null, tuya.valueConverter.phaseVariant3],
[
10,
"fault",
tuya.valueConverterBasic.lookup({
clear: 0,
ov_cr: 1,
unbalance_alarm: 2,
ov_vol: 4,
undervoltage_alarm: 8,
miss_phase_alarm: 16,
outage_alarm: 32,
magnetism_alarm: 64,
terminal_alarm: 128,
cover_alarm: 256,
credit_alarm: 512,
no_balance_alarm: 1024,
battery_alarm: 2048,
meter_hardware_alarm: 4096,
}),
],
[16, "state", tuya.valueConverter.onOff],
[
18,
"meter_id",
{
from: (v) => {
if (typeof v === "object") {
const buf = Buffer.from([v[0], v[1], v[2], v[3]]);
return buf.readUInt32BE();
}
return v;
},
},
],
[
23,
"power_outage_memory",
tuya.valueConverterBasic.lookup({
on: 0,
off: 1,
restore: 2,
}),
],
],
},
},
{
zigbeeModel: ["5rta89nj"],
fingerprint: [{ modelID: "TS0601", manufacturerName: "_TZ3210_emqmwtym" }],
model: "EPJ-ZB",
vendor: "Nova Digital",
description: "Smart sliding window pusher",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.battery(),
te.coverPosition(),
e.binary("charge_state", ea.STATE, "true", "false"),
e.enum("manual_mode", ea.STATE_SET, ["Enable", "Disable"]),
e.enum("fault", ea.STATE, ["Normal", "None", "Fault"]).withDescription("Motor Fault"),
e.numeric("countdown", ea.STATE_SET).withValueMin(10).withValueMax(90).withUnit("s").withDescription("Motor timeout"),
te.motorDirectionSide(),
te.slowMode(),
e.enum("fixed_window_sash", ea.STATE_SET, ["Up", "Down"]).withDescription("Button position"),
e.enum("window_detection", ea.STATE, ["Opened", "Closed", "Pending"]).withDescription("Window detection status"),
],
meta: {
tuyaSendCommand: "sendData",
tuyaDatapoints: [
[4, "battery", tuya.valueConverter.raw],
[102, "state", tuya.valueConverterBasic.lookup({ OPEN: 0, CLOSE: 1, STOP: 2 })],
[104, "position", tuya.valueConverter.coverPosition],
[105, "charge_state", tuya.valueConverter.trueFalse1],
[106, "manual_mode", tuya.valueConverterBasic.lookup({ Enable: 0, Disable: 1 })],
[107, "fault", tuya.valueConverterBasic.lookup({ Normal: 0, None: 1, Fault: 2 })],
[108, "countdown", tuya.valueConverter.raw],
[109, "motor_direction", tuya.valueConverterBasic.lookup({ left: 1, right: 0 })], // swapped ?
[110, "slow_mode", tuya.valueConverter.onOffEnumOn1],
[112, "fixed_window_sash", tuya.valueConverterBasic.lookup({ Up: 1, Down: 0 })],
[114, "window_detection", tuya.valueConverterBasic.lookup({ Opened: 0, Closed: 1, Pending: 2 })],
],
},
},
{
zigbeeModel: ["ZG-102ZM", "AY02SZ"],
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_wzk0x7fq", "_TZE200_jfw0a4aa"]),
model: "ZG-102ZM",
vendor: "HOBEIAN",
description: "Vibration sensor",
whiteLabel: [
{
model: "AY02SZ",
vendor: "AOYAN",
description: "Vibration sensor",
fingerprint: [{ modelID: "AY02SZ", manufacturerName: "AOYAN" }, { manufacturerName: "AOYAN" }],
},
],
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.vibration(),
e.contact(),
e.battery(),
e
.numeric("sensitivity", ea.STATE_SET)
.withValueMin(1)
.withValueMax(50)
.withValueStep(1)
.withDescription("The larger the value, the more sensitive it is (refresh and update only while active)"),
],
meta: {
tuyaDatapoints: [
[1, "vibration", tuya.valueConverter.trueFalse1],
[101, "contact", tuya.valueConverter.inverse],
[4, "battery", tuya.valueConverter.raw],
[6, "sensitivity", tuya.valueConverter.raw],
],
},
},
{
zigbeeModel: ["ZG-204ZV", "AY204T"],
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_uli8wasj", "_TZE200_grgol3xp", "_TZE200_rhgsbacq"]),
model: "ZG-204ZV",
vendor: "HOBEIAN",
description: "Millimeter wave motion detection",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence(),
e.illuminance(),
e.temperature(),
e.humidity(),
tuya.exposes.temperatureUnit(),
tuya.exposes.temperatureCalibration(),
tuya.exposes.humidityCalibration(),
e.battery(),
e
.numeric("fading_time", ea.STATE_SET)
.withValueMin(0)
.withValueMax(28800)
.withValueStep(1)
.withUnit("s")
.withDescription("Motion keep time"),
e.binary("indicator", ea.STATE_SET, "ON", "OFF").withDescription("LED indicator mode"),
e
.numeric("illuminance_interval", ea.STATE_SET)
.withValueMin(1)
.withValueMax(720)
.withValueStep(1)
.withUnit("minutes")
.withDescription("Light sensing sampling(refresh and update only while active)"),
e
.numeric("motion_detection_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(19)
.withValueStep(1)
.withDescription("The larger the value, the more sensitive it is (refresh and update only while active)"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse1],
[106, "illuminance", tuya.valueConverter.raw],
[102, "fading_time", tuya.valueConverter.raw],
[2, "motion_detection_sensitivity", tuya.valueConverter.raw],
[108, "indicator", tuya.valueConverter.onOff],
[110, "battery", tuya.valueConverter.raw],
[111, "temperature", tuya.valueConverter.divideBy10],
[101, "humidity", tuya.valueConverter.raw],
[109, "temperature_unit", tuya.valueConverter.temperatureUnit],
[105, "temperature_calibration", tuya.valueConverter.divideBy10],
[104, "humidity_calibration", tuya.valueConverter.raw],
[107, "illuminance_interval", tuya.valueConverter.raw],
],
},
whiteLabel: [
{
model: "AY204T",
vendor: "AOYAN",
description: "Millimeter wave motion detection",
fingerprint: [{ modelID: "AY204T", manufacturerName: "AOYAN " }],
},
],
},
{
zigbeeModel: ["ZG-223Z"],
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_jsaqgakf", "_TZE200_u6x1zyv2", "_TZE200_2pddnnrk"]),
model: "ZG-223Z",
vendor: "HOBEIAN",
description: "Rainwater detection sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.enum("rainwater", ea.STATE, ["none", "raining"]).withDescription("Sensor rainwater status"),
e.illuminance(),
e
.numeric("sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(9)
.withValueStep(1)
.withDescription("The larger the value, the more sensitive it is (refresh and update only while active)"),
e
.numeric("illuminance_sampling", ea.STATE_SET)
.withValueMin(1)
.withValueMax(480)
.withValueStep(1)
.withUnit("minutes")
.withDescription("Brightness acquisition interval (refresh and update only while active)"),
e.battery(),
],
meta: {
tuyaDatapoints: [
[
1,
"rainwater",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
raining: tuya.enum(1),
}),
],
[102, "illuminance", tuya.valueConverter.raw],
[104, "battery", tuya.valueConverter.raw],
[2, "sensitivity", tuya.valueConverter.raw],
[101, "illuminance_sampling", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_no6qtgtl"]),
model: "RD24G01",
vendor: "Tuya",
description: "24GHz human presence sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
exposes
.enum("presence_state", exposes.access.STATE, ["none", "motion", "stationary"])
.withDescription("Presence state: none, motion, or stationary"),
exposes.numeric("target_distance_closest", exposes.access.STATE).withUnit("m").withDescription("Closest target distance"),
exposes
.numeric("near_detection", exposes.access.STATE_SET)
.withUnit("m")
.withDescription("Minimum detection range")
.withValueMin(0.1)
.withValueMax(6),
exposes
.numeric("far_detection", exposes.access.STATE_SET)
.withUnit("m")
.withDescription("Maximum detection range")
.withValueMin(0.1)
.withValueMax(6),
exposes
.numeric("static_sensitivity", exposes.access.STATE_SET)
.withDescription("Static presence sensitivity")
.withValueMin(0)
.withValueMax(10),
exposes.numeric("motion_sensitivity", exposes.access.STATE_SET).withDescription("Motion sensitivity").withValueMin(0).withValueMax(10),
],
meta: {
tuyaDatapoints: [
[
1,
"presence_state",
tuya.valueConverterBasic.lookup({
none: 0,
motion: 1,
stationary: 2,
}),
],
[3, "near_detection", tuya.valueConverter.divideBy100],
[4, "far_detection", tuya.valueConverter.divideBy100],
[9, "target_distance_closest", tuya.valueConverter.divideBy100],
[101, "static_sensitivity", tuya.valueConverter.raw],
[102, "motion_sensitivity", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_gubdgai2", "_TZE200_vdiuwbkq"]),
model: "M515EGBZTN",
vendor: "Quoya",
description: "Zigbee roller blind",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
options: [exposes.options.invert_cover()],
exposes: [te.coverPosition(), te.motorDirection(), te.coverLimit()],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[2, "position", tuya.valueConverter.coverPositionInverted],
[5, "motor_direction", tuya.valueConverter.tubularMotorDirection],
[16, "cover_limit", tuya.valueConverter.coverLimit],
],
},
},
{
zigbeeModel: ["ZG-303Z", "AY-303Z", "AY-302Z"],
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_npj9bug3", "_TZE200_wrmhp6b3"]),
model: "CS-201Z",
vendor: "COOLO",
description: "Soil moisture sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
whiteLabel: [
{
model: "AY-303Z",
vendor: "AOYAN",
description: "Soil moisture sensor",
fingerprint: [{ modelID: "AY-303Z", manufacturerName: "AOYAN " }],
},
{
model: "AY-302Z",
vendor: "AOYAN",
description: "Soil moisture sensor",
fingerprint: [{ modelID: "AY-302Z", manufacturerName: "AOYAN " }],
},
],
exposes: (device) => {
const exposes = [
e.dry(),
e.temperature(),
e.soil_moisture(),
tuya.exposes.temperatureUnit(),
tuya.exposes.temperatureCalibration(),
tuya.exposes.soilCalibration(),
tuya.exposes.temperatureSampling(),
tuya.exposes.soilSampling(),
tuya.exposes.soilWarning(),
e.battery(),
];
if (!(0, utils_1.isDummyDevice)(device) && device.modelID !== "AY-302Z") {
exposes.splice(2, 0, e.humidity());
exposes.splice(6, 0, tuya.exposes.humidityCalibration());
}
return exposes;
},
meta: {
tuyaDatapoints: [
[106, "dry", tuya.valueConverter.raw],
[5, "temperature", tuya.valueConverter.divideBy10],
[109, "humidity", tuya.valueConverter.raw],
[3, "soil_moisture", tuya.valueConverter.raw],
[15, "battery", tuya.valueConverter.raw],
[9, "temperature_unit", tuya.valueConverter.temperatureUnit],
[104, "temperature_calibration", tuya.valueConverter.divideBy10],
[105, "humidity_calibration", tuya.valueConverter.raw],
[102, "soil_calibration", tuya.valueConverter.raw],
[111, "temperature_sampling", tuya.valueConverter.raw],
[112, "soil_sampling", tuya.valueConverter.raw],
[110, "soil_warning", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0001", ["_TZE21C_dohbhb5k", "_TZE21C_i2ij4rb3"]),
model: "1-ZB-WSD",
vendor: "Scimagic",
description: "Smart temperature and humidity switch (thermostat/hygrostat)",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: (device, options) => {
const exps = [e.switch(), e.temperature()];
// for _TZE21C_i2ij4rb3
if (device?.manufacturerName === "_TZE21C_i2ij4rb3") {
exps.push(e.humidity());
}
// for all
exps.push(e
.numeric("temperature_calibration", ea.STATE_SET)
.withValueMin(-10)
.withValueMax(10)
.withValueStep(0.5)
.withUnit("°C")
.withDescription("Temperature calibration"));
// for _TZE21C_i2ij4rb3
if (device?.manufacturerName === "_TZE21C_i2ij4rb3") {
exps.push(e
.numeric("humidity_calibration", ea.STATE_SET)
.withValueMin(-9)
.withValueMax(9)
.withValueStep(1)
.withUnit("%")
.withDescription("Humidity calibration"));
}
// for all
exps.push(e.binary("auto_work", ea.STATE_SET, "ON", "OFF").withDescription("Auto work mode"), e
.numeric("temperature_target", ea.STATE_SET)
.withValueMin(-100)
.withValueMax(100)
.withValueStep(0.5)
.withUnit("°C")
.withDescription("Temperature target"), e
.numeric("temperature_range", ea.STATE_SET)
.withValueMin(1)
.withValueMax(10)
.withValueStep(0.1)
.withUnit("°C")
.withDescription("Keep the temperature in a range"));
// for _TZE21C_i2ij4rb3
if (device?.manufacturerName === "_TZE21C_i2ij4rb3") {
exps.push(e
.numeric("humidity_range", ea.STATE_SET)
.withValueMin(1)
.withValueMax(10)
.withValueStep(1)
.withUnit("%")
.withDescription("Humidity range / hysteresis"), e
.numeric("humidity_target", ea.STATE_SET)
.withValueMin(1)
.withValueMax(99)
.withValueStep(1)
.withUnit("%")
.withDescription("Target humidity"));
}
if (device?.manufacturerName === "_TZE21C_i2ij4rb3") {
exps.push(e.enum("mode", ea.STATE_SET, ["heating", "dehumidify", "cooling", "wet"]).withDescription("Work mode"));
}
else {
exps.push(e.enum("mode", ea.STATE_SET, ["heating", "cooling"]).withDescription("Work mode"));
}
// for all
exps.push(e.binary("delay", ea.STATE_SET, "ON", "OFF").withDescription("Switch delay time mode"), e
.numeric("delay_time", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(1)
.withUnit("minute")
.withDescription("Switch delay time"));
return exps;
},
meta: {
tuyaDatapoints: [
[0x02, "state", tuya.valueConverter.onOff],
[0x1b, "temperature", tuya.valueConverter.divideBy10],
[0x2e, "humidity", tuya.valueConverter.raw],
[0x1e, "temperature_calibration", tuya.valueConverter.divideBy2],
[0x1d, "temperature_range", tuya.valueConverter.divideBy10],
[0x09, "auto_work", tuya.valueConverter.onOff],
[0x16, "temperature_target", tuya.valueConverter.divideBy10],
[0x08, "mode", tuya.valueConverterBasic.lookup({ heating: 0, dehumidify: 1, cooling: 2, wet: 3 })],
[0x38, "delay", tuya.valueConverter.onOff],
[0x37, "delay_time", tuya.valueConverter.raw],
[0x29, "humidity_target", tuya.valueConverter.raw],
[0x2a, "humidity_range", tuya.valueConverter.raw],
[0x2f, "humidity_calibration", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_5ys44kzo"]),
model: "TYSCENECUBE",
vendor: "iHseno",
description: "Scene cube",
extend: [
tuya.modernExtend.tuyaMagicPacket(),
...convLocal.sceneCubeAction(),
tuya.modernExtend.dpBattery({
dp: 0x0a,
}),
],
},
{
zigbeeModel: ["ZG-305Z"],
fingerprint: tuya.fingerprint("TS011F", ["_TZ3000_rul9yxcc"]),
model: "SM-PW801EZ",
vendor: "Coswall",
description: "2 gang switch with USB",
ota: true,
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
childLock: true,
endpoints: ["l1", "l2"],
}),
],
endpoint: (device) => {
return { l1: 1, l2: 2 };
},
meta: {
multiEndpoint: true,
multiEndpointSkip: ["power_on_behavior"],
},
configure: tuya.configureMagicPacket,
whiteLabel: [{ vendor: "HOBEIAN", model: "ZG-305Z", fingerprint: [{ modelID: "ZG-305Z" }] }],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_tgeqdjgk", "_TZE200_tgeqdjgk"]),
model: "TS0601_knob_dimmer_switch",
vendor: "Tuya",
description: "Dimmer knob with two lights",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
toZigbee: [tzLocal.TS0601_knob_dimmer_switch_group_id],
exposes: [
e.switch(),
e.switch().withEndpoint("l1"),
e.switch().withEndpoint("l2"),
tuya.exposes.switchMode().withEndpoint("l1"),
tuya.exposes.switchMode().withEndpoint("l2"),
e.light_brightness_colortemp([154, 370]),
e.enum("adjustment_mode", ea.ALL, ["brightness", "color_temp"]).withDescription("Adjustment mode"),
tuya.exposes.powerOnBehavior(),
e.action(["button_1", "button_2"]),
e.enum("mode", ea.ALL, ["knob", "scene"]).withDescription("Mode"),
e.numeric("group_id", ea.SET).withValueMin(1).withValueMax(10000).withDescription("Group ID to bind to."),
],
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[102, "state", tuya.valueConverter.onOff],
[121, "state_l1", tuya.valueConverter.onOff],
[122, "state_l2", tuya.valueConverter.onOff],
[131, "switch_mode_l1", tuya.valueConverter.switchMode],
[132, "switch_mode_l2", tuya.valueConverter.switchMode],
[103, "brightness", tuya.valueConverterBasic.scale(0, 254, 0, 1000)],
[107, "color_temp", tuya.valueConverterBasic.scale(154, 370, 0, 1000)],
[105, "adjustment_mode", tuya.valueConverterBasic.lookup({ brightness: tuya.enum(0), color_temp: tuya.enum(1) })],
[106, "power_on_behavior", tuya.valueConverter.powerOnBehavior],
[111, "action", tuya.valueConverterBasic.lookup({ button_1: tuya.enum(0) })],
[112, "action", tuya.valueConverterBasic.lookup({ button_2: tuya.enum(0) })],
[141, "mode", tuya.valueConverterBasic.lookup({ knob: tuya.enum(0), scene: tuya.enum(1) })],
],
},
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await endpoint.command("genBasic", "tuyaSetup", {}, { disableDefaultResponse: true });
},
endpoint: (device) => ({
default: 1,
l1: 1,
l2: 1,
}),
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_nj7sfid2"]),
model: "TS0601_smart_scene_knob",
vendor: "Tuya",
description: "Smart scene knob controller with 4 buttons",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
fromZigbee: [
fzLocal.TS0601_smart_scene_knob_light_onoff,
fzLocal.TS0601_smart_scene_knob_light_brightness,
fzLocal.TS0601_smart_scene_knob_light_colortemp,
fzLocal.TS0601_smart_scene_knob_curtain_command,
],
toZigbee: [
tzLocal.TS0601_smart_scene_knob_bind_all,
tzLocal.TS0601_smart_scene_knob_assign_button_1,
tzLocal.TS0601_smart_scene_knob_set_base_group_id,
],
exposes: [
e
.action([
"scene_1",
"scene_2",
"scene_3",
"scene_4",
"light_1_on",
"light_1_off",
"light_1_brightness_up",
"light_1_brightness_down",
"light_1_colortemp_up",
"light_1_colortemp_down",
"light_2_on",
"light_2_off",
"light_2_brightness_up",
"light_2_brightness_down",
"light_2_colortemp_up",
"light_2_colortemp_down",
"light_3_on",
"light_3_off",
"light_3_brightness_up",
"light_3_brightness_down",
"light_3_colortemp_up",
"light_3_colortemp_down",
"light_4_on",
"light_4_off",
"light_4_brightness_up",
"light_4_brightness_down",
"light_4_colortemp_up",
"light_4_colortemp_down",
"curtain_1_start",
"curtain_1_stop",
"curtain_1_position_open",
"curtain_1_position_close",
"curtain_2_start",
"curtain_2_stop",
"curtain_2_position_open",
"curtain_2_position_close",
"curtain_3_start",
"curtain_3_stop",
"curtain_3_position_open",
"curtain_3_position_close",
"curtain_4_start",
"curtain_4_stop",
"curtain_4_position_open",
"curtain_4_position_close",
])
.withDescription("Triggered action from scene button, light knob, or curtain control"),
e.numeric("brightness", ea.STATE).withValueMin(1).withValueMax(255).withDescription("Brightness level from light mode (1-255)"),
e.numeric("color_temp", ea.STATE).withValueMin(153).withValueMax(523).withDescription("Color temperature from light mode (mired)"),
e
.numeric("curtain_position", ea.STATE)
.withValueMin(0)
.withValueMax(100)
.withUnit("%")
.withDescription("Curtain position from curtain mode (0-100%)"),
e
.enum("assignment_status", ea.STATE, [statusUnassigned, statusWaiting, statusReady])
.withCategory("diagnostic")
.withDescription("Button assignment status"),
e
.numeric("base_group_id", ea.STATE)
.withCategory("diagnostic")
.withDescription("Base Group ID for button 1 (buttons 2-4 are +20, +40, +60)"),
e
.numeric("action_button", ea.STATE)
.withValueMin(1)
.withValueMax(4)
.withCategory("diagnostic")
.withDescription("Button number from last action"),
e.numeric("action_group", ea.STATE).withCategory("diagnostic").withDescription("Group ID from last action"),
e.enum("bind_all_scene", ea.SET, ["bind"]).withCategory("config").withDescription("Bind all buttons to Scene mode (red LED)"),
e.enum("bind_all_light", ea.SET, ["bind"]).withCategory("config").withDescription("Bind all buttons to Light mode (green LED)"),
e.enum("bind_all_curtain", ea.SET, ["bind"]).withCategory("config").withDescription("Bind all buttons to Curtain mode (blue LED)"),
e.enum("assign_button_1", ea.SET, ["assign"]).withCategory("config").withDescription("Start assignment: press button 1 after clicking"),
e
.numeric("set_base_group_id", ea.SET)
.withValueMin(1)
.withValueMax(65000)
.withCategory("config")
.withDescription("Manually set base Group ID (advanced)"),
],
meta: {
tuyaDatapoints: [
[1, "action", tuya.valueConverter.static("scene_1")],
[2, "action", tuya.valueConverter.static("scene_2")],
[3, "action", tuya.valueConverter.static("scene_3")],
[4, "action", tuya.valueConverter.static("scene_4")],
[52, "binding_confirmation", tuya.valueConverter.raw],
[102, "binding_config", tuya.valueConverter.raw],
],
},
},
{
fingerprint: [{ modelID: "TS0601", manufacturerName: "_TZE200_khah2lkr" }],
model: "HY607W-3A",
vendor: "Tuya",
description: "Thermostat for gas boiler",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.climate().withLocalTemperature(ea.STATE).withSetpoint("occupied_heating_setpoint", 5, 35, 0.5, ea.STATE_SET),
e
.enum("mode_state", ea.STATE_SET, ["manual", "auto", "temp_override"])
.withDescription("Thermostat mode: manual, auto, or temp_override"),
e.binary("state", ea.STATE_SET, "ON", "OFF").withDescription("ON/OFF thermostat"),
e.enum("running_state", ea.STATE, ["idle", "heat"]).withDescription("State of heating"),
],
meta: {
tuyaDatapoints: [
[16, "local_temperature", tuya.valueConverter.divideBy10],
[50, "occupied_heating_setpoint", tuya.valueConverter.divideBy10],
[125, "state", tuya.valueConverter.onOff],
[
128,
"mode_state",
{
from: (v) => {
function extractFirstNumericByte(v) {
try {
if (v === undefined || v === null)
return undefined;
if (Buffer.isBuffer(v))
return v.length > 0 ? v[0] : undefined;
if (typeof v === "object" && !Array.isArray(v) && "data" in v && Array.isArray(v.data))
return v.data.length > 0 ? v.data[0] : undefined;
if (Array.isArray(v))
return v.length > 0 && typeof v[0] === "number" ? v[0] : undefined;
if (typeof v === "number")
return v;
if (typeof v === "string" && v.length > 0) {
const n = Number.parseInt(v, 10);
return Number.isNaN(n) ? undefined : n;
}
}
catch (_err) { }
return undefined;
}
const b = extractFirstNumericByte(v);
const lookup = { 0: "manual", 1: "auto", 3: "temp_override" };
return b !== undefined ? lookup[b] : undefined;
},
to: (v) => {
if (typeof v !== "string")
return undefined;
const lookup = { manual: 0, auto: 1, temp_override: 3 };
const n = lookup[v];
return n !== undefined ? [n] : undefined;
},
},
],
[102, "running_state", { from: (v) => (v === true ? "heat" : "idle") }],
],
},
},
{
zigbeeModel: ["ZG-204ZH", "AY208Z"],
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_vuqzj1ej", "_TZE200_hdih4foa"]),
model: "ZG-204ZH",
vendor: "HOBEIAN",
description: "PIR 24Ghz human presence sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence(),
e.enum("motion_state", ea.STATE, ["none", "large", "small", "static"]).withDescription("Motion state"),
e.illuminance(),
e.temperature(),
e.humidity(),
tuya.exposes.temperatureUnit(),
tuya.exposes.temperatureCalibration(),
tuya.exposes.humidityCalibration(),
e.battery(),
e
.numeric("fading_time", ea.STATE_SET)
.withValueMin(0)
.withValueMax(28800)
.withValueStep(1)
.withUnit("s")
.withDescription("Motion keep time"),
e.binary("indicator", ea.STATE_SET, "ON", "OFF").withDescription("LED indicator mode"),
e
.numeric("illuminance_interval", ea.STATE_SET)
.withValueMin(1)
.withValueMax(720)
.withValueStep(1)
.withUnit("minutes")
.withDescription("Light sensing sampling(refresh and update only while active)"),
e
.numeric("static_detection_distance", ea.STATE_SET)
.withValueMin(0)
.withValueMax(5)
.withValueStep(0.01)
.withUnit("m")
.withDescription("Static detection distance"),
e
.numeric("static_detection_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(1)
.withDescription("Static detection sensitivity"),
e.enum("motion_detection_mode", ea.STATE_SET, ["pir_and_radar", "pir_or_radar", "only_radar"]).withDescription("Motion detection mode"),
e
.numeric("motion_detection_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(1)
.withDescription("Radar Motion detection sensitivity"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse1],
[106, "illuminance", tuya.valueConverter.raw],
[102, "fading_time", tuya.valueConverter.raw],
[2, "static_detection_sensitivity", tuya.valueConverter.raw],
[4, "static_detection_distance", tuya.valueConverter.divideBy100],
[108, "indicator", tuya.valueConverter.onOff],
[110, "battery", tuya.valueConverter.raw],
[111, "temperature", tuya.valueConverter.divideBy10],
[101, "humidity", tuya.valueConverter.raw],
[109, "temperature_unit", tuya.valueConverter.temperatureUnit],
[105, "temperature_calibration", tuya.valueConverter.divideBy10],
[104, "humidity_calibration", tuya.valueConverter.raw],
[107, "illuminance_interval", tuya.valueConverter.raw],
[
112,
"motion_detection_mode",
tuya.valueConverterBasic.lookup({
pir_and_radar: tuya.enum(0),
pir_or_radar: tuya.enum(1),
only_radar: tuya.enum(2),
}),
],
[
103,
"motion_state",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
large: tuya.enum(1),
small: tuya.enum(2),
static: tuya.enum(3),
}),
],
[123, "motion_detection_sensitivity", tuya.valueConverter.raw],
],
},
whiteLabel: [
{
model: "AY208Z",
vendor: "AOYAN",
description: "24G millimeter wave human presence sensor",
fingerprint: [{ modelID: "AY208Z", manufacturerName: "AOYAN" }],
},
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_z5jz7wpo"]),
model: "_TZE284_z5jz7wpo",
vendor: "Tuya",
description: "Ceiling fan control module",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.fan().withState().withSpeed(),
e.power_on_behavior(["off", "on", "restore"]).withAccess(ea.STATE_SET),
exposes
.numeric("countdown_hours", ea.STATE_SET)
.withUnit("h")
.withValueMin(0.25)
.withValueMax(12)
.withValueStep(0.25)
.withDescription("Fan ON time in hours (15 min increments)"),
e.enum("light_mode", ea.STATE_SET, ["none", "relay", "pos"]),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[
2,
"countdown_hours",
{
from: (seconds) => +(seconds / 3600).toFixed(2), // device → HA (hours with 2 decimal places)
to: (hours) => Math.min(43200, Math.round(hours * 3600)), // HA → device (seconds)
},
],
[
3,
"speed",
{
from: (value) => (value + 1) * 51, // device → HA %
to: (value) => {
// Snap HA % to nearest step: 20%, 40%, 60%, 80%, 100%
const snappedStep = Math.max(0, Math.min(4, Math.round(value / 51) - 1));
return tuya.enum(snappedStep);
},
},
],
[11, "power_on_behavior", tuya.valueConverterBasic.lookup({ off: tuya.enum(0), on: tuya.enum(1), restore: tuya.enum(2) })],
[12, "light_mode", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), relay: tuya.enum(1), pos: tuya.enum(2) })],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_pglpvdar", "_TZE284_4hdbt6rn"]),
model: "TO-Q-SA1",
vendor: "Tongou",
description: "Zigbee energy meter (transformer clamp)",
whiteLabel: [tuya.whitelabel("Tongou", "TOSA1-01WXJAT2A", "Smart energy meter, two wire", ["_TZE284_4hdbt6rn"])],
extend: [
tuya.modernExtend.tuyaBase({
dp: true,
queryOnConfigure: true,
queryIntervalSeconds: 60,
}),
],
exposes: [
e.energy(),
e.voltage(),
e.current(),
e.power(),
e.ac_frequency(),
e.power_factor().withUnit("%"),
e.numeric("temperature", ea.STATE).withUnit("°C").withDescription("Current temperature"),
e
.enum("event", ea.STATE, [
"normal",
"over_current_trip",
"over_power_trip",
"high_temp_trip",
"over_voltage_trip",
"under_voltage_trip",
"over_current_alarm",
"over_power_alarm",
"high_temp_alarm",
"over_voltage_alarm",
"under_voltage_alarm",
"remote_on",
"remote_off",
"manual_on",
"manual_off",
"leakage_trip",
"leakage_alarm",
"restore_default",
"automatic_closing",
"electricity_shortage",
"electricity_shortage_alarm",
"timing_switch_On",
"timing_switch_off",
])
.withDescription("Last event of the device"),
e.enum("over_current_setting", ea.STATE_SET, ["Ignore", "Alarm"]).withDescription("Over current setting"),
e
.numeric("over_current_threshold", ea.STATE_SET)
.withUnit("A")
.withDescription("Setup the value on the device")
.withValueMin(1)
.withValueMax(50),
e.enum("over_voltage_setting", ea.STATE_SET, ["Ignore", "Alarm"]).withDescription("Over voltage setting"),
e
.numeric("over_voltage_threshold", ea.STATE_SET)
.withUnit("V")
.withDescription("Setup value on the device")
.withValueMin(240)
.withValueMax(295),
e.enum("under_voltage_setting", ea.STATE_SET, ["Ignore", "Alarm"]).withDescription("Under voltage setting"),
e
.numeric("under_voltage_threshold", ea.STATE_SET)
.withUnit("V")
.withDescription("Setup value on the device")
.withValueMin(90)
.withValueMax(220),
e.enum("temperature_setting", ea.STATE_SET, ["Ignore", "Alarm"]).withDescription("Temperature setting"),
e
.numeric("temperature_threshold", ea.STATE_SET)
.withUnit("°C")
.withDescription("Setup value on the device")
.withValueMin(-25)
.withValueMax(80),
e.enum("over_power_setting", ea.STATE_SET, ["Ignore", "Alarm"]).withDescription("Over power setting"),
e
.numeric("over_power_threshold", ea.STATE_SET)
.withUnit("W")
.withDescription("Setup value on the device")
.withValueMin(1000)
.withValueMax(26000),
],
meta: {
multiEndpointSkip: ["test1", "forward_electricity"],
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100], // Total forward energy
[6, null, tuya.valueConverter.phaseVariant2], // Phase A
[13, "test1", tuya.valueConverter.raw], // Remaining electricity
[32, "ac_frequency", tuya.valueConverter.divideBy100], // Grid supply frequency
[50, "power_factor", tuya.valueConverter.raw], // Overall power factor
[
102,
"over_voltage_setting",
tuya.valueConverterBasic.lookup({
Ignore: tuya.enum(0),
Alarm: tuya.enum(1),
}),
],
[
103,
"under_voltage_setting",
tuya.valueConverterBasic.lookup({
Ignore: tuya.enum(0),
Alarm: tuya.enum(1),
}),
],
[
104,
"over_current_setting",
tuya.valueConverterBasic.lookup({
Ignore: tuya.enum(0),
Alarm: tuya.enum(1),
}),
],
[
105,
"over_power_setting",
tuya.valueConverterBasic.lookup({
Ignore: tuya.enum(0),
Alarm: tuya.enum(1),
}),
],
[
107,
"temperature_setting",
tuya.valueConverterBasic.lookup({
Ignore: tuya.enum(0),
Alarm: tuya.enum(1),
}),
],
[
110,
"event",
tuya.valueConverterBasic.lookup({
normal: 0,
over_current_trip: 1,
over_power_trip: 2,
high_temp_trip: 3,
over_voltage_trip: 4,
under_voltage_trip: 5,
over_current_alarm: 6,
over_power_alarm: 7,
high_temp_alarm: 8,
over_voltage_alarm: 9,
under_voltage_alarm: 10,
remote_on: 11,
remote_off: 12,
manual_on: 13,
manual_off: 14,
leakage_trip: 15,
leakage_alarm: 16,
restore_default: 17,
automatic_closing: 18,
electricity_shortage: 19,
electricity_shortage_alarm: 20,
timing_switch_on: 21,
timing_switch_off: 22,
}),
],
[114, "over_current_threshold", tuya.valueConverter.raw],
[115, "over_voltage_threshold", tuya.valueConverter.raw],
[116, "under_voltage_threshold", tuya.valueConverter.raw],
[118, "temperature_threshold", tuya.valueConverter.divideBy10],
[119, "over_power_threshold", tuya.valueConverter.raw],
[125, "forward_electricity", tuya.valueConverter.raw],
[131, "temperature", tuya.valueConverter.divideBy10],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_q9xty0ad"]),
model: "_TZE204_q9xty0ad",
vendor: "Tuya",
description: "Ayvolt Blinds",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
options: [exposes.options.invert_cover()],
exposes: [te.coverPosition(), te.motorState(), te.motorDirection()],
meta: {
tuyaDatapoints: [
[
1,
"state",
tuya.valueConverterBasic.lookup((options) => options.invert_cover
? { OPEN: tuya.enum(2), STOP: tuya.enum(1), CLOSE: tuya.enum(0) }
: { OPEN: tuya.enum(0), STOP: tuya.enum(1), CLOSE: tuya.enum(2) }),
],
[
3,
"motor_state",
tuya.valueConverterBasic.lookup((options) => options.invert_cover ? { opening: tuya.enum(1), closing: tuya.enum(0) } : { opening: tuya.enum(0), closing: tuya.enum(1) }),
],
[
8,
null,
{
from: (v, meta, options, publish) => {
return {
position: tuya.valueConverter.coverPosition.from(v, meta, options, publish),
};
},
},
],
[9, "position", tuya.valueConverter.coverPosition],
[11, "motor_direction", tuya.valueConverter.tubularMotorDirection],
],
},
},
{
fingerprint: tuya.fingerprint("TS011F", ["_TZ3000_qiutut5y"]),
model: "TS011F_quad_power1",
vendor: "Tuya",
description: "Switch with 4 gangs",
extend: [
m.deviceEndpoints({ endpoints: { "1": 1, "2": 2, "3": 3, "4": 4 } }),
m.onOff({ powerOnBehavior: false, endpointNames: ["1", "2", "3", "4"] }),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_3regm3h6", "_TZE204_0hcjew5p", "_TZE204_6vwfjkcj", "_TZE284_3regm3h6", "_TZE204_ouy7vpm1"]),
model: "_TZE204_3regm3h6",
vendor: "Tuya",
description: "Smart thermostat for electric radiator with pilot wire",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "1970" })],
whiteLabel: [
tuya.whitelabel("THALEOS", "TH-P1Z", "Smart thermostat for electric heater", ["_TZE204_0hcjew5p"]),
tuya.whitelabel("RKHK", "TH-P0Z", "Smart thermostat for electric heater", ["_TZE204_6vwfjkcj"]),
tuya.whitelabel("MAZDA", "MZTE1Z", "Smart thermostat for electric radiator with pilot wire", ["_TZE204_ouy7vpm1"]),
],
exposes: [
e.binary("state", ea.STATE_SET, "ON", "OFF").withDescription("Turn the heater on or off").withCategory("config"),
e.child_lock(),
e
.climate()
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withLocalTemperatureCalibration(-9, 9, 1, ea.STATE_SET)
.withSystemMode(["off", "heat"], ea.STATE)
.withPreset(["comfort", "eco", "antifrost", "off", "comfort_1", "comfort_2", "program", "manual"]),
e
.enum("mode", ea.STATE, ["comfort", "eco", "antifrost", "off", "comfort_1", "comfort_2", "program", "manual"])
.withDescription("Current running mode")
.withCategory("diagnostic"),
e
.binary("radiators_without_integrated_regulation", ea.STATE_SET, "ON", "OFF")
.withDescription("Enable this for radiator without integrated regulation. OFF if Comfort, Eco and Antifrost temperatures can be defined on the radiator. ON if the radiator has no integrated regulation (i.e define temperatures on the thermostat).")
.withCategory("config"),
e
.numeric("comfort_temperature", ea.STATE_SET)
.withUnit(" C")
.withValueMin(5)
.withValueMax(35)
.withValueStep(0.5)
.withDescription("Set comfort temperature")
.withCategory("config"),
e
.numeric("eco_temperature", ea.STATE_SET)
.withUnit(" C")
.withValueMin(5)
.withValueMax(30)
.withValueStep(0.5)
.withDescription("Set ECO temperature")
.withCategory("config"),
e
.numeric("antifrost_temperature", ea.STATE_SET)
.withUnit(" C")
.withValueMin(5)
.withValueMax(15)
.withValueStep(0.5)
.withDescription("Set antifrost temperature")
.withCategory("config"),
e
.numeric("temperature_sensibility", ea.STATE_SET)
.withUnit(" C")
.withValueMin(0.5)
.withValueMax(5)
.withValueStep(0.5)
.withDescription("Set thermostat sensitivity")
.withCategory("config"),
e.binary("antifrost", ea.STATE_SET, "ON", "OFF").withDescription("Enable antifrost protection feature").withCategory("config"),
e
.binary("window_detection", ea.STATE_SET, "ON", "OFF")
.withLabel("Open window detection")
.withDescription("Enable / Disable open window detection feature")
.withCategory("config"),
e.enum("window", ea.STATE, ["close", "open"]).withDescription("Indicates if window is open").withCategory("diagnostic"),
e.power(),
e.voltage(),
e.current(),
e.energy(),
e.numeric("energy_today", ea.STATE).withUnit("kWh").withDescription("Energy consumed today"),
e.numeric("energy_yesterday", ea.STATE).withUnit("kWh").withDescription("Energy consumed yesterday"),
e
.enum("device_mode_type", ea.STATE, ["four", "six", "switch"])
.withDescription("Indicates the actual pilot wire mode of the thermostat")
.withCategory("diagnostic"),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[1, "system_mode", tuya.valueConverterBasic.lookup({ heat: true, off: false })],
[
2,
"mode",
tuya.valueConverterBasic.lookup({
comfort: tuya.enum(0),
eco: tuya.enum(1),
antifrost: tuya.enum(2),
off: tuya.enum(3),
comfort_1: tuya.enum(4),
comfort_2: tuya.enum(5),
program: tuya.enum(6),
manual: tuya.enum(7),
}),
],
[
2,
"preset",
tuya.valueConverterBasic.lookup({
comfort: tuya.enum(0),
eco: tuya.enum(1),
antifrost: tuya.enum(2),
off: tuya.enum(3),
comfort_1: tuya.enum(4),
comfort_2: tuya.enum(5),
program: tuya.enum(6),
manual: tuya.enum(7),
}),
],
[11, "power", tuya.valueConverter.divideBy10],
[16, "local_temperature", tuya.valueConverter.divideBy10],
[
17,
"window",
tuya.valueConverterBasic.lookup({
close: tuya.enum(0),
open: tuya.enum(1),
}),
],
[19, "local_temperature_calibration", tuya.valueConverter.raw],
[20, "fault", tuya.valueConverter.raw],
[29, "window_detection", tuya.valueConverter.onOff],
[39, "child_lock", tuya.valueConverter.lockUnlock],
[50, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[101, "voltage", tuya.valueConverter.divideBy10],
[102, "current", tuya.valueConverter.divideBy1000],
[103, "temperature_sensibility", tuya.valueConverter.divideBy10],
[104, "energy_today", tuya.valueConverter.divideBy10],
[105, "energy_yesterday", tuya.valueConverter.divideBy10],
[
106,
"device_mode_type",
tuya.valueConverterBasic.lookup({
four: tuya.enum(0),
six: tuya.enum(1),
switch: tuya.enum(2),
}),
],
[107, "energy", tuya.valueConverter.divideBy10],
[108, "week_program_1", tuya.valueConverter.raw],
[109, "week_program_2", tuya.valueConverter.raw],
[110, "week_program_3", tuya.valueConverter.raw],
[111, "week_program_4", tuya.valueConverter.raw],
[112, "week_program_5", tuya.valueConverter.raw],
[113, "week_program_6", tuya.valueConverter.raw],
[114, "week_program_7", tuya.valueConverter.raw],
[115, "set_temp_switch", tuya.valueConverter.onOff],
[115, "radiators_without_integrated_regulation", tuya.valueConverter.onOff],
[116, "antifrost", tuya.valueConverter.onOff],
[117, "eco_temperature", tuya.valueConverter.divideBy10],
[118, "comfort_temperature", tuya.valueConverter.divideBy10],
[119, "antifrost_temperature", tuya.valueConverter.divideBy10],
[120, "light", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_ilzkxrav"]),
model: "TWC-R01",
vendor: "THALEOS",
description: "Smart thermostat for electric heater with pilot wire",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.pilot_wire_mode(["comfort", "eco", "antifrost", "off", "comfort_1", "comfort_2"]), // Not using climate here because setpoint is not supported
e.local_temperature(),
e
.numeric("local_temperature_calibration", ea.STATE_SET)
.withUnit("°C")
.withValueMin(-8)
.withValueMax(8)
.withValueStep(0.5)
.withDescription("Set temperature calibration"),
e.binary("eco_mode", ea.STATE_SET, "ON", "OFF").withDescription("Enables/disables the weekly program."),
e.open_window(),
e.open_window_temperature(),
e.power(),
e.voltage(),
e.current(),
e.energy(),
e.numeric("energy_today", ea.STATE).withUnit("kWh").withDescription("Energy consumed today"),
e.numeric("energy_yesterday", ea.STATE).withUnit("kWh").withDescription("Energy consumed yesterday"),
e.binary("device_mode_type", ea.STATE_SET, "ON", "OFF").withDescription("Set pilot wire mode to 6 modes (includes comfort 1 & 2)."),
],
meta: {
tuyaDatapoints: [
[
2,
"pilot_wire_mode",
tuya.valueConverterBasic.lookup({
comfort: tuya.enum(0),
eco: tuya.enum(1),
antifrost: tuya.enum(2),
off: tuya.enum(3),
comfort_1: tuya.enum(4),
comfort_2: tuya.enum(5),
}),
],
[11, "power", tuya.valueConverter.divideBy10],
[16, "local_temperature", tuya.valueConverter.divideBy10],
[19, "local_temperature_calibration", tuya.valueConverter.raw],
[20, "fault", tuya.valueConverter.raw],
[40, "eco_mode", tuya.valueConverter.onOff],
[101, "week_program", tuya.valueConverter.raw],
[102, "work_state", tuya.valueConverter.raw],
[103, "week_program_1", tuya.valueConverter.raw],
[104, "week_program_2", tuya.valueConverter.raw],
[105, "week_program_3", tuya.valueConverter.raw],
[106, "week_program_4", tuya.valueConverter.raw],
[107, "week_program_5", tuya.valueConverter.raw],
[108, "week_program_6", tuya.valueConverter.raw],
[109, "week_program_7", tuya.valueConverter.raw],
[110, "open_window", tuya.valueConverter.onOff],
[111, "open_window_temperature", tuya.valueConverter.raw],
[112, "window_time", tuya.valueConverter.raw],
[113, "window_timeout", tuya.valueConverter.raw],
[114, "device_mode_type", tuya.valueConverter.onOff],
[115, "voltage", tuya.valueConverter.divideBy10],
[116, "current", tuya.valueConverter.divideBy10],
[117, "energy", tuya.valueConverter.divideBy10],
[119, "energy_today", tuya.valueConverter.divideBy10],
[120, "energy_yesterday", tuya.valueConverter.divideBy10],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_r3szw0xr"]),
model: "TS0601_cover_11",
vendor: "Tuya",
description: "Cover motor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
te.coverPosition(),
e.enum("dot_mode", ea.STATE_SET, ["single", "multi"]).withDescription("Dot mode"), // DP 104
e.enum("direction", ea.STATE_SET, ["normal", "reversed"]).withDescription("Motor direction"), // DP 11
te.coverLimit(),
e.numeric("speed", ea.STATE_SET).withValueMin(1).withValueMax(5).withDescription("Motor speed"), // DP 103
te.motorState(),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[3, "motor_state", tuya.valueConverter.motorState],
[9, "position", tuya.valueConverter.coverPositionInverted],
[8, "position", tuya.valueConverter.coverPositionInverted],
[103, "speed", tuya.valueConverter.raw],
[104, "dot_mode", tuya.valueConverterBasic.lookup({ single: tuya.enum(0), multi: tuya.enum(1) })],
[11, "direction", tuya.valueConverterBasic.lookup({ normal: tuya.enum(0), reversed: tuya.enum(1) })],
[16, "cover_limit", tuya.valueConverter.coverLimit], // up down delete ?
],
},
},
{
// Incomplete. See _TZE200_mlglxwp3 datapoints here:
// https://github.com/Koenkk/zigbee-herdsman-converters/pull/12014
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_mlglxwp3"]),
model: "TS0601_cover_12",
vendor: "Tuya",
description: "Curtain motor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
te.coverPosition(),
te.motorDirection(),
te.motorState(),
e.numeric("total_time", ea.STATE).withUnit("ms").withDescription("Total running time in milliseconds"),
e.enum("situation_set", ea.STATE_SET, ["fully_close", "fully_open"]).withDescription("Set fully open or fully close position"),
e.text("fault", ea.STATE).withDescription("Fault details"),
e.battery(),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[2, "position", tuya.valueConverter.coverPosition],
[3, "position", tuya.valueConverter.coverPosition],
[5, "motor_direction", tuya.valueConverter.tubularMotorDirection],
[7, "motor_state", tuya.valueConverter.motorState],
[10, "total_time", tuya.valueConverter.raw],
[11, "situation_set", tuya.valueConverterBasic.lookup({ fully_close: tuya.enum(0), fully_open: tuya.enum(1) })],
[12, "fault", tuya.valueConverter.raw],
[103, "battery", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_tgl8i2np"]),
model: "TS0601_cover_13",
vendor: "Tuya",
description: "Curtain motor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
te.coverPosition(),
te.motorDirection(),
te.motorState(),
e.numeric("total_time", ea.STATE).withUnit("ms").withDescription("Total running time in milliseconds"),
e.enum("situation_set", ea.STATE_SET, ["fully_close", "fully_open"]).withDescription("Set fully open or fully close position"),
e.binary("auto_power", ea.STATE_SET, true, false),
e.text("fault", ea.STATE).withDescription("Fault details"),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[2, "position", tuya.valueConverter.coverPosition],
[3, "position", tuya.valueConverter.coverPosition],
[5, "motor_direction", tuya.valueConverter.tubularMotorDirection],
[6, "auto_power", tuya.valueConverter.raw],
[7, "motor_state", tuya.valueConverter.motorState],
[10, "total_time", tuya.valueConverter.raw],
[11, "situation_set", tuya.valueConverterBasic.lookup({ fully_close: tuya.enum(0), fully_open: tuya.enum(1) })],
[12, "fault", tuya.valueConverter.raw],
],
},
},
{
zigbeeModel: ["ZS-304Z"],
fingerprint: tuya.fingerprint("TS0601", [
"_TZE284_k7p2q5d9",
"_TZE284_65gzcss7",
"_TZE284_0ints6wl",
"_TZE284_yzr43ayq",
"_TZE2841000000_0ints6wl",
]),
model: "ZS-300Z",
vendor: "Arteco",
description: "Soil moisture sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
whiteLabel: [
tuya.whitelabel("Arteco", "ZS-302Z", "Soil moisture sensor", ["_TZE284_65gzcss7"]),
{ model: "ZS-304Z", fingerprint: [{ manufacturerName: "_TZE284_0ints6wl" }, { manufacturerName: "_TZE284_yzr43ayq" }, { modelID: "ZS-304Z" }] },
],
exposes: [
e.enum("water_warning", ea.STATE, ["none", "alarm"]).withDescription("Water shortage warning"),
e
.enum("battery_state", ea.STATE, ["low", "middle", "high"])
.withDescription("low: 16.67%, middle:16.68-83.33%, high: 83.34-100%")
.withCategory("diagnostic"),
e.soil_moisture(),
e.temperature(),
e.humidity(),
e.illuminance(),
tuya.exposes.soilSampling(),
tuya.exposes.soilCalibration(),
tuya.exposes.humidityCalibration(),
e
.numeric("illuminance_calibration", ea.STATE_SET)
.withValueMin(-1000)
.withValueMax(1000)
.withValueStep(1)
.withUnit("lx")
.withDescription("Illuminance calibration"),
tuya.exposes.temperatureCalibration(),
tuya.exposes.soilWarning(),
],
meta: {
tuyaDatapoints: [
[3, "soil_moisture", tuya.valueConverter.raw],
[5, "temperature", tuya.valueConverter.divideBy10],
[101, "humidity", tuya.valueConverter.raw],
[102, "illuminance", tuya.valueConverter.raw],
[14, "battery_state", tuya.valueConverterBasic.lookup({ low: tuya.enum(0), middle: tuya.enum(1), high: tuya.enum(2) })],
[103, "soil_sampling", tuya.valueConverter.raw],
[104, "soil_calibration", tuya.valueConverter.raw],
[105, "humidity_calibration", tuya.valueConverter.raw],
[106, "illuminance_calibration", tuya.valueConverter.raw],
[107, "temperature_calibration", tuya.valueConverter.divideBy10],
[110, "soil_warning", tuya.valueConverter.raw],
[111, "water_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), alarm: tuya.enum(1) })],
],
},
},
{
zigbeeModel: ["ZG-204ZK", "AY-204ZX"],
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_ka8l86iu", "_TZE200_zbfmvj13"]),
model: "ZG-204ZK",
vendor: "HOBEIAN",
description: "24Ghz human presence sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence(),
e.battery(),
e
.numeric("fading_time", ea.STATE_SET)
.withValueMin(10)
.withValueMax(28800)
.withValueStep(1)
.withUnit("s")
.withDescription("Presence keep time"),
e
.numeric("detection_distance", ea.STATE_SET)
.withValueMin(0)
.withValueMax(5)
.withValueStep(0.01)
.withUnit("m")
.withDescription("Detection distance"),
e
.numeric("static_detection_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(1)
.withDescription("Static detection sensitivity"),
e
.numeric("motion_detection_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(1)
.withDescription("Motion detection sensitivity"),
e.binary("indicator", ea.STATE_SET, "ON", "OFF").withDescription("LED indicator mode"),
e.binary("anti_interference", ea.STATE_SET, "ON", "OFF").withDescription("Anti interference function"),
],
whiteLabel: [
{
model: "AY-204ZX",
vendor: "AOYAN",
description: "24Ghz millimeter wave and T&H sensor",
fingerprint: [{ modelID: "AY-204ZX", manufacturerName: "AOYAN" }],
},
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse1],
[102, "fading_time", tuya.valueConverter.raw],
[4, "detection_distance", tuya.valueConverter.divideBy100],
[2, "static_detection_sensitivity", tuya.valueConverter.raw],
[107, "indicator", tuya.valueConverter.onOff],
[123, "motion_detection_sensitivity", tuya.valueConverter.raw],
[121, "battery", tuya.valueConverter.raw],
[122, "anti_interference", tuya.valueConverter.onOff],
],
},
},
{
zigbeeModel: ["ZG-204ZE"],
fingerprint: [
{ modelID: "CK-BL702-MWS-01(7016)", manufacturerName: "ZG-204ZE" },
{ modelID: "TS0601", manufacturerName: "_TZE200_cq8lu23i" },
{ modelID: "TS0601", manufacturerName: "_TZE200_4pm4pekt" },
{ modelID: "TS0601", manufacturerName: "_TZE200_y8jijhba" },
],
model: "ZG-204ZE",
vendor: "HOBEIAN",
description: "10G mw motion detection",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence(),
e.illuminance(),
e.battery(),
e
.numeric("fading_time", ea.STATE_SET)
.withValueMin(0)
.withValueMax(28800)
.withValueStep(1)
.withUnit("s")
.withDescription("Motion keep time"),
e.binary("indicator", ea.STATE_SET, "ON", "OFF").withDescription("LED indicator mode"),
e
.numeric("motion_detection_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(19)
.withValueStep(1)
.withDescription("The larger the value, the more sensitive it is (refresh and update only while active)"),
e
.numeric("illuminance_interval", ea.STATE_SET)
.withValueMin(1)
.withValueMax(720)
.withValueStep(1)
.withUnit("minutes")
.withDescription("Light sensing sampling(refresh and update only while active)"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse1],
[102, "fading_time", tuya.valueConverter.raw],
[2, "motion_detection_sensitivity", tuya.valueConverter.raw],
[108, "indicator", tuya.valueConverter.onOff],
[110, "battery", tuya.valueConverter.raw],
[106, "illuminance", tuya.valueConverter.raw],
[107, "illuminance_interval", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0049", ["_TZ3000_kz1anoi8"]),
model: "HZ-WT02",
vendor: "Haozee",
description: "Water valve",
toZigbee: [tzLocal.ts0049_countdown],
fromZigbee: [fzLocal.ts0049_countdown],
extend: [
m.battery(),
m.onOff({ powerOnBehavior: false }),
m.deviceAddCustomCluster("manuSpecificTuyaE001", {
name: "manuSpecificTuyaE001",
ID: 0xe001,
attributes: {},
commands: {
setCountdown: {
name: "setCountdown",
ID: 0xfe,
parameters: [{ name: "data", type: 1008 }],
},
},
commandsResponse: {},
}),
],
exposes: [
e
.numeric("water_countdown", ea.STATE_SET)
.withValueMin(1)
.withValueMax(1440)
.withValueStep(1)
.withUnit("minute")
.withDescription("Watering countdown to turn device off after a certain time"),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_7zazvlyn", "_TZE284_idn2htgu"]),
model: "F3-Pro",
vendor: "Tuya",
description: "Smart panel, 4-gang switch with scene, dimmer, and curtain control",
extend: [
tuya.modernExtend.tuyaWeatherForecast({
includeCurrentWeather: true,
numberOfForecastDays: 3,
correctForNegativeValues: false,
weatherConditionMap: tuya.F3ProTuyaWeatherCondition,
}),
tuya.modernExtend.tuyaBase({
dp: true,
forceTimeUpdates: true,
queryOnConfigure: true,
timeStart: "1970", // needed else date/time doesn't sync with z2m > 2.6.2
}),
],
endpoint: (device) => {
return { l1: 1, l2: 1, l3: 1, l4: 1, l5: 1, l6: 1, l7: 1, l8: 1 };
},
exposes: [
e.binary("backlight_switch", ea.STATE_SET, "ON", "OFF").withDescription("Panel screen on/off"),
tuya.exposes.switch().withEndpoint("l1"),
tuya.exposes.switch().withEndpoint("l2"),
tuya.exposes.switch().withEndpoint("l3"),
tuya.exposes.switch().withEndpoint("l4"),
e.text("switch_name", ea.STATE_SET).withEndpoint("l1").withDescription("Name for Switch 1"),
e.text("switch_name", ea.STATE_SET).withEndpoint("l2").withDescription("Name for Switch 2"),
e.text("switch_name", ea.STATE_SET).withEndpoint("l3").withDescription("Name for Switch 3"),
e.text("switch_name", ea.STATE_SET).withEndpoint("l4").withDescription("Name for Switch 4"),
e.text("scene_name", ea.STATE_SET).withEndpoint("l1").withDescription("Name for Scene 1"),
e.text("scene_name", ea.STATE_SET).withEndpoint("l2").withDescription("Name for Scene 2"),
e.text("scene_name", ea.STATE_SET).withEndpoint("l3").withDescription("Name for Scene 3"),
e.text("scene_name", ea.STATE_SET).withEndpoint("l4").withDescription("Name for Scene 4"),
e.text("scene_name", ea.STATE_SET).withEndpoint("l5").withDescription("Name for Scene 5"),
e.text("scene_name", ea.STATE_SET).withEndpoint("l6").withDescription("Name for Scene 6"),
e.text("scene_name", ea.STATE_SET).withEndpoint("l7").withDescription("Name for Scene 7"),
e.text("scene_name", ea.STATE_SET).withEndpoint("l8").withDescription("Name for Scene 8"),
e.text("cover_name", ea.STATE_SET).withEndpoint("l1").withDescription("Name for Cover 1"),
e.text("cover_name", ea.STATE_SET).withEndpoint("l2").withDescription("Name for Cover 2"),
e.text("cover_name", ea.STATE_SET).withEndpoint("l3").withDescription("Name for Cover 3"),
e.text("cover_name", ea.STATE_SET).withEndpoint("l4").withDescription("Name for Cover 4"),
e.text("led_switch_name", ea.STATE_SET).withEndpoint("l1").withDescription("Name for LED Switch 1"),
e.text("led_switch_name", ea.STATE_SET).withEndpoint("l2").withDescription("Name for LED Switch 2"),
e.text("led_switch_name", ea.STATE_SET).withEndpoint("l3").withDescription("Name for LED Switch 3"),
e.text("led_switch_name", ea.STATE_SET).withEndpoint("l4").withDescription("Name for LED Switch 4"),
e.action(["scene_1", "scene_2", "scene_3", "scene_4", "scene_5", "scene_6", "scene_7", "scene_8"]),
e.binary("led_switch", ea.ALL, "ON", "OFF").withEndpoint("l1").withDescription("Switch of LED 1"),
e.numeric("led_warm", ea.STATE_SET).withEndpoint("l1").withDescription("Color temperature of LED 1").withValueMin(1).withValueMax(100),
e.numeric("led_bright", ea.STATE_SET).withEndpoint("l1").withDescription("Brightness of LED 1").withValueMin(1).withValueMax(100),
e.binary("led_switch", ea.ALL, "ON", "OFF").withEndpoint("l2").withDescription("Switch of LED 2"),
e.numeric("led_warm", ea.STATE_SET).withEndpoint("l2").withDescription("Color temperature of LED 2").withValueMin(1).withValueMax(100),
e.numeric("led_bright", ea.STATE_SET).withEndpoint("l2").withDescription("Brightness of LED 2").withValueMin(1).withValueMax(100),
e.binary("led_switch", ea.ALL, "ON", "OFF").withEndpoint("l3").withDescription("Switch of LED 3"),
e.numeric("led_warm", ea.STATE_SET).withEndpoint("l3").withDescription("Color temperature of LED 3").withValueMin(1).withValueMax(100),
e.numeric("led_bright", ea.STATE_SET).withEndpoint("l3").withDescription("Brightness of LED 3").withValueMin(1).withValueMax(100),
e.binary("led_switch", ea.ALL, "ON", "OFF").withEndpoint("l4").withDescription("Switch of LED 4"),
e.numeric("led_warm", ea.STATE_SET).withEndpoint("l4").withDescription("Color temperature of LED 4").withValueMin(1).withValueMax(100),
e.numeric("led_bright", ea.STATE_SET).withEndpoint("l4").withDescription("Brightness of LED 4").withValueMin(1).withValueMax(100),
e.enum("cover_state", ea.STATE_SET, ["open", "stop", "close"]).withEndpoint("l1").withDescription("State of Cover 1"),
e
.numeric("cover_position", ea.STATE_SET)
.withEndpoint("l1")
.withDescription("Position of Cover 1")
.withUnit("%")
.withValueMin(0)
.withValueMax(100),
e.enum("cover_state", ea.STATE_SET, ["open", "stop", "close"]).withEndpoint("l2").withDescription("State of Cover 2"),
e
.numeric("cover_position", ea.STATE_SET)
.withEndpoint("l2")
.withDescription("Position of Cover 2")
.withUnit("%")
.withValueMin(0)
.withValueMax(100),
e.enum("cover_state", ea.STATE_SET, ["open", "stop", "close"]).withEndpoint("l3").withDescription("State of Cover 3"),
e
.numeric("cover_position", ea.STATE_SET)
.withEndpoint("l3")
.withDescription("Position of Cover 3")
.withUnit("%")
.withValueMin(0)
.withValueMax(100),
e.enum("cover_state", ea.STATE_SET, ["open", "stop", "close"]).withEndpoint("l4").withDescription("State of Cover 4"),
e
.numeric("cover_position", ea.STATE_SET)
.withEndpoint("l4")
.withDescription("Position of Cover 4")
.withUnit("%")
.withValueMin(0)
.withValueMax(100),
e.numeric("temperature_1", ea.STATE_SET).withValueMin(-65).withValueMax(99).withDescription("Temperature").withValueStep(0.1),
e.enum("condition_1", ea.STATE_SET, Object.keys(tuya.F3ProTuyaWeatherCondition)).withDescription("Weather condition"),
],
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "action", tuya.valueConverter.static("scene_1")],
[2, "action", tuya.valueConverter.static("scene_2")],
[3, "action", tuya.valueConverter.static("scene_3")],
[4, "action", tuya.valueConverter.static("scene_4")],
[5, "action", tuya.valueConverter.static("scene_5")],
[6, "action", tuya.valueConverter.static("scene_6")],
[7, "action", tuya.valueConverter.static("scene_7")],
[8, "action", tuya.valueConverter.static("scene_8")],
[102, "led_bright_l1", tuya.valueConverter.raw],
[103, "led_bright_l2", tuya.valueConverter.raw],
[105, "led_bright_l3", tuya.valueConverter.raw],
[107, "led_bright_l4", tuya.valueConverter.raw],
[109, "led_warm_l1", tuya.valueConverter.raw],
[110, "led_warm_l2", tuya.valueConverter.raw],
[111, "led_warm_l3", tuya.valueConverter.raw],
[112, "led_warm_l4", tuya.valueConverter.raw],
[113, "cover_position_l1", tuya.valueConverter.raw],
[114, "cover_position_l2", tuya.valueConverter.raw],
[115, "cover_position_l3", tuya.valueConverter.raw],
[116, "cover_position_l4", tuya.valueConverter.raw],
[117, "led_switch_l1", tuya.valueConverter.onOff],
[118, "led_switch_l2", tuya.valueConverter.onOff],
[119, "led_switch_l3", tuya.valueConverter.onOff],
[120, "led_switch_l4", tuya.valueConverter.onOff],
[121, "state_l1", tuya.valueConverter.onOff],
[122, "state_l2", tuya.valueConverter.onOff],
[123, "state_l3", tuya.valueConverter.onOff],
[124, "state_l4", tuya.valueConverter.onOff],
[125, "led_switch_name_l1", tuya.valueConverter.utf16BEHexString],
[126, "led_switch_name_l2", tuya.valueConverter.utf16BEHexString],
[127, "led_switch_name_l3", tuya.valueConverter.utf16BEHexString],
[128, "led_switch_name_l4", tuya.valueConverter.utf16BEHexString],
[129, "cover_name_l1", tuya.valueConverter.utf16BEHexString],
[130, "cover_name_l2", tuya.valueConverter.utf16BEHexString],
[131, "cover_name_l3", tuya.valueConverter.utf16BEHexString],
[132, "cover_name_l4", tuya.valueConverter.utf16BEHexString],
[133, "cover_state_l1", tuya.valueConverterBasic.lookup({ open: tuya.enum(0), stop: tuya.enum(1), close: tuya.enum(2) })],
[134, "cover_state_l2", tuya.valueConverterBasic.lookup({ open: tuya.enum(0), stop: tuya.enum(1), close: tuya.enum(2) })],
[135, "cover_state_l3", tuya.valueConverterBasic.lookup({ open: tuya.enum(0), stop: tuya.enum(1), close: tuya.enum(2) })],
[136, "cover_state_l4", tuya.valueConverterBasic.lookup({ open: tuya.enum(0), stop: tuya.enum(1), close: tuya.enum(2) })],
[137, "switch_name_l1", tuya.valueConverter.utf16BEHexString],
[138, "switch_name_l2", tuya.valueConverter.utf16BEHexString],
[139, "switch_name_l3", tuya.valueConverter.utf16BEHexString],
[140, "switch_name_l4", tuya.valueConverter.utf16BEHexString],
[141, "scene_name_l1", tuya.valueConverter.utf16BEHexString],
[142, "scene_name_l2", tuya.valueConverter.utf16BEHexString],
[143, "scene_name_l3", tuya.valueConverter.utf16BEHexString],
[144, "scene_name_l4", tuya.valueConverter.utf16BEHexString],
[145, "scene_name_l5", tuya.valueConverter.utf16BEHexString],
[146, "scene_name_l6", tuya.valueConverter.utf16BEHexString],
[147, "scene_name_l7", tuya.valueConverter.utf16BEHexString],
[148, "scene_name_l8", tuya.valueConverter.utf16BEHexString],
[149, "backlight_switch", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_5m4nchbm"]),
model: "SMKG-2KNL-SD",
vendor: "Tuya",
description: "Smart leakage protector 63a with leakage monitoring",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.switch().setAccess("state", ea.STATE_SET),
e.voltage(),
e.current(),
e.power(),
e.energy(),
e.temperature(),
te.leakageCurrent(),
e.numeric("over_voltage_threshold", ea.STATE_SET).withUnit("V").withValueMin(220).withValueMax(265),
e.numeric("under_voltage_threshold", ea.STATE_SET).withUnit("V").withValueMin(76).withValueMax(240),
e.numeric("over_current_threshold", ea.STATE_SET).withUnit("A").withValueMin(1).withValueMax(63),
e.numeric("leakage_threshold", ea.STATE_SET).withUnit("mA").withValueMin(10).withValueMax(100),
e.numeric("temp_threshold", ea.STATE_SET).withUnit("°C").withValueMin(40).withValueMax(150),
te.circuitBreakerFaults(),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[20, "voltage", tuya.valueConverter.divideBy10],
[18, "current", tuya.valueConverter.divideBy100],
[19, "power", tuya.valueConverter.divideBy10],
[17, "energy", tuya.valueConverter.divideBy100],
[47, "temperature", tuya.valueConverter.raw],
[53, "leakage_current", tuya.valueConverter.raw],
[41, "leakage_threshold", tuya.valueConverter.raw],
[42, "over_voltage_threshold", tuya.valueConverter.raw],
[43, "under_voltage_threshold", tuya.valueConverter.raw],
[44, "over_current_threshold", tuya.valueConverter.raw],
[45, "temp_threshold", tuya.valueConverter.raw],
[9, "faults", tuya.valueConverter.circuitBreakerFaults],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_mvtclclq", "_TZE284_mvtclclq"]),
model: "DS-1450WN",
vendor: "Tuya",
description: "Smart Zigbee Switch with power monitoring",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.switch().withEndpoint("usb_a"),
e.switch().withEndpoint("usb_c"),
e.switch().withEndpoint("plug_1"),
e.switch().withEndpoint("plug_2"),
e
.numeric("countdown_usb_a", ea.STATE_SET)
.withLabel("USB A Countdown")
.withValueMin(0)
.withValueMax(86400)
.withValueStep(1)
.withDescription("Countdown timer for USB A")
.withUnit("s"),
e
.numeric("countdown_usb_c", ea.STATE_SET)
.withLabel("USB C Countdown")
.withValueMin(0)
.withValueMax(86400)
.withValueStep(1)
.withDescription("Countdown timer for USB C")
.withUnit("s"),
e
.numeric("countdown_plug_1", ea.STATE_SET)
.withLabel("Plug 1 Countdown")
.withValueMin(0)
.withValueMax(86400)
.withValueStep(1)
.withDescription("Countdown timer for Plug 1")
.withUnit("s"),
e
.numeric("countdown_plug_2", ea.STATE_SET)
.withLabel("Plug 2 Countdown")
.withValueMin(0)
.withValueMax(86400)
.withValueStep(1)
.withDescription("Countdown timer for Plug 2")
.withUnit("s"),
e.enum("relay_status", ea.STATE_SET, ["memory", "on", "off"]).withLabel("Relay Status").withDescription("Set the Relay Status"),
e
.binary("switch_backlight", ea.STATE_SET, "ON", "OFF")
.withLabel("Switch Backlight")
.withDescription("Enables/disables backlight indicator"),
e.current(),
e.power(),
e.voltage(),
e.produced_energy(),
e.child_lock(),
],
endpoint: (_) => ({
default: 1,
usb_a: 1,
usb_c: 1,
plug_1: 1,
plug_2: 1,
}),
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_usb_a", tuya.valueConverter.onOff],
[2, "state_usb_c", tuya.valueConverter.onOff],
[3, "state_plug_1", tuya.valueConverter.onOff],
[4, "state_plug_2", tuya.valueConverter.onOff],
[7, "countdown_usb_a", tuya.valueConverter.countdown],
[8, "countdown_usb_c", tuya.valueConverter.countdown],
[9, "countdown_plug_1", tuya.valueConverter.countdown],
[10, "countdown_plug_2", tuya.valueConverter.countdown],
[14, "relay_status", tuya.valueConverterBasic.lookup({ memory: tuya.enum(0), on: tuya.enum(1), off: tuya.enum(2) })],
[16, "switch_backlight", tuya.valueConverter.onOff],
[21, "current", tuya.valueConverter.divideBy10],
[22, "power", tuya.valueConverter.divideBy10],
[23, "voltage", tuya.valueConverter.divideBy10],
[105, "produced_energy", tuya.valueConverter.divideBy10],
[106, "child_lock", tuya.valueConverter.lockUnlock],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_vjpaih9f", "_TZE284_vjpaih9f"]),
model: "TS0601_thermostat_14",
vendor: "AVATTO",
description: "Thermostatic radiator valve",
extend: [tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true, timeStart: "1970" })],
exposes: [
e
.climate()
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withPreset(["manual", "schedule", "eco", "comfort", "frost_protection", "holiday", "off"])
.withRunningState(["idle", "heat"], ea.STATE)
.withLocalTemperatureCalibration(-9.5, 9.5, 0.5, ea.STATE_SET),
e.child_lock(),
e.battery(),
e.battery_low(),
e.window_detection(),
...tuya.exposes.scheduleAllDays(ea.STATE_SET, "HH:MM/C HH:MM/C HH:MM/C HH:MM/C HH:MM/C HH:MM/C"),
e.eco_temperature().withValueMin(5).withValueMax(35).withValueStep(0.5),
e.comfort_temperature().withValueMin(5).withValueMax(35).withValueStep(0.5),
e.holiday_temperature().withValueMin(5).withValueMax(35).withValueStep(0.5),
e.binary("window_open", ea.STATE, "OPEN", "CLOSE"),
e.binary("frost_protection", ea.STATE_SET, "ON", "OFF"),
e.binary("scale_protection", ea.STATE_SET, "ON", "OFF"),
e.numeric("error", ea.STATE).withDescription('If NTC is damaged, "Er" will be on the TRV display.'),
e.binary("boost_heating", ea.STATE_SET, "ON", "OFF").withDescription("Boost Heating: the device will enter the boost heating mode."),
],
meta: {
tuyaDatapoints: [
[
2,
"preset",
tuya.valueConverterBasic.lookup({
manual: tuya.enum(0),
schedule: tuya.enum(1),
eco: tuya.enum(2),
comfort: tuya.enum(3),
frost_protection: tuya.enum(4),
holiday: tuya.enum(5),
off: tuya.enum(6),
}),
],
[3, "running_state", tuya.valueConverterBasic.lookup({ idle: 0, heat: 1 })],
[4, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[5, "local_temperature", tuya.valueConverter.divideBy10],
[6, "battery", tuya.valueConverter.raw],
[7, "child_lock", tuya.valueConverter.lockUnlock],
[14, "window_detection", tuya.valueConverter.onOff],
[15, "window_open", tuya.valueConverterBasic.lookup({ CLOSE: 0, OPEN: 1 })],
[21, "holiday_temperature", tuya.valueConverter.divideBy10],
[28, "schedule_monday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(1)],
[29, "schedule_tuesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(2)],
[30, "schedule_wednesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(3)],
[31, "schedule_thursday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(4)],
[32, "schedule_friday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(5)],
[33, "schedule_saturday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(6)],
[34, "schedule_sunday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(7)],
[35, "battery_low", tuya.valueConverter.errorOrBatteryLow],
[36, "frost_protection", tuya.valueConverter.onOff],
[47, "local_temperature_calibration", tuya.valueConverter.divideBy10],
[103, "eco_temperature", tuya.valueConverter.divideBy10],
[104, "comfort_temperature", tuya.valueConverter.divideBy10],
[105, "frost_protection_temperature", tuya.valueConverter.divideBy10],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_snfdqllf"]),
model: "AETZ01_AC",
vendor: "Tuya",
description: "Smart air conditioner",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.binary("state", ea.STATE_SET, "ON", "OFF").withDescription("Turn AC ON/OFF"),
e
.climate()
.withLocalTemperature(ea.STATE)
.withSetpoint("current_heating_setpoint", 16, 30, 1, ea.STATE_SET)
.withSystemMode(["cool", "dry", "fan_only"], ea.STATE_SET)
.withFanMode(["low", "medium", "high", "auto"], ea.STATE_SET)
.withSwingMode(["off", "on"], ea.STATE_SET),
e.binary("sleep", ea.STATE_SET, "ON", "OFF").withDescription("Sleep Mode"),
e.binary("turbo", ea.STATE_SET, "ON", "OFF").withDescription("Turbo Mode"),
e.binary("quiet", ea.STATE_SET, "ON", "OFF").withDescription("Quiet Mode"),
e.power(),
e.energy(),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[30, "swing_mode", tuya.valueConverter.onOff],
[2, "current_heating_setpoint", tuya.valueConverter.raw],
[3, "local_temperature", tuya.valueConverter.raw],
[4, "system_mode", tuya.valueConverterBasic.lookup({ cool: tuya.enum(0), dry: tuya.enum(1), fan_only: tuya.enum(2) })],
[5, "fan_mode", tuya.valueConverterBasic.lookup({ low: tuya.enum(0), medium: tuya.enum(1), high: tuya.enum(2), auto: tuya.enum(3) })],
[25, "sleep", tuya.valueConverter.onOff],
[102, "turbo", tuya.valueConverter.onOff],
[103, "quiet", tuya.valueConverter.onOff],
[116, "power", tuya.valueConverter.raw],
[117, "energy", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_gyzlwu5q"]),
model: "228WZH",
vendor: "Tuya",
description: "Smoke detector with temperature and humidity sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.smoke(),
e.temperature(),
e.humidity(),
tuya.exposes.batteryState(),
e.enum("self_test", ea.STATE, ["Checking", "Check success", "Check failure"]).withDescription("Test button status"),
e.enum("silence", ea.STATE_SET, ["ON"]).withDescription("Silence alarm"),
e.text("version", ea.STATE_GET).withDescription("Device version"),
],
meta: {
// All datapoints go in here
tuyaDatapoints: [
[1, "smoke", tuya.valueConverter.trueFalse0],
[9, "self_test", tuya.valueConverterBasic.lookup({ checking: 0, check_success: 1, check_failure: 2 })],
[14, "battery_state", tuya.valueConverter.batteryState],
[16, "silence", tuya.valueConverter.onOff],
[23, "temperature", tuya.valueConverter.divideBy10],
[24, "humidity", tuya.valueConverter.raw],
[103, "version", tuya.valueConverter.raw], // maybe!?
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_4cqhd2ha", "_TZE200_8ply8mjj"]),
model: "TZE284_4cqhd2ha",
vendor: "Girier",
description: "Vibration sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
whiteLabel: [tuya.whitelabel("Conecto", "COZIGVS", "Vibration sensor", ["_TZE200_8ply8mjj"])],
exposes: [
e.vibration(),
e
.numeric("sensitivity", ea.STATE)
.withValueMin(0)
.withValueMax(2)
.withDescription("Sensitivity of the sensor (single press the button when muted to switch between" +
" low (one beep), medium (two beeps) and max (three beeps))"),
e.text("buzzer_mute", ea.STATE).withDescription("ON when buzzer is muted (double press the button on device to toggle)"),
],
meta: {
tuyaDatapoints: [
[1, "vibration", tuya.valueConverter.trueFalse1],
[101, "sensitivity", tuya.valueConverter.raw],
[103, "buzzer_mute", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_fodv6bkr"]),
model: "RM28-LE",
vendor: "Ronco",
description: "Zigbee roller shade motor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
options: [exposes.options.invert_cover()],
exposes: [
te.coverPosition(),
te.slowMode(),
te.motorDirection(),
e.binary("auto_power", ea.STATE_SET, true, false).withDescription("Auto-complete open/close when curtain is manually pulled"),
te.motorState(),
e.enum("countdown", ea.STATE_SET, ["cancel", "1h", "2h", "3h", "4h"]).withDescription("Countdown timer to trigger open/close"),
e.numeric("countdown_left", ea.STATE).withUnit("s").withDescription("Remaining countdown time in seconds"),
e.numeric("time_total", ea.STATE).withUnit("ms").withDescription("Full travel time in ms, populated after calibration"),
e
.enum("situation_set", ea.STATE, ["fully_open", "fully_close"])
.withDescription("Whether motor treats 100% as fully open or fully closed"),
e.binary("motor_fault", ea.STATE, true, false).withDescription("Motor hardware fault"),
e.battery(),
te.coverLimit(),
te.favoritePosition(),
te.clickControl(),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[2, "position", tuya.valueConverter.coverPosition],
[3, "position", tuya.valueConverter.coverPosition],
[4, "slow_mode", tuya.valueConverter.onOffEnumOn1],
[5, "motor_direction", tuya.valueConverter.tubularMotorDirection],
[6, "auto_power", tuya.valueConverter.onOff],
[7, "motor_state", tuya.valueConverter.motorState],
[
8,
"countdown",
tuya.valueConverterBasic.lookup({
cancel: tuya.enum(0),
"1h": tuya.enum(1),
"2h": tuya.enum(2),
"3h": tuya.enum(3),
"4h": tuya.enum(4),
}),
],
[9, "countdown_left", tuya.valueConverter.raw],
[10, "time_total", tuya.valueConverter.raw],
[
11,
"situation_set",
tuya.valueConverterBasic.lookup({
fully_open: tuya.enum(0),
fully_close: tuya.enum(1),
}),
],
[12, "motor_fault", { from: (v) => v !== 0 }],
[13, "battery", tuya.valueConverter.raw],
[16, "cover_limit", tuya.valueConverter.coverLimit],
[19, "favorite_position", tuya.valueConverter.raw],
[20, "click_control", tuya.valueConverter.clickControl],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_iwyqtclw"]),
model: "M9Pro",
vendor: "Tuya",
description: "Smart 4 gang switch, curtain, smart light or scene. 1x thermostat control",
exposes: [
...[1, 2, 3, 4].map((i) => e.switch().withEndpoint(`l${i}`).setAccess("state", ea.STATE_SET)),
...[1, 2, 3].map((i) => e.enum("mode", ea.STATE_SET, ["switch", "scene", "smart_light", "curtain"]).withEndpoint(`l${i}`).withDescription(`Switch ${i} mode`)),
e
.enum("mode", ea.STATE_SET, ["switch", "scene", "smart_light", "curtain", "thermostat"])
.withEndpoint("l4")
.withDescription("Switch 4 mode"),
...[1, 2, 3, 4].map((i) => e.text("name", ea.STATE_SET).withEndpoint(`l${i}`).withDescription(`Name for Switch ${i} (max 8 chars displayed)`)),
...[1, 2, 3, 4].map((i) => e.text("scene_name", ea.STATE_SET).withEndpoint(`l${i}`).withDescription(`Scene name for switch ${i} (max 8 chars displayed)`)),
...[1, 2, 3, 4].map((i) => e.text("dimmer_name", ea.STATE_SET).withEndpoint(`l${i}`).withDescription(`Smart Light name for switch ${i} (max 8 chars displayed)`)),
...[1, 2, 3, 4].map((i) => exposes.binary("dimmer_switch", ea.STATE_SET, "ON", "OFF").withEndpoint(`l${i}`).withDescription(`Smart Light - toggle switch ${i}`)),
...[1, 2, 3, 4].map((i) => e
.numeric("brightness", ea.STATE_GET)
.withEndpoint(`l${i}`)
.withValueMin(0)
.withValueMax(254)
.withDescription(`Brightness for endpoint ${i} (Read Only)`)),
...[1, 2, 3, 4].map((i) => e
.numeric("color_temp", ea.STATE_GET)
.withEndpoint(`l${i}`)
.withValueMin(0)
.withValueMax(254)
.withDescription(`Color temp for endpoint ${i} (Read Only)`)),
...[1, 2, 3, 4].map((i) => e.text("curtain_name", ea.STATE_SET).withEndpoint(`l${i}`).withDescription(`Curtain name for switch ${i} (max 8 chars displayed)`)),
...[1, 2, 3, 4].map((i) => exposes.binary("curtain_switch", ea.STATE_SET, "ON", "OFF").withEndpoint(`l${i}`).withDescription(`Curtain - toggle switch ${i}`)),
...[1, 2, 3, 4].map((i) => e
.numeric("group_id", ea.STATE_SET)
.withEndpoint(`l${i}`)
.withValueMin(1)
.withValueMax(10000)
.withDescription(`Group ID to bind to for switch ${i}.`)),
e.power_on_behavior(["off", "on", "previous"]).withAccess(ea.STATE_SET).withDescription("Whole panel override.").withCategory("config"),
...[1, 2, 3, 4].map((i) => e.power_on_behavior(["off", "on", "previous"]).withAccess(ea.STATE_SET).withEndpoint(`l${i}`)),
exposes.binary("show_weather", ea.STATE_SET, "ON", "OFF").withDescription("Show time and weather (on) or just switch names (off)"),
exposes.binary("backlight", ea.STATE_SET, "ON", "OFF").withDescription("Button LED backlights"),
e.enum("show_screen", ea.STATE_SET, ["motion", "on_press", "on"]).withDescription("Screen display mode"),
// Thermostat - Switch 4 only
exposes.binary("thermostat", ea.STATE_SET, "ON", "OFF").withDescription("Thermostat - toggle switch"),
e.text("thermostat_name", ea.STATE_SET).withDescription("Name for Thermostat (max 8 chars displayed)"),
e
.numeric("current_heating_setpoint", ea.STATE_GET)
.withValueMin(16)
.withValueMax(32)
.withDescription("Thermostat temperature setting (Read Only)"),
e.numeric("fan_mode", ea.STATE_GET).withValueMin(0).withValueMax(3).withDescription("Thermostat fan setting (Read Only)"),
e
.enum("scene_switch", ea.STATE_SET, [
"switch_1",
"switch_2",
"switch_3",
"switch_4",
"switch_5",
"switch_6",
"switch_7",
"switch_8",
"switch_9",
])
.withDescription("Scene Switch"),
e.action(["scene_0", "scene_1", "scene_2", "scene_3", "scene_4", "scene_5", "scene_6", "scene_7", "scene_8"]),
// Set weather
// Temperature will accept decimal values e.g. 0.1 but display will round off to nearest whole number.
// Best to handle rounding in HA before passing to z2m.
e.numeric("temperature_1", ea.STATE_SET).withValueMin(-65).withValueMax(99).withDescription("Temperature").withValueStep(0.1),
// If you need other values to match your weather provider, map them with a template in HA or
// add a z2m 'External Extension' to override tuya.M8ProTuyaWeatherCondition.
e.enum("condition_1", ea.STATE_SET, Object.keys(tuya.M8ProTuyaWeatherCondition)).withDescription("Weather condition"),
],
meta: {
multiEndpoint: true,
disableDefaultResponse: true,
tuyaDatapoints: [
[1, "action", tuya.valueConverter.static("scene_1")],
[2, "action", tuya.valueConverter.static("scene_2")],
[3, "action", tuya.valueConverter.static("scene_3")],
[4, "action", tuya.valueConverter.static("scene_4")],
[17, "action", tuya.valueConverter.static("scene_0")],
[
18,
"mode_l1",
tuya.valueConverterBasic.lookup({
switch: tuya.enum(0),
scene: tuya.enum(1),
smart_light: tuya.enum(2),
curtain: tuya.enum(3),
}),
],
[
19,
"mode_l2",
tuya.valueConverterBasic.lookup({
switch: tuya.enum(0),
scene: tuya.enum(1),
smart_light: tuya.enum(2),
curtain: tuya.enum(3),
}),
],
[
20,
"mode_l3",
tuya.valueConverterBasic.lookup({
switch: tuya.enum(0),
scene: tuya.enum(1),
smart_light: tuya.enum(2),
curtain: tuya.enum(3),
}),
],
[
21,
"mode_l4",
tuya.valueConverterBasic.lookup({
switch: tuya.enum(0),
scene: tuya.enum(1),
smart_light: tuya.enum(2),
curtain: tuya.enum(3),
thermostat: tuya.enum(4),
}),
],
[24, "state_l1", tuya.valueConverter.onOff],
[25, "state_l2", tuya.valueConverter.onOff],
[26, "state_l3", tuya.valueConverter.onOff],
[27, "state_l4", tuya.valueConverter.onOff],
[36, "show_weather", tuya.valueConverter.onOff],
// Screen display modes.
// From Tuya Dev Platform: enum "range": ["relay", "pos", "none"]. These names don't match what's happening on the device
[
37,
"show_screen",
tuya.valueConverterBasic.lookup({
motion: tuya.enum(0), // Screen on/off with motion detection.
on_press: tuya.enum(1), // Disable motion detection. Only show screen on physical button press.
on: tuya.enum(2), // Screen stays on.
}),
],
[38, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum], // whole panel
[39, "power_on_behavior_l1", tuya.valueConverter.powerOnBehaviorEnum],
[40, "power_on_behavior_l2", tuya.valueConverter.powerOnBehaviorEnum],
[41, "power_on_behavior_l3", tuya.valueConverter.powerOnBehaviorEnum],
[42, "power_on_behavior_l4", tuya.valueConverter.powerOnBehaviorEnum],
[101, "backlight", tuya.valueConverter.onOff], // Buttton backlights
[
102,
"scene_switch",
tuya.valueConverterBasic.lookup({
switch_1: tuya.enum(0),
switch_2: tuya.enum(1),
switch_3: tuya.enum(2),
switch_4: tuya.enum(3),
switch_5: tuya.enum(4),
switch_6: tuya.enum(5),
switch_7: tuya.enum(6),
switch_8: tuya.enum(7),
switch_9: tuya.enum(8),
}),
],
// The M9 Pro only displays first 8 chars of names, but state will hold the whole string.
// I prefer that the state always equals what is displayed, so trim name to 8 chars.
// Switch names
[103, "name_l1", convLocal.nameTrunc],
[104, "name_l2", convLocal.nameTrunc],
[105, "name_l3", convLocal.nameTrunc],
[106, "name_l4", convLocal.nameTrunc],
// Scene names
[107, "scene_name_l1", convLocal.nameTrunc],
[108, "scene_name_l2", convLocal.nameTrunc],
[109, "scene_name_l3", convLocal.nameTrunc],
[110, "scene_name_l4", convLocal.nameTrunc],
// Dimmer names
[111, "dimmer_name_l1", convLocal.nameTrunc],
[112, "dimmer_name_l2", convLocal.nameTrunc],
[113, "dimmer_name_l3", convLocal.nameTrunc],
[114, "dimmer_name_l4", convLocal.nameTrunc],
// Curtain names
[115, "curtain_name_l1", convLocal.nameTrunc],
[116, "curtain_name_l2", convLocal.nameTrunc],
[117, "curtain_name_l3", convLocal.nameTrunc],
[118, "curtain_name_l4", convLocal.nameTrunc],
[119, "curtain_switch_l1", tuya.valueConverter.onOff],
[120, "curtain_switch_l2", tuya.valueConverter.onOff],
[121, "curtain_switch_l3", tuya.valueConverter.onOff],
[122, "curtain_switch_l4", tuya.valueConverter.onOff],
[123, "dimmer_switch_l1", tuya.valueConverter.onOff],
[124, "dimmer_switch_l2", tuya.valueConverter.onOff],
[125, "dimmer_switch_l3", tuya.valueConverter.onOff],
[126, "dimmer_switch_l4", tuya.valueConverter.onOff],
[127, "thermostat", tuya.valueConverter.onOff],
[128, "thermostat_name", convLocal.nameTrunc],
],
},
extend: [
// NOTE: M9 Pro only updates the weather condition and temperature on power cycle and once per hour.
tuya.modernExtend.tuyaWeatherForecast({ includeCurrentWeather: true, numberOfForecastDays: 3, correctForNegativeValues: false }),
tuya.modernExtend.tuyaBase({
dp: true,
timeStart: "1970", // needed else date/time doesn't sync with z2m > 2.6.2
}),
m.deviceEndpoints({ endpoints: { l1: 1, l2: 1, l3: 1, l4: 1 } }),
],
fromZigbee: [fzLocal.TS0601_smart_thermostat_command, fzLocal.TS0601_smart_light_brightness, fzLocal.TS0601_smart_light_color_temp],
toZigbee: [tzLocal.TS0601_smart_light_group_id],
},
{
fingerprint: [
{
modelID: "TLSR82xx",
manufacturerName: "TELINK",
applicationVersion: 0,
endpoints: [
{ ID: 1, profileID: 260, deviceID: 0, inputClusters: [0, 3, 1, 6], outputClusters: [4, 5, 4096] },
{ ID: 2, profileID: 260, deviceID: 0, inputClusters: [0, 3, 6], outputClusters: [] },
],
},
],
model: "TLSR82xx_2btn_remote",
vendor: "Telink",
description: "2 button remote",
fromZigbee: [fzLocal.TLSR82xxAction, fz.battery],
toZigbee: [],
configure: async (device, coordinatorEndpoint, logger) => {
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
},
exposes: [e.battery(), e.action(["on_1", "off_1", "on_2", "off_2"])],
},
{
fingerprint: tuya.fingerprint("ZP-301Z", ["_TZE284_d4h8j2n6", "B3876M9"]),
model: "ZP-301Z",
vendor: "Arteco",
description: "PIR motion sensor light with night light function",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence().withDescription("Human presence detected"),
e.numeric("battery_value", ea.STATE).withDescription("battery value in %").withUnit("%"),
e.illuminance(),
e
.numeric("brightness_value", ea.STATE_SET)
.withDescription("When the light brightness is activated after the lights are turned on")
.withUnit("%")
.withValueMin(5)
.withValueMax(100)
.withValueStep(1),
e
.numeric("presence_time", ea.STATE_SET)
.withDescription("How long to wait before turning on the lights after detecting a person and meeting the light conditions")
.withValueMin(0)
.withValueMax(60)
.withUnit("s")
.withValueStep(1),
e
.numeric("presence_delay", ea.STATE_SET)
.withDescription("How long after no one is detected will the lights turn off")
.withValueMin(5)
.withValueMax(120)
.withUnit("s")
.withValueStep(1),
e
.numeric("illuminance_trigger", ea.STATE_SET)
.withDescription("Detection is only allowed when the illuminance is less than the current value.")
.withValueMin(0)
.withValueMax(10000)
.withValueStep(1),
e
.numeric("detection_cycle", ea.STATE_SET)
.withDescription("How often is the battery level and illuminance detected")
.withValueMin(10)
.withValueMax(1200)
.withValueStep(5)
.withUnit("s"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse1],
[14, "battery_value", tuya.valueConverter.raw],
[20, "illuminance", tuya.valueConverter.raw],
[100, "brightness_value", tuya.valueConverter.raw],
[101, "illuminance_trigger", tuya.valueConverter.raw],
[102, "presence_time", tuya.valueConverter.raw],
[103, "presence_delay", tuya.valueConverter.raw],
[104, "detection_cycle", tuya.valueConverter.raw],
],
},
},
{
zigbeeModel: ["ZG-204ZQ"],
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_p9zbdqgs"]),
model: "ZG-204ZQ",
vendor: "HOBEIAN",
description: "PIR temperature&humidity sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence(),
e.illuminance(),
e.temperature(),
e.humidity(),
tuya.exposes.temperatureUnit(),
tuya.exposes.temperatureCalibration(),
tuya.exposes.humidityCalibration(),
e.battery(),
e
.numeric("fading_time", ea.STATE_SET)
.withValueMin(0)
.withValueMax(28800)
.withValueStep(1)
.withUnit("s")
.withDescription("Motion keep time"),
e.binary("indicator", ea.STATE_SET, "ON", "OFF").withDescription("LED indicator mode"),
e
.numeric("illuminance_interval", ea.STATE_SET)
.withValueMin(1)
.withValueMax(720)
.withValueStep(1)
.withUnit("minutes")
.withDescription("Light sensing sampling(refresh and update only while active)"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse1],
[106, "illuminance", tuya.valueConverter.raw],
[102, "fading_time", tuya.valueConverter.raw],
[108, "indicator", tuya.valueConverter.onOff],
[110, "battery", tuya.valueConverter.raw],
[111, "temperature", tuya.valueConverter.divideBy10],
[101, "humidity", tuya.valueConverter.raw],
[109, "temperature_unit", tuya.valueConverter.temperatureUnit],
[105, "temperature_calibration", tuya.valueConverter.divideBy10],
[104, "humidity_calibration", tuya.valueConverter.raw],
[107, "illuminance_interval", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_eqpaxqdv"]),
model: "PIMS3028",
vendor: "KnockautX",
description: "Cover plug-in receiver multi STAK 3/STAS 3",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.enum("state", ea.STATE_SET, ["open", "stop", "close", "lock", "unlock"]).withDescription("Control commands"),
te.coverPosition(),
e.enum("mode", ea.STATE_SET, ["up", "up_delete", "remove_up_down"]).withDescription("Set mode"),
e.enum("control_back", ea.STATE_SET, ["forward", "back"]).withDescription("Set the motor running direction"),
e.binary("auto_power", ea.STATE_SET, true, false).withDescription("Add wireless remote"),
e.enum("work_state", ea.STATE, ["opening", "closing", "123"]).withDescription("Work state"),
e.numeric("time_total", ea.STATE).withValueMin(0).withValueMax(600).withUnit("s").withDescription("Total travel time"),
e.enum("situation_set", ea.STATE, ["fully_open", "fully_close"]).withDescription("Situation of the blinds"),
e.binary("fault", ea.STATE, true, false).withDescription("Motor fault"),
te.coverLimit(),
te.favoritePosition(),
e
.numeric("angle_horizontal", ea.STATE_SET)
.withValueMin(0)
.withValueMax(100)
.withValueStep(25)
.withUnit("°")
.withDescription("Set the angle"),
e.enum("calibration", ea.STATE_SET, ["start", "end"]).withDescription("Calibrate the travel limits"),
e.numeric("quick_calibration", ea.STATE_SET).withValueMin(0).withValueMax(900).withUnit("s").withDescription("Set quick calibration"),
e.binary("switch", ea.STATE_SET, true, false).withDescription("Trigger best position"),
e.enum("reset", ea.STATE_SET, ["reset"]).withDescription("Trigger factory reset"),
],
meta: {
tuyaDatapoints: [
[
1,
"state",
tuya.valueConverterBasic.lookup({
open: tuya.enum(0),
stop: tuya.enum(1),
close: tuya.enum(2),
lock: tuya.enum(3),
unlock: tuya.enum(4),
}),
],
[2, "position", tuya.valueConverter.coverPosition],
[3, "position", tuya.valueConverter.coverPositionInverted],
[4, "mode", tuya.valueConverterBasic.lookup({ up: tuya.enum(0), up_delete: tuya.enum(1), remove_up_down: tuya.enum(2) })],
[5, "control_back", tuya.valueConverterBasic.lookup({ forward: tuya.enum(0), back: tuya.enum(1) })],
[6, "auto_power", tuya.valueConverter.raw],
[7, "work_state", tuya.valueConverterBasic.lookup({ opening: tuya.enum(0), closing: tuya.enum(1), 123: tuya.enum(2) })],
[10, "time_total", tuya.valueConverter.raw],
[11, "situation_set", tuya.valueConverterBasic.lookup({ fully_open: tuya.enum(0), fully_close: tuya.enum(1) })],
[12, "fault", tuya.valueConverter.raw],
[16, "cover_limit", tuya.valueConverter.coverLimit],
[19, "favorite_position", tuya.valueConverter.raw],
[21, "angle_horizontal", tuya.valueConverter.raw],
[101, "calibration", tuya.valueConverterBasic.lookup({ start: tuya.enum(0), end: tuya.enum(1) })],
[102, "quick_calibration", tuya.valueConverter.raw],
[103, "switch", tuya.valueConverter.raw],
[104, "reset", tuya.valueConverterBasic.lookup({ reset: tuya.enum(0) })],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_ajuasrmx", "_TZE200_hyhl5y36", "_TZE284_ozf4e02o"]),
model: "MSA201Z",
vendor: "Merrytek",
description: "24 GHz human presence sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.enum("state", ea.STATE, ["absence", "presence", "disabled"]).withLabel("Status"),
e.presence(),
e.enum("current_status", ea.STATE, ["approaching", "departing", "clear"]),
e.illuminance().withLabel("Luminance"),
e
.numeric("hold_delay_time", ea.STATE_SET)
.withUnit("s")
.withValueMin(0)
.withValueMax(300)
.withValueStep(1)
.withCategory("config")
.withDescription(`Delay (seconds) before switching from Presence to Absence after no motion is detected.
Recommended ≥ 15s to avoid premature Absence switching.`),
e
.enum("sensitivity", ea.STATE_SET, ["low", "medium", "high"])
.withCategory("config")
.withDescription(`
Sensitivity of human presence detection:
• High: suitable for environments with minimal motion interference, such as meeting rooms or cloakrooms away from windows.
• Medium: suitable for most scenarios, such as living rooms or restaurants.
• Low: suitable for environments with some minor motion interference, such as bedrooms.
`),
e
.numeric("trigger_distance", ea.STATE_SET)
.withUnit("m")
.withValueMin(0.5)
.withValueMax(4)
.withValueStep(0.5)
.withCategory("config")
.withDescription("Distance within which the sensor detects motion, adjustable from 0.5m to 4m, in 0.5m steps."),
e
.numeric("forbidden_area", ea.STATE_SET)
.withUnit("m")
.withValueMin(0)
.withValueMax(1.8)
.withValueStep(0.1)
.withCategory("config")
.withDescription("Distance from the sensor within which motion is ignored to prevent false detection (0-1.8m)."),
e
.enum("ai_self_learning", ea.SET, ["AI self-learning"])
.withLabel("AI environment self-learning")
.withCategory("config")
.withDescription(`AI self-learning to help the sensor ignore non-human motion in the environment.
The learning process takes 1 minute, during which the sensor will blink. Ensure the area is completely empty
during this time.`),
e
.enum("fast_setting", ea.STATE_SET, ["small", "medium", "large"])
.withCategory("config")
.withDescription(`
Fast setting based on the size of the space:
• Small: area less than 16m², e.g., tea room, bathroom.
• Medium: area 16–25m², e.g., private office, public toilet, kitchen.
• Large: area greater than 25m², e.g., open office, large conference room, living room.
`),
e
.binary("indicator", ea.STATE_SET, "ON", "OFF")
.withLabel("LED indicator")
.withCategory("config")
.withDescription("LED indicator providing a visual signal when motion is detected or the sensor changes state."),
e
.enum("sensor_mode", ea.STATE_SET, ["presence", "motion"])
.withCategory("config")
.withDescription(`
Operating mode of the radar sensor:
• Presence: detects micro-movements for continuous occupancy detection.
• Motion: detects larger movements while ignoring small activity to reduce false triggers.
`),
e
.binary("single_mode", ea.STATE_SET, "ON", "OFF")
.withCategory("config")
.withDescription(`Suitable for spaces where only one person is present at a time. The sensor maintains Presence
while the person is within range and automatically switches to Absence 15 seconds after they leave.`),
e
.binary("absence_circling_report", ea.STATE_SET, "ON", "OFF")
.withCategory("config")
.withDescription(`Periodic reporting of the Absence state after the sensor switches to Absence, it will
send regular status updates automatically.`),
e
.numeric("absence_circling_interval", ea.STATE_SET)
.withUnit("min")
.withValueMin(2)
.withValueMax(30)
.withValueStep(1)
.withCategory("config")
.withDescription("Interval (minutes) between periodic Absence reports after switching to Absence."),
// Internal DP; for internal use only, not exposed in UI
/*
e
.binary("status_flip", ea.STATE_SET, "ON", "OFF")
.withCategory("config")
.withDescription("Invert reported presence state. When enabled, Presence becomes Absence and vice-versa."),
*/
e
.binary("find_device", ea.STATE_SET, "ON", "OFF")
.withCategory("config")
.withDescription("Indicator LED flashes to help locate the sensor."),
e.binary("enable_sensor", ea.STATE_SET, "ON", "OFF").withCategory("config").withDescription("Enable or disable the sensor."),
e
.enum("factory_reset", ea.SET, ["factory reset"])
.withCategory("config")
.withDescription("Restores factory defaults, removing all customized settings."),
e
.enum("lux_mode", ea.STATE_SET, ["threshold", "report"])
.withCategory("config")
.withDescription(`
Lux sensor operation mode:
• Threshold: triggers based on a fixed daylight level.
• Report: periodically reports light levels using time-based or change-based intervals.
`),
e
.numeric("daylight_threshold", ea.STATE_SET)
.withUnit("lux")
.withValueMin(1)
.withValueMax(3000)
.withValueStep(1)
.withCategory("config")
.withDescription(`Lux level defining sufficient natural light (daylight), applicable only when Lux mode is set to
Threshold.`),
e
.enum("lux_report_mode", ea.STATE_SET, ["timed", "difference"])
.withCategory("config")
.withDescription(`
Reporting style when Lux mode is set to Report:
• Timed: sends light level updates at fixed intervals.
• Difference: (Not implemented on this device) reports only when lux changes exceed a set threshold.
`),
e
.numeric("lux_timed_interval", ea.STATE_SET)
.withUnit("s")
.withValueMin(5)
.withValueMax(3600)
.withValueStep(5)
.withCategory("config")
.withDescription("Interval (seconds) for timed lux reports when Lux report mode is set to Timed."),
e
.numeric("lux_difference_threshold", ea.STATE_SET)
.withUnit("lux")
.withValueMin(1)
.withValueMax(2000)
.withValueStep(1)
.withCategory("config")
.withDescription(`
Lux change required to trigger a report when Lux report mode is set to Difference.
Note: This device does not support Difference reporting, so this setting has no effect.
`),
e
.numeric("lux_difference_value", ea.STATE)
.withCategory("diagnostic")
.withDescription(`
Reported lux value for Difference mode.
Note: This device does not support Difference reporting, so this setting has no effect.
`),
e
.text("interference_positions", ea.STATE)
.withCategory("diagnostic")
.withDescription("List of distances (m) where non-human interference was detected."),
e
.enum("home_environment", ea.STATE, ["normal", "slight", "strong", "severe"])
.withCategory("diagnostic")
.withDescription("Environmental interference level detected by the sensor."),
],
meta: {
tuyaDatapoints: [
[
1,
null,
{
from: (v) => {
switch (v) {
case 0:
return { state: "absence", presence: false };
case 1:
return { state: "presence", presence: true };
case 2:
return { state: "disabled", presence: false };
default:
console.warn("Unknown DP1 value:", v);
return { state: "absence", presence: false };
}
},
to: (value) => {
switch (value.state) {
case "presence":
return 1;
case "absence":
return 0;
case "disabled":
return 2;
default:
return 0;
}
},
},
],
[2, "trigger_distance", tuya.valueConverter.divideBy10],
[101, "illuminance", tuya.valueConverter.raw],
[102, "lux_difference_value", tuya.valueConverter.raw],
[
103,
"ai_self_learning",
{
from: (v) => ({ 0: "end", 4: "learning" })[v],
to: () => 1,
},
],
[
104,
"factory_reset",
{
from: () => "idle",
to: (v) => ({ "factory reset": 1 })[v] || 0,
},
],
[
105,
"fast_setting",
{
from: (v) => ({ 1: "large", 2: "medium", 3: "small" })[v] ?? "medium",
to: (v) => ({ Small: 3, Medium: 2, Large: 1 })[v] ?? 2,
},
],
[107, "indicator", tuya.valueConverter.onOff],
[106, "hold_delay_time", tuya.valueConverter.raw],
[
108,
"current_status",
tuya.valueConverterBasic.lookup({
approaching: tuya.enum(0),
departing: tuya.enum(1),
clear: tuya.enum(2),
}),
],
[109, "enable_sensor", tuya.valueConverter.onOff],
[
110,
"sensitivity",
tuya.valueConverterBasic.lookup({
low: tuya.enum(3),
medium: tuya.enum(2),
high: tuya.enum(1),
}),
],
//[111, 'presence', tuya.valueConverter.trueFalse],
[112, "status_flip", tuya.valueConverter.onOff],
[113, "interference_positions", tuya.valueConverter.raw],
[114, "forbidden_area", tuya.valueConverter.divideBy10],
[115, "daylight_threshold", tuya.valueConverter.raw],
[
116,
"sensor_mode",
{
from: (v) => ({ 1: "presence", 2: "motion" })[v] ?? "unknown",
to: (v) => ({ presence: 1, motion: 2 })[v] ?? 1,
},
],
[117, "single_mode", tuya.valueConverter.onOff],
[118, "find_device", tuya.valueConverter.onOff],
[
119,
"lux_mode",
tuya.valueConverterBasic.lookup({
threshold: tuya.enum(0),
report: tuya.enum(1),
}),
],
[
120,
"lux_report_mode",
tuya.valueConverterBasic.lookup({
timed: tuya.enum(0),
difference: tuya.enum(1),
}),
],
[121, "lux_difference_threshold", tuya.valueConverter.raw],
[122, "lux_timed_interval", tuya.valueConverter.raw],
[123, "absence_circling_report", tuya.valueConverter.onOff],
[124, "absence_circling_interval", tuya.valueConverter.raw],
[
125,
"home_environment",
{
from: (v) => ({ 0: "normal", 1: "slight", 2: "strong", 3: "severe" })[v] ?? "unknown",
to: (v) => ({ normal: 0, slight: 1, strong: 2, severe: 3 })[v] ?? 0,
},
],
],
},
},
{
fingerprint: [{ modelID: "Arteco", manufacturerName: "A89G12C" }],
model: "ZS-SF00",
vendor: "Arteco",
description: "Soil fertility sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.enum("water_warning", ea.STATE, ["none", "alarm"]).withDescription("Water shortage warning"),
e.enum("soil_fertility_warning", ea.STATE, ["none", "alarm"]).withDescription("Soil fertility warning"),
e.battery(),
e.soil_moisture(),
e
.numeric("soil_fertility", ea.STATE)
.withUnit("μS/cm")
.withValueMin(0)
.withValueMax(5000)
.withDescription("Soil fertility value,between 0-5000"),
e.temperature(),
e.humidity(),
e.illuminance(),
tuya.exposes.soilSampling(),
tuya.exposes.soilCalibration(),
tuya.exposes.humidityCalibration(),
e
.numeric("illuminance_calibration", ea.STATE_SET)
.withValueMin(-15)
.withValueMax(1000)
.withValueStep(1)
.withUnit("lux")
.withDescription("Illuminance calibration"),
tuya.exposes.temperatureCalibration(),
tuya.exposes.soilWarning(),
e
.numeric("soil_fertility_warning_setting", ea.STATE_SET)
.withValueMin(100)
.withValueMax(5000)
.withValueStep(1)
.withUnit("μS/cm")
.withDescription("When the soil fertility value is lower than what threshold should a warning be issued"),
],
meta: {
tuyaDatapoints: [
[3, "soil_moisture", tuya.valueConverter.raw],
[5, "temperature", tuya.valueConverter.divideBy10],
[14, "battery", tuya.valueConverter.raw],
[101, "humidity", tuya.valueConverter.raw],
[102, "illuminance", tuya.valueConverter.raw],
[103, "soil_sampling", tuya.valueConverter.raw],
[104, "soil_calibration", tuya.valueConverter.raw],
[105, "humidity_calibration", tuya.valueConverter.raw],
[106, "illuminance_calibration", tuya.valueConverter.raw],
[107, "temperature_calibration", tuya.valueConverter.divideBy10],
[110, "soil_warning", tuya.valueConverter.raw],
[111, "water_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), alarm: tuya.enum(1) })],
[112, "soil_fertility", tuya.valueConverter.raw],
[114, "soil_fertility_warning_setting", tuya.valueConverter.raw],
[115, "soil_fertility_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), alarm: tuya.enum(1) })],
],
},
},
{
fingerprint: [{ modelID: "TS0601", manufacturerName: "_TZE284_8se38w3c" }],
model: "TZ-ZT01_GA4",
vendor: "Tuya",
description: "Temperature & humidity Sensor with external probe",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.temperature(),
e.numeric("temperature_probe", ea.STATE).withUnit("°C").withDescription("Probe temperature"),
e.humidity(),
tuya.exposes.batteryState(),
],
meta: {
tuyaDatapoints: [
[1, "temperature", tuya.valueConverter.divideBy10],
[2, "humidity", tuya.valueConverter.raw],
[3, "battery_state", tuya.valueConverter.batteryState],
[38, "temperature_probe", tuya.valueConverter.divideBy10],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_qcasmfan"]),
model: "GSKS-ZB",
vendor: "GISE",
description: "Smoke sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [e.battery(), e.tamper(), e.smoke()],
meta: {
tuyaDatapoints: [
[1, "smoke", tuya.valueConverter.trueFalse0],
[4, "tamper", tuya.valueConverter.raw],
[15, "battery", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_byzdayie"]),
model: "DDS238-1-Z1",
vendor: "TOMZN",
description: "Single phase DIN-rail energy meter with switch function",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [tuya.exposes.switch(), e.current(), e.power(), e.voltage(), e.energy()],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[17, "energy", tuya.valueConverter.divideBy100],
[18, "current", tuya.valueConverter.divideBy1000],
[19, "power", tuya.valueConverter.divideBy10],
[20, "voltage", tuya.valueConverter.divideBy10],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_fphxkxue"]),
model: "ZVL-PRO",
vendor: "Nova Digital",
description: "Water walve",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.switch().setAccess("state", ea.STATE_SET),
e.battery(),
e.numeric("countdown", ea.STATE_SET).withUnit("s").withValueMin(0).withValueMax(86400).withDescription("Countdown time in seconds"),
e.enum("work_state", ea.STATE, ["auto", "manual", "idle"]).withDescription("Current state of operation"),
e.numeric("water_once", ea.STATE).withUnit("L").withDescription("Consumption from the last watering"),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[7, "battery", tuya.valueConverter.raw],
[11, "countdown", tuya.valueConverter.raw],
[12, "work_state", tuya.valueConverter.raw],
[5, "water_once", tuya.valueConverter.raw],
[4, "fault", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_wc2w9t1s"]),
model: "BOT-R9V-ZB",
vendor: "Beok",
description: "Wall-mount thermostat",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e
.climate()
.withSetpoint("current_heating_setpoint", 5, 60, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withLocalTemperatureCalibration(-9.9, 9.9, 0.1, ea.STATE_SET)
.withSystemMode(["off", "heat"], ea.STATE_SET)
.withRunningState(["idle", "heat"], ea.STATE)
.withPreset(["auto", "manual", "eco"]),
e.child_lock(),
e.battery(),
e.enum("valve_state", ea.STATE, ["open", "close"]).withDescription("Valve state (open/close)"),
e.binary("frost", ea.STATE_SET, "ON", "OFF").withDescription("Frost protection mode"),
e
.numeric("deadzone_temperature", ea.STATE_SET)
.withUnit("°C")
.withValueMin(0)
.withValueMax(5)
.withValueStep(0.5)
.withDescription("Temperature deadzone (hysteresis)"),
e
.numeric("min_temperature_limit", ea.STATE_SET)
.withUnit("°C")
.withValueMin(5)
.withValueMax(30)
.withValueStep(0.5)
.withDescription("Minimum temperature limit"),
e
.numeric("max_temperature_limit", ea.STATE_SET)
.withUnit("°C")
.withValueMin(15)
.withValueMax(60)
.withValueStep(0.5)
.withDescription("Maximum temperature limit"),
e
.numeric("eco_temperature", ea.STATE_SET)
.withUnit("°C")
.withValueMin(5)
.withValueMax(30)
.withValueStep(0.5)
.withDescription("Eco mode temperature"),
...tuya.exposes.scheduleAllDays(ea.STATE_SET, "HH:MM/C HH:MM/C HH:MM/C HH:MM/C"),
],
meta: {
tuyaDatapoints: [
[1, "system_mode", tuya.valueConverterBasic.lookup({ heat: true, off: false })],
[
2,
"preset",
tuya.valueConverterBasic.lookup({
auto: tuya.enum(0),
manual: tuya.enum(1),
eco: tuya.enum(2),
}),
],
[10, "frost", tuya.valueConverter.onOff],
[16, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[18, "min_temperature_limit", tuya.valueConverter.divideBy10],
[19, "max_temperature_limit", tuya.valueConverter.divideBy10],
[24, "local_temperature", tuya.valueConverter.divideBy10],
[
36,
null,
{
from: (v) => {
return {
valve_state: v === "open" || v === true || v === 1 ? "open" : "close",
running_state: v === "open" || v === true || v === 1 ? "heat" : "idle",
};
},
},
],
[40, "child_lock", tuya.valueConverter.lockUnlock],
[65, "schedule_monday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(4)],
[66, "schedule_tuesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(4)],
[67, "schedule_wednesday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(4)],
[68, "schedule_thursday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(4)],
[69, "schedule_friday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(4)],
[70, "schedule_saturday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(4)],
[71, "schedule_sunday", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(4)],
[107, "battery", tuya.valueConverter.raw],
[109, "local_temperature_calibration", tuya.valueConverter.divideBy10],
[112, "deadzone_temperature", tuya.valueConverter.divideBy10],
[116, "eco_temperature", tuya.valueConverter.divideBy10],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_roujjevx"]),
model: "TS0601_smart_temperature_switch",
vendor: "Tuya",
description: "Smart temperature switch with manual and automatic modes: heating & cooling",
extend: [
tuya.modernExtend.tuyaBase({
dp: true,
timeStart: "1970",
queryOnDeviceAnnounce: true,
queryOnConfigure: true,
}),
],
exposes: [
e
.numeric("temperature_c", ea.STATE)
.withUnit("℃")
.withDescription("Measured temperature in ℃. Device reports it only when it set ℃ unit.")
.withCategory("diagnostic"),
e.numeric("temperature_f", ea.STATE).withUnit("℉").withDescription("Measured temperature in ℉").withCategory("diagnostic"),
e.switch().withDescription("Turn on/off device connected to output"),
e
.binary("autowork", ea.ALL, "ON", "OFF")
.withDescription("Turn on automatic mode to control constant temperature")
.withCategory("config"),
e
.enum("work_mode", ea.ALL, ["heating", "cooling"])
.withDescription(`
Heating: switch is turned ON until temperature will increase to setpoint (then will turn OFF). When temperature will go down below set temperature range, switch will turn ON again.
Cooling: switch is turned ON until temperature will decrease to setpoint (then will turn OFF). When temperature will go up above set temperature range, switch will turn ON again.
`)
.withCategory("config"),
tuya.exposes.temperatureUnit().withAccess(ea.ALL).withCategory("config"),
e
.numeric("temperature_c_setpoint", ea.ALL)
.withUnit("℃")
.withDescription("Temperature ℃ set point")
.withCategory("config")
.withValueMin(-20)
.withValueMax(102)
.withValueStep(0.5),
e
.numeric("temperature_f_setpoint", ea.ALL)
.withUnit("℉")
.withDescription("Temperature ℉ set point")
.withCategory("config")
.withValueMin(-4)
.withValueMax(221)
.withValueStep(0.5),
e
.numeric("temperature_range", ea.ALL)
.withDescription("The delta between temperature setpoint and measured temperature to trigger heating or cooling")
.withCategory("config")
.withValueMin(1)
.withValueMax(9)
.withValueStep(0.5),
tuya.exposes
.temperatureCalibration()
.withUnit("℉")
.withDescription("Temperature calibration always in ℉")
.withCategory("config")
.withValueMin(-9)
.withValueMax(9)
.withValueStep(1),
e.binary("cooling_delay_switch", ea.ALL, "ON", "OFF").withDescription("Turn on cooling mode delay").withCategory("config"),
e
.numeric("cooling_delay", ea.ALL)
.withUnit("m")
.withDescription("Delay to turn on - only for cooling mode")
.withCategory("config")
.withValueMin(0)
.withValueMax(10)
.withValueStep(1),
tuya.exposes
.countdown()
.withAccess(ea.ALL)
.withDescription("Countdown to turn device on/off after a certain time")
.withCategory("config")
.withValueMax(86400), // 24h
e
.list("schedules", ea.SET, exposes
.composite("schedule", "schedule", ea.SET)
.withFeature(e.binary("enabled", ea.SET, true, false).withDescription("Enable schedule"))
.withFeature(e.enum("work_mode", ea.SET, ["heating", "cooling"]))
.withFeature(e.numeric("temperature_f", ea.SET).withUnit("℉").withDescription("Target temperature in ℉"))
.withFeature(exposes
.composite("start", "start", ea.SET)
.withFeature(e.numeric("hour", ea.SET).withValueMin(0).withValueMax(23).withValueStep(1))
.withFeature(e.numeric("minute", ea.SET).withValueMin(0).withValueMax(59).withValueStep(1)))
.withFeature(exposes
.composite("end", "end", ea.SET)
.withFeature(e.numeric("hour", ea.SET).withValueMin(0).withValueMax(23).withValueStep(1))
.withFeature(e.numeric("minute", ea.SET).withValueMin(0).withValueMax(59).withValueStep(1)))
.withFeature(exposes
.composite("week_days", "week_days", ea.SET)
.withFeature(e.binary("sunday", ea.SET, true, false))
.withFeature(e.binary("monday", ea.SET, true, false))
.withFeature(e.binary("tuesday", ea.SET, true, false))
.withFeature(e.binary("wednesday", ea.SET, true, false))
.withFeature(e.binary("thursday", ea.SET, true, false))
.withFeature(e.binary("friday", ea.SET, true, false))
.withFeature(e.binary("saturday", ea.SET, true, false))))
.withDescription("Schedules for automatic mode work at certain week day and time")
.withCategory("config")
.withLengthMax(3),
],
meta: {
tuyaDatapoints: [
[2, "state", tuya.valueConverter.onOffNotStrict],
[4, "countdown", tuya.valueConverter.countdown],
[7, "schedules", tuya.valueConverter.thermostatSchedule],
[8, "work_mode", tuya.valueConverterBasic.lookup({ heating: tuya.enum(0), cooling: tuya.enum(2) })],
[9, "autowork", tuya.valueConverter.onOffNotStrict],
[20, "temperature_unit", tuya.valueConverter.temperatureUnitEnum],
[21, "temperature_f_setpoint", tuya.valueConverter.divideBy10],
[22, "temperature_c_setpoint", tuya.valueConverter.divideBy10],
[27, "temperature_c", tuya.valueConverter.divideBy10],
[28, "temperature_f", tuya.valueConverter.divideBy10],
[29, "temperature_range", tuya.valueConverter.divideBy10],
[30, "temperature_calibration", tuya.valueConverter.raw],
// 50: Fault - not mapped
[55, "cooling_delay", tuya.valueConverter.raw],
[56, "cooling_delay_switch", tuya.valueConverter.onOffNotStrict],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_vceqncho", "_TZE284_who1jxwd"]),
model: "ZIS-01P",
vendor: "Novato",
description: "Dual-tech presence sensor (PIR + radar)",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.occupancy(),
e.illuminance(),
e.battery(),
e
.numeric("presence_distance", ea.STATE_SET)
.withDescription("Maximum detection distance (1=~2m, 2=~4m, 3=~6m)")
.withValueMin(1)
.withValueMax(3)
.withValueStep(1),
e
.numeric("presence_sensitivity", ea.STATE_SET)
.withDescription("Radar presence detection sensitivity (1=Low, 2=Medium, 3=High)")
.withValueMin(1)
.withValueMax(3)
.withValueStep(1),
e.binary("radar_switch", ea.STATE_SET, "ON", "OFF").withDescription("Enable or disable radar detection"),
e
.numeric("pir_sensitivity", ea.STATE_SET)
.withDescription("PIR motion sensor sensitivity (1=Low, 2=Medium, 3=High)")
.withValueMin(1)
.withValueMax(3)
.withValueStep(1),
e
.numeric("delay_time", ea.STATE_SET)
.withUnit("s")
.withDescription("Time delay before reporting no presence (factory default: 30 s)")
.withValueMin(10)
.withValueMax(9600)
.withValueStep(1),
e.binary("led_switch", ea.STATE_SET, "ON", "OFF").withDescription("Enable or disable LED indicator"),
e
.numeric("radar_threshold", ea.STATE_SET)
.withCategory("config")
.withDescription("Radar detection threshold, advanced calibration (factory default: 15; manufacturer-recommended range: 10-15)")
.withValueMin(5)
.withValueMax(255)
.withValueStep(1),
e
.numeric("pir_threshold", ea.STATE_SET)
.withCategory("config")
.withDescription("PIR detection threshold, advanced calibration (factory default: 20)")
.withValueMin(1)
.withValueMax(250)
.withValueStep(1),
],
meta: {
tuyaDatapoints: [
[1, "occupancy", tuya.valueConverter.trueFalse1],
[101, "radar_delay", tuya.valueConverter.raw],
[102, "presence_distance", tuya.valueConverter.raw],
[103, "presence_sensitivity", tuya.valueConverter.raw],
[104, "radar_switch", tuya.valueConverter.onOff],
[105, "pir_sensitivity", tuya.valueConverter.raw],
[106, "delay_time", tuya.valueConverter.raw],
[107, "led_switch", tuya.valueConverter.onOff],
[108, "illuminance", tuya.valueConverter.raw],
[109, "battery", tuya.valueConverter.raw],
[160, "pir_threshold", tuya.valueConverter.raw],
[161, "pir_trigger_pulses", tuya.valueConverter.raw],
[162, "pir_trigger_time", tuya.valueConverter.raw],
[163, "pir_lock_time", tuya.valueConverter.raw],
[164, "radar_threshold", tuya.valueConverter.raw],
[165, "radar_distance_door_test", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("Excellux", ["DHT0001", "DHTA001"]),
model: "DHT0001",
vendor: "Excellux",
whiteLabel: [{ vendor: "Excellux", model: "DHTA001", fingerprint: tuya.fingerprint("Excellux", ["DHTA001"]) }],
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
description: "Temperature and humidity sensor",
exposes: [
e
.enum("temperature_warning", ea.STATE, ["none", "low", "high"])
.withDescription("Temperature warning. Low: temperature is lower than v0 and v1. High: temperature is higher than v0 and v1"),
e
.enum("humidity_warning", ea.STATE, ["none", "low", "high"])
.withDescription("Humidity warning. Low: humidity is lower than v0 and v1. High: humidity is higher than v0 and v1"),
e.battery(),
e.temperature(),
e.humidity(),
e
.numeric("sampling_interval", ea.STATE_SET)
.withValueMin(5)
.withValueMax(1200)
.withValueStep(5)
.withUnit("s")
.withDescription("Sampling interval"),
e
.numeric("temperature_calibration", ea.STATE_SET)
.withValueMin(-2)
.withValueMax(2)
.withValueStep(0.01)
.withUnit("°C")
.withDescription("Temperature calibration"),
e
.numeric("temperature_v0_set", ea.STATE_SET)
.withValueMin(-40)
.withValueMax(85)
.withValueStep(0.01)
.withUnit("°C")
.withDescription("Temperature v0 threshold setting"),
e
.numeric("temperature_v1_set", ea.STATE_SET)
.withValueMin(-40)
.withValueMax(85)
.withValueStep(0.01)
.withUnit("°C")
.withDescription("Temperature v1 threshold setting"),
e
.numeric("humidity_calibration", ea.STATE_SET)
.withValueMin(-10)
.withValueMax(10)
.withValueStep(0.01)
.withUnit("%")
.withDescription("Humidity calibration"),
e
.numeric("humidity_v0_set", ea.STATE_SET)
.withValueMin(0)
.withValueMax(100)
.withValueStep(0.01)
.withUnit("%")
.withDescription("Humidity v0 threshold setting"),
e
.numeric("humidity_v1_set", ea.STATE_SET)
.withValueMin(0)
.withValueMax(100)
.withValueStep(0.01)
.withUnit("%")
.withDescription("Humidity v1 threshold setting"),
],
meta: {
tuyaDatapoints: [
[4, "battery", tuya.valueConverter.raw],
[5, "temperature", tuya.valueConverter.divideBy100],
[101, "sampling_interval", tuya.valueConverter.raw],
[114, "temperature_calibration", tuya.valueConverter.divideBy100],
[115, "temperature_v0_set", tuya.valueConverter.divideBy100],
[116, "temperature_v1_set", tuya.valueConverter.divideBy100],
[117, "temperature_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), low: tuya.enum(1), high: tuya.enum(2) })],
[118, "humidity", tuya.valueConverter.divideBy100],
[119, "humidity_calibration", tuya.valueConverter.divideBy100],
[120, "humidity_v0_set", tuya.valueConverter.divideBy100],
[121, "humidity_v1_set", tuya.valueConverter.divideBy100],
[122, "humidity_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), low: tuya.enum(1), high: tuya.enum(2) })],
],
},
},
{
fingerprint: tuya.fingerprint("Excellux", ["NTCHT01", "NTCHT02", "NTCHT03"]),
model: "ZG-105NTH",
vendor: "Excellux",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
description: "Temperature and humidity sensor with probe",
whiteLabel: [
tuya.whitelabel("Excellux", "ZG-106NTH", "Temperature and humidity sensor with probe", ["NTCHT02"]),
tuya.whitelabel("Excellux", "EZ-L01NTH", "Probe Temperature && Temperature && Humidity with LCD", ["NTCHT03"]),
],
exposes: [
e
.enum("probe_temperature_warning", ea.STATE, ["none", "low", "high"])
.withDescription("Probe temperature sensor warning. Low: temperature is lower than v0 and v1. High: temperature is higher than v0 and v1"),
e
.enum("temperature_warning", ea.STATE, ["none", "low", "high"])
.withDescription("Temperature warning. Low: temperature is lower than v0 and v1. High: temperature is higher than v0 and v1"),
e
.enum("humidity_warning", ea.STATE, ["none", "low", "high"])
.withDescription("Humidity warning. Low: humidity is lower than v0 and v1. High: humidity is higher than v0 and v1"),
e.battery(),
e
.numeric("probe_temperature", ea.STATE)
.withValueMin(-40)
.withValueMax(120)
.withValueStep(0.1)
.withUnit("°C")
.withDescription("Probe temperature sensor"),
e.temperature(),
e.humidity(),
e
.numeric("sampling_interval", ea.STATE_SET)
.withValueMin(5)
.withValueMax(1200)
.withValueStep(5)
.withUnit("s")
.withDescription("Sampling interval"),
e
.numeric("probe_temperature_calibration", ea.STATE_SET)
.withValueMin(-2)
.withValueMax(2)
.withValueStep(0.1)
.withUnit("°C")
.withDescription("Probe temperature sensor calibration"),
e
.numeric("probe_temperature_v0_set", ea.STATE_SET)
.withValueMin(-40)
.withValueMax(125)
.withValueStep(0.1)
.withUnit("°C")
.withDescription("Probe temperature sensor v0 threshold setting"),
e
.numeric("probe_temperature_v1_set", ea.STATE_SET)
.withValueMin(-40)
.withValueMax(125)
.withValueStep(0.1)
.withUnit("°C")
.withDescription("Probe temperature sensor v1 threshold setting"),
e
.numeric("temperature_calibration", ea.STATE_SET)
.withValueMin(-2)
.withValueMax(2)
.withValueStep(0.01)
.withUnit("°C")
.withDescription("Temperature calibration"),
e
.numeric("temperature_v0_set", ea.STATE_SET)
.withValueMin(-40)
.withValueMax(85)
.withValueStep(0.01)
.withUnit("°C")
.withDescription("Temperature v0 threshold setting"),
e
.numeric("temperature_v1_set", ea.STATE_SET)
.withValueMin(-40)
.withValueMax(85)
.withValueStep(0.01)
.withUnit("°C")
.withDescription("Temperature v1 threshold setting"),
e
.numeric("humidity_calibration", ea.STATE_SET)
.withValueMin(-10)
.withValueMax(10)
.withValueStep(0.01)
.withUnit("%")
.withDescription("Humidity calibration"),
e
.numeric("humidity_v0_set", ea.STATE_SET)
.withValueMin(0)
.withValueMax(100)
.withValueStep(0.01)
.withUnit("%")
.withDescription("Humidity v0 threshold setting"),
e
.numeric("humidity_v1_set", ea.STATE_SET)
.withValueMin(0)
.withValueMax(100)
.withValueStep(0.01)
.withUnit("%")
.withDescription("Humidity v1 threshold setting"),
],
meta: {
tuyaDatapoints: [
[1, "probe_temperature", tuya.valueConverter.divideBy10],
[4, "battery", tuya.valueConverter.raw],
[5, "temperature", tuya.valueConverter.divideBy100],
[101, "sampling_interval", tuya.valueConverter.raw],
[108, "probe_temperature_calibration", tuya.valueConverter.divideBy10],
[109, "probe_temperature_v0_set", tuya.valueConverter.divideBy10],
[110, "probe_temperature_v1_set", tuya.valueConverter.divideBy10],
[112, "probe_temperature_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), low: tuya.enum(1), high: tuya.enum(2) })],
[114, "temperature_calibration", tuya.valueConverter.divideBy100],
[115, "temperature_v0_set", tuya.valueConverter.divideBy100],
[116, "temperature_v1_set", tuya.valueConverter.divideBy100],
[117, "temperature_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), low: tuya.enum(1), high: tuya.enum(2) })],
[118, "humidity", tuya.valueConverter.divideBy100],
[119, "humidity_calibration", tuya.valueConverter.divideBy100],
[120, "humidity_v0_set", tuya.valueConverter.divideBy100],
[121, "humidity_v1_set", tuya.valueConverter.divideBy100],
[122, "humidity_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), low: tuya.enum(1), high: tuya.enum(2) })],
],
},
},
{
fingerprint: tuya.fingerprint("Excellux", ["PIRIV01"]),
model: "ZG-104PLV",
vendor: "Excellux",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
description: "PIR motion sensor, vibration sensor, and light sensor",
exposes: [
e.occupancy().withDescription("Presence state, true: motion detected, false: no motion"),
e.binary("vibration", ea.STATE, true, false).withDescription("Vibration state, true: vibration detected, false: no vibration"),
e.enum("illuminance_warning", ea.STATE, ["none", "low", "high"]).withDescription("Illuminance warning level"),
e.battery(),
e.illuminance(),
e
.numeric("vibration_count", ea.STATE)
.withValueMin(0)
.withValueMax(500)
.withValueStep(1)
.withUnit("times")
.withDescription("Vibration count detected by the vibration sensor"),
e
.numeric("sampling_interval", ea.STATE_SET)
.withValueMin(5)
.withValueMax(1200)
.withValueStep(5)
.withUnit("s")
.withDescription("Sampling illuminance interval"),
e
.numeric("vibration_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(50)
.withValueStep(1)
.withDescription("Vibration sensitivity"),
e
.numeric("illuminance_v0", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10000)
.withValueStep(1)
.withUnit("lux")
.withDescription("Illuminance v0 threshold setting"),
e
.numeric("illuminance_v1", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10000)
.withValueStep(1)
.withUnit("lux")
.withDescription("Illuminance v1 threshold setting"),
e
.numeric("illuminance_calibration", ea.STATE_SET)
.withValueMin(-1000)
.withValueMax(1000)
.withValueStep(1)
.withUnit("lux")
.withDescription("Illuminance calibration"),
],
meta: {
tuyaDatapoints: [
[4, "battery", tuya.valueConverter.raw],
[1, "occupancy", tuya.valueConverter.trueFalse1],
[3, "vibration", tuya.valueConverter.raw],
[6, "vibration_sensitivity", tuya.valueConverter.raw],
[20, "illuminance", tuya.valueConverter.raw],
[50, "vibration_count", tuya.valueConverter.raw],
[101, "sampling_interval", tuya.valueConverter.raw],
[104, "illuminance_v0", tuya.valueConverter.raw],
[105, "illuminance_v1", tuya.valueConverter.raw],
[106, "illuminance_calibration", tuya.valueConverter.raw],
[107, "illuminance_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), low: tuya.enum(1), high: tuya.enum(2) })],
],
},
},
{
fingerprint: tuya.fingerprint("Excellux", ["CAT0001"]),
model: "ZG-102MV",
vendor: "Excellux",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
description: "Contact sensor and vibration sensor",
exposes: [
e.contact(),
e.binary("vibration", ea.STATE, true, false).withDescription("Vibration state, true: vibration detected, false: no vibration"),
e.battery(),
e
.numeric("vibration_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(50)
.withValueStep(1)
.withDescription("Vibration sensitivity"),
],
meta: {
tuyaDatapoints: [
[4, "battery", tuya.valueConverter.raw],
[7, "contact", tuya.valueConverter.trueFalseInvert],
[3, "vibration", tuya.valueConverter.raw],
[6, "vibration_sensitivity", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("Excellux", ["DSS0010"]),
model: "ZG-101K",
vendor: "Excellux",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
description: "Scene switch",
fromZigbee: [
fz.command_step,
fz.command_on,
fz.command_off,
fz.command_move_to_color_temp,
fz.command_move_to_level,
tuya.fz.multi_action,
tuya.fz.operation_mode,
],
exposes: [
e.battery(),
e.action([
"single",
"double",
"hold",
"brightness_move_to_level",
"color_temperature_move",
"brightness_step_up",
"brightness_step_down",
"on",
"off",
]),
e
.enum("operation_mode", ea.ALL, ["command", "event"])
.withDescription('Operation mode: "command" - for group control, "event" - for clicks'),
],
meta: {
tuyaDatapoints: [[4, "battery", tuya.valueConverter.raw]],
},
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await tuya.configureMagicPacket(device, coordinatorEndpoint);
await endpoint.write("genOnOff", { tuyaOperationMode: 1 });
await endpoint.read("genOnOff", ["tuyaOperationMode"]);
await endpoint.read("genPowerCfg", ["batteryVoltage", "batteryPercentageRemaining"]);
await reporting.bind(endpoint, coordinatorEndpoint, ["genPowerCfg"]);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff"]);
await reporting.batteryPercentageRemaining(endpoint);
},
},
{
fingerprint: tuya.fingerprint("Excellux", ["VABRATE"]),
model: "ZG-103V",
vendor: "Excellux",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
description: "Vibration sensor",
exposes: [
e.binary("vibration", ea.STATE, true, false).withDescription("Vibration state"),
e.battery(),
e
.numeric("vibration_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(50)
.withValueStep(1)
.withDescription("Vibration sensitivity"),
],
meta: {
tuyaDatapoints: [
[4, "battery", tuya.valueConverter.raw],
[3, "vibration", tuya.valueConverter.raw],
[6, "vibration_sensitivity", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_hdml1aav", "_TZE2841000000_hdml1aav"]),
model: "ZS-300TF",
vendor: "Excellux",
description: "Soil fertility sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.enum("water_warning", ea.STATE, ["none", "alarm"]).withDescription("Water shortage warning"),
e.enum("soil_fertility_warning", ea.STATE, ["none", "low", "high"]).withDescription("Soil fertility warning"),
e.numeric("battery", ea.STATE).withValueMin(1).withValueMax(100).withValueStep(1).withUnit("%").withDescription("Battery percentage"),
e.soil_moisture(),
e
.numeric("soil_fertility", ea.STATE)
.withUnit("μS/cm")
.withValueMin(0)
.withValueMax(5000)
.withDescription("Soil fertility value,between 0-5000"),
e.temperature(),
e.humidity(),
e.illuminance(),
e
.numeric("report_period", ea.STATE_SET)
.withValueMin(5)
.withValueMax(1200)
.withValueStep(5)
.withUnit("S")
.withDescription("sensor reporting period"),
tuya.exposes.soilCalibration(),
tuya.exposes.humidityCalibration(),
e
.numeric("illuminance_calibration", ea.STATE_SET)
.withValueMin(-15)
.withValueMax(1000)
.withValueStep(1)
.withUnit("lux")
.withDescription("Illuminance calibration"),
tuya.exposes.temperatureCalibration(),
tuya.exposes.soilWarning(),
e
.numeric("soil_fertility_calibration", ea.STATE_SET)
.withValueMin(-1000)
.withValueMax(1000)
.withValueStep(5)
.withUnit("μS/cm")
.withDescription("Soil Fertility Calibration"),
e
.numeric("soil_fertility_set_v0", ea.STATE_SET)
.withValueMin(0)
.withValueMax(5000)
.withValueStep(1)
.withUnit("μS/cm")
.withDescription("When the soil fertility value is lower than what threshold should a warning be issued"),
e
.numeric("soil_fertility_set_v1", ea.STATE_SET)
.withValueMin(0)
.withValueMax(5000)
.withValueStep(1)
.withUnit("μS/cm")
.withDescription("When the soil fertility value is lower than what threshold should a warning be issued"),
],
meta: {
tuyaDatapoints: [
[3, "soil_moisture", tuya.valueConverter.raw],
[5, "temperature", tuya.valueConverter.divideBy10],
[15, "battery", tuya.valueConverter.raw],
[101, "humidity", tuya.valueConverter.raw],
[102, "illuminance", tuya.valueConverter.raw],
[103, "report_period", tuya.valueConverter.raw],
[104, "soil_calibration", tuya.valueConverter.raw],
[105, "humidity_calibration", tuya.valueConverter.raw],
[106, "illuminance_calibration", tuya.valueConverter.raw],
[107, "temperature_calibration", tuya.valueConverter.divideBy10],
[110, "soil_warning", tuya.valueConverter.raw],
[111, "water_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), alarm: tuya.enum(1) })],
[112, "soil_fertility", tuya.valueConverter.raw],
[113, "soil_fertility_calibration", tuya.valueConverter.raw],
[114, "soil_fertility_set_v0", tuya.valueConverter.raw],
[115, "soil_fertility_set_v1", tuya.valueConverter.raw],
[116, "soil_fertility_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), low: tuya.enum(1), high: tuya.enum(2) })],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_qaxkdgyt"]),
model: "JKD-816COM-Z",
vendor: "Tuya",
description: "Carbon monoxide & gas sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.carbon_monoxide(),
e.co().withUnit("ppm"),
e.gas(),
tuya.exposes.gasValue().withUnit("LEL"),
e.binary("silence", ea.STATE_SET, true, false).withDescription("Mute the alarm buzzer"),
e.enum("self_test", ea.STATE, ["checking", "check_success", "check_failure", "others"]),
e
.enum("fault", ea.STATE, ["none", "fault", "serious_fault", "sensor_fault", "probe_fault", "power_fault"])
.withDescription("Fault status of the device (none = no fault)"),
],
meta: {
tuyaDatapoints: [
[1, "gas", tuya.valueConverter.raw],
[2, "gas_value", tuya.valueConverter.raw],
[18, "carbon_monoxide", tuya.valueConverter.raw],
[19, "co", tuya.valueConverter.raw],
[16, "silence", tuya.valueConverter.raw],
[
9,
"self_test",
tuya.valueConverterBasic.lookup({
checking: tuya.enum(0),
check_success: tuya.enum(1),
check_failure: tuya.enum(2),
others: tuya.enum(3),
}),
],
[
11,
"fault",
tuya.valueConverterBasic.lookup({
none: 0,
fault: 1,
serious_fault: 2,
sensor_fault: 3,
probe_fault: 4,
power_fault: 5,
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_seq9cm6u"]),
model: "TS0601_bed_presence_sensor",
vendor: "Tuya",
description: "Pressure Sensing Strap/Bed Occupancy Sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.occupancy(),
e.battery(),
e.illuminance(),
exposes.enum("sensitivity", ea.ALL, ["low", "middle", "high"]).withDescription("Sensitivity"),
exposes
.numeric("interval_time", ea.ALL)
.withUnit("min")
.withValueMin(5)
.withValueMax(720)
.withValueStep(5)
.withDescription("Sampling interval"),
exposes
.numeric("presence_delay", ea.ALL)
.withUnit("s")
.withValueMin(0)
.withValueMax(3600)
.withValueStep(1)
.withDescription("Delay to report no presence"),
exposes
.numeric("presence_time", ea.ALL)
.withUnit("s")
.withValueMin(0)
.withValueMax(3600)
.withValueStep(1)
.withDescription("Delay to report presence"),
exposes
.enum("work_state", ea.STATE, ["presence", "none", "presence_5min", "presence_30min", "none_5min", "none_30min"])
.withDescription("Summary of the state of the device"),
],
meta: {
tuyaDatapoints: [
[1, "occupancy", tuya.valueConverter.trueFalse0],
[4, "battery", tuya.valueConverter.raw],
[
9,
"sensitivity",
tuya.valueConverterBasic.lookup({
low: tuya.enum(0),
middle: tuya.enum(1),
high: tuya.enum(2),
}),
],
[12, "illuminance", tuya.valueConverter.raw],
[101, "interval_time", tuya.valueConverter.raw],
[102, "presence_delay", tuya.valueConverter.raw],
[103, "presence_time", tuya.valueConverter.raw],
[
104,
"work_state",
tuya.valueConverterBasic.lookup({
presence: tuya.enum(0),
none: tuya.enum(1),
presence_5min: tuya.enum(2),
presence_30min: tuya.enum(3),
none_5min: tuya.enum(4),
none_30min: tuya.enum(5),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_hbnfokum"]),
model: "TS0601_water_valve_1",
vendor: "Tuya",
description: "Water valve",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.switch(),
e
.numeric("position", ea.STATE_SET)
.withUnit("%")
.withValueMin(0)
.withValueMax(100)
.withValueStep(10)
.withDescription("Target valve position"),
e
.numeric("position_current", ea.STATE)
.withUnit("%")
.withValueMin(0)
.withValueMax(100)
.withValueStep(10)
.withDescription("Current valve position (feedback, steps of 10%)"),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[101, "position", tuya.valueConverter.raw],
[102, "position_current", tuya.valueConverter.raw],
],
},
},
{
zigbeeModel: ["ZG-226Z"],
model: "ZG-226Z",
vendor: "HOBEIAN",
description: "Water leak alarm",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.water_leak(),
e.binary("alarm", ea.STATE_SET, "ON", "OFF").withDescription("Audible and visual alarm"),
e.binary("muffling", ea.STATE_SET, "ON", "OFF").withDescription("Stop alarm"),
e
.numeric("alarm_time", ea.STATE_SET)
.withValueMin(0)
.withValueMax(1800)
.withValueStep(1)
.withUnit("s")
.withDescription("Alarm sounding time"),
e.enum("alarm_volume", ea.STATE_SET, ["low", "middle", "high", "mute"]).withDescription("Alarm Volume"),
e.enum("alarm_ring", ea.STATE_SET, ["mute", "beep", "music"]).withDescription("Alarm Volume"),
e.battery(),
],
meta: {
tuyaDatapoints: [
[1, "water_leak", tuya.valueConverter.trueFalse0],
[101, "alarm", tuya.valueConverter.onOff],
[7, "muffling", tuya.valueConverter.onOff],
[4, "battery", tuya.valueConverter.raw],
[102, "alarm_time", tuya.valueConverter.raw],
[
104,
"alarm_volume",
tuya.valueConverterBasic.lookup({
low: tuya.enum(0),
middle: tuya.enum(1),
high: tuya.enum(2),
mute: tuya.enum(3),
}),
],
[
103,
"alarm_ring",
tuya.valueConverterBasic.lookup({
mute: tuya.enum(0),
beep: tuya.enum(1),
music: tuya.enum(2),
}),
],
],
},
},
{
zigbeeModel: ["ZG-228Z"],
model: "ZG-228Z",
vendor: "HOBEIAN",
description: "Vibration alarm",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.vibration(),
e.enum("vibration_siren", ea.STATE_SET, ["OFF", "ON"]).withDescription("Vibration"),
e.enum("alarm", ea.STATE_SET, ["beep", "ring", "stop"]).withDescription("Initiatively trigger an alarm"),
e.binary("muffling", ea.STATE_SET, "ON", "OFF").withDescription("Stop alarm"),
e
.numeric("alarm_time", ea.STATE_SET)
.withValueMin(0)
.withValueMax(1800)
.withValueStep(1)
.withUnit("s")
.withDescription("Alarm sounding time"),
e.enum("alarm_volume", ea.STATE_SET, ["low", "middle", "high", "mute"]).withDescription("Alarm volume"),
e.enum("alarm_ring", ea.STATE_SET, ["mute", "beep", "music"]).withDescription("Alarm ring"),
e
.numeric("sensitivity", ea.STATE_SET)
.withValueMin(1)
.withValueMax(50)
.withValueStep(1)
.withDescription("The larger the value, the more sensitive it is (refresh and update only while active)"),
e.battery(),
],
meta: {
tuyaDatapoints: [
[1, "vibration", tuya.valueConverter.trueFalse1],
[
101,
"vibration_siren",
tuya.valueConverterBasic.lookup({
OFF: tuya.enum(0),
ON: tuya.enum(1),
}),
],
[
105,
"alarm",
tuya.valueConverterBasic.lookup({
beep: tuya.enum(0),
ring: tuya.enum(1),
stop: tuya.enum(2),
}),
],
[102, "muffling", tuya.valueConverter.onOff],
[4, "battery", tuya.valueConverter.raw],
[106, "alarm_time", tuya.valueConverter.raw],
[6, "sensitivity", tuya.valueConverter.raw],
[
103,
"alarm_volume",
tuya.valueConverterBasic.lookup({
low: tuya.enum(0),
middle: tuya.enum(1),
high: tuya.enum(2),
mute: tuya.enum(3),
}),
],
[
104,
"alarm_ring",
tuya.valueConverterBasic.lookup({
mute: tuya.enum(0),
beep: tuya.enum(1),
music: tuya.enum(2),
}),
],
],
},
},
{
zigbeeModel: ["ZG-229Z"],
model: "ZG-229Z",
vendor: "HOBEIAN",
description: "Smart light & sound siren",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e
.enum("alarm", ea.STATE_SET, ["alarm_sound", "alarm_light", "alarm_sound_light", "normal"])
.withDescription("Initiatively trigger an alarm"),
e.binary("doorbell", ea.STATE_SET, "ON", "OFF").withDescription("Doorbell"),
e.binary("muffling", ea.STATE_SET, "ON", "OFF").withDescription("Stop alarm"),
e
.numeric("alarm_time", ea.STATE_SET)
.withValueMin(0)
.withValueMax(1800)
.withValueStep(1)
.withUnit("s")
.withDescription("Alarm sounding time"),
e.enum("alarm_volume", ea.STATE_SET, ["low", "middle", "high", "mute"]).withDescription("Alarm volume"),
e.enum("doorbell_volume", ea.STATE_SET, ["low", "middle", "high", "mute"]).withDescription("Doorbell volume"),
e.battery(),
],
meta: {
tuyaDatapoints: [
[
1,
"alarm",
tuya.valueConverterBasic.lookup({
alarm_sound: tuya.enum(0),
alarm_light: tuya.enum(1),
alarm_sound_light: tuya.enum(2),
normal: tuya.enum(3),
}),
],
[102, "doorbell", tuya.valueConverter.onOff],
[16, "muffling", tuya.valueConverter.onOff],
[15, "battery", tuya.valueConverter.raw],
[7, "alarm_time", tuya.valueConverter.raw],
[
5,
"alarm_volume",
tuya.valueConverterBasic.lookup({
low: tuya.enum(0),
middle: tuya.enum(1),
high: tuya.enum(2),
mute: tuya.enum(3),
}),
],
[
101,
"doorbell_volume",
tuya.valueConverterBasic.lookup({
low: tuya.enum(0),
middle: tuya.enum(1),
high: tuya.enum(2),
mute: tuya.enum(3),
}),
],
],
},
},
{
zigbeeModel: ["ZG-204ZX"],
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_w0ap83qu"]),
model: "ZG-204ZX",
vendor: "HOBEIAN",
description: "24Ghz millimeter wave and T&H sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.presence(),
e.illuminance(),
e.temperature(),
e.humidity(),
tuya.exposes.temperatureUnit(),
tuya.exposes.temperatureCalibration(),
tuya.exposes.humidityCalibration(),
e.battery(),
e
.numeric("fading_time", ea.STATE_SET)
.withValueMin(0)
.withValueMax(28800)
.withValueStep(1)
.withUnit("s")
.withDescription("Presence keep time"),
e.binary("indicator", ea.STATE_SET, "ON", "OFF").withDescription("LED indicator mode"),
e
.numeric("illuminance_interval", ea.STATE_SET)
.withValueMin(1)
.withValueMax(720)
.withValueStep(1)
.withUnit("minutes")
.withDescription("Light sensing sampling(refresh and update only while active)"),
e
.numeric("detection_distance", ea.STATE_SET)
.withValueMin(0)
.withValueMax(5)
.withValueStep(0.01)
.withUnit("m")
.withDescription("Detection distance"),
e
.numeric("motion_detection_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(1)
.withDescription("Motion detection sensitivity"),
e
.numeric("static_detection_sensitivity", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10)
.withValueStep(1)
.withDescription("Static detection sensitivity"),
e.binary("anti_interference", ea.STATE_SET, "ON", "OFF").withDescription("Anti interference function"),
],
meta: {
tuyaDatapoints: [
[1, "presence", tuya.valueConverter.trueFalse1],
[106, "illuminance", tuya.valueConverter.raw],
[102, "fading_time", tuya.valueConverter.raw],
[103, "anti_interference", tuya.valueConverter.onOff],
[4, "detection_distance", tuya.valueConverter.divideBy100],
[2, "static_detection_sensitivity", tuya.valueConverter.raw],
[123, "motion_detection_sensitivity", tuya.valueConverter.raw],
[108, "indicator", tuya.valueConverter.onOff],
[110, "battery", tuya.valueConverter.raw],
[111, "temperature", tuya.valueConverter.divideBy10],
[101, "humidity", tuya.valueConverter.raw],
[109, "temperature_unit", tuya.valueConverter.temperatureUnit],
[105, "temperature_calibration", tuya.valueConverter.divideBy10],
[104, "humidity_calibration", tuya.valueConverter.raw],
[107, "illuminance_interval", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("Excellux", ["C6B7KM9"]),
model: "ZG-301A",
vendor: "Excellux",
description: "PIR Motion Sensor Light with Night Light Function",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e
.switch("Light trigger switch")
.setAccess("state", ea.STATE_SET)
.withDescription("Button/switch style control from HA: ON triggers light-on behavior, OFF sends light-off command."),
e.enum("presence_state", ea.STATE, ["NONE", "PRESENCE"]).withDescription("NONE: no presence, PRESENCE: presence"),
e.numeric("battery_value", ea.STATE).withDescription("Battery value in %"),
e.illuminance(),
e
.numeric("bright_value", ea.STATE_SET)
.withDescription("When the light brightness is activated after the lights are turned on")
.withValueMin(5)
.withValueMax(100)
.withValueStep(1),
e
.numeric("presence_time", ea.STATE_SET)
.withDescription("How long to wait before turning on the lights after detecting a person and meeting the light conditions")
.withValueMin(0)
.withValueMax(60)
.withUnit("s")
.withValueStep(1),
e
.numeric("presence_delay", ea.STATE_SET)
.withDescription("How long after no one is detected will the lights turn off")
.withValueMin(5)
.withValueMax(1800)
.withUnit("s")
.withValueStep(1),
e
.numeric("illuminance_trig", ea.STATE_SET)
.withDescription("Detection is only allowed when the illuminance is less than the current value.")
.withValueMin(0)
.withValueMax(10000)
.withUnit("lx")
.withValueStep(1),
e
.numeric("detection_cycle", ea.STATE_SET)
.withDescription("How often is the battery level and illuminance detected")
.withValueMin(10)
.withValueMax(1200)
.withValueStep(5)
.withUnit("s"),
],
meta: {
tuyaDatapoints: [
[1, "presence_state", tuya.valueConverterBasic.lookup({ NONE: tuya.enum(0), PRESENCE: tuya.enum(1) })],
[13, "state", tuya.valueConverter.onOff],
[14, "battery_value", tuya.valueConverter.raw],
[20, "illuminance", tuya.valueConverter.raw],
[100, "bright_value", tuya.valueConverter.raw],
[101, "illuminance_trig", tuya.valueConverter.raw],
[102, "presence_time", tuya.valueConverter.raw],
[103, "presence_delay", tuya.valueConverter.raw],
[104, "detection_cycle", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_4vbj3fxh"]),
model: "L2-T-F-MF",
vendor: "Tuya",
description: "Smart Zigbee fan coil thermostat",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "1970" })],
exposes: [
e
.climate()
.withLocalTemperature(ea.STATE)
.withSystemMode(["off", "cool", "heat", "fan_only"], ea.STATE_SET)
.withFanMode(["low", "medium", "high", "auto"], ea.STATE_SET)
.withSetpoint("current_heating_setpoint", 5, 45, 0.5, ea.STATE_SET),
e
.numeric("local_temperature_calibration", ea.STATE_SET)
.withUnit("°C")
.withValueMin(-9)
.withValueMax(9)
.withValueStep(1)
.withDescription("Temperature compensation setting"),
e.numeric("screen_brightness", ea.STATE_SET).withValueMin(1).withValueMax(9).withValueStep(1).withDescription("Display brightness level"),
e
.numeric("deadzone_temperature", ea.STATE_SET)
.withUnit("°C")
.withValueMin(0.5)
.withValueMax(5)
.withValueStep(0.5)
.withDescription("Temperature deadzone"),
e
.numeric("min_temperature_limit", ea.STATE_SET)
.withUnit("°C")
.withValueMin(5)
.withValueMax(15)
.withValueStep(1)
.withDescription("Minimum temperature limit"),
e
.numeric("max_temperature_limit", ea.STATE_SET)
.withUnit("°C")
.withValueMin(16)
.withValueMax(45)
.withValueStep(1)
.withDescription("Maximun temperature limit"),
e.enum("child_lock", ea.STATE_SET, ["locked", "unlocked"]).withDescription("Child lock"),
e
.numeric("eco_temperature_heating", ea.STATE_SET)
.withUnit("°C")
.withValueMin(20)
.withValueMax(30)
.withValueStep(1)
.withDescription("Eco heating temperature"),
e
.numeric("eco_temperature_cooling", ea.STATE_SET)
.withUnit("°C")
.withValueMin(20)
.withValueMax(30)
.withValueStep(1)
.withDescription("Eco cooling temperature"),
],
meta: {
tuyaDatapoints: [
[
1,
"state",
{
from: (v, meta) => {
// when the thermstat is returned to ON, we setup to fan_only, but would be better
// to remain in the mode it was (and it is, even while off)
meta.state.system_mode = v === true ? (meta.state.system_mode_device ?? "fan_only") : "off";
delete meta.state.state;
return null;
},
},
],
[
2,
"system_mode",
{
to: async (v, meta) => {
const ep = meta.device.endpoints[0];
if (v === "off") {
// mode off we redirect to datapoint 1, and that is
await tuya.sendDataPointBool(ep, 1, false, "dataRequest", 1);
return;
}
// any other mode, we force ON state
await tuya.sendDataPointBool(ep, 1, true, "dataRequest", 1);
if (v === "cool")
await tuya.sendDataPointEnum(ep, 2, 0, "dataRequest", 1);
if (v === "heat")
await tuya.sendDataPointEnum(ep, 2, 1, "dataRequest", 1);
if (v === "fan_only")
await tuya.sendDataPointEnum(ep, 2, 2, "dataRequest", 1);
},
from: (v, meta) => {
const modes = ["cool", "heat", "fan_only"];
meta.state.system_mode_device = modes[v];
return modes[v];
},
},
],
[16, "local_temperature", tuya.valueConverter.divideBy10],
[18, "min_temperature_limit", tuya.valueConverter.divideBy10],
[34, "max_temperature_limit", tuya.valueConverter.divideBy10],
[
39,
"child_lock",
tuya.valueConverterBasic.lookup({
unlocked: false,
locked: true,
}),
],
[
49,
"fan_mode",
tuya.valueConverterBasic.lookup({
auto: tuya.enum(0),
high: tuya.enum(1),
medium: tuya.enum(2),
low: tuya.enum(3),
}),
],
[50, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[101, "screen_brightness", tuya.valueConverter.raw],
[102, "local_temperature_calibration", tuya.valueConverter.raw],
[104, "deadzone_temperature", tuya.valueConverter.divideBy10],
[107, "eco_temperature_heating", tuya.valueConverter.raw],
[109, "eco_temperature_cooling", tuya.valueConverter.raw],
],
},
},
{
fingerprint: [{ modelID: "TS0601", manufacturerName: "_TZE200_spyvfeti" }],
model: "HD-T1000",
vendor: "Heat Decor",
description: "Floor thermostat",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e
.climate()
.withSystemMode(["off", "heat"], ea.STATE_SET)
.withRunningState(["idle", "heat"], ea.STATE)
.withLocalTemperature(ea.STATE)
.withLocalTemperatureCalibration(-9, 9, 1, ea.STATE_SET)
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET),
e.enum("work_mode", ea.STATE_SET, ["manual", "schedule"]),
e.binary("child_lock", ea.STATE_SET, true, false),
e.binary("frost_protection", ea.STATE_SET, true, false),
e.binary("window_detection", ea.STATE_SET, true, false),
e.numeric("max_air_temperature", ea.STATE_SET).withUnit("°C").withValueMin(35).withValueMax(95).withValueStep(0.5),
e.numeric("max_floor_temperature", ea.STATE_SET).withUnit("°C").withValueMin(5).withValueMax(60).withValueStep(0.5),
e.numeric("deadzone_temperature", ea.STATE_SET).withUnit("°C").withValueMin(0.5).withValueMax(5).withValueStep(0.5),
e.numeric("window_detection_time", ea.STATE_SET).withUnit("min").withValueMin(2).withValueMax(30).withValueStep(1),
e.numeric("window_detection_temperature", ea.STATE_SET).withUnit("°C").withValueMin(2).withValueMax(4).withValueStep(1),
e.numeric("window_detection_recovery_time", ea.STATE_SET).withUnit("min").withValueMin(10).withValueMax(60).withValueStep(1),
],
meta: {
tuyaDatapoints: [
[1, "system_mode", tuya.valueConverterBasic.lookup({ heat: true, off: false })],
[2, "work_mode", tuya.valueConverterBasic.lookup({ manual: tuya.enum(0), schedule: tuya.enum(1) })],
[3, "running_state", tuya.valueConverterBasic.lookup({ heat: 0, idle: 1 })],
[10, "frost_protection", tuya.valueConverter.raw],
[8, "window_detection", tuya.valueConverter.raw],
[16, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[19, "max_air_temperature", tuya.valueConverter.divideBy10],
[24, "local_temperature", tuya.valueConverter.divideBy10],
[27, "local_temperature_calibration", tuya.valueConverter.raw],
[40, "child_lock", tuya.valueConverter.raw],
[101, "max_floor_temperature", tuya.valueConverter.divideBy10],
[102, "deadzone_temperature", tuya.valueConverter.divideBy10],
[104, "window_detection_time", tuya.valueConverter.raw],
[105, "window_detection_temperature", tuya.valueConverter.raw],
[106, "window_detection_recovery_time", tuya.valueConverter.raw],
],
},
},
{
zigbeeModel: ["ZG-IR01"],
model: "ZG-IR01",
vendor: "HOBEIAN",
description: "Smart IR remote switch",
extend: [
tuya.modernExtend.tuyaBase({ dp: true }),
zosung.zosungExtend.addZosungIRTransmitCluster(),
zosung.zosungExtend.addZosungIRControlCluster(),
],
fromZigbee: [
fzZosung.zosung_send_ir_code_00,
fzZosung.zosung_send_ir_code_01,
fzZosung.zosung_send_ir_code_02,
fzZosung.zosung_send_ir_code_03,
fzZosung.zosung_send_ir_code_04,
fzZosung.zosung_send_ir_code_05,
fz.battery,
],
toZigbee: [tzZosung.zosung_ir_code_to_send, tzZosung.zosung_learn_ir_code],
exposes: [
e.binary("switch1", ea.STATE_SET, "ON", "OFF").withDescription("IR Switch1"),
e.binary("switch2", ea.STATE_SET, "ON", "OFF").withDescription("IR Switch2"),
e.binary("switch3", ea.STATE_SET, "ON", "OFF").withDescription("IR Switch3"),
e.binary("switch4", ea.STATE_SET, "ON", "OFF").withDescription("IR Switch4"),
e.binary("switch5", ea.STATE_SET, "ON", "OFF").withDescription("IR Switch5"),
e.binary("switch6", ea.STATE_SET, "ON", "OFF").withDescription("IR Switch6"),
e.temperature(),
e.humidity(),
ez.learn_ir_code().withDescription("Turn on to learn new IR code "),
ez.learned_ir_code(),
ez.learned_ir_timings(),
ez
.ir_code_to_send()
.withDescription("The IR code to send by device (Firmware ID must be >01062026,Support SmartIR IR code library https://github.com/smartHomeHub/SmartIR/blob/master/docs/CLIMATE.md)"),
ez.ir_emitter().withDescription("IR emitter feature. IR remote Firmware ID must be Firmware ID>01062026)"),
e.enum("switch1_on", ea.STATE_SET, ["study", "registered", "unregistered"]).withDescription("Switch 1 on IR code Study and Study status"),
e
.enum("switch1_off", ea.STATE_SET, ["study", "registered", "unregistered"])
.withDescription("Switch 1 off IR code Study and Study status"),
e.enum("switch2_on", ea.STATE_SET, ["study", "registered", "unregistered"]).withDescription("Switch 2 on IR code Study and Study status"),
e
.enum("switch2_off", ea.STATE_SET, ["study", "registered", "unregistered"])
.withDescription("Switch 2 off IR code Study and Study status"),
e.enum("switch3_on", ea.STATE_SET, ["study", "registered", "unregistered"]).withDescription("Switch 3 on IR code Study and Study status"),
e
.enum("switch3_off", ea.STATE_SET, ["study", "registered", "unregistered"])
.withDescription("Switch 3 off IR code Study and Study status"),
e.enum("switch4_on", ea.STATE_SET, ["study", "registered", "unregistered"]).withDescription("Switch 4 on IR code Study and Study status"),
e
.enum("switch4_off", ea.STATE_SET, ["study", "registered", "unregistered"])
.withDescription("Switch 4 off IR code Study and Study status"),
e.enum("switch5_on", ea.STATE_SET, ["study", "registered", "unregistered"]).withDescription("Switch 5 on IR code Study and Study status"),
e
.enum("switch5_off", ea.STATE_SET, ["study", "registered", "unregistered"])
.withDescription("Switch 5 off IR code Study and Study status"),
e.enum("switch6_on", ea.STATE_SET, ["study", "registered", "unregistered"]).withDescription("Switch 6 on IR code Study and Study status"),
e
.enum("switch6_off", ea.STATE_SET, ["study", "registered", "unregistered"])
.withDescription("Switch 6 off IR code Study and Study status"),
tuya.exposes.temperatureUnit(),
tuya.exposes.temperatureCalibration(),
tuya.exposes.humidityCalibration(),
e.battery(),
],
meta: {
tuyaDatapoints: [
[1, "switch1", tuya.valueConverter.onOff],
[2, "switch2", tuya.valueConverter.onOff],
[3, "switch3", tuya.valueConverter.onOff],
[4, "switch4", tuya.valueConverter.onOff],
[5, "switch5", tuya.valueConverter.onOff],
[6, "switch6", tuya.valueConverter.onOff],
[109, "temperature", tuya.valueConverter.divideBy10],
[110, "humidity", tuya.valueConverter.raw],
[112, "battery", tuya.valueConverter.raw],
[111, "temperature_unit", tuya.valueConverter.temperatureUnit],
[107, "temperature_calibration", tuya.valueConverter.divideBy10],
[108, "humidity_calibration", tuya.valueConverter.raw],
[120, "switch1_on", tuya.valueConverterBasic.lookup({ study: tuya.enum(0), registered: tuya.enum(1), unregistered: tuya.enum(2) })],
[121, "switch1_off", tuya.valueConverterBasic.lookup({ study: tuya.enum(0), registered: tuya.enum(1), unregistered: tuya.enum(2) })],
[122, "switch2_on", tuya.valueConverterBasic.lookup({ study: tuya.enum(0), registered: tuya.enum(1), unregistered: tuya.enum(2) })],
[123, "switch2_off", tuya.valueConverterBasic.lookup({ study: tuya.enum(0), registered: tuya.enum(1), unregistered: tuya.enum(2) })],
[124, "switch3_on", tuya.valueConverterBasic.lookup({ study: tuya.enum(0), registered: tuya.enum(1), unregistered: tuya.enum(2) })],
[125, "switch3_off", tuya.valueConverterBasic.lookup({ study: tuya.enum(0), registered: tuya.enum(1), unregistered: tuya.enum(2) })],
[126, "switch4_on", tuya.valueConverterBasic.lookup({ study: tuya.enum(0), registered: tuya.enum(1), unregistered: tuya.enum(2) })],
[127, "switch4_off", tuya.valueConverterBasic.lookup({ study: tuya.enum(0), registered: tuya.enum(1), unregistered: tuya.enum(2) })],
[128, "switch5_on", tuya.valueConverterBasic.lookup({ study: tuya.enum(0), registered: tuya.enum(1), unregistered: tuya.enum(2) })],
[129, "switch5_off", tuya.valueConverterBasic.lookup({ study: tuya.enum(0), registered: tuya.enum(1), unregistered: tuya.enum(2) })],
[130, "switch6_on", tuya.valueConverterBasic.lookup({ study: tuya.enum(0), registered: tuya.enum(1), unregistered: tuya.enum(2) })],
[131, "switch6_off", tuya.valueConverterBasic.lookup({ study: tuya.enum(0), registered: tuya.enum(1), unregistered: tuya.enum(2) })],
],
},
},
{
zigbeeModel: ["ZG-301Z-MOTO"],
model: "ZG-301Z-MOTO",
vendor: "HOBEIAN",
description: "Curtain Motor Controller",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
te.coverPosition(),
e.enum("cur_calibration", ea.STATE_SET, ["start", "end"]).withDescription("Curtain calibration"),
e.enum("control_back", ea.STATE_SET, ["forward", "back"]).withDescription("Set curtain control back"),
e.numeric("tr_timecon", ea.STATE_SET).withValueMin(0).withValueMax(120).withValueStep(1).withUnit("s").withDescription("Quick Calibrate"),
e
.enum("switch_type", ea.STATE_SET, ["flip_switch", "sync_switch", "button_switch"])
.withDescription("Set curtain controller switch type"),
e.enum("indicator_mode", ea.STATE_SET, ["relay", "pos", "none"]).withDescription("Set Controller indicator mode"),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[2, "position", tuya.valueConverter.coverPosition],
[3, "cur_calibration", tuya.valueConverterBasic.lookup({ start: tuya.enum(0), end: tuya.enum(1) })],
[8, "control_back", tuya.valueConverterBasic.lookup({ forward: tuya.enum(0), back: tuya.enum(1) })],
[10, "tr_timecon", tuya.valueConverter.raw],
[14, "indicator_mode", tuya.valueConverterBasic.lookup({ relay: tuya.enum(0), pos: tuya.enum(1), none: tuya.enum(2) })],
[
101,
"switch_type",
tuya.valueConverterBasic.lookup({ flip_switch: tuya.enum(0), sync_switch: tuya.enum(1), button_switch: tuya.enum(2) }),
],
],
},
},
{
fingerprint: [{ modelID: "TS0601", manufacturerName: "_TZE204_gm8h14wy" }],
model: "TS0601_1gang_switch",
vendor: "Tuya",
description: "1 gang touch panel switch with backlight and child lock",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.switch().setAccess("state", ea.STATE_SET),
e.power_on_behavior().withAccess(ea.STATE_SET),
e.binary("backlight_mode", ea.STATE_SET, "ON", "OFF").withDescription("Backlight"),
e.binary("child_lock", ea.STATE_SET, "ON", "OFF").withDescription("Child Lock"),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[14, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum],
[16, "backlight_mode", tuya.valueConverter.onOff],
[101, "child_lock", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: [{ modelID: "TS0601", manufacturerName: "_TZE204_trwaxi57" }],
model: "TS0601_cover_switch_2",
vendor: "Tuya",
description: "2 gang switch and cover control touch panel with backlight and child lock",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
te.coverPosition(),
e.switch().withEndpoint("l1"),
e.switch().withEndpoint("l2"),
e.binary("backlight_mode", ea.STATE_SET, "ON", "OFF").withDescription("Backlight"),
e.enum("motor_steering", ea.STATE_SET, ["FORWARD", "BACKWARD"]).withDescription("Motor Steering"),
e.enum("calibration", ea.STATE_SET, ["START", "END"]).withDescription("Calibration"),
e.binary("child_lock", ea.STATE_SET, "ON", "OFF").withDescription("Child Lock"),
],
endpoint: (device) => {
return { l1: 1, l2: 1 };
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[2, "position", tuya.valueConverter.coverPosition],
[
3,
"calibration",
tuya.valueConverterBasic.lookup({
START: tuya.enum(0),
END: tuya.enum(1),
}),
],
[7, "backlight_mode", tuya.valueConverter.onOff],
[
8,
"motor_steering",
tuya.valueConverterBasic.lookup({
FORWARD: tuya.enum(0),
BACKWARD: tuya.enum(1),
}),
],
[102, "child_lock", tuya.valueConverter.onOff],
[107, "state_l2", tuya.valueConverter.onOff],
[108, "state_l1", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: [{ modelID: "TS0601", manufacturerName: "_TZE204_ccgyhbvd" }],
model: "TS0601_3gang_switch",
vendor: "Tuya",
description: "3 gang touch panel switch with backlight and child lock",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.switch().withEndpoint("l1"),
e.switch().withEndpoint("l2"),
e.switch().withEndpoint("l3"),
e.power_on_behavior().withAccess(ea.STATE_SET),
e.binary("backlight_mode", ea.STATE_SET, "ON", "OFF").withDescription("Backlight"),
e.binary("child_lock", ea.STATE_SET, "ON", "OFF").withDescription("Child Lock"),
],
endpoint: (device) => {
return { l1: 1, l2: 1, l3: 1 };
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
[14, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum],
[16, "backlight_mode", tuya.valueConverter.onOff],
[101, "child_lock", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_0kihjsys"]),
model: "_TZE284_0kihjsys",
vendor: "EyZEE",
description: "5-Gang TZExxx Zigbee Touch Switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.switch().withEndpoint("l1"),
e.switch().withEndpoint("l2"),
e.switch().withEndpoint("l3"),
e.switch().withEndpoint("l4"),
e.switch().withEndpoint("l5"),
e
.binary("couple_decouple", ea.STATE_SET, "ON", "OFF")
.withDescription("Effective with EyZEE Decouplar switch only. " +
"Couple/Decouple: Click ON, then DOUBLE-TAP the physical button to activate Decouple mode. " +
"Click OFF, then DOUBLE-TAP the physical button to deactivate Decouple mode."),
e
.enum("indicator_mode", ea.STATE_SET, ["off", "on_off_status", "switch_position"])
.withDescription("Indicator Mode: off=Backlight off, on_off_status=Red ON / Blue OFF, switch_position=Pink ON / Off when OFF"),
e.enum("restart_status_1", ea.STATE_SET, ["off", "on", "previous"]).withDescription("Power-on behavior for Switch 1"),
e.enum("restart_status_2", ea.STATE_SET, ["off", "on", "previous"]).withDescription("Power-on behavior for Switch 2"),
e.enum("restart_status_3", ea.STATE_SET, ["off", "on", "previous"]).withDescription("Power-on behavior for Switch 3"),
e.enum("restart_status_4", ea.STATE_SET, ["off", "on", "previous"]).withDescription("Power-on behavior for Switch 4"),
e.enum("restart_status_5", ea.STATE_SET, ["off", "on", "previous"]).withDescription("Power-on behavior for Switch 5"),
e.binary("restart_status", ea.STATE_SET, "ON", "OFF").withDescription("Global power restoration status"),
],
endpoint: () => ({ l1: 1, l2: 1, l3: 1, l4: 1, l5: 1 }),
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
[4, "state_l4", tuya.valueConverter.onOff],
[5, "state_l5", tuya.valueConverter.onOff],
[7, "timer_1", tuya.valueConverter.countdown],
[8, "timer_2", tuya.valueConverter.countdown],
[9, "timer_3", tuya.valueConverter.countdown],
[10, "timer_4", tuya.valueConverter.countdown],
[11, "timer_5", tuya.valueConverter.countdown],
[14, "restart_status", tuya.valueConverter.onOff],
[
15,
"indicator_mode",
tuya.valueConverterBasic.lookup({ off: tuya.enum(0), on_off_status: tuya.enum(1), switch_position: tuya.enum(2) }),
],
[29, "restart_status_1", tuya.valueConverter.powerOnBehaviorEnum],
[30, "restart_status_2", tuya.valueConverter.powerOnBehaviorEnum],
[31, "restart_status_3", tuya.valueConverter.powerOnBehaviorEnum],
[32, "restart_status_4", tuya.valueConverter.powerOnBehaviorEnum],
[33, "restart_status_5", tuya.valueConverter.powerOnBehaviorEnum],
],
},
},
{
fingerprint: [
{ modelID: "TS0601", manufacturerName: "_TZE204_y8ficeai" },
{ modelID: "TS0601", manufacturerName: "_TZE284_tokhh9pf" },
],
model: "TS0601_6gang_switch",
vendor: "Tuya",
description: "6 gang touch panel switch with backlight and child lock",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.switch().withEndpoint("l1"),
e.switch().withEndpoint("l2"),
e.switch().withEndpoint("l3"),
e.switch().withEndpoint("l4"),
e.switch().withEndpoint("l5"),
e.switch().withEndpoint("l6"),
e.power_on_behavior().withAccess(ea.STATE_SET),
e.binary("backlight_mode", ea.STATE_SET, "ON", "OFF").withDescription("Backlight"),
e.binary("child_lock", ea.STATE_SET, "ON", "OFF").withDescription("Child Lock"),
],
endpoint: (device) => {
return { l1: 1, l2: 1, l3: 1, l4: 1, l5: 1, l6: 1 };
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[3, "state_l3", tuya.valueConverter.onOff],
[4, "state_l4", tuya.valueConverter.onOff],
[5, "state_l5", tuya.valueConverter.onOff],
[6, "state_l6", tuya.valueConverter.onOff],
[14, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum],
[16, "backlight_mode", tuya.valueConverter.onOff],
[101, "child_lock", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: [{ modelID: "TS0601", manufacturerName: "_TZE284_7e6v8u9f" }],
model: "TS0601_multifunction_switch",
vendor: "Tuya",
description: "1 gang touch panel switch with backlight color modes, child lock, timer, and brightness",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.switch().setAccess("state", ea.STATE_SET),
e.power_on_behavior().withAccess(ea.STATE_SET),
e.numeric("countdown", ea.STATE_SET).withUnit("s").withValueMin(0).withValueMax(86400).withDescription("Countdown to turn off"),
tuya.exposes.indicatorModeNoneRelayPos(),
e.binary("backlight_mode", ea.STATE_SET, "ON", "OFF").withDescription("Backlight"),
tuya.exposes.inchingSwitch2(),
e.enum("on_color", ea.STATE_SET, ["red", "blue", "green", "white", "yellow", "magenta", "cyan"]).withDescription("ON Color"),
e.enum("off_color", ea.STATE_SET, ["red", "blue", "green", "white", "yellow", "magenta", "cyan"]).withDescription("OFF Color"),
e.numeric("backlight_brightness", ea.STATE_SET).withValueMin(0).withValueMax(100).withDescription("Backlight Brightness"),
e.binary("child_lock", ea.STATE_SET, "ON", "OFF").withDescription("Child Lock"),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[7, "countdown", tuya.valueConverter.countdown],
[14, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum],
[
15,
"indicator_mode",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
relay: tuya.enum(1),
pos: tuya.enum(2),
}),
],
[16, "backlight_mode", tuya.valueConverter.onOff],
[19, "inching", tuya.valueConverter.inchingSwitch2],
[101, "child_lock", tuya.valueConverter.onOff],
[102, "backlight_brightness", tuya.valueConverter.raw],
[
103,
"on_color",
tuya.valueConverterBasic.lookup({
red: tuya.enum(0),
blue: tuya.enum(1),
green: tuya.enum(2),
white: tuya.enum(3),
yellow: tuya.enum(4),
magenta: tuya.enum(5),
cyan: tuya.enum(6),
}),
],
[
104,
"off_color",
tuya.valueConverterBasic.lookup({
red: tuya.enum(0),
blue: tuya.enum(1),
green: tuya.enum(2),
white: tuya.enum(3),
yellow: tuya.enum(4),
magenta: tuya.enum(5),
cyan: tuya.enum(6),
}),
],
],
},
},
{
fingerprint: [{ modelID: "TS0601", manufacturerName: "_TZE204_apiu8k13" }],
model: "TS0601_power_monitoring_switch",
vendor: "Tuya",
description: "Touch panel switch with power monitoring and timer",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.switch().setAccess("state", ea.STATE_SET),
e.numeric("countdown", ea.STATE_SET).withUnit("m").withValueMin(0).withValueMax(120).withDescription("Countdown to turn off"),
e.energy(),
e.current(),
e.power(),
e.voltage(),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[7, "countdown", tuya.valueConverter.countdown],
[20, "energy", tuya.valueConverter.divideBy100],
[21, "current", tuya.valueConverter.divideBy1000],
[22, "power", tuya.valueConverter.raw],
[23, "voltage", tuya.valueConverter.raw],
],
},
},
{
fingerprint: [{ modelID: "TS0601", manufacturerName: "_TZE204_he9apaui" }],
model: "TS0601_2gang_switch",
vendor: "Tuya",
description: "2 gang touch panel switch with backlight and child lock",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e.switch().withEndpoint("l1"),
e.switch().withEndpoint("l2"),
e.power_on_behavior().withAccess(ea.STATE_SET),
e.binary("backlight_mode", ea.STATE_SET, "ON", "OFF").withDescription("Backlight"),
e.binary("child_lock", ea.STATE_SET, "ON", "OFF").withDescription("Child Lock"),
],
endpoint: (device) => {
return { l1: 1, l2: 1 };
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "state_l2", tuya.valueConverter.onOff],
[14, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum],
[16, "backlight_mode", tuya.valueConverter.onOff],
[101, "child_lock", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: [{ modelID: "TS0601", manufacturerName: "_TZE204_fhv95pf1" }],
model: "TS0601_stairwell_switch",
vendor: "Tuya",
description: "1 gang stairwell switch with child lock",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [e.switch().setAccess("state", ea.STATE_SET), e.binary("child_lock", ea.STATE_SET, "ON", "OFF").withDescription("Child Lock")],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.onOff],
[29, "child_lock", tuya.valueConverter.onOff],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_izy1g1mb", "_TZE204_f2rflfa6"]),
model: "ZIS-03", // _TZE204_izy1g1mb
vendor: "Novato",
description: "24 GHz radar human presence sensor with relay output",
whiteLabel: [tuya.whitelabel("Novato", "ZIS-04", "24 GHz radar human presence sensor with relay output", ["_TZE204_f2rflfa6"])],
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await tuya.sendDataPointBool(endpoint, 107, true);
await tuya.sendDataPointBool(endpoint, 108, true);
await tuya.sendDataPointBool(endpoint, 112, false);
},
exposes: (device, options) => {
const exps = [
e.presence(),
e.illuminance(),
exposes.binary("radar", ea.STATE_SET, "ON", "OFF").withDescription("Enable/disable radar detection"),
exposes
.numeric("detection_range", ea.STATE_SET)
.withValueMin(1)
.withValueMax(7)
.withValueStep(1)
.withDescription("Detection range level (1=closest, 7=furthest)"),
exposes.enum("sensitivity", ea.STATE_SET, ["low", "medium", "high", "max"]).withDescription("Radar sensitivity"),
exposes.enum("detection_area", ea.STATE_SET, ["all", "left", "right"]).withDescription("Active detection zone"),
exposes
.numeric("fading_time", ea.STATE_SET)
.withUnit("s")
.withValueMin(2)
.withValueMax(3600)
.withValueStep(1)
.withDescription("Delay before reporting absence (s)"),
exposes.binary("indicator", ea.STATE_SET, "ON", "OFF").withDescription("LED status indicator"),
exposes.binary("presence_switch", ea.STATE_SET, "ON", "OFF").withDescription("Presence relay output (relay variants only)"),
exposes
.numeric("compensation_coefficient", ea.STATE_SET)
.withValueMin(1)
.withValueMax(10)
.withValueStep(1)
.withCategory("config")
.withDescription("Illuminance compensation multiplier (1x-10x)"),
exposes
.binary("state_reversal", ea.STATE_SET, "ON", "OFF")
.withCategory("config")
.withDescription("Invert the presence output signal"),
];
// detection_distance only for ZIS-04
if (!device || device.manufacturerName === "_TZE204_f2rflfa6") {
exps.splice(2, 0, exposes.numeric("detection_distance", ea.STATE).withUnit("m").withDescription("Live distance to detected person"));
}
return exps;
},
meta: {
tuyaDatapoints: [
[1, "presence", { from: (v) => v === 1 }],
[4, "detection_range", tuya.valueConverter.raw],
[101, "detection_distance", tuya.valueConverter.raw],
[102, "indicator", tuya.valueConverter.onOff],
[103, "illuminance", tuya.valueConverter.raw],
[104, "fading_time", tuya.valueConverter.raw],
[106, "compensation_coefficient", tuya.valueConverter.raw],
[107, "presence_switch", tuya.valueConverter.onOff],
[108, "radar", tuya.valueConverter.onOff],
[111, "detection_area", tuya.valueConverterBasic.lookup({ all: tuya.enum(0), left: tuya.enum(1), right: tuya.enum(2) })],
[112, "state_reversal", tuya.valueConverter.onOff],
[
113,
"sensitivity",
tuya.valueConverterBasic.lookup({ low: tuya.enum(0), medium: tuya.enum(1), high: tuya.enum(2), max: tuya.enum(3) }),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS000F", ["_TZ3218_n0jsuogs"]),
model: "ZRM01",
vendor: "Novato",
description: "Smart relay 1 channel",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({ powerOutageMemory: true }),
m.enumLookup({
name: "switch_type",
lookup: { momentary: 0, toggle: 1, state: 2 },
cluster: "genOnOff",
attribute: { ID: 0x8001, type: zcl_1.DataType.ENUM8 },
zigbeeCommandOptions: { manufacturerCode: 0x1141 },
description: "External switch type",
}),
],
},
{
fingerprint: tuya.fingerprint("TS000F", ["_TZ3218_sgbsg6mr"]),
model: "ZRM02",
vendor: "Novato",
description: "Smart relay 2 channel",
endpoint: (device) => ({ l1: 1, l2: 2 }),
meta: { multiEndpoint: true },
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({ powerOutageMemory: true, endpoints: ["l1", "l2"] }),
m.enumLookup({
name: "switch_type",
lookup: { momentary: 0, toggle: 1, state: 2 },
cluster: "genOnOff",
attribute: { ID: 0x8001, type: zcl_1.DataType.ENUM8 },
zigbeeCommandOptions: { manufacturerCode: 0x1141 },
description: "External switch type (shared between channels)",
endpointName: "l1",
}),
],
},
{
fingerprint: [{ modelID: "Excellux", manufacturerName: "DTS1XM9" }],
model: "ZG-109TDS",
vendor: "Excellux",
description: "Probe Temperature with EC && TDS && Salinity && SG && ht(humidity and temperature) Sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e
.enum("probe_temperature_warning", ea.STATE, ["none", "low", "high"])
.withDescription("Probe temperature sensor warning.low :temperature is lower than v0 and v1.high:temperature is higher than v0 and v1"),
e
.enum("temperature_warning", ea.STATE, ["none", "low", "high"])
.withDescription("Temperature warning.low :temperature is lower than v0 and v1.high:temperature is higher than v0 and v1"),
e
.enum("humidity_warning", ea.STATE, ["none", "low", "high"])
.withDescription("Humidity warning.low :humidity is lower than v0 and v1.high:humidity is higher than v0 and v1"),
e.enum("tds_warning", ea.STATE, ["none", "high"]).withDescription("TDS warning.high:TDS is higher than user setting"),
e
.enum("ec_warning", ea.STATE, ["none", "low", "high"])
.withDescription("ec warning.low :ec is lower than v0 and v1.high:ec is higher than v0 and v1"),
e.enum("mode", ea.STATE_SET, ["freshwater", "seawater"]).withDescription("Freshwater or Seawater"),
e.numeric("battery", ea.STATE).withValueMin(1).withValueMax(100).withValueStep(1).withUnit("%").withDescription("Battery percentage"),
e
.numeric("probe_temperature", ea.STATE)
.withValueMin(-40)
.withValueMax(120)
.withValueStep(0.1)
.withUnit("°C")
.withDescription("Probe temperature sensor"),
e.temperature(),
e.humidity(),
e
.numeric("tds", ea.STATE)
.withValueMin(0)
.withValueMax(13000)
.withValueStep(1)
.withUnit("ppm")
.withDescription("Total Dissolved Solids(0~13000 ppm)"),
e
.numeric("salinity", ea.STATE)
.withValueMin(0.1)
.withValueMax(50)
.withValueStep(0.1)
.withUnit("‰")
.withDescription("Salinity value(0.1‰~50‰)"),
e
.numeric("ec", ea.STATE)
.withValueMin(0)
.withValueMax(20000)
.withValueStep(1)
.withUnit("μS/cm")
.withDescription("EC value @ 25°C Standard Temperature (0~20000us/cm)"),
e.numeric("sg", ea.STATE).withValueMin(1000).withValueMax(1100).withValueStep(1).withDescription("specific gravity(1.000~1.100)"),
e
.numeric("sampling_interval", ea.STATE_SET)
.withValueMin(5)
.withValueMax(1200)
.withValueStep(5)
.withUnit("s")
.withDescription("sampling interval"),
e
.numeric("probe_temperature_calibration", ea.STATE_SET)
.withValueMin(-2)
.withValueMax(2)
.withValueStep(0.1)
.withUnit("°C")
.withDescription("Probe temperature sensor calibration"),
e
.numeric("probe_temperature_v0_set", ea.STATE_SET)
.withValueMin(-40)
.withValueMax(125)
.withValueStep(0.1)
.withUnit("°C")
.withDescription("Probe temperature sensor v0 threshold setting"),
e
.numeric("probe_temperature_v1_set", ea.STATE_SET)
.withValueMin(-40)
.withValueMax(125)
.withValueStep(0.1)
.withUnit("°C")
.withDescription("Probe temperature sensor v1 threshold setting"),
e
.numeric("tds_warning_set", ea.STATE_SET)
.withValueMin(0)
.withValueMax(13000)
.withValueStep(1)
.withUnit("ppm")
.withDescription("TDS alarm threshold settings"),
e
.numeric("ec_v0_set", ea.STATE_SET)
.withValueMin(0)
.withValueMax(20000)
.withValueStep(1)
.withUnit("μS/cm")
.withDescription("EC v0 threshold setting"),
e
.numeric("ec_v1_set", ea.STATE_SET)
.withValueMin(0)
.withValueMax(20000)
.withValueStep(1)
.withUnit("μS/cm")
.withDescription("EC v1 threshold setting"),
e
.numeric("temperature_calibration", ea.STATE_SET)
.withValueMin(-2)
.withValueMax(2)
.withValueStep(0.01)
.withUnit("°C")
.withDescription("Temperature calibration"),
e
.numeric("temperature_v0_set", ea.STATE_SET)
.withValueMin(-40)
.withValueMax(85)
.withValueStep(0.01)
.withUnit("°C")
.withDescription("Temperature v0 threshold setting"),
e
.numeric("temperature_v1_set", ea.STATE_SET)
.withValueMin(-40)
.withValueMax(85)
.withValueStep(0.01)
.withUnit("°C")
.withDescription("Temperature v1 threshold setting"),
e
.numeric("humidity_calibration", ea.STATE_SET)
.withValueMin(-10)
.withValueMax(10)
.withValueStep(0.01)
.withUnit("%")
.withDescription("Humidity calibration"),
e
.numeric("humidity_v0_set", ea.STATE_SET)
.withValueMin(0)
.withValueMax(100)
.withValueStep(0.01)
.withUnit("%")
.withDescription("Humidity v0 threshold setting"),
e
.numeric("humidity_v1_set", ea.STATE_SET)
.withValueMin(0)
.withValueMax(100)
.withValueStep(0.01)
.withUnit("%")
.withDescription("Humidity v1 threshold setting"),
],
meta: {
tuyaDatapoints: [
[1, "probe_temperature", tuya.valueConverter.divideBy10],
[4, "battery", tuya.valueConverter.raw],
[5, "temperature", tuya.valueConverter.divideBy100],
[101, "sampling_interval", tuya.valueConverter.raw],
[108, "probe_temperature_calibration", tuya.valueConverter.divideBy10],
[109, "probe_temperature_v0_set", tuya.valueConverter.divideBy10],
[110, "probe_temperature_v1_set", tuya.valueConverter.divideBy10],
[112, "probe_temperature_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), low: tuya.enum(1), high: tuya.enum(2) })],
[114, "temperature_calibration", tuya.valueConverter.divideBy100],
[115, "temperature_v0_set", tuya.valueConverter.divideBy100],
[116, "temperature_v1_set", tuya.valueConverter.divideBy100],
[117, "temperature_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), low: tuya.enum(1), high: tuya.enum(2) })],
[118, "humidity", tuya.valueConverter.divideBy100],
[119, "humidity_calibration", tuya.valueConverter.divideBy100],
[120, "humidity_v0_set", tuya.valueConverter.divideBy100],
[121, "humidity_v1_set", tuya.valueConverter.divideBy100],
[122, "humidity_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), low: tuya.enum(1), high: tuya.enum(2) })],
[124, "tds", tuya.valueConverter.raw],
[125, "tds_warning_set", tuya.valueConverter.raw],
[126, "tds_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), high: tuya.enum(1) })],
[127, "ec", tuya.valueConverter.raw],
[128, "ec_v0_set", tuya.valueConverter.raw],
[129, "ec_v1_set", tuya.valueConverter.raw],
[130, "ec_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), low: tuya.enum(1), high: tuya.enum(2) })],
[131, "salinity", tuya.valueConverter.divideBy10],
[132, "sg", tuya.valueConverter.divideBy1000],
[133, "mode", tuya.valueConverterBasic.lookup({ freshwater: tuya.enum(0), seawater: tuya.enum(1) })],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_aaeasoll"]),
model: "TZE284_aaeasoll",
vendor: "SMARTERCURRY",
description: "Illuminance sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "1970" })],
exposes: [
e.illuminance().withAccess(ea.STATE),
e.battery().withAccess(ea.STATE),
e.enum("report_interval", ea.STATE_SET, ["5m", "10m", "15m", "20m", "30m", "1h"]).withDescription("Data reporting interval"),
],
meta: {
tuyaDatapoints: [
[2, "illuminance", tuya.valueConverter.raw],
[4, "battery", tuya.valueConverter.raw],
[101, "report_interval", tuya.valueConverterBasic.lookup({ "5m": 0, "10m": 1, "15m": 2, "20m": 3, "30m": 4, "1h": 5 })],
],
},
},
{
fingerprint: tuya.fingerprint("TS0726", ["_TZ300A_82iab0pn"]),
model: "TS0726_4_gang_switch_and_4_scene",
vendor: "Tuya",
description: "Multi 4 gang switch and 4 scene with backlight",
extend: [tuya.modernExtend.tuyaBase()],
fromZigbee: [fz.on_off, tuya.fz.power_on_behavior_2, fzLocal.TS0726_action],
toZigbee: [tz.on_off, tuya.tz.power_on_behavior_2, tzLocal.TS0726_switch_mode],
exposes: [
...[1, 2, 3, 4].map((ep) => e.switch().withEndpoint(`l${ep}`)),
...[1, 2, 3, 4].map((ep) => e.power_on_behavior().withEndpoint(`l${ep}`)),
...[1, 2, 3, 4].map((ep) => e.enum("switch_mode", ea.STATE_SET, ["switch", "scene"]).withEndpoint(`l${ep}`)),
e.action(["scene_1", "scene_2", "scene_3", "scene_4", "scene_5", "scene_6", "scene_7", "scene_8"]),
],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3, l4: 4, l5: 5, l6: 6, l7: 7, l8: 8 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
await tuya.configureMagicPacket(device, coordinatorEndpoint);
for (const ep of [1, 2, 3, 4, 5, 6, 7, 8]) {
await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ["genOnOff"]);
}
},
},
{
zigbeeModel: ["ZG-303B", "ZG-303L"],
model: "HZ-SL10",
vendor: "Haozee",
description: "Soil moisture sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: (device) => {
const exposes = [
e.dry(),
e.temperature(),
e.soil_moisture(),
tuya.exposes.temperatureUnit(),
tuya.exposes.temperatureCalibration(),
tuya.exposes.soilCalibration(),
tuya.exposes.temperatureSampling(),
tuya.exposes.soilSampling(),
tuya.exposes.soilWarning(),
e.battery(),
];
if (!(0, utils_1.isDummyDevice)(device) && device.modelID === "ZG-303L") {
exposes.splice(3, 0, e.illuminance());
exposes.splice(9, 0, e
.numeric("illuminance_sampling", ea.STATE_SET)
.withValueMin(5)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Brightness acquisition interval (refresh and update only while active)"));
}
return exposes;
},
meta: {
tuyaDatapoints: [
[106, "dry", tuya.valueConverter.raw],
[5, "temperature", tuya.valueConverter.divideBy10],
[3, "soil_moisture", tuya.valueConverter.raw],
[15, "battery", tuya.valueConverter.raw],
[9, "temperature_unit", tuya.valueConverter.temperatureUnit],
[104, "temperature_calibration", tuya.valueConverter.divideBy10],
[102, "soil_calibration", tuya.valueConverter.raw],
[111, "temperature_sampling", tuya.valueConverter.raw],
[112, "soil_sampling", tuya.valueConverter.raw],
[110, "soil_warning", tuya.valueConverter.raw],
[105, "illuminance", tuya.valueConverter.raw],
[107, "illuminance_sampling", tuya.valueConverter.raw],
],
},
},
{
zigbeeModel: ["ZG-210Z"],
model: "ZG-210Z",
vendor: "HOBEIAN",
description: "Pressure Sensing Strap/Bed Occupancy Sensor",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
e
.enum("pressure_state", ea.STATE, ["none", "sit", "sedentary"])
.withDescription("Pressure state,none: Vacant,sit: Sitting,sedentary: Sedentary"),
e.numeric("current_pressure", ea.STATE).withUnit("x").withDescription("Sensing pressure value"),
e.temperature(),
e.humidity(),
e.battery(),
e
.numeric("pressure_intensity", ea.ALL)
.withUnit("x")
.withValueMin(0)
.withValueMax(2000)
.withValueStep(1)
.withDescription("Set Sensing pressure intensity value"),
exposes
.numeric("presence_delay", ea.ALL)
.withUnit("s")
.withValueMin(0)
.withValueMax(3600)
.withValueStep(1)
.withDescription("Delay to report no presence"),
exposes
.numeric("sedentary_reminder", ea.ALL)
.withUnit("minutes")
.withValueMin(0)
.withValueMax(1440)
.withValueStep(1)
.withDescription("Set sedentary Reminder Time"),
tuya.exposes.temperatureCalibration(),
tuya.exposes.humidityCalibration(),
],
meta: {
tuyaDatapoints: [
[1, "pressure_state", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), sit: tuya.enum(1), sedentary: tuya.enum(2) })],
[2, "current_pressure", tuya.valueConverter.raw],
[101, "temperature", tuya.valueConverter.divideBy10],
[102, "humidity", tuya.valueConverter.raw],
[15, "battery", tuya.valueConverter.raw],
[105, "pressure_intensity", tuya.valueConverter.raw],
[106, "presence_delay", tuya.valueConverter.raw],
[107, "sedentary_reminder", tuya.valueConverter.raw],
[103, "temperature_calibration", tuya.valueConverter.divideBy10],
[104, "humidity_calibration", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("Excellux", ["AIRPRS1"]),
model: "EZ-104UPT",
vendor: "Excellux",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
description: "Contact sensor and vibration sensor",
exposes: [
e
.enum("uv_warning", ea.STATE, ["Low", "Moderate", "High", "Very High", "Extreme"])
.withDescription("UV Warning.Low < Moderate < High < Very High < Extreme"),
e
.enum("pressure_warn", ea.STATE, ["none", "low", "high"])
.withDescription("none: pressure is between v0 and v1;low : pressure is lower than v0 and v1;high: pressure is higher than v0 and v1"),
e
.enum("pressure_tend", ea.STATE, ["normal", "rise", "fall"])
.withDescription("normal: Minor fluctuations in air pressure;Rising: Air pressure shows a steady upward trend;Falling: Air pressure shows a steady downward trend"),
e
.enum("temperature_warning", ea.STATE, ["none", "low", "high"])
.withDescription("Temperature warning. Low: temperature is lower than v0 and v1. High: temperature is higher than v0 and v1"),
e.enum("illuminance_warning", ea.STATE, ["none", "low", "high"]).withDescription("Illuminance warning level,low:low,high:high,none"),
e.battery(),
e.temperature(),
e.illuminance(),
e.numeric("uv", ea.STATE).withValueMin(0).withValueMax(3300).withUnit("Volt").withDescription("uv Value"),
e.numeric("uv_level", ea.STATE).withValueMin(0).withValueMax(15.0).withValueStep(0.1).withDescription("uv level from 0.0 to 15.0"),
e
.numeric("pressure", ea.STATE)
.withValueMin(300)
.withValueMax(1100)
.withValueStep(0.01)
.withUnit("hPa")
.withDescription("pressure by sensor"),
e
.numeric("pressure_calibration", ea.STATE_SET)
.withValueMin(-10.0)
.withValueMax(10.0)
.withValueStep(0.01)
.withUnit("hPa")
.withDescription("calibration pressure"),
e
.numeric("pressure_v0_set", ea.STATE_SET)
.withValueMin(300)
.withValueMax(1100)
.withValueStep(0.01)
.withUnit("hPa")
.withDescription("set v0 pressure"),
e
.numeric("pressure_v1_set", ea.STATE_SET)
.withValueMin(300)
.withValueMax(1100)
.withValueStep(0.01)
.withUnit("hPa")
.withDescription("set v1 pressure"),
e.numeric("uv_calibration", ea.STATE_SET).withValueMin(-1.0).withValueMax(1.0).withValueStep(0.1).withDescription("calibration uv"),
e
.numeric("sampling_interval", ea.STATE_SET)
.withValueMin(5)
.withValueMax(1200)
.withValueStep(5)
.withUnit("s")
.withDescription("Sampling interval"),
e
.numeric("temperature_calibration", ea.STATE_SET)
.withValueMin(-2)
.withValueMax(2)
.withValueStep(0.01)
.withUnit("°C")
.withDescription("Temperature calibration"),
e
.numeric("temperature_v0_set", ea.STATE_SET)
.withValueMin(-40)
.withValueMax(85)
.withValueStep(0.01)
.withUnit("°C")
.withDescription("Temperature v0 threshold setting"),
e
.numeric("temperature_v1_set", ea.STATE_SET)
.withValueMin(-40)
.withValueMax(85)
.withValueStep(0.01)
.withUnit("°C")
.withDescription("Temperature v1 threshold setting"),
e
.numeric("illuminance_v0", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10000)
.withValueStep(1)
.withUnit("lux")
.withDescription("Illuminance v0 threshold setting"),
e
.numeric("illuminance_v1", ea.STATE_SET)
.withValueMin(0)
.withValueMax(10000)
.withValueStep(1)
.withUnit("lux")
.withDescription("Illuminance v1 threshold setting"),
e
.numeric("illuminance_calibration", ea.STATE_SET)
.withValueMin(-1000)
.withValueMax(1000)
.withValueStep(1)
.withUnit("lux")
.withDescription("Illuminance calibration"),
],
meta: {
tuyaDatapoints: [
[4, "battery", tuya.valueConverter.raw],
[5, "temperature", tuya.valueConverter.divideBy100],
[20, "illuminance", tuya.valueConverter.raw],
[101, "sampling_interval", tuya.valueConverter.raw],
[104, "illuminance_v0", tuya.valueConverter.raw],
[105, "illuminance_v1", tuya.valueConverter.raw],
[106, "illuminance_calibration", tuya.valueConverter.raw],
[107, "illuminance_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), low: tuya.enum(1), high: tuya.enum(2) })],
[108, "uv", tuya.valueConverter.raw],
[109, "uv_level", tuya.valueConverter.divideBy10],
[110, "uv_calibration", tuya.valueConverter.divideBy10],
[
111,
"uv_warning",
tuya.valueConverterBasic.lookup({
Low: tuya.enum(0),
Moderate: tuya.enum(1),
High: tuya.enum(2),
"Very High": tuya.enum(3),
Extreme: tuya.enum(4),
}),
],
[114, "temperature_calibration", tuya.valueConverter.divideBy100],
[115, "temperature_v0_set", tuya.valueConverter.divideBy100],
[116, "temperature_v1_set", tuya.valueConverter.divideBy100],
[117, "temperature_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), low: tuya.enum(1), high: tuya.enum(2) })],
[124, "pressure", tuya.valueConverter.divideBy100],
[125, "pressure_calibration", tuya.valueConverter.divideBy100],
[126, "pressure_v0_set", tuya.valueConverter.divideBy100],
[127, "pressure_v1_set", tuya.valueConverter.divideBy100],
[128, "pressure_warn", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), low: tuya.enum(1), high: tuya.enum(2) })],
[129, "pressure_tend", tuya.valueConverterBasic.lookup({ normal: tuya.enum(0), rise: tuya.enum(1), fall: tuya.enum(2) })],
],
},
},
{
fingerprint: tuya.fingerprint("Excellux", ["EZ500FL"]),
model: "EZ-500FL",
vendor: "Excellux",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
description: "Split-type sensor for detecting soil moisture, soil fertility, temperature and humidity",
exposes: [
e.battery().withDescription("Battery level"),
e
.enum("probe_temperature_warning", ea.STATE, ["none", "low", "high"])
.withDescription("Probe temperature sensor warning. Low: temperature is lower than v0 and v1. High: temperature is higher than v0 and v1"),
e
.enum("temperature_warning", ea.STATE, ["none", "low", "high"])
.withDescription("Temperature warning. Low: temperature is lower than v0 and v1. High: temperature is higher than v0 and v1"),
e
.enum("humidity_warning", ea.STATE, ["none", "low", "high"])
.withDescription("Humidity warning. Low: humidity is lower than v0 and v1. High: humidity is higher than v0 and v1"),
e
.enum("fertility_warning", ea.STATE, ["none", "low", "high"])
.withDescription("Fertility warning. Low: Fertility is lower than v0 and v1. High: Fertility is higher than v0 and v1"),
e
.enum("moisture_warning", ea.STATE, ["none", "low", "high"])
.withDescription("Moisture warning. Low: Moisture is lower than v0 and v1. High: Moisture is higher than v0 and v1"),
e
.numeric("probe_temperature", ea.STATE)
.withValueMin(-40)
.withValueMax(120)
.withValueStep(0.1)
.withUnit("°C")
.withDescription("Probe temperature sensor"),
e.numeric("moisture", ea.STATE).withValueMin(0).withValueMax(100).withValueStep(1).withUnit("%").withDescription("Soil moisture"),
e.temperature(),
e.humidity(),
e
.numeric("fertility", ea.STATE)
.withValueMin(0)
.withValueStep(1)
.withValueMax(5000)
.withUnit("μS/cm")
.withDescription("Soil fertility value,between 0-5000μS/cm"),
e
.numeric("fertility_calibration", ea.STATE_SET)
.withValueMin(0.5)
.withValueStep(0.01)
.withValueMax(2.0)
.withDescription("Calibration of cell constant"),
e
.numeric("sampling_interval", ea.STATE_SET)
.withValueMin(5)
.withValueMax(1200)
.withValueStep(5)
.withUnit("s")
.withDescription("Sampling interval"),
e
.numeric("probe_temperature_calibration", ea.STATE_SET)
.withValueMin(-2)
.withValueMax(2)
.withValueStep(0.1)
.withUnit("°C")
.withDescription("Probe temperature sensor calibration"),
e
.numeric("probe_temperature_v0_set", ea.STATE_SET)
.withValueMin(-40)
.withValueMax(125)
.withValueStep(0.1)
.withUnit("°C")
.withDescription("Probe temperature sensor v0 threshold setting"),
e
.numeric("probe_temperature_v1_set", ea.STATE_SET)
.withValueMin(-40)
.withValueMax(125)
.withValueStep(0.1)
.withUnit("°C")
.withDescription("Probe temperature sensor v1 threshold setting"),
e
.numeric("temperature_calibration", ea.STATE_SET)
.withValueMin(-2)
.withValueMax(2)
.withValueStep(0.01)
.withUnit("°C")
.withDescription("Temperature calibration"),
e
.numeric("temperature_v0_set", ea.STATE_SET)
.withValueMin(-40)
.withValueMax(85)
.withValueStep(0.01)
.withUnit("°C")
.withDescription("Temperature v0 threshold setting"),
e
.numeric("temperature_v1_set", ea.STATE_SET)
.withValueMin(-40)
.withValueMax(85)
.withValueStep(0.01)
.withUnit("°C")
.withDescription("Temperature v1 threshold setting"),
e
.numeric("humidity_calibration", ea.STATE_SET)
.withValueMin(-10)
.withValueMax(10)
.withValueStep(0.01)
.withUnit("%")
.withDescription("Humidity calibration"),
e
.numeric("humidity_v0_set", ea.STATE_SET)
.withValueMin(0)
.withValueMax(100)
.withValueStep(0.01)
.withUnit("%")
.withDescription("Humidity v0 threshold setting"),
e
.numeric("humidity_v1_set", ea.STATE_SET)
.withValueMin(0)
.withValueMax(100)
.withValueStep(0.01)
.withUnit("%")
.withDescription("Humidity v1 threshold setting"),
e
.numeric("fertility_v0_set", ea.STATE_SET)
.withValueMin(0)
.withValueMax(5000)
.withValueStep(1)
.withUnit("μS/cm")
.withDescription("Fertility v0 threshold setting"),
e
.numeric("fertility_v1_set", ea.STATE_SET)
.withValueMin(0)
.withValueMax(5000)
.withValueStep(1)
.withUnit("μS/cm")
.withDescription("Fertility v1 threshold setting"),
e
.numeric("moisture_calibration", ea.STATE_SET)
.withValueMin(0.2)
.withValueMax(1.5)
.withValueStep(0.01)
.withDescription("Coefficient 0.2~1.5. Higher for loose‑hard soil, lower for wet soft clay."),
e
.numeric("moisture_v0_set", ea.STATE_SET)
.withValueMin(0)
.withValueMax(100)
.withValueStep(1)
.withUnit("%")
.withDescription("Moisture v0 threshold setting"),
e
.numeric("moisture_v1_set", ea.STATE_SET)
.withValueMin(0)
.withValueMax(100)
.withValueStep(1)
.withUnit("%")
.withDescription("Moisture v1 threshold setting"),
],
meta: {
tuyaDatapoints: [
[1, "probe_temperature", tuya.valueConverter.divideBy10],
[4, "battery", tuya.valueConverter.raw],
[5, "temperature", tuya.valueConverter.divideBy100],
[101, "sampling_interval", tuya.valueConverter.raw],
[118, "humidity", tuya.valueConverter.divideBy100],
[108, "probe_temperature_calibration", tuya.valueConverter.divideBy10],
[109, "probe_temperature_v0_set", tuya.valueConverter.divideBy10],
[110, "probe_temperature_v1_set", tuya.valueConverter.divideBy10],
[112, "probe_temperature_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), low: tuya.enum(1), high: tuya.enum(2) })],
[114, "temperature_calibration", tuya.valueConverter.divideBy100],
[115, "temperature_v0_set", tuya.valueConverter.divideBy100],
[116, "temperature_v1_set", tuya.valueConverter.divideBy100],
[117, "temperature_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), low: tuya.enum(1), high: tuya.enum(2) })],
[119, "humidity_calibration", tuya.valueConverter.divideBy100],
[120, "humidity_v0_set", tuya.valueConverter.divideBy100],
[121, "humidity_v1_set", tuya.valueConverter.divideBy100],
[122, "humidity_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), low: tuya.enum(1), high: tuya.enum(2) })],
[124, "fertility", tuya.valueConverter.raw],
[125, "fertility_v0_set", tuya.valueConverter.raw],
[126, "fertility_v1_set", tuya.valueConverter.raw],
[127, "fertility_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), low: tuya.enum(1), high: tuya.enum(2) })],
[128, "fertility_calibration", tuya.valueConverter.divideBy1000],
[3, "moisture", tuya.valueConverter.raw],
[129, "moisture_v0_set", tuya.valueConverter.raw],
[130, "moisture_v1_set", tuya.valueConverter.raw],
[131, "moisture_calibration", tuya.valueConverter.divideBy100],
[132, "moisture_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), low: tuya.enum(1), high: tuya.enum(2) })],
],
},
},
{
fingerprint: tuya.fingerprint("Excellux", ["EZ500FS"]),
model: "EZ-500FS",
vendor: "Excellux",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
description: "Split-type sensor for detecting soil moisture, temperature and humidity",
exposes: [
e.battery().withDescription("Battery level"),
e
.enum("probe_temperature_warning", ea.STATE, ["none", "low", "high"])
.withDescription("Probe temperature sensor warning. Low: temperature is lower than v0 and v1. High: temperature is higher than v0 and v1"),
e
.enum("temperature_warning", ea.STATE, ["none", "low", "high"])
.withDescription("Temperature warning. Low: temperature is lower than v0 and v1. High: temperature is higher than v0 and v1"),
e
.enum("humidity_warning", ea.STATE, ["none", "low", "high"])
.withDescription("Humidity warning. Low: humidity is lower than v0 and v1. High: humidity is higher than v0 and v1"),
e
.enum("moisture_warning", ea.STATE, ["none", "low", "high"])
.withDescription("Moisture warning. Low: Moisture is lower than v0 and v1. High: Moisture is higher than v0 and v1"),
e
.numeric("probe_temperature", ea.STATE)
.withValueMin(-40)
.withValueMax(120)
.withValueStep(0.1)
.withUnit("°C")
.withDescription("Probe temperature sensor"),
e.numeric("moisture", ea.STATE).withValueMin(0).withValueMax(100).withValueStep(1).withUnit("%").withDescription("Soil moisture"),
e.temperature(),
e.humidity(),
e
.numeric("sampling_interval", ea.STATE_SET)
.withValueMin(5)
.withValueMax(1200)
.withValueStep(5)
.withUnit("s")
.withDescription("Sampling interval"),
e
.numeric("probe_temperature_calibration", ea.STATE_SET)
.withValueMin(-2)
.withValueMax(2)
.withValueStep(0.1)
.withUnit("°C")
.withDescription("Probe temperature sensor calibration"),
e
.numeric("probe_temperature_v0_set", ea.STATE_SET)
.withValueMin(-40)
.withValueMax(125)
.withValueStep(0.1)
.withUnit("°C")
.withDescription("Probe temperature sensor v0 threshold setting"),
e
.numeric("probe_temperature_v1_set", ea.STATE_SET)
.withValueMin(-40)
.withValueMax(125)
.withValueStep(0.1)
.withUnit("°C")
.withDescription("Probe temperature sensor v1 threshold setting"),
e
.numeric("temperature_calibration", ea.STATE_SET)
.withValueMin(-2)
.withValueMax(2)
.withValueStep(0.01)
.withUnit("°C")
.withDescription("Temperature calibration"),
e
.numeric("temperature_v0_set", ea.STATE_SET)
.withValueMin(-40)
.withValueMax(85)
.withValueStep(0.01)
.withUnit("°C")
.withDescription("Temperature v0 threshold setting"),
e
.numeric("temperature_v1_set", ea.STATE_SET)
.withValueMin(-40)
.withValueMax(85)
.withValueStep(0.01)
.withUnit("°C")
.withDescription("Temperature v1 threshold setting"),
e
.numeric("humidity_calibration", ea.STATE_SET)
.withValueMin(-10)
.withValueMax(10)
.withValueStep(0.01)
.withUnit("%")
.withDescription("Humidity calibration"),
e
.numeric("humidity_v0_set", ea.STATE_SET)
.withValueMin(0)
.withValueMax(100)
.withValueStep(0.01)
.withUnit("%")
.withDescription("Humidity v0 threshold setting"),
e
.numeric("humidity_v1_set", ea.STATE_SET)
.withValueMin(0)
.withValueMax(100)
.withValueStep(0.01)
.withUnit("%")
.withDescription("Humidity v1 threshold setting"),
e
.numeric("moisture_calibration", ea.STATE_SET)
.withValueMin(0.2)
.withValueMax(1.5)
.withValueStep(0.01)
.withDescription("Coefficient 0.2~1.5. Higher for loose‑hard soil, lower for wet soft clay."),
e
.numeric("moisture_v0_set", ea.STATE_SET)
.withValueMin(0)
.withValueMax(100)
.withValueStep(1)
.withUnit("%")
.withDescription("Moisture v0 threshold setting"),
e
.numeric("moisture_v1_set", ea.STATE_SET)
.withValueMin(0)
.withValueMax(100)
.withValueStep(1)
.withUnit("%")
.withDescription("MMoisture v1 threshold setting"),
],
meta: {
tuyaDatapoints: [
[1, "probe_temperature", tuya.valueConverter.divideBy10],
[4, "battery", tuya.valueConverter.raw],
[5, "temperature", tuya.valueConverter.divideBy100],
[101, "sampling_interval", tuya.valueConverter.raw],
[118, "humidity", tuya.valueConverter.divideBy100],
[108, "probe_temperature_calibration", tuya.valueConverter.divideBy10],
[109, "probe_temperature_v0_set", tuya.valueConverter.divideBy10],
[110, "probe_temperature_v1_set", tuya.valueConverter.divideBy10],
[112, "probe_temperature_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), low: tuya.enum(1), high: tuya.enum(2) })],
[114, "temperature_calibration", tuya.valueConverter.divideBy100],
[115, "temperature_v0_set", tuya.valueConverter.divideBy100],
[116, "temperature_v1_set", tuya.valueConverter.divideBy100],
[117, "temperature_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), low: tuya.enum(1), high: tuya.enum(2) })],
[119, "humidity_calibration", tuya.valueConverter.divideBy100],
[120, "humidity_v0_set", tuya.valueConverter.divideBy100],
[121, "humidity_v1_set", tuya.valueConverter.divideBy100],
[122, "humidity_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), low: tuya.enum(1), high: tuya.enum(2) })],
[3, "moisture", tuya.valueConverter.raw],
[129, "moisture_v0_set", tuya.valueConverter.raw],
[130, "moisture_v1_set", tuya.valueConverter.raw],
[131, "moisture_calibration", tuya.valueConverter.divideBy100],
[132, "moisture_warning", tuya.valueConverterBasic.lookup({ none: tuya.enum(0), low: tuya.enum(1), high: tuya.enum(2) })],
],
},
},
];
//# sourceMappingURL=tuya.js.map