Hello. J’ai acheté et modifié / finalisé un convertisseur pour un thermostat ou commande fil pilote pour ceci sur Ali:
Il me manque encore les programmes pour le mode adéquat. Pas grave pour moi, si quelqu’un aime le javascript je lui explique le souci. Sinon ça peut aider comme alternative à un Nodon avec le thermostat et sonde intégrés.
Le convertisseur:
const legacy = require('zigbee-herdsman-converters/lib/legacy');
const fz = require('zigbee-herdsman-converters/converters/fromZigbee');
const tz = require('zigbee-herdsman-converters/converters/toZigbee');
const exposes = require('zigbee-herdsman-converters/lib/exposes');
const reporting = require('zigbee-herdsman-converters/lib/reporting');
const modernExtend = require('zigbee-herdsman-converters/lib/modernExtend');
const e = exposes.presets;
const ea = exposes.access;
const tuya = require('zigbee-herdsman-converters/lib/tuya');
const SLOT_MINUTES = 30;
const SLOTS_PER_DAY = 48;
const DAYS = [1, 2, 3, 4, 5, 6, 7]; // Zigbee day numbering: 1=Mon ... 7=Sun
function slotsToTransitions(slots) {
const transitions = [];
let lastTemp = null;
for (let slot = 0; slot < SLOTS_PER_DAY; slot++) {
const temp = slots[String(slot)];
if (temp !== lastTemp) {
const minutes = slot * SLOT_MINUTES;
const hh = String(Math.floor(minutes / 60)).padStart(2, '0');
const mm = String(minutes % 60).padStart(2, '0');
transitions.push({ time: `${hh}:${mm}`, heat_setpoint: temp });
lastTemp = temp;
}
}
return transitions;
}
function timeToSlot(timeStr) {
const [h, m] = timeStr.split(':').map(Number);
return (h * 60 + m) / SLOT_MINUTES;
}
function transitionsToSlots(transitions) {
const slots = {};
let currentTemp = transitions.length ? transitions[0].heat_setpoint : 0;
let idx = 1;
let nextSlot = idx < transitions.length ? timeToSlot(transitions[idx].time) : SLOTS_PER_DAY;
for (let slot = 0; slot < SLOTS_PER_DAY; slot++) {
if (slot === nextSlot && idx < transitions.length) {
currentTemp = transitions[idx].heat_setpoint;
idx++;
nextSlot = idx < transitions.length ? timeToSlot(transitions[idx].time) : SLOTS_PER_DAY;
}
slots[slot] = currentTemp;
}
return slots;
}
function tuyaWeekToZ2MQTT(tuyaData) {
return DAYS.map((dayNum, i) => ({
dayofweek: dayNum,
transitions: slotsToTransitions(tuyaData[`week_program_${i + 1}`] || {})
}));
}
function z2MQTTToTuya(weeklySchedule) {
const weekPrograms = {};
weeklySchedule.forEach((dayObj, i) => {
weekPrograms[`week_program_${i + 1}`] = transitionsToSlots(dayObj.transitions || []);
});
return weekPrograms;
}
// store fetched days until all 7 are ready
const pendingWeekData = {};
const definition = {
// Since a lot of Tuya devices use the same modelID, but use different datapoints
// it's necessary to provide a fingerprint instead of a zigbeeModel
fingerprint: [
{
// The model ID from: Device with modelID 'TS0601' is not supported
// You may need to add \u0000 at the end of the name in some cases
modelID: 'TS0601',
// The manufacturer name from: Device with modelID 'TS0601' is not supported.
manufacturerName: '_TZE204_3regm3h6',
},
],
model: 'TS0601_TZE204_3regm3h6', // Update this with the real model of the device (written on the device itself or product page)
vendor: 'Moes', // Update this with the real vendor of the device (written on the device itself or product page)
description: 'Smart Thermostat For Pilot Wire Heating Radiator', // Description of the device, copy from vendor site. (only used for documentation and startup logging)
fromZigbee: [
tuya.fz.datapoints,
//legacy.fz.tuya_thermostat_weekly_schedule_2,
],
toZigbee: [
tuya.tz.datapoints,
//legacy.tz.tuya_thermostat_weekly_schedule,
],
onEvent: tuya.onEventSetLocalTime,
configure: tuya.configureMagicPacket,
exposes: [
e
.climate()
.withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET)
.withLocalTemperature(ea.STATE)
.withPreset(['comfort', 'eco', 'antifrost', 'standby', 'comfort_minus_1', 'comfort_minus_2', 'program', 'manual'])
.withSystemMode(['off', 'heat', 'auto'], ea.STATE)
.withLocalTemperatureCalibration(-9, 9, 1, ea.STATE_SET)
.withWeeklySchedule(["heat"], ea.ALL),
//...tuya.exposes.scheduleAllDays(ea.STATE_SET, "HH:MM/C HH:MM/C HH:MM/C HH:MM/C"),
//e.text("schedule_monday", ea.STATE).withDescription("Schedule for the working week"),
// e.text("week_program_4", ea.STATE_SET).withDescription("Schedule for the working week"),
e
.window_detection(),
e
.child_lock(),
e
.enum('mode', ea.STATE, ['comfort', 'eco', 'antifrost', 'standby', 'comfort_minus_1', 'comfort_minus_2', 'program', 'manual'])
.withDescription('Current running mode'),
e
.binary("window_check", ea.STATE_SET, "ON", "OFF")
.withDescription('Enable window open detection')
.withLabel("Window check enable")
.withCategory("config"),
e
.binary("set_temp_btn", ea.STATE_SET, "ON", "OFF")
.withDescription('Temperature button enable')
.withLabel('Temperature button enable')
.withCategory('config'),
e
.binary("switch", ea.STATE_SET, "ON", "OFF")
.withDescription('Main switch state')
.withLabel('Global ON/OFF'),
e
.binary("antifrost_btn", ea.STATE_SET, "ON", "OFF")
.withDescription('Antifrost button enable')
.withLabel('Antifrost button enable')
.withCategory('config'),
e
.numeric('comfort_temperature', ea.STATE_SET)
.withUnit('°C')
.withValueMin(5)
.withValueMax(40)
.withValueStep(0.5)
.withDescription('Set comfort temperature'),
e
.numeric('eco_temperature', ea.STATE_SET)
.withUnit('°C')
.withValueMin(5)
.withValueMax(35)
.withValueStep(0.5)
.withDescription('Set ECO temperature'),
e
.numeric('antifrost_temperature', ea.STATE_SET)
.withUnit('°C')
.withValueMin(5)
.withValueMax(35)
.withValueStep(0.5)
.withDescription('Set antifrost temperature'),
e
.numeric('temperature_hysteresis', ea.STATE_SET)
.withUnit('°C')
.withValueMin(5)
.withValueMax(50)
.withValueStep(5)
.withDescription('Set thermostat hysteresis'),
e
.numeric('local_temperature_calibration', ea.STATE_SET)
.withUnit('°C')
.withValueMin(-9)
.withValueMax(9)
.withValueStep(1)
.withDescription('Set temperature calibration'),
e
.numeric('voltage', ea.STATE)
.withUnit('V')
.withDescription('Mains input voltage'),
e
.numeric('current', ea.STATE)
.withUnit('mA')
.withDescription('Heater current'),
e
.numeric('power', ea.STATE)
.withUnit('W')
.withDescription('Current power'),
e
.binary('fault', ea.STATE, 'DETECTED', 'NOT_DETECTED')
.withDescription('Fault status'),
e
.numeric('elec_statistics_day', ea.STATE)
.withUnit('kWh')
.withDescription('Total electricity usage statistics'),
e
.numeric('elec_statistics_yesterday', ea.STATE)
.withUnit('kWh')
.withDescription('Total electricity usage statistics'),
e
.numeric('elec_statistics_all', ea.STATE)
.withUnit('kWh')
.withDescription('Total electricity usage statistics'),
e.numeric('night_led_config', ea.STATE_SET).withDescription('Night LED configuration'),
],
meta: {
thermostat: {
weeklyScheduleMaxTransitions: 4,
weeklyScheduleSupportedModes: [1], // bits: 0-heat present, 1-cool present (dec: 1-heat,2-cool,3-heat+cool)
weeklyScheduleFirstDayDpId: 108,
},
tuyaDatapoints: [
[1, 'switch', tuya.valueConverter.onOff],
[
2,
'mode',
tuya.valueConverterBasic.lookup({
comfort: tuya.enum(0),
eco: tuya.enum(1),
antifrost: tuya.enum(2),
standby: tuya.enum(3),
comfort_minus_1: tuya.enum(4),
comfort_minus_2: tuya.enum(5),
program: tuya.enum(6),
manual: tuya.enum(7),
}),
],
[
2,
'preset',
tuya.valueConverterBasic.lookup({
comfort: tuya.enum(0),
eco: tuya.enum(1),
antifrost: tuya.enum(2),
standby: tuya.enum(3),
comfort_minus_1: tuya.enum(4),
comfort_minus_2: tuya.enum(5),
program: tuya.enum(6),
manual: tuya.enum(7),
}),
],
[11, 'power', tuya.valueConverter.divideBy10],
[16, 'local_temperature', tuya.valueConverter.divideBy10],
[17, 'window_detection', tuya.valueConverter.onOff],
[19, 'local_temperature_calibration', tuya.valueConverter.raw],
[20, 'fault', tuya.valueConverter.raw], // e1: Probe abnormal e2: Power statistics chip communication abnormality
[29, 'window_check', tuya.valueConverter.onOff],
[39, 'child_lock', tuya.valueConverter.lockUnlock],
[50, 'current_heating_setpoint', tuya.valueConverter.divideBy10],
[101, 'voltage', tuya.valueConverter.divideBy10], // *0.1V
[102, 'current', tuya.valueConverter.raw], // *1mA
[103, 'temperature_hysteresis', tuya.valueConverter.divideBy10],
[104, 'elec_statistics_day', tuya.valueConverter.raw],
[105, 'elec_statistics_yesterday', tuya.valueConverter.raw],
[
106,
'device_mode_type',
tuya.valueConverterBasic.lookup({
four: tuya.enum(0),
six: tuya.enum(1),
switch : tuya.enum(2),
}),
],
[107, 'electricity_statistics_all', tuya.valueConverter.raw],
[108, "week_program_1", tuya.valueConverter.thermostatScheduleDayMultiDPWithDayNumber(1,36)],
[109, "week_program_2", tuya.valueConverter.raw],
[110, "week_program_3", tuya.valueConverter.raw],
[111, 'week_program_4', tuya.valueConverter.raw],
[112, 'week_program_5', tuya.valueConverter.raw],
[113, 'week_program_6', tuya.valueConverter.raw],
[114, 'week_program_7', tuya.valueConverter.raw],
[115, 'set_temp_btn', tuya.valueConverter.onOff],
[116, 'antifrost_btn', tuya.valueConverter.onOff],
[117, 'eco_temperature', { from: (value) => value / 10, to: (value) => Math.round(value * 10) }],
[118, 'comfort_temperature', { from: (value) => value / 10, to: (value) => Math.round(value * 10) }],
[119, 'antifrost_temperature', { from: (value) => value / 10, to: (value) => Math.round(value * 10) }],
[120, 'night_led_config', tuya.valueConverter.raw], // 5byte\nByte0-Byte1: Start time, the number of minutes away from 00:00, the big end indicates \nByte2-Byte3: End time, the number of minutes away from 00:00, the big end indicates \nByte4: Enable switch
],
},
extend: [
// A preferred new way of extending functionality.
],
};
module.exports = definition;