Code : Tout sélectionner
// 13/09 22_30 version simplifiée: 6 modes ; current temperature; etalonnage temperature ; child_lock
// chrisgen inspiré par Zelox69 T (https://github.com/Koenkk/zigbee2mqtt/discussions/25687)
//et aldoushx (https://github.com/Koenkk/zigbee2mqtt/discussions/26256)
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 e = exposes.presets;
const ea = exposes.access;
const tuya = require('zigbee-herdsman-converters/lib/tuya');
const CHILD_LOCK_DP = 39; // child_lock DP confirmé
const HUMIDITY_DP = 8; // // ne fonctionne pas idem pour 9 10,11.. semble fonctionner pour 8
const definition = {
fingerprint: [{ modelID: 'TS0601', manufacturerName: '_TZE204_3q3maeoo' }],
model: 'TS0601_3q3maeoo',
vendor: 'Tuya',
description: 'Gestion de chauffage par fil pilote (version simplifiée)',
fromZigbee: [
tuya.fz.datapoints,
{
cluster: 'manuSpecificTuya',
type: ['attributeReport', 'readResponse'],
convert: (model, msg, publish, options, meta) => {
const dp = msg.data.dp;
const value = msg.data.value;
switch (dp) {
case HUMIDITY_DP: // humidity
return { humidity: value };
case 127: // Mode
const modes = {
2: 'comfort_minus_2',
1: 'comfort_minus_1',
0: 'comfort',
3: 'eco',
4: 'anti_frost',
5: 'off',
6: 'program',
};
return { mode: modes[value] || `unknown_${value}` };
case 16: // Température actuelle
return { current_temperature: value / 10 };
case 101: // Etalonnage température
{
const signed = value > 32767 ? value - 65536 : value;
return { etalonnage_temp: signed / 10 };
}
case CHILD_LOCK_DP: // Child lock
return { child_lock: (value === 1 || value === true) ? 'LOCK' : 'UNLOCK' };
default:
meta.logger.warn(`Datapoint non géré : ${dp}, valeur : ${value}`);
return null;
}
},
},
],
toZigbee: [
{
key: ['mode'],
convertSet: async (entity, key, value, meta) => {
try {
const modes = {
'comfort_minus_2': 2,
'comfort_minus_1': 1,
'comfort': 0,
'eco': 3,
'anti_frost': 4,
'off': 5,
'program': 6,
};
const dpValue = modes[value];
if (dpValue === undefined) throw new Error(`Mode invalide : ${value}`);
await tuya.sendDataPointEnum(entity, 127, dpValue);
return { mode: value };
} catch (error) {
meta.logger.error(`Erreur en envoyant mode: ${error?.message || error}`);
}
},
},
{
key: ['etalonnage_temp'],
convertSet: async (entity, key, value, meta) => {
try {
const raw = Math.round(value * 10);
const tempValue = raw < 0 ? 65536 + raw : raw; // signed 16 bits
await tuya.sendDataPointValue(entity, 101, tempValue);
} catch (error) {
meta.logger.error(`Erreur etalonnage_temp: ${error?.message || error}`);
}
},
},
// child_lock DP39 (LOCK/UNLOCK)
{
key: ['child_lock'],
convertSet: async (entity, key, value, meta) => {
try {
const v = (typeof value === 'string') ? value.toUpperCase() : value;
const mapping = {
'LOCK': 1, 'UNLOCK': 0,
'ON': 1, 'OFF': 0,
true: 1, false: 0,
1: 1, 0: 0
};
const dpVal = mapping[v];
if (dpVal === undefined) throw new Error(`Valeur child_lock invalide : ${value}`);
await tuya.sendDataPointEnum(entity, CHILD_LOCK_DP, dpVal);
return { child_lock: dpVal === 1 ? 'LOCK' : 'UNLOCK' };
} catch (error) {
meta.logger.error(`Erreur child_lock convertSet: ${error?.message || error}`);
}
},
},
],
exposes: [
// e.switch().withDescription('Contrôle ON/OFF de l\'appareil'),
e.enum('mode', ea.SET, [
'comfort_minus_2', 'comfort_minus_1', 'comfort', 'eco', 'anti_frost', 'off', 'program'
]).withDescription('Mode de fonctionnement'),
e.numeric('current_temperature', ea.STATE).withUnit('C').withDescription('Temperature actuelle mesurée'),
e.numeric('humidity', ea.STATE).withUnit('%').withDescription('humidite mesuree'),
e.numeric('etalonnage_temp', ea.SET).withUnit('C').withDescription('Réglage température courante').withValueMin(-6).withValueMax(6).withValueStep(0.1),
e.enum('child_lock', ea.SET, ['LOCK','UNLOCK']).withDescription('Verrouillage enfant (LOCK/UNLOCK)'),
],
meta: {
tuyaDatapoints: [
[127, 'mode', {
from: (value) => {
const modes = { 2:'comfort_minus_2',1:'comfort_minus_1',0:'comfort',3:'eco',4:'anti_frost',5:'off',6:'program' };
return modes[value] || `unknown_${value}`;
},
to: (value) => {
const modes = { 'comfort_minus_2':2,'comfort_minus_1':1,'comfort':0,'eco':3,'anti_frost':4,'off':5,'program':6 };
return modes[value];
}
}],
[16, 'current_temperature', { from: (value) => value / 10 }],
[HUMIDITY_DP, 'humidity', { from: (value) => value }],
[101, 'etalonnage_temp', {
from: (value) => {
const signed = value > 32767 ? value - 65536 : value;
return signed / 10;
},
to: (value) => {
const raw = Math.round(value * 10);
return raw < 0 ? 65536 + raw : raw;
},
}],
[CHILD_LOCK_DP, 'child_lock', {
from: (v) => (v === 1 || v === true) ? 'LOCK' : 'UNLOCK',
to: (val) => {
const vv = (typeof val === 'string') ? val.toUpperCase() : val;
return (vv === 'LOCK' || vv === 'ON' || vv === true || vv === 1) ? 1 : 0;
}
}],
],
},
};
module.exports = definition;
il est fonctionnel. J' utilise MqttMapper (pour le relier à Domoticz), le bien nommé et fort apprécié programme de Flying Domotic mais j' aimerais encore l' améliorer si possible .. Le boitier fonctionne bien (pour les modes , la température , child_lock) et me remplacera avantageusement un vieux qbino zwave qui devient introuvable . Si vous avez quelques idées