So I switched to v3.4.0 SDK version since I noticed it had Long Term Support. I figured that must be a well tested SDK for LTS. I also switched to “device tree” specificaion for flash mapping instead of the partition manager method.
Top prj.conf changes:
CONFIG_CHIP_FACTORY_DATA=y
CONFIG_CHIP_FACTORY_DATA_BUILD=n
CONFIG_CHIP_FACTORY_DATA_USE_DEFAULT_CERTS=y
top sysbuild.conf changes:
SB_CONFIG_MATTER_FACTORY_DATA_GENERATE=y
Then added this flash mapping into 2 files:
boards/xiao_nrf54l15_nrf54l15_cpuapp.overlay
sysbuild/mcuboot/boards/xiao_nrf54l15_nrf54l15_cpuapp.overlay
&cpuapp_rram {
/delete-node/ partitions;
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x0 0xd000>;
};
/* mcuboot_pad + app merged into your primary application slot */
slot0_partition: partition@d000 {
label = "image-0";
reg = <0xd000 0xd2000>;
};
/* mcuboot_secondary_pad + mcuboot_secondary_app merged into secondary slot */
slot1_partition: partition@df000 {
label = "image-1";
reg = <0xdf000 0x93000>;
};
factory_data_partition: partition@172000 {
label = "factory-data";
reg = <0x172000 0x1000>;
};
storage_partition: partition@173000 {
label = "storage";
reg = <0x173000 0xa000>;
};
};
};
Also had to add this to ./sysbuild/mcuboot/prj.conf, since for some reason the app hash was failing boot integrity checks:
CONFIG_BOOT_VALIDATE_SLOT0=n
The Matter app wants to support OTA firmware updates, and will ping-pong between the slots. This is likely a failsafe in case a bad firmware was uploaded, it can revert back to the one in the other slot. Furthermore, we need room for the factory data and the “storage” for network pariing data. When you subtract the mcuboot, factory data and storage space, the remaining space would be divided by 2 for slot0 and slot1. My window cover matter app is larger than tha. This is why slot1 is smaller than slot0. This seems to be a hack the original Seeedstudio developer did in their nrf54l15 wiki code. I did try to delete slot1 and disable OTA, but I got compile errors, so I reverted back to the above. I don’t need OTA for my app, so I can live with the hack.
The next issue I ran into is west build doesn’t seem to create the build.ninja command properly to build the factory data. “west build” died when it got to the factory data build step. The error shows It’s missing the “test/debug” certificates. I don’t understand why the “USE_DEFAULT_CERTS” above didn’t resolve this. I had to add these after build/build.ninja was created:
--dac_key /opt/nordic/ncs/v3.4.0/modules/lib/matter/credentials/development/attestation/Matter-Development-DAC-FFF1-8000-Key.der \
--pai_cert /opt/nordic/ncs/v3.4.0/modules/lib/matter/credentials/development/attestation/Matter-Development-PAI-FFF1-noPID-Cert.der \
--dac_cert /opt/nordic/ncs/v3.4.0/modules/lib/matter/credentials/development/attestation/Matter-Development-DAC-FFF1-8000-Cert.der
Note that the FFF1 is hex for the vendor, and that needs to aling with the “–vendor_id 65521” flag passed to nordic/ncs/v3.4.0/modules/lib/matter/scripts/tools/nrfconnect/generate_nrfconnect_chip_factory_data.py. Addiotnally, the 800D needs to match the " --product_id 32781". I’m
After all this, I have a properly compiling app that flashes to the XIAO board. But the app stopped with a “failed to parse factory data” message. I edited the nordic/ncs/v3.4.0/modules/lib/matter/src/platform/nrfconnect/FactoryDataParser.c to add some printk messages to figure out what happened. Many debug hours later (with AI assist), it seems that the “openocd” command that flashes the factory data was truncating the data it flashed. Maybe something about only flashing full pages or something? To solve this, I wrote my own bash script to build the factory data and I pad out the factory_data.bin file with 0x00 to the full 4k partition size. I re-generate the hex and flash that:
/opt/nordic/ncs/toolchains/ccc010f809/opt/[email protected]/bin/python3.12 \
/opt/nordic/ncs/v3.4.0/modules/lib/matter/scripts/tools/nrfconnect/generate_nrfconnect_chip_factory_data.py \
--sn 11223344556677889900 \
--date '2026-07-17' \
--vendor_id 65521 \
--product_id 32781 \
--vendor_name nonyabiz \
--product_name BlindController \
--hw_ver 0 \
--hw_ver_str prerelease \
--spake2_it 1000 \
--spake2_salt U1BBS0UyUCBLZXkgU2FsdA== \
--discriminator 0xF00 \
--passcode 20202222 \
--include_passcode \
--overwrite \
--product_finish other \
--enable_key 00112233445566778899AABBCCDDEEFF \
--generate_rd_uid \
--part_number 5 \
--product_label "none" \
--product_url "na" \
--product_color "blue" \
-o ./build/matter_factory_data/zephyr/factory_data \
-s /opt/nordic/ncs/v3.4.0/modules/lib/matter/scripts/tools/nrfconnect/nrfconnect_factory_data.schema \
--offset 0x172000 \
--size 0x1000 \
--dac_key /opt/nordic/ncs/v3.4.0/modules/lib/matter/credentials/development/attestation/Matter-Development-DAC-FFF1-800D-Key.der \
--pai_cert /opt/nordic/ncs/v3.4.0/modules/lib/matter/credentials/development/attestation/Matter-Development-PAI-FFF1-noPID-Cert.der \
--dac_cert /opt/nordic/ncs/v3.4.0/modules/lib/matter/credentials/development/attestation/Matter-Development-DAC-FFF1-800D-Cert.der
dd if=/dev/zero bs=1 count=4096 >> ./build/matter_factory_data/zephyr/factory_data.bin
arm-zephyr-eabi-objcopy \
-I binary \
-O ihex \
--change-addresses 0x172000 \
./build/matter_factory_data/zephyr/factory_data.bin \
./build/matter_factory_data/zephyr/factory_data.hex
openocd \
-s /opt/nordic/ncs/v3.4.0/zephyr/boards/seeed/xiao_nrf54l15/support \
-f /opt/nordic/ncs/v3.4.0/zephyr/boards/seeed/xiao_nrf54l15/support/openocd.cfg \
-c 'init' \
-c 'targets' \
-c 'reset init' \
-c 'nrf54l-load ./build/matter_factory_data/zephyr/factory_data.hex' \
-c 'reset run' \
-c 'mdb 0x001725c0 32' \
-c shutdown
The “mbd 0x001725c0 32” part of the flash command tells openocd to dump 32 bytes at that address. That is the end of the valid data in the factory_data.hex, so this is an extra confirmation nothing got truncated.
The device won’t pair with my Home assistant matter/thread network. Probably something wrong with my factory data above. At least I have the app running now.