zigbee-herdsman-converters
Version:
Collection of device converters to be used with zigbee-herdsman
2,521 lines • 125 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
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 fz = __importStar(require("../converters/fromZigbee"));
const tz = __importStar(require("../converters/toZigbee"));
const exposes = __importStar(require("../lib/exposes"));
const legacy = __importStar(require("../lib/legacy"));
const m = __importStar(require("../lib/modernExtend"));
const reporting = __importStar(require("../lib/reporting"));
const tuya = __importStar(require("../lib/tuya"));
const zosung = __importStar(require("../lib/zosung"));
const e = exposes.presets;
const ea = exposes.access;
const te = tuya.exposes;
const fzZosung = zosung.fzZosung;
const tzZosung = zosung.tzZosung;
const ez = zosung.presetsZosung;
const exposesLocal = {
hour: (name) => e.numeric(name, ea.STATE_SET).withUnit("h").withValueMin(0).withValueMax(23),
minute: (name) => e.numeric(name, ea.STATE_SET).withUnit("m").withValueMin(0).withValueMax(59),
program_temperature: (name) => e.numeric(name, ea.STATE_SET).withUnit("°C").withValueMin(5).withValueMax(35).withValueStep(0.5),
};
// Moes ADCBZI01 curtain robot: travel-range calibration is driven by the Tuya
// vendor attribute tuyaMotorReversal (0xF002) on closuresWindowCovering, used as
// a 3-value control (0 stop, 1 calibrate, 2 calibrate in the opposite direction).
const adcbziCalibrationTo = { stop: 0, calibrate: 1, calibrate_reverse: 2 };
const adcbziCalibrationFrom = { 0: "stop", 1: "calibrate", 2: "calibrate_reverse" };
const tzLocal = {
adcbzi_calibration: {
key: ["calibration"],
convertSet: async (entity, key, value, meta) => {
const v = adcbziCalibrationTo[value];
if (v === undefined)
throw new Error(`calibration: invalid value '${value}'`);
await entity.write("closuresWindowCovering", { tuyaMotorReversal: v });
return { state: { calibration: value } };
},
convertGet: async (entity, key, meta) => {
await entity.read("closuresWindowCovering", ["tuyaMotorReversal"]);
},
},
};
const fzLocal = {
adcbzi_calibration: {
cluster: "closuresWindowCovering",
type: ["attributeReport", "readResponse"],
convert: (model, msg) => {
if (msg.data.tuyaMotorReversal !== undefined) {
return { calibration: adcbziCalibrationFrom[msg.data.tuyaMotorReversal] ?? String(msg.data.tuyaMotorReversal) };
}
},
},
};
exports.definitions = [
{
fingerprint: tuya.fingerprint("TS030F", ["_TZ3210_sxtfesc6", "_TZ3210_rundhkxp"]),
model: "ADCBZI01",
vendor: "Moes",
description: "Curtain Robot",
fromZigbee: [fz.cover_position_tilt, fzLocal.adcbzi_calibration, tuya.fz.datapoints],
toZigbee: [tz.cover_position_tilt, tz.cover_state, tzLocal.adcbzi_calibration, tuya.tz.datapoints],
extend: [tuya.clusters.addTuyaClosuresWindowCoveringCluster()],
exposes: [
e.cover_position(),
e.position(),
e.battery(),
e.illuminance(),
e.enum("work_state", ea.STATE, ["standby", "opening", "closing"]).withDescription("Current work state"),
e.numeric("total_time", ea.STATE).withUnit("s").withDescription("Total operation time"),
e.enum("situation_set", ea.STATE_SET, ["fully_open", "fully_close"]).withDescription("Situation control"),
e.enum("fault", ea.STATE, ["none"]).withDescription("Fault status"),
e.enum("charging_status", ea.STATE, ["none", "uncharged", "charging", "charged"]).withDescription("Charging status"),
e.numeric("open_threshold", ea.STATE_SET).withValueMin(0).withValueMax(100).withDescription("Light threshold for opening"),
e.numeric("close_threshold", ea.STATE_SET).withValueMin(0).withValueMax(100).withDescription("Light threshold for closing"),
e.numeric("curtain_status", ea.STATE_SET).withValueMin(0).withValueMax(255).withDescription("Curtain status"),
e.numeric("total_distance", ea.STATE).withUnit("m").withDescription("Total distance traveled"),
e.numeric("factory_test", ea.STATE).withValueMin(0).withValueMax(100).withDescription("Factory test feedback"),
e.text("custom_week_prog_1", ea.STATE_SET).withDescription("Custom week program 1"),
e.text("custom_week_prog_2", ea.STATE_SET).withDescription("Custom week program 2"),
e.text("custom_week_prog_3", ea.STATE_SET).withDescription("Custom week program 3"),
e.text("custom_week_prog_4", ea.STATE_SET).withDescription("Custom week program 4"),
e
.enum("calibration", ea.ALL, ["stop", "calibrate", "calibrate_reverse"])
.withDescription("Travel-range calibration. With the curtain fully closed, set 'calibrate' and the motor free-runs " +
"toward open; at the fully-open end set 'stop' to halt it and store the limit. Use 'calibrate_reverse' " +
"if it runs the wrong way. Always start fully closed, otherwise the open/close commands end up reversed."),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverterBasic.lookup({ open: 0, stop: 1, close: 2 })], // Control - open/stop/close
[2, "position", tuya.valueConverter.coverPosition], // Percent control - set position (0-100)
[3, "position", tuya.valueConverter.coverPositionInverted], // Percent state - current position (0-100)
[7, "work_state", tuya.valueConverterBasic.lookup({ standby: 0, opening: 1, closing: 2 })], // Work state
[10, "total_time", tuya.valueConverter.raw], // Total time (0-120000)
[11, "situation_set", tuya.valueConverterBasic.lookup({ fully_open: 0, fully_close: 1 })], // Situation control
[12, "fault", tuya.valueConverterBasic.lookup({ none: 0 })], // Fault (only 'none' available)
[13, "battery", tuya.valueConverter.raw], // Battery percentage (0-100)
[
101,
"charging_status",
tuya.valueConverterBasic.lookup({
none: 0,
uncharged: 1,
charging: 2,
charged: 3,
}),
], // Charging status
[103, "custom_week_prog_1", tuya.valueConverter.raw], // Custom week program 1
[104, "custom_week_prog_2", tuya.valueConverter.raw], // Custom week program 2
[105, "custom_week_prog_3", tuya.valueConverter.raw], // Custom week program 3
[106, "custom_week_prog_4", tuya.valueConverter.raw], // Custom week program 4
[107, "illuminance", tuya.valueConverter.raw], // Light intensity (0-100)
[108, "open_threshold", tuya.valueConverter.raw], // Open window threshold (0-100)
[109, "close_threshold", tuya.valueConverter.raw], // Close window threshold (0-100)
[110, "curtain_status", tuya.valueConverter.raw], // Curtain status (0-255)
[111, "factory_test", tuya.valueConverter.raw], // Factory test (0-100)
[112, "total_distance", tuya.valueConverter.raw], // Total running distance in meters
],
},
// Poll EF00 datapoints so battery/illuminance/total_* populate (they were permanently N/A
// without a poll). This unit exposes no genPowerCfg, so bind only closuresWindowCovering.
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["closuresWindowCovering"]);
await reporting.currentPositionLiftPercentage(endpoint);
await tuya.configureMagicPacket(device, coordinatorEndpoint);
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_zxkwaztm"]),
model: "ZHT-S03",
vendor: "Moes",
description: "Zigbee wall thermostat",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "1970" })],
exposes: [
e.child_lock(),
e
.climate()
.withSystemMode(["off", "heat"], ea.STATE)
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withRunningState(["idle", "heat"], ea.STATE)
.withPreset(["schedule", "manual"])
.withLocalTemperature(ea.STATE)
.withLocalTemperatureCalibration(-9.9, 9.9, 0.1, ea.STATE_SET),
e
.numeric("temperature_delta", ea.STATE_SET)
.withUnit("°C")
.withValueMax(10)
.withValueMin(0.1)
.withValueStep(0.1)
.withPreset("default", 1, "Default value")
.withDescription("The delta between local_temperature and current_heating_setpoint to trigger Heat"),
e.enum("working_day", ea.STATE_SET, ["mon_fri", "mon_sat", "mon_sun"]).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)`"),
e.binary("frost_protection", ea.STATE_SET, "ON", "OFF").withDescription("Antifreeze function"),
],
meta: {
tuyaDatapoints: [
[
36,
"running_state",
tuya.valueConverterBasic.lookup({
cool: tuya.enum(2),
heat: tuya.enum(1),
idle: tuya.enum(0),
}),
],
[1, "system_mode", tuya.valueConverterBasic.lookup({ heat: true, off: false })],
[
2,
"preset",
tuya.valueConverterBasic.lookup({
schedule: tuya.enum(0),
manual: tuya.enum(1),
}),
],
[10, "frost_protection", tuya.valueConverter.onOff],
[16, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[24, "local_temperature", tuya.valueConverter.divideBy10],
[40, "child_lock", tuya.valueConverter.lockUnlock],
[109, "local_temperature_calibration", tuya.valueConverter.divideBy10],
[112, "temperature_delta", tuya.valueConverter.divideBy10],
[
31,
"working_day",
tuya.valueConverterBasic.lookup({
mon_fri: tuya.enum(0),
mon_sat: tuya.enum(1),
mon_sun: tuya.enum(2),
}),
],
[67, null, tuya.valueConverter.ZWT198_schedule],
[67, "schedule_weekday", tuya.valueConverter.ZWT198_schedule],
[68, "schedule_holiday", tuya.valueConverter.ZWT198_schedule],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_a1ovdobn", "_TZE200_a1ovdobn"]),
model: "ZS-D1",
vendor: "Moes",
description: "Intelligent dimming switch - 1 gang",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: (device, options) => {
return [
e.switch().withEndpoint("l1"),
e.numeric("brightness_1", ea.STATE_SET).withValueMin(10).withValueMax(1000).withValueStep(1).withDescription("Channel 1 brightness"),
e
.numeric("brightness_min_1", ea.STATE_SET)
.withValueMin(10)
.withValueMax(1000)
.withValueStep(1)
.withDescription("Channel 1 minimum brightness"),
e
.numeric("brightness_max_1", ea.STATE_SET)
.withValueMin(10)
.withValueMax(1000)
.withValueStep(1)
.withDescription("Channel 1 maximum brightness"),
e
.numeric("countdown_1", ea.STATE_SET)
.withValueMin(0)
.withValueMax(86400)
.withValueStep(1)
.withUnit("s")
.withDescription("Channel 1 countdown"),
e.enum("relay_status", ea.STATE_SET, ["off", "on", "memory"]).withDescription("Relay mode after power restoration"),
e.enum("light_mode", ea.STATE_SET, ["none", "relay", "pos"]).withDescription("Indicator light mode"),
e.binary("switch_backlight", ea.STATE_SET, "ON", "OFF").withDescription("Backlight switch"),
];
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "brightness_1", tuya.valueConverter.raw],
[3, "brightness_min_1", tuya.valueConverter.raw],
[5, "brightness_max_1", tuya.valueConverter.raw],
[6, "countdown_1", tuya.valueConverter.raw],
[
14,
"relay_status",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
on: tuya.enum(1),
memory: tuya.enum(2),
}),
],
[
21,
"light_mode",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
relay: tuya.enum(1),
pos: tuya.enum(2),
}),
],
[26, "switch_backlight", tuya.valueConverter.onOff],
],
},
endpoint: (device) => {
return {
l1: 1,
};
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_rlqamjhp"]),
model: "ZS-D2",
vendor: "Moes",
description: "Intelligent dimming switch - 2 gang",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: (device, options) => {
return [
e.switch().withEndpoint("l1"),
e.numeric("brightness_1", ea.STATE_SET).withValueMin(10).withValueMax(1000).withValueStep(1).withDescription("Channel 1 brightness"),
e
.numeric("brightness_min_1", ea.STATE_SET)
.withValueMin(10)
.withValueMax(1000)
.withValueStep(1)
.withDescription("Channel 1 minimum brightness"),
e
.numeric("brightness_max_1", ea.STATE_SET)
.withValueMin(10)
.withValueMax(1000)
.withValueStep(1)
.withDescription("Channel 1 maximum brightness"),
e
.numeric("countdown_1", ea.STATE_SET)
.withValueMin(0)
.withValueMax(86400)
.withValueStep(1)
.withUnit("s")
.withDescription("Channel 1 countdown"),
e.switch().withEndpoint("l2"),
e.numeric("brightness_2", ea.STATE_SET).withValueMin(10).withValueMax(1000).withValueStep(1).withDescription("Channel 2 brightness"),
e
.numeric("brightness_min_2", ea.STATE_SET)
.withValueMin(10)
.withValueMax(1000)
.withValueStep(1)
.withDescription("Channel 2 minimum brightness"),
e
.numeric("brightness_max_2", ea.STATE_SET)
.withValueMin(10)
.withValueMax(1000)
.withValueStep(1)
.withDescription("Channel 2 maximum brightness"),
e
.numeric("countdown_2", ea.STATE_SET)
.withValueMin(0)
.withValueMax(86400)
.withValueStep(1)
.withUnit("s")
.withDescription("Channel 2 countdown"),
e.enum("relay_status", ea.STATE_SET, ["off", "on", "memory"]).withDescription("Relay mode after power restoration"),
e.enum("light_mode", ea.STATE_SET, ["none", "relay", "pos"]).withDescription("Indicator light mode"),
e.binary("switch_backlight", ea.STATE_SET, "ON", "OFF").withDescription("Backlight switch"),
];
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "brightness_1", tuya.valueConverter.raw],
[3, "brightness_min_1", tuya.valueConverter.raw],
[5, "brightness_max_1", tuya.valueConverter.raw],
[6, "countdown_1", tuya.valueConverter.raw],
[7, "state_l2", tuya.valueConverter.onOff],
[8, "brightness_2", tuya.valueConverter.raw],
[9, "brightness_min_2", tuya.valueConverter.raw],
[11, "brightness_max_2", tuya.valueConverter.raw],
[12, "countdown_2", tuya.valueConverter.raw],
[
14,
"relay_status",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
on: tuya.enum(1),
memory: tuya.enum(2),
}),
],
[
21,
"light_mode",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
relay: tuya.enum(1),
pos: tuya.enum(2),
}),
],
[26, "switch_backlight", tuya.valueConverter.onOff],
],
},
endpoint: (device) => {
return {
l1: 1,
l2: 1,
};
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_vizxbhco"]),
model: "ZS-D3",
vendor: "Moes",
description: "Intelligent dimming switch - 3 gang",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: (device, options) => {
return [
e.switch().withEndpoint("l1"),
e.switch().withEndpoint("l2"),
e.switch().withEndpoint("l3"),
e.numeric("brightness_1", ea.STATE_SET).withValueMin(10).withValueMax(1000).withValueStep(1).withDescription("Channel 1 brightness"),
e
.numeric("brightness_min_1", ea.STATE_SET)
.withValueMin(10)
.withValueMax(1000)
.withValueStep(1)
.withDescription("Channel 1 minimum brightness"),
e
.numeric("brightness_max_1", ea.STATE_SET)
.withValueMin(10)
.withValueMax(1000)
.withValueStep(1)
.withDescription("Channel 1 maximum brightness"),
e
.numeric("countdown_1", ea.STATE_SET)
.withValueMin(0)
.withValueMax(86400)
.withValueStep(1)
.withUnit("s")
.withDescription("Channel 1 countdown"),
e.numeric("brightness_2", ea.STATE_SET).withValueMin(10).withValueMax(1000).withValueStep(1).withDescription("Channel 2 brightness"),
e
.numeric("brightness_min_2", ea.STATE_SET)
.withValueMin(10)
.withValueMax(1000)
.withValueStep(1)
.withDescription("Channel 2 minimum brightness"),
e
.numeric("brightness_max_2", ea.STATE_SET)
.withValueMin(10)
.withValueMax(1000)
.withValueStep(1)
.withDescription("Channel 2 maximum brightness"),
e
.numeric("countdown_2", ea.STATE_SET)
.withValueMin(0)
.withValueMax(86400)
.withValueStep(1)
.withUnit("s")
.withDescription("Channel 2 countdown"),
e.enum("relay_status", ea.STATE_SET, ["off", "on", "memory"]).withDescription("Relay mode after power restoration"),
e.numeric("brightness_3", ea.STATE_SET).withValueMin(10).withValueMax(1000).withValueStep(1).withDescription("Channel 3 brightness"),
e
.numeric("brightness_min_3", ea.STATE_SET)
.withValueMin(10)
.withValueMax(1000)
.withValueStep(1)
.withDescription("Channel 3 minimum brightness"),
e
.numeric("brightness_max_3", ea.STATE_SET)
.withValueMin(10)
.withValueMax(1000)
.withValueStep(1)
.withDescription("Channel 3 maximum brightness"),
e
.numeric("countdown_3", ea.STATE_SET)
.withValueMin(0)
.withValueMax(86400)
.withValueStep(1)
.withUnit("s")
.withDescription("Channel 3 countdown"),
e.enum("light_mode", ea.STATE_SET, ["none", "relay", "pos"]).withDescription("Indicator light mode"),
e.binary("switch_backlight", ea.STATE_SET, "ON", "OFF").withDescription("Backlight switch"),
];
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "state_l1", tuya.valueConverter.onOff],
[2, "brightness_1", tuya.valueConverter.raw],
[3, "brightness_min_1", tuya.valueConverter.raw],
[5, "brightness_max_1", tuya.valueConverter.raw],
[6, "countdown_1", tuya.valueConverter.raw],
[7, "state_l2", tuya.valueConverter.onOff],
[8, "brightness_2", tuya.valueConverter.raw],
[9, "brightness_min_2", tuya.valueConverter.raw],
[11, "brightness_max_2", tuya.valueConverter.raw],
[12, "countdown_2", tuya.valueConverter.raw],
[
14,
"relay_status",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
on: tuya.enum(1),
memory: tuya.enum(2),
}),
],
[15, "state_l3", tuya.valueConverter.onOff],
[16, "brightness_3", tuya.valueConverter.raw],
[17, "brightness_min_3", tuya.valueConverter.raw],
[19, "brightness_max_3", tuya.valueConverter.raw],
[20, "countdown_3", tuya.valueConverter.raw],
[
21,
"light_mode",
tuya.valueConverterBasic.lookup({
none: tuya.enum(0),
relay: tuya.enum(1),
pos: tuya.enum(2),
}),
],
[26, "switch_backlight", tuya.valueConverter.onOff],
],
},
endpoint: (device) => {
return {
l1: 1,
l2: 1,
l3: 1,
};
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_upt8lzi0"]),
model: "ZS-SF-EUC-WH-MS",
vendor: "Moes",
description: "Star feather Zigbee curtain switch",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
options: [exposes.options.invert_cover()],
exposes: [te.coverPosition()],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[2, "position", tuya.valueConverter.coverPosition],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_koxaopnk"]),
model: "ZC-LS02",
vendor: "Moes",
description: "Roller blind motor",
// This motor never reports battery spontaneously; DP 13 (battery) is only sent in
// response to a dataQuery. Without polling, `battery` stays null forever. Confirmed
// on hardware: dp 13 -> 100 only arrives after a dataQuery. Poll periodically and on
// device announce so the battery level is reported reliably.
// respondToMcuVersionResponse is left at its default (false): with it enabled, one
// of the units gets stuck in an mcuVersionRequest/Response ping-pong (~3x/s) that
// floods the network/MQTT (see https://github.com/Koenkk/zigbee2mqtt/issues/28367).
extend: [
tuya.modernExtend.tuyaBase({
dp: true,
queryOnConfigure: true,
queryOnDeviceAnnounce: true,
queryIntervalSeconds: 24 * 60 * 60,
}),
],
exposes: [te.coverPosition(), te.motorDirection(), e.battery()],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[2, "position", tuya.valueConverter.coverPositionInverted],
[3, "position", tuya.valueConverter.coverPositionInverted],
[5, "motor_direction", tuya.valueConverterBasic.lookup({ normal: false, reversed: true })], // maybe enum ?
[13, "battery", tuya.valueConverter.raw],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_stvgmdjz", "_TZE200_ydkqbmpt", "_TZE200_z3u99qxt"]),
model: "SFL02-Z-1",
vendor: "Moes",
description: "Star feather smart switch 1 gang",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "1970" })],
whiteLabel: [tuya.whitelabel("Nova Digital", "TPZ-1", "Topazio smart switch 1 gang", ["_TZE200_ydkqbmpt"])],
exposes: [
tuya.exposes.backlightModeOffOn().withAccess(ea.STATE_SET),
e.switch().withEndpoint("l1").setAccess("state", ea.STATE_SET),
tuya.exposes.countdown().withEndpoint("l1"),
e
.numeric("momentary_1", ea.STATE_SET)
.withValueMin(0)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Momentary switch timer (0=disable)"),
e.power_on_behavior().withAccess(ea.STATE_SET),
exposes.enum("mode", ea.STATE_SET, ["switch_1", "scene_1"]).withEndpoint("l1").withDescription("Switch1 mode"),
e.action(["scene_1"]),
exposes.enum("indicator_status", ea.ALL, ["off", "relay", "invert"]).withDescription("Indicator status"),
exposes.enum("induction_mode", ea.ALL, ["ON", "OFF"]).withDescription("Induction mode"),
exposes.enum("vibration_mode", ea.ALL, ["Gear 0", "Gear 1", "Gear 2", "Gear 3"]).withDescription("Vibration"),
],
endpoint: (device) => {
return {
l1: 1,
state: 1,
backlight: 1,
induction: 1,
};
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "action", tuya.valueConverter.static("scene_1")],
[18, "mode_l1", tuya.valueConverterBasic.lookup({ switch_1: tuya.enum(0), scene_1: tuya.enum(1) })],
[24, "state_l1", tuya.valueConverter.onOff],
[30, "countdown_l1", tuya.valueConverter.countdown],
[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_mode", tuya.valueConverter.onOff],
[
104,
"vibration_mode",
tuya.valueConverterBasic.lookup({ "Gear 0": tuya.enum(0), "Gear 1": tuya.enum(1), "Gear 2": tuya.enum(2), "Gear 3": tuya.enum(3) }),
],
[105, "momentary_1", tuya.valueConverter.countdown],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_uenof8jd", "_TZE200_tzyy0rtq", "_TZE200_hktk6hze"]),
model: "SFL02-Z-2",
vendor: "Moes",
description: "Star feather smart switch 2 gangs",
whiteLabel: [tuya.whitelabel("Nova Digital", "TPZ-2", "Topazio smart switch 2 gangs", ["_TZE200_hktk6hze"])],
exposes: [
tuya.exposes.backlightModeOffOn().withAccess(ea.STATE_SET),
e.switch().withEndpoint("l1").setAccess("state", ea.STATE_SET),
e.switch().withEndpoint("l2").setAccess("state", ea.STATE_SET),
tuya.exposes.countdown().withEndpoint("l1"),
tuya.exposes.countdown().withEndpoint("l2"),
e
.numeric("momentary_1", ea.STATE_SET)
.withValueMin(0)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Momentary switch timer (0=disable)"),
e
.numeric("momentary_2", ea.STATE_SET)
.withValueMin(0)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Momentary switch timer (0=disable)"),
e.power_on_behavior().withAccess(ea.STATE_SET),
exposes.enum("mode", ea.STATE_SET, ["switch_1", "scene_1"]).withEndpoint("l1").withDescription("Switch1 mode"),
exposes.enum("mode", ea.STATE_SET, ["switch_2", "scene_2"]).withEndpoint("l2").withDescription("Switch2 mode"),
e.action(["scene_1", "scene_2"]),
exposes.enum("indicator_status", ea.ALL, ["off", "relay", "invert"]).withDescription("Indicator status"),
exposes.enum("induction_mode", ea.ALL, ["ON", "OFF"]).withDescription("Induction mode"),
exposes.enum("vibration_mode", ea.ALL, ["Gear 0", "Gear 1", "Gear 2", "Gear 3"]).withDescription("Vibration"),
],
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "1970" })],
endpoint: (device) => {
return { l1: 1, l2: 1, state: 1, backlight: 1 };
},
meta: {
multiEndpoint: true,
tuyaDatapoints: [
[1, "action", tuya.valueConverter.static("scene_1")],
[2, "action", tuya.valueConverter.static("scene_2")],
[18, "mode_l1", tuya.valueConverterBasic.lookup({ switch_1: tuya.enum(0), scene_1: tuya.enum(1) })],
[19, "mode_l2", tuya.valueConverterBasic.lookup({ switch_2: tuya.enum(0), scene_2: tuya.enum(1) })],
[24, "state_l1", tuya.valueConverter.onOff],
[25, "state_l2", tuya.valueConverter.onOff],
[30, "countdown_l1", tuya.valueConverter.countdown],
[31, "countdown_l2", tuya.valueConverter.countdown],
[37, "indicator_status", tuya.valueConverterBasic.lookup({ off: tuya.enum(0), relay: tuya.enum(1), invert: tuya.enum(2) })],
[36, "backlight_mode", tuya.valueConverter.onOff],
[38, "power_on_behavior", tuya.valueConverter.powerOnBehaviorEnum],
[103, "induction_mode", tuya.valueConverter.onOff],
[
104,
"vibration_mode",
tuya.valueConverterBasic.lookup({ "Gear 0": tuya.enum(0), "Gear 1": tuya.enum(1), "Gear 2": tuya.enum(2), "Gear 3": tuya.enum(3) }),
],
[105, "momentary_1", tuya.valueConverter.countdown],
[106, "momentary_2", tuya.valueConverter.countdown],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_rd8cdssd", "_TZE200_wv9ukqca", "_TZE200_zo0cfekv"]),
model: "SFL02-Z-3",
vendor: "Moes",
description: "Star feather smart switch 3 gangs",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "1970" })],
whiteLabel: [tuya.whitelabel("Nova Digital", "TPZ-3", "Topazio smart switch 3 gangs", ["_TZE200_rd8cdssd"])],
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),
tuya.exposes.countdown().withEndpoint("l1"),
tuya.exposes.countdown().withEndpoint("l2"),
tuya.exposes.countdown().withEndpoint("l3"),
e
.numeric("momentary_1", ea.STATE_SET)
.withValueMin(0)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Momentary switch timer (0=disable)"),
e
.numeric("momentary_2", ea.STATE_SET)
.withValueMin(0)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Momentary switch timer (0=disable)"),
e
.numeric("momentary_3", ea.STATE_SET)
.withValueMin(0)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Momentary switch timer (0=disable)"),
exposes.enum("mode", ea.STATE_SET, ["switch_1", "scene_1"]).withEndpoint("l1").withDescription("Switch1 mode"),
exposes.enum("mode", ea.STATE_SET, ["switch_2", "scene_2"]).withEndpoint("l2").withDescription("Switch2 mode"),
exposes.enum("mode", ea.STATE_SET, ["switch_3", "scene_3"]).withEndpoint("l3").withDescription("Switch3 mode"),
e.action(["scene_1", "scene_2", "scene_3"]),
tuya.exposes.backlightModeOffOn().withAccess(ea.STATE_SET),
e.power_on_behavior().withAccess(ea.STATE_SET),
exposes.enum("indicator_status", ea.ALL, ["off", "relay", "invert"]).withDescription("Indicator status"),
exposes.enum("induction_mode", ea.ALL, ["ON", "OFF"]).withDescription("Induction mode"),
exposes.enum("vibration_mode", ea.ALL, ["Gear 0", "Gear 1", "Gear 2", "Gear 3"]).withDescription("Vibration"),
],
endpoint: (device) => {
return {
l1: 1,
l2: 1,
l3: 1,
l4: 1,
state: 1,
backlight: 1,
induction: 1,
};
},
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")],
[18, "mode_l1", tuya.valueConverterBasic.lookup({ switch_1: tuya.enum(0), scene_1: tuya.enum(1) })],
[19, "mode_l2", tuya.valueConverterBasic.lookup({ switch_2: tuya.enum(0), scene_2: tuya.enum(1) })],
[20, "mode_l3", tuya.valueConverterBasic.lookup({ switch_3: tuya.enum(0), scene_3: tuya.enum(1) })],
[24, "state_l1", tuya.valueConverter.onOff],
[25, "state_l2", tuya.valueConverter.onOff],
[26, "state_l3", tuya.valueConverter.onOff],
[30, "countdown_l1", tuya.valueConverter.countdown],
[31, "countdown_l2", tuya.valueConverter.countdown],
[32, "countdown_l3", tuya.valueConverter.countdown],
[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_mode", tuya.valueConverter.onOff],
[
104,
"vibration_mode",
tuya.valueConverterBasic.lookup({ "Gear 0": tuya.enum(0), "Gear 1": tuya.enum(1), "Gear 2": tuya.enum(2), "Gear 3": tuya.enum(3) }),
],
[105, "momentary_1", tuya.valueConverter.countdown],
[106, "momentary_2", tuya.valueConverter.countdown],
[107, "momentary_3", tuya.valueConverter.countdown],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_dq8bu0pt", "_TZE200_hmabvy81", "_TZE200_9dhenr94"]),
model: "SFL02-Z-4",
vendor: "Moes",
description: "Star feather smart switch 4 gangs",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "1970" })],
whiteLabel: [tuya.whitelabel("Nova Digital", "TPZ-4", "Topazio smart switch 4 gangs", ["_TZE200_hmabvy81"])],
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),
tuya.exposes.countdown().withEndpoint("l1"),
tuya.exposes.countdown().withEndpoint("l2"),
tuya.exposes.countdown().withEndpoint("l3"),
tuya.exposes.countdown().withEndpoint("l4"),
e
.numeric("momentary_1", ea.STATE_SET)
.withValueMin(0)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Momentary switch timer (0=disable)"),
e
.numeric("momentary_2", ea.STATE_SET)
.withValueMin(0)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Momentary switch timer (0=disable)"),
e
.numeric("momentary_3", ea.STATE_SET)
.withValueMin(0)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Momentary switch timer (0=disable)"),
e
.numeric("momentary_4", ea.STATE_SET)
.withValueMin(0)
.withValueMax(3600)
.withValueStep(1)
.withUnit("s")
.withDescription("Momentary switch timer (0=disable)"),
exposes.enum("mode", ea.STATE_SET, ["switch_1", "scene_1"]).withEndpoint("l1").withDescription("Switch1 mode"),
exposes.enum("mode", ea.STATE_SET, ["switch_2", "scene_2"]).withEndpoint("l2").withDescription("Switch2 mode"),
exposes.enum("mode", ea.STATE_SET, ["switch_3", "scene_3"]).withEndpoint("l3").withDescription("Switch3 mode"),
exposes.enum("mode", ea.STATE_SET, ["switch_4", "scene_4"]).withEndpoint("l4").withDescription("Switch4 mode"),
e.action(["scene_1", "scene_2", "scene_3", "scene_4"]),
tuya.exposes.backlightModeOffOn().withAccess(ea.STATE_SET),
e.power_on_behavior().withAccess(ea.STATE_SET),
exposes.enum("indicator_status", ea.ALL, ["off", "relay", "invert"]).withDescription("Indicator status"),
exposes.enum("induction_mode", ea.ALL, ["ON", "OFF"]).withDescription("Induction mode"),
exposes.enum("vibration_mode", ea.ALL, ["Gear 0", "Gear 1", "Gear 2", "Gear 3"]).withDescription("Vibration"),
],
endpoint: (device) => {
return {
l1: 1,
l2: 1,
l3: 1,
l4: 1,
state: 1,
backlight: 1,
induction: 1,
};
},
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")],
[18, "mode_l1", tuya.valueConverterBasic.lookup({ switch_1: tuya.enum(0), scene_1: tuya.enum(1) })],
[19, "mode_l2", tuya.valueConverterBasic.lookup({ switch_2: tuya.enum(0), scene_2: tuya.enum(1) })],
[20, "mode_l3", tuya.valueConverterBasic.lookup({ switch_3: tuya.enum(0), scene_3: tuya.enum(1) })],
[21, "mode_l4", tuya.valueConverterBasic.lookup({ switch_4: tuya.enum(0), scene_4: 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],
[30, "countdown_l1", tuya.valueConverter.countdown],
[31, "countdown_l2", tuya.valueConverter.countdown],
[32, "countdown_l3", tuya.valueConverter.countdown],
[33, "countdown_l4", tuya.valueConverter.countdown],
[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_mode", tuya.valueConverter.onOff],
[
104,
"vibration_mode",
tuya.valueConverterBasic.lookup({ "Gear 0": tuya.enum(0), "Gear 1": tuya.enum(1), "Gear 2": tuya.enum(2), "Gear 3": tuya.enum(3) }),
],
[105, "momentary_1", tuya.valueConverter.countdown],
[106, "momentary_2", tuya.valueConverter.countdown],
[107, "momentary_3", tuya.valueConverter.countdown],
[108, "momentary_4", tuya.valueConverter.countdown],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_ivdc0kwl"]),
model: "ZTRV-S01",
vendor: "Moes",
description: "Zigbee temperature control valve",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "2000" })],
exposes: [
e
.climate()
.withLocalTemperature(ea.STATE)
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withLocalTemperatureCalibration(-10, 10, 0.5, ea.STATE_SET)
.withPreset(["auto", "manual", "off", "on"])
.withRunningState(["idle", "heat"], ea.STATE),
e.window_detection_bool(),
e.child_lock(),
e.binary("frost_protection", ea.STATE_SET, "ON", "OFF"),
e.binary("eco", ea.STATE_SET, "ON", "OFF").withDescription("Eco mode"),
e.eco_temperature().withValueMin(5).withValueMax(35).withValueStep(0.5),
e.binary("window", ea.STATE, "OPENED", "CLOSED").withDescription("Window status closed or open "),
...tuya.exposes.scheduleAllDays(ea.STATE_SET, "HH:MM/C HH:MM/C HH:MM/C HH:MM/C"),
e.min_temperature().withValueMin(5).withValueMax(15),
e.max_temperature().withValueMin(20).withValueMax(35),
e.position(),
e.battery(),
e.enum("screen_orientation", ea.STATE_SET, ["0", "1"]).withDescription("Screen orientation"),
],
meta: {
tuyaDatapoints: [
[
2,
"preset",
tuya.valueConverterBasic.lookup({
auto: tuya.enum(0),
manual: tuya.enum(1),
off: tuya.enum(2),
on: tuya.enum(3),
holiday: tuya.enum(4),
}),
],
[3, "running_state", tuya.valueConverterBasic.lookup({ heat: 0, idle: 1 })],
[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.raw()],
[
15,
"window",
tuya.valueConverterBasic.lookup({
CLOSED: tuya.enum(0),
OPENED: tuya.enum(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)],
[
36,
"frost_protection",
tuya.valueConverterBasic.lookup({
ON: true,
OFF: false,
}),
],
[47, "local_temperature_calibration", tuya.valueConverter.divideBy10],
[102, "position", tuya.valueConverter.raw],
[
103,
"screen_orientation",
tuya.valueConverterBasic.lookup({
0: tuya.enum(0),
1: tuya.enum(1),
}),
],
[105, "eco_temperature", tuya.valueConverter.divideBy10],
[106, "eco", tuya.valueConverter.onOff],
//[107, "holiday_start_stop", tuya.valueConverter.raw],(Invalid situations may occur)
[108, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[109, "local_temperature", tuya.valueConverter.divideBy10],
],
},
},
{
fingerprint: tuya.fingerprint("TS011F", ["_TZ3000_cymsnfvf", "_TZ3000_2xlvlnez", "_TZ3210_2uk4z8ce"]),
model: "ZP-LZ-FR2U",
vendor: "Moes",
description: "Zigbee 3.0 dual USB wireless socket plug",
extend: [
tuya.modernExtend.tuyaBase(),
tuya.modernExtend.tuyaOnOff({
powerOutageMemory: true,
indicatorMode: true,
childLock: true,
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"]);
await reporting.onOff(device.getEndpoint(1));
await reporting.onOff(device.getEndpoint(2));
},
},
{
fingerprint: [...tuya.fingerprint("TS0121", ["_TYZB01_iuepbmpv"]), ...tuya.fingerprint("TS011F", ["_TZ3000_zmy1waw6", "_TZ3000_bkfe0bab"])],
model: "MS-104Z",
description: "Smart light switch module (1 gang)",
vendor: "Moes",
extend: [tuya.modernExtend.tuyaBase(), tuya.modernExtend.tuyaOnOff()],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff"]);
try {
// Fails for some devices.
// https://github.com/Koenkk/zigbee2mqtt/issues/4598
await reporting.onOff(endpoint);
}
catch {
/* empty */
}
},
},
{
fingerprint: tuya.fingerprint("TS011F", ["_TZ3000_pmz6mjyu", "_TZ3000_iv6ph5tr"]),
model: "MS-104BZ",
description: "Smart light switch module (2 gang)",
vendor: "Moes",
extend: [tuya.modernExtend.tuyaBase(), tuya.modernExtend.tuyaOnOff({ endpoints: ["l1", "l2"] })],
meta: { multiEndpoint: true },
endpoint: (device) => {
return { l1: 1, l2: 2 };
},
configure: async (device, coordinatorEndpoint) => {
const endpoint1 = device.getEndpoint(1);
await reporting.bind(endpoint1, coordinatorEndpoint, ["genOnOff"]);
await reporting.onOff(endpoint1);
const endpoint2 = device.getEndpoint(2);
await reporting.bind(endpoint2, coordinatorEndpoint, ["genOnOff"]);
await reporting.onOff(endpoint2);
},
whiteLabel: [tuya.whitelabel("KnockautX", "FMS2C017", "2 gang switch", ["_TZ3000_iv6ph5tr"])],
},
{
zigbeeModel: ["TS0112"],
model: "ZK-EU-2U",
vendor: "Moes",
description: "Zigbee 3.0 dual USB wireless socket plug",
extend: [m.onOff({ endpointNames: ["l1", "l2"] })],
meta: { multiEndpoint: true },
endpoint: (device) => {
const hasEndpoint2 = !!device.getEndpoint(2);
return { l1: 1, l2: hasEndpoint2 ? 2 : 7 };
},
},
{
fingerprint: tuya.fingerprint("TS0601", [
"_TZE200_aoclfnxz",
"_TZE200_ztvwu4nk",
"_TZE204_5toc8efa",
"_TZE200_5toc8efa",
"_TZE200_ye5jkfsb",
"_TZE284_ye5jkfsb",
"_TZE204_aoclfnxz",
"_TZE200_u9bfwha0",
"_TZE204_u9bfwha0",
]),
model: "BHT-002",
vendor: "Moes",
description: "Moes BHT series Thermostat",
fromZigbee: [legacy.fz.moes_thermostat],
toZigbee: [
legacy.tz.moes_thermostat_child_lock,
legacy.tz.moes_thermostat_current_heating_setpoint,
legacy.tz.moes_thermostat_mode,
legacy.tz.moes_thermostat_standby,
legacy.tz.moes_thermostat_sensor,
legacy.tz.moes_thermostat_calibration,
legacy.tz.moes_thermostat_deadzone_temperature,
legacy.tz.moes_thermostat_max_temperature_limit,
legacy.tz.moes_thermostat_min_temperature_limit,
legacy.tz.moes_thermostat_program_schedule,
],
whiteLabel: [tuya.whitelabel("Moes", "BHT-002/BHT-006", "Smart heating thermostat", ["_TZE204_aoclfnxz"])],
exposes: (device, options) => {
const heatingStepSize = device.manufacturerName === "_TZE204_5toc8efa" ? 0.5 : 1;
const runningStates = device.manufacturerName === "_TZE200_aoclfnxz" ? ["idle", "heat"] : ["idle", "heat", "cool"];
return [
e.child_lock(),
e.deadzone_temperature(),
e.max_temperature_limit().withValueMax(80),
e.min_temperature_limit(),
e
.climate()
.withSetpoint("current_heating_setpoint", 5, 90, heatingStepSize, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withLocalTemperatureCalibration(-30, 30, ["_TZE204_aoclfnxz", "_TZE204_ltwbm23f"].includes(device.manufacturerName) ? 1 : 0.1, ea.STATE_SET)
.withSystemMode(["off", "heat"], ea.STATE_SET)
.withRunningState(runningStates, ea.STATE)
.withPreset(["hold", "program"]),
e.temperature_sensor_select(["IN", "AL", "OU"]),
e
.composite("program", "program", ea.STATE_SET)
.withDescription("Time of day and setpoint to use when in program mode")
.withFeature(exposesLocal.hour("weekdays_p1_hour"))
.withFeature(exposesLocal.minute("weekdays_p1_minute"))
.withFeature(exposesLocal.program_temperature("weekdays_p1_temperature"))
.withFeature(exposesLocal.hour("weekdays_p2_hour"))
.withFeature(exposesLocal.minute("weekdays_p2_minute"))
.withFeature(exposesLocal.program_temperature("weekdays_p2_temperature"))
.withFeature(exposesLocal.hour("weekdays_p3_hour"))
.withFeature(exposesLocal.minute("weekdays_p3_minute"))
.withFeature(exposesLocal.program_temperature("weekdays_p3_temperature"))
.withFeature(exposesLocal.hour("weekdays_p4_hour"))
.withFeature(exposesLocal.minute("weekdays_p4_minute"))
.withFeature(exposesLocal.program_temperature("weekdays_p4_temperature"))
.withFeature(exposesLocal.hour("saturday_p1_hour"))
.withFeature(exposesLocal.minute("saturday_p1_minute"))
.withFeature(exposesLocal.program_temperature("saturday_p1_temperature"))
.withFeature(exposesLocal.hour("saturday_p2_hour"))
.withFeature(exposesLocal.minute("saturday_p2_minute"))
.withFeature(exposesLocal.program_temperature("saturday_p2_temperature"))
.withFeature(exposesLocal.hour("saturday_p3_hour"))
.withFeature(exposesLocal.minute("saturday_p3_minute"))
.withFeature(exposesLocal.program_temperature("saturday_p3_temperature"))
.withFeature(exposesLocal.hour("saturday_p4_hour"))
.withFeature(exposesLocal.minute("saturday_p4_minute"))
.withFeature(exposesLocal.program_temperature("saturday_p4_temperature"))
.withFeature(exposesLocal.hour("sunday_p1_hour"))
.withFeature(exposesLocal.minute("sunday_p1_minute"))
.withFeature(exposesLocal.program_temperature("sunday_p1_temperature"))
.withFeature(exposesLocal.hour("sunday_p2_hour"))
.withFeature(exposesLocal.minute("sunday_p2_minute"))
.withFeature(exposesLocal.program_temperature("sunday_p2_temperature"))
.withFeature(exposesLocal.hour("sunday_p3_hour"))
.withFeature(exposesLocal.minute("sunday_p3_minute"))
.withFeature(exposesLocal.program_temperature("sunday_p3_temperature"))
.withFeature(exposesLocal.hour("sunday_p4_hour"))
.withFeature(exposesLocal.minute("sunday_p4_minute"))
.withFeature(exposesLocal.program_temperature("sunday_p4_temperature")),
];
},
extend: [tuya.modernExtend.tuyaBase({ forceTimeUpdates: true, timeStart: "1970" })],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_amp6tsvy", "_TZE200_tviaymwx"]),
model: "ZTS-EU_1gang",
vendor: "Moes",
description: "Wall touch light switch (1 gang)",
exposes: [
e.switch().setAccess("state", ea.STATE_SET),
e.enum("indicate_light", ea.STATE_SET, Object.values(legacy.moesSwitch.indicateLight)).withDescription("Indicator light status"),
e
.enum("power_on_behavior", ea.STATE_SET, Object.values(legacy.moesSwitch.powerOnBehavior))
.withDescription("Controls the behavior when the device is powered on"),
],
fromZigbee: [legacy.fz.tuya_switch, legacy.fz.moes_switch],
toZigbee: [legacy.tz.tuya_switch_state, legacy.tz.moes_switch],
extend: [tuya.modernExtend.tuyaBase({ forceTimeUpdates: true })],
configure: async (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("TS0601", ["_TZE200_g1ib5ldv"]),
model: "ZTS-EU_2gang",
vendor: "Moes",
description: "Wall touch light switch (2 gang)",
exposes: [
e.switch().withEndpoint("l1").setAccess("state", ea.STATE_SET),
e.switch().withEndpoint("l2").setAccess("state", ea.STATE_SET),
e.enum("indicate_light", ea.STATE_SET, Object.values(legacy.moesSwitch.indicateLight)).withDescription("Indicator light status"),
e
.enum("power_on_behavior", ea.STATE_SET, Object.values(legacy.moesSwitch.powerOnBehavior))
.withDescription("Controls the behavior when the device is powered on"),
],
fromZigbee: [legacy.fz.tuya_switch, legacy.fz.moes_switch],
toZigbee: [legacy.tz.tuya_switch_state, legacy.tz.moes_switch],
extend: [tuya.modernExtend.tuyaBase({ forceTimeUpdates: true })],
meta: { multiEndpoint: true },
endpoint: (device) => {
// Endpoint selection is made in tuya_switch_state
return { l1: 1, l2: 1 };
},
configure: async (device, coordinatorEndpoint) => {
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
if (device.getEndpoint(2))
await reporting.bind(device.getEndpoint(2), 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("TS0601", ["_TZE200_tz32mtza"]),
model: "ZTS-EU_3gang",
vendor: "Moes",
description: "Wall touch light switch (3 gang)",
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.enum("indicate_light", ea.STATE_SET, Object.values(legacy.moesSwitch.indicateLight)).withDescription("Indicator light status"),
e
.enum("power_on_behavior", ea.STATE_SET, Object.values(legacy.moesSwitch.powerOnBehavior))
.withDescription("Controls the behavior when the device is powered on"),
],
fromZigbee: [legacy.fz.tuya_switch, legacy.fz.moes_switch],
toZigbee: [legacy.tz.tuya_switch_state, legacy.tz.moes_switch],
extend: [tuya.modernExtend.tuyaBase({ forceTimeUpdates: true })],
meta: { multiEndpoint: true },
endpoint: (device) => {
// Endpoint selection is made in tuya_switch_state
return { l1: 1, l2: 1, l3: 1 };
},
configure: async (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"]);
// 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("TS0601", ["_TZE200_1ozguk6x"]),
model: "ZTS-EU_4gang",
vendor: "Moes",
description: "Wall touch light switch (4 gang)",
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),
e.enum("indicate_light", ea.STATE_SET, Object.values(legacy.moesSwitch.indicateLight)).withDescription("Indicator light status"),
e
.enum("power_on_behavior", ea.STATE_SET, Object.values(legacy.moesSwitch.powerOnBehavior))
.withDescription("Controls the behavior when the device is powered on"),
],
fromZigbee: [legacy.fz.tuya_switch, legacy.fz.moes_switch],
toZigbee: [legacy.tz.tuya_switch_state, legacy.tz.moes_switch],
extend: [tuya.modernExtend.tuyaBase({ forceTimeUpdates: true })],
meta: { multiEndpoint: true },
endpoint: (device) => {
// Endpoint selection is made in tuya_switch_state
return { l1: 1, l2: 1, l3: 1, l4: 1 };
},
configure: async (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"]);
// 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("TS0222", ["_TYZB01_kvwjujy9", "_TYZB01_ftdkanlj"]),
model: "ZSS-ZK-THL",
vendor: "Moes",
description: "Smart temperature and humidity meter with display",
fromZigbee: [fz.battery, fz.humidity, fz.temperature],
toZigbee: [],
exposes: [e.battery(), e.humidity(), e.temperature()],
extend: [m.illuminance()],
},
{
fingerprint: tuya.fingerprint("TS0222", ["_TZ3000_ubuikmgo"]),
model: "ZSS-QT-LTH-C",
vendor: "Moes",
description: "Smart 3-in-1 brightness, temperature and humidity sensor",
configure: tuya.configureMagicPacket,
extend: [m.battery(), m.temperature(), m.humidity(), m.illuminance()],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_fhn3negr"]),
model: "SH4-ZB",
vendor: "Moes",
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.binary("boost_heating", ea.STATE_SET, "ON", "OFF"),
e.numeric("boost_heating_countdown", ea.STATE).withUnit("s").withValueMin(0).withValueMax(900),
e.numeric("auto_setpoint_override", ea.STATE_SET).withUnit("°C").withValueMin(0.5).withValueMax(30).withValueStep(0.5),
e
.climate()
.withLocalTemperature(ea.STATE)
.withSetpoint("current_heating_setpoint", 0, 30, 0.5, ea.STATE_SET)
.withLocalTemperatureCalibration(-5, 5, 0.1, ea.STATE_SET)
.withPreset(["auto", "manual", "holiday"])
.withRunningState(["idle", "heat"], ea.STATE),
e.numeric("comfort_temperature", ea.STATE_SET).withUnit("°C").withValueMin(5).withValueMax(30).withValueStep(0.5),
e.numeric("eco_temperature", ea.STATE_SET).withUnit("°C").withValueMin(5).withValueMax(30).withValueStep(0.5),
e.numeric("open_window_temperature", ea.STATE_SET).withUnit("°C").withValueMin(5).withValueMax(30).withValueStep(0.5),
e.numeric("window_detection_time", ea.STATE_SET).withUnit("min").withValueMin(0).withValueMax(60),
e.binary("online", ea.STATE_SET, "ON", "OFF"),
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)],
[45, "error_status", tuya.valueConverter.raw],
[101, "comfort_temperature", tuya.valueConverter.divideBy2],
[102, "eco_temperature", tuya.valueConverter.divideBy2],
[104, "local_temperature_calibration", tuya.valueConverter.divideBy10],
[105, "auto_setpoint_override", tuya.valueConverter.divideBy2],
[106, "boost_heating", tuya.valueConverter.onOff],
[107, "window_detection", tuya.valueConverter.onOff],
[116, "open_window_temperature", tuya.valueConverter.divideBy2],
[117, "window_detection_time", tuya.valueConverter.raw],
[118, "boost_heating_countdown", tuya.valueConverter.raw],
[120, "online", tuya.valueConverter.onOffNotStrict],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_b6wax7g0", "_TZE200_qsoecqlk", "_TZE200_6y7kyjga"]),
model: "BRT-100-TRV",
vendor: "Moes",
description: "Thermostatic radiator valve",
whiteLabel: [tuya.whitelabel("Sibling", "Powerswitch-ZK(W)", "Thermostatic radiator valve", ["_TZE200_qsoecqlk"])],
// ota: true,
// OTA available but bricks device https://github.com/Koenkk/zigbee2mqtt/issues/18840
extend: [tuya.modernExtend.tuyaBase({ forceTimeUpdates: true, timeStart: "1970" })],
fromZigbee: [fz.ignore_tuya_set_time, legacy.fz.moesS_thermostat],
toZigbee: [
legacy.tz.moesS_thermostat_current_heating_setpoint,
legacy.tz.moesS_thermostat_child_lock,
legacy.tz.moesS_thermostat_window_detection,
legacy.tz.moesS_thermostat_temperature_calibration,
legacy.tz.moesS_thermostat_boost_heating,
legacy.tz.moesS_thermostat_boostHeatingCountdownTimeSet,
legacy.tz.moesS_thermostat_eco_temperature,
legacy.tz.moesS_thermostat_max_temperature,
legacy.tz.moesS_thermostat_min_temperature,
legacy.tz.moesS_thermostat_moesSecoMode,
legacy.tz.moesS_thermostat_preset,
legacy.tz.moesS_thermostat_schedule_programming,
legacy.tz.moesS_thermostat_system_mode,
],
exposes: [
e.battery(),
e.child_lock(),
e.eco_mode(),
e.eco_temperature().withValueMin(5),
e.max_temperature().withValueMax(45),
e.min_temperature().withValueMin(0),
e.valve_state(),
e.position(),
e.window_detection(),
e.binary("window", ea.STATE, "OPEN", "CLOSED").withDescription("Window status closed or open "),
e
.climate()
.withLocalTemperature(ea.STATE)
.withSetpoint("current_heating_setpoint", 0, 35, 1, ea.STATE_SET)
.withLocalTemperatureCalibration(-9, 9, 1, ea.STATE_SET)
.withSystemMode(["heat"], ea.STATE_SET)
.withRunningState(["idle", "heat"], ea.STATE)
.withPreset(["programming", "manual", "temporary_manual", "holiday"], "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). ' +
"PROGRAMMING MODE ⏱ - In this mode, the device executes a preset week programming temperature time and temperature. " +
"HOLIDAY MODE ⛱ - In this mode, for example, the vacation mode is set for 10 days and the temperature is set" +
"to 15 degrees Celsius. After 10 days, the device will automatically switch to programming mode. " +
"TEMPORARY MANUAL MODE - In this mode, ☝ icon will flash. At this time, the device executes the manually set " +
"temperature and returns to the weekly programming mode in the next time period. "),
e
.text("programming_mode", ea.STATE_SET)
.withDescription("PROGRAMMING MODE ⏱ - In this mode, " +
"the device executes a preset week programming temperature time and temperature. " +
"You can set up to 4 stages of temperature every for WEEKDAY ➀➁➂➃➄, SATURDAY ➅ and SUNDAY ➆."),
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_heating_countdown", ea.STATE).withUnit("min").withDescription("Countdown in minutes").withValueMin(0).withValueMax(15),
e
.numeric("boost_heating_countdown_time_set", ea.STATE_SET)
.withUnit("s")
.withDescription("Boost Time Setting 0 sec - 900 sec, (default = 300 sec)")
.withValueMin(0)
.withValueMax(900)
.withValueStep(1),
],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_nhyj64w2", "_TZE200_127x7wnl", "_TZE204_5slehgeo", "_TZE284_5slehgeo"]),
model: "ZTS-EUR-C",
vendor: "Moes",
description: "Zigbee + RF curtain switch",
extend: [tuya.modernExtend.tuyaBase({ forceTimeUpdates: true })],
fromZigbee: [legacy.fz.moes_cover],
toZigbee: [legacy.tz.moes_cover],
exposes: [
te.coverPosition(),
e.enum("backlight", ea.STATE_SET, ["OFF", "ON"]),
e.enum("calibration", ea.STATE_SET, ["OFF", "ON"]),
e.enum("motor_reversal", ea.STATE_SET, ["OFF", "ON"]),
],
},
{
fingerprint: tuya.fingerprint("TS1201", [
"_TZ3290_j37rooaxrcdcqo5n",
"_TZ3290_ot6ewjvmejq5ekhl",
"_TZ3290_xjpbcxn92aaxvmlz",
"_TZ3290_yyax9ajf",
"_TZ3290_nkpxapoz",
"_TZ3290_785fbxik",
]),
model: "UFO-R11",
vendor: "Moes",
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,
fz.battery,
],
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(),
e.battery(),
e.battery_voltage(),
],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await endpoint.read("genPowerCfg", ["batteryVoltage", "batteryPercentageRemaining"]);
await reporting.bind(endpoint, coordinatorEndpoint, ["genPowerCfg"]);
await reporting.batteryPercentageRemaining(endpoint);
await reporting.batteryVoltage(endpoint);
},
whiteLabel: [tuya.whitelabel("Tuya", "iH-F8260", "Universal smart IR remote control", ["_TZ3290_785fbxik"])],
},
{
fingerprint: tuya.fingerprint("TS0049", ["_TZ3000_cjfmu5he", "_TZ3000_mq4wujmp", "_TZ3000_5af5r192", "_TZ3000_ogjpfoyn"]),
model: "ZWV-YC",
vendor: "Moes",
description: "Water valve",
extend: [m.battery(), m.onOff({ powerOnBehavior: false, configureReporting: true })],
},
{
fingerprint: tuya.fingerprint("TS0011", ["_TZ3000_hhiodade"]),
model: "ZS-EUB_1gang",
vendor: "Moes",
description: "Wall light switch (1 gang)",
extend: [tuya.modernExtend.tuyaBase(), tuya.modernExtend.tuyaOnOff({ backlightModeOffNormalInverted: true })],
configure: async (device, coordinatorEndpoint) => {
await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
device.powerSource = "Mains (single phase)";
device.save();
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_hr0tdd47", "_TZE200_rjxqso4a", "_TZE284_rjxqso4a"]),
model: "ZC-HM",
vendor: "Moes",
description: "Carbon monoxide alarm",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [e.carbon_monoxide(), e.co(), tuya.exposes.selfTestResult(), e.battery(), tuya.exposes.silence()],
meta: {
tuyaDatapoints: [
[1, "carbon_monoxide", tuya.valueConverter.trueFalse0],
[2, "co", tuya.valueConverter.raw],
[9, "self_test_result", tuya.valueConverter.selfTestResult],
[15, "battery", tuya.valueConverter.raw],
[16, "silence", tuya.valueConverter.raw],
],
},
whiteLabel: [tuya.whitelabel("Heiman", "HS-720ES", "Carbon monoxide alarm", ["_TZE200_hr0tdd47"])],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE200_vawy74yh", "_TZE204_vawy74yh", "_TZE284_vawy74yh"]),
model: "ZSS-HM-SSD01",
vendor: "Moes",
description: "Smoke sensor",
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],
],
},
},
{
zigbeeModel: ["ZG-101ZL"],
fingerprint: tuya.fingerprint("TS004F", [
"_TZ3000_ja5osu5g",
"_TZ3000_kjfzuycl",
"_TZ3000_egvb1p2g",
"_TZ3000_kaflzta4",
"_TZ3000_lrfvzq1e",
"_TZ3000_wc3gjyp3",
]),
model: "ERS-10TZBVB-AA",
vendor: "Moes",
description: "Smart button",
whiteLabel: [
tuya.whitelabel("Loginovo", "ZG-101ZL", "Smart button", ["_TZ3000_ja5osu5g", "_TZ3000_lrfvzq1e", "_TZ3000_kaflzta4", "_TZ3000_wc3gjyp3"]),
],
extend: [tuya.clusters.addTuyaGenOnOffCluster()],
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,
fz.battery,
],
toZigbee: [tuya.tz.operation_mode],
exposes: [
e.action([
"single",
"double",
"hold",
"brightness_move_to_level",
"color_temperature_move",
"brightness_step_up",
"brightness_step_down",
"on",
"off",
]),
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 tuya.configureMagicPacket(device, coordinatorEndpoint);
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", ["_TZE204_srmahpwl"]),
model: "ZS-SR-EUC",
vendor: "Moes",
description: "Star ring - smart curtain switch",
options: [exposes.options.invert_cover()],
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [
te.coverPosition(),
e.enum("calibration", ea.STATE_SET, ["START", "END"]).withDescription("Calibration"),
e.enum("motor_steering", ea.STATE_SET, ["FORWARD", "BACKWARD"]).withDescription("Motor Steering"),
],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[
2,
"position",
{
// Workaround: Fix overflow / underdlow in position readings when limits are reached for some seconds:
// - When the curtain is fully opened it coninues with 101, 102, ...
// - When the curtain is fully closed it coninues with 255, 254, ...
from: (v) => {
if (v > 100) {
return v > 150 ? 0 : 100;
}
return v;
},
to: (v, meta) => {
return tuya.valueConverter.coverPosition.to(v, meta);
},
},
],
[
3,
"calibration",
tuya.valueConverterBasic.lookup({
START: tuya.enum(0),
END: tuya.enum(1),
}),
],
[
8,
"motor_steering",
tuya.valueConverterBasic.lookup({
FORWARD: tuya.enum(0),
BACKWARD: tuya.enum(1),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0726", ["_TZ3002_vaq2bfcu"]),
model: "SR-ZS",
vendor: "Moes",
description: "Smart switch (light + sence)",
extend: [
tuya.modernExtend.tuyaBase(),
m.deviceEndpoints({ endpoints: { l1: 1, l2: 2, l3: 3 } }),
tuya.modernExtend.tuyaOnOff({
endpoints: ["l1", "l2", "l3"],
powerOnBehavior2: true,
switchMode: true,
}),
m.actionEnumLookup({
cluster: "genOnOff",
commands: ["commandTuyaAction"],
attribute: "value",
actionLookup: { button: 0 },
buttonLookup: {
"1_up": 4,
"1_down": 1,
"2_up": 5,
"2_down": 2,
"3_up": 6,
"3_down": 3,
},
}),
tuya.modernExtend.tuyaLedIndicator(),
],
},
{
fingerprint: [{ modelID: "TS0601", manufacturerName: "_TZE204_lpedvtvr" }],
model: "ZHT-SR",
vendor: "Moes",
description: "Smart ring thermostat",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "1970" })],
exposes: [
e
.climate()
.withSetpoint("current_heating_setpoint", 5, 45, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withLocalTemperatureCalibration(-10, 10, 1, ea.STATE_SET)
.withSystemMode(["off", "heat"], ea.STATE_SET)
.withRunningState(["idle", "heat"], ea.STATE)
.withPreset(["Manual", "Temporary manual", "Program", "Eco"]),
//eco_temperature,
new exposes.Numeric("eco_temperature", exposes.access.STATE_SET)
.withUnit("°C")
.withDescription("Temperature for eco mode")
.withValueMin(10)
.withValueMax(30),
e.child_lock(),
e.deadzone_temperature().withValueMin(0.5).withValueMax(5).withValueStep(0.5),
e.max_temperature_limit().withValueMin(35).withValueMax(45),
e.min_temperature_limit().withValueMin(1).withValueMax(15),
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"),
new exposes.Numeric("floor_temperature", exposes.access.STATE).withUnit("°C").withDescription("Floor temperature"),
new exposes.Numeric("high_protect_temperature", exposes.access.STATE_SET)
.withUnit("°C")
.withDescription("High protect temperature")
.withValueMin(10)
.withValueMax(70),
new exposes.Numeric("low_protect_temperature", exposes.access.STATE_SET)
.withUnit("°C")
.withDescription("Low protect temperature")
.withValueMin(0)
.withValueMax(10),
//backlight_brightness
new exposes.Numeric("backlight_brightness", exposes.access.STATE_SET)
.withUnit("%")
.withDescription("Backlight brightness")
.withValueMin(0)
.withValueMax(100),
//Screen time
new exposes.Enum("screen_time", exposes.access.STATE_SET, [10, 20, 30, 40, 50, 60].map((secs) => `${secs}_seconds`))
.withDescription("Screen on time")
.withCategory("config"),
e.binary("rgb_light", exposes.access.STATE_SET, "ON", "OFF"),
],
meta: {
tuyaDatapoints: [
[1, "system_mode", tuya.valueConverterBasic.lookup({ off: false, heat: true })],
[
2,
"preset",
tuya.valueConverterBasic.lookup({
Manual: 0,
"Temporary manual": 1,
Program: 2,
Eco: 3,
}),
],
[16, "local_temperature", tuya.valueConverter.divideBy10],
[18, "min_temperature_limit", tuya.valueConverter.divideBy10],
[32, "sensor_mode", tuya.valueConverterBasic.lookup({ IN: 0, AL: 1, OU: 2 })],
[34, "max_temperature_limit", tuya.valueConverter.divideBy10],
[39, "child_lock", tuya.valueConverter.lockUnlock],
[47, "running_state", tuya.valueConverterBasic.lookup({ heat: 0, idle: 1 })],
[48, "backlight_brightness", tuya.valueConverter.raw],
[50, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[101, "local_temperature_calibration", tuya.valueConverter.localTemperatureCalibration_256],
[109, "floor_temperature", tuya.valueConverter.divideBy10],
[110, "deadzone_temperature", tuya.valueConverter.divideBy10],
[111, "high_protect_temperature", tuya.valueConverter.divideBy10],
[112, "low_protect_temperature", tuya.valueConverter.divideBy10],
[113, "eco_temperature", tuya.valueConverter.divideBy10],
[
114,
"screen_time",
tuya.valueConverterBasic.lookup({
"10_seconds": 0,
"20_seconds": 1,
"30_seconds": 2,
"40_seconds": 3,
"50_seconds": 4,
"60_seconds": 5,
}),
],
[115, "rgb_light", tuya.valueConverterBasic.lookup({ ON: true, OFF: false })],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZ3210_5rta89nj"]),
model: "ZC-LP01",
vendor: "Moes",
description: "Smart sliding window pusher",
options: [exposes.options.invert_cover()],
extend: [tuya.modernExtend.tuyaBase({ dp: true }), tuya.modernExtend.dpBattery({ dp: 4 })],
exposes: [
e.cover_position(),
e.binary("charging", ea.STATE, true, false).withDescription("Whether the device is being charged via USB-C"),
e
.binary("automatic_mode", ea.STATE_SET, "ON", "OFF")
.withDescription("When set to `ON`, the device will start pushing in the same direction the window was pushed"),
te.slowMode(),
e.enum("button_position", ea.STATE_SET, ["UP", "DOWN"]).withDescription("Swaps the behavior of the device's physical buttons"),
],
meta: {
tuyaSendCommand: "sendData",
tuyaDatapoints: [
[
102,
"state",
tuya.valueConverterBasic.lookup((options) =>
// Some devices were shipped with a reversed firmware.
options.invert_cover ? { OPEN: 1, STOP: 2, CLOSE: 0 } : { OPEN: 0, STOP: 2, CLOSE: 1 }),
],
[104, "position", tuya.valueConverter.coverPosition],
[105, "charging", tuya.valueConverter.trueFalse1],
[106, "automatic_mode", tuya.valueConverterBasic.lookup({ ON: 1, OFF: 0 })],
[110, "slow_mode", tuya.valueConverter.onOffEnumOn1], // slow_stop
[112, "button_position", tuya.valueConverterBasic.lookup({ UP: 1, DOWN: 0 })],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_xtrnjaoz", "_TZE200_xtrnjaoz", "_TZE284_8whfphjv"]),
model: "GM25TEQ-TYZ-2/25",
vendor: "Moes",
description: "Roller Shade Blinds Motor for 38mm Tube",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
whiteLabel: [tuya.whitelabel("Tuya", "GM35TEQ-TYZ-2/25", "Roller Shade Blinds Motor for 38mm Tube", ["_TZE284_8whfphjv"])],
exposes: [te.coverPosition(), te.motorDirection()],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[9, "position", tuya.valueConverter.coverPositionInverted],
[8, "position", tuya.valueConverter.coverPositionInverted],
[11, "motor_direction", tuya.valueConverter.tubularMotorDirection],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_x9usygq1"]),
model: "ZHT-PT01-M-MS",
vendor: "Moes",
description: "Smart thermostat for pilot wire heating radiator",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "1970" })],
exposes: [
e
.climate()
.withSetpoint("current_heating_setpoint", 5, 30, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withPreset(["off", "comfort", "comfort_1", "comfort_2", "eco", "antifrost", "program", "thermostat"])
.withSystemMode(["off", "heat", "auto"], ea.STATE)
.withLocalTemperatureCalibration(-10, 10, 0.1, ea.STATE_SET),
e
.enum("mode", ea.STATE_SET, ["standby", "comfort", "comfort_1", "comfort_2", "eco", "antifrost", "program", "thermostat"])
.withDescription("Working mode"),
e.window_open(),
e.binary("fault", ea.STATE, "DETECTED", "NOT_DETECTED").withDescription("Fault status"),
e.window_detection(),
e.child_lock(),
e.enum("temp_unit_convert", ea.STATE_SET, ["c", "f"]).withDescription("Temperature unit"),
e.numeric("lower_temp", ea.STATE_SET).withUnit("°C").withValueMin(5).withValueMax(30).withDescription("Set min temperature"),
e.numeric("upper_temp", ea.STATE_SET).withUnit("°C").withValueMin(5).withValueMax(30).withDescription("Set max temperature"),
e.numeric("electricity_statistics", ea.STATE).withUnit("kWh").withDescription("Electricity usage statistics"),
e.numeric("boost_duration", ea.STATE_SET).withUnit("min").withValueMin(0).withValueMax(120).withDescription("Boost mode duration"),
e.numeric("elec_statistics_day", ea.STATE).withDescription("Daily energy usage"),
e.numeric("elec_statistics_month", ea.STATE).withDescription("Monthly energy usage"),
e.numeric("elec_statistics_year", ea.STATE).withDescription("Annual energy usage"),
e
.numeric("window_keep_time", ea.STATE_SET)
.withUnit("min")
.withValueMin(0)
.withValueMax(120)
.withDescription("Time window should be kept open"),
e.enum("fault_alarm", ea.STATE, ["e1", "e2"]).withDescription("Fault alarm (e1, e2)"),
e
.enum("running_mode", ea.STATE, ["standby", "comfort", "comfort_1", "comfort_2", "eco", "antifrost"])
.withDescription("Current running mode"),
],
meta: {
tuyaDatapoints: [
[
2,
"mode",
tuya.valueConverterBasic.lookup({
standby: tuya.enum(0),
comfort: tuya.enum(1),
comfort_1: tuya.enum(2),
comfort_2: tuya.enum(3),
eco: tuya.enum(4),
antifrost: tuya.enum(5),
program: tuya.enum(6),
thermostat: tuya.enum(7),
}),
],
[
2,
"system_mode",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
auto: tuya.enum(6),
heat: tuya.enum(7),
}),
],
[
2,
"preset",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
comfort: tuya.enum(1),
comfort_1: tuya.enum(2),
comfort_2: tuya.enum(3),
eco: tuya.enum(4),
antifrost: tuya.enum(5),
program: tuya.enum(6),
thermostat: tuya.enum(7),
}),
],
[16, "local_temperature", tuya.valueConverter.divideBy10],
[
17,
"window_open",
tuya.valueConverterBasic.lookup({
close: tuya.enum(0),
open: tuya.enum(1),
}),
],
[18, "lower_temp", tuya.valueConverter.divideBy10],
[19, "temp_correction", tuya.valueConverter.divideBy10],
[19, "local_temperature_calibration", tuya.valueConverter.divideBy10],
[20, "fault", tuya.valueConverter.raw],
[29, "window_detection", tuya.valueConverter.onOff],
[34, "upper_temp", tuya.valueConverter.divideBy10],
[39, "child_lock", tuya.valueConverter.lockUnlock],
[
46,
"temp_unit_convert",
tuya.valueConverterBasic.lookup({
c: tuya.enum(0),
f: tuya.enum(1),
}),
],
[50, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[65, "week_program_1", tuya.valueConverter.raw],
[66, "week_program_2", tuya.valueConverter.raw],
[67, "week_program_3", tuya.valueConverter.raw],
[68, "week_program_4", tuya.valueConverter.raw],
[69, "week_program_5", tuya.valueConverter.raw],
[70, "week_program_6", tuya.valueConverter.raw],
[71, "week_program_7", tuya.valueConverter.raw],
[101, "vacation_duration", tuya.valueConverter.raw],
[102, "boost_duration", tuya.valueConverter.raw],
[103, "electricity_statistics", tuya.valueConverter.raw],
[104, "elec_statistics_day", tuya.valueConverter.raw],
[105, "elec_statistics_month", tuya.valueConverter.raw],
[106, "elec_statistics_year", tuya.valueConverter.raw],
[107, "average_power", tuya.valueConverter.raw],
[108, "air_pressure_index", tuya.valueConverter.raw],
[109, "support_features", tuya.valueConverter.raw],
[110, "window_keep_time", tuya.valueConverter.raw],
[111, "app_features", tuya.valueConverter.raw],
[112, "switch_diff", tuya.valueConverter.raw],
[
113,
"running_mode",
tuya.valueConverterBasic.lookup({
standby: tuya.enum(0),
comfort: tuya.enum(1),
comfort_1: tuya.enum(2),
comfort_2: tuya.enum(3),
eco: tuya.enum(4),
antifrost: tuya.enum(5),
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0041", ["_TZ3000_axpdxqgu"]),
model: "ZT-B-EU1",
vendor: "Moes",
description: "Scene remote with 1 key",
extend: [tuya.clusters.addTuyaGenOnOffCluster()],
fromZigbee: [tuya.fz.on_off_action, fz.battery, tuya.fz.datapoints],
toZigbee: [],
configure: tuya.configureMagicPacket,
exposes: [e.battery(), e.action(["single", "double", "hold"])],
meta: {
tuyaDatapoints: [
[
1,
"action",
tuya.valueConverterBasic.lookup({
single: 0,
double: 1,
hold: 2,
}),
],
],
},
endpoint: () => ({}),
},
{
fingerprint: tuya.fingerprint("TS0042", ["_TZ3000_5e235jpa"]),
model: "ZT-B-EU2",
vendor: "Moes",
description: "Scene remote with 2 keys",
extend: [tuya.clusters.addTuyaGenOnOffCluster()],
fromZigbee: [tuya.fz.on_off_action, fz.battery, tuya.fz.datapoints],
toZigbee: [],
configure: tuya.configureMagicPacket,
exposes: [e.battery(), e.action(["1_single", "1_double", "1_hold", "2_single", "2_double", "2_hold"])],
meta: {
tuyaDatapoints: [
[
1,
"action",
tuya.valueConverterBasic.lookup({
"1_single": 0,
"1_double": 1,
"1_hold": 2,
}),
],
[
2,
"action",
tuya.valueConverterBasic.lookup({
"2_single": 0,
"2_double": 1,
"2_hold": 2,
}),
],
],
},
endpoint: () => ({}),
},
{
fingerprint: tuya.fingerprint("TS0043", ["_TZ3000_gbm10jnj", "_TZ3000_sj7jbgks"]),
model: "ZT-B-EU3",
vendor: "Moes",
description: "Scene remote with 3 keys",
extend: [tuya.clusters.addTuyaGenOnOffCluster()],
fromZigbee: [tuya.fz.on_off_action, fz.battery, tuya.fz.datapoints],
toZigbee: [],
configure: tuya.configureMagicPacket,
exposes: [e.battery(), e.action(["1_single", "1_double", "1_hold", "2_single", "2_double", "2_hold", "3_single", "3_double", "3_hold"])],
meta: {
tuyaDatapoints: [
[
1,
"action",
tuya.valueConverterBasic.lookup({
"1_single": 0,
"1_double": 1,
"1_hold": 2,
}),
],
[
2,
"action",
tuya.valueConverterBasic.lookup({
"2_single": 0,
"2_double": 1,
"2_hold": 2,
}),
],
[
3,
"action",
tuya.valueConverterBasic.lookup({
"3_single": 0,
"3_double": 1,
"3_hold": 2,
}),
],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_qoi1aqxg"]),
model: "FWJZCEH18A001",
vendor: "Moes",
description: "Roller blind motor 17mm/25mm/28mm",
extend: [tuya.modernExtend.tuyaBase({ dp: true })],
exposes: [te.coverPosition(), te.motorDirection(), te.coverLimit(), e.battery()],
meta: {
tuyaDatapoints: [
[1, "state", tuya.valueConverter.coverAction],
[9, "position", tuya.valueConverter.coverPosition],
[8, "position", tuya.valueConverter.coverPosition],
[11, "motor_direction", tuya.valueConverter.tubularMotorDirection],
[13, "battery", { from: (v) => Buffer.from(v, "base64").readUInt32BE(0) }],
[16, "cover_limit", tuya.valueConverter.coverLimit],
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_rlytpmij"]),
model: "ZHT-S01",
vendor: "Moes",
description: "Zigbee wall thermostat (air/internal temperature priority)",
extend: [tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true, timeStart: "1970" })],
exposes: (device, options) => {
const WEEKDAY_PERIODS = [
{ id: 1, desc: "get_up_in_the_morning" },
{ id: 2, desc: "go_out_in_the_morning" },
{ id: 3, desc: "back_home_in_the_noon" },
{ id: 4, desc: "go_out_in_the_noon" },
{ id: 5, desc: "back_home_in_the_evening" },
{ id: 6, desc: "sleep_at_night" },
];
const WEEKEND_PERIODS = [
{ id: 1, desc: "get_up" },
{ id: 2, desc: "sleep" },
];
const SCHEDULE_DEFAULTS = {
weekday: [
{ hour: 6, minute: 0, temp: 20 },
{ hour: 8, minute: 0, temp: 16 },
{ hour: 11, minute: 30, temp: 20 },
{ hour: 12, minute: 30, temp: 16 },
{ hour: 17, minute: 0, temp: 20 },
{ hour: 22, minute: 0, temp: 16 },
],
weekend: [
{ hour: 8, minute: 0, temp: 20 },
{ hour: 23, minute: 0, temp: 16 },
],
};
// Generate schedule exposes
const scheduleExposes = [];
for (let i = 0; i < 6; i++) {
const num = i + 1;
const period = WEEKDAY_PERIODS[i];
const def = SCHEDULE_DEFAULTS.weekday[i];
scheduleExposes.push(e
.numeric(`weekday_${num}_hour`, ea.STATE_SET)
.withValueMin(0)
.withValueMax(23)
.withValueStep(1)
.withPreset("default", def.hour, "Default value")
.withDescription(`Weekday P${num} ${period.desc} - Hour`), e
.numeric(`weekday_${num}_minute`, ea.STATE_SET)
.withValueMin(0)
.withValueMax(59)
.withValueStep(1)
.withPreset("default", def.minute, "Default value")
.withDescription(`Weekday P${num} ${period.desc} - Minute`), e
.numeric(`weekday_${num}_temp`, ea.STATE_SET)
.withUnit("°C")
.withValueMin(5)
.withValueMax(35)
.withValueStep(0.5)
.withPreset("default", def.temp, "Default value")
.withDescription(`Weekday P${num} ${period.desc} - Temperature`));
}
for (let i = 0; i < 2; i++) {
const num = i + 1;
const period = WEEKEND_PERIODS[i];
const def = SCHEDULE_DEFAULTS.weekend[i];
scheduleExposes.push(e
.numeric(`weekend_${num}_hour`, ea.STATE_SET)
.withValueMin(0)
.withValueMax(23)
.withValueStep(1)
.withPreset("default", def.hour, "Default value")
.withDescription(`Weekend P${num} ${period.desc} - Hour`), e
.numeric(`weekend_${num}_minute`, ea.STATE_SET)
.withValueMin(0)
.withValueMax(59)
.withValueStep(1)
.withPreset("default", def.minute, "Default value")
.withDescription(`Weekend P${num} ${period.desc} - Minute`), e
.numeric(`weekend_${num}_temp`, ea.STATE_SET)
.withUnit("°C")
.withValueMin(5)
.withValueMax(35)
.withValueStep(0.5)
.withPreset("default", def.temp, "Default value")
.withDescription(`Weekend P${num} ${period.desc} - Temperature`));
}
return [
// Child lock
e.child_lock(),
// System mode separate expose (clearer in UI)
e.enum("system_mode", ea.STATE_SET, ["off", "heat"]).withDescription("Thermostat system mode (device enabled/disabled)"),
// Main climate control
e
.climate()
.withPreset(["manual", "auto", "eco"])
.withRunningState(["idle", "heat"], ea.STATE)
.withSystemMode(["off", "heat"], ea.STATE)
.withSetpoint("current_heating_setpoint", 5, 35, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE) // DP 117 (Air/Internal sensor)
.withLocalTemperatureCalibration(-9, 9, 1, ea.STATE_SET),
// Floor temperature (secondary sensor)
e.numeric("floor_temperature", ea.STATE).withUnit("°C").withDescription("Temperature from floor sensor (secondary)"),
// Valve state
e.binary("valve_state", ea.STATE, "OPEN", "CLOSED").withDescription("Valve state"),
// Fault alarm
e
.enum("fault_alarm", ea.STATE, ["none", "e1", "e2", "e3", "e1_e2", "e1_e3", "e2_e3", "e1_e2_e3"])
.withDescription("Fault alarm status"),
// Sensor selection
e.enum("sensor", ea.STATE_SET, ["internal", "external", "both"]).withDescription("Temperature sensor selection"),
// Temperature scale
e
.enum("temperature_scale", ea.STATE_SET, ["celsius", "fahrenheit"])
.withDescription("Temperature scale (WARNING: converter only supports Celsius datapoints)"),
// Backlight brightness
e.enum("backlight_brightness", ea.STATE_SET, ["off", "low", "medium", "high"]).withDescription("Backlight brightness"),
// Antifreeze
e.binary("antifreeze", ea.STATE_SET, "ON", "OFF").withDescription("Antifreeze mode"),
// Temperature limits
e
.numeric("min_temperature_limit", ea.STATE_SET)
.withValueMin(5)
.withValueMax(20)
.withValueStep(1)
.withUnit("°C")
.withDescription("Minimum temperature limit"),
e
.numeric("max_temperature_limit", ea.STATE_SET)
.withValueMin(30)
.withValueMax(70)
.withValueStep(1)
.withUnit("°C")
.withDescription("Maximum temperature limit"),
// Temperature hysteresis/deadzone
e
.numeric("deadzone_temperature", ea.STATE_SET)
.withUnit("°C")
.withValueMin(0.5)
.withValueMax(3.0)
.withValueStep(0.5)
.withDescription("Temperature hysteresis/deadzone"),
// ECO mode temperature
e
.numeric("eco_temperature", ea.STATE_SET)
.withUnit("°C")
.withValueMin(5)
.withValueMax(35)
.withValueStep(1)
.withDescription("ECO mode temperature setting"),
// Program mode
e
.enum("program_mode", ea.STATE_SET, ["off", "weekend", "single_break", "no_day_off"])
.withDescription("Weekly programming mode type"),
// Factory reset
e.binary("factory_reset", ea.SET, "ON", "OFF").withDescription("Factory reset (use with caution)"),
// Schedule exposes - individual fields for each period
...scheduleExposes,
];
},
meta: {
tuyaDatapoints: [
[
1,
"system_mode",
{
from: (value) => (value === true ? "heat" : "off"),
to: (value) => value === "heat",
},
],
[
2,
"preset",
tuya.valueConverterBasic.lookup({
auto: tuya.enum(0),
manual: tuya.enum(1),
eco: tuya.enum(2),
}),
],
[
3,
"backlight_brightness",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
low: tuya.enum(1),
medium: tuya.enum(2),
high: tuya.enum(3),
}),
],
[19, "local_temperature_calibration", tuya.valueConverter.raw],
[
20,
"fault_alarm",
{
from: (v) => {
if (v === 0)
return "none";
const faults = [];
if (v & 1)
faults.push("e1");
if (v & 2)
faults.push("e2");
if (v & 4)
faults.push("e3");
return faults.join("_") || "none";
},
},
],
[28, "factory_reset", tuya.valueConverter.onOff],
[
32,
"sensor",
tuya.valueConverterBasic.lookup({
internal: tuya.enum(0),
external: tuya.enum(1),
both: tuya.enum(2),
}),
],
[39, "child_lock", tuya.valueConverter.lockUnlock],
[
46,
"temperature_scale",
tuya.valueConverterBasic.lookup({
celsius: tuya.enum(0),
fahrenheit: tuya.enum(1),
}),
],
[
47,
"running_state",
tuya.valueConverterBasic.lookup({
heat: tuya.enum(0),
idle: tuya.enum(1),
}),
],
[
47,
"valve_state",
tuya.valueConverterBasic.lookup({
OPEN: tuya.enum(0),
CLOSED: tuya.enum(1),
}),
],
// === STANDARD SENSOR MAPPING (AIR/INTERNAL PRIORITY) ===
[117, "local_temperature", tuya.valueConverter.divideBy10],
[101, "floor_temperature", tuya.valueConverter.divideBy10],
// ========================================================
[
103,
"antifreeze",
{
from: (value) => (value === true ? "ON" : "OFF"),
to: (value) => value === "ON",
},
],
[
104,
"program_mode",
tuya.valueConverterBasic.lookup({
off: tuya.enum(0),
weekend: tuya.enum(1),
single_break: tuya.enum(2),
no_day_off: tuya.enum(3),
}),
],
[106, "deadzone_temperature", tuya.valueConverter.divideBy10],
[107, "eco_temperature", tuya.valueConverter.raw],
[111, "current_heating_setpoint", tuya.valueConverter.divideBy10],
[114, "max_temperature_limit", tuya.valueConverter.divideBy10],
[116, "min_temperature_limit", tuya.valueConverter.divideBy10],
// Weekly programming DP 108 - read converter
[
108,
null,
{
from: (value) => {
const arr = Array.isArray(value) ? value : Array.from(value);
if (!arr || arr.length < 24)
return null;
// Initialize cache if not exists
// @ts-expect-error global cache
if (!globalThis.zhtS01ScheduleCache) {
// @ts-expect-error global cache
globalThis.zhtS01ScheduleCache = {
weekday_1_hour: 6,
weekday_1_minute: 0,
weekday_1_temp: 20,
weekday_2_hour: 8,
weekday_2_minute: 0,
weekday_2_temp: 16,
weekday_3_hour: 11,
weekday_3_minute: 30,
weekday_3_temp: 20,
weekday_4_hour: 12,
weekday_4_minute: 30,
weekday_4_temp: 16,
weekday_5_hour: 17,
weekday_5_minute: 0,
weekday_5_temp: 20,
weekday_6_hour: 22,
weekday_6_minute: 0,
weekday_6_temp: 16,
weekend_1_hour: 8,
weekend_1_minute: 0,
weekend_1_temp: 20,
weekend_2_hour: 23,
weekend_2_minute: 0,
weekend_2_temp: 16,
};
}
// @ts-expect-error global cache
const cache = globalThis.zhtS01ScheduleCache;
const result = {};
// 6 weekday periods (18 bytes)
for (let i = 0; i < 6; i++) {
const num = i + 1;
result[`weekday_${num}_hour`] = arr[i * 3];
result[`weekday_${num}_minute`] = arr[i * 3 + 1];
result[`weekday_${num}_temp`] = arr[i * 3 + 2];
cache[`weekday_${num}_hour`] = arr[i * 3];
cache[`weekday_${num}_minute`] = arr[i * 3 + 1];
cache[`weekday_${num}_temp`] = arr[i * 3 + 2];
}
// 2 weekend periods (6 bytes)
for (let i = 0; i < 2; i++) {
const num = i + 1;
result[`weekend_${num}_hour`] = arr[18 + i * 3];
result[`weekend_${num}_minute`] = arr[18 + i * 3 + 1];
result[`weekend_${num}_temp`] = arr[18 + i * 3 + 2];
cache[`weekend_${num}_hour`] = arr[18 + i * 3];
cache[`weekend_${num}_minute`] = arr[18 + i * 3 + 1];
cache[`weekend_${num}_temp`] = arr[18 + i * 3 + 2];
}
return result;
},
},
],
// Individual schedule field converters for DP 108
...(() => {
const getCache = () => {
// @ts-expect-error global cache
if (!globalThis.zhtS01ScheduleCache) {
// @ts-expect-error global cache
globalThis.zhtS01ScheduleCache = {
weekday_1_hour: 6,
weekday_1_minute: 0,
weekday_1_temp: 20,
weekday_2_hour: 8,
weekday_2_minute: 0,
weekday_2_temp: 16,
weekday_3_hour: 11,
weekday_3_minute: 30,
weekday_3_temp: 20,
weekday_4_hour: 12,
weekday_4_minute: 30,
weekday_4_temp: 16,
weekday_5_hour: 17,
weekday_5_minute: 0,
weekday_5_temp: 20,
weekday_6_hour: 22,
weekday_6_minute: 0,
weekday_6_temp: 16,
weekend_1_hour: 8,
weekend_1_minute: 0,
weekend_1_temp: 20,
weekend_2_hour: 23,
weekend_2_minute: 0,
weekend_2_temp: 16,
};
}
// @ts-expect-error global cache
return globalThis.zhtS01ScheduleCache;
};
const createScheduleFieldConverter = (field) => ({
to: (value) => {
const cache = getCache();
cache[field] = value;
// Build buffer from cache
const buffer = [];
for (let i = 1; i <= 6; i++) {
buffer.push(cache[`weekday_${i}_hour`] || 0);
buffer.push(cache[`weekday_${i}_minute`] || 0);
buffer.push(cache[`weekday_${i}_temp`] || 20);
}
for (let i = 1; i <= 2; i++) {
buffer.push(cache[`weekend_${i}_hour`] || 0);
buffer.push(cache[`weekend_${i}_minute`] || 0);
buffer.push(cache[`weekend_${i}_temp`] || 20);
}
return buffer;
},
});
const fields = [];
for (let i = 1; i <= 6; i++) {
fields.push([108, `weekday_${i}_hour`, createScheduleFieldConverter(`weekday_${i}_hour`)]);
fields.push([108, `weekday_${i}_minute`, createScheduleFieldConverter(`weekday_${i}_minute`)]);
fields.push([108, `weekday_${i}_temp`, createScheduleFieldConverter(`weekday_${i}_temp`)]);
}
for (let i = 1; i <= 2; i++) {
fields.push([108, `weekend_${i}_hour`, createScheduleFieldConverter(`weekend_${i}_hour`)]);
fields.push([108, `weekend_${i}_minute`, createScheduleFieldConverter(`weekend_${i}_minute`)]);
fields.push([108, `weekend_${i}_temp`, createScheduleFieldConverter(`weekend_${i}_temp`)]);
}
return fields;
})(),
],
},
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE284_2fnssffc"]),
model: "ZM6LT1",
vendor: "Moes",
description: "Smart 1-phase energy power meter with CT sensor clamp",
extend: [
tuya.modernExtend.tuyaBase({
dp: true,
queryOnConfigure: true,
queryIntervalSeconds: 60,
}),
],
exposes: [
e.energy(),
e.voltage(),
e.current(),
e.power(),
e.ac_frequency(),
e.numeric("reverse_energy", ea.STATE).withUnit("kWh").withDescription("Total reverse active energy"),
e.numeric("active_energy", ea.STATE).withUnit("kWh").withDescription("Total active energy"),
e.numeric("fault", ea.STATE).withDescription("Fault status"),
e.binary("clear_event", ea.STATE_SET, "ON", "OFF").withDescription("Clear event"),
e.enum("online_state", ea.STATE, ["offline", "online"]).withDescription("Online state"),
e.numeric("countdown_1", ea.STATE_SET).withUnit("s").withDescription("Countdown timer").withValueMin(0).withValueMax(2000),
e.binary("device_restart", ea.SET, "ON", "OFF").withDescription("Device restart"),
],
meta: {
multiEndpointSkip: ["reverse_energy", "active_energy"],
tuyaDatapoints: [
[1, "energy", tuya.valueConverter.divideBy100],
[2, "reverse_energy", tuya.valueConverter.divideBy100],
[6, null, tuya.valueConverter.phaseVariant5],
[10, "fault", tuya.valueConverter.raw],
[17, "alarm_set_2", tuya.valueConverter.raw],
[20, "clear_event", tuya.valueConverter.onOff],
[
44,
"online_state",
tuya.valueConverterBasic.lookup({
offline: tuya.enum(0),
online: tuya.enum(1),
}),
],
[49, "ac_frequency", tuya.valueConverter.divideBy100],
[51, "active_energy", tuya.valueConverter.divideBy100],
[101, "countdown_1", tuya.valueConverter.raw],
[104, "device_restart", tuya.valueConverter.onOff],
],
},
},
];
//# sourceMappingURL=moes.js.map