[ZIGBEE2MQTT] [FIL PILOTE] Support Z2M Zigbee pour thermostat fil pilote prix bas Aliexpre$$

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;
1 « J'aime »

Juste ce produit que je consultair cette semaine pour piloter radiateur électrique

Bah ça aura servi mon temps :slight_smile: Attention: je n’ai pas géré les programmes journaliers, pas assez de temps ni de connaissances en javascript… Si tu t’ennuies

1 « J'aime »

Bonjour dbn je viens de recevoir ce module, peut tu me faire un petit tuto pour utiliser ton script.

Merci à toi

Cordialement paul

Paire le avec z2m et donne moi le nom du produit que tu vois stp

Sinon tu regardes sur le Web le nom du dossier à créer dans le directory zigbee2mqtt je crois external_converters puis tu créé un fichier nommé par exemple test.mjs et tu copie le Javascript donne ci-dessus. Tu redémarre z2m et tu fais le pairing

Voici le numéro 0xa4c138775b7165f6

et la copie d’ecran

Merci

Oui c’est le même donc il faut faire ce que j’ai écrit dans mon dernier message.