Matter Binding

I have a probable stupid question: why my 40 W bulb connected to that socket has an instantaneous consumption of 0.001 kWh and not 0.040 as I would expect?

Electrical work (energy) is measured in Wattseconds (Ws), which is the product of power (W) and time (s).
In your example, the question is how long the lamp was on after 0.001 kWh was displayed.
Only after 15 minutes should a calculated value of 0.001 kWh be obtained.
Is there a rounding error because the value is still so small after a shorter time, but is displayed in the ā€œlargeā€ unit kWh?

Thank you for the summary.

So the energy measurements clusters have additional properties that can be exposed as channels for info on the reporting period, right now i’m just exposing the energy value, but i can add these as well if they exist.

    /\*\*

     \* This field shall be the reported energy.

     \* If the EnergyMeasurementStruct represents cumulative energy, then this shall represent the cumulative energy

     \* recorded at either the value of the EndTimestamp field or the value of the EndSystime field, or both.

     \* If the EnergyMeasurementStruct represents periodic energy, then this shall represent the energy recorded

     \* during the period specified by either the StartTimestamp and EndTimestamp fields, the period specified by the

     \* StartSystime and EndSystime fields, or both.

     \*/

public BigInteger energy; // energy-mWh

    /\*\*

     \* This field shall indicate the timestamp in UTC of the beginning of the period during which the value of the

     \* Energy field was measured.

     \* If this EnergyMeasurementStruct represents cumulative energy, this field shall be omitted.

     \* Otherwise, if the server had determined the time in UTC at or before the beginning of the measurement period,

     \* this field shall be indicated.

     \* Otherwise, if the server had not yet determined the time in UTC at or before the beginning of the measurement

     \* period, or does not have the capability of determining the time in UTC, this field shall be omitted.

     \*/

public Integer startTimestamp; // epoch-s

    /\*\*

     \* This field shall indicate the timestamp in UTC of the end of the period during which the value of the Energy

     \* field was measured.

     \* If the server had determined the time in UTC by the end of the measurement period, this field shall be

     \* indicated.

     \* Otherwise, if the server had not yet determined the time in UTC by the end of the measurement period, or does

     \* not have the capability of determining the time in UTC, this field shall be omitted.

     \*/

public Integer endTimestamp; // epoch-s

    /\*\*

     \* This field shall indicate the time elapsed since boot at the beginning of the period during which the value

     \* of the Energy field was measured.

     \* If this EnergyMeasurementStruct represents cumulative energy, this field shall be omitted.

     \* Otherwise, if the server had not yet determined the time in UTC at the start of the measurement period, or

     \* does not have the capability of determining the time in UTC, this field shall be indicated.

     \* Otherwise, if the server had determined the time in UTC at or before the beginning of the measurement period,

     \* this field may be omitted; if it is indicated, its value shall be the time elapsed since boot at the UTC time

     \* indicated in StartTimestamp.

     \*/

public BigInteger startSystime; // systime-ms

    /\*\*

     \* This field shall indicate the time elapsed since boot at the end of the period during which the value of the

     \* Energy field was measured.

     \* If the server had not yet determined the time in UTC by the end of the measurement period, or does not have

     \* the capability of determining the time in UTC, this field shall be indicated.

     \* Otherwise, if the server had determined the time in UTC by the end of the measurement period, this field may

     \* be omitted; if it is indicated, its value shall be the time elapsed since boot at the UTC time indicated in

     \* EndTimestamp.

     \*/
public BigInteger endSystime; // systime-ms

This is likely the issue, we are converting the raw value from what i’m assuming is milliWatts?

return new QuantityType<Energy>(new BigDecimal(number.intValue() / 1000), Units.WATT_HOUR);

I guess my question for those with stronger EE skills then i, what QuantityType should we be reporting these numbers as ? (i would assume something more granular then what we are).

For me, the question is what level of inaccuracy can/will one live with; it depends on the specific use case.
For private use, I don’t think it needs to be so granular. I don’t know anyone who personally measures or evaluates such small energy values. If larger deviations arise when summing the values, that would be rather critical.

Furthermore, the measured values provided by the various devices are certainly not calibrated in most cases and therefore inherently contain errors.
For me, it’s more of an academic question.
But in principle, I agree with you: a little more precision would be better.

When my lamp is switched on, I can see the channel value periodicenergyimported switching between 0.00100 and 0.00000 kWh around every minute it is not stable.

And for me, the value should be 0.040 kWh as it is a 40 W bulb.

