Skip to content

Simplicity SDK 2026.6.0 - #205

Merged
puddly merged 30 commits into
NabuCasa:mainfrom
puddly:puddly/ssdk-v2025.12.3-test
Jul 15, 2026
Merged

Simplicity SDK 2026.6.0#205
puddly merged 30 commits into
NabuCasa:mainfrom
puddly:puddly/ssdk-v2025.12.3-test

Conversation

@puddly

@puddly puddly commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

Supersedes and builds upon #182.

This PR bumps all firmwares to the latest release of Simplicity SDK: Zigbee, Z-Wave, and Thread. We retain a bit of backwards/forwards compatibility code to allow our firmware extensions to still be used with Gecko SDK but these can be removed later.

@puddly

puddly commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator Author

CC @burmistrzak. I've also added the Z2M config tweaks in 21db05c (this PR).

@zigpy-review-bot

Copy link
Copy Markdown

Reviewed against the local Simplicity SDK 2025.12.3 headers, the Nerivec/silabs-firmware-builder fork, and the bellows host-side PRs. TL;DR: clean SDK migration, no correctness blockers, config is well-grounded in an existing fork — one open config question (bdb_3dot1_trust_center_extension). Details below 👇

Full review — click to expand

Scope

Draft PR, all firmware targets build green (SkyConnect / Yellow / ZBT-2 NCP + router + RCP, ZWA-2, bootloaders). Gecko SDK 4.5.0 → Simplicity SDK 2025.12.3 for Zigbee NCP; SSDK bump for Thread RCP and Z-Wave. Supersedes #182. Verified each behavioral change rather than just reading the diff.

Behavioral changes — all grounded in the Nerivec fork

The Test: use Z2M config tweaks commit ports config from the Nerivec fork (shipping on SSDK 2025.12.2). I diffed them value-for-value:

  • Router security type SLI_ZIGBEE_NETWORK_SECURITY_TYPE_3_0_4_0 (src/zigbee_router/zigbee_router.slcp). Valid enum (0x06, "ZigBee 4.0 Security" — confirmed in zigbee/app/framework/common/config/zigbee_device_config.h; SDK default is 3_0). Pairs with the new R23 components (zigbee_dynamic_commissioning, zigbee_bdb_3dot1_rejoin_algorithm). Identical to Nerivec. The ? in the commit message notwithstanding, this is established prior art, not a new experiment — though a "router joins a 3.0 coordinator OK" smoke test before un-drafting would still be reassuring.

  • TC keepalive cadence — base 1 min, jitter 30 s (zigbee_router.slcp). More aggressive than the SDK defaults (2 min / 60 s, per trust-center-keepalive-config.h), but identical to Nerivec, so intentional.

  • Network steering autostart 0 + manual steering via commissioning_retry_event_handleridentical to Nerivec.

  • NCP table-size increases — match Nerivec's per-device-family tuning exactly:

    Param (EFR32MG24 / ZBT-2) Nerivec (xg24) This PR (zbt2)
    SOURCE_ROUTE_TABLE_SIZE 254 254
    ADDRESS_TABLE_SIZE 128 128
    APS_UNICAST_MESSAGE_COUNT 128 128
    MAX_END_DEVICE_CHILDREN 64 64
    APS_DUPLICATE_REJECTION_MAX_ENTRIES 64 64
    PACKET_BUFFER_HEAP_SIZE HUGE HUGE

    (SkyConnect/EFR32MG21 also matches Nerivec's xg21 block: SR 200, addr 32, APS 64, children 32.) The EMBER_PACKET_BUFFER_COUNT: 255 count-based model is replaced by the heap-based SL_ZIGBEE_PACKET_BUFFER_HEAP_SIZE (LARGE from the .slcp, HUGE override for ZBT-2) — same as Nerivec.

Host side — covered by bellows

  • XNCP reply status byte now carries truncated sl_status_t codes instead of Ember codes (success is 0x00 in both). Handled by bellows #707 ("Extend EmberStatussl_Status mapping").
  • EmberZNet 7.5 → 9.0 / new EZSP version: bellows #704 (EZSP v18) + #725 ("Add support for Simplicity SDK 2025.12.3").

One open question

src/zigbee_router/zigbee_router.slcp enables zigbee_bdb_3dot1_trust_center_extension, whereas the Nerivec reference config deliberately keeps it commented out (# - id: zigbee_bdb_3dot1_trust_center_extension). Every other router component matches. Is enabling the TC extension on a (non-TC) router intentional R23 behavior, or an inadvertent inclusion? This is the only place the PR diverges from the proven config.

Confirmed good — no action

  • xncp_types.h dual-SDK shim — clean #ifdef STACK_TYPES_HEADER layer aliasing xncp_* types/macros to SSDK or Gecko names, so the same .c files build against both SDKs.
  • Blue-pulse fix (zbt2_router_callbacks.c) — verified genuine: the search pattern omitted brightness_min/brightness_max, defaulting them to 0, and led_manager.c's min_b + ((max_b-min_b)*brightness/65535) collapses to 0 → LED dark. 6554/65535 matches led_effects.c.
  • create_gbl.pysl_zigbee_versionemberVersion fallback plus the symbols is None guard in _jump_to_elf_symbol (pyelftools returns None, not [], on a miss); OpenThread header path now probes both old and new SSDK layouts.
  • main.c — standard SSDK sl_main_init/sl_main_kernel_start template (incl. the SDK's own MSLA→Zlib header).
  • (void)param; casts and -Wno-error=unused-function are just satisfying -Werror -Wextra.

Verdict: no correctness blockers; the config is well-grounded in the Nerivec fork. The single substantive reviewer question is the bdb_3dot1_trust_center_extension divergence.

@burmistrzak

Copy link
Copy Markdown
Contributor

I've also added the Z2M config tweaks in 21db05c (this PR).

Looking good!
Let me know if you want me to give it a spin. ✌️

I assume you searched by hand for all these Z2M optimizations?

@puddly

puddly commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator Author

Let me know if you want me to give it a spin. ✌️

Sure, go for it!

I assume you searched by hand for all these Z2M optimizations?

There's only one spot to look so it's not much of a search 😅. But you can always diff the intermediate SLCP + configs as a last resort, since they will contain the final config for the generated build tree.

@burmistrzak

Copy link
Copy Markdown
Contributor

Sure, go for it!

Sweet! Finally blinking lights and a fresh SDK! 😄

There's only one spot to look so it's not much of a search 😅. But you can always diff the intermediate SLCP + configs as a last resort, since they will contain the final config for the generated build tree.

Duh! Checking the SLCP again, I totally misread the various conditions @Nerivec uses...
Makes perfect sense now. 🤝

Comment thread src/zigbee_router/zigbee_router.slcp Outdated
- id: zigbee_trust_center_keepalive
- id: zigbee_poll_control_client
- id: zigbee_bdb_3dot1_rejoin_algorithm
- id: zigbee_bdb_3dot1_trust_center_extension

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to Silabs, zigbee_bdb_3dot1_trust_center_extension should be disabled here.

A new BDB 3.1 Trust Center Extension component has been added to perform necessary Zigbee 4.0 behavior for trust centers. This component handles auto-discovery of devices that join the network. The component will send binding requests to any end devices that support the Poll Control cluster, as mandated by the BDB specifications. This component is only necessary for Zigbee 4.0 trust centers or coordinators.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zigbee_router.slcp is essentially just the zigbee_z4_light.slcp project distributed with Simplicity SDK, just with some extensions added in. In earlier SDKs we based it on zigbee_z3_light.slcp but I've migrated it to the Zigbee 4.0 example router project in this PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that zigbee_z4_light.slcp example is a bit odd because it's a fancy dual role device.

The light acts as a dual role coordinator/router, which can either form a Zigbee 4.0 network or join (as a router) a Zigbee 4.0 network.

So for a router that simply joins an existing network, said component is not necessary.

See https://github.com/Nerivec/silabs-firmware-builder/blob/bfabc37808b3b7386a66793d7076730065c84b28/src/zigbee_router/zigbee_router.slcp#L116

@Nerivec

Nerivec commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Something's not right with 2015.12.x Z4 support for NCP.
The SDK project for NCP hasn't changed, not even zigbee_r23_support was added. I don't think it is complete.

In my initial testing (with ZC having zigbee_r23_support + zigbee_dynamic_commissioning components -same as default zigbeed project basically-, and full Z4 on ZR), I couldn't get the ZR to join the ZC (seems the ZC isn't properly replying to the ZR, missing a code path). If you try to join a Z3 device, no issue (i.e. can use the ZC with current devices without problem, just not with a new Z4 device...).
It all rather points to Z4 support not being complete. Didn't have enough time to dig further though.

Also, can't add the new zigbee_bdb_3dot1_trust_center_extension to NCP, as it pulls in af.h (not available in NCP) - guess that might need doing on the app side (from what I glanced, it's the poll stuff mostly).

@zigpy-review-bot

zigpy-review-bot commented Jun 4, 2026

Copy link
Copy Markdown

Thanks Nerivec — "not complete" holds up. tl;dr: the NCP is still R22, R23 DLK is unimplemented host-side in both bellows and Z2M/herdsman, and the join failure looks like a disabled-in-production TC path that DLK can't recover from once it's underway. 👇

Full breakdown — click to expand

⚠️ Grain of salt: a fair bit of the join-failure analysis below comes from tracing optimized release binaries plus reading the R23.2 spec, so treat it as a strong working theory to double-check (ideally against a packet capture / on real hardware), not gospel. The config/host-side facts are solid; the exact runtime trigger is the inferred part.

Verified:

  • NCP is R22 by construction — still zigbee_r22_support, no zigbee_r23_support / zigbee_dynamic_commissioning, so the coordinator firmware never attempts R23/DLK.
  • zigbee_bdb_3dot1_trust_center_extension — already covered in burmistrzak's inline thread on zigbee_router.slcp, just confirming it from the binary side: the component #includes app/framework/include/af.h, requires zigbee_device_query_service, and its slcc is marked "only necessary for coordinator applications." Since this firmware is a join-only router (DEVICE_TYPE_ROUTER, no network_creator), it's unused here — agreed it should be disabled (as the Nerivec fork does); it only came along from the zigbee_z4_light dual-role base project.
  • Host side — same gap in both ecosystems. bellows and Z2M/herdsman both support install codes, but only the classic path (install code → AES-MMO → transient link key → encrypts the network-key transport). Neither implements R23 DLK (install code as SPEKE passphrase): bellows has no DLK/dynamic-commissioning code (#725 is just EZSP plumbing); herdsman v10.3.0 advertises supportedKeyNegotiationMethods: 0, no SPEKE/Curve25519, R23 ZDO frames in src/zspec/ defined-but-never-invoked (Z2M R23 issue #17702 closed/not-planned).

Join failure — from tracing the commissioning + BDB TC-link-key state machines. Per R23.2, DLK is optional/negotiated (§4.9.3: Key Request is the universal SHALL; DLK is MAY), with a spec-guaranteed classic fallback. The binary bears this out, with one important asymmetry:

  • DLK never launches → joiner falls back to classic. sli_zigbee_request_key_process_node_descriptor_response engages DLK only when the TC's node descriptor advertises R23 + a key-negotiation TLV; otherwise (or if the local launch fails) it falls straight through to the classic request_link_key path. So a non-R23 / non-advertising TC just gets a classic join.
  • DLK launches then stalls → no fallback. The state-4 (DLK-in-progress) timeout path ends in a key-establishment failure with no classic retry. And the TC can't complete a no-install-code DLK: in zdo_dlk_negotiation_clone_psk, the well-known-key (0xff) branch copies the spec's apscWellknownPSK (ZigBeeAlliance18) only if zdo_dlk_allow_anonymous_secret is set — a .bss flag (default 0) written only by a Golden-Unit cert-test hook, never by normal code (it mirrors the TC's requireInstallCodesOrPresetPassphrase policy). At the default it returns NOT_SUPPORTED, so the TC handler fails closed without admitting the device. Install-code joins (secret source 1) have a real path; the anonymous one doesn't.

So a Z4 joiner with no install code launches DLK, the TC dead-ends on the disabled anonymous path, and the joiner times out with no recovery — which fits "the ZC isn't replying, missing a code path." Z3 devices are unaffected (no DLK TLV → classic path directly). Caveat: whether your TC times out vs. sends an explicit reject, and whether an app-layer steering retry exists above the stack lib, needs a packet capture — but the gated anonymous dead-end and the no-fallback-once-DLK-is-underway are both in the binary.

Net: the missing piece is host-side R23 DLK commissioning (absent in both bellows and Z2M) plus an enabled secret path on the TC (install code, or the gated anonymous one) — not the firmware bump. NETWORK_SECURITY_TYPE_4_0 is safe for existing networks: a Z4 device that can't launch DLK just does a classic join.

@burmistrzak

Copy link
Copy Markdown
Contributor

Something's not right with 2015.12.x Z4 support for NCP. The SDK project for NCP hasn't changed, not even zigbee_r23_support was added. I don't think it is complete.

Yes, that's my impression as well.
But their next SDK release should be just around the corner. Hopefully we'll get an updated NCP example this time... 🤞

I don't think missing Z4 support on NCP is a deal breaker, at least for the moment. Bugfixes for Z3 and ZGP are way more important, IMHO.
Btw. there isn't even a Zigbee 4.0 section on the CSA certification site yet. 😛

Comment on lines 86 to 96
//------------------------------------------------------------------------------
// Multicast override (XNCP_FEATURE_MEMBER_OF_ALL_GROUPS)
//------------------------------------------------------------------------------

bool __wrap_sli_zigbee_am_multicast_member(EmberMulticastId multicastId)
bool __wrap_sli_zigbee_am_multicast_member(xncp_multicast_id_t multicastId)
{
(void)multicastId;
// Ignore all binding and multicast table logic, we want all group packets
return true;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@puddly I believe that's the modification you were talking about?

At the moment, Z2M doesn't understand this behavior and @Nerivec is (rightfully) a bit hesitant about adding support.

Is there a good reason for overriding the default?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit annoying to have to pre-register groups to appease the firmware. If you don't know the group ID in advance, you won't receive the packet. TI, deCONZ, etc. firmwares don't require this rigamarole and (rightfully) pass through all received packets.

I spoke to SiLabs about this and they're not interested in adding a wildcard value so this is the hack that we use. It can coexist with the existing multicast registration system if we increase the table size but it's simpler to just skip it outright, since it's unnecessary with this patch.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I spoke to SiLabs about this and they're not interested in adding a wildcard value so this is the hack that we use.

Welp... Can't wait for the day we're able to move the entire Zigbee stack to the host and go full RCP. 🙏

It can coexist with the existing multicast registration system if we increase the table size but it's simpler to just skip it outright, since it's unnecessary with this patch.

Does ZHA expect said patch is always present or do you check before skipping registration?

@TheJulianJES TheJulianJES Jun 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's checked for and skipped if the feature is supported by the coordinator: https://github.com/zigpy/bellows/blob/cd3378f08b2a1275b02244c78079148faec4fe4c/bellows/zigbee/application.py#L248-L252

For other firmware builds, zigpy and bellows register the groups where we expect to receive packets.

@burmistrzak burmistrzak Jun 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... I can now see where @Nerivec's hesitation was coming from:
While the ember driver in zigbee-herdsman has support for xncp, it's mostly uncharted territory. 😬

Implementing these custom xncp commands would however improve compatibility with stock NC firmware and free users from having to flash their new ZBT-2 with different firmware to get the best Z2M experience.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think compatibility is really a concern here, the two firmware builds are functionally identical save for a few table sizes. The only reason compatibility was even brought up in the past was because our multicast table was unintentionally a little small, something that is easily fixable in a future update. The tweak I added to disable firmware multicast filtering coexists cleanly with applications that still expect to manage the multicast table manually.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so there wouldn't be any tangible benefits (for Z2M users) if we were to support your xncp extensions on our end?

It's fine by me, just wanted to make sure the the out-of-the-box experience is seamless, regardless of Zigbee implementation. 😊

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only as tangible as you make them 😄.

  • I use get_mfg_token_override to override the board name and model of adapters by specifying XNCP_MFG_BOARD_NAME in the manifest YAML. MG21 Sonoff adapters, for example, didn't program a board or manufacturer name in USERDATA in the factory. With this, you can just set it within the config and provide user-friendly strings via firmware.
  • get_build_string provides a way to include a build string, to show when a firmware was built. We show it as part of the version string.
  • get_flow_control_type can be used to detect mismatches between the flow control expected by the firmware and the host-side serial config.
  • get_chip_info tells you RAM size and the exact part number for the chip. ZHA uses this to set different software concurrency limits for MG21 and MG24/MG26 adapters.
  • set_route_table_entry and get_route_table_entry are used by ZHA to back up and restore the route table at runtime, allowing for quickly restoring the Zigbee network state and bypassing the firmware route discovery storm on startup.
  • I'll eventually be adding a more self-contained packet sending command that will help reduce the number of serial round trips, speeding things up.

I very deliberately wrote the SiLabs firmware builder for the community. It's 100% generic and uses simple YAML config files specifically to allow the Zigbee community to generate good firmware for the endlessly growing list of adapters on the market. I build all of the Nabu Casa adapter firmwares out in the open for this very reason and keep all of the NC hardware-specific stuff isolated from the "core".

Personally, "stock" firmware for me doesn't exist, especially once you have control over the build. I added these XNCP commands to deal with SiLabs bugs and limitations and I think all of the extensions that we have are pretty useful. IMO, Z2M would find them as beneficial as ZHA does and I would greatly prefer to use an API that both projects can benefit from instead of fracturing the ecosystem.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only as tangible as you make them 😄.

🤓🫶

  • set_route_table_entry and get_route_table_entry are used by ZHA to back up and restore the route table at runtime, allowing for quickly restoring the Zigbee network state and bypassing the firmware route discovery storm on startup.

This is actually pretty huge! 🤩

Personally, "stock" firmware for me doesn't exist, especially once you have control over the build. I added these XNCP commands to deal with SiLabs bugs and limitations and I think all of the extensions that we have are pretty useful. IMO, Z2M would find them as beneficial as ZHA does and I would greatly prefer to use an API that both projects can benefit from instead of fracturing the ecosystem.

You're right, a minimal firmware is probably "worse" in some aspects because it doesn't include those mitigations. 😬
I'm in favor of implementing support for your XNCP extensions in ZH. @Nerivec Thoughts?

@burmistrzak

Copy link
Copy Markdown
Contributor

Ok, compiling the firmware via Docker worked well and I was able to successfully upgrade my ZBT-2 via ember-zli to 9.0.2 🥳

However, the LED still stays off. Is that expected?

I haven't cleared+restored NVM yet. Bootloader is at v2.04.03 👀

@puddly

puddly commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator Author

However, the LED still stays off. Is that expected?

Yeah. For Zigbee, the LED pulses only as long as no network is formed. Once a network is formed, it stops and the LED turns off.

OpenThread has no persistent state so the LED will start pulsing once a network stops running and then stop again once the network starts running.


LED state is a bit hard to get right. It's been a low priority for the time being but I think we would need some NV3 storage mechanism (similar to the ZCL one) shared across both OpenThread and Zigbee firmware, along with an XNCP command (and whatever the OpenThread RCP equivalent is) to control the behavior. There already are commands to interact with the LEDs and the accelerometer but these won't persist if the coordinator is rebooted.

@burmistrzak

Copy link
Copy Markdown
Contributor

Yeah. For Zigbee, the LED pulses only as long as no network is formed. Once a network is formed, it stops and the LED turns off.

Alright, thanks for confirming!

LED state is a bit hard to get right. It's been a low priority for the time being but I think we would need some NV3 storage mechanism (similar to the ZCL one) shared across both OpenThread and Zigbee firmware, along with an XNCP command (and whatever the OpenThread RCP equivalent is) to control the behavior. There already are commands to interact with the LEDs and the accelerometer but these won't persist if the coordinator is rebooted.

Sounds interesting, for sure.
One fun thing I had in mind was using the LED to signal IAS states or critical battery levels.

Also, here's a quick comparison between firmware builds (diff only):

Variable Nerivec (SDK 2025.6.2) puddly (SDK 2025.12.3)
CONFIG.PACKET_BUFFER_HEAP_SIZE 8192 16384
CONFIG.ADDRESS_TABLE_SIZE 64 128
CONFIG.ROUTE_TABLE_SIZE 16 254
CONFIG.KEY_TABLE_SIZE 1 12
CONFIG.BROADCAST_TABLE_SIZE 30 64
CONFIG.MAC_FILTER_TABLE_SIZE 2 15
CONFIG.NEW_BROADCAST_ENTRY_THRESHOLD 24 58

Comment on lines 70 to 72
@@ -71,68 +71,79 @@ c_defines:
# should not use hardware flow control.
XNCP_FLOW_CONTROL_TYPE: usartHwFlowControlNone

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@puddly Wait, there's no RTS/CTS between the host (Z2M/ZHA) and the ZBT-2? 😳

@tube0013 tube0013 Jun 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ZBT-2 uses an ESP32-S3 for the uart to usb connection. and the nature of that means the HW Flow is between the EFR32 and the ESP32 as the note says. It's similar to a network connected coordinator in that way - I use HW Flow for the mg24 devices I sell, but the socket:// is blind to that.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, so I guess the Z2M docs are wrong then? 😬

@burmistrzak

Copy link
Copy Markdown
Contributor

Quick status update:

  • No anomalies observed so far.
  • Pairing new devices via install code works.
  • ZGP switches via proxies work as well.
  • Disabling RTS/CTS improved reliability..?

Anything specific for me to test? ☺️

@puddly

puddly commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

Disabling RTS/CTS improved reliability..?

Strange. In what way?

@burmistrzak burmistrzak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You basically made the exact same changes I did on my local fork! Only minor differences to be found. 😁

Comment thread manifests/nabucasa/zbt2/zbt2_zigbee_ncp.yaml Outdated
Comment thread manifests/nabucasa/zbt2/zbt2_zigbee_ncp.yaml Outdated
Comment thread src/zigbee_ncp/zigbee_ncp.slcp Outdated
EMBER_SOURCE_ROUTE_TABLE_SIZE: 200
SL_ZIGBEE_APS_UNICAST_MESSAGE_COUNT: 128
SL_ZIGBEE_BINDING_TABLE_SIZE: 32
SL_ZIGBEE_BROADCAST_TABLE_SIZE: 64

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
SL_ZIGBEE_BROADCAST_TABLE_SIZE: 64
SL_ZIGBEE_BROADCAST_TABLE_SIZE: 32

Z2M default is 30, but I guess that's a matter of taste?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I bumped it to allow more multicasts/group commands to fly around, since they're broadcasts. Z-Stack effectively disables this check so this tries to do the same.

@MattWestb MattWestb Jul 1, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its one complete braking change and is making all commercials devices blocking broadcast.

SL_ZIGBEE_DEFAULT_BROADCAST_TABLE_SIZE 15
The maximum number of broadcasts during a single broadcast timeout period. The minimum and default value is 15 and can only be changed on compatible Ember stacks. Be very careful when changing the broadcast table size as it affects timing of the broadcasts and the number of possible broadcasts. Additionally, this value must be universal for all devices in the network. Otherwise, a single router can overwhelm all its neighbors with more broadcasts than they can support. In general, this value should be left alone.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for the coordinator device there isn't a good solution to group commands being spammed...

  • If you 100% follow the spec, you really quickly run into the "network busy" error when the broadcast table gets full. This appears for some users really quickly even during normal use, especially when you have to send multiple group commands to control lights (i.e. one for color and one for brightness). Maybe it's a SiLabs bug?
  • If you relax the filtering, you trade the "network busy" error for the network actually being busy due to a broadcast storm.

Both end up looking the same to an end user (devices don't work) and we had more users reporting issues with 15 than with 64.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Z-Stack already has the limit basically disabled completely since at least 6 or 7 years: https://github.com/Koenkk/Z-Stack-firmware/blob/58dda33d834ef09132427cb41721d7d68e11a7a6/coordinator/Z-Stack_3.x.0/firmware.patch#L171-L173

I've been using a really high broadcast table size for years with SiLabs as well. Only the increased limit actually makes Zigbee groups somewhat useable in HA when changing the color by sliding it along the UI, for example.

There was a long discussion in the following HA Core issue and for most people, the increased broadcast table size was a positive improvement: home-assistant/core#86411 (comment)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have making one RP for Bellows zigpy/bellows#728.
Its up to puddly if hi like it or not and if hi like do the same with TI coordinators in zigpy.
The bad side is that is on the boarder to braking changes but as it was not one standard settings i think it shall being OK or it must being in (Z)HA braking changes.
My Standard ZHA config:

    ezsp_config:
      CONFIG_MAX_END_DEVICE_CHILDREN: 0
      CONFIG_BROADCAST_TABLE_SIZE: 15 

No direct children then they is only getting problems then the NCP is not replaying / having problems and building one star network if having hotting the TX power (as Decons is doing) or old Aqara sensors that can jumping.

compliant devices? All Zigbee commercial sold router devices shall have 15 in router table as long they is HA1.2 (i think its Zigbee PRO / 2007) or newer and user can overriding the system default in HA config (for ZHA) for fixing it if like broadcast storming the 15.4 and locking the mesh.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it can being good going out with one recommendation for users that Zigbee Groups shall being used for On/Off/Toggle and scene switching (and Open/Close for my blinds) and not as adaptive lighting and for the last using HA groups that using unicast.
I think then its getting stable and very fast light switching and no routing problems but if having routing problems the user is knowing it with there spamming unicasts.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No direct children then they is only getting problems then the NCP is not replaying / having problems and building one star network if having hotting the TX power (as Decons is doing) or old Aqara sensors that can jumping.

@MattWestb Yea, star topology should be avoided. Especially with high-gain adapters.

compliant devices? All Zigbee commercial sold router devices shall have 15 in router table as long they is HA1.2 (i think its Zigbee PRO / 2007) or newer and user can overriding the system default in HA config (for ZHA) for fixing it if like broadcast storming the 15.4 and locking the mesh.

The problem is that nothing prevents manufacturers from selling non-compliant devices with all sorts of weird defaults...
Certification is mandatory if you want to advertise your product as Zigbee Certified. However, a lot of popular brands (e.g. Bosch, Lumi) use the Zigbee protocol but haven't undergone certification.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhat related: Silabs published quite interesting data about their large test network.

@MattWestb MattWestb Jul 8, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They have one old PDF but its good if like see how its working with real testing
https://www.silabs.com/documents/login/application-notes/an1138-zigbee-mesh-network-performance.pdf and interesting conclusions from the tests.
Also look on the AN1142 liked in the PDF for getting more of difference between Zigee/Thrad and BTMesh.

Also i have seen Espressif bringing up one thread network from factory state in under 2 minutes with 300 devices.
https://www.youtube.com/watch?v=0WXcu_r_lvQ

Edit: My link its the original an1138 that is updated in @burmistrzak post to EZSP 9.1

Edit 2:
They is describing the new broadcast and how its working with old devices (3 broadcast and out).
For Zigbee devices, a multicast message is forward- ed by a device only after a jitter of up to 64 milliseconds. Unless a repeat of a multicast message has been heard from all known neighbors, each node will transmit two repeats of the multicast message spaced by 500ms. This 500ms gap is observed in the performance data results presented in this AN.
https://docs.silabs.com/zigbee/9.1.0/zigbee-mesh-network-performance/01-introduction-and-background

Comment thread src/zigbee_ncp/extension/xncp_common_extension/config/xncp_config.h
@burmistrzak

Copy link
Copy Markdown
Contributor

Yeah, we swapped from USART to EUSART for our Thread firmwares last year because of a SiLabs bug triggered by receiving IEEE 802.15.4 beacon requests 😅. PR #162 has the background.

@puddly Great tidbits! Thanks for sharing.

I'll have to check again why I didn't swap over the Zigbee firmware at the time but I think it's because Gecko SDK didn't support the EUSART peripheral? Both USART and EUSART are used for pretty low-speed UART purposes at the end of the day and should shuttle bytes more or less the same (except in that one weird Thread scenario...). It shouldn't make a difference for Zigbee but I've swapped over the ZBT-2 firmware in the most recent commit.

But it's enhanced USART, so it surly must be better in every aspect. 😜
Jokes aside, it's probably good to use the same serial transport for Thread and Zigbee.

This SDK release was a lot more troublesome than expected for native ARM64 compilation but I'm happy to say that native building on ARM64 hosts continues to be possible! Arm's official GCC toolchain for ARM64 was misconfigured and is lacking ZSTD support (unlike x86) so none of the pre-compiled SiLabs SDK libraries can be linked. I've worked around this by compiling a portion of the toolchain from scratch within the Docker container. The generated GBL files remain byte-for-byte identical across architectures.

Fantastic work! 🙌

Comment thread src/zigbee_ncp/extension/xncp_common_extension/config/xncp_config.h
Co-authored-by: burmistrzak <61958704+burmistrzak@users.noreply.github.com>

@burmistrzak burmistrzak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With these patches applied, we should be on par with Nerivec's fork. 😄

Comment thread src/zigbee_ncp/extension/xncp_common_extension/config/xncp_config.h Outdated
- id: toolchain_llvm_lto
condition:
- toolchain_llvm

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Zigbee 4.0
- id: zigbee_r23_support
- id: zigbee_dynamic_commissioning

Have them enabled on my end, seems to be fine.

I'm not sure this would qualify as full Z4 support, but it's a start.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I played around with Zigbee 4.0 and...the results aren't very interesting as far as I can tell ☹️. Anonymous DLK seems to have been explicitly disabled so the joining mode 99% of people use still leaks the network key to a passive attacker...

@burmistrzak burmistrzak Jun 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anonymous DLK seems to have been explicitly disabled so the joining mode 99% of people use still leaks the network key to a passive attacker...

Wasn't that a huge selling point for Z4?
Another good reason to finally move the Zigbee stack to the host... 😅

@puddly Btw. you'll have to remove those two lines #205 (comment)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't that a huge selling point for Z4?

I got it working by hacking the joining policy for both a router and a coordinator but this was removed in the spec itself as far as I can tell, not by SiLabs. Without both, it just didn't work. But this was on the earlier release, not 2026.6.0. I'll have to re-test it.

Another good reason to finally move the Zigbee stack to the host

Once I finish up ESP32 support for Ziggurat, the MG24 is next 😄 (SiLabs doesn't support Rust at all so I think it may be an uphill battle...)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once I finish up ESP32 support for Ziggurat, the MG24 is next 😄 (SiLabs doesn't support Rust at all so I think it may be an uphill battle...)

Huh, interesting!
So you're trying to use the ESP32-C6 as a RCP?
Why not OpenThread RCP, especially on the MG24?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the whole ziggurat stack on the esp32 c6!

zigpy/ziggurat#29 (comment)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OpenThread RCP backend is all done, it's what I run at home 😄. The stack is small enough to fit entirely onto a C6, I'm hoping to eventually tie it into ESPHome so that it can be installed directly onto existing networked coordinators and sidestep OpenThread RCP-over-TCP latency issues.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the whole ziggurat stack on the esp32 c6!

zigpy/ziggurat#29 (comment)

Holy smokes! 🤯
A completely open and memory-safe NCP firmware is actually a huge achievement.

Will be super interesting to see whether MG24 support is actually possible.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OpenThread RCP backend is all done, it's what I run at home 😄.

@puddly Would love to try this myself but we have a lot of ZGP switches and there's also no driver for Z2M yet.
AFAICT, it's a custom wire protocol over WebSocket?

The stack is small enough to fit entirely onto a C6, I'm hoping to eventually tie it into ESPHome so that it can be installed directly onto existing networked coordinators and sidestep OpenThread RCP-over-TCP latency issues.

Wow! A Rust-based ESPHome component is a first, no?

Comment thread src/zigbee_ncp/zigbee_ncp.slcp

@burmistrzak burmistrzak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be good to go!

Edit: Btw. do we have a timeline for hardware flow control on the ESP32?

@puddly

puddly commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

Edit: Btw. do we have a timeline for hardware flow control on the ESP32?

Not at the moment, unfortunately... Upgrading the firmware on the ESP32 is a bit of a hassle right now (especially for VM users) because it effectively unplugs the ESP32 and plugs in a new ESP32 with different VID and PID. We'd likely make this a one-time semi-manual upgrade to ESPHome firmware to make future upgrades more ergonomic.

@zigpy-review-bot

Copy link
Copy Markdown

Follow-up review — deltas since the 2026.6.0 bump (prior review covered the earlier SSDK migration state)

Re-reviewed the changes layered on since the last pass — the 2026.6.0 bump, EUSART migration, ARM64 zstd-GCC toolchain, LLVM plumbing, bootloader→SSDK migration, and the R23/R22 commits. Cross-checked config keys, enums and component structure against a local Simplicity SDK install. No correctness blockers; CI is green and the on-device testing in this thread is reassuring.

Verified good
  • R22 + R23 both enabled (the "untested" commit): confirmed the two .slccs declare no conflicts and only provides/requires disjoint capabilities, so they compose cleanly — matches the zigbeed.slcp precedent you cited.
  • Bootloader single-GPIO switch: the stock bootloader_gpio_activation component uses exactly SL_GPIO_ACTIVATION_POLARITY / SL_BTL_BUTTON_PORT / SL_BTL_BUTTON_PIN, so dropping the custom dual-GPIO extension is a clean revert to stock.
  • Config names all resolve: SLI_ZIGBEE_NETWORK_SECURITY_TYPE_4_0 (0x06), EUSART flow-control macros, SL_ZIGBEE_CUSTOM_MAC_FILTER_TABLE_SIZE (now the SDK default of 2), SL_ZIGBEE_{LARGE,HUGE}_PACKET_BUFFER_HEAP, SL_CLOCK_MANAGER_PCLK_DIV_MIN.
  • build_project.py DWARF-remap: all prefix_map targets are ≤ their sources, so the length assertion holds; padded rewrites stay under /src.

Two questions on the ZBT-2 bootloader (manifests/nabucasa/zbt2/zbt2_bootloader.yaml), the one spot with a user-facing behavior change vs. main:

  1. The migration drops the secondary GPIO activation — the rear reset button (was port C pin 5) no longer enters the bootloader; only the S3 trace (port A pin 6) does. Intentional, or should the ZBT-2 keep a two-button recovery path?
  2. BOOTLOADER_VERSION_MAIN_CUSTOMER: 4 is removed, so it reverts to the SDK default (1). The comment you removed noted the bump was there because "the bootloader does not allow downgrades." Does the SSDK base-version bump make already-deployed (customer-version-4) units still see this as an upgrade?

Trivial: src/zwa2_controller/app.c — "lastest System Ryeset reason" looks like an accidental typo (was "Reset").

@puddly

puddly commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

For the bot review:

  1. The extra component got in the way of the migration 😄. Secondary GPIO activation was never released for any device so it's effectively a main-only tweak. It conflicts with holding down the reset button to erase NVRAM, since the device ends up being stuck in the bootloader after you let go. I'll maybe re-introduce it in a follow-up PR with some better logic.
  2. For the same reason as above. The Simplicity SDK bootloader also bumped the major version to 3 so the manual version bump isn't necessary.

@burmistrzak

Copy link
Copy Markdown
Contributor

Not at the moment, unfortunately... Upgrading the firmware on the ESP32 is a bit of a hassle right now (especially for VM users) because it effectively unplugs the ESP32 and plugs in a new ESP32 with different VID and PID. We'd likely make this a one-time semi-manual upgrade to ESPHome firmware to make future upgrades more ergonomic.

@puddly I see. Well, at least the ESPHome aspect makes it worth the effort.
Just hit me up when you're ready to start working on new firmware. 🤝

Btw. regarding dogfooding the beta, is it safe to re-install the firmware via HA once it's released, or will I have to restore my network from backup?

@puddly

puddly commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

The beta will likely be byte-identical to the one you currently have running but I haven't run into a situation where I needed to erase the network, I reflash mine back and forth between major versions all the time.

SL_ZIGBEE_APS_UNICAST_MESSAGE_COUNT: 128
SL_ZIGBEE_BINDING_TABLE_SIZE: 32
SL_ZIGBEE_BROADCAST_TABLE_SIZE: 64
SL_ZIGBEE_KEY_TABLE_SIZE: 12

@burmistrzak burmistrzak Jul 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still a bit puzzled by this parameter.

Silabs is providing conflicting information whether or not a key slot is required for each and every device joining via install code.
With the default key table size being 1 on the Z2M side, I was nevertheless able to hookup every Z3 device I have. So something isn't adding up here... 🤔

@puddly Do you happen to know more about that?

Edit: New Silabs example default is 20...

@TheJulianJES TheJulianJES Jul 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this value mostly doesn't matter because both Z2M and ZHA/bellows use hashed link keys, so nothing needs to be stored per Z3 device. (TRUST_CENTER_USES_HASHED_LINK_KEY)

And I don't think the difference is whether a device is joined with an install-code or not – should just be about being a Zigbee 3 device or not (Z3 devices should always request a unique TC link key, older ones do not).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To back the above up with the SDK source, and to note the one case where the size would matter 👇

What SL_ZIGBEE_KEY_TABLE_SIZE actually bounds (SiSDK sources)

What the table stores. SL_ZIGBEE_KEY_TABLE_SIZE sizes only the persistent application/link-key table — key type SL_ZB_SEC_MAN_KEY_TYPE_APP_LINK, "an indexed key table of size SL_ZIGBEE_KEY_TABLE_SIZE" (zigbee-security-manager-types.h); component default is 6. Joining doesn't consume it. Both the well-known-key ("quick join") and install-code/QR credentials are added to a separate transient table in RAM (SL_ZB_SEC_MAN_KEY_TYPE_TC_LINK_WITH_TIMEOUT), capacity SL_ZIGBEE_TRANSIENT_DEVICE_MGMT_MAX_CAPACITY = 64, expiring after max(SL_ZIGBEE_TRANSIENT_KEY_TIMEOUT_S = 300 s, network-open-time). So the join method only picks which transient credential is used — it never touches the persistent table. (This is why it's Z3-vs-legacy, not install-code-vs-not, as noted above.)

Why hashed link keys make the size a non-issue. With TRUST_CENTER_USES_HASHED_LINK_KEY (0x0084) the TC keeps a single root key and derives each device's TC link key on the fly by hashing it with the device EUI64 — nothing stored per device. It's documented ("a Root Key known only to the Trust Center… hashed with the IEEE Address of the destination device to create the actual Link Key", sl_zigbee_types.h; "There is only one of these keys in storage", zigbee-security-manager-types.h; have_link_key "always returns true if hashed link keys are used", zigbee-security-manager.h), and the derivation itself is in source: sli_zigbee_stack_sec_man_hmac_aes_mmo(context->eui64, …) for …DERIVED_KEY_TYPE_TC_HASHED_LINK_KEY (zigbee-security-manager-no-vault.c). SiLabs' own writeup literally calls it "a shortcut for unique link key storage on devices with constrained key table capacity" (Hashed Link Keys).

Both host stacks turn it on unconditionally on current firmware:

  • bellows: use_hashed_tclk = ezsp.ezsp_version > 4 → sets the bit in zha_security() (application.py / util.py); same for fresh form and restore-from-backup.
  • zigbee-herdsman: the bit is hardcoded into the EmberInitialSecurityState bitmask in formNetwork (emberAdapter.ts), for both form and restore.

The one case where the size would matter — hashing off. If the TC used SL_ZIGBEE_ALLOW_TC_LINK_KEY_REQUEST_AND_GENERATE_NEW_KEY instead, each Z3 device gets a unique random key stored in the table; the enum comment warns "make sure that the link key table size is not zero as this can result in the newly generated key not being saved and communication breaking between the trust center and the nodes" (sl_zigbee_types.h). There the table has to hold ~one entry per Z3 device. (Legacy pre-R21 devices never run the request+verify handshake — ALLOW_HA_DEVICES_TO_STAY, default TRUE and marked "non-compliant", is what keeps them from being kicked on VERIFY_KEY_TIMEOUT, network-creator-security.c — so they establish no unique key and take no slot either way.)

What still uses the table regardless of hashing. The same table also backs stored application/SE link keys and R23 symmetric passphrases (stored as APP_LINK flagged KEY_TABLE_SYMMETRIC_PASSPHRASE = BIT 7, zigbee-security-manager.c) — worth keeping in mind here since this PR enables R23 + dynamic commissioning. Not populated by ordinary joins today, but it's why the accurate framing is "doesn't gate joins" rather than "unused."

One tradeoff: since hashed keys aren't persisted, there's no stored incoming APS frame counter per device — the article flags this as an APS-replay exposure, the flip side of "no per-device storage."

So 12 is fine and inconsequential for onboarding in the hashed config both stacks use; it'd only become a device-count ceiling if hashing were disabled.

Verified against Simplicity SDK 2025.12.3 — mechanism/doc wording is stable; I didn't re-confirm the exact default integers against 2026.6.0.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TheJulianJES Aha! Makes total sense now. 🙌

We might as well set it to 1 and reclaim a bit of memory?

@MattWestb MattWestb Jul 7, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bellow is hard coded to min 4 so if compiling the firmware with less it can making problems without gaining any benefits and i think 6 is better then its more standard.
If running MG2X some bites or ram is not a problem so its useless tweaking things that can braking some systems if not hawing any evidence its fixing some large problems.
ZHA / bellows is having many user running old hardware on EM35X and MG1X that is different then have very little ram and flash but they cant use this SDK so no problem.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lowering ?? 1 -> 4 is rising in my part of the universe.
I knowing ZHA / Bellows is using hashed tructcenter Link keys and i was one of the alfa tester then it was implemented for some years ago. Also the security setting is for my opinion not good then its not Zigbee 3 complainant then we is not forcing updating the TCLinkKey then joining devices (and denying if not doing it as per R18) and cant changing it in ZHAs config.

I wold like to see (wish full thinking from my side) that bellows is implementing what i was trying for some years ago but old chips cant handling it (to less ram) and its supporting unhashed TCLink Keys but Silabs was not supporting enough TCLK in GSDK (i think 128 was max) but is not fixed so it shall being possible. With 256 TCKL then its not problem migrating from TI and Deconz coordinators. But for users that not migrating and is ruining EZSP hashed is best then its making max devices in the mesh 64K that no other system can matching.

Puddly is the hash key stored in TCLK space ??
I think its very likely and also some temporary things like LL and HA09 (well known key) that is making the problems if setting it too low.

@burmistrzak burmistrzak Jul 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lowering ?? 1 -> 4 is rising in my part of the universe.

@MattWestb Lowering because the current value is still 12. 😉

I knowing ZHA / Bellows is using hashed tructcenter Link keys and i was one of the alfa tester then it was implemented for some years ago. Also the security setting is for my opinion not good then its not Zigbee 3 complainant then we is not forcing updating the TCLinkKey then joining devices (and denying if not doing it as per R18) and cant changing it in ZHAs config.

I wold like to see (wish full thinking from my side) that bellows is implementing what i was trying for some years ago but old chips cant handling it (to less ram) and its supporting unhashed TCLink Keys but Silabs was not supporting enough TCLK in GSDK (i think 128 was max) but is not fixed so it shall being possible. With 256 TCKL then its not problem migrating from TI and Deconz coordinators. But for users that not migrating and is ruining EZSP hashed is best then its making max devices in the mesh 64K that no other system can matching.

The most likely place we'll see individual keys be enabled is ziggurat because of the near unlimited capacity of the Zigbee-on-host architecture.

@puddly puddly Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we also keep around the extra unhashed key capacity for people migrating from TI coordinators. They use a different (insecure) algorithm for hashed link keys and must be migrated individually. Ziggurat supports both algorithms, in addition to manual key loading.

These sum to like 20 bytes per entry, so 200 bytes of NVRAM flash (I think they're not loaded into RAM)? I also am not sure what would happen to users who rely on the 12 slots if the table is suddenly truncated.

We have a lot of extra space (especially on MG24 coordinators) so there's IMO no real downside to keeping the table a little larger than it realistically needs to be.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we also keep around the extra unhashed key capacity for people migrating from TI coordinators. They use a different (insecure) algorithm for hashed link keys and must be migrated individually. Ziggurat supports both algorithms, in addition to manual key loading.

I'm unreasonably hyped about ziggurat, ngl... 😅

These sum to like 20 bytes per entry, so 200 bytes of NVRAM flash (I think they're not loaded into RAM)? I also am not sure what would happen to users who rely on the 12 slots if the table is suddenly truncated.

@puddly Fair point.

We have a lot of extra space (especially on MG24 coordinators) so there's IMO no real downside to keeping the table a little larger than it realistically needs to be.

I guess those keys will have to be loaded in RAM at some point?
But I'm fine with leaving the 12 slots allocated.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If not loaded i RAM its little strange then the keys is used for decoding frames in and out so i saying its 100% in RAM (one router is decrypting and encrypting every frame on the flay for every hope with the network key but the TCLK is also used then talking to devices). If the chip must read one key from flash and then decrypting is the latency to large for good operation (therefor most is using NCP and not RCP + host).
The large problem is if changing the TCLK in NVRAM its also must being changes in the flash. Sonoff was doing it one time and was getting many bricked devices and was hacking one OTA update of the NVRAM file for debricking the NCP (GBL update writing raw file to the flash).

Im very sad but i have not getting the ziggurat working (on the host side with MG21 RCP) but i like that Puddly dont drinking Java with C Python and is implanting that Silabs was trying and was not getting working OK with there Multi Protocol for some years ago. Its one great concept and was working very well with its limits and im sure we getting it working well on our hardware with puddlys help (not sure if ZGP is possible then was one of the problematic things like its still some discrepancy between Zigbee HA X and ZLL both implemented in Zigbee 3 and using the same definitions for different purposes).

Great work done @puddly and @burmistrzak !!

@burmistrzak burmistrzak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional suggestions based on recent discussions. ✌️

SL_ZIGBEE_APS_UNICAST_MESSAGE_COUNT: 64
SL_ZIGBEE_BINDING_TABLE_SIZE: 32
SL_ZIGBEE_BROADCAST_TABLE_SIZE: 64
SL_ZIGBEE_KEY_TABLE_SIZE: 12

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
SL_ZIGBEE_KEY_TABLE_SIZE: 12
SL_ZIGBEE_KEY_TABLE_SIZE: 1

SL_ZIGBEE_APS_UNICAST_MESSAGE_COUNT: 64
SL_ZIGBEE_BINDING_TABLE_SIZE: 32
SL_ZIGBEE_BROADCAST_TABLE_SIZE: 64
SL_ZIGBEE_KEY_TABLE_SIZE: 12

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
SL_ZIGBEE_KEY_TABLE_SIZE: 12
SL_ZIGBEE_KEY_TABLE_SIZE: 1

SL_ZIGBEE_APS_UNICAST_MESSAGE_COUNT: 128
SL_ZIGBEE_BINDING_TABLE_SIZE: 32
SL_ZIGBEE_BROADCAST_TABLE_SIZE: 64
SL_ZIGBEE_KEY_TABLE_SIZE: 12

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
SL_ZIGBEE_KEY_TABLE_SIZE: 12
SL_ZIGBEE_KEY_TABLE_SIZE: 1

Comment thread manifests/nabucasa/zbt2/zbt2_zigbee_ncp.yaml Outdated
Comment thread manifests/nabucasa/zbt2/zbt2_zigbee_ncp.yaml Outdated
XNCP_MFG_MANUF_NAME: '"Nabu Casa"'
XNCP_MFG_BOARD_NAME: '"Home Assistant Connect ZBT-2"'
XNCP_BUILD_STRING: template:"{now:%Y%m%d%H%M%S}"
XNCP_MANUAL_SOURCE_ROUTE_TABLE_SIZE: 200

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this match the size of SL_ZIGBEE_SOURCE_ROUTE_TABLE_SIZE or are the 54 additional entries a safety margin?

@burmistrzak

Copy link
Copy Markdown
Contributor

I've been running a custom build with the aforementioned suggestions without issues.

Parameters changed:

  • SL_ZIGBEE_MAX_END_DEVICE_CHILDREN: 32
  • SL_ZIGBEE_CHILD_TABLE_SIZE: 32
  • SL_ZIGBEE_BROADCAST_TABLE_SIZE: 64
  • SL_ZIGBEE_KEY_TABLE_SIZE: 1

@puddly

puddly commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator Author

I think this should be good to merge, at least as a first pass 😄. Everything works fine.

The Silicon Labs OpenThread RCP firmware had one fun surprise left: Green Power packet filtering for zigbeed. This is baked in and cannot be turned off without an SDK patch. I wasn't expecting this to cause problems (since Ziggurat doesn't itself implement ZGP at the moment) but...the SDK apparently has both a bug that reads into uninitialized memory and overly broad filtering for short packets! These two combined into a bizarre situation: if a device with a Silicon Labs IEEE address prefix sent an "Association Request", the next "Data Request" packet that came from the device was completely dropped by the stack, causing random devices to just never join...

vendor: nabucasa

sdk_patches:
- disable_gp_rx_filter.patch

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@puddly I assume this is for ZGP in ziggurat?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment above (#205 (comment)). The GP filtering is currently breaking devices joins for RCP 😞.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woops! Was on the review page and totally missed it. 😅

@burmistrzak

Copy link
Copy Markdown
Contributor

The Silicon Labs OpenThread RCP firmware had one fun surprise left: Green Power packet filtering for zigbeed. This is baked in and cannot be turned off without an SDK patch. I wasn't expecting this to cause problems (since Ziggurat doesn't itself implement ZGP at the moment) but...

Ok... Go on... 👀

the SDK apparently has both a bug that reads into uninitialized memory and overly broad filtering for short packets!

What the actual …!?
This is shipping production firmware!

These two combined into a bizarre situation: if a device with a Silicon Labs IEEE address prefix sent an "Association Request", the next "Data Request" packet that came from the device was completely dropped by the stack, causing random devices to just never join...

THIS IS FINE. ☕️

@puddly Have you reported your findings to Silabs already?

Why has vendor firmware always be so comically bad?
I guess there's no way around Simplicity SDK for OT at the moment?

@burmistrzak burmistrzak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Long-term testing revealed no additional blocking issues in a large network.
So good to go from my side as well. 👌

@MattWestb

Copy link
Copy Markdown

I think we have one long and very good record of Silabs bugs in there Zigbee SDKs.
Then IKEAs first, second and third gen devices is using elusive Silabs chips and and some sensors and lights in gen 4 (thread/zigbee),
Only renaming in most devices is device announcement and Silabs routers is flagging the rejoined device is using one already used short address so it must use one new but the IEEE is not changed.
And older router firmware was crashing if being spammed with little device announcement but it shall being fixed in current production.
Good is they is documenting reported bugs and also fixes if possible.

And way we see much Silabs bugs ?
1: Very likely then many user have devices using there ships (I think TLink is more used by tuya and CO but tuya is not implant all functionality most of the time like GPP).
And looks 4 gen routers in Zigbee mode is not doing device announcement at all (Silabs/TLink but stripped but most Zigbee 3 certificated in direct paring / ZTL mode).
2: Our deep digging PUDDLY !!!!

@puddly
puddly merged commit df44c39 into NabuCasa:main Jul 15, 2026
18 checks passed
@puddly
puddly deleted the puddly/ssdk-v2025.12.3-test branch July 15, 2026 12:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants