[Matter] Ajouter la consommation électrique d'un appareil

Suite à la demande de @jmarandet pour gérer ses Shelly ( Aide pour connexion Shelly EM avec Gladys en MQTT - #8 par jmarandet ), ajouter la gestion de la consommation électrique et des clusters Matter suivant :

"id": "156",
"name": "PowerTopology",
"id": "144",
"name": "ElectricalPowerMeasurement",
"id": "145",
"name": "ElectricalEnergyMeasurement",

Hello à tous :slight_smile:

J’ai développé une PR sur ce sujet, entièrement à l’aveugle, en me basant sur ce que j’ai trouvé dans Matter.js.

@Nagromdark Est-ce que tu peux regarder la PR et me dire ce que tu en penses ? Si jamais tu as du matériel pour tester, je suis preneur aussi :smiley:

@jmarandet J’aurais besoin de ton aide. Dans Matterbridge, pour les Shelly, tu vois quoi comme « supported_feature » ?

Normalement, c’est une information que tu peux trouver dans chaque device.

Je veux bien une capture d’écran d’un de tes Shelly avec toutes les valeurs exposées !

Autre question, les valeurs en kWh qui sont affichées, c’est bien un index ? Dans le sens, c’est bien une consommation « cumulée », qui ne fait que grossir ?

1 « J'aime »

Salut je n’ai pas encore regardé, j’ai un peu moins de temps en ce moment. Je n’ai pas d’appareil pour tester ça en tout cas dsl

1 « J'aime »

Je me suis fais un petit environnement de test avec ma prise Eve Energy qui remonte la consommation, mais je deviens fou j’ai réussi à faire le voltage, intensité, et puissance, mais impossible d’avoir l’index :

Comme l’index est remonté toutes les 10 minutes uniquement, c’est très chronophage de tester, ça fait 1h30 que je suis dessus :joy:

Ok, j’ai pas mal discuté avec le développeur de Matter.js, et c’est sûrement lié à ma prise Eve Energy qui ne doit pas remonter la valeur de consommation cumulée.

J’ai testé avec Matterbridge et matterbridge-example-dynamic-platform mais ça ne fonctionne pas non plus, la valeur n’est pas envoyée…

Je sèche un peu niveau matériel de test :sweat_smile:

Salut à tous,

Je n’ai malheureusement pas pu tester, mais pour avancer j’ai déployé ce développement dans Gladys Assistant 4.65, ça sera plus simple pour avoir des retours.

Je suis preneur de retours pour me dire si ça marche ou pas :slight_smile:

cc @jmarandet

Salut @pierre-gilles,

Chez moi cela ne fonctionne pas. J’ai une prise Nous A8M https://www.domadoo.fr/fr/devices-matter/7147-nous-prise-connectee-wifi-matter-16a-mesure-consommation-a8m.html avec mesure de consommation mais je n’ai pas de remonté dans Gladys.

Voici une capture de l’app de la prise ou j’ai ici une conso de 25w. Donc la mesure est OK

Côté Gladys j’ai que la fonctionnalité Commutateur

Toutes les fonctionnalitées disponibles sont dans le json ?

Si j’essai de comprendre pourquoi je doit pouvoir retrouver une des fonctionnalités que tu as ajouté?

image

Ci dessous le json

{
    "node_id": "15556639130473728473",
    "is_connected": true,
    "devices": [
      {
        "name": "MA-onoffpluginunit",
        "number": "1",
        "cluster_clients": [
          {
            "id": "3",
            "name": "Identify",
            "attributes": [
              "0",
              "1",
              "65528",
              "65529",
              "65531",
              "65532",
              "65533",
              "identifyTime",
              "identifyType",
              "clusterRevision",
              "featureMap",
              "attributeList",
              "acceptedCommandList",
              "generatedCommandList"
            ],
            "commands": [
              "0",
              "64",
              "identify",
              "triggerEffect"
            ],
            "all_keys": [
              "id",
              "name",
              "revision",
              "_type",
              "supportedFeatures",
              "isUnknown",
              "endpointId",
              "attributes",
              "events",
              "commands",
              "subscribeAllAttributes",
              "_triggerAttributeUpdate",
              "_triggerEventUpdate",
              "isAttributeSupported",
              "isAttributeSupportedByName",
              "isCommandSupported",
              "isCommandSupportedByName",
              "getIdentifyTimeAttribute",
              "setIdentifyTimeAttribute",
              "subscribeIdentifyTimeAttribute",
              "addIdentifyTimeAttributeListener",
              "removeIdentifyTimeAttributeListener",
              "getIdentifyTypeAttribute",
              "setIdentifyTypeAttribute",
              "subscribeIdentifyTypeAttribute",
              "addIdentifyTypeAttributeListener",
              "removeIdentifyTypeAttributeListener",
              "getClusterRevisionAttribute",
              "setClusterRevisionAttribute",
              "subscribeClusterRevisionAttribute",
              "addClusterRevisionAttributeListener",
              "removeClusterRevisionAttributeListener",
              "getFeatureMapAttribute",
              "setFeatureMapAttribute",
              "subscribeFeatureMapAttribute",
              "addFeatureMapAttributeListener",
              "removeFeatureMapAttributeListener",
              "getAttributeListAttribute",
              "setAttributeListAttribute",
              "subscribeAttributeListAttribute",
              "addAttributeListAttributeListener",
              "removeAttributeListAttributeListener",
              "getAcceptedCommandListAttribute",
              "setAcceptedCommandListAttribute",
              "subscribeAcceptedCommandListAttribute",
              "addAcceptedCommandListAttributeListener",
              "removeAcceptedCommandListAttributeListener",
              "getGeneratedCommandListAttribute",
              "setGeneratedCommandListAttribute",
              "subscribeGeneratedCommandListAttribute",
              "addGeneratedCommandListAttributeListener",
              "removeGeneratedCommandListAttributeListener",
              "identify",
              "triggerEffect"
            ]
          },
          {
            "id": "4",
            "name": "Groups",
            "attributes": [
              "0",
              "65528",
              "65529",
              "65531",
              "65532",
              "65533",
              "nameSupport",
              "clusterRevision",
              "featureMap",
              "attributeList",
              "acceptedCommandList",
              "generatedCommandList"
            ],
            "commands": [
              "0",
              "1",
              "2",
              "3",
              "4",
              "5",
              "addGroup",
              "viewGroup",
              "getGroupMembership",
              "removeGroup",
              "removeAllGroups",
              "addGroupIfIdentifying"
            ],
            "all_keys": [
              "id",
              "name",
              "revision",
              "_type",
              "supportedFeatures",
              "isUnknown",
              "endpointId",
              "attributes",
              "events",
              "commands",
              "subscribeAllAttributes",
              "_triggerAttributeUpdate",
              "_triggerEventUpdate",
              "isAttributeSupported",
              "isAttributeSupportedByName",
              "isCommandSupported",
              "isCommandSupportedByName",
              "getNameSupportAttribute",
              "setNameSupportAttribute",
              "subscribeNameSupportAttribute",
              "addNameSupportAttributeListener",
              "removeNameSupportAttributeListener",
              "getClusterRevisionAttribute",
              "setClusterRevisionAttribute",
              "subscribeClusterRevisionAttribute",
              "addClusterRevisionAttributeListener",
              "removeClusterRevisionAttributeListener",
              "getFeatureMapAttribute",
              "setFeatureMapAttribute",
              "subscribeFeatureMapAttribute",
              "addFeatureMapAttributeListener",
              "removeFeatureMapAttributeListener",
              "getAttributeListAttribute",
              "setAttributeListAttribute",
              "subscribeAttributeListAttribute",
              "addAttributeListAttributeListener",
              "removeAttributeListAttributeListener",
              "getAcceptedCommandListAttribute",
              "setAcceptedCommandListAttribute",
              "subscribeAcceptedCommandListAttribute",
              "addAcceptedCommandListAttributeListener",
              "removeAcceptedCommandListAttributeListener",
              "getGeneratedCommandListAttribute",
              "setGeneratedCommandListAttribute",
              "subscribeGeneratedCommandListAttribute",
              "addGeneratedCommandListAttributeListener",
              "removeGeneratedCommandListAttributeListener",
              "addGroup",
              "viewGroup",
              "getGroupMembership",
              "removeGroup",
              "removeAllGroups",
              "addGroupIfIdentifying"
            ]
          },
          {
            "id": "6",
            "name": "OnOff",
            "attributes": [
              "0",
              "16384",
              "16385",
              "16386",
              "16387",
              "65528",
              "65529",
              "65531",
              "65532",
              "65533",
              "onOff",
              "clusterRevision",
              "featureMap",
              "attributeList",
              "acceptedCommandList",
              "generatedCommandList",
              "globalSceneControl",
              "onTime",
              "offWaitTime",
              "startUpOnOff"
            ],
            "commands": [
              "0",
              "1",
              "2",
              "64",
              "65",
              "66",
              "off",
              "on",
              "toggle",
              "offWithEffect",
              "onWithRecallGlobalScene",
              "onWithTimedOff"
            ],
            "all_keys": [
              "id",
              "name",
              "revision",
              "_type",
              "supportedFeatures",
              "isUnknown",
              "endpointId",
              "attributes",
              "events",
              "commands",
              "subscribeAllAttributes",
              "_triggerAttributeUpdate",
              "_triggerEventUpdate",
              "isAttributeSupported",
              "isAttributeSupportedByName",
              "isCommandSupported",
              "isCommandSupportedByName",
              "getOnOffAttribute",
              "setOnOffAttribute",
              "subscribeOnOffAttribute",
              "addOnOffAttributeListener",
              "removeOnOffAttributeListener",
              "getClusterRevisionAttribute",
              "setClusterRevisionAttribute",
              "subscribeClusterRevisionAttribute",
              "addClusterRevisionAttributeListener",
              "removeClusterRevisionAttributeListener",
              "getFeatureMapAttribute",
              "setFeatureMapAttribute",
              "subscribeFeatureMapAttribute",
              "addFeatureMapAttributeListener",
              "removeFeatureMapAttributeListener",
              "getAttributeListAttribute",
              "setAttributeListAttribute",
              "subscribeAttributeListAttribute",
              "addAttributeListAttributeListener",
              "removeAttributeListAttributeListener",
              "getAcceptedCommandListAttribute",
              "setAcceptedCommandListAttribute",
              "subscribeAcceptedCommandListAttribute",
              "addAcceptedCommandListAttributeListener",
              "removeAcceptedCommandListAttributeListener",
              "getGeneratedCommandListAttribute",
              "setGeneratedCommandListAttribute",
              "subscribeGeneratedCommandListAttribute",
              "addGeneratedCommandListAttributeListener",
              "removeGeneratedCommandListAttributeListener",
              "getGlobalSceneControlAttribute",
              "setGlobalSceneControlAttribute",
              "subscribeGlobalSceneControlAttribute",
              "addGlobalSceneControlAttributeListener",
              "removeGlobalSceneControlAttributeListener",
              "getOnTimeAttribute",
              "setOnTimeAttribute",
              "subscribeOnTimeAttribute",
              "addOnTimeAttributeListener",
              "removeOnTimeAttributeListener",
              "getOffWaitTimeAttribute",
              "setOffWaitTimeAttribute",
              "subscribeOffWaitTimeAttribute",
              "addOffWaitTimeAttributeListener",
              "removeOffWaitTimeAttributeListener",
              "getStartUpOnOffAttribute",
              "setStartUpOnOffAttribute",
              "subscribeStartUpOnOffAttribute",
              "addStartUpOnOffAttributeListener",
              "removeStartUpOnOffAttributeListener",
              "off",
              "on",
              "toggle",
              "offWithEffect",
              "onWithRecallGlobalScene",
              "onWithTimedOff"
            ]
          },
          {
            "id": "29",
            "name": "Descriptor",
            "attributes": [
              "0",
              "1",
              "2",
              "3",
              "4",
              "65528",
              "65529",
              "65531",
              "65532",
              "65533",
              "deviceTypeList",
              "serverList",
              "clientList",
              "partsList",
              "clusterRevision",
              "featureMap",
              "attributeList",
              "acceptedCommandList",
              "generatedCommandList",
              "tagList"
            ],
            "commands": [],
            "all_keys": [
              "id",
              "name",
              "revision",
              "_type",
              "supportedFeatures",
              "isUnknown",
              "endpointId",
              "attributes",
              "events",
              "commands",
              "subscribeAllAttributes",
              "_triggerAttributeUpdate",
              "_triggerEventUpdate",
              "isAttributeSupported",
              "isAttributeSupportedByName",
              "isCommandSupported",
              "isCommandSupportedByName",
              "getDeviceTypeListAttribute",
              "setDeviceTypeListAttribute",
              "subscribeDeviceTypeListAttribute",
              "addDeviceTypeListAttributeListener",
              "removeDeviceTypeListAttributeListener",
              "getServerListAttribute",
              "setServerListAttribute",
              "subscribeServerListAttribute",
              "addServerListAttributeListener",
              "removeServerListAttributeListener",
              "getClientListAttribute",
              "setClientListAttribute",
              "subscribeClientListAttribute",
              "addClientListAttributeListener",
              "removeClientListAttributeListener",
              "getPartsListAttribute",
              "setPartsListAttribute",
              "subscribePartsListAttribute",
              "addPartsListAttributeListener",
              "removePartsListAttributeListener",
              "getClusterRevisionAttribute",
              "setClusterRevisionAttribute",
              "subscribeClusterRevisionAttribute",
              "addClusterRevisionAttributeListener",
              "removeClusterRevisionAttributeListener",
              "getFeatureMapAttribute",
              "setFeatureMapAttribute",
              "subscribeFeatureMapAttribute",
              "addFeatureMapAttributeListener",
              "removeFeatureMapAttributeListener",
              "getAttributeListAttribute",
              "setAttributeListAttribute",
              "subscribeAttributeListAttribute",
              "addAttributeListAttributeListener",
              "removeAttributeListAttributeListener",
              "getAcceptedCommandListAttribute",
              "setAcceptedCommandListAttribute",
              "subscribeAcceptedCommandListAttribute",
              "addAcceptedCommandListAttributeListener",
              "removeAcceptedCommandListAttributeListener",
              "getGeneratedCommandListAttribute",
              "setGeneratedCommandListAttribute",
              "subscribeGeneratedCommandListAttribute",
              "addGeneratedCommandListAttributeListener",
              "removeGeneratedCommandListAttributeListener",
              "getTagListAttribute",
              "setTagListAttribute",
              "subscribeTagListAttribute",
              "addTagListAttributeListener",
              "removeTagListAttributeListener"
            ]
          }
        ],
        "child_endpoints": []
      }
    ],
    "node_information": {
      "vendor_name": "NOUS TECHNOLOGY",
      "product_name": "A8M",
      "product_id": 2268,
      "vendor_id": 5168
    }
  }

Si pas présent cela veut dire que la prise ne remonte pas l’iinfo sur Matter ?

J’ai trouvé l’info sur un autre forum

‘La consommation d’énergie n’est implémentée que dans la version 1.3 de matter.

Tant qu’il n’y aura pas de mise à jour du firmware la consommation ne sera pas remontée via le protocole matter.’

1 « J'aime »

Effectivement il faut attendre que NOUS mette à jour sa prise !

C’est compliqué de savoir du coup ce qui est disponible avec matter.

Cette marque sous zigbee je pouvais l’appairer directement avec zigbee2mqtt.

La il a fallu que je créé un compte Nous, appairer la prise a l’application Nous et ensuite partager la prise avec matter. Une usine a gaz ….

Premier composant matter pour moi et mauvaise expérience……

Donc ça veut dire qu’un appareil Wifi+Matter est bien plus compliqué à intégrer qu’un appareil Thread+Matter ? Je pensais que c’était plug’n’play :frowning:
C’est clair que si on doit créer un compte pour chaque marque ça va devenir pénible.

En fait, ça dépend des marques.

Certaines marques jouent le jeu de Matter, mais d’autres préfèrent imposer leur application.

Chez Eve par exemple, tu peux utiliser tous leurs produits de bout en bout sans application :

Après, on est dans les débuts de Matter, les marques font des tests.

En tant que consommateur, c’est aussi notre rôle de se plaindre aux marques et de demander du local-only sans applications :slightly_smiling_face:

Mais ça n’a pas trop de lien avec Matter qui est bien un protocole purement local.

Eve => Thread => pas de compte à créer
Nous => Wifi => compte à créer

Ce n’est pas parce qu’une prise est wifi qu’elle doit forcément nécessiter la création d’un compte, on pourrait avoir le même process que la Eve thread sur une prise Matter wifi… c’est un choix du constructeur

1 « J'aime »

@pierre-gilles, je reviens sur ma prise NOUS A8M Matter WiFi que j’ai appairée dans Gladys.

Le commutateur est bien remonté, mais je n’ai pas toutes les données liées à la conso, courant, tension.

Pour faire un essai, j’ai ajouté l’intégration Matter dans Home Assistant. Là, toutes les données sont bien remontées. Pourquoi pas dans Gladys ?
À ton retour de vacances, je serais intéressé de comprendre pourquoi.

1 « J'aime »

Alors j’ai cherché un peu de mon côté car Home Assistant permet aussi de télécharger un json. Je pourrais te le donner si besoin.
J’ai donné ce json a une IA et voilà sa conclusion NOUS utilise ici un cluster spécifique au fabricant :

D'après le fichier JSON que vous avez fourni, il s'agit d'une analyse très intéressante. Vous utilisez Home Assistant 2025.12 et l'appareil en question est un produit de la marque NOUS TECHNOLOGY.

Voici les fonctionnalités que j'ai pu déduire de l'analyse des clusters et des attributs du "Node 2" :

1. Identification de l'appareil
Fabricant : NOUS TECHNOLOGY (Attribut 0/40/1)

Modèle : A8M (Attribut 0/40/3)

Type d'appareil : Prise connectée (On/Off Plug-in Unit).

Le code 266 (soit 0x010A) trouvé dans le cluster Descriptor de l'Endpoint 1 confirme qu'il s'agit d'une unité de commutation de puissance.

2. Fonctionnalités de contrôle (Endpoint 1)
L'Endpoint 1 est celui qui gère l'aspect "domotique" de la prise :

Commutation (Cluster 0x0006 - On/Off) :

L'appareil peut être allumé ou éteint à distance.

État actuel : La prise est actuellement Éteinte ("1/6/0": false).

Mémoire d'état : Elle supporte la configuration de l'état après une coupure de courant (attributs 16384 et suivants).

Identification (Cluster 0x0003) :

Vous pouvez faire "clignoter" la LED de la prise pour l'identifier physiquement parmi d'autres.

Gestion de groupes (Cluster 0x0004) :

Elle peut être assignée à des groupes Matter pour être contrôlée simultanément avec d'autres prises.

3. Mesure de consommation (Cluster Propriétaire)
C'est ici que cela devient intéressant. On voit un cluster avec un ID très élevé : 308149265 (0x125B0011).

Dans l'écosystème Matter actuel (version 1.0 à 1.3), la mesure d'énergie n'était pas encore totalement standardisée de la même manière pour tous. NOUS utilise ici un cluster spécifique au fabricant pour remonter les informations électriques. D'après les valeurs brutes dans le JSON (1/308149265/...), la prise remonte :

Le Voltage : Probablement l'attribut 308084772 (valeur 2249 soit 224.9V).

L'Intensité : Probablement l'attribut 308084774 (valeur 542 soit 54.2 mA).

La Puissance (Watts) : Les autres attributs correspondent à la puissance active et à la consommation cumulée (kWh).

4. Connectivité et Santé
Protocole : C'est une prise Matter-over-WiFi. On le voit car le cluster Network Commissioning (0/49) est présent et configuré pour le Wi-Fi, contrairement au Thread qui n'apparaît pas comme actif ici.

Signal : La prise rapporte un RSSI de -34 (0/54/4), ce qui indique une excellente connexion Wi-Fi (très proche de la borne).

Dans le JSON de votre prise NOUS A8M :

L'appareil a été conçu ou certifié avant la généralisation de Matter 1.3, ou le fabricant a choisi d'utiliser son propre cluster pour assurer la compatibilité avec ses anciens systèmes.

C'est pour cela que vous voyez des IDs comme 308084772 (qui est un ID propriétaire) au lieu de l'ID standard 2 pour le voltage.

Comment Home Assistant gère cela ?
Home Assistant possède des "traducteurs" (appelés Discovery Schemas).

Si l'ID est 0x0090, Home Assistant crée automatiquement les capteurs de puissance.

Pour les IDs propriétaires (comme le vôtre), l'intégration Matter de Home Assistant doit inclure une règle spécifique pour dire : "Si c'est une prise NOUS, alors l'attribut X correspond au Voltage".

Salut @_Will_71,

Merci pour ton investigation, c’est très clair ce qui se passe :+1:

NOUS a implémenté le suivi de l’énergie avant son intégration officielle dans Matter 1.3. Il s’agit donc d’une implémentation propriétaire NOUS, et non d’une fonctionnalité Matter standard.

Home Assistant arrive à récupérer cette valeur car ils ont développé une couche spécifique pour NOUS, afin de gérer ce cas particulier hors spécification Matter.

De notre côté, Gladys ne manipule pas Matter directement : nous nous appuyons sur la dépendance JavaScript officielle matter-js

:backhand_index_pointing_right: GitHub - matter-js/matter.js: A complete typescript implementation of the Matter protocol specification (https://buildwithmatter.com). Includes full support for controller, device, commissioning, secure communications, device types, and cluster definitions.

Je ne sais pas à ce stade si cette librairie permet d’accéder facilement à des attributs non officiels / non standard.

À titre personnel, je m’interroge aussi sur l’intérêt d’investir du temps dans ce type de spécificité. Pour moi, c’est un peu l’anti-thèse de Matter : l’objectif est justement d’uniformiser les comportements entre constructeurs. Si l’on encourage des implémentations Matter non officielles, cela risque de pérenniser ces pratiques et de décrédibiliser l’initiative.

Je suis d’ailleurs assez surpris de la position de Home Assistant sur ce point : en tant qu’acteur majeur de l’écosystème, ils pourraient aussi montrer l’exemple sur le respect strict des spécifications.

Dans un premier temps, est-ce que tu pourrais contacter NOUS pour leur demander si une mise à jour de firmware conforme à Matter 1.3 est prévue ?

Merci encore pour ton retour très détaillé :slightly_smiling_face:

1 « J'aime »