Skip to content

ConBee II / RaspBee II fail to start since zigpy 2.4.0: no endpoint slot left for the Green Power endpoint 242 #280

Closed

Description

@zigpy-review-bot

Problem

Since zigpy 2.4.0, ZHA no longer starts on a ConBee II (and presumably a RaspBee II) that was previously used with zigpy ≤ 2.3. Setup fails on every retry with:

Failed setup, will retry: No available endpoint slots exist: {0: SimpleDescriptor(endpoint=1, profile=260, device_type=1024, device_version=0, input_clusters=[0, 6, 10, 25, 37, 1281], output_clusters=[1, 32, 1280, 1282]), 1: SimpleDescriptor(endpoint=2, profile=49246, device_type=2080, device_version=0, input_clusters=[0], output_clusters=[])}

Reported in home-assistant/core#189366 on HA 2026.11.0b0 (zha 2.4.1, zigpy 2.4.1, zigpy-deconz 1.1.0). Practically every existing ConBee II / RaspBee II ZHA user will hit this when upgrading to 2026.11.

Cause

Reproduced with a mocked slot table in this repo's test fixtures (origin/dev + zigpy 2.4.1):

Slot table Result
2 slots already holding zigpy's endpoint 1 and 2 (any stick used with zigpy ≤ 2.3) ValueError on every startup
2 slots with other contents (fresh stick, or previously used by the deCONZ app) No error, but 242 overwrites the ZLL endpoint 2 slot, and every startup rewrites that slot twice (2, then 242)
3 slots Works

The second row is a separate bookkeeping bug: the write path never adds target_index to self._written_endpoints (only the "already registered" path does, application.py:519), so a later registration can pick a slot that was just written.

Who is affected

Only the contents of the two slots at the first zigpy 2.4 start matter; forming or resetting a network doesn't touch them.

  • Fails: sticks last run with zigpy 2.1–2.3, whose slots hold exactly zigpy's endpoint 1 and 2 (endpoint 1 gained KeepAlive in 2.1.0). That's every existing ZHA ConBee II / RaspBee II install upgrading to HA 2026.11.
  • Works (with the ZLL endpoint silently replaced by 242): a fresh stick, one previously used with deCONZ or Z2M, one last run with zigpy 2.0, or three/four-slot firmware. This is why it doesn't show up in test setups. More detail in this comment.

Firmware

Updating the firmware doesn't help today. Stable ConBee II / RaspBee II firmware (0x26780700) has two slots; a four-slot ConBee II test build from 2022 (0x26790700) never shipped; and the new ConBee II beta 0x28010700 (2026-10-09) doesn't mention endpoints in its changelog, so its slot count needs testing. RaspBee II has no beta, and ConBee III is unknown. Sources in this comment.

Proposed fix

deCONZ and zigbee2mqtt both use the two slots for endpoint 1 (HA) and endpoint 242 (GP), with no ZLL endpoint. Code references are in this comment.

  1. zigpy-deconz: on two-slot firmware, do the same: give slot 1 to endpoint 242 instead of the ZLL endpoint 2. Keep registering endpoint 2 where a third slot exists, and record written slots in _written_endpoints. Nothing in zigpy, ZHA or the radio libraries sends from or relies on the coordinator's endpoint 2: send_packet collapses every source endpoint other than 242 to 1.
  2. zigpy (only for radios that can't host 242 at all): make the GP endpoint optional, and skip the sends that depend on it (proxy commissioning mode, pairing, GP response, the GP group subscription). Direct GPDF reception keeps working without the endpoint: the firmware's GP indications go through api.py straight into gp_packet_received().

Skipping 242 in add_endpoint with a warning is not safe on its own. zigpy sends from source endpoint 242 outside of GP pairing too: every permit() call goes through permit_gp() (zigpy/application.py:1850), including the permit(0) at the end of startup (zigpy/application.py:289), and that broadcasts a GP Proxy Commissioning Mode with src_ep=242 (zigpy/zgp/manager.py:616). Z2M hit exactly this: ConBee II firmware rejected the send from the unregistered endpoint with APS confirm status 0xA6 (Koenkk/zigbee2mqtt#27902; the reports were on 0x26580700 and 0x266b0700). In zigpy that becomes a DeliveryError from permit(0), which only tolerates MAC_CHANNEL_ACCESS_FAILURE, so startup would still fail on such firmware. (On 0x26780700 the send works, see puddly's hardware test in the comments, but incoming Green Power proxy frames to an unregistered 242 are dropped there.)

Fix

Also seen in the report

An AttributeError: 'NoneType' object has no attribute 'aps_data_request' from send_packet (application.py:579), raised while replying to an OTA Query Next Image. It looks like a side effect: an incoming frame was handled after the failed startup had already disconnected and cleared self._api. Separate from the endpoint problem, but worth a guard.

Activity

  1. zigpy-review-bot commented on Oct 28, 2026

    @zigpy-review-bot
    CollaboratorAuthor

    Follow-up: firmware versions, deCONZ and Z2M

    Firmware

    Sending from an unregistered endpoint

    Z2M hit exactly this in Koenkk/zigbee2mqtt#27902 (July 2025). zigbee-herdsman 2.5.x sent GP Proxy Commissioning Mode from endpoint 242 on permit join while 242 wasn't configured, and the firmware failed the APS-DATA.request with confirm status 0xA6. Every report there was on ConBee II firmware 0x26580700 or 0x266b0700. The changelog entry for 0x266d0700 (2021) reads "Support receiving and sending APS frames to and from endpoints which aren't registered". So on current firmware an unregistered 242 may work, but the firmware in those reports rejected it, and we can't assume every user has updated.

    In zigpy, that rejection would surface as a DeliveryError from the startup permit(0) → permit_gp(), which only tolerates MAC_CHANNEL_ACCESS_FAILURE, so startup would still fail on such firmware. A "skip 242 with a warning" fix therefore isn't safe on its own.

    What deCONZ and Z2M do with the two slots

    Both use slot 0 = HA endpoint 1, slot 1 = GP endpoint 242 (profile 0xA1E0, device 0x0064, client cluster 0x0021), and neither has a ZLL endpoint:

    zigpy only sends from endpoint 1 or 242 on deCONZ: send_packet collapses every other source endpoint to 1. Nothing in zigpy, ZHA or the radio libraries references the coordinator's endpoint 2.

    I've updated the proposed fix in the issue body accordingly: on two-slot firmware, give slot 1 to the GP endpoint 242 like deCONZ and Z2M, keep the ZLL endpoint 2 where a third slot exists, and fix the _written_endpoints bookkeeping.

  2. zigpy-review-bot commented on Oct 28, 2026

    @zigpy-review-bot
    CollaboratorAuthor

    Why it works on some setups and fails on others

    Whether startup fails depends only on what's in the two endpoint slots the first time zigpy 2.4 starts. Forming or resetting a network doesn't touch them (form_network, reset_network_info and write_network_info never write configure_endpoint), so a new network on an already-used stick fails the same way.

    • Fails: the slots hold exactly zigpy's endpoint 1 and endpoint 2 descriptors, i.e. the stick was last run with zigpy 2.1–2.3 (endpoint 1 gained KeepAlive in 2.1.0, so that's when the exact match starts). Both match the "already registered" branch, both slot indexes get marked as written, and 242 has nowhere to go. Nothing is written, so every retry fails identically. This is what ZHA not working with ConBee II home-assistant/core#189366 shows, and it's the path every existing ZHA ConBee II / RaspBee II user takes on upgrade.
    • Works: anything else in the slots: a stick fresh out of the box, one previously used with deCONZ or Z2M, one last run with zigpy 2.0, or firmware with three or four slots. Because of the write-path bookkeeping bug, the slot endpoint 2 was just written to isn't marked as written, so 242 overwrites it. Startup succeeds with {0: endpoint 1, 1: endpoint 242}, and every later startup works too, writing slot 1 twice (endpoint 2, then 242).

    So a test setup that started from deCONZ/Z2M or a freshly flashed stick never hits the error, while upgraded production installs always do.

    My post-merge review of #279 (comment) caught the second case (the ZLL endpoint being evicted on two-slot firmware) and called it "probably harmless, but accidental". It missed the first one: I only probed slot tables that didn't already match zigpy's descriptors, not the upgrade path.

  3. zigpy-review-bot commented on Oct 28, 2026

    @zigpy-review-bot
    CollaboratorAuthor

    Fix PRs

    • Fit the Green Power endpoint into two-slot firmware #281 (draft): registers all endpoints in one pass. On two-slot firmware, zigpy's ZLL endpoint 2 makes room for the Green Power endpoint 242 (the deCONZ/Z2M layout), endpoints already in a slot are kept, and only missing ones are written. An upgraded stick gets a single write (slot 1: 2 → 242), later starts write nothing. Firmware with more slots keeps endpoint 2.
    • Fail cleanly when sending while disconnected #282 (draft): send_packet raises a DeliveryError instead of the AttributeError traceback when the radio is already disconnected.

    What endpoint 2 was for

    • It started in zigpy-znp in 2020: Z-Stack only sends from a registered endpoint with a matching profile, so zigpy-znp registered a ZLL-profile endpoint (first 100, then 2) to send ZLL-profile frames from. In 2022, zigpy 0.47.0 moved endpoint registration into ControllerApplication.register_endpoints with endpoint 1 (HA) and endpoint 2 (ZLL) as defaults for every radio, which is how zigpy-deconz started writing endpoint 2. Before that, zigpy-deconz wrote no endpoints at all.
    • Since 2019 (Override ZLL profile id for replies. zigpy#235), zigpy sends everything to ZLL-profile devices with the HA profile, except LightLink commands below 0x40 (Touchlink commissioning, normally inter-PAN, which zigpy doesn't use). So ZLL-profile frames are practically never sent, and on deCONZ send_packet sends everything except ZDO and Green Power from endpoint 1 anyway.
    • For receiving it does matter: the firmware changelog says 0x266d0700 and later deliver frames for unregistered endpoints, but puddly's hardware test below (0x26780700) shows the firmware only delivers a frame when its destination endpoint is in a slot with a matching profile. Without endpoint 2, ZLL-profile (0xC05E) frames sent to the coordinator are dropped and the sender gets no APS ACK.
    • So the trade-off of Fit the Green Power endpoint into two-slot firmware #281 on two-slot sticks is losing incoming ZLL-profile frames instead of incoming Green Power proxy frames (the same trade-off deCONZ and Z2M make). Nothing on the sending side depends on endpoint 2.
    • Side note: on a stick that came from deCONZ, zigpy 0.47's endpoint 2 would have replaced deCONZ's Green Power endpoint in slot 1. Fit the Green Power endpoint into two-slot firmware #281 effectively puts it back.

    Testing #281 on Home Assistant OS

    Test build: zha-test-pr281.zip

    It's the ZHA integration from HA 2026.11.0b0, unchanged except that its manifest.json also installs zigpy-deconz from the #281 branch (plus the generated English translations, so the UI isn't missing text). It's meant for 2026.11.0b0; remove it before updating Home Assistant.

    1. Put the zha folder from the ZIP into custom_components in your config folder (the one with configuration.yaml; create custom_components if it doesn't exist):
      • Without a terminal: download the ZIP, unpack it, and copy the zha folder over, e.g. with the Samba share app.
      • With the Terminal & SSH app:
        mkdir -p /homeassistant/custom_components
        curl -fsSL -o /tmp/zha-test-pr281.zip https://github.com/user-attachments/files/34105264/zha-test-pr281.zip
        unzip -o /tmp/zha-test-pr281.zip -d /homeassistant/custom_components/
      The result should be custom_components/zha/manifest.json, not custom_components/zha/zha/….
    2. Restart Home Assistant (a full restart, not a reload). The log warns that a custom integration zha is loaded, which is expected. HA reinstalls the zigpy-deconz test build on every start, so GitHub has to be reachable; if that install fails, ZHA won't load.
    3. ZHA should start. If you can, enable debug logging for zigpy_deconz and share the Got endpoint slots / Writing ... to slot lines plus the ConBee firmware version.

    To go back: delete custom_components/zha and restart. The new endpoint layout stays on the stick and works with the released version too. The zigpy-deconz test build stays installed until the next HA update replaces the container.

    The same files are also on the branch zigpy-review-bot/zigpy-deconz@zigpy-bot/zha-test-pr281, e.g. for git clone --depth 1 -b zigpy-bot/zha-test-pr281 https://github.com/zigpy-review-bot/zigpy-deconz.git and copying custom_components/zha over.

    How the test build was made and checked
    • homeassistant/components/zha from the 2026.11.0b0 tag, translations/en.json generated with python -m script.translations develop --integration zha, and in manifest.json a version, a changed name and the extra requirement zigpy-deconz @ git+https://github.com/zigpy/zigpy-deconz.git@zigpy-bot/gp-endpoint-slots. Nothing else differs from the tag.
    • zha 2.4.1 pins zigpy-deconz==1.1.0 and the branch build reports 1.1.0.post2+git…, but HA installs and checks each manifest requirement separately, so the extra line only replaces zigpy-deconz, and nothing checks zha's own pin at runtime.
    • The ZIP is a git archive of custom_components on that branch (SHA-256 ddd8f213d0d8d19e9cbfb7cb2eb7114457cb24e885367ac13fd831029bf1bf5f).
    • Loaded with Home Assistant 2026.11.0b0's own loader and requirements manager from a fresh config: the custom zha is picked up, the requirements install, zigpy-deconz 1.1.0.post2+git.7d9b369e with the fix replaces 1.1.0 while zha, zha-quirks and zigpy stay at 2.4.1 / 2.4.0 / 2.4.1, and the component and config flow import. A second start only reinstalls the zigpy-deconz build. Not run against a real ConBee here.
  4. puddly commented on Oct 28, 2026

    @puddly
    Member

    I ran some tests on a two-device network with Ziggurat and a Conbee II...

    The Conbee has the unfortunate restriction of filtering incoming packets by both profile and endpoint ID.

    I think dropping ZLL is the the most practical solution here, unfortunately. A better solution would be to switch to a different coordinator 😄.

    AI Summary

    Tested on ConBee II (firmware 0x26780700, 2 endpoint slots): loopback, plus over the air from a second radio joined as a router.

    • The firmware answers Active_EP_req and Simple_Desc_req from the slot table.
    • A frame reaches the host only if its destination endpoint is in a slot with a matching profile. ZLL profile 0xC05E sent to HA endpoint 1 is dropped. Clusters are not checked.
    • Dropped frames get no APS ACK. The sender sees a delivery failure.
    • Source endpoints are not checked on transmit. permit_gp() from an unregistered 242 works.
    Frame sent to the coordinator Slot 1 = GP 242 Slot 1 = ZLL 2
    ep 1, 0x0104 delivered delivered
    ep 1, 0xC05E dropped dropped
    ep 2, 0xC05E dropped delivered
    ep 242, 0xA1E0 delivered dropped

    With 2 slots, one of zigpy's 3 endpoints is always missing:

    • Without 242, GP frames from proxies fail. Direct GPDFs still arrive by the firmware's GP command.
    • Without ZLL endpoint 2, all 0xC05E frames fail.

    deCONZ registers HA endpoint 1 in slot 0 and GP 242 in slot 1. It has no ZLL endpoint and sends ZLL requests from endpoint 1.

    Aside: the firmware sends its own ZCL Default Response (SUCCESS) to every ZCL frame it delivers, even Read Attributes. The host then answers too.

  5. puddly commented on Oct 28, 2026

    @puddly
    Member

    A third solution would be to drop GP support from the Conbee II, to avoid regressing existing users relying on ZLL packet parsing.

    @zigpy-review-bot Check quirks to see if any devices rely on the coordinator being able to receive ZLL profile packets.

  6. zigpy-review-bot commented on Oct 28, 2026

    @zigpy-review-bot
    CollaboratorAuthor

    Do any quirks or ZHA features need incoming ZLL (0xC05E) frames at the coordinator?

    No. I found nothing in zha-device-handlers (dev, b9cf6214), zha or zigpy that depends on the coordinator receiving ZLL-profile packets.

    • Nothing sends a ZLL-profile request to a device that would get a reply back. zigpy only uses the ZLL profile on the wire for LightLink commands below 0x40 (touchlink commissioning: scan, identify, reset to factory new, …), see Endpoint.request in zigpy/endpoint.py. Everything else to a ZLL endpoint goes out as ZHA profile, including get_group_identifiers (0x41), the only LightLink command ZHA sends (LightLinkGroupJoin in zha/application/platforms/virtual.py). Its reply therefore comes back as ZHA profile to endpoint 1. Neither zha nor the quirks send any touchlink commissioning command, so nothing ever waits for a ZLL-profile reply.
    • Quirks only deal with ZLL-profile endpoints on the device side. The signatures that declare profile 0xC05E are: IKEA TRADFRI remote / on-off switch / motion sensor, Philips Hue dimmer (first gen) and motion sensor (SML001/002), Lutron LZL4B-WH-L01 remote, Feibit FNB56 switches, plus lights and plugs (Gledopto, OSRAM, innr, iluminize, …). The lights and plugs only receive commands, which the coordinator sends as ZHA profile. For the remotes and sensors, the coordinator-side traffic is bindings: zigpy always binds to coordinator endpoint 1 (Application.get_endpoint_id returns DEFAULT_ENDPOINT_ID), never endpoint 2. By your test table a 0xC05E frame sent to endpoint 1 is dropped whichever layout is used, so those devices are no worse off under either option. Whether a given remote tags its button commands with the ZLL or the ZHA profile is something only a sniffer capture of the device can answer; I haven't verified that for any of them.
    • The only consumer of coordinator endpoint 2 is zigpy's own registration. The ZLL controller endpoint is added in zigpy/application.py and registered by the radio libraries. No quirk, ZHA entity or radio library sends from or targets it.

    So I don't see a quirk-level reason to keep the ZLL endpoint on two-slot sticks. The one case I can't rule out is a third-party or unquirked device that sends an unsolicited ZLL-profile frame to the coordinator (e.g. a touchlink-style scan response), which needs a sniffer capture rather than a code search.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    regressionWorked in an earlier release

    Type

    Fields

    Priority

    None yet

    Size

    None yet

    Area

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions