[Matter] Añadir el consumo eléctrico de un dispositivo

Tras la solicitud de @jmarandet para gestionar sus dispositivos Shelly (Aide pour connexion Shelly EM avec Gladys en MQTT - #8 par jmarandet), añadir la gestión del consumo eléctrico y los clusters Matter siguientes:

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

Hola a todos :slight_smile:

He desarrollado una PR sobre este tema, totalmente a ciegas, basándome en lo que encontré en Matter.js.

@Nagromdark ¿Podrías revisar la PR y decirme qué opinas? Si tienes material para probar, ¡también lo acepto! :smiley:

@jmarandet Necesitaría tu ayuda. En Matterbridge, para los Shelly, ¿qué ves como « supported_feature »?

Normalmente, es una información que puedes encontrar en cada dispositivo.

¡Me encantaría una captura de pantalla de uno de tus Shelly con todos los valores expuestos!

Otra pregunta, los valores en kWh que se muestran, ¿es un índice? En el sentido de que es un consumo « acumulado », que solo aumenta?

Hola, aún no la he visto, tengo un poco menos de tiempo en este momento. En todo caso, no tengo un dispositivo para probarlo, lo siento

Me he creado un pequeño entorno de prueba con mi toma de corriente Eve Energy que sube el consumo, pero me estoy volviendo loco, he logrado hacer el voltaje, la intensidad y la potencia, pero imposible obtener el índice:

Como el índice solo se sube cada 10 minutos, es muy tedioso probar, llevo 1h30 en ello :joy:

Vale, he hablado bastante con el desarrollador de Matter.js, y probablemente esté relacionado con mi toma de corriente Eve Energy que no debe estar subiendo el valor de consumo acumulado.

Lo probé con Matterbridge y matterbridge-example-dynamic-platform, pero tampoco funciona, el valor no se envía…

Estoy un poco atascado a nivel de hardware de prueba :sweat_smile:

Hola a todos,

Lamentablemente no he podido probarlo, pero para avanzar he desplegado este desarrollo en Gladys Assistant 4.65, será más fácil para obtener comentarios.

Estoy abierto a comentarios para saber si funciona o no :slight_smile:

cc @jmarandet

Hola @pierre-gilles,

En mi caso no funciona. Tengo un enchufe Nous A8M https://www.domadoo.fr/fr/devices-matter/7147-nous-prise-connectee-wifi-matter-16a-mesure-consommation-a8m.html con medición de consumo, pero no tengo datos en Gladys.

Aquí tienes una captura de la app del enchufe donde tengo un consumo de 25w. Por lo tanto, la medición es correcta

En Gladys solo tengo la funcionalidad de interruptor

¿Todas las funcionalidades disponibles están en el json?

¿Por qué debería poder encontrar una de las funcionalidades que has añadido?

image

A continuación el 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 no está presente, ¿significa que el enchufe no reporta la información a Matter?

Encontré la información en otro foro

‘El consumo de energía solo está implementado en la versión 1.3 de matter.

Hasta que no haya una actualización del firmware, el consumo no se enviará a través del protocolo matter.’

Efectivamente hay que esperar a que NOUS actualice su toma.

Es complicado saber qué está disponible con Matter.

Esta marca bajo Zigbee podía emparejarla directamente con Zigbee2mqtt.

Aquí tuve que crear una cuenta Nous, emparejar el enchufe con la aplicación Nous y luego compartir el enchufe con Matter. Un lío…

Primer componente Matter para mí y mala experiencia…

¿Eso significa que un dispositivo WiFi+Matter es mucho más complicado de integrar que un dispositivo Thread+Matter? Pensé que era plug’n’play :frowning:
Está claro que si hay que crear una cuenta para cada marca se va a volver molesto.

En realidad, depende de las marcas.

Algunas marcas juegan el juego de Matter, pero otras prefieren imponer su aplicación.

Por ejemplo, en Eve puedes usar todos sus productos de principio a fin sin aplicación:

Después, estamos en los inicios de Matter, las marcas están haciendo pruebas.

Como consumidores, también es nuestro papel quejarnos a las marcas y pedirles local-only sin aplicaciones :slightly_smiling_face:

Pero esto no tiene mucho que ver con Matter, que es un protocolo puramente local.

Eve => Hilo => no hay que crear una cuenta
Nosotros => Wifi => hay que crear una cuenta

No es porque un enchufe sea WiFi que necesariamente debe requerir la creación de una cuenta, podríamos tener el mismo proceso que el hilo de Eve en un enchufe WiFi Matter… es una elección del fabricante

@pierre-gilles, vuelvo sobre mi toma NOUS A8M Matter WiFi que he emparejado en Gladys.

El interruptor se ha subido correctamente, pero no tengo todos los datos relacionados con el consumo, corriente, tensión.

Para hacer una prueba, he añadido la integración Matter en Home Assistant. Allí, todos los datos se han subido correctamente. ¿Por qué no en Gladys?
Al volver de vacaciones, me gustaría entender por qué.

Entonces, busqué un poco por mi cuenta, ya que Home Assistant también permite descargar un archivo json. Podría dártelo si es necesario.
Le di este json a una IA y esta es su conclusión: NOUS utiliza aquí un clúster específico del fabricante :

Según el archivo JSON que has proporcionado, es un análisis muy interesante. Estás utilizando Home Assistant 2025.12 y el dispositivo en cuestión es un producto de la marca NOUS TECHNOLOGY.

Estas son las características que he podido deducir del análisis de los clústeres y los atributos del "Node 2":

1. Identificación del dispositivo
Fabricante: NOUS TECHNOLOGY (Atributo 0/40/1)

Modelo: A8M (Atributo 0/40/3)

Tipo de dispositivo: Enchufe conectado (Unidad enchufable On/Off).

El código 266 (es decir, 0x010A) encontrado en el clúster Descriptor del Endpoint 1 confirma que se trata de una unidad de conmutación de potencia.

2. Características de control (Endpoint 1)
El Endpoint 1 es el que gestiona el aspecto "domótico" del enchufe:

Conmutación (Clúster 0x0006 - On/Off):

El dispositivo puede encenderse o apagarse a distancia.

Estado actual: El enchufe está actualmente apagado ("1/6/0": false).

Memoria de estado: Soporta la configuración del estado después de un corte de energía (atributos 16384 y siguientes).

Identificación (Clúster 0x0003):

Puedes hacer que el LED del enchufe parpadee para identificarlo físicamente entre otros.

Gestión de grupos (Clúster 0x0004):

Puede ser asignado a grupos Matter para ser controlado simultáneamente con otros enchufes.

3. Medición de consumo (Clúster Propietario)
Aquí es donde se pone interesante. Se ve un clúster con un ID muy alto: 308149265 (0x125B0011).

En el ecosistema Matter actual (versión 1.0 a 1.3), la medición de energía no estaba totalmente estandarizada de la misma manera para todos. NOUS utiliza aquí un clúster específico del fabricante para enviar las informaciones eléctricas. Según los valores brutos en el JSON (1/308149265/...), el enchufe envía:

El voltaje: Probablemente el atributo 308084772 (valor 2249, es decir, 224.9V).

La intensidad: Probablemente el atributo 308084774 (valor 542, es decir, 54.2 mA).

La potencia (Watts): Los otros atributos corresponden a la potencia activa y al consumo acumulado (kWh).

4. Conectividad y Salud
Protocolo: Es un enchufe Matter-over-WiFi. Se puede ver porque el clúster Network Commissioning (0/49) está presente y configurado para Wi-Fi, a diferencia de Thread que no aparece como activo aquí.

Señal: El enchufe informa un RSSI de -34 (0/54/4), lo que indica una excelente conexión Wi-Fi (muy cerca del punto de acceso).

En el JSON de tu enchufe NOUS A8M:

El dispositivo fue diseñado o certificado antes de la generalización de Matter 1.3, o el fabricante ha elegido utilizar su propio clúster para asegurar la compatibilidad con sus sistemas antiguos.

Por eso ves IDs como 308084772 (que es un ID propietario) en lugar del ID estándar 2 para el voltaje.

¿Cómo gestiona esto Home Assistant?
Home Assistant tiene "traductoras" (llamadas Discovery Schemas).

Si el ID es 0x0090, Home Assistant crea automáticamente los sensores de potencia.

Para los IDs propietarios (como el tuyo), la integración Matter de Home Assistant debe incluir una regla específica para decir: "Si es un enchufe NOUS, entonces el atributo X corresponde al voltaje".

Hola @Will_71,

Gracias por tu investigación, es muy claro lo que está pasando :+1:

NOUS ha implementado el seguimiento de energía antes de su integración oficial en Matter 1.3. Por lo tanto, se trata de una implementación propietaria de NOUS, y no de una funcionalidad estándar de Matter.

Home Assistant logra recuperar este valor porque han desarrollado una capa específica para NOUS, para gestionar este caso particular fuera de la especificación de Matter.

Por nuestra parte, Gladys no manipula Matter directamente: nos basamos en la dependencia oficial de JavaScript 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. · GitHub

No sé en este momento si esta biblioteca permite acceder fácilmente a atributos no oficiales / no estándar.

Personalmente, también me pregunto sobre el interés de invertir tiempo en este tipo de especificidad. Para mí, es un poco la antítesis de Matter: el objetivo es precisamente uniformizar los comportamientos entre fabricantes. Si se fomentan implementaciones no oficiales de Matter, esto podría perpetuar estas prácticas y desprestigiar la iniciativa.

De hecho, estoy bastante sorprendido por la posición de Home Assistant en este punto: como actor importante del ecosistema, también podrían dar ejemplo en el estricto cumplimiento de las especificaciones.

De momento, ¿podrías contactar con NOUS para preguntarles si se planea una actualización de firmware conforme a Matter 1.3?

Gracias de nuevo por tu comentario muy detallado :slightly_smiling_face:

@Will_71 Voy a cerrar esta solicitud de funcionalidad, ya que ahora gestionamos bien el consumo eléctrico de un dispositivo de los dispositivos Matter nativos, la PR Matter: Add electricity consumption features by Pierre-Gilles · Pull Request #2365 · GladysAssistant/Gladys · GitHub se fusionó el mes pasado.

¿Podemos continuar la discusión sobre tu dispositivo en un tema separado? :slight_smile: