Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 0 additions & 73 deletions src/converters/fromZigbee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1974,79 +1974,6 @@ export const power_source: Fz.Converter<"genBasic", undefined, ["attributeReport
// #endregion

// #region Non-generic converters
export const namron_thermostat: Fz.Converter<"hvacThermostat", undefined, ["attributeReport", "readResponse"]> = {
cluster: "hvacThermostat",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result: KeyValueAny = {};
const data = msg.data;
if (data[0x1000] !== undefined) {
// Display brightness
const lookup: KeyValueAny = {0: "low", 1: "mid", 2: "high"};
result.lcd_brightness = lookup[data[0x1000] as number];
}
if (data[0x1001] !== undefined) {
// Button vibration level
const lookup: KeyValueAny = {0: "off", 1: "low", 2: "high"};
result.button_vibration_level = lookup[data[0x1001] as number];
}
if (data[0x1002] !== undefined) {
// Floor sensor type
const lookup: KeyValueAny = {1: "10k", 2: "15k", 3: "50k", 4: "100k", 5: "12k"};
result.floor_sensor_type = lookup[data[0x1002] as number];
}
if (data[0x1003] !== undefined) {
// Sensor
const lookup: KeyValueAny = {0: "air", 1: "floor", 2: "both"};
result.sensor = lookup[data[0x1003] as number];
}
if (data[0x1004] !== undefined) {
// PowerUpStatus
const lookup: KeyValueAny = {0: "default", 1: "last_status"};
result.powerup_status = lookup[data[0x1004] as number];
}
if (data[0x1005] !== undefined) {
// FloorSensorCalibration
result.floor_sensor_calibration = precisionRound(data[0x1005] as number, 2) / 10;
}
if (data[0x1006] !== undefined) {
// DryTime
result.dry_time = data[0x1006];
}
if (data[0x1007] !== undefined) {
// ModeAfterDry
const lookup: KeyValueAny = {0: "off", 1: "manual", 2: "auto", 3: "away"};
result.mode_after_dry = lookup[data[0x1007] as number];
}
if (data[0x1008] !== undefined) {
// TemperatureDisplay
const lookup: KeyValueAny = {0: "room", 1: "floor"};
result.temperature_display = lookup[data[0x1008] as number];
}
if (data[0x1009] !== undefined) {
// WindowOpenCheck
result.window_open_check = (data[0x1009] as number) / 2;
}
if (data[0x100a] !== undefined) {
// Hysterersis
result.hysterersis = precisionRound(data[0x100a] as number, 2) / 10;
}
if (data[0x100b] !== undefined) {
// DisplayAutoOffEnable
result.display_auto_off_enabled = data[0x100b] ? "enabled" : "disabled";
}
if (data[0x2001] !== undefined) {
// AlarmAirTempOverValue
result.alarm_airtemp_overvalue = data[0x2001];
}
if (data[0x2002] !== undefined) {
// Away Mode Set
result.away_mode = data[0x2002] ? "ON" : "OFF";
}

return result;
},
};
export const namron_hvac_user_interface: Fz.Converter<"hvacUserInterfaceCfg", undefined, ["attributeReport", "readResponse"]> = {
cluster: "hvacUserInterfaceCfg",
type: ["attributeReport", "readResponse"],
Expand Down
124 changes: 0 additions & 124 deletions src/converters/toZigbee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {determineEndpoint} from "../lib/utils";

const NS = "zhc:tz";
const manufacturerOptions = {
sunricher: {manufacturerCode: Zcl.ManufacturerCode.SHENZHEN_SUNRICHER_TECHNOLOGY_LTD},
lumi: {manufacturerCode: Zcl.ManufacturerCode.LUMI_UNITED_TECHOLOGY_LTD_SHENZHEN, disableDefaultResponse: true},
eurotronic: {manufacturerCode: Zcl.ManufacturerCode.NXP_SEMICONDUCTORS},
hue: {manufacturerCode: Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V},
Expand Down Expand Up @@ -2636,129 +2635,6 @@ export const ZMCSW032D_cover_position: Tz.Converter = {
await entity.read("closuresWindowCovering", [isPosition ? "currentPositionLiftPercentage" : "currentPositionTiltPercentage"]);
},
};
export const namron_thermostat: Tz.Converter = {
key: [
"lcd_brightness",
"button_vibration_level",
"floor_sensor_type",
"sensor",
"powerup_status",
"floor_sensor_calibration",
"dry_time",
"mode_after_dry",
"temperature_display",
"window_open_check",
"hysterersis",
"display_auto_off_enabled",
"alarm_airtemp_overvalue",
"away_mode",
],
convertSet: async (entity, key, value, meta) => {
if (key === "lcd_brightness") {
const lookup = {low: 0, mid: 1, high: 2};
const payload = {4096: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.ENUM8}};
await entity.write("hvacThermostat", payload, manufacturerOptions.sunricher);
} else if (key === "button_vibration_level") {
const lookup = {off: 0, low: 1, high: 2};
const payload = {4097: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.ENUM8}};
await entity.write("hvacThermostat", payload, manufacturerOptions.sunricher);
} else if (key === "floor_sensor_type") {
const lookup = {"10k": 1, "15k": 2, "50k": 3, "100k": 4, "12k": 5};
const payload = {4098: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.ENUM8}};
await entity.write("hvacThermostat", payload, manufacturerOptions.sunricher);
} else if (key === "sensor") {
const lookup = {air: 0, floor: 1, both: 2};
const payload = {4099: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.ENUM8}};
await entity.write("hvacThermostat", payload, manufacturerOptions.sunricher);
} else if (key === "powerup_status") {
const lookup = {default: 0, last_status: 1};
const payload = {4100: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.ENUM8}};
await entity.write("hvacThermostat", payload, manufacturerOptions.sunricher);
} else if (key === "floor_sensor_calibration") {
utils.assertNumber(value);
const payload = {4101: {value: Math.round(value * 10), type: 0x28}}; // INT8S
await entity.write("hvacThermostat", payload, manufacturerOptions.sunricher);
} else if (key === "dry_time") {
const payload = {4102: {value: value, type: 0x20}}; // INT8U
await entity.write("hvacThermostat", payload, manufacturerOptions.sunricher);
} else if (key === "mode_after_dry") {
const lookup = {off: 0, manual: 1, auto: 2, away: 3};
const payload = {4103: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.ENUM8}};
await entity.write("hvacThermostat", payload, manufacturerOptions.sunricher);
} else if (key === "temperature_display") {
const lookup = {room: 0, floor: 1};
const payload = {4104: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.ENUM8}};
await entity.write("hvacThermostat", payload, manufacturerOptions.sunricher);
} else if (key === "window_open_check") {
utils.assertNumber(value);
const payload = {4105: {value: value * 2, type: 0x20}};
await entity.write("hvacThermostat", payload, manufacturerOptions.sunricher);
} else if (key === "hysterersis") {
utils.assertNumber(value);
const payload = {4106: {value: value * 10, type: 0x20}};
await entity.write("hvacThermostat", payload, manufacturerOptions.sunricher);
} else if (key === "display_auto_off_enabled") {
const lookup = {disabled: 0, enabled: 1};
const payload = {4107: {value: utils.getFromLookup(value, lookup), type: Zcl.DataType.ENUM8}};
await entity.write("hvacThermostat", payload, manufacturerOptions.sunricher);
} else if (key === "alarm_airtemp_overvalue") {
const payload = {8193: {value: value, type: 0x20}};
await entity.write("hvacThermostat", payload, manufacturerOptions.sunricher);
} else if (key === "away_mode") {
const payload = {8194: {value: Number(value === "ON"), type: 0x30}};
await entity.write("hvacThermostat", payload, manufacturerOptions.sunricher);
}
},
convertGet: async (entity, key, meta) => {
switch (key) {
case "lcd_brightness":
await entity.read("hvacThermostat", [0x1000], manufacturerOptions.sunricher);
break;
case "button_vibration_level":
await entity.read("hvacThermostat", [0x1001], manufacturerOptions.sunricher);
break;
case "floor_sensor_type":
await entity.read("hvacThermostat", [0x1002], manufacturerOptions.sunricher);
break;
case "sensor":
await entity.read("hvacThermostat", [0x1003], manufacturerOptions.sunricher);
break;
case "powerup_status":
await entity.read("hvacThermostat", [0x1004], manufacturerOptions.sunricher);
break;
case "floor_sensor_calibration":
await entity.read("hvacThermostat", [0x1005], manufacturerOptions.sunricher);
break;
case "dry_time":
await entity.read("hvacThermostat", [0x1006], manufacturerOptions.sunricher);
break;
case "mode_after_dry":
await entity.read("hvacThermostat", [0x1007], manufacturerOptions.sunricher);
break;
case "temperature_display":
await entity.read("hvacThermostat", [0x1008], manufacturerOptions.sunricher);
break;
case "window_open_check":
await entity.read("hvacThermostat", [0x1009], manufacturerOptions.sunricher);
break;
case "hysterersis":
await entity.read("hvacThermostat", [0x100a], manufacturerOptions.sunricher);
break;
case "display_auto_off_enabled":
await entity.read("hvacThermostat", [0x100b], manufacturerOptions.sunricher);
break;
case "alarm_airtemp_overvalue":
await entity.read("hvacThermostat", [0x2001], manufacturerOptions.sunricher);
break;
case "away_mode":
await entity.read("hvacThermostat", [0x2002], manufacturerOptions.sunricher);
break;

default: // Unknown key
throw new Error(`Unhandled key toZigbee.namron_thermostat.convertGet ${key}`);
}
},
};
export const namron_thermostat_child_lock: Tz.Converter = {
key: ["child_lock"],
convertSet: async (entity, key, value, meta) => {
Expand Down
88 changes: 43 additions & 45 deletions src/devices/namron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,42 @@ const fzLocal = {
return fz.thermostat.convert(model, msg, publish, options, meta); // as KeyValue;
},
} satisfies Fz.Converter<"hvacThermostat", undefined, ["attributeReport", "readResponse"]>,
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 as 0x00 | 0x01 | 0x02];
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) as string | undefined;
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`});
},
} satisfies Fz.Converter<"namronPrivateE004", NamronPrivateE004, ["raw"]>,
};
// Namron Simplify 3-button remote (4512793 / 4512794)
// -----------------------------------------------------------
Expand Down Expand Up @@ -180,47 +216,6 @@ function parseNamronBytes(msg: Fz.Message<"namronPrivateE004", NamronPrivateE004

return [];
}

const fzNamronSimplifyRemote: Fz.Converter<"namronPrivateE004", NamronPrivateE004, ["raw"]> = {
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 as 0x00 | 0x01 | 0x02];
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) as string | undefined;
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`});
},
};

