Convertisseur externe pour Aqara W100

Bonjour à tous, j’ai mis l’étiquette z2m car cela concerne zigbee2mqtt, si ce n’est pas bon dites le moi :wink:

Voilà, sur la base du travail de @greenspeedracer, GitHub - greenspeedracer/W100-Scripts: Some scripts to get the W100's HVAC and middle line working with Z2M. , j’ai modifié son convertisseur pour que tout ce qu’il a fait soit accessible depuis z2m et donc par ricoché par jeedom. Il a réussi à faire allumer la ligne du milieu du W100 et à le paramétrer les caractéristiques de la partie HVAC depuis son installation, ce qu’il appelle les PMTSD. Chez lui il utilise des scripts dans Home Assistant pour gérer tout ça mais j’ai intégré cette logique dans le convertisseur.

Pour installer ce convertisseur rien de très compliqué, vous avez 2 possibilité:

  • depuis l’interface de z2m: aller dans le menu du haut à Paramètres puis console de développement puis convertisseur externe, saisissez un nom pour votre convertisseur et copier / coller le texte du fichier ci dessous dans la partie code avec de sauvegarder

  • depuis jeedom: téléchargez le fichier ci-dessous, enlevez l’extension .txt et collez le dans html/plugins/z2m/data/external_converters , renommez le si vous le voulez ce n’est pas un problème.

Dans les 2 cas il faut redémarrer z2m pour que le convertisseur soit pris en compte. Et pour que jeedom le prenne en compte il faut enfin faire une synchronisation depuis la page du plugin JeeZigbee (pour les autres je ne sais pas).

Voici le fichier du convertisseur: w100.js.txt (20,6 Ko)

Et voici ce que vous obtiendrez (extrait focus sur les nouveautés):

Salut

Le tag zigbee2mqtt serait peut-être plus pertinent car cela ne concerne pas le plugin jeezigbee mais un outils utilisé par jeezigbee et d’autres plugins.

Antoine

ok, je n’avais pas fait attention à ce tag, je m’y perd un peu dans ceux concernant zigbee :wink:

J’avais fait attention à écrire zigbee2mqtt
Le tag plugin-zigbee2mqtt correspond à ZigbeeLinker, qui utilise lui aussi zigbee2mqtt.

Antoine

merci, ça confirme mon post précédent :wink:

Salut !
Génial ça, un grand merci ! Ca tombe bien je viens d’en commander un en me disant que ça finirait par être développé :slight_smile:
Une chance que ça soit intégré directement dans z2m à terme ? En attendant quand on met z2m à jour, sais-tu s’il faut refaire la manip’ ?
Encore merci !

Attention, je viens de faire quelques tests
Si n active le mode « thermostat » - ligne du milieu, le mode « action » sur les boutons ne marche plus

on peut modifier la temperature de consigne du thermostat mais plus utiliser les double actions ou hold actions des boutons +, - ou bouton central

Norbert

Koenkk vient de me demander de faire une pr, à suivre

Oui c’est le w100 qui fonctionne comme ça. C’est l’un ou l’autre mais pas les 2

2 « J'aime »

Bonjour !
J’ai reçu mon W100 et commencé mes tests. Globalement ça fonctionne nickel, donc merci encore.
Il me reste toutefois encore deux questions :

  • Il est possible de modifier depuis l’interface z2m le paramètre " PMTSD S", ce qui permet de modifier le nombre de traits de la vitesse de ventilation sur l’écran. Comment fait-on pour modifier ce paramètre depuis Jeedom ? Le retour d’état se fait bien mais pas la commande …
  • Même chose pour le symbole soleil/flocon de neige

Merci d’avance !

Ah je n’ai pas essayé depuis jeedom. As tu essayé de publier sur le topic /zigbee2mqtt/le nom de ton w100/set/PMTD_S ?

Bonjour !
Je n’ai pas l’impression que ça fonctionne …

Pour détailler un peu, voici des captures d’écran :

  • Côté Z2M

  • Côté Jeedom

Pour la consigne du thermostat (PMTSD T côté Z2M), on retrouve côté Jeedom à la fois la valeur d’état (commande 8518) et la commande slider pour la contrôler (commande 8519)

Par contre, pour la vitesse de ventillation (PMTSD S côté Z2M), on retrouve côté Jeedom uniquement la commande d’état (8520), et pas de commande action qui correspondrait à la case dans laquelle il y a un 2 côté Z2M.

Y a-t-il un moyen de créer manuellement cette commande action ? Sachant qu’elle fonctionne bien côté Z2M, puisque quand je remplace le 2 par 1 et que je clique sur le bouton bleu, l’affichage change bien sur le W100.

Merci d’avance, et désolé j’ai encore de grosses lacunes en Z2M !

je n’avais pas fait de test depuis jeedom, désolé… En effet il y a un loupé. Je regarde ça et je poste la solution dès que possible

Bonjour ! Ne t’inquiète pas, au moins je me serai senti utile à quelque chose :grin:. Je testerai la modif… Merci encore !

alors j’ai bien trouvé une façon de faire mais cela rajoute 1 bouton par valeur dans jeedom (par exemple pour PMTSD_S tu auras 4 bouton, un pour 0, 1, …) alors je ne sais pas mais ça me plait moyen et je ne trouve pas d’expose qui permette d’associer une liste dans jeedom. La solution sans rien modifier au fichier ni dans jeedom c’est de publier sur un topic de z2m mais je m’étais un peu planté lorsque je te l’avais soumis la première fois, il faut dans le topic: zigbee2mqtt/le nom de ton w100/set et dans la valeur à envoyer {"PMTSD_S": "la valeur que tu veux" . Ou alors créer une commande dans ton équipement w100 de jeezigbee de cette manière (un par PMTSD sauf le T qui est déjà géré en slider):

Si tu veux essayer l’ajout de boutons dans l’interface jeedom et me dire ce que tu en penses voici le code à mettre dans le convertisseur (redémarrer z2m après la mise à jour et faire une synchronisation dans jeezigbee):

const {Zcl} = require("zigbee-herdsman");
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 { logger } = require("zigbee-herdsman-converters/lib/logger");
const lumi = require("zigbee-herdsman-converters/lib/lumi");
const m = require("zigbee-herdsman-converters/lib/modernExtend");

const e = exposes.presets;
const ea = exposes.access;

const {
    lumiAction,
    lumiZigbeeOTA,
    lumiExternalSensor,
} = lumi.modernExtend;

const NS = "zhc:lumi";
const manufacturerCode = lumi.manufacturerCode;

const W100_0844_req = {
    cluster: 'manuSpecificLumi',
    type: ['attributeReport', 'readResponse'],
    convert: async (model, msg, publish, options, meta) => {
        const attr = msg.data[65522];
        if (!attr || !Buffer.isBuffer(attr)) return;

        const endsWith = Buffer.from([0x08, 0x00, 0x08, 0x44]);
        if (attr.slice(-4).equals(endsWith)) {
            meta.logger.info(`PMTSD request detected from device ${meta.device.ieeeAddr}`);
            
            // Retrieve PMTSD values from meta.state
            const pmtsdValues = {
                P: meta.state?.PMTSD_P || '0',
                M: meta.state?.PMTSD_M || '0',
                T: meta.state?.PMTSD_T || '15.0', // Valeur par défaut décimale
                S: meta.state?.PMTSD_S || '0',
                D: meta.state?.PMTSD_D || '0'
            };
            
            // Send PMTSD frame with stored values
            try {
                await PMTSD_to_W100.convertSet(meta.device.getEndpoint(1), 'PMTSD_to_W100', pmtsdValues, meta);
                meta.logger.info(`PMTSD frame sent for ${meta.device.ieeeAddr}`);
            } catch (error) {
                meta.logger.error(`Failed to send PMTSD frame: ${error.message}`);
            }
            
            return { action: 'W100_PMTSD_request' };
        }
    },
};

const PMTSD_to_W100 = {
    key: ['PMTSD_P', 'PMTSD_M', 'PMTSD_T', 'PMTSD_S', 'PMTSD_D', 'PMTSD_to_W100'],
    convertSet: async (entity, key, value, meta) => {
        // Retrieve current PMTSD values from meta.state
        let pmtsd = {
            P: meta.state?.PMTSD_P || '0',
            M: meta.state?.PMTSD_M || '0',
            T: meta.state?.PMTSD_T || '15.0',
            S: meta.state?.PMTSD_S || '0',
            D: meta.state?.PMTSD_D || '0'
        };
        let hasChanged = false;

        // Convertir la valeur en chaîne si elle est numérique
        const stringValue = value.toString();

        logger.debug(`PMTSD_to_W100: Received key=${key}, value=${value}, type=${typeof value}, stringValue=${stringValue}`);

        if (key === 'PMTSD_to_W100') {
            // Internal call: use value as {P, M, T, S, D} object
            pmtsd = { ...pmtsd, ...value };
            hasChanged = true; // Force send for internal calls
        } else {
            // Check if the value has changed
            const previousValue = pmtsd[key.replace('PMTSD_', '')];
            switch (key) {
                case 'PMTSD_P':
                    if (!['0', '1'].includes(stringValue)) {
                        logger.error(`Invalid PMTSD_P value: ${stringValue}, expected '0' or '1'`);
                        throw new Error('PMTSD_P must be "0" (on) or "1" (off)');
                    }
                    pmtsd.P = stringValue;
                    hasChanged = stringValue !== previousValue;
                    break;
                case 'PMTSD_M':
                    if (!['0', '1', '2'].includes(stringValue)) {
                        logger.error(`Invalid PMTSD_M value: ${stringValue}, expected '0', '1', or '2'`);
                        throw new Error('PMTSD_M must be "0" (cooling), "1" (heating), or "2" (auto)');
                    }
                    pmtsd.M = stringValue;
                    hasChanged = stringValue !== previousValue;
                    break;
                case 'PMTSD_T':
                    const numValue = parseFloat(stringValue);
                    if (isNaN(numValue) || numValue < 15.0 || numValue > 30.0 || (numValue * 10) % 1 !== 0) {
                        logger.error(`Invalid PMTSD_T value: ${stringValue}, expected 15.0 to 30.0 with 0.1 step`);
                        throw new Error('PMTSD_T must be a number between 15.0 and 30.0 with one decimal place');
                    }
                    pmtsd.T = numValue.toFixed(1);
                    hasChanged = stringValue !== previousValue;
                    break;
                case 'PMTSD_S':
                    if (!['0', '1', '2', '3'].includes(stringValue)) {
                        logger.error(`Invalid PMTSD_S value: ${stringValue}, expected '0', '1', '2', or '3'`);
                        throw new Error('PMTSD_S must be "0" (auto), "1", "2", or "3"');
                    }
                    pmtsd.S = stringValue;
                    hasChanged = stringValue !== previousValue;
                    break;
                case 'PMTSD_D':
                    if (!['0', '1'].includes(stringValue)) {
                        logger.error(`Invalid PMTSD_D value: ${stringValue}, expected '0' or '1'`);
                        throw new Error('PMTSD_D must be "0" or "1"');
                    }
                    pmtsd.D = stringValue;
                    hasChanged = stringValue !== previousValue;
                    break;
                default:
                    logger.error(`Unrecognized key: ${key}`);
                    throw new Error(`Unrecognized key: ${key}`);
            }
        }

        // Log update
        logger.info(`Processed ${key}, PMTSD: ${JSON.stringify(pmtsd)}, Changed: ${hasChanged}`);

        // Update state, even if no frame is sent
        const stateUpdate = { state: { PMTSD_P: pmtsd.P, PMTSD_M: pmtsd.M, PMTSD_T: pmtsd.T, PMTSD_S: pmtsd.S, PMTSD_D: pmtsd.D } };

        // Check if all PMTSD values are defined
        const { P, M, T, S, D } = pmtsd;
        if (!P || !M || !T || !S || !D) {
            logger.info(`PMTSD frame not sent: missing values (P:${P}, M:${M}, T:${T}, S:${S}, D:${D})`);
            return stateUpdate;
        }

        // Do not send frame if no value changed (except for internal calls)
        if (!hasChanged) {
            logger.info(`PMTSD frame not sent: no value change`);
            return stateUpdate;
        }

        // Utiliser T tel quel avec le point décimal
        const pmtsdStr = `P${P}_M${M}_T${T}_S${S}_D${D}`;
        const pmtsdBytes = Array.from(pmtsdStr).map(c => c.charCodeAt(0));
        const pmtsdLen = pmtsdBytes.length;

        const fixedHeader = [
            0xAA, 0x71, 0x1F, 0x44,
            0x00, 0x00, 0x05, 0x41, 0x1C,
            0x00, 0x00,
            0x54, 0xEF, 0x44, 0x80, 0x71, 0x1A,
            0x08, 0x00, 0x08, 0x44, pmtsdLen,
        ];

        const counter = Math.floor(Math.random() * 256);
        fixedHeader[4] = counter;

        const fullPayload = [...fixedHeader, ...pmtsdBytes];

        const checksum = fullPayload.reduce((sum, b) => sum + b, 0) & 0xFF;
        fullPayload[5] = checksum;

        // Ensure entity is an Endpoint
        const endpoint = entity.getEndpoint ? entity.getEndpoint(1) : entity;
        if (!endpoint || typeof endpoint.write !== 'function') {
            logger.error(`Invalid endpoint for write: ${JSON.stringify(endpoint)}`);
            throw new Error('Endpoint does not support write operation');
        }

        await endpoint.write(
            64704,
            { 65522: { value: Buffer.from(fullPayload), type: 65 } },
            { manufacturerCode: 4447, disableDefaultResponse: true },
        );

        logger.info(`PMTSD frame sent: ${pmtsdStr}`);
        return stateUpdate;
    },
    convertGet: async (entity, key, meta) => {
        // Return persisted value from meta.state
        return { [key]: meta.state?.[key] || (key === 'PMTSD_T' ? '15.0' : '0') };
    },
};

const PMTSD_from_W100 = {
    cluster: 'manuSpecificLumi',
    type: ['attributeReport', 'readResponse'],
    convert: (model, msg, publish, options, meta) => {
        const data = msg.data[65522];
        if (!data || !Buffer.isBuffer(data)) return;

        const endsWith = Buffer.from([0x08, 0x44]);
        const idx = data.indexOf(endsWith);
        if (idx === -1 || idx + 2 >= data.length) return;

        const payloadLen = data[idx + 2];
        const payloadStart = idx + 3;
        const payloadEnd = payloadStart + payloadLen;

        if (payloadEnd > data.length) return;

        const payloadBytes = data.slice(payloadStart, payloadEnd);
        let payloadAscii;
        try {
            payloadAscii = payloadBytes.toString('ascii');
        } catch {
            return;
        }

        // Log initial state for debugging
        meta.logger.info(`Initial meta.state: ${JSON.stringify(meta.state)}`);

        const result = {};
        const stateUpdate = { state: {} };
        const partsForCombined = [];
        const pairs = payloadAscii.split('_');
        pairs.forEach(p => {
            if (p.length >= 2) {
                const key = p[0].toLowerCase(); // Normaliser la clé en minuscules
                const value = p.slice(1);
                let newKey;
                let stateKey;
                let processedValue = value;
                switch (key) {
                    case 'p':
                        newKey = 'PW';
                        stateKey = 'PMTSD_P';
                        break;
                    case 'm':
                        newKey = 'MW';
                        stateKey = 'PMTSD_M';
                        break;
                    case 't':
                        newKey = 'TW';
                        stateKey = 'PMTSD_T';
                        // Valider que TW est un nombre décimal avec un chiffre après la virgule
                        const numValue = parseFloat(value);
                        if (!isNaN(numValue) && numValue >= 15.0 && numValue <= 30.0 && (numValue * 10) % 1 === 0) {
                            processedValue = numValue.toFixed(1);
                        } else {
                            return; // Ignorer les valeurs invalides
                        }
                        break;
                    case 's':
                        newKey = 'SW';
                        stateKey = 'PMTSD_S';
                        break;
                    case 'd':
                        newKey = 'DW';
                        stateKey = 'PMTSD_D';
                        break;
                    default:
                        newKey = key.toUpperCase() + 'W';
                        stateKey = null;
                }
                result[newKey] = value; // Conserver la valeur brute pour PW, MW, TW, etc.
                if (stateKey) {
                    stateUpdate.state[stateKey] = processedValue;
                    result[stateKey] = processedValue; // Publier la valeur traitée
                }
                partsForCombined.push(`${newKey}${value}`);
            }
        });

        // Formater la date et l'heure
        const date = new Date();
        const formattedDate = date.toLocaleString('fr-FR', {
            year: 'numeric',
            month: '2-digit',
            day: '2-digit',
            hour: '2-digit',
            minute: '2-digit',
            second: '2-digit',
            hour12: false
        }).replace(/,/, '').replace(/(\d+)\/(\d+)\/(\d+)/, '$3-$2-$1'); // Format: YYYY-MM-DD HH:mm:ss
        const combinedString = partsForCombined.length
            ? `${formattedDate}_${partsForCombined.join('_')}`
            : `${formattedDate}`;

        // Log updated state for debugging
        meta.logger.info(`PMTSD decoded: ${JSON.stringify(result)} from ${meta.device.ieeeAddr}`);
        meta.logger.info(`Updated meta.state: ${JSON.stringify({ ...meta.state, ...stateUpdate.state })}`);

        return { 
            ...result,
            PMTSD_from_W100_Data: combinedString,
            ...stateUpdate
        };
    },
};

const Thermostat_Mode = {
    key: ['Thermostat_Mode'],
    convertSet: async (entity, key, value, meta) => {
        const deviceMac = meta.device.ieeeAddr.replace(/^0x/, '').toLowerCase();
        const hubMac = '54ef4480711a';
        function cleanMac(mac, expectedLen) {
            const cleaned = mac.replace(/[:\-]/g, '');
            if (cleaned.length !== expectedLen) {
                throw new Error(`MAC address must contain ${expectedLen} hexadecimal digits`);
            }
            return cleaned;
        }

        const dev = Buffer.from(cleanMac(deviceMac, 16), 'hex');
        const hub = Buffer.from(cleanMac(hubMac, 12), 'hex');

        // Ensure entity is an Endpoint
        const endpoint = entity.getEndpoint ? entity.getEndpoint(1) : entity;
        if (!endpoint || typeof endpoint.write !== 'function') {
            logger.error(`Invalid endpoint for write: ${JSON.stringify(endpoint)}`);
            throw new Error('Endpoint does not support write operation');
        }

        let frame;

        if (value === 'ON') {
            const prefix = Buffer.from('aa713244', 'hex');
            const messageAlea = Buffer.from([Math.floor(Math.random() * 256), Math.floor(Math.random() * 256)]);
            const zigbeeHeader = Buffer.from('02412f6891', 'hex');
            const messageId = Buffer.from([Math.floor(Math.random() * 256), Math.floor(Math.random() * 256)]);
            const control = Buffer.from([0x18]);
            const payloadMacs = Buffer.concat([dev, Buffer.from('0000', 'hex'), hub]);
            const payloadTail = Buffer.from('08000844150a0109e7a9bae8b083e58a9f000000000001012a40', 'hex');

            frame = Buffer.concat([prefix, messageAlea, zigbeeHeader, messageId, control, payloadMacs, payloadTail]);

            // Log the frame for debugging
            logger.info(`Thermostat_Mode ON frame: ${frame.toString('hex')}`);
            
            await endpoint.write(
                64704,
                { 65522: { value: frame, type: 0x41 } },
                { manufacturerCode: 4447, disableDefaultResponse: true },
            );
        } else {
            const prefix = Buffer.from([
                0xaa, 0x71, 0x1c, 0x44, 0x69, 0x1c,
                0x04, 0x41, 0x19, 0x68, 0x91
            ]);
            const frameId = Buffer.from([Math.floor(Math.random() * 256)]);
            const seq = Buffer.from([Math.floor(Math.random() * 256)]);
            const control = Buffer.from([0x18]);

            frame = Buffer.concat([prefix, frameId, seq, control, dev]);
            if (frame.length < 34) {
                frame = Buffer.concat([frame, Buffer.alloc(34 - frame.length, 0x00)]);
            }

            await endpoint.write(
                64704,
                { 65522: { value: frame, type: 0x41 } },
                { manufacturerCode: 4447, disableDefaultResponse: true },
            );
        }

        logger.info(`Thermostat_Mode set to ${value}`);
        return {};
    },
};

module.exports = {
    zigbeeModel: ["lumi.sensor_ht.agl001"],
    model: "TH-S04D",
    vendor: "Aqara",
    description: "Climate Sensor W100",
    fromZigbee: [W100_0844_req, PMTSD_from_W100],
    toZigbee: [PMTSD_to_W100, Thermostat_Mode],
    exposes: [
        e.binary('Thermostat_Mode', ea.ALL, 'ON', 'OFF').withDescription('ON: Enables thermostat mode, buttons send encrypted payloads, and the middle line is displayed. OFF: Disables thermostat mode, buttons send actions, and the middle line is hidden.'),
        e.action(['W100_PMTSD_request']).withDescription('PMTSD request sent by the W100 via the 08000844 sequence'),
        e.text('PMTSD_from_W100_Data', ea.STATE).withDescription('Latest PMTSD values sent by the W100 when manually changed, formatted as "YYYY-MM-DD HH:mm:ss_Px_Mx_Tx_Sx_Dx"'),
        e.enum('PMTSD_P', ea.ALL, ['0', '1']).withDescription('Value P: activates or not the operation of the thermostat => 0 = on, 1 = off'),
        e.enum('PMTSD_M', ea.ALL, ['0', '1', '2']).withDescription('Value M: thermostat operating mode => 0 = cooling, 1 = heating, 2 = auto'),
        e.numeric('PMTSD_T', ea.ALL).withValueMin(15.0).withValueMax(30.0).withValueStep(0.1).withUnit('°C').withDescription('Value T: setpoint temperature'),
        e.enum('PMTSD_S', ea.ALL, ['0', '1', '2', '3']).withDescription('Value S: speed => 0 = auto, 1 to 3'),
        e.enum('PMTSD_D', ea.ALL, ['0', '1']).withDescription('Value D: wind mode => 0 or 1'),
    ],
    extend: [
        lumiZigbeeOTA(),
        m.temperature(),
        m.humidity(),
        lumiExternalSensor(),
        m.deviceEndpoints({endpoints: {plus: 1, center: 2, minus: 3}}),
        lumiAction({
            actionLookup: {hold: 0, single: 1, double: 2, release: 255},
            endpointNames: ["plus", "center", "minus"],
        }),
        m.binary({
            name: "Auto_Hide_Middle_Line",
            cluster: "manuSpecificLumi",
            attribute: {ID: 0x0173, type: Zcl.DataType.BOOLEAN},
            valueOn: [true, 0],
            valueOff: [false, 1],
            description: "Applies only when thermostat mode is enabled. True: Hides the middle line after 30 seconds of inactivity. False: Always displays the middle line.",
            access: "ALL",
            entityCategory: "config",
            zigbeeCommandOptions: {manufacturerCode},
            reporting: false,
        }),
        m.numeric({
            name: "high_temperature",
            valueMin: 26,
            valueMax: 60,
            valueStep: 0.5,
            scale: 100,
            unit: "°C",
            cluster: "manuSpecificLumi",
            attribute: {ID: 0x0167, type: Zcl.DataType.INT16},
            description: "High temperature alert",
            zigbeeCommandOptions: {manufacturerCode},
        }),
        m.numeric({
            name: "low_temperature",
            valueMin: -20,
            valueMax: 20,
            valueStep: 0.5,
            scale: 100,
            unit: "°C",
            cluster: "manuSpecificLumi",
            attribute: {ID: 0x0166, type: Zcl.DataType.INT16},
            description: "Low temperature alert",
            zigbeeCommandOptions: {manufacturerCode},
        }),
        m.numeric({
            name: "high_humidity",
            valueMin: 65,
            valueMax: 100,
            valueStep: 1,
            scale: 100,
            unit: "%",
            cluster: "manuSpecificLumi",
            attribute: {ID: 0x016e, type: Zcl.DataType.INT16},
            description: "High humidity alert",
            zigbeeCommandOptions: {manufacturerCode},
        }),
        m.numeric({
            name: "low_humidity",
            valueMin: 0,
            valueMax: 30,
            valueStep: 1,
            scale: 100,
            unit: "%",
            cluster: "manuSpecificLumi",
            attribute: {ID: 0x016d, type: Zcl.DataType.INT16},
            description: "Low humidity alert",
            zigbeeCommandOptions: {manufacturerCode},
        }),
        m.enumLookup({
            name: "sampling",
            lookup: {low: 1, standard: 2, high: 3, custom: 4},
            cluster: "manuSpecificLumi",
            attribute: {ID: 0x0170, type: Zcl.DataType.UINT8},
            description: "Temperature and humidity sampling settings",
            zigbeeCommandOptions: {manufacturerCode},
        }),
        m.numeric({
            name: "period",
            valueMin: 0.5,
            valueMax: 600,
            valueStep: 0.5,
            scale: 1000,
            unit: "sec",
            cluster: "manuSpecificLumi",
            attribute: {ID: 0x0162, type: Zcl.DataType.UINT32},
            description: "Sampling period",
            zigbeeCommandOptions: {manufacturerCode},
        }),
        m.enumLookup({
            name: "temp_report_mode",
            lookup: {no: 0, threshold: 1, period: 2, threshold_period: 3},
            cluster: "manuSpecificLumi",
            attribute: {ID: 0x0165, type: Zcl.DataType.UINT8},
            description: "Temperature reporting mode",
            zigbeeCommandOptions: {manufacturerCode},
        }),
        m.numeric({
            name: "temp_period",
            valueMin: 1,
            valueMax: 10,
            valueStep: 1,
            scale: 1000,
            unit: "sec",
            cluster: "manuSpecificLumi",
            attribute: {ID: 0x0163, type: Zcl.DataType.UINT32},
            description: "Temperature reporting period",
            zigbeeCommandOptions: {manufacturerCode},
        }),
        m.numeric({
            name: "temp_threshold",
            valueMin: 0.2,
            valueMax: 3,
            valueStep: 0.1,
            scale: 100,
            unit: "°C",
            cluster: "manuSpecificLumi",
            attribute: {ID: 0x0164, type: Zcl.DataType.UINT16},
            description: "Temperature reporting threshold",
            zigbeeCommandOptions: {manufacturerCode},
        }),
        m.enumLookup({
            name: "humi_report_mode",
            lookup: {no: 0, threshold: 1, period: 2, threshold_period: 3},
            cluster: "manuSpecificLumi",
            attribute: {ID: 0x016c, type: Zcl.DataType.UINT8},
            description: "Humidity reporting mode",
            zigbeeCommandOptions: {manufacturerCode},
        }),
        m.numeric({
            name: "humi_period",
            valueMin: 1,
            valueMax: 10,
            valueStep: 1,
            scale: 1000,
            unit: "sec",
            cluster: "manuSpecificLumi",
            attribute: {ID: 0x016a, type: Zcl.DataType.UINT32},
            description: "Humidity reporting period",
            zigbeeCommandOptions: {manufacturerCode},
        }),
        m.numeric({
            name: "humi_threshold",
            valueMin: 2,
            valueMax: 10,
            valueStep: 0.5,
            scale: 100,
            unit: "%",
            cluster: "manuSpecificLumi",
            attribute: {ID: 0x016b, type: Zcl.DataType.UINT16},
            description: "Humidity reporting threshold",
            zigbeeCommandOptions: {manufacturerCode},
        }),
        m.identify(),
    ],
};

pour la solution de la création d’un commande toi même par PMTSD il faut modifier une des fonctions du convertisseur sinon jeedom envoie un nombre alors que z2m attend un caractère. Le convertisseur est mis à jour sur le premier message

1 « J'aime »

ça fonctionne parfaitement, merci !

Tu parles de quelle solution ?

Bonjour !
Effectivement je me rends compte que je n’ai pas été clair :grin:.
J’ai installé le fichier modifié de ton premier message et j’ai créé une commande liste côté jeedom. Je ferai une capture d’écran en rentrant ce soir.

1 « J'aime »

Merci d’avance :wink: