Menu
Home Videos BlogTech ProjectsAiHardwareSoftware Shop Digital Designs Physical Art Layered Maps Chinese Paper Cuttings About Contact Cart (0)

The second radio personality built for the MG21 was a Zigbee Network Co-Processor, based on Silicon Labs’ production zigbee_ncp_uart_hw example generated for the brd4180a reference board and then adapted for the W1700K’s two-wire UART. The official example’s USART0 pin assignment and CTS/RTS requirement did not match the board’s actual wiring; the adaptation disables hardware flow control and applies the board-specific UART pin swap.

Firmware, flash layout, and a real startup fault

The generated application starts at 0x4000, with NVM3 reserved starting at 0x74000 — the same layout established during reverse engineering. The verified working uncompressed GBL was firmware/zigbee-ncp/generated/build/release/w1700k-zigbee-ncp-uncompressed.gbl (SHA-256 059691680abe457de51e6d867f14322f0d3ab82c97459ab2d727073cc812ef70).

SWD debugging during bring-up found that the generated TrustZone-unaware image’s default SMU/PPU setup could block peripheral access and park execution in SMU_SECURE_IRQHandler; an app.c workaround grants the required peripheral access and disables that secure interrupt safety path. The same HFXO_E301 workaround described in the BLE chapter also applies here — do not toggle DISONDEMAND from 0 to 1 while HFXO is enabled. The HFXO startup behavior was investigated extensively during this phase: stock firmware itself was observed reaching only HFXO_STATUS_RDY rather than the full SDK lock mask the crystal-mode wait expects (RDY|COREBIASOPTRDY|ENS|FSMLOCK). An SDK-local wait-condition change was considered but deliberately not applied, since it would edit shared installed SDK code and could compromise RF calibration.

Coordinator network and PARASOLL pairing

The coordinator formed a test network with:

FieldValue
Channel15
PAN ID0x1A62 / 6754
Extended PAN ID0011223344556677
Coordinator IEEE1c:c0:89:ff:fe:0c:88:f1
Sensor IEEE94:a0:81:ff:fe:65:51:00
Sensor modelPARASOLL Door/Window Sensor (IKEA of Sweden)

The pairing and monitoring chronology went through several corrections:

  1. A temporary Bellows permit process joined and discovered the sensor, but captured no door event.
  2. A first Python monitor attached to the sensor’s own endpoint-2 server-side IAS Zone cluster — the wrong receive-side location for notifications.
  3. The monitor was corrected to attach to the coordinator’s endpoint-1 client-side IAS Zone cluster, which is where IAS Zone status notifications actually arrive. This script became the reusable tests/door_monitor.py.
  4. Re-pairing (bellows ... permit -D tests/w1700k-coordinator.db -t 180) moved the sensor from network address 0x6D3D to 0x5FB7, with endpoint 2 confirmed as the IAS Zone endpoint (cluster 0x0500).
  5. After IAS enrollment/configuration, the monitor reliably decoded live door open/close notifications — zone_state=Enrolled, zone_type=Contact_Switch, cie_addr=1c:c0:89:ff:fe:0c:88:f1, zone_id=0, and bind status SUCCESS.

A restart test confirmed recovery behavior: stopping and relaunching the monitor without --permit (an existing-device recovery test, not a new pairing) reconnected, reloaded PARASOLL and a Remote Control N2 from tests/w1700k-coordinator.db, and — after the sleeping PARASOLL woke and re-applied IAS configuration — resumed decoding live frames:

00:04:57 PARASOLL cluster 0x0500 command 0x00 zone_status=0 (closed)
00:04:59 PARASOLL cluster 0x0500 command 0x00 zone_status=1 (open)
00:05:02 Remote Control N2 cluster 0x0005 raw command data=057c110e0700010d00
00:05:03 Remote Control N2 cluster 0x0006 raw command data=010f01

Each PARASOLL notification appeared twice because the identical status frame was delivered to both destination endpoint 1 and endpoint 2 — this reflects the delivery path, not two physical door actuations.

Zigbee2MQTT container and Home Assistant integration

Host-side integration was later moved from the temporary Python monitor to a dedicated Zigbee2MQTT 2.14.1 container (koenkk/zigbee2mqtt:latest, zigbee-herdsman 10.9.2) on a separate Docker host, deliberately kept independent of the user’s existing production Zigbee2MQTT instance (which runs on its own USB coordinator):

Home Assistant Discovery was enabled (homeassistant: { enabled: true }) after two operational snags: MQTT passwords containing # needed careful handling, and a Compose .env variable was not automatically passed into the Zigbee2MQTT application config (the working password ended up stored directly in the restricted Zigbee2MQTT data file). Once corrected, logs showed Connected to MQTT server, Zigbee2MQTT started!, and discovery publications for the bridge, PARASOLL contact/battery/voltage entities, and Remote Control N2 entities — with Home Assistant creating a door binary sensor and a Permit Join control for this isolated coordinator.

Verification

Confirmed: the custom Zigbee NCP image builds and was flashed without reverting to stock; Bellows ASH reset and EZSP negotiation work over the real W1700K bridge; the coordinator forms a network on channel 15; PARASOLL joins, re-joins, and completes endpoint discovery; the router-side serial bridge and two-wire UART transport are operational; restarting the monitor with an existing database restores listeners for already-paired devices and resumes receiving live frames; and Zigbee2MQTT 2.14.1 connects via the ember adapter, resumes the network, and successfully publishes Home Assistant discovery for both test devices.

Early Thread bring-up in the same session

This same work session also produced the first Thread RCP and OTBR bring-up on this hardware — using the zigbee_ncp_uart_hw-adjacent ot-rcp example and a Docker-hosted OTBR reaching the router’s serial bridge through a socat PTY proxy. otbr-agent reached running state, created wpan0, and Thread reached leader, with Home Assistant automatically discovering the resulting OpenThread-0a9b network and border router. Matter Server was reachable but no device had yet been commissioned. This Docker + TCP-bridge + socat architecture, and its stability limitations, set up the work covered in the next chapter, where OTBR was migrated to run natively on the router itself.