zigbee-herdsman-converters
Version:
Collection of device converters to be used with zigbee-herdsman
2,953 lines • 135 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 zigbee_herdsman_1 = require("zigbee-herdsman");
const fz = __importStar(require("../converters/fromZigbee"));
const tz = __importStar(require("../converters/toZigbee"));
const constants = __importStar(require("../lib/constants"));
const exposes = __importStar(require("../lib/exposes"));
const m = __importStar(require("../lib/modernExtend"));
const namron = __importStar(require("../lib/namron"));
const reporting = __importStar(require("../lib/reporting"));
const store = __importStar(require("../lib/store"));
const tuya = __importStar(require("../lib/tuya"));
const utils = __importStar(require("../lib/utils"));
const ea = exposes.access;
const e = exposes.presets;
const sunricherManufacturer = { manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SHENZHEN_SUNRICHER_TECHNOLOGY_LTD };
const fzLocal = {
namron_panelheater: {
cluster: "hvacThermostat",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
const data = msg.data;
const isPro = model.model === "4512776/4512777";
if (data.operateDisplayBrightness !== undefined) {
// OperateDisplayBrightness
if (isPro) {
result.display_brightness = data.operateDisplayBrightness;
}
else {
result.display_brightnesss = data.operateDisplayBrightness;
}
}
if (data.displayAutoOff !== undefined) {
// DisplayAutoOffActivation
if (isPro) {
result.display_auto_off = data.displayAutoOff === 1;
}
else {
const lookup = { 0: "deactivated", 1: "activated" };
result.display_auto_off = utils.getFromLookup(data.displayAutoOff, lookup);
}
}
if (data.powerUpStatus !== undefined) {
// PowerUpStatus (non-PRO only)
const lookup = { 0: "manual", 1: "last_state" };
result.power_up_status = utils.getFromLookup(data.powerUpStatus, lookup);
}
if (data.windowOpenCheck2 !== undefined) {
// WindowOpenCheck
if (isPro) {
// PRO: 0=enable, 1=disable
result.window_open_detection = data.windowOpenCheck2 === 0;
}
else {
// Non-PRO: According to real life testing 0: disable, 1: enable
result.window_detection = data.windowOpenCheck2 === 1;
}
}
if (data.hysterersis !== undefined) {
// Hysteresis
const value = utils.precisionRound(data.hysterersis, 2) / 10;
if (isPro) {
result.hysteresis = value;
}
else {
result.hysterersis = value;
}
}
if (data.windowOpen !== undefined) {
// WindowOpen, 0: Window is not opened, 1: Window is opened
result.window_open = data.windowOpen === 1;
}
// PRO-specific attributes
if (data.controlMethod !== undefined) {
// System control method: 0=PID, 1=Hysteresis
result.control_method = data.controlMethod === 0 ? "pid" : "hysteresis";
}
if (data.adaptiveFunction !== undefined) {
// Adaptive function AS: 0=Enable, 1=Disable
result.adaptive_function = data.adaptiveFunction === 0;
}
if (data.pidKp !== undefined) {
result.pid_kp = data.pidKp / 1000.0;
}
if (data.pidKd !== undefined) {
result.pid_kd = data.pidKd / 1000.0;
}
if (data.pidKi !== undefined) {
result.pid_ki = data.pidKi / 1000.0;
}
return result;
},
},
namron_thermostat2: {
cluster: "hvacThermostat",
type: ["attributeReport", "readResponse"],
options: [exposes.options.local_temperature_based_on_sensor()],
convert: (model, msg, publish, options, meta) => {
const runningModeStateMap = { 0: 0, 3: 2, 4: 5 };
// override mode "idle" - not a supported running mode
if (msg.data.runningMode === 0x10)
msg.data.runningMode = 0;
// map running *mode* to *state*, as that's what used
// in homeAssistant climate ui card (red background)
if (msg.data.runningMode !== undefined)
msg.data.runningState = runningModeStateMap[msg.data.runningMode];
return fz.thermostat.convert(model, msg, publish, options, meta); // as KeyValue;
},
},
namronSimplifyRemote: {
cluster: "namronPrivateE004",
type: ["raw"],
convert(model, msg, publish, _options, meta) {
const bytes = parseNamronBytes(msg);
if (bytes.length === 0)
return;
const btn = bytes.at(-2);
const raw = bytes.at(-1);
if (btn == null || raw == null)
return;
const kind = NAMRON_SIMPLIFY_ACTIONS[raw];
const base = `button_${simplify_col(btn)}_${simplify_sub(btn)}_`;
// Firmware sometimes sends empty action after hold: synthesize release
if (!kind) {
const lastHold = store.getValue(meta.device, HOLD_KEY_SIMPLIFY);
if (lastHold?.endsWith("_hold")) {
publish({ action: lastHold.replace("_hold", "_release") });
store.putValue(meta.device, HOLD_KEY_SIMPLIFY, null);
}
return;
}
if (kind === "hold") {
store.putValue(meta.device, HOLD_KEY_SIMPLIFY, `${base}hold`);
publish({ action: `${base}hold` });
return;
}
if (kind === "release") {
publish({ action: `${base}press` });
publish({ action: `${base}release` });
return;
}
publish({ action: `${base}press` });
},
},
};
// Namron Simplify 3-button remote (4512793 / 4512794)
// -----------------------------------------------------------
const NAMRON_SIMPLIFY_ACTIONS = {
0: "press",
1: "release",
2: "hold",
};
const HOLD_KEY_SIMPLIFY = "namron_simplify_lastHold";
const simplify_col = (n) => Math.floor((n - 1) / 2) + 1;
const simplify_sub = (n) => (n % 2 === 1 ? "up" : "down");
// Helper to safely parse bytes from msg without any/unknown
function parseNamronBytes(msg) {
const m = msg;
if (m.type === "raw" &&
m.data &&
typeof m.data === "object" &&
"data" in m.data &&
Array.isArray(m.data.data)) {
return m.data.data;
}
if (Array.isArray(m.data)) {
return m.data;
}
if (m.data && typeof m.data === "object") {
const obj = m.data;
const keys = Object.keys(obj)
.filter((k) => !Number.isNaN(Number(k)))
.sort((a, b) => Number(a) - Number(b));
return keys.map((k) => obj[k]);
}
return [];
}
// END SimplifyBryter
const tzLocal = {
namron_panelheater: {
key: ["display_brightnesss", "display_auto_off", "power_up_status", "window_detection", "hysterersis", "window_open"],
convertSet: async (entity, key, value, meta) => {
if (key === "display_brightnesss") {
const payload = { 4096: { value: value, type: zigbee_herdsman_1.Zcl.DataType.ENUM8 } };
await entity.write("hvacThermostat", payload, sunricherManufacturer);
}
else if (key === "display_auto_off") {
const lookup = { deactivated: 0, activated: 1 };
const payload = { 4097: { value: utils.getFromLookup(value, lookup), type: zigbee_herdsman_1.Zcl.DataType.ENUM8 } };
await entity.write("hvacThermostat", payload, sunricherManufacturer);
}
else if (key === "power_up_status") {
const lookup = { manual: 0, last_state: 1 };
const payload = { 4100: { value: utils.getFromLookup(value, lookup), type: zigbee_herdsman_1.Zcl.DataType.ENUM8 } };
await entity.write("hvacThermostat", payload, sunricherManufacturer);
}
else if (key === "window_detection") {
const payload = { 4105: { value: value ? 1 : 0, type: zigbee_herdsman_1.Zcl.DataType.ENUM8 } };
await entity.write("hvacThermostat", payload, sunricherManufacturer);
}
else if (key === "hysterersis") {
const payload = { 4106: { value: utils.toNumber(value, "hysterersis") * 10, type: zigbee_herdsman_1.Zcl.DataType.UINT8 } };
await entity.write("hvacThermostat", payload, sunricherManufacturer);
}
},
convertGet: async (entity, key, meta) => {
switch (key) {
case "display_brightnesss":
await entity.read("hvacThermostat", ["operateDisplayBrightness"], sunricherManufacturer);
break;
case "display_auto_off":
await entity.read("hvacThermostat", ["displayAutoOff"], sunricherManufacturer);
break;
case "power_up_status":
await entity.read("hvacThermostat", ["powerUpStatus"], sunricherManufacturer);
break;
case "window_detection":
await entity.read("hvacThermostat", ["windowOpenCheck2"], sunricherManufacturer);
break;
case "hysterersis":
await entity.read("hvacThermostat", ["hysterersis"], sunricherManufacturer);
break;
case "window_open":
await entity.read("hvacThermostat", ["windowOpen"], sunricherManufacturer);
break;
default: // Unknown key
throw new Error(`Unhandled key toZigbee.namron_panelheater.convertGet ${key}`);
}
},
},
namron_panelheater_pro_hysteresis: {
key: ["hysteresis"],
convertSet: async (entity, key, value, meta) => {
let num = utils.toNumber(value, "hysteresis");
if (num < 0.5)
num = 0.5;
if (num > 5.0)
num = 5.0;
const raw = Math.round(num * 10);
await entity.write("hvacThermostat", { 4106: { value: raw, type: zigbee_herdsman_1.Zcl.DataType.UINT8 } }, sunricherManufacturer);
return { state: { hysteresis: num } };
},
convertGet: async (entity, key, meta) => {
await entity.read("hvacThermostat", ["hysterersis"], sunricherManufacturer);
},
},
namron_panelheater_pro_window_open_detection: {
key: ["window_open_detection"],
convertSet: async (entity, key, value, meta) => {
const enable = value === true || String(value).toUpperCase() === "ON";
// 0=enable, 1=disable
const raw = enable ? 0 : 1;
await entity.write("hvacThermostat", { 4105: { value: raw, type: zigbee_herdsman_1.Zcl.DataType.ENUM8 } }, sunricherManufacturer);
return { state: { window_open_detection: enable } };
},
convertGet: async (entity, key, meta) => {
await entity.read("hvacThermostat", ["windowOpenCheck2", "windowOpen"], sunricherManufacturer);
},
},
namron_panelheater_pro_display_auto_off: {
key: ["display_auto_off"],
convertSet: async (entity, key, value, meta) => {
const enable = value === true || String(value).toUpperCase() === "ON";
const raw = enable ? 1 : 0;
await entity.write("hvacThermostat", { 4097: { value: raw, type: zigbee_herdsman_1.Zcl.DataType.ENUM8 } }, sunricherManufacturer);
return { state: { display_auto_off: enable } };
},
convertGet: async (entity, key, meta) => {
await entity.read("hvacThermostat", ["displayAutoOff"], sunricherManufacturer);
},
},
namron_panelheater_pro_control_method: {
key: ["control_method"],
convertSet: async (entity, key, value, meta) => {
const mode = String(value).toLowerCase();
let raw;
if (mode === "pid" || mode === "0")
raw = 0;
else if (mode === "hysteresis" || mode === "1")
raw = 1;
else
return;
await entity.write("hvacThermostat", { 8201: { value: raw, type: zigbee_herdsman_1.Zcl.DataType.ENUM8 } }, sunricherManufacturer);
return { state: { control_method: raw === 0 ? "pid" : "hysteresis" } };
},
convertGet: async (entity, key, meta) => {
await entity.read("hvacThermostat", ["controlMethod"], sunricherManufacturer);
},
},
namron_panelheater_pro_adaptive_function: {
key: ["adaptive_function"],
convertSet: async (entity, key, value, meta) => {
const enable = value === true || String(value).toUpperCase() === "ON";
// 0=Enable, 1=Disable
const raw = enable ? 0 : 1;
await entity.write("hvacThermostat", { 4108: { value: raw, type: zigbee_herdsman_1.Zcl.DataType.ENUM8 } }, sunricherManufacturer);
return { state: { adaptive_function: enable } };
},
convertGet: async (entity, key, meta) => {
await entity.read("hvacThermostat", ["adaptiveFunction"], sunricherManufacturer);
},
},
namron_panelheater_pro_pid_kp: {
key: ["pid_kp"],
convertSet: async (entity, key, value, meta) => {
let num = utils.toNumber(value, "pid_kp");
num = Math.min(Math.max(num, 0), 1);
await entity.write("hvacThermostat", { 8198: { value: Math.round(num * 1000), type: zigbee_herdsman_1.Zcl.DataType.UINT16 } }, sunricherManufacturer);
return { state: { pid_kp: num } };
},
convertGet: async (entity, key, meta) => {
await entity.read("hvacThermostat", ["pidKp"], sunricherManufacturer);
},
},
namron_panelheater_pro_pid_ki: {
key: ["pid_ki"],
convertSet: async (entity, key, value, meta) => {
let num = utils.toNumber(value, "pid_ki");
num = Math.min(Math.max(num, 0), 1);
await entity.write("hvacThermostat", { 8200: { value: Math.round(num * 1000), type: zigbee_herdsman_1.Zcl.DataType.UINT16 } }, sunricherManufacturer);
return { state: { pid_ki: num } };
},
convertGet: async (entity, key, meta) => {
await entity.read("hvacThermostat", ["pidKi"], sunricherManufacturer);
},
},
namron_panelheater_pro_pid_kd: {
key: ["pid_kd"],
convertSet: async (entity, key, value, meta) => {
let num = utils.toNumber(value, "pid_kd");
num = Math.min(Math.max(num, 0), 1);
await entity.write("hvacThermostat", { 8199: { value: Math.round(num * 1000), type: zigbee_herdsman_1.Zcl.DataType.UINT16 } }, sunricherManufacturer);
return { state: { pid_kd: num } };
},
convertGet: async (entity, key, meta) => {
await entity.read("hvacThermostat", ["pidKd"], sunricherManufacturer);
},
},
namron_panelheater_pro_state: {
key: ["state"],
convertSet: async (entity, key, value, meta) => {
const v = String(value).toUpperCase();
const isOn = v === "ON";
const systemMode = isOn ? 0x04 : 0x00; // 0x04=heat, 0x00=off
await entity.write("hvacThermostat", { systemMode }, { disableDefaultResponse: true });
return { state: { state: isOn ? "ON" : "OFF" } };
},
convertGet: async (entity, key, meta) => {
await entity.read("hvacThermostat", ["systemMode"]);
},
},
namron_panelheater_pro_frost_mode: {
key: ["frost_mode"],
convertSet: async (entity, key, value, meta) => {
const enable = value === true || String(value).toUpperCase() === "ON";
if (enable) {
// Save current state before enabling frost mode
if (meta.state) {
if (meta.state._prev_system_mode === undefined && meta.state.system_mode !== undefined) {
meta.state._prev_system_mode = meta.state.system_mode;
}
if (meta.state._prev_occupied_heating_setpoint === undefined && meta.state.occupied_heating_setpoint !== undefined) {
meta.state._prev_occupied_heating_setpoint = meta.state.occupied_heating_setpoint;
}
}
// Set to heat mode with 7°C setpoint (700 = 7.00°C)
await entity.write("hvacThermostat", { systemMode: 0x04, occupiedHeatingSetpoint: 700 }, { disableDefaultResponse: true });
}
else {
// Restore previous state
let systemMode = 0x04; // Default to heat
let occupiedHeatingSetpoint = 2100; // Default to 21°C
if (meta.state) {
if (meta.state._prev_system_mode !== undefined) {
const sm = meta.state._prev_system_mode;
if (typeof sm === "number") {
systemMode = sm;
}
else {
const smStr = String(sm);
if (smStr === "off")
systemMode = 0x00;
else if (smStr === "auto")
systemMode = 0x01;
else if (smStr === "heat")
systemMode = 0x04;
}
}
if (meta.state._prev_occupied_heating_setpoint !== undefined) {
let sp = meta.state._prev_occupied_heating_setpoint;
// Convert to centidegrees if needed
if (typeof sp === "number" && sp < 100) {
sp = Math.round(sp * 100);
}
occupiedHeatingSetpoint = sp;
}
delete meta.state._prev_system_mode;
delete meta.state._prev_occupied_heating_setpoint;
}
await entity.write("hvacThermostat", { systemMode, occupiedHeatingSetpoint }, { disableDefaultResponse: true });
}
return { state: { frost_mode: enable } };
},
},
};
const sdClamp = (v, min, max) => Math.min(Math.max(v, min), max);
const sdPctToLevel = (pct) => sdClamp(Math.round((Number(pct) / 100) * 254), 1, 254);
const sdLevelToPct = (lvl) => sdClamp(Math.round((Number(lvl) / 254) * 100), 1, 100);
const tzLocalSimplifyDimmer4512791 = {
// Device supports off-spec writing to minLevel (0x0002).
// Requires read-before-write pattern - device returns NOT_AUTHORIZED without prior read.
min_brightness: {
key: ["min_brightness"],
convertSet: async (entity, key, value, meta) => {
const pct = Number(value);
if (!Number.isFinite(pct) || pct < 1 || pct > 50)
throw new Error("min_brightness must be 1..50 (%)");
const lvl = sdClamp(sdPctToLevel(pct), 1, 127);
// Device requires read-before-write to accept the write (off-spec HZC firmware behavior)
await entity.read("genLevelCtrl", ["minLevel"]);
await entity.write("genLevelCtrl", { [0x0002]: { value: lvl, type: 0x20 } }, { disableDefaultResponse: true, disableResponse: false });
return { state: { min_brightness: sdLevelToPct(lvl) } };
},
convertGet: async (entity, key, meta) => {
await entity.read("genLevelCtrl", ["minLevel"]);
},
},
// Device supports off-spec writing to maxLevel (0x0003).
// Requires read-before-write pattern - device returns NOT_AUTHORIZED without prior read.
max_brightness: {
key: ["max_brightness"],
convertSet: async (entity, key, value, meta) => {
const pct = Number(value);
if (!Number.isFinite(pct) || pct < 51 || pct > 100)
throw new Error("max_brightness must be 51..100 (%)");
const lvl = sdClamp(sdPctToLevel(pct), 127, 254);
// Device requires read-before-write to accept the write (off-spec HZC firmware behavior)
await entity.read("genLevelCtrl", ["maxLevel"]);
await entity.write("genLevelCtrl", { [0x0003]: { value: lvl, type: 0x20 } }, { disableDefaultResponse: true, disableResponse: false });
return { state: { max_brightness: sdLevelToPct(lvl) } };
},
convertGet: async (entity, key, meta) => {
await entity.read("genLevelCtrl", ["maxLevel"]);
},
},
// Device supports writing to defaultMoveRate (0x0014)
dimming_speed: {
key: ["dimming_speed"],
convertSet: async (entity, key, value, meta) => {
const s = Number(value);
if (!Number.isFinite(s) || s < 1 || s > 10)
throw new Error("dimming_speed must be 1..10 seconds");
await entity.write("genLevelCtrl", { [0x0014]: { value: s, type: 0x20 } }, { disableDefaultResponse: true });
return { state: { dimming_speed: s } };
},
convertGet: async (entity, key, meta) => {
await entity.read("genLevelCtrl", ["defaultMoveRate"]);
},
},
// start_brightness maps to onLevel (0x0011)
start_brightness: {
key: ["start_brightness"],
convertSet: async (entity, key, value, meta) => {
const lvl = sdClamp(Math.round(Number(value)), 1, 254);
if (!Number.isFinite(lvl))
throw new Error("start_brightness must be 1..254");
await entity.write("genLevelCtrl", { [0x0011]: { value: lvl, type: 0x20 } }, { disableDefaultResponse: true });
return { state: { start_brightness: lvl } };
},
convertGet: async (entity, key, meta) => {
await entity.read("genLevelCtrl", ["onLevel"]);
},
},
};
// End Simplify Dimmer (4512791)
// ─── Namron Zigbee Edge Thermostat (4566702/4566703/4512783/4512784) ──────────
const ZIGBEE_EPOCH_OFFSET = 946684800; // seconds between 1970-01-01 and 2000-01-01
function smartDateDecode(value) {
if (!value)
return null;
try {
if (value > 100000) {
const s = String(value).padStart(6, "0");
return `20${s.slice(0, 2)}-${s.slice(2, 4)}-${s.slice(4, 6)}`;
}
return new Date(946684800000 + value * 86400000).toISOString().slice(0, 10);
}
catch (_) {
return null;
}
}
function dateToYymmdd(value) {
const match = String(value).match(/^20(\d{2})-(\d{2})-(\d{2})$/);
if (!match)
throw new Error(`Invalid date: ${value}. Use YYYY-MM-DD format, e.g. 2026-06-05.`);
return Number(match[1] + match[2] + match[3]);
}
function deriveEdgeThermostatMode(frost, vacationMode, sensorMode, progOpMode, boostTimeSet) {
if (frost === "ON")
return "frost";
if (vacationMode === "ON")
return "holiday";
if (sensorMode === "percent")
return "regulator";
if (boostTimeSet > 0)
return "boost";
if (progOpMode === "schedule")
return "schedule";
if (progOpMode === "eco")
return "eco";
return "manual";
}
const edgeSensorModeLookup = { "0": "air", "1": "floor", "2": "both", "3": "air2", "4": "both2", "5": "floor_percent", "6": "percent" };
const edgeOnOffLookup = { OFF: 0, ON: 1 };
const edgeOnOffReverseLookup = { "0": "OFF", "1": "ON" };
const edgeScreenOnTimeLookup = { "0": "always_on", "1": "10s", "2": "60s", "3": "30s" };
const edgeScreenOnTimeValueLookup = { always_on: 0, "10s": 1, "60s": 2, "30s": 3 };
// biome-ignore lint/suspicious/noExplicitAny: endpoint type is complex generic
async function safeReadEdge(endpoint, cluster, attrs) {
try {
await endpoint.read(cluster, attrs);
}
catch (_) { }
}
// biome-ignore lint/suspicious/noExplicitAny: entity type is complex generic
async function writeEdgeHvac(entity, attr, value, type) {
await entity.write("hvacThermostat", { [attr]: { value, type } });
}
const fzEdge = {
basic: {
cluster: "genBasic",
type: ["attributeReport", "readResponse"],
convert: (model, msg) => {
const result = {};
if (msg.data["swBuildId"] !== undefined)
result["firmware_version"] = msg.data["swBuildId"];
if (msg.data["dateCode"] !== undefined)
result["firmware_date"] = msg.data["dateCode"];
return result;
},
},
namron_private: {
cluster: "hvacThermostat",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
for (const [key, value] of Object.entries(msg.data)) {
switch (Number(key)) {
case 0x8000:
result["window_open_check"] = edgeOnOffReverseLookup[String(value)] ?? String(value);
break;
case 0x8001:
result["frost"] = edgeOnOffReverseLookup[String(value)] ?? String(value);
break;
case 0x8002:
result["window_state"] = value ? "open" : "closed";
break;
case 0x8004:
result["sensor_mode"] = edgeSensorModeLookup[String(value)] ?? String(value);
break;
case 0x8005:
result["panel_brightness"] = value;
break;
case 0x8007:
result["regulator_cycle"] = value;
break;
case 0x8013:
result["holiday_temp_set"] = value / 100;
break;
case 0x801d:
result["regulator_percentage"] = value;
break;
case 0x801f:
result["vacation_mode"] = edgeOnOffReverseLookup[String(value)] ?? String(value);
break;
case 0x8020:
result["vacation_start"] = smartDateDecode(value);
break;
case 0x8021:
result["vacation_end"] = smartDateDecode(value);
break;
case 0x800a:
result["time_sync_flag"] = edgeOnOffReverseLookup[String(value)] ?? String(value);
if (value === 1) {
const ts = Math.round(Date.now() / 1000) - ZIGBEE_EPOCH_OFFSET;
msg.endpoint
.write("hvacThermostat", { 32779: { value: ts, type: 0x23 } })
.then(() => msg.endpoint.write("hvacThermostat", { 32778: { value: 0, type: 0x10 } }))
.catch(() => { });
}
break;
case 0x800b:
try {
result["time_sync_value"] =
`${new Date((value + ZIGBEE_EPOCH_OFFSET) * 1000).toISOString().replace("T", " ").slice(0, 19)} UTC`;
}
catch (_) {
result["time_sync_value"] = String(value);
}
break;
case 0x8022:
result["auto_time"] = edgeOnOffReverseLookup[String(value)] ?? String(value);
break;
case 0x8023:
result["boost_time_set"] = value;
break;
case 0x8024:
result["boost_time_remaining"] = value;
break;
case 0x8025:
result["max_heat_temp"] = value / 10;
break;
case 0x8029:
result["screen_on_time"] = edgeScreenOnTimeLookup[String(value)] ?? String(value);
break;
}
}
const merged = Object.assign({}, meta?.state ?? {}, result);
result["thermostat_mode"] = deriveEdgeThermostatMode(merged["frost"], merged["vacation_mode"], merged["sensor_mode"], merged["programming_operation_mode"], merged["boost_time_set"] ?? 0);
return result;
},
},
};
const tzEdge = {
thermostat_mode: {
key: ["thermostat_mode", "thermostat_mode_extra"],
convertSet: async (entity, key, value, meta) => {
const state = {};
const wasRegulator = meta.state?.["sensor_mode"] === "percent";
switch (value) {
case "manual":
case "schedule":
case "eco":
await writeEdgeHvac(entity, 0x8001, 0, zigbee_herdsman_1.Zcl.DataType.BOOLEAN);
await writeEdgeHvac(entity, 0x801f, 0, zigbee_herdsman_1.Zcl.DataType.BOOLEAN);
await tz.thermostat_programming_operation_mode.convertSet(entity, "programming_operation_mode", value === "manual" ? "setpoint" : value, meta);
if (wasRegulator) {
await writeEdgeHvac(entity, 0x8004, 1, zigbee_herdsman_1.Zcl.DataType.ENUM8);
state["sensor_mode"] = "floor";
}
state["frost"] = "OFF";
state["vacation_mode"] = "OFF";
state["programming_operation_mode"] = value === "manual" ? "setpoint" : value;
state["boost_time_set"] = 0;
break;
case "regulator":
await writeEdgeHvac(entity, 0x8001, 0, zigbee_herdsman_1.Zcl.DataType.BOOLEAN);
await writeEdgeHvac(entity, 0x801f, 0, zigbee_herdsman_1.Zcl.DataType.BOOLEAN);
await writeEdgeHvac(entity, 0x8004, 6, zigbee_herdsman_1.Zcl.DataType.ENUM8);
state["frost"] = "OFF";
state["vacation_mode"] = "OFF";
state["sensor_mode"] = "percent";
state["boost_time_set"] = 0;
break;
case "frost":
await writeEdgeHvac(entity, 0x801f, 0, zigbee_herdsman_1.Zcl.DataType.BOOLEAN);
await writeEdgeHvac(entity, 0x8001, 1, zigbee_herdsman_1.Zcl.DataType.BOOLEAN);
state["vacation_mode"] = "OFF";
state["frost"] = "ON";
state["boost_time_set"] = 0;
break;
case "holiday":
await writeEdgeHvac(entity, 0x8001, 0, zigbee_herdsman_1.Zcl.DataType.BOOLEAN);
await writeEdgeHvac(entity, 0x801f, 1, zigbee_herdsman_1.Zcl.DataType.BOOLEAN);
state["frost"] = "OFF";
state["vacation_mode"] = "ON";
state["boost_time_set"] = 0;
break;
case "boost": {
await writeEdgeHvac(entity, 0x8001, 0, zigbee_herdsman_1.Zcl.DataType.BOOLEAN);
await writeEdgeHvac(entity, 0x801f, 0, zigbee_herdsman_1.Zcl.DataType.BOOLEAN);
const hours = meta.state?.["boost_time_set"] > 0 ? meta.state["boost_time_set"] : 1;
await writeEdgeHvac(entity, 0x8023, hours, zigbee_herdsman_1.Zcl.DataType.ENUM8);
state["frost"] = "OFF";
state["vacation_mode"] = "OFF";
state["boost_time_set"] = hours;
break;
}
default:
throw new Error(`Invalid thermostat_mode: ${value}`);
}
state["thermostat_mode"] = value;
state["thermostat_mode_extra"] = value;
return { state };
},
convertGet: async (entity) => {
await entity.read("hvacThermostat", [0x8001, 0x8004, 0x801f, 0x8023]);
await entity.read("hvacThermostat", ["programingOperMode"]);
},
},
frost: {
key: ["frost"],
convertSet: async (entity, key, value) => {
if (value === "ON") {
await entity.write("hvacThermostat", { 32799: { value: 0, type: 0x10 } });
await entity.write("hvacThermostat", { 32769: { value: 1, type: 0x10 } });
}
else {
await entity.write("hvacThermostat", { 32769: { value: 0, type: 0x10 } });
}
return { state: { frost: value } };
},
},
keypad_lockout: {
key: ["keypad_lockout"],
convertSet: async (entity, key, value, meta) => {
const mapped = value === "lock" ? "lock1" : "unlock";
await tz.thermostat_keypad_lockout.convertSet(entity, key, mapped, meta);
return { state: { keypad_lockout: value } };
},
convertGet: async (entity, key, meta) => tz.thermostat_keypad_lockout.convertGet(entity, key, meta),
},
regulator_percentage: {
key: ["regulator_percentage"],
convertSet: async (entity, key, value) => {
const num = Number(value);
if (Number.isNaN(num) || num < 0 || num > 100)
throw new Error(`Invalid regulator_percentage: ${value}`);
await writeEdgeHvac(entity, 0x801d, Math.round(num), zigbee_herdsman_1.Zcl.DataType.INT16);
return { state: { regulator_percentage: num } };
},
convertGet: async (entity) => {
await entity.read("hvacThermostat", [0x801d]);
},
},
regulator_cycle: {
key: ["regulator_cycle"],
convertSet: async (entity, key, value) => {
const num = Math.round(Number(value));
if (Number.isNaN(num) || num < 1 || num > 30)
throw new Error(`Invalid regulator_cycle: ${value}`);
await writeEdgeHvac(entity, 0x8007, num, zigbee_herdsman_1.Zcl.DataType.UINT8);
return { state: { regulator_cycle: num } };
},
convertGet: async (entity) => {
await entity.read("hvacThermostat", [0x8007]);
},
},
max_heat_temp: {
key: ["max_heat_temp"],
convertSet: async (entity, key, value) => {
const num = Number(value);
if (Number.isNaN(num) || num < 15 || num > 35)
throw new Error(`Invalid max_heat_temp: ${value}`);
await writeEdgeHvac(entity, 0x8025, Math.round(num * 10), zigbee_herdsman_1.Zcl.DataType.INT16);
return { state: { max_heat_temp: num } };
},
convertGet: async (entity) => {
await entity.read("hvacThermostat", [0x8025]);
},
},
vacation_start: {
key: ["vacation_start"],
convertSet: async (entity, key, value) => {
const raw = dateToYymmdd(value);
await writeEdgeHvac(entity, 0x8020, raw, zigbee_herdsman_1.Zcl.DataType.UINT32);
return { state: { vacation_start: value } };
},
convertGet: async (entity) => {
await entity.read("hvacThermostat", [0x8020]);
},
},
vacation_end: {
key: ["vacation_end"],
convertSet: async (entity, key, value) => {
const raw = dateToYymmdd(value);
await writeEdgeHvac(entity, 0x8021, raw, zigbee_herdsman_1.Zcl.DataType.UINT32);
return { state: { vacation_end: value } };
},
convertGet: async (entity) => {
await entity.read("hvacThermostat", [0x8021]);
},
},
holiday_temp_set: {
key: ["holiday_temp_set"],
convertSet: async (entity, key, value) => {
const num = Number(value);
if (Number.isNaN(num) || num < 5 || num > 35)
throw new Error(`Invalid holiday_temp_set: ${value}`);
await writeEdgeHvac(entity, 0x8013, Math.round(num * 100), zigbee_herdsman_1.Zcl.DataType.INT16);
return { state: { holiday_temp_set: num } };
},
convertGet: async (entity) => {
await entity.read("hvacThermostat", [0x8013]);
},
},
boost_time_set: {
key: ["boost_time_set"],
convertSet: async (entity, key, value) => {
const num = Math.round(Number(value));
if (Number.isNaN(num) || num < 0 || num > 24)
throw new Error(`Invalid boost_time_set: ${value}`);
await writeEdgeHvac(entity, 0x8023, num, zigbee_herdsman_1.Zcl.DataType.ENUM8);
return { state: { boost_time_set: num } };
},
convertGet: async (entity) => {
await entity.read("hvacThermostat", [0x8023, 0x8024]);
},
},
window_open_check: {
key: ["window_open_check"],
convertSet: async (entity, key, value) => {
const raw = edgeOnOffLookup[value];
if (raw === undefined)
throw new Error(`Invalid window_open_check: ${value}`);
await writeEdgeHvac(entity, 0x8000, raw, zigbee_herdsman_1.Zcl.DataType.BOOLEAN);
return { state: { window_open_check: value } };
},
convertGet: async (entity) => {
await entity.read("hvacThermostat", [0x8000]);
},
},
screen_on_time: {
key: ["screen_on_time"],
convertSet: async (entity, key, value) => {
const raw = edgeScreenOnTimeValueLookup[value];
if (raw === undefined)
throw new Error(`Invalid screen_on_time: ${value}`);
await writeEdgeHvac(entity, 0x8029, raw, zigbee_herdsman_1.Zcl.DataType.ENUM8);
return { state: { screen_on_time: value } };
},
convertGet: async (entity) => {
await entity.read("hvacThermostat", [0x8029]);
},
},
panel_brightness: {
key: ["panel_brightness"],
convertSet: async (entity, key, value) => {
const num = Math.round(Number(value));
if (Number.isNaN(num) || num < 0 || num > 100)
throw new Error(`Invalid panel_brightness: ${value}`);
await writeEdgeHvac(entity, 0x8005, num, zigbee_herdsman_1.Zcl.DataType.UINT8);
return { state: { panel_brightness: num } };
},
convertGet: async (entity) => {
await entity.read("hvacThermostat", [0x8005]);
},
},
auto_time: {
key: ["auto_time"],
convertSet: async (entity, key, value) => {
const raw = edgeOnOffLookup[value];
if (raw === undefined)
throw new Error(`Invalid auto_time: ${value}`);
await writeEdgeHvac(entity, 0x8022, raw, zigbee_herdsman_1.Zcl.DataType.BOOLEAN);
return { state: { auto_time: value } };
},
convertGet: async (entity) => {
await entity.read("hvacThermostat", [0x8022]);
},
},
sync_time: {
key: ["sync_time"],
convertSet: async (entity) => {
const ts = Math.round(Date.now() / 1000) - ZIGBEE_EPOCH_OFFSET;
await writeEdgeHvac(entity, 0x800b, ts, zigbee_herdsman_1.Zcl.DataType.UINT32);
await writeEdgeHvac(entity, 0x800a, 0, zigbee_herdsman_1.Zcl.DataType.BOOLEAN);
await entity.read("hvacThermostat", [0x800a, 0x800b]);
return { state: { sync_time: "sync" } };
},
},
};
// ─── Namron Zigbee Edge Thermostat END ───────────────────────────────────────
exports.definitions = [
{
zigbeeModel: ["4566702", "4566703", "4512783", "4512784"],
model: "4566702",
vendor: "Namron",
description: "Zigbee Edge Thermostat",
ota: true,
extend: [m.humidity()],
fromZigbee: [fzEdge.basic, fz.thermostat, fzEdge.namron_private, fz.hvac_user_interface, fz.metering, fz.electrical_measurement],
toZigbee: [
tz.thermostat_occupied_heating_setpoint,
tz.thermostat_system_mode,
tz.thermostat_local_temperature_calibration,
tz.thermostat_programming_operation_mode,
tz.thermostat_temperature_display_mode,
tzEdge.thermostat_mode,
tzEdge.frost,
tzEdge.keypad_lockout,
tzEdge.regulator_percentage,
tzEdge.regulator_cycle,
tzEdge.max_heat_temp,
tzEdge.vacation_start,
tzEdge.vacation_end,
tzEdge.holiday_temp_set,
tzEdge.boost_time_set,
tzEdge.window_open_check,
tzEdge.screen_on_time,
tzEdge.panel_brightness,
tzEdge.auto_time,
tzEdge.sync_time,
],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, [
"genTime",
"genOta",
"hvacThermostat",
"hvacUserInterfaceCfg",
"msRelativeHumidity",
"seMetering",
"haElectricalMeasurement",
]);
await reporting.thermostatTemperature(endpoint, { min: 10, max: 300, change: 10 });
await reporting.thermostatOccupiedHeatingSetpoint(endpoint, { min: 10, max: 300, change: 50 });
try {
await endpoint.configureReporting("haElectricalMeasurement", [
{ attribute: "rmsCurrent", minimumReportInterval: 10, maximumReportInterval: 300, reportableChange: 1 },
{ attribute: "activePower", minimumReportInterval: 10, maximumReportInterval: 300, reportableChange: 100 },
]);
}
catch (_) { }
await safeReadEdge(endpoint, "genBasic", ["swBuildId", "dateCode"]);
await safeReadEdge(endpoint, "hvacThermostat", [
"localTemp",
"occupiedHeatingSetpoint",
"systemMode",
"runningMode",
"runningState",
"localTemperatureCalibration",
"pIHeatingDemand",
"programingOperMode",
"tempDisplayMode",
]);
await safeReadEdge(endpoint, "hvacUserInterfaceCfg", ["keypadLockout"]);
await safeReadEdge(endpoint, "seMetering", ["currentSummDelivered", "divisor", "multiplier"]);
await safeReadEdge(endpoint, "haElectricalMeasurement", [
"activePower",
"rmsCurrent",
"acPowerMultiplier",
"acPowerDivisor",
"acCurrentMultiplier",
"acCurrentDivisor",
]);
await safeReadEdge(endpoint, "hvacThermostat", [
0x8000, 0x8001, 0x8002, 0x8004, 0x8005, 0x8007, 0x8013, 0x801d, 0x801f, 0x8020, 0x8021, 0x800a, 0x800b, 0x8022, 0x8023, 0x8024,
0x8025, 0x8029,
]);
// Sync time at configure
const ts = Math.round(Date.now() / 1000) - ZIGBEE_EPOCH_OFFSET;
await endpoint.write("hvacThermostat", { 32779: { value: ts, type: zigbee_herdsman_1.Zcl.DataType.UINT32 } });
await endpoint.write("hvacThermostat", { 32778: { value: 0, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN } });
// Write defaults: screen_on_time = 30s, temperature_display_mode = celsius
await endpoint.write("hvacThermostat", { [0x8029]: { value: 3, type: zigbee_herdsman_1.Zcl.DataType.ENUM8 } });
await endpoint.write("hvacUserInterfaceCfg", { [0x0000]: { value: 0, type: zigbee_herdsman_1.Zcl.DataType.ENUM8 } });
device.powerSource = "Mains (single phase)";
device.save();
},
// Periodic time sync regardless of heating status.
// Syncs time at most once per hour so vacation mode always has correct clock.
onEvent: async (event) => {
if (event.type === "stop")
return;
const now = Date.now();
const device = event.data.device;
const lastSync = device.meta["lastTimeSync"] ?? 0;
if (now - lastSync > 60 * 60 * 1000) {
try {
const endpoint = device.getEndpoint(1);
const ts = Math.round(now / 1000) - ZIGBEE_EPOCH_OFFSET;
await endpoint.write("hvacThermostat", { 32779: { value: ts, type: zigbee_herdsman_1.Zcl.DataType.UINT32 } });
await endpoint.write("hvacThermostat", { 32778: { value: 0, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN } });
device.meta["lastTimeSync"] = now;
device.save();
}
catch (_) {
/* ignore */
}
}
},
exposes: [
e
.climate()
.withLocalTemperature()
.withSetpoint("occupied_heating_setpoint", 5, 35, 0.5)
.withSystemMode(["off", "heat"])
.withRunningState(["idle", "heat"])
.withLocalTemperatureCalibration(-5, 5, 0.5),
e.numeric("max_heat_temp", ea.ALL).withUnit("°C").withValueMin(15).withValueMax(35).withValueStep(0.5).withLabel("Max heat temperature"),
e.enum("thermostat_mode", ea.ALL, ["manual", "schedule", "regulator"]).withLabel("Thermostat mode"),
e.enum("thermostat_mode_extra", ea.ALL, ["eco", "frost", "holiday"]).withLabel("Special mode"),
e.binary("frost", ea.STATE_SET, "ON", "OFF").withLabel("Frost Mode"),
e.enum("temperature_display_mode", ea.STATE_SET, ["celsius", "fahrenheit"]).withLabel("Temperature unit"),
e
.numeric("regulator_percentage", ea.ALL)
.withUnit("%")
.withValueMin(0)
.withValueMax(100)
.withValueStep(5)
.withLabel("Regulator set point"),
e
.numeric("regulator_cycle", ea.ALL)
.withUnit("min")
.withValueMin(1)
.withValueMax(30)
.withValueStep(1)
.withLabel("Regulator cycle duration"),
e.binary("vacation_mode", ea.STATE, "ON", "OFF").withLabel("Vacation active"),
e.text("vacation_start", ea.ALL).withLabel("Vacation start (YYYY-MM-DD)"),
e.text("vacation_end", ea.ALL).withLabel("Vacation end (YYYY-MM-DD)"),
e.numeric("holiday_temp_set", ea.ALL).withUnit("°C").withValueMin(5).withValueMax(35).withValueStep(0.5).withLabel("Holiday temperature"),
e
.numeric("boost_time_set", ea.ALL)
.withUnit("h")
.withValueMin(0)
.withValueMax(24)
.withValueStep(1)
.withLabel("Boost time set")
.withDescription("Set hours for boost heating. Setting a value > 0 activates boost mode immediately. Set to 0 to stop boost."),
e.numeric("boost_time_remaining", ea.STATE).withUnit("min").withLabel("Boost time remaining"),
e.binary("window_open_check", ea.ALL, "ON", "OFF").withLabel("Window detection"),
e.enum("window_state", ea.STATE, ["open", "closed"]).withLabel("Window state"),
e.binary("keypad_lockout", ea.STATE_SET, "lock", "unlock").withLabel("Child Lock"),
e.enum("screen_on_time", ea.ALL, ["always_on", "10s", "30s", "60s"]).withLabel("Screen on time"),
e.numeric("panel_brightness", ea.ALL).withValueMin(0).withValueMax(100).withValueStep(1).withLabel("Panel brightness"),
e.binary("auto_time", ea.ALL, "ON", "OFF").withLabel("Auto time sync"),
e.text("time_sync_value", ea.STATE).withLabel("Thermostat time (UTC)"),
e.enum("sync_time", ea.SET, ["sync"]).withLabel("Sync time"),
e.text("firmware_version", ea.STATE).withLabel("Firmware version"),
e.text("firmware_date", ea.STATE).withLabel("Firmware date"),
e.numeric("energy", ea.STATE).withUnit("kWh").withLabel("Energy"),
e.numeric("current", ea.STATE).withUnit("A").withLabel("Current"),
e.numeric("power", ea.STATE).withUnit("W").withLabel("Power"),
],
},
{
zigbeeModel: ["3308431"],
model: "3308431",
vendor: "Namron",
description: "Luna ceiling light",
extend: [m.light({ colorTemp: { range: [153, 370] } })],
},
{
zigbeeModel: ["3802967"],
model: "3802967",
vendor: "Namron",
description: "Led bulb 6w RGBW",
extend: [m.light({ colorTemp: { range: [153, 555] }, color: true })],
},
{
zigbeeModel: ["4512700"],
model: "4512700",
vendor: "Namron",
description: "Zigbee dimmer 400W",
ota: true,
extend: [m.light({ configureReporting: true })],
},
{
zigbeeModel: ["4512739"],
model: "4512739",
vendor: "Namron",
description: "Zigbee dimmer TW 400W",
ota: true,
extend: [m.light({ configureReporting: true, colorTemp: { range: [160, 450] } }), m.electricityMeter()],
},
{
zigbeeModel: ["4512760"],
model: "4512760",
vendor: "Namron",
description: "Zigbee dimmer 400W",
ota: true,
extend: [m.light({ configureReporting: true }), m.electricityMeter({ voltage: false, current: false })],
},
{
zigbeeModel: ["4512708"],
model: "4512708",
vendor: "Namron",
description: "Zigbee LED dimmer",
extend: [m.light({ configureReporting: true })],
},
{
zigbeeModel: ["4512766"],
model: "4512766",
vendor: "Namron",
description: "Zigbee smart plug 16A",
ota: true,
extend: [m.onOff(), m.electricityMeter()],
},
{
zigbeeModel: ["4512767"],
model: "4512767",
vendor: "Namron",
description: "Zigbee smart plug 16A",
ota: true,
extend: [m.onOff(), m.electricityMeter()],
},
{
zigbeeModel: ["4512789"],
model: "4512789",
vendor: "Namron",
description: "Zigbee smart plug 16A IP44",
extend: [m.deviceTemperature({ scale: 100 }), m.onOff(), m.electricityMeter()],
},
{
zigbeeModel: ["1402767"],
model: "1402767",
vendor: "Namron",
description: "Zigbee LED dimmer",
extend: [m.light({ effect: false, configureReporting: true }), m.forcePowerSource({ powerSource: "Mains (single phase)" })],
meta: { disableDefaultResponse: true },
},
{
zigbeeModel: ["1402768"],
model: "1402768",
vendor: "Namron",
description: "Zigbee LED dimmer TW 250W",
extend: [m.light({ effect: false, configureReporting: true, colorTemp: { range: [250, 65279] } })],
},
{
zigbeeModel: ["4512733"],
model: "4512733",
vendor: "Namron",
description: "Zigbee dimmer 2-pol 400W",
extend: [m.light({ configureReporting: true })],
},
{
zigbeeModel: ["4512704"],
model: "4512704",
vendor: "Namron",
description: "Zigbee switch 400W",
extend: [m.onOff()],
ota: true,
},
{
zigbeeModel: ["1402755"],
model: "1402755",
vendor: "Namron",
description: "Zigbee LED dimmer",
extend: [m.light({ configureReporting: true })],
},
{
zigbeeModel: ["4512703"],
model: "4512703",
vendor: "Namron",
description: "Zigbee 4 channel switch K8 (white)",
fromZigbee: [fz.command_on, fz.command_off, fz.battery, fz.command_move, fz.command_stop],
exposes: [
e.battery(),
e.action([
"on_l1",
"off_l1",
"brightness_move_up_l1",
"brightness_move_down_l1",
"brightness_stop_l1",
"on_l2",
"off_l2",
"brightness_move_up_l2",
"brightness_move_down_l2",
"brightness_stop_l2",
"on_l3",
"off_l3",
"brightness_move_up_l3",
"brightness_move_down_l3",
"brightness_stop_l3",
"on_l4",
"off_l4",
"brightness_move_up_l4",
"brightness_move_down_l4",
"brightness_stop_l4",
]),
],
toZigbee: [],
meta: { multiEndpoint: true },
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3, l4: 4 };
},
ota: true,
},
{
zigbeeModel: ["4512721"],
model: "4512721",
vendor: "Namron",
description: "Zigbee 4 channel switch K8 (black)",
fromZigbee: [fz.command_on, fz.command_off, fz.battery, fz.command_move, fz.command_stop],
toZigbee: [],
meta: { multiEndpoint: true },
exposes: [
e.battery(),
e.action([
"on_l1",
"off_l1",
"brightness_move_up_l1",
"brightness_move_down_l1",
"brightness_stop_l1",
"on_l2",
"off_l2",
"brightness_move_up_l2",
"brightness_move_down_l2",
"brightness_stop_l2",
"on_l3",
"off_l3",
"brightness_move_up_l3",
"brightness_move_down_l3",
"brightness_stop_l3",
"on_l4",
"off_l4",
"brightness_move_up_l4",
"brightness_move_down_l4",
"brightness_stop_l4",
]),
],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3, l4: 4 };
},
ota: true,
},
{
zigbeeModel: ["4512701"],
model: "4512701",
vendor: "Namron",
description: "Zigbee 1 channel switch K2 (White)",
fromZigbee: [fz.command_on, fz.command_off, fz.battery, fz.command_move, fz.command_stop],
exposes: [e.battery(), e.action(["on", "off", "brightness_move_up", "brightness_move_down", "brightness_stop"])],
toZigbee: [],
},
{
zigbeeModel: ["4512728"],
model: "4512728",
vendor: "Namron",
description: "Zigbee 1 channel switch K2 (Black)",
fromZigbee: [fz.command_on, fz.command_off, fz.battery, fz.command_move, fz.command_stop],
exposes: [e.battery(), e.action(["on", "off", "brightness_move_up", "brightness_move_down", "brightness_stop"])],
toZigbee: [],
},
{
zigbeeModel: ["1402769"],
model: "1402769",
vendor: "Namron",
description: "Zigbee LED dimmer",
extend: [m.light({ configureReporting: true }), m.forcePowerSource({ powerSource: "Mains (single phase)" })],
ota: true,
},
{
zigbeeModel: ["4512702"],
model: "4512702",
vendor: "Namron",
description: "Zigbee 1 channel switch K4",
fromZigbee: [fz.command_on, fz.command_off, fz.battery, fz.command_move, fz.command_stop, fz.command_step],
exposes: [
e.battery(),
e.action(["on", "off", "brightness_move_up", "brightness_move_down", "brightness_stop", "brightness_step_up", "brightness_step_down"]),
],
toZigbee: [],
},
{
zigbeeModel: ["4512719"],
model: "4512719",
vendor: "Namron",
description: "Zigbee 2 channel switch K4 (white)",
fromZigbee: [fz.command_on, fz.command_off, fz.battery, fz.command_move, fz.command_stop],
meta: { multiEndpoint: true },
exposes: [
e.battery(),
e.action([
"on_l1",
"off_l1",
"brightness_move_up_l1",
"brightness_move_down_l1",
"brightness_stop_l1",
"on_l2",
"off_l2",
"brightness_move_up_l2",
"brightness_move_down_l2",
"brightness_stop_l2",
]),
],
toZigbee: [],
endpoint: (device) => {
return { l1: 1, l2: 2 };
},
ota: true,
},
{
fingerprint: [{ modelID: "DIM Lighting", manufacturerName: "Namron As" }],
model: "4512707",
vendor: "Namron",
description: "Zigbee LED-Controller",
extend: [m.light()],
},
{
zigbeeModel: ["4512726"],
model: "4512726",
vendor: "Namron",
description: "Zigbee 4 in 1 dimmer",
fromZigbee: [fz.battery, fz.command_on, fz.command_off, fz.command_move_to_level, fz.command_move_to_color_temp, fz.command_move_to_hue],
toZigbee: [],
exposes: [e.battery(), e.battery_voltage(), e.action(["on", "off", "brightness_move_to_level", "color_temperature_move", "move_to_hue"])],
meta: { battery: { dontDividePercentage: true } },
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
const binds = ["genBasic", "genPowerCfg", "genIdentify", "haDiagnostic", "genOta"];
await reporting.bind(endpoint, coordinatorEndpoint, binds);
await reporting.batteryPercentageRemaining(endpoint);
await reporting.batteryVoltage(endpoint);
},
ota: true,
},
{
zigbeeModel: ["4512729"],
model: "4512729",
vendor: "Namron",
description: "Zigbee 2 channel switch K4 (black)",
fromZigbee: [fz.command_on, fz.command_off, fz.battery, fz.command_move, fz.command_stop],
meta: { multiEndpoint: true },
exposes: [
e.battery(),
e.action([
"on_l1",
"off_l1",
"brightness_move_up_l1",
"brightness_move_down_l1",
"brightness_stop_l1",
"on_l2",
"off_l2",
"brightness_move_up_l2",
"brightness_move_down_l2",
"brightness_stop_l2",
]),
],
toZigbee: [],
endpoint: (device) => {
return { l1: 1, l2: 2 };
},
ota: true,
},
{
zigbeeModel: ["4512706"],
model: "4512706",
vendor: "Namron",
description: "Remote control",
fromZigbee: [
fz.command_on,
fz.command_off,
fz.command_step,
fz.command_step_color_temperature,
fz.command_recall,
fz.command_move_to_color_temp,
fz.battery,
fz.command_move_to_hue,
],
exposes: [
e.battery(),
e.action([
"on",
"off",
"brightness_step_up",
"brightness_step_down",
"color_temperature_step_up",
"color_temperature_step_down",
"recall_*",
"color_temperature_move",
"move_to_hue_l1",
"move_to_hue_l2",
"move_to_hue_l3",
"move_to_hue_l4",
]),
],
toZigbee: [],
meta: { multiEndpoint: true },
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3, l4: 4 };
},
},
{
zigbeeModel: ["4512705"],
model: "4512705",
vendor: "Namron",
description: "Zigbee 4 channel remote control",
fromZigbee: [fz.command_on, fz.command_off, fz.battery, fz.command_move, fz.command_stop, fz.command_recall],
toZigbee: [],
ota: true,
exposes: [
e.battery(),
e.action([
"on_l1",
"off_l1",
"brightness_move_up_l1",
"brightness_move_down_l1",
"brightness_stop_l1",
"on_l2",
"off_l2",
"brightness_move_up_l2",
"brightness_move_down_l2",
"brightness_stop_l2",
"on_l3",
"off_l3",
"brightness_move_up_l3",
"brightness_move_down_l3",
"brightness_stop_l3",
"on_l4",
"off_l4",
"brightness_move_up_l4",
"brightness_move_down_l4",
"brightness_stop_l4",
"recall_*",
]),
],
meta: { multiEndpoint: true },
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3, l4: 4 };
},
},
{
zigbeeModel: ["3802960"],
model: "3802960",
vendor: "Namron",
description: "LED 9W DIM E27",
extend: [m.light()],
},
{
zigbeeModel: ["3802961"],
model: "3802961",
vendor: "Namron",
description: "LED 9W CCT E27",
extend: [m.light({ colorTemp: { range: [153, 370] } })],
},
{
zigbeeModel: ["3802962"],
model: "3802962",
vendor: "Namron",
description: "LED 9W RGBW E27",
extend: [m.light({ colorTemp: { range: undefined }, color: true, turnsOffAtBrightness1: true })],
},
{
zigbeeModel: ["3802963"],
model: "3802963",
vendor: "Namron",
description: "LED 5,3W DIM E14",
extend: [m.light()],
},
{
zigbeeModel: ["3802964"],
model: "3802964",
vendor: "Namron",
description: "LED 5,3W CCT E14",
extend: [m.light({ colorTemp: { range: undefined } })],
},
{
zigbeeModel: ["3802965"],
model: "3802965",
vendor: "Namron",
description: "LED 4,8W DIM GU10",
extend: [m.light()],
},
{
zigbeeModel: ["3802966"],
model: "3802966",
vendor: "Namron",
description: "LED 4.8W CCT GU10",
extend: [m.light({ colorTemp: { range: [153, 454] } })],
},
{
zigbeeModel: ["89665"],
model: "89665",
vendor: "Namron",
description: "LED Strip RGB+W (5m) IP20",
extend: [m.light({ colorTemp: { range: undefined }, color: true, turnsOffAtBrightness1: true })],
},
{
zigbeeModel: ["4512737", "4512738"],
model: "4512737/4512738",
vendor: "Namron",
description: "Touch thermostat",
fromZigbee: [
fz.thermostat,
namron.fromZigbee.namron_thermostat,
fz.metering,
fz.electrical_measurement,
namron.fromZigbee.namron_hvac_user_interface,
],
toZigbee: [
tz.thermostat_occupied_heating_setpoint,
tz.thermostat_unoccupied_heating_setpoint,
tz.thermostat_occupancy,
tz.thermostat_local_temperature_calibration,
tz.thermostat_local_temperature,
tz.thermostat_outdoor_temperature,
tz.thermostat_system_mode,
tz.thermostat_control_sequence_of_operation,
tz.thermostat_running_state,
namron.toZigbee.namron_thermostat_child_lock,
namron.toZigbee.namron_thermostat,
],
exposes: [
e.local_temperature(),
e.numeric("outdoor_temperature", ea.STATE_GET).withUnit("°C").withDescription("Current temperature measured from the floor sensor"),
e
.climate()
.withSetpoint("occupied_heating_setpoint", 0, 40, 0.1)
.withLocalTemperature()
.withLocalTemperatureCalibration(-3, 3, 0.1)
.withSystemMode(["off", "auto", "dry", "heat"])
.withRunningState(["idle", "heat"]),
e.binary("away_mode", ea.ALL, "ON", "OFF").withDescription("Enable/disable away mode"),
e.binary("child_lock", ea.ALL, "LOCK", "UNLOCK").withDescription("Enables/disables physical input on the device"),
e.power(),
e.current(),
e.voltage(),
e.energy(),
e.enum("lcd_brightness", ea.ALL, ["low", "mid", "high"]).withDescription("OLED brightness when operating the buttons. Default: Medium."),
e.enum("button_vibration_level", ea.ALL, ["off", "low", "high"]).withDescription("Key beep volume and vibration level. Default: Low."),
e
.enum("floor_sensor_type", ea.ALL, ["10k", "15k", "50k", "100k", "12k"])
.withDescription("Type of the external floor sensor. Default: NTC 10K/25."),
e.enum("sensor", ea.ALL, ["air", "floor", "both"]).withDescription("The sensor used for heat control. Default: Room Sensor."),
e.enum("powerup_status", ea.ALL, ["default", "last_status"]).withDescription("The mode after a power reset. Default: Previous Mode."),
e
.numeric("floor_sensor_calibration", ea.ALL)
.withUnit("°C")
.withValueMin(-3)
.withValueMax(3)
.withValueStep(0.1)
.withDescription("The tempearatue calibration for the external floor sensor, between -3 and 3 in 0.1°C. Default: 0."),
e
.numeric("dry_time", ea.ALL)
.withUnit("min")
.withValueMin(5)
.withValueMax(100)
.withDescription("The duration of Dry Mode, between 5 and 100 minutes. Default: 5."),
e.enum("mode_after_dry", ea.ALL, ["off", "manual", "auto", "away"]).withDescription("The mode after Dry Mode. Default: Auto."),
e.enum("temperature_display", ea.ALL, ["room", "floor"]).withDescription("The temperature on the display. Default: Room Temperature."),
e
.numeric("window_open_check", ea.ALL)
.withUnit("°C")
.withValueMin(0)
.withValueMax(4)
.withValueStep(0.5)
.withDescription("The threshold to detect window open, between 1.5 and 4 in 0.5 °C. Default: 0 (disabled)."),
e
.numeric("hysterersis", ea.ALL)
.withUnit("°C")
.withValueMin(0.5)
.withValueMax(5)
.withValueStep(0.1)
.withDescription("Hysteresis setting, between 0.5 and 5 in 0.1 °C. Default: 0.5."),
e.enum("display_auto_off_enabled", ea.ALL, ["enabled", "disabled"]),
e
.numeric("alarm_airtemp_overvalue", ea.ALL)
.withUnit("°C")
.withValueMin(0)
.withValueMax(35)
.withDescription("Floor temperature over heating threshold, range is 0-35, unit is 1ºC, " +
"0 means this function is disabled, default value is 27."),
],
// Device does not asks for the time with binding, therefore we write the time every 24 hours
extend: [m.writeTimeDaily({ endpointId: 1 }), namron.namronExtend.addNamronHvacThermostat2Cluster()],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
const binds = [
"genBasic",
"genIdentify",
"hvacThermostat",
"seMetering",
"haElectricalMeasurement",
"genAlarms",
"msOccupancySensing",
"genTime",
"hvacUserInterfaceCfg",
];
await reporting.bind(endpoint, coordinatorEndpoint, binds);
// standard ZCL attributes
await reporting.thermostatTemperature(endpoint, { min: 0, change: 50 });
await reporting.thermostatOccupiedHeatingSetpoint(endpoint);
await reporting.thermostatUnoccupiedHeatingSetpoint(endpoint);
await reporting.thermostatKeypadLockMode(endpoint);
// Metering
await endpoint.read("haElectricalMeasurement", ["acVoltageMultiplier", "acVoltageDivisor", "acCurrentMultiplier"]);
await endpoint.read("haElectricalMeasurement", ["acCurrentDivisor"]);
await reporting.rmsVoltage(endpoint, { min: 10, change: 20 }); // Voltage - Min change of 2v
await reporting.rmsCurrent(endpoint, { min: 10, change: 10 }); // A - z2m displays only the first decimals, so change of 10 (0,01)
await reporting.activePower(endpoint, { min: 10, change: 15 }); // W - Min change of 1,5W
await reporting.currentSummDelivered(endpoint, { min: 300 }); // Report KWH every 5min
await reporting.readMeteringMultiplierDivisor(endpoint);
// OperateDisplayLcdBrightnesss
await endpoint.configureReporting("hvacThermostat", [
{
attribute: "lcdBrightness",
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: null,
},
], sunricherManufacturer);
// ButtonVibrationLevel
await endpoint.configureReporting("hvacThermostat", [
{
attribute: "buttonVibrationLevel",
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: null,
},
], sunricherManufacturer);
// FloorSensorType
await endpoint.configureReporting("hvacThermostat", [
{
attribute: "floorSensorType",
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: null,
},
], sunricherManufacturer);
// ControlType
await endpoint.configureReporting("hvacThermostat", [
{
attribute: "controlType",
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: null,
},
], sunricherManufacturer);
// PowerUpStatus
await endpoint.configureReporting("hvacThermostat", [
{
attribute: "powerUpStatus",
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: null,
},
], sunricherManufacturer);
// FloorSensorCalibration
await endpoint.configureReporting("hvacThermostat", [
{
attribute: "floorSensorCalibration",
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: 0,
},
], sunricherManufacturer);
// DryTime
await endpoint.configureReporting("hvacThermostat", [
{
attribute: "dryTime",
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: 0,
},
], sunricherManufacturer);
// ModeAfterDry
await endpoint.configureReporting("hvacThermostat", [
{
attribute: "modeAfterDry",
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: null,
},
], sunricherManufacturer);
// TemperatureDisplay
await endpoint.configureReporting("hvacThermostat", [
{
attribute: "temperatureDisplay",
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: null,
},
], sunricherManufacturer);
// WindowOpenCheck
await endpoint.configureReporting("hvacThermostat", [
{
attribute: "windowOpenCheck2",
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: 0,
},
], sunricherManufacturer);
// Hysterersis
await endpoint.configureReporting("hvacThermostat", [
{
attribute: "hysterersis",
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: 0,
},
], sunricherManufacturer);
// DisplayAutoOffEnable
await endpoint.configureReporting("hvacThermostat", [
{
attribute: "displayAutoOffEnable",
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: null,
},
], sunricherManufacturer);
// AlarmAirTempOverValue
await endpoint.configureReporting("hvacThermostat", [
{
attribute: "alarmAirTempOverValue",
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: 0,
},
], sunricherManufacturer);
// Away Mode Set
await endpoint.configureReporting("hvacThermostat", [
{
attribute: "awayModeSet",
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: null,
},
], sunricherManufacturer);
// Trigger initial read
await endpoint.read("hvacThermostat", ["systemMode", "runningState", "occupiedHeatingSetpoint"]);
await endpoint.read("hvacThermostat", ["lcdBrightness", "buttonVibrationLevel", "floorSensorType", "controlType"], sunricherManufacturer);
await endpoint.read("hvacThermostat", ["powerUpStatus", "floorSensorCalibration", "dryTime", "modeAfterDry"], sunricherManufacturer);
await endpoint.read("hvacThermostat", ["temperatureDisplay", "windowOpenCheck2", "hysterersis", "displayAutoOffEnable"], sunricherManufacturer);
await endpoint.read("hvacThermostat", ["alarmAirTempOverValue", "awayModeSet"], sunricherManufacturer);
},
ota: true,
},
{
zigbeeModel: ["4512735"],
model: "4512735",
vendor: "Namron",
description: "Multiprise with 4 AC outlets and 2 USB super charging ports (16A)",
fromZigbee: [fz.on_off],
toZigbee: [tz.on_off],
exposes: [
e.switch().withEndpoint("l1"),
e.switch().withEndpoint("l2"),
e.switch().withEndpoint("l3"),
e.switch().withEndpoint("l4"),
e.switch().withEndpoint("l5"),
],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3, l4: 4, l5: 5 };
},
meta: { multiEndpoint: true },
configure: async (device, coordinatorEndpoint) => {
for (const ID of [1, 2, 3, 4, 5]) {
const endpoint = device.getEndpoint(ID);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff"]);
}
device.powerSource = "Mains (single phase)";
device.save();
},
},
{
zigbeeModel: ["5401392", "5401396", "5401393", "5401397", "5401394", "5401398", "5401395", "5401399"],
model: "540139X",
vendor: "Namron",
description: "Panel heater 400/600/800/1000 W",
extend: [namron.namronExtend.addNamronHvacThermostatCluster()],
ota: true,
fromZigbee: [fz.thermostat, fz.metering, fz.electrical_measurement, fzLocal.namron_panelheater, namron.fromZigbee.namron_hvac_user_interface],
toZigbee: [
tz.thermostat_occupied_heating_setpoint,
tz.thermostat_local_temperature_calibration,
tz.thermostat_system_mode,
tz.thermostat_running_state,
tz.thermostat_local_temperature,
tzLocal.namron_panelheater,
namron.toZigbee.namron_thermostat_child_lock,
],
exposes: [
e.power(),
e.current(),
e.voltage(),
e.energy(),
e
.climate()
.withSetpoint("occupied_heating_setpoint", 5, 35, 0.5)
.withLocalTemperature()
// Unit also supports Auto, but i haven't added support the scheduler yet
// so the function is not listed for now, as this doesn´t allow you the set the temperature
.withSystemMode(["off", "heat"])
.withLocalTemperatureCalibration(-3, 3, 0.1)
.withRunningState(["idle", "heat"]),
// Namron proprietary stuff
e.binary("child_lock", ea.ALL, "LOCK", "UNLOCK").withDescription("Enables/disables physical input on the device"),
e
.numeric("hysterersis", ea.ALL)
.withUnit("°C")
.withValueMin(0.5)
.withValueMax(2)
.withValueStep(0.1)
.withDescription("Hysteresis setting, default: 0.5"),
e
.numeric("display_brightnesss", ea.ALL)
.withValueMin(1)
.withValueMax(7)
.withValueStep(1)
.withDescription("Adjust brightness of display values 1(Low)-7(High)"),
e.enum("display_auto_off", ea.ALL, ["deactivated", "activated"]).withDescription("Enable / Disable display auto off"),
e
.enum("power_up_status", ea.ALL, ["manual", "last_state"])
.withDescription("The mode after a power reset. Default: Previous Mode. See instructions for information about manual"),
e.window_detection_bool(),
e.window_open(ea.STATE_GET),
],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
const binds = [
"genBasic",
"genIdentify",
"hvacThermostat",
"seMetering",
"haElectricalMeasurement",
"genAlarms",
"genTime",
"hvacUserInterfaceCfg",
];
// Reporting
// Metering
await reporting.readEletricalMeasurementMultiplierDivisors(endpoint);
await reporting.readMeteringMultiplierDivisor(endpoint);
await reporting.rmsVoltage(endpoint, { min: 10, change: 20 }); // Voltage - Min change of 2v
await reporting.rmsCurrent(endpoint, { min: 10, change: 10 }); // A - z2m displays only the first decimals, so change of 10 (0,01)
await reporting.activePower(endpoint, { min: 10, change: 15 }); // W - Min change of 1,5W
await reporting.currentSummDelivered(endpoint, { min: 300 }); // Report KWH every 5min
// Thermostat reporting
await reporting.thermostatTemperature(endpoint);
await reporting.thermostatOccupiedHeatingSetpoint(endpoint);
await reporting.thermostatTemperature(endpoint);
await reporting.thermostatKeypadLockMode(endpoint);
// LocalTemp is spammy, reports 0.01C diff by default, min change is now 0.5C
await reporting.thermostatTemperature(endpoint, { min: 0, change: 50 });
// display_brightnesss
await endpoint.configureReporting("hvacThermostat", [
{
attribute: "operateDisplayBrightness",
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: null,
},
], sunricherManufacturer);
// display_auto_off
await endpoint.configureReporting("hvacThermostat", [
{
attribute: "displayAutoOff",
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: null,
},
], sunricherManufacturer);
// power_up_status
await endpoint.configureReporting("hvacThermostat", [
{
attribute: "powerUpStatus",
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: null,
},
], sunricherManufacturer);
// window_detection
await endpoint.configureReporting("hvacThermostat", [
{
attribute: "windowOpenCheck2",
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: null,
},
], sunricherManufacturer);
// hysterersis
await endpoint.configureReporting("hvacThermostat", [
{
attribute: "hysterersis",
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: null,
},
], sunricherManufacturer);
// window_open
await endpoint.configureReporting("hvacThermostat", [
{
attribute: "windowOpen",
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: null,
},
], sunricherManufacturer);
await endpoint.read("hvacThermostat", ["systemMode", "runningState", "occupiedHeatingSetpoint"]);
await endpoint.read("hvacUserInterfaceCfg", ["keypadLockout"]);
await endpoint.read("hvacThermostat", ["operateDisplayBrightness", "displayAutoOff", "powerUpStatus", "windowOpenCheck2", "hysterersis", "windowOpen"], sunricherManufacturer);
await reporting.bind(endpoint, coordinatorEndpoint, binds);
},
},
{
zigbeeModel: ["Panel Heater"],
model: "4512776/4512777",
vendor: "Namron",
description: "Zigbee thermostat for panel heater PRO (white 4512776 / black 4512777)",
extend: [
namron.namronExtend.addNamronHvacThermostatCluster(),
m.electricityMeter({ cluster: "both", energy: { divisor: 10 }, power: false, voltage: false, current: false, configureReporting: false }),
],
fromZigbee: [fz.thermostat, fzLocal.namron_panelheater, namron.fromZigbee.namron_hvac_user_interface, fz.electrical_measurement],
toZigbee: [
tz.thermostat_occupied_heating_setpoint,
tz.thermostat_local_temperature_calibration,
tz.thermostat_system_mode,
namron.toZigbee.namron_thermostat_child_lock,
tzLocal.namron_panelheater_pro_state,
tzLocal.namron_panelheater_pro_frost_mode,
tzLocal.namron_panelheater_pro_hysteresis,
tzLocal.namron_panelheater_pro_window_open_detection,
tzLocal.namron_panelheater_pro_display_auto_off,
tzLocal.namron_panelheater_pro_control_method,
tzLocal.namron_panelheater_pro_adaptive_function,
tzLocal.namron_panelheater_pro_pid_kp,
tzLocal.namron_panelheater_pro_pid_ki,
tzLocal.namron_panelheater_pro_pid_kd,
],
exposes: [
e
.climate()
.withLocalTemperature()
.withSetpoint("occupied_heating_setpoint", 5, 35, 0.5)
.withLocalTemperatureCalibration(-10, 10, 0.5)
.withSystemMode(["off", "heat", "auto"])
.withRunningState(["idle", "heat"]),
e.binary("state", ea.ALL, "ON", "OFF").withDescription("Virtual on/off (maps to systemMode)"),
e
.binary("frost_mode", ea.ALL, true, false)
.withDescription("Frost protection: HEAT + 7°C, restores previous mode/setpoint when disabled"),
e.binary("child_lock", ea.ALL, "LOCK", "UNLOCK").withDescription("Enables/disables physical input on the device"),
e
.numeric("hysteresis", ea.ALL)
.withUnit("°C")
.withValueMin(0.5)
.withValueMax(5.0)
.withValueStep(0.1)
.withDescription("Hysteresis (0.5–5.0°C) for on/off control"),
e.binary("window_open_detection", ea.ALL, true, false).withDescription("Window open detection"),
e.binary("window_open", ea.STATE, true, false).withDescription("Whether the heater thinks window is open"),
e.enum("control_method", ea.ALL, ["pid", "hysteresis"]).withDescription("System control method (PID or hysteresis)"),
e.binary("adaptive_function", ea.ALL, true, false).withDescription("Adaptive preheat function (on/off)"),
e.numeric("pid_kp", ea.ALL).withValueMin(0).withValueMax(1).withValueStep(0.01).withDescription("PID Kp"),
e.numeric("pid_ki", ea.ALL).withValueMin(0).withValueMax(1).withValueStep(0.01).withDescription("PID Ki"),
e.numeric("pid_kd", ea.ALL).withValueMin(0).withValueMax(1).withValueStep(0.01).withDescription("PID Kd"),
e
.numeric("display_brightness", ea.STATE)
.withValueMin(1)
.withValueMax(7)
.withValueStep(1)
.withDescription("Display brightness (read-only, set on the heater)"),
e.binary("display_auto_off", ea.ALL, true, false).withDescription("Display auto off after 30s without interaction"),
e.power(),
],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
// Save energy divisor manually since configureReporting is disabled
endpoint.saveClusterAttributeKeyValue("seMetering", { divisor: 10, multiplier: 1 });
endpoint.save();
await reporting.bind(endpoint, coordinatorEndpoint, [
"genBasic",
"genIdentify",
"hvacThermostat",
"hvacUserInterfaceCfg",
"seMetering",
"haElectricalMeasurement",
]);
await reporting.thermostatTemperature(endpoint, { min: 0, change: 50 });
await reporting.thermostatOccupiedHeatingSetpoint(endpoint);
await endpoint.read("hvacThermostat", ["localTemp", "occupiedHeatingSetpoint", "systemMode"]);
// Proprietary attrs including display, window, PID, control_method, adaptive
try {
await endpoint.read("hvacThermostat", [
"operateDisplayBrightness",
"displayAutoOff",
"windowOpenCheck2",
"hysterersis",
"windowOpen",
"adaptiveFunction",
"pidKp",
"pidKd",
"pidKi",
"controlMethod",
], sunricherManufacturer);
}
catch {
// Ignore - some attributes may not be supported
}
try {
await endpoint.read("hvacUserInterfaceCfg", ["keypadLockout"]);
}
catch {
// Ignore
}
try {
await endpoint.read("haElectricalMeasurement", ["activePower"]);
}
catch {
// Ignore
}
try {
await endpoint.read("seMetering", ["currentSummDelivered"]);
}
catch {
// Ignore
}
device.powerSource = "Mains (single phase)";
device.save();
},
},
{
zigbeeModel: ["3802968"],
model: "3802968",
vendor: "Namron",
description: "LED Filament Flex 5W CCT E27 Clear",
extend: [m.light({ colorTemp: { range: [153, 555] }, turnsOffAtBrightness1: true })],
},
{
zigbeeModel: ["4512749"],
model: "4512749",
vendor: "Namron",
description: "Thermostat outlet socket",
fromZigbee: [fz.metering, fz.electrical_measurement, fz.on_off, fz.temperature],
toZigbee: [tz.on_off, tz.power_on_behavior],
exposes: [e.temperature(), e.power(), e.current(), e.voltage(), e.switch(), e.power_on_behavior()],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff", "haElectricalMeasurement", "msTemperatureMeasurement"]);
await endpoint.read("haElectricalMeasurement", ["acVoltageMultiplier", "acVoltageDivisor"]);
await endpoint.read("haElectricalMeasurement", ["acPowerMultiplier", "acPowerDivisor"]);
await endpoint.read("haElectricalMeasurement", ["acCurrentMultiplier", "acCurrentDivisor"]);
await reporting.onOff(endpoint);
await reporting.temperature(endpoint);
await reporting.rmsVoltage(endpoint);
await reporting.rmsCurrent(endpoint);
await reporting.activePower(endpoint);
},
},
{
zigbeeModel: ["4512749-N"],
model: "4512749-N",
vendor: "Namron",
description: "Thermostat outlet socket",
fromZigbee: [fz.metering, fz.electrical_measurement, fz.on_off, fz.temperature],
toZigbee: [tz.on_off, tz.power_on_behavior],
exposes: [e.temperature(), e.power(), e.current(), e.voltage(), e.switch(), e.power_on_behavior()],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genOnOff", "haElectricalMeasurement", "msTemperatureMeasurement"]);
await endpoint.read("haElectricalMeasurement", ["acVoltageMultiplier", "acVoltageDivisor"]);
await endpoint.read("haElectricalMeasurement", ["acPowerMultiplier", "acPowerDivisor"]);
await endpoint.read("haElectricalMeasurement", ["acCurrentMultiplier", "acCurrentDivisor"]);
await reporting.onOff(endpoint);
await reporting.temperature(endpoint, { min: 10, change: 10 });
await reporting.rmsVoltage(endpoint, { min: 10, change: 20 }); // Voltage - Min change of 2v
await reporting.rmsCurrent(endpoint, { min: 10, change: 10 }); // A - z2m displays only the first decimals, so change of 10
await reporting.activePower(endpoint, { min: 10, change: 1 }); // W - Min change of 0,1W
},
},
{
zigbeeModel: ["4512747"],
model: "4512747",
vendor: "Namron",
description: "Curtain motor controller",
fromZigbee: [fz.cover_position_tilt],
toZigbee: [tz.cover_state, tz.cover_position_tilt],
exposes: [e.cover_position()],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["closuresWindowCovering"]);
await reporting.currentPositionLiftPercentage(endpoint);
},
},
{
zigbeeModel: ["4512758", "4512759"],
model: "4512758",
vendor: "Namron",
description: "Zigbee thermostat 16A",
whiteLabel: [{ model: "4512759", fingerprint: [{ modelID: "4512759" }] }],
fromZigbee: [fzLocal.namron_thermostat2, fz.metering, fz.electrical_measurement, namron.fromZigbee.namron_hvac_user_interface],
toZigbee: [
{
// map running *mode* to *state*, as that's what used
// in homeAssistant climate ui card (red background)
key: ["running_state"],
convertGet: async (entity, key, meta) => {
await entity.read("hvacThermostat", ["runningMode"]);
},
},
tz.thermostat_local_temperature,
tz.thermostat_min_heat_setpoint_limit,
tz.thermostat_max_heat_setpoint_limit,
// tz.thermostat_min_cool_setpoint_limit,
// tz.thermostat_max_cool_setpoint_limit,
// tz.thermostat_pi_heating_demand,
tz.thermostat_local_temperature_calibration,
// tz.thermostat_occupied_cooling_setpoint,
tz.thermostat_occupied_heating_setpoint,
tz.thermostat_control_sequence_of_operation,
tz.thermostat_system_mode,
tz.thermostat_running_mode,
namron.toZigbee.namron_thermostat_child_lock,
],
extend: [
namron.namronExtend.addNamronHvacThermostatCluster(),
m.onOff({ powerOnBehavior: false }),
m.electricityMeter({ voltage: false }),
m.binary({
name: "away_mode",
valueOn: ["ON", 1],
valueOff: ["OFF", 0],
cluster: "hvacThermostat",
attribute: "antiFrost",
description: "Enable or Disable Away/Anti-freeze mode",
}),
m.binary({
name: "window_open_check",
valueOn: ["ON", 1],
valueOff: ["OFF", 0],
cluster: "hvacThermostat",
attribute: "windowOpenCheck",
description: "Enable or Disable open window detection",
entityCategory: "config",
}),
m.binary({
name: "window_open",
valueOn: ["ON", 1],
valueOff: ["OFF", 0],
cluster: "hvacThermostat",
attribute: "windowState",
description: "On if window is currently detected as open",
}),
m.numeric({
name: "backlight_level",
unit: "%",
valueMin: 0,
valueMax: 100,
valueStep: 10,
cluster: "hvacThermostat",
attribute: "displayActiveBacklight",
description: "Brightness of the display",
entityCategory: "config",
}),
m.binary({
name: "backlight_onoff",
valueOn: ["ON", 1],
valueOff: ["OFF", 0],
cluster: "hvacThermostat",
attribute: "backlightOnoff",
description: "Enable or Disable display light",
entityCategory: "config",
}),
m.enumLookup({
name: "sensor_mode",
lookup: { air: 0, floor: 1, both: 2, percent: 6 },
cluster: "hvacThermostat",
attribute: "sensorMode",
description: "Select which sensor the thermostat uses to control the room",
entityCategory: "config",
}),
],
exposes: [
// FUTURE: could maybe translate to a common cooling/heating setpoint depending on the mode
// and state.. HomeAssistant climate widget doesn't play nice with two setpoints.
e
.climate()
.withLocalTemperature()
.withSetpoint("occupied_heating_setpoint", 0, 40, 0.5)
//.withSetpoint('occupied_cooling_setpoint', 0, 40, 0.5)
.withLocalTemperatureCalibration(-10, 10, 1)
//.withSystemMode(['off', 'auto', 'cool', 'heat'])
.withSystemMode(["off", "heat"])
//.withRunningMode(['off', 'cool','heat'])
.withRunningState(["idle", "cool", "heat"]),
//.withPiHeatingDemand()
e.binary("child_lock", ea.ALL, "LOCK", "UNLOCK").withDescription("Enables/disables physical input on the device"),
],
configure: async (device, coordinatorEndpoint, logger) => {
const endpoint = device.getEndpoint(1);
const binds = ["genBasic", "genIdentify", "hvacThermostat", "seMetering", "haElectricalMeasurement", "genAlarms", "hvacUserInterfaceCfg"];
await reporting.bind(endpoint, coordinatorEndpoint, binds);
await reporting.thermostatTemperature(endpoint, { min: 0, change: 50 });
// await reporting.thermostatPIHeatingDemand(endpoint);
await reporting.thermostatOccupiedHeatingSetpoint(endpoint);
await reporting.thermostatKeypadLockMode(endpoint);
// Trigger initial read
await endpoint.read("hvacThermostat", ["systemMode", "runningMode", "occupiedHeatingSetpoint"]);
await endpoint.read("hvacThermostat", [
"windowOpenCheck",
"antiFrost",
"windowState",
"sensorMode",
]);
device.powerSource = "Mains (single phase)";
device.save();
},
},
{
zigbeeModel: ["4512762"],
model: "4512762",
vendor: "Namron",
description: "Zigbee Door Sensor",
fromZigbee: [fz.ias_contact_alarm_1, fz.battery, fz.ias_contact_alarm_1_report],
toZigbee: [],
exposes: [e.contact(), e.battery(), e.battery_voltage()],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genPowerCfg"]);
await reporting.batteryPercentageRemaining(endpoint);
await reporting.batteryVoltage(endpoint);
},
},
{
zigbeeModel: ["4512763"],
model: "4512763",
vendor: "Namron",
description: "Zigbee movement sensor",
fromZigbee: [fz.ias_occupancy_alarm_1],
toZigbee: [],
exposes: [e.occupancy()],
},
{
zigbeeModel: ["4512764"],
model: "4512764",
vendor: "Namron",
description: "Zigbee water leak sensor",
fromZigbee: [fz.ias_water_leak_alarm_1, fz.battery],
toZigbee: [],
exposes: [e.battery_low(), e.water_leak(), e.battery()],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genPowerCfg"]);
await reporting.batteryPercentageRemaining(endpoint);
},
},
{
zigbeeModel: ["4512765"],
model: "4512765",
vendor: "Namron",
description: "Zigbee humidity and temperature Sensor",
fromZigbee: [fz.battery, fz.temperature, fz.humidity],
toZigbee: [],
exposes: [e.battery(), e.temperature(), e.humidity()],
},
{
zigbeeModel: ["4512750", "4512751"],
model: "4512750",
vendor: "Namron",
description: "Zigbee dimmer 2.0",
ota: true,
extend: [m.light({ configureReporting: true })],
whiteLabel: [{ vendor: "Namron", model: "4512751", description: "Zigbee dimmer 2.0", fingerprint: [{ modelID: "4512751" }] }],
},
{
zigbeeModel: ["4512772", "4512773"],
model: "4512773",
vendor: "Namron",
description: "Zigbee 8 channel switch black",
whiteLabel: [{ vendor: "Namron", model: "4512772", description: "Zigbee 8 channel switch white", fingerprint: [{ modelID: "4512772" }] }],
fromZigbee: [fz.battery, fz.command_on, fz.command_off, fz.command_move, fz.command_stop],
toZigbee: [],
meta: { multiEndpoint: true },
exposes: [
e.battery(),
e.action([
"on_l1",
"off_l1",
"brightness_move_up_l1",
"brightness_move_down_l1",
"brightness_stop_l1",
"on_l2",
"off_l2",
"brightness_move_up_l2",
"brightness_move_down_l2",
"brightness_stop_l2",
"on_l3",
"off_l3",
"brightness_move_up_l3",
"brightness_move_down_l3",
"brightness_stop_l3",
"on_l4",
"off_l4",
"brightness_move_up_l4",
"brightness_move_down_l4",
"brightness_stop_l4",
]),
],
endpoint: (device) => {
return { l1: 1, l2: 2, l3: 3, l4: 4 };
},
ota: true,
},
{
zigbeeModel: ["4512768"],
model: "4512768",
vendor: "Namron",
description: "Zigbee 2 channel switch",
extend: [
m.deviceEndpoints({ endpoints: { l1: 1, l2: 2 } }),
m.onOff({ endpointNames: ["l1", "l2"] }),
m.electricityMeter({ endpointNames: ["l1", "l2"] }),
],
},
{
zigbeeModel: ["4512761"],
model: "4512761",
vendor: "Namron",
description: "Zigbee relais 16A",
fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, fz.power_on_behavior],
toZigbee: [tz.on_off, tz.power_on_behavior],
exposes: [e.switch(), e.power(), e.current(), e.voltage(), e.energy(), e.power_on_behavior()],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ["genBasic", "genOnOff", "haElectricalMeasurement", "seMetering"]);
await reporting.readEletricalMeasurementMultiplierDivisors(endpoint);
await reporting.readMeteringMultiplierDivisor(endpoint);
await reporting.onOff(endpoint);
},
ota: true,
},
{
zigbeeModel: ["4512770", "4512771"],
model: "4512770",
vendor: "Namron",
description: "Zigbee multisensor (white)",
fromZigbee: [fz.ias_occupancy_alarm_1, fz.battery, fz.temperature, fz.humidity],
toZigbee: [],
exposes: [e.occupancy(), e.battery(), e.battery_voltage(), e.temperature(), e.humidity()],
whiteLabel: [{ vendor: "Namron", model: "4512771", description: "Zigbee multisensor (black)", fingerprint: [{ modelID: "4512771" }] }],
configure: async (device, coordinatorEndpoint) => {
const endpoint3 = device.getEndpoint(3);
const endpoint4 = device.getEndpoint(4);
await reporting.bind(endpoint3, coordinatorEndpoint, ["msTemperatureMeasurement"]);
await reporting.bind(endpoint4, coordinatorEndpoint, ["msRelativeHumidity"]);
},
extend: [m.illuminance()],
},
{
fingerprint: tuya.fingerprint("TS0601", ["_TZE204_p3lqqy2r"]),
model: "4512752/4512753",
vendor: "Namron",
description: "Touch thermostat 16A 2.0",
extend: [tuya.modernExtend.tuyaBase({ dp: true, timeStart: "2000" })],
options: [],
exposes: [
e
.enum("mode", ea.STATE_SET, ["regulator", "thermostat"])
.withDescription("Controls how the operating mode of the device. Possible values:" +
" regulator (open-loop controller), thermostat (control with target temperature)"),
e
.enum("regulator_period", ea.STATE_SET, ["15min", "30min", "45min", "60min", "90min"])
.withLabel("Regulator cycle duration")
.withDescription("Regulator cycle duration. Not applicable when in thermostat mode."),
e
.numeric("regulator_set_point", ea.STATE_SET)
.withUnit("%")
.withDescription("Desired heating set point (%) when in regulator mode.")
.withValueMin(0)
.withValueMax(95),
e
.climate()
.withSystemMode(["off", "heat"], ea.STATE_SET, "Whether the thermostat is turned on or off")
.withPreset(["manual", "home", "away"])
.withLocalTemperature(ea.STATE)
.withLocalTemperatureCalibration(-9, 9, 1, ea.STATE_SET)
.withRunningState(["idle", "heat"], ea.STATE)
.withSetpoint("current_heating_setpoint", 5, 35, 1, ea.STATE_SET),
e.current(),
e.power(),
e.energy(),
e.voltage(),
e.temperature_sensor_select(["air_sensor", "floor_sensor", "both"]),
e
.numeric("local_temperature", ea.STATE)
.withUnit("°C")
.withDescription("Current temperature measured with internal sensor")
.withValueStep(1),
e
.numeric("local_temperature_floor", ea.STATE)
.withUnit("°C")
.withDescription("Current temperature measured on the external sensor (floor)")
.withValueStep(1),
e.child_lock(),
e.window_detection().withLabel("Open window detection"),
e
.numeric("hysteresis", ea.STATE_SET)
.withUnit("°C")
.withDescription("The offset from the target temperature in which the temperature has to " +
"change for the heating state to change. This is to prevent erratically turning on/off " +
"when the temperature is close to the target.")
.withValueMin(1)
.withValueMax(9)
.withValueStep(1),
e
.numeric("max_temperature_protection", ea.STATE_SET)
.withUnit("°C")
.withDescription("Max guarding temperature")
.withValueMin(20)
.withValueMax(95)
.withValueStep(1),
],
meta: {
tuyaDatapoints: [
[1, "system_mode", tuya.valueConverterBasic.lookup({ off: false, heat: true })],
[2, "preset", tuya.valueConverterBasic.lookup({ manual: tuya.enum(0), home: tuya.enum(1), away: tuya.enum(2) })],
[16, "current_heating_setpoint", tuya.valueConverter.raw],
[24, "local_temperature", tuya.valueConverter.raw],
[28, "local_temperature_calibration", tuya.valueConverter.raw],
[30, "child_lock", tuya.valueConverter.lockUnlock],
[101, "local_temperature_floor", tuya.valueConverter.raw],
[102, "sensor", tuya.valueConverterBasic.lookup({ air_sensor: tuya.enum(0), floor_sensor: tuya.enum(1), both: tuya.enum(2) })],
[103, "hysteresis", tuya.valueConverter.raw],
[104, "running_state", tuya.valueConverterBasic.lookup({ idle: false, heat: true })],
[106, "window_detection", tuya.valueConverter.onOff],
[107, "max_temperature_protection", tuya.valueConverter.raw],
[108, "mode", tuya.valueConverterBasic.lookup({ regulator: tuya.enum(0), thermostat: tuya.enum(1) })],
[
109,
"regulator_period",
tuya.valueConverterBasic.lookup({
"15min": tuya.enum(0),
"30min": tuya.enum(1),
"45min": tuya.enum(2),
"60min": tuya.enum(3),
"90min": tuya.enum(4),
}),
],
[110, "regulator_set_point", tuya.valueConverter.raw],
[120, "current", tuya.valueConverter.divideBy10],
[121, "voltage", tuya.valueConverter.raw],
[122, "power", tuya.valueConverter.raw],
[123, "energy", tuya.valueConverter.divideBy100],
],
},
},
{
zigbeeModel: ["4512782", "4512781", "4566700", "4566701"],
model: "4512782 / 4512781 / 4566700 / 4566701",
vendor: "Namron",
description: "Namron Edge Dimmer",
extend: [
m.light({ effect: false, configureReporting: true, powerOnBehavior: false }),
m.electricityMeter({ voltage: false, current: false, configureReporting: true }),
],
meta: {},
},
{
zigbeeModel: ["4512788"],
model: "4512788",
vendor: "Namron",
description: "Zigbee smart plug dimmer 150W",
extend: [m.light({ effect: false, configureReporting: true }), m.electricityMeter({ cluster: "electrical" })],
},
{
zigbeeModel: ["1402790"],
model: "1402790",
vendor: "Namron",
description: "Stove guard for safe cooking",
extend: [
m.deviceEndpoints({ endpoints: { main_switch: 1, short_override: 2 } }),
m.onOff({ powerOnBehavior: false, endpointNames: ["main_switch"], description: "Main relay switch" }),
m.onOff({ powerOnBehavior: false, endpointNames: ["short_override"], description: "Short override switch" }),
m.electricityMeter({
endpointNames: ["main_switch"],
power: { multiplier: 1, divisor: 1 },
voltage: false,
current: false,
}),
m.battery(),
m.temperature({ reporting: undefined }),
],
},
{
zigbeeModel: ["4512792"],
model: "4512792",
vendor: "Namron",
description: "Simplify 1-2p relay (Zigbee / BT)",
extend: [
m.onOff(),
m.electricityMeter({
power: { multiplier: 1, divisor: 10 }, // W
voltage: { multiplier: 1, divisor: 10 }, // V -> 2383 -> 238.3
current: { multiplier: 1, divisor: 100 }, // A
energy: { multiplier: 1, divisor: 100 }, // kWh
}),
],
},
{
zigbeeModel: ["4512793", "4512794"],
model: "4512793",
vendor: "Namron",
description: "Simplify 6-button remote with battery",
extend: [m.battery(), namron.namronExtend.addCustomClusterNamronPrivateE004()],
fromZigbee: [fzLocal.namronSimplifyRemote],
toZigbee: [],
exposes: [
e.action([
"button_1_up_press",
"button_1_up_hold",
"button_1_up_release",
"button_1_down_press",
"button_1_down_hold",
"button_1_down_release",
"button_2_up_press",
"button_2_up_hold",
"button_2_up_release",
"button_2_down_press",
"button_2_down_hold",
"button_2_down_release",
"button_3_up_press",
"button_3_up_hold",
"button_3_up_release",
"button_3_down_press",
"button_3_down_hold",
"button_3_down_release",
]),
],
},
{
zigbeeModel: ["4512791"],
model: "4512791",
vendor: "Namron",
description: "Namron Simplify Zigbee dimmer (1/2-polet / Zigbee / BT)",
extend: [
m.electricityMeter({
power: { multiplier: 1, divisor: 10 },
voltage: { multiplier: 1, divisor: 10 },
current: { multiplier: 1, divisor: 100 },
energy: { multiplier: 1, divisor: 100 },
}),
],
exposes: [
e.light_brightness(),
exposes
.numeric("min_brightness", ea.ALL)
.withValueMin(1)
.withValueMax(50)
.withUnit("%")
.withDescription("Minimum brightness in % (1–50%). Written to device minLevel (0x0002).")
.withCategory("config"),
exposes
.numeric("max_brightness", ea.ALL)
.withValueMin(51)
.withValueMax(100)
.withUnit("%")
.withDescription("Maximum brightness in % (51–100%). Written to device maxLevel (0x0003).")
.withCategory("config"),
exposes
.numeric("dimming_speed", ea.ALL)
.withValueMin(1)
.withValueMax(10)
.withUnit("s")
.withDescription("Default dimming time in seconds (1–10s). Written to device defaultMoveRate (0x0014).")
.withCategory("config"),
exposes
.numeric("start_brightness", ea.ALL)
.withValueMin(1)
.withValueMax(254)
.withDescription("Default brightness at power-on/startup (1–254). Written to device onLevel (0x0011).")
.withCategory("config"),
exposes
.enum("startup_on_off", ea.ALL, ["off", "on", "toggle", "previous"])
.withDescription("On/Off state at power-on/startup.")
.withCategory("config"),
exposes
.enum("dimmer_mode", ea.STATE, ["trailing_edge", "leading_edge"])
.withDescription("Dimmer type: trailing edge (RC) or leading edge (RL). Set via Namron Simplify Hub/app."),
],
fromZigbee: [
fz.on_off,
fz.brightness,
fz.electrical_measurement,
fz.metering,
{
cluster: "genLevelCtrl",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
// Only use readResponse for min/max - attributeReport may contain stale cached values
if (Object.hasOwn(msg.data, "minLevel") && msg.type === "readResponse")
result["min_brightness"] = sdLevelToPct(msg.data["minLevel"]);
if (Object.hasOwn(msg.data, "maxLevel") && msg.type === "readResponse")
result["max_brightness"] = sdLevelToPct(msg.data["maxLevel"]);
if (Object.hasOwn(msg.data, "onLevel"))
result["start_brightness"] = msg.data["onLevel"];
if (Object.hasOwn(msg.data, "defaultMoveRate"))
result["dimming_speed"] = msg.data["defaultMoveRate"];
if (Object.hasOwn(msg.data, 0xb000))
result["dimmer_mode"] = msg.data[0xb000] === 0 ? "trailing_edge" : "leading_edge";
return result;
},
},
{
cluster: "genOnOff",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
if (Object.hasOwn(msg.data, "startUpOnOff")) {
const map = { 0: "off", 1: "on", 2: "toggle", 255: "previous" };
result["startup_on_off"] = map[msg.data["startUpOnOff"]] ?? String(msg.data["startUpOnOff"]);
}
return result;
},
},
],
toZigbee: [
tz.light_onoff_brightness,
tzLocalSimplifyDimmer4512791.min_brightness,
tzLocalSimplifyDimmer4512791.max_brightness,
tzLocalSimplifyDimmer4512791.dimming_speed,
tzLocalSimplifyDimmer4512791.start_brightness,
{
key: ["startup_on_off"],
convertSet: async (entity, key, value, meta) => {
const map = { off: 0, on: 1, toggle: 2, previous: 255 };
await entity.write("genOnOff", { startUpOnOff: map[value] ?? Number.parseInt(value, 10) });
return { state: { startup_on_off: value } };
},
convertGet: async (entity, key, meta) => {
await entity.read("genOnOff", ["startUpOnOff"]);
},
},
],
configure: async (device, _coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await endpoint.read("genOnOff", ["startUpOnOff"]);
try {
await endpoint.read("genLevelCtrl", ["minLevel", "maxLevel", "onLevel", "defaultMoveRate"]);
await endpoint.read("genLevelCtrl", [0xb000]);
}
catch (_e) {
// Not all firmware versions support reading these
}
},
},
{
zigbeeModel: ["4512785"],
model: "4512785",
vendor: "Namron",
description: "Zigbee 30A relay with NTC temperature sensors and water leak detection",
extend: [
m.deviceAddCustomCluster("namronPrivate04E0", {
ID: 0x04e0,
name: "namronPrivate04E0",
attributes: {
ntc2Temperature: { ID: 0x0000, name: "ntc2Temperature", type: zigbee_herdsman_1.Zcl.DataType.INT16 },
ntcSensorType1: { ID: 0x0001, name: "ntcSensorType1", type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true },
ntcSensorType2: { ID: 0x0002, name: "ntcSensorType2", type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true },
waterSensorValue: { ID: 0x0003, name: "waterSensorValue", type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN },
ntcCalibration1: { ID: 0x0004, name: "ntcCalibration1", type: zigbee_herdsman_1.Zcl.DataType.INT8, write: true },
ntcCalibration2: { ID: 0x0005, name: "ntcCalibration2", type: zigbee_herdsman_1.Zcl.DataType.INT8, write: true },
waterAlarmRelayAction: { ID: 0x0006, name: "waterAlarmRelayAction", type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true },
ntc1OperationSelect: { ID: 0x0007, name: "ntc1OperationSelect", type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true },
ntc2OperationSelect: { ID: 0x0008, name: "ntc2OperationSelect", type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true },
ntc1RelayAutoTemp: { ID: 0x0009, name: "ntc1RelayAutoTemp", type: zigbee_herdsman_1.Zcl.DataType.INT16, write: true },
ntc2RelayAutoTemp: { ID: 0x000a, name: "ntc2RelayAutoTemp", type: zigbee_herdsman_1.Zcl.DataType.INT16, write: true },
overrideOption: { ID: 0x000b, name: "overrideOption", type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true },
ntc1TempHysteresis: { ID: 0x000c, name: "ntc1TempHysteresis", type: zigbee_herdsman_1.Zcl.DataType.INT8, write: true },
ntc2TempHysteresis: { ID: 0x000d, name: "ntc2TempHysteresis", type: zigbee_herdsman_1.Zcl.DataType.INT8, write: true },
waterConditionAlarm: { ID: 0x000e, name: "waterConditionAlarm", type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN },
ntcConditionAlarm: { ID: 0x000f, name: "ntcConditionAlarm", type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN },
isExecuteCondition: { ID: 0x0010, name: "isExecuteCondition", type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN },
},
commands: {},
commandsResponse: {},
}),
m.onOff({ powerOnBehavior: true }),
m.electricityMeter({ voltage: { divisor: 10 }, current: { divisor: 1000 }, power: { divisor: 1 }, energy: { divisor: 100 } }),
// Override genDeviceTempCfg to relax currentTemperature max constraint
// (device reports in 0.1°C units, e.g. 311 = 31.1°C, exceeding ZCL max of 200)
m.deviceAddCustomCluster("genDeviceTempCfg", {
ID: 0x0002,
name: "genDeviceTempCfg",
attributes: {
currentTemperature: { ID: 0x0000, name: "currentTemperature", type: zigbee_herdsman_1.Zcl.DataType.INT16, min: -2000, max: 2000 },
},
commands: {},
commandsResponse: {},
}),
// Device reports currentTemperature in 0.1°C units (non-standard; ZCL spec is °C).
// e.g. raw 311 = 31.1°C. change: 10 raw units = 1°C reporting threshold; fzConvert divides by 10.
m.numeric({
name: "device_temperature",
cluster: "genDeviceTempCfg",
attribute: "currentTemperature",
reporting: { min: 15, max: 600, change: 10 },
description: "Internal device temperature",
unit: "°C",
access: "STATE_GET",
entityCategory: "diagnostic",
fzConvert: (model, msg) => {
if (msg.data.currentTemperature !== undefined) {
const raw = msg.data.currentTemperature;
if (raw !== -32768 && raw !== 0x8000) {
return { device_temperature: utils.precisionRound(raw, 2) / 10 };
}
}
},
}),
// Device reports measuredValue in 0.01°C units (standard for msTemperatureMeasurement).
// e.g. raw 2250 = 22.50°C. change: 10 raw units = 0.1°C reporting threshold; fzConvert divides by 100.
m.numeric({
name: "ntc1_temperature",
cluster: "msTemperatureMeasurement",
attribute: "measuredValue",
reporting: { min: 15, max: 600, change: 10 },
description: "External NTC1 temperature probe",
unit: "°C",
access: "STATE_GET",
fzConvert: (model, msg) => {
if (msg.data.measuredValue !== undefined) {
const raw = msg.data.measuredValue;
if (raw !== -32768 && raw !== 0x8000) {
return { ntc1_temperature: utils.precisionRound(raw, 2) / 100 };
}
}
},
}),
// Device reports ntc2Temperature in 0.01°C units (same as NTC1).
// e.g. raw 2350 = 23.50°C. change: 10 raw units = 0.1°C reporting threshold; fzConvert divides by 100.
m.numeric({
name: "ntc2_temperature",
cluster: "namronPrivate04E0",
attribute: "ntc2Temperature",
reporting: { min: 15, max: 600, change: 10 },
description: "External NTC2 temperature probe",
unit: "°C",
access: "STATE_GET",
fzConvert: (model, msg) => {
if (msg.data.ntc2Temperature !== undefined) {
const raw = msg.data.ntc2Temperature;
if (raw !== -32768 && raw !== 0x8000) {
return { ntc2_temperature: utils.precisionRound(raw, 2) / 100 };
}
}
},
}),
// Water sensor (NO contacts: shorted = water detected)
m.binary({
name: "water_sensor",
cluster: "namronPrivate04E0",
attribute: "waterSensorValue",
description: "External water sensor (true = water detected)",
valueOn: [true, 1],
valueOff: [false, 0],
access: "STATE_GET",
reporting: { min: 1, max: 300, change: 1 },
}),
// NTC sensor type configuration
m.enumLookup({
name: "ntc1_sensor_type",
lookup: { none: 0, "NTC-10K": 1, "NTC-12K": 2, "NTC-15K": 3, "NTC-22K": 4, "NTC-33K": 5, "NTC-47K": 6 },
cluster: "namronPrivate04E0",
attribute: "ntcSensorType1",
description: "NTC probe type for sensor #1 (must be set for temperature reporting)",
entityCategory: "config",
}),
m.enumLookup({
name: "ntc2_sensor_type",
lookup: { none: 0, "NTC-10K": 1, "NTC-12K": 2, "NTC-15K": 3, "NTC-22K": 4, "NTC-33K": 5, "NTC-47K": 6 },
cluster: "namronPrivate04E0",
attribute: "ntcSensorType2",
description: "NTC probe type for sensor #2 (must be set for temperature reporting)",
entityCategory: "config",
}),
// Water alarm relay action
m.enumLookup({
name: "water_alarm_relay_action",
lookup: {
no_action: 0,
turn_off_restore: 1,
turn_on_restore: 2,
turn_off_stay: 3,
turn_on_stay: 4,
no_water_turn_off: 5,
no_water_turn_on: 6,
},
cluster: "namronPrivate04E0",
attribute: "waterAlarmRelayAction",
description: "Relay behavior when water sensor detects a leak",
entityCategory: "config",
}),
// NTC operation modes
m.enumLookup({
name: "ntc1_operation_mode",
lookup: { no_action: 0, off_when_hot_on_when_cold: 1, on_when_hot_off_when_cold: 2, off_when_hot_stay: 3, on_when_hot_stay: 4 },
cluster: "namronPrivate04E0",
attribute: "ntc1OperationSelect",
description: "Relay behavior based on NTC1 temperature threshold",
entityCategory: "config",
}),
m.enumLookup({
name: "ntc2_operation_mode",
lookup: { no_action: 0, off_when_hot_on_when_cold: 1, on_when_hot_off_when_cold: 2, off_when_hot_stay: 3, on_when_hot_stay: 4 },
cluster: "namronPrivate04E0",
attribute: "ntc2OperationSelect",
description: "Relay behavior based on NTC2 temperature threshold",
entityCategory: "config",
}),
// Temperature thresholds for relay auto control
m.numeric({
name: "ntc1_relay_auto_temp",
cluster: "namronPrivate04E0",
attribute: "ntc1RelayAutoTemp",
description: "Temperature threshold for NTC1 relay control",
unit: "°C",
valueMin: 0,
valueMax: 100,
valueStep: 0.1,
scale: 10,
precision: 1,
entityCategory: "config",
}),
m.numeric({
name: "ntc2_relay_auto_temp",
cluster: "namronPrivate04E0",
attribute: "ntc2RelayAutoTemp",
description: "Temperature threshold for NTC2 relay control",
unit: "°C",
valueMin: 0,
valueMax: 100,
valueStep: 0.1,
scale: 10,
precision: 1,
entityCategory: "config",
}),
// Override priority
m.enumLookup({
name: "override_option",
lookup: { no_priority: 0, water_alarm_priority: 1, ntc_priority: 2 },
cluster: "namronPrivate04E0",
attribute: "overrideOption",
description: "Priority when both water alarm and temperature conditions trigger",
entityCategory: "config",
}),
// Calibration offsets
m.numeric({
name: "ntc1_calibration",
cluster: "namronPrivate04E0",
attribute: "ntcCalibration1",
description: "Temperature calibration offset for NTC1",
unit: "°C",
valueMin: -10,
valueMax: 10,
entityCategory: "config",
}),
m.numeric({
name: "ntc2_calibration",
cluster: "namronPrivate04E0",
attribute: "ntcCalibration2",
description: "Temperature calibration offset for NTC2",
unit: "°C",
valueMin: -10,
valueMax: 10,
entityCategory: "config",
}),
// Hysteresis
m.numeric({
name: "ntc1_temp_hysteresis",
cluster: "namronPrivate04E0",
attribute: "ntc1TempHysteresis",
description: "Temperature hysteresis for NTC1 relay control",
unit: "°C",
valueMin: -10,
valueMax: 10,
entityCategory: "config",
}),
m.numeric({
name: "ntc2_temp_hysteresis",
cluster: "namronPrivate04E0",
attribute: "ntc2TempHysteresis",
description: "Temperature hysteresis for NTC2 relay control",
unit: "°C",
valueMin: -10,
valueMax: 10,
entityCategory: "config",
}),
// Condition alarms (read-only status)
m.binary({
name: "water_condition_alarm",
cluster: "namronPrivate04E0",
attribute: "waterConditionAlarm",
description: "Water leak alarm active",
valueOn: [true, 1],
valueOff: [false, 0],
access: "STATE",
}),
m.binary({
name: "ntc_condition_alarm",
cluster: "namronPrivate04E0",
attribute: "ntcConditionAlarm",
description: "NTC temperature alarm active",
valueOn: [true, 1],
valueOff: [false, 0],
access: "STATE",
}),
m.binary({
name: "is_execute_condition",
cluster: "namronPrivate04E0",
attribute: "isExecuteCondition",
description: "Relay action triggered by conditions",
valueOn: [true, 1],
valueOff: [false, 0],
access: "STATE",
}),
// Polling (5 min interval)
m.poll({
key: "namron_4512785_poll",
optionKey: "temperature_poll_interval",
option: e
.numeric("temperature_poll_interval", ea.SET)
.withValueMin(-1)
.withDescription("Polling interval for NTC temperature sensors (default: 300s, -1 to disable)"),
defaultIntervalSeconds: 300,
poll: async (device) => {
const ep = device.getEndpoint(1);
if (!ep)
return;
await ep.read("genOnOff", ["onOff"]);
await ep.read("msTemperatureMeasurement", ["measuredValue"]);
await ep.read("namronPrivate04E0", [
"ntc2Temperature",
"waterSensorValue",
"waterConditionAlarm",
"ntcConditionAlarm",
"isExecuteCondition",
]);
},
}),
],
},
];
//# sourceMappingURL=namron.js.map