This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
quickstart-bluetooth is a production out-of-tree nRF Connect SDK (NCS) application
for the nRF Connect for Desktop Quick Start guide. It is the peripheral_lbs sample
(button/LED over BLE) plus Memfault observability delivered over the Memfault Diagnostic
Service (MDS) BLE gateway path: the device collects heartbeat metrics and coredumps and
serves them as chunks to a phone/desktop gateway that performs the HTTPS upload.
The device never does on-device HTTP/TLS.
Target board is nrf54l15dk/nrf54l15/cpuapp only.
This is the top-level west manifest repository (T2 star topology). It imports sdk-nrf,
which pulls Zephyr and all NCS modules. Building requires the NCS toolchain.
# Bootstrap the workspace (done once, outside the repo dir)
west init -m https://github.com/<org>/quickstart-bluetooth <workspace-dir>
cd <workspace-dir>
west update
# Build / flash for the only supported board
west build -b nrf54l15dk/nrf54l15/cpuapp project/app
west flashThe single zephyr.hex is the complete image — there is no MCUboot/sysbuild, no
merged/signed hex, no DFU zip (OTA is explicitly out of scope).
- Upstream-first dependency model. The reusable Memfault glue is upstreamed into
sdk-nrf(and the vendored Memfault firmware SDK), not carried as an out-of-tree fork.west.ymlpins a specificsdk-nrfmain SHA (not themainbranch) that already contains that work. Bumping the SHA is a deliberate, CI-gated action. - Plain Zephyr app, not a Zephyr module. Default to a plain application under
app/with nozephyr/module.yml. Only add module machinery if app-local shared code emerges. - Runtime project key via settings shell. The Memfault project key is provisioned over
the serial shell:
settings write string memfault/project_key <32-char-key>. The key is applied on boot, not live — akernel reboot coldis required after writing. A stored key overrides the build-time default key (CONFIG_QSBT_DEFAULT_PROJECT_KEY, if any — seeapp/src/main.c). The key is stored unencrypted (same at-rest protection as baking it into flash). BLE/SMP provisioning is out of scope.- Release builds bake in a real key at build time (CI passes
-DCONFIG_QSBT_DEFAULT_PROJECT_KEYfrom theMEMFAULT_PROJECT_KEYrepo secret — seerelease.yml/build-app.yml) so a freshly flashed release artifact reports to Memfault out of the box, without requiring shell provisioning first. CONFIG_MEMFAULT_NCS_PROJECT_KEYis a dead/deprecated upstream Kconfig option with no effect whenCONFIG_MEMFAULT_PROJECT_KEY_SETTINGS=y(always the case here) — do not reintroduce it as the build-time override mechanism.
- Release builds bake in a real key at build time (CI passes
- Runtime advertising name via settings shell. The BLE advertising name is provisioned
over the serial shell with Zephyr's built-in
bt name <name>command (CONFIG_BT_SHELL), which persists to the settings keybt/nameviaCONFIG_BT_DEVICE_NAME_DYNAMIC. The advertising data (ad[]inapp/src/main.c) is rebuilt frombt_get_name()each time advertising (re)starts, so a stored name takes effect after akernel reboot cold— same provisioning UX as the project key above. A stored name overrides the build-time default (CONFIG_BT_DEVICE_NAME, currentlyQuickstart_Bluetooth). - Memfault feature scope is core-only: heartbeat metrics + RAM-backed coredump + runtime key, on top of the LBS button/LED base. No MCUboot/MCUmgr/SMP/OTA.
- Coredump is RAM-backed (
CONFIG_MEMFAULT_RAM_BACKED_COREDUMP), so it needs no bootloader or flash partition. Re-measure size with themflt coredump_sizeshell command on the LBS build before fixingCONFIG_MEMFAULT_RAM_BACKED_COREDUMP_SIZE.
- No pairing/bonding:
CONFIG_BT_SMP=n— this sample has no encryption or bonding. A real product handling sensitive data should setCONFIG_BT_SMP=yand use bonding (CONFIG_BT_BONDABLE=y, the default) instead. Because of this, we can't use theCONFIG_BT_MDS_PERM_RW_ENCRYPToption described in Restricting Access to MDS — that requires a bonded, encrypted link — so this app falls back to the customaccess_enablecallback from that same doc (see MDS access control below). - MDS access control: register
bt_mds_cbwith anaccess_enablecallback that gates MDS access to the first connected gateway link (tracked viamds_conninconnected(), since there is no security level to check withoutCONFIG_BT_SMP). - Heartbeat-on-connect: in
connected(), once the gateway link is captured asmds_conn, callmemfault_metrics_heartbeat_debug_trigger()once so the device shows up in Memfault immediately instead of waiting for the periodic timer. - Crash button (demo-only): map an LBS button to a forced fault (e.g.
k_oops) to demonstrate a coredump. Comment it clearly as demo-only. - Keep the LBS LED/button behavior so it remains a recognizable LBS device for the guide — the GATT LBS service/characteristics stay standard regardless of what's advertised.
- Scan-time identity: the scan response advertises the custom app-identity UUID
(
BT_UUID_QSBT_ID_VAL,b2007aaa-...), not the LBS UUID, so the mobile app can tag this device as "quick start" in its scan list before connecting.
The tests under test/ are on-hardware bench tests: they drive a connected
nRF54L15 DK over its USB serial console and BLE. There is no pure-host test suite.
If no DK is connected, ask the user whether they want to connect an nRF54L15 DK so
you can verify functionality before running anything. On macOS a connected DK shows
up as /dev/tty.usbmodem*01/03 (VCOM1 = …03); no ports means no board.
test/gateway/— a phone-free Node/noble MDS gateway that stands in for the mobile app (connects, secures the link, drains chunks, optionally uploads). Needs Node on PATH; runnpm installin that dir first. Seetest/gateway/README.md.test/e2e/— serial project-key contract tests plus a cloud project-switch e2e (the latter drives the gateway above and confirms the switch via the Memfault REST API). Needs the NCS toolchain Python (for pyserial); the e2e also needs credentials in the gitignored repo-root.env. Seetest/e2e/README.md.
Both suites run through the NCS toolchain launcher
(nrfutil toolchain-manager launch … -- python3 …) — the exact invocations, options,
and credential setup are in the two READMEs above; don't duplicate them here.
app/VERSION (Zephyr/Asset-Tracker-Template format) is the single source of truth for
the firmware version. With CONFIG_MEMFAULT_NCS_FW_VERSION_STATIC=y and no explicit
CONFIG_MEMFAULT_NCS_FW_VERSION, the Memfault software version defaults to
$(APP_VERSION_TWEAK_STRING) (e.g. 1.0.0+0). Do not hardcode the firmware version —
bump a release by editing app/VERSION only. The GNU Build ID (used for symbolication) is
independent and changes every build — zephyr.elf must be uploaded to Memfault for
symbol resolution.