// END SimplifyBryter
const tzLocal = {
namron_panelheater: {
Expand Down Expand Up @@ -883,7 +878,7 @@ export const definitions: DefinitionWithExtend[] = [
model: "4512737/4512738",
vendor: "Namron",
description: "Touch thermostat",
fromZigbee: [fz.thermostat, fz.namron_thermostat, fz.metering, fz.electrical_measurement, fz.namron_hvac_user_interface],
fromZigbee: [fz.thermostat, namron.fromZigbee.namron_thermostat, fz.metering, fz.electrical_measurement, fz.namron_hvac_user_interface],
toZigbee: [
tz.thermostat_occupied_heating_setpoint,
tz.thermostat_unoccupied_heating_setpoint,
Expand All @@ -895,7 +890,7 @@ export const definitions: DefinitionWithExtend[] = [
tz.thermostat_control_sequence_of_operation,
tz.thermostat_running_state,
tz.namron_thermostat_child_lock,
tz.namron_thermostat,
namron.toZigbee.namron_thermostat,
],
exposes: [
e.local_temperature(),
Expand Down Expand Up @@ -961,7 +956,7 @@ export const definitions: DefinitionWithExtend[] = [
),
],
// Device does not asks for the time with binding, therefore we write the time every 24 hours
extend: [m.writeTimeDaily({endpointId: 1})],
extend: [m.writeTimeDaily({endpointId: 1}), namron.namronExtend.addNamronHvacThermostatCluster()],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
const binds = [
Expand Down Expand Up @@ -1218,6 +1213,7 @@ export const definitions: DefinitionWithExtend[] = [
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, fz.namron_hvac_user_interface],
toZigbee: [
Expand Down Expand Up @@ -1388,6 +1384,7 @@ export const definitions: DefinitionWithExtend[] = [
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, fz.namron_hvac_user_interface, fz.electrical_measurement],
Expand Down Expand Up @@ -2001,6 +1998,7 @@ export const definitions: DefinitionWithExtend[] = [
device.save();
},
extend: [
namron.namronExtend.addNamronHvacThermostatCluster(),
m.poll({
key: "time",
defaultIntervalSeconds: 60 * 60 * 24,
Expand Down Expand Up @@ -2106,7 +2104,7 @@ export const definitions: DefinitionWithExtend[] = [
vendor: "Namron",
description: "Simplify 6-button remote with battery",
extend: [m.battery(), namron.namronExtend.addCustomClusterNamronPrivateE004()],
fromZigbee: [fzNamronSimplifyRemote],
fromZigbee: [fzLocal.namronSimplifyRemote],
toZigbee: [],
exposes: [
e.action([
Expand Down
Loading