#25000·Tasmota

Matter non-bridge mode produces an unreachable endpoint tree (root PartsList omits endpoint 1)

Author: spudwebbCreated Sep 3, 2026Updated Sep 3, 2026

PROBLEM DESCRIPTION

When Matter bridge mode is disabled, the Root Node endpoint's Descriptor.PartsList (0/0x001D/0x0003) omits the aggregator at endpoint 1 — but endpoint 1 is still instantiated, still reports device type 0x000E (Aggregator), and its own PartsList still claims the application endpoints. The result is an endpoint tree in which the app endpoints are only reachable through an endpoint that the root never announces.

Per the Matter Core Specification (Descriptor cluster, PartsList), the Root Node endpoint's PartsList is a flat "full-family" list that must enumerate every endpoint on the node except endpoint 0. A controller is entitled to read 0/0x001D/0x0003 and treat it as the complete endpoint inventory.

Controllers that build a real endpoint hierarchy therefore lose the whole subtree: endpoint 1 is claimed by nobody, so it is never attached, and the app endpoints it claims go with it. The device shows up with endpoint 0 only and cannot be read or controlled. Controllers that merely keep a flat attribute map (Home Assistant's python-matter-server) never notice, which is why this has gone unreported.

What the device reports

Read from a real device (Tasmota 15.6.0.1, ESP32-U4WDH-D v3.1, VID 0xFFF1 / PID 0x8000, two On/Off Plug-in Units), decoded from the raw TLV of a wildcard read:

Endpoint DeviceTypeList PartsList
0 0x0016 Root Node, rev 3 [2, 3]
1 0x000E Aggregator, rev 1 [2, 3]
2 0x010A On/Off Plug-in Unit, rev 3 []
3 0x010A On/Off Plug-in Unit, rev 3 []

Raw bytes for 0/0x001D/0x0003:

2402 00   endpoint 0
2403 1d   cluster 0x001D (Descriptor)
2404 03   attribute 0x0003 (PartsList)
18        end of path
3602      array, context tag 2 (Data)
04 02     unsigned 2
04 03     unsigned 3
18 18 18  end

Endpoint 1 exists and answers the read. Nothing in the node claims it.

Where it comes from

lib/libesp32/berry_matter/src/embedded/Matter_Plugin_1_Root.be (current development):

berry
elif attribute == 0x0003          # ---------- PartsList / list[endpoint-no]----------
  var pl = TLV.Matter_TLV_array()
  var eps = self.device.get_active_endpoints(true)
  var disable_bridge_mode = self.device.disable_bridge_mode
  for ep: eps
    # if bridge mode is disabled, don't announce Aggregatore (above 0xFF00)
    if !disable_bridge_mode || ep != 0x0001 #-matter.AGGREGATOR_ENDPOINT-#
      pl.add_TLV(nil, 0x05 #-TLV.U2-#, ep)     # add each endpoint
    end
  end
  return pl

The aggregator is filtered out of the root's list, but nothing stops it from being created or from claiming endpoints 2 and 3 in Matter_Plugin_1_Aggregator.be. (Side note: the comment still says "above 0xFF00" while the check is ep != 0x0001, so the filter looks older than the move of the aggregator to endpoint 1 in #20654.)

Suggested fix

Either would resolve it:

  1. Announce the aggregator. Drop the filter so the root PartsList is [1, 2, 3]. Hiding endpoint 1 from the root list does not hide it from the network — a controller reading endpoint 1 directly still finds an Aggregator — so the filter buys nothing while breaking tree construction.
  2. Don't create the aggregator when bridge mode is disabled. Root PartsList becomes [2, 3] and the app endpoints hang directly off the root, which is what the reported list already implies. This is the cleaner shape if the intent is "this device is not a bridge".

A smaller, unrelated observation while looking at this: endpoints 2 and 3 expose cluster 0x0039 (Bridged Device Basic Information) without carrying the 0x0013 Bridged Node device type. That cluster is only meant to appear on a Bridged Node endpoint. It is not the cause of this problem, but it may be worth tidying in the same area.

REQUESTED INFORMATION

  • Read the Contributing Guide and Policy and the Code of Conduct
  • Searched the problem in issues
  • Searched the problem in discussions
  • Searched the problem in the docs
  • Searched the problem in the chat
  • Problem is not scripter related, in this case open a discussion and tag gemu2015
  • Device used (e.g., Sonoff Basic): _ESP32 (ESP32-U4WDH-D v3.1)
  • Tasmota binary firmware version number used: 15.6.0.1
    • Pre-compiled
    • Self-compiled
  • Flashing tools used:
  • Provide the output of command: Backlog Template; Module; GPIO 255:
lua
  Configuration output here:

  N/A
  • If using rules, provide the output of this command: Backlog Rule1; Rule2; Rule3:
lua
  Rules output here:

  N/A
  • Provide the output of this command: Status 0:
lua
  STATUS 0 output here:

  N/A
  • Set weblog to 4 and then, when you experience your issue, provide the output of the Console log:
lua
  Console output here:

  N/A

TO REPRODUCE

  1. Configure Matter on an ESP32 with two On/Off endpoints and bridge mode disabled.
  2. Commission the device with a controller that builds a real endpoint hierarchy from the Descriptor cluster (matter.js-based controllers do; python-matter-server does not).
  3. Read 0/0x001D/0x0003 — it returns [2, 3], omitting the aggregator at endpoint 1.
  4. Observe that the controller ends up with endpoint 0 only, and endpoints 2 and 3 cannot be read or commanded.

Enabling bridge mode on the same device makes the root PartsList include endpoint 1, and everything then works.

EXPECTED BEHAVIOUR

The root endpoint's PartsList enumerates every endpoint on the node, so that a controller reading 0/0x001D/0x0003 obtains the complete endpoint inventory and can build the endpoint tree — in bridge mode and non-bridge mode alike.

SCREENSHOTS

N/A — the relevant evidence is the decoded Descriptor tree above.

ADDITIONAL CONTEXT

Originally reported by a user of a matter.js-based controller, where the device appeared with endpoint 0 and nothing else. The controller-side handling of this non-compliant tree is being tracked separately against matter.js: https://github.com/matter-js/matter.js/issues/4405 This issue is about the device-side PartsList.