@digitaldan : is startTimestamp and endTimestamp considered to compute the value?

Edit: I checked the code and the answer is no. So maybe that is the issue with the non understandable value. I will add a log to show end - start tlmestamps.

Energy imported is a measure of energy so the UoM would be Wh, kWh, MWh, etc. You would see 0.040 kWh in that channel after one hour if a bulb (or any other device) with a power rating of 40W had been on continuously for an hour and if the value on the device was zero when the bulb was switched on. Or a device was on for an hour and consumed 20W for 30 minutes and then 60W for 30 mins or whatever combination of values adds up to 0.040kWh (40Wh) consumption (or import to use the language on the channel) over an hour.

I’m not sure what would cause the value to switch between 0.00100 and 0.00000 every minute. Maybe some rounding is happening somewhere?

The active power channel would show ~40W while the device is turned on.

I have a Zwave thermostat that also allows setting the fan speed (low/high), that I’m exposing via the Matter bridge. Since Matter thermostats do not support fan control, I just created a separate entity for it:

Number SecondFloor_Tstat_FanMode "Fan Mode" {
  channel="zwavejs:node:bridge:thermostat2:thermostat-fan-mode-mode",
  matter="Fan, fanControl.fanMode" [LOW=0, HIGH=1, fanControl-fanModeSequence=1]
}

Now in the Google Home app, the fan control is rendered as a percent-scale slider. I think it should be a picker that lets me rotate between the low/high states instead. Any idea if this can be done?

Screenshot_20250806_203516_Home

I’m seeing stability issues with the bridge. After setting up 20-odd devices via the bridge and adding them to Google Home, they frequently show up as unavailble in the Google Home app. When this happens there are logs indicating some errors:

answers. Re-Announce in 48 seconds
2025-08-06 20:44:12.565 [TRACE] [ternal.client.MatterWebsocketService] - SecureSession: Register Session secure/42840 to close when exchange is ended.
2025-08-06 20:44:12.566 [TRACE] [ternal.client.MatterWebsocketService] - CaseServer: An error occurred during the commissioning [retransmission-limit-reached] Operation timed out
2025-08-06 20:44:12.566 [TRACE] [ternal.client.MatterWebsocketService] -   at #retransmitMessage (webpack://matter-server/./node_modules/@matter/protocol/dist/cjs/protocol/MessageExchange.js?:404:37)
2025-08-06 20:44:12.566 [TRACE] [ternal.client.MatterWebsocketService] -   at StandardTimer.eval [as callback] (webpack://matter-server/./node_modules/@matter/protocol/dist/cjs/protocol/MessageExchange.js?:397:42)
2025-08-06 20:44:12.567 [TRACE] [ternal.client.MatterWebsocketService] -   at Timeout.eval [as _onTimeout] (webpack://matter-server/./node_modules/@matter/general/dist/cjs/time/Time.js?:147:12)
2025-08-06 20:44:12.567 [TRACE] [ternal.client.MatterWebsocketService] - InsecureSession: End insecure session insecure/13006415383313405628
2025-08-06 20:44:12.568 [TRACE] [ternal.client.MatterWebsocketService] -   at listOnTimeout (node:internal/timers:569:17)
2025-08-06 20:44:12.568 [TRACE] [ternal.client.MatterWebsocketService] -   at process.processTimers (node:internal/timers:512:7)
2025-08-06 20:44:12.568 [TRACE] [ternal.client.MatterWebsocketService] - ExchangeManager: Error on channel udp://[2600:4040:52f1:a300:9c6a:f927:732b:7331]:5540: The previous message has not been acked yet, cannot send a new message.


2025-08-06 20:45:37.176 [TRACE] [ternal.client.MatterWebsocketService] - BridgeController: Executing function bridge.setEndpointStates(["Bath2Light",[{"clusterName":"onOff","attributeName":"onOff","state":true}]])
2025-08-06 20:45:37.176 [TRACE] [ternal.client.MatterWebsocketService] - GenericDevice: Updating states: {"onOff":{"onOff":true}}
2025-08-06 20:45:37.177 [TRACE] [ternal.client.MatterWebsocketService] - matter: Sending response: {"type":"response","message":{"type":"resultSuccess","id":"6157886a-19d4-41a6-a923-47c1f43d30c3","result":"undefined","error":"undefined","errorId":"undefined"}}

Then sometimes when refreshing the GH view, the devices come back online, for a shorter or longer period.

The openHAB devices themselves work fine (outside of Matter).

So i don’t test frequently with GA, i’ll get that going again this week, but i do run 90+ tagged devices to both Alexa and Apple and do not have any issues, so its likely there is something Google is choking on, i’ll see if i can track it down.

Also if you have logs, sending me much larger chunks of them would be helpful, errors like you posted can be common and recoverable, so its helpful to see them in context of other logs, unfortunately there’s not much i can do with small snippets. You can also PM me large logs files, even if they span sever hours.

Any progress on combining the thermostat and fan into one Apple Home item?

I made progress with periodicEnergyImported by adding few DEBUG logs and the result is what I was imagining:

20:01:37.488 [INFO ] [openhab.event.ItemCommandEvent       ] - Item 'PriseMatter' received command ON
20:01:37.616 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'PriseMatter' changed from OFF to ON
20:01:44.981 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'Prise_Tapo_1_Active_Power' changed from 0 W to 42 W
20:02:15.134 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'Prise_Tapo_1_Energy2' changed from 0.072 kWh to 0.073 kWh
20:02:15.138 [DEBUG] [.ElectricalEnergyMeasurementConverter] - periodicEnergyImported energy 1000 startTimestamp null endTimestamp null startSystime 749756624 endSystime 749806624
20:02:15.139 [DEBUG] [.ElectricalEnergyMeasurementConverter] - periodicEnergyImported endSystime - startSystime = 50000 ms
20:02:15.139 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'Prise_Tapo_1_Energy' changed from 0 kWh to 0.001 kWh
20:03:05.134 [DEBUG] [.ElectricalEnergyMeasurementConverter] - periodicEnergyImported energy 0 startTimestamp null endTimestamp null startSystime 749806624 endSystime 749856624
20:03:05.135 [DEBUG] [.ElectricalEnergyMeasurementConverter] - periodicEnergyImported endSystime - startSystime = 50000 ms
20:03:05.135 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'Prise_Tapo_1_Energy' changed from 0.001 kWh to 0 kWh
20:03:55.137 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'Prise_Tapo_1_Energy2' changed from 0.073 kWh to 0.074 kWh
20:03:55.141 [DEBUG] [.ElectricalEnergyMeasurementConverter] - periodicEnergyImported energy 1000 startTimestamp null endTimestamp null startSystime 749856624 endSystime 749906625
20:03:55.142 [DEBUG] [.ElectricalEnergyMeasurementConverter] - periodicEnergyImported endSystime - startSystime = 50001 ms
20:03:55.142 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'Prise_Tapo_1_Energy' changed from 0 kWh to 0.001 kWh
20:04:45.134 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'Prise_Tapo_1_Energy2' changed from 0.074 kWh to 0.075 kWh
20:04:45.137 [DEBUG] [.ElectricalEnergyMeasurementConverter] - periodicEnergyImported energy 1000 startTimestamp null endTimestamp null startSystime 749906625 endSystime 749956624
20:04:45.138 [DEBUG] [.ElectricalEnergyMeasurementConverter] - periodicEnergyImported endSystime - startSystime = 49999 ms
20:05:00.436 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'TemperatureVeranda' changed from 30.7 °C to 30.5 °C
20:05:35.189 [DEBUG] [.ElectricalEnergyMeasurementConverter] - periodicEnergyImported energy 0 startTimestamp null endTimestamp null startSystime 749956624 endSystime 750006677
20:05:35.190 [DEBUG] [.ElectricalEnergyMeasurementConverter] - periodicEnergyImported endSystime - startSystime = 50053 ms
20:05:35.191 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'Prise_Tapo_1_Energy' changed from 0.001 kWh to 0 kWh

As you can see, an event is sent every 50 s and the provided value is the computed value for the (last) 50 s. The current code is assuming it is a value for 1 hour. It has to be fixed to consider the duration for which it was computed.

1000 mW for 50s = 0.072 kWh. It is more than the expected 0.040 kWh.

Remains one thing I do not understand: why the received value is switching between 1000 and 0 ? Maybe a bug outside openHAB binding ? In the Tapo firmware ?

If we take an average between 0.072 kWh and 0 kWh, we are at 0.036 kWh and then not so far from the expected 0.040 kWh.

Maybe there is a bug somewhere making a value < 1000 not possible ?

The energy consumption is a separate channel sent from the Tapo device. It’s a cumulative figure for all the time that the plug has ever been switched on. So 0.072 kWh is the total energy consumed during all the time the Tapo device has ever been turned on. I assume this figure would be reset to zero if the Tapo was factory reset to defaults.

1000mW for 50s would be 1W of power for 50/3600 of an hour or 0.0138888889Wh or 0.0000138889kWh.

Matter is sending two kind of events, the cumulative since the beginning (so very long period of many months in my case) and an instantaneous value which is in fact a cumulative on a short period. For the first, there is no problem, the value is correct. For the second event, I can see in the event that the frame is 50s for the provided value and I can also see that the event is sent every 50s.

I am preparing a fix.

I FINALLY figured out how to commission a dimmer (Inovelli White Series) with the ESP32 OpenThread border router, without use of any other thread router! Here are the steps.

  • Build and configure the ESP Thread BR according to instructions, ensure the OTBR has IPv6 connectivity (you can ping it from the LAN, but the ping command on the OTBR doesn’t work, don’t worry about that)
  • On the OTBR, issue the command dataset active -x to obtain the operational dataset as a long hex string.
  • Build or download chip-tool
  • Obtain the pairing code (11-digit) or QR code from the device. Use a tool like SetupPayload to decode it and extract the pin code (8-digit) and discriminator values
  • Enter pairing mode on the device, for this dimmer it’s done by pressing the config button 3 times.
  • Commission the device onto the thread network with BLE using chip-tool pairing ble-thread <node-id> hex:<dataset> <pin> <discriminator>. The node ID is arbitrary. For example:
chip-tool pairing ble-thread 100 hex:0e08000... 20202021 3840
  • This failed for me in the Attestation step, saying it couldn’t find the attestation certification. Turns out this has to be downloaded from the DCL registry. I did this by grabbing the X.509 certificate from the chip-tool log, looking at the issuer which was HOPERF Matter PAA 01, and searching for ā€œHOPERFā€ in the registry. Then download the PAA certificate, stick it in credentials/production/paa-root-certs/ in the connectedhomeip repo, and append --paa-trust-store-path credentials/production/paa-root-certs to the above chip-tool invocation.
  • Assuming pairing succeeds, the device is now commissioned to chip-tool’s fabric. We need to allow commissioning to openHAB:
chip-tool pairing open-commissioning-window <NODEID> 1 300 1000 <DISCR>

substituting your node ID and discriminator. This command prints out a manual setup code and QR code, different from the original one printed on the device.

  • Now go to openHAB, view the controller Thing, and choose the ā€œPair a Matter deviceā€ action as usual (described in the binding’s README), using the pairing code from the command above.

That’s it, the device should now be commissioned to openHAB and show up in the Inbox. Great Success!!

Hi,

is there a timeline for these items in matter binding, please?

  • Custom cluster (e.g. for power metering)
  • Setting offline device mode
  • Setting device defaults

Thank you for your work.

I see now, thanks. I’ve just looked at the log in your earlier message and can see the start and end times show a 50 second duration. I hadn’t looked closely enough before, apologies.

(I’m going to be pedantic now so I apologise in advance :slight_smile: It’s not possible to have an instantaneous value which is cumulative over a short period of time. Instantaneous means that there was no perceptible measurement of time.)

No, i worked on it quite a bit, but with disastrous results. The problem is that a Matter Bridge is a special type of Node that contains a child endpoint of type ā€œAggregatorā€, which then itself has child endpoints for each device Like:

Node → Endpoint 1 (Aggregator) → Device Endpoint 2, Device Endpoint 3, …. Device Endpoint N.

An actual matter Thermostat may have a single Node and then for example 3 endpoints on the root of the node (Thermostat, Fan, Humidity) and I think Apple at least groups these together when on a single Matter Node in the UI.

I tried various ways that are legal in matter to support this, fist i tried adding multiple Aggregators, but no system liked that and either did not load the devices or loaded some in a half baked state.

Next i tried adding a single Child endpoint to the aggregator as the bridged device, then added child endpoints to that endpoint for Thermostat, Fan, etc….so similar to a actual matter device. Alexa choked on this and would not load, Apple was wildly inconsistent, sometimes it grouped, sometimes it did not, but when it did the UI was all messed up, so i don’t think thats supported yet.

This may be something that gets better with the next couple releases from apple/alexa, so i’ll keep an eye on it, but for right now i don’t have a good solution.

If you can find another matter bridge (not standalone node) that somehow does manages to do this, i can look and see what magic they are using, but neither I or the matter.js devs have a good solution yet.

No there is not a timeline and I don’t have anything started yet.