Curiosity
Beacon BLE da ~9€ che è in realtà una dev board nRF54L15. Venduto come iBeacon per il posizionamento indoor (30 mm × 8.4 mm, 7 g, CR2032), monta il Nordic nRF54L15 — lo stesso chip scelto per il Visla Tag di produzione — con pulsante, LED RGB e accelerometro LIS2DH12.
⭐ Al banco (16/07/2026): SWD completamente aperto, core halted, firmware originale dumpato, chip riflashato. Cinque di questi beacon = cinque dev board nRF54L15 disponibili ORA (lo XIAO nRF54L15 arriva il 03/08/2026).
🛒 AliExpress — Holyiot Official Store · €9.29 · modello nRF54L15 25008
Specs
| Parametro | Valore |
|---|---|
| SoC | Nordic nRF54L15 (N54L15 QFAAB0) — Cortex-M33 + TrustZone |
| BLE | 6.0 (+ Zigbee / Thread / Matter capable) |
| Accelerometro | LIS2DH12 3 assi (I2C) |
| Pulsante | 1 (P1.13) |
| LED | RGB (P2.09 rosso / P1.10 verde / P2.07 blu) |
| Alimentazione | CR2032 |
| Dimensioni | Ø 30 mm × 8.4 mm · 7 g |
| Cristalli | 32 MHz (HFXO) + 32.768 kHz (LFXO) |
| Programmazione | SWD — 4 pad · ⚠️ nessun DFU sul firmware di serie |
| Consumo (firmware custom) | ~6 µA medi → ~4 anni su CR2032 (fonte: power profiling uAmpHome) |
Varianti Holyiot nRF54L15 (⚠️ modelli diversi, hardware diverso — verificare sempre la serigrafia del PCB):
| Modello | Pulsante | Buzzer | Accel | Altro |
|---|---|---|---|---|
| 25008 | ✅ P1.13 | ❌ | LIS2DH12 (tap rotto) | “button only” |
| 25015 | ✅ | ❌ | ✅ | + SHT40 temp/umidità |
| 25027 | ❌ NO | ✅ P1.06 | LIS2DH12 | serigrafia HOLYIOT-25027-V1.0 |
⚠️ Il buzzer P1.06 del 25027 non è documentato e non ha board Zephyr: trovato al banco con una web page che scriveva nella RAM via J-Link a chip acceso (firmware).
🚩 Compilare per holyiot_25008 una board 25027 funziona ma mente: sw0 esiste nel devicetree e il codice aspetta un pulsante inesistente.
🔓 SWD: il chip è APERTO
Quattro pad serigrafati sul PCB, dall’alto: VDD / GND / DIO / CLK.
| J-Link (20 pin) | Beacon | |
|---|---|---|
pin 1 VTref | VDD | ⚠️ solo lettura del livello → la CR2032 resta inserita |
pin 4 GND | GND | |
pin 7 SWDIO | DIO | |
pin 9 SWCLK | CLK |
Esito con J-Link V9.8 PLUS + JLinkExe V9.24a (il device nRF54L15_M33 è nel DB dalla V9.24):
VTref = 3.300 V
Device "NRF54L15_M33" selected
Found SW-DP with ID 0x6BA02477
AP[0]: Core found ← ✅ APPROTECT NON ATTIVO
Core halted, registri Cortex-M33 letti dal vivo (SP=0x20007DA8, TrustZone attivo).
💡
VTrefnon alimenta: dice solo al J-Link a che livello logico parlare.
💾 Backup del firmware stock — preso PRIMA di scrivere
printf "device nRF54L15_M33\nif SWD\nspeed 4000\nconnect\nhalt\n\
savebin holyiot_25008_stock.bin,0x00000000,0x17D000\nexit\n" | JLinkExe -nogui 1
1524 KB · 222 KB non vuoti (14.6%)
f0 7d 00 20 | f5 2d 00 00 | a1 3b 00 00 ... ← vettore ARM Cortex-M corretto
stringhe: 'clock@10e000' ← nodo DEVICETREE
⭐ Il firmware Holyiot è già Zephyr. Dump in test-moduli/holyiot-beacon-zephyr/backup/ (repo). Ripristino con loadbin ...,0.
🟢 Riflashato: da iBeacon cinese a Matter
Binari precompilati di uAmpHome/micro_matter_button (video) — niente SDK da installare:
erase → loadfile micro_matter_button_h25008_om2.hex → r → g
Verifica in aria:
PRIMA: 5 iBeacon 'NexBeacon Pro' / 'Holy-IOT' / 'B2-S'
DOPO: 4 iBeacon + 🟢 'MatterSwitch' RSSI -67, service 0xFFF6
0000fff6-...: 00 00 0f f1 ff 04 80 00
⚠️ verifybin vuole un .bin: su un .hex fallisce con Expected 3A read 20 (3A = : ASCII, prima lettera dell’Intel HEX). Usare loadfile, che verifica da sé (Contents already match).
Alternativa senza J-Link: XIAO RP2040/Pico con picoprobe + clip a molle 4P passo 1.5 mm (niente saldature) → pyocd load ... -t nrf54l. Cablaggio: GP2→SWCLK, GP3→SWDIO, GND→GND, 3V3→VCC (opzionale, o si usa la CR2032).
Le due modalità (si sceglie il binario, niente ricompilazione):
| Accelerometro | Note | |
|---|---|---|
| om1 — Dimmable switch | ✅ usato: double-click → sveglia l’accel → ruoti = regoli la luce | richiede binding Matter |
| om2 — Generic switch | ❌ spento al boot | ✅ nessun binding: Apple/Google/Alexa/HA |
⚠️ Al banco ho flashato la om2 → accelerometro spento. Per giocare col LIS2DH12 serve la om1.
Factory reset: pulsante premuto 12 s (LED bianco lampeggia) → rilascia prima di 15 s per annullare, oltre i 15 s torna in commissioning. Pairing code Matter: 34970112332.
⭐ Il repo contiene boards/ con le definizioni per holyiot_25008 e 25015 → per firmware custom non serve la board upstream (marcata “not actively maintained”):
west build -b holyiot_25008_nrf54l15_cpuapp -- -DCONFIG_MATTER_BUTTON_MODE_1=y
Costruito con nRF Connect SDK v3.2.4. La 25015 aggiunge SHT40 (temp/umidità, funzionante) e LPS22HB (pressione, non implementato: il driver NCS assume I²C ma la board usa SPI → spento al boot).
🔔 Firmware VISLA — il pulsante esce in BLE (16/07/2026)
Quello che né Holyiot né il progetto Matter fanno: mettere lo stato del pulsante nell’advertising. Gli eventi del MatterSwitch viaggiano su Thread (serve un hub); il firmware Holyiot il pulsante non lo espone affatto. Il nostro lo trasmette in broadcast: il tag lo sente senza connettersi, senza hub.
💻 Codice: test-moduli/holyiot-beacon-zephyr/visla-beacon/ · ricevitore: test-moduli/xiao-ble-doorbell/
124 KB di firmware (contro 1.6 MB del Matter button): niente stack Thread, solo BLE broadcast.
Payload (manufacturer data 0xFFFF)
[0..1] FF FF company ID [8] flags bit0 = premuto ORA
[2] 'V' magic Visla [9] press_count +1 a ogni pressione
[3] 01 versione [10] batteria %
[4..7] device ID ⭐ DAL SILICIO
Reale: 56 01 81 40 3c 51 01 07 64 → 'V' v1 · devID 81403C51 · premuto · count 7 · batt 100%
⭐ Il problema dei cloni si risolve da solo
hwinfo_get_device_id() legge l’ID di fabbrica del silicio: ogni nRF54L15 ne ha uno diverso. Niente UUID da assegnare a mano, niente app cinese, nessuno può clonarlo.
press_count: l’advertising è inaffidabile per natura
Se il tag scansiona altrove nell’istante del click, quel pacchetto lo perde. Il contatore no: al pacchetto dopo vede 7 → 9 e recupera. Verificato al banco — un rilascio perso in aria, ma il conteggio 14 → 15 è arrivato lo stesso.
Advertising adattivo
1 Hz a riposo (batteria) → 100 ms per 2 s alla pressione: un campanello a 1 Hz avrebbe fino a 1 s di ritardo. bt_le_adv_update_data() aggiorna il payload senza fermare la radio.
🐛 Il bug più istruttivo: il DEBOUNCE
Prima versione senza debounce → un click = 10 din-don. Un pulsante meccanico rimbalza: una pressione = 5-10 fronti = contatore +10 = dieci suonate. Avevo scritto il codice sul comportamento ideale del pulsante, non su quello reale.
Due difese complementari:
- beacon:
DEBOUNCE_MS 200→ ignora i fronti ravvicinati. Elimina la causa. - ricevitore: salto contatore > 3 → suona una volta sola. Anche col debounce, un riavvio del beacon fa balzare il contatore.
→ L’advertising è inaffidabile: il ricevitore dev’essere robusto per conto suo.
Build
west init -m https://github.com/zephyrproject-rtos/zephyr --mr main ws && cd ws && west update # ⚠️ ~6.6 GB
west build -p -b holyiot_25008/nrf54l15/cpuapp visla-beacon
⚠️ prj.conf deve avere CONFIG_HWINFO=y → senza: undefined reference to z_impl_hwinfo_get_device_id.
⚠️ Gotcha ricevitore: Bluefruit NON toglie il company ID
parseReportByType(MANUFACTURER_SPECIFIC_DATA) restituisce anche i 2 byte di company ID → b[0..1] = FF FF, il magic 'V' sta in b[2], non in b[0]. Avendo assunto il contrario, il beacon veniva scartato e il display restava OFF.
⚠️ Gotchas
- 🚩 Tap detection rotto da bug hardware — ma l’accelerometro FUNZIONA. Precisazione importante: il LIS2DH12 legge il movimento benissimo (il firmware uAmpHome fa rotational dimming: ruoti il beacon e regoli la luce). A essere rotto è l’interrupt, cioè la capacità del sensore di svegliare l’MCU da solo — infatti nella Mode 1 usano il doppio click del pulsante per attivare l’accelerometro, proprio perché non può auto-svegliarsi.
→ Conseguenza per Visla: niente wake-on-motion — l’accelerometro va interrogato a polling, che costa corrente (il video parla di “hidden accelerometer drawing 3× too much current”). Il pulsante invece funziona.
→ Al banco avevo dedotto “accelerometro presente ma firmware muto”: era l’hardware, non il firmware. Terzo annuncio AliExpress della giornata che promette una feature che l’hardware non regge (cfr.
-LASEvs-FASEsull’A7672E, connettore BT/WiFi sul BG95-M3). - ⚠️ Il pulsante NON è visibile via BLE — verificato al banco su firmware stock e su MatterSwitch: l’advertising non cambia mai. Gli eventi del pulsante Matter viaggiano su Thread, non nell’advertising. → per un “pulsante → azione” fuori da Matter serve firmware custom.
- 🔇 La 25008 non ha cicalino (è “button only”). Il buzzer è su un’altra variante Holyiot.
- ❌ Nessun DFU sul firmware di serie → si passa per forza dai pad SWD (confermato dalle recensioni).
- 🔁 Sono tutti CLONI: stesso UUID
FDA50693-A4E2-4FB1-AFCF-C6EB07647825, Major 10011, Minor 19641 — l’UUID di default più diffuso tra i beacon cinesi. Per Visla vanno resi unici, altrimenti il tag non distingue il proprietario da chiunque altro. - 🇨🇳 App di configurazione
NexBeacon Pro, in cinese e senza documentazione (una recensione: “3 scambi di email con Holyiot per farlo funzionare”). Il nome dell’advertising è il nome dell’app → è così che si riconoscono i 25008 tra i beacon. - 📱 iOS/macOS filtrano gli iBeacon: qui passano solo perché usano manufacturer ID
FFFFinvece di quello Apple (4C00). Con l’ID ufficiale, dal Mac non si vedrebbero. - ⏱️ Advertising ~1 Hz → la finestra di scan del tag deve essere ≥1 s: uno scan da 100 ms beccherebbe il beacon ~1 volta su 10.
🔬 Cosa trasmette (decodificato al banco)
Il service data contiene MAC + livello batteria, senza doversi connettere:
01 | f2 28 39 2b 62 c1 | 04 | 64 → MAC F2:28:39:2B:62:C1 · batteria 0x64 = 100%
01 | e8 10 a5 83 56 6c | 04 | 61 → MAC E8:10:A5:83:56:6C · batteria 0x61 = 97%
→ il tag saprebbe che il beacon del proprietario si sta scaricando a costo zero.
iBeacon: TxPower@1m = −55 dBm → distanza ≈ 10^((TxPower−RSSI)/20), ~1 m verificato. Affidabile da vicino, indicativa oltre.
Espone anche la Nordic UART Service (6e400001-...) → connettibile, ma protocollo binario proprietario (8 comandi AT provati → zero risposte).
🎯 Note per Visla
- ⭐ 5 dev board nRF54L15 a ~10€ con lo stesso chip del Tag di produzione, disponibili ora.
- 📡 FATTO: reflector Channel Sounding (21/07/2026). Riflashato col ruolo RAS reflector del sample NCS e usato come partner di ranging BLE 6.0 con la XIAO nRF54LM20A initiator → distanza
phase_slope~2 m stabile. Firmwarecs-reflector-holyiot. Gotcha J-Link: pad DIO↔CLK facilissimi da invertire (VTref 3.3 V presente ma “debug port unavailable” = linee dati scambiate, non approtect); reset pin non esposto → catturare il core al boot durante il power-cycle della CR2032. - Per l’arm/disarm NON serve riprogrammarli: il MAC pubblico (
C0:9A:C2:7D:A0:68) è già un ID stabile, e l’RSSI basta per la soglia di prossimità. - ✅ FATTO: firmware Visla con UUID univoco dal silicio e pulsante in broadcast BLE → campanello funzionante al banco. Per il wake-on-motion a µA serve un’altra board: qui l’accelerometro c’è e legge, ma non ha l’interrupt per svegliare l’MCU → solo polling.
- ✅ Board supportata in Zephyr:
holyiot_25008— LIS2DH12, pulsante e LED già nel devicetree. ⚠️ marcata “not actively maintained”.west build -b holyiot_25008/nrf54l15/cpuapp samples/basic/blinky - 🔓 La lezione del 16/07/2026: SimCom nega l’SDK (moduli canale cinese), Quectel lo pubblica ma il flash riscrive RF/NV senza backup possibile. Nordic: 4 fili →
Core found→ dump → flash. La toolchain aperta è un asset di business, non solo tecnico.
🔗 Risorse
- 🛒 AliExpress — Holyiot 25008
- 📺 uAmpHome — Hacking a Cheap Bluetooth Beacon into a DIY Matter Button
- 💻 GitHub — micro_matter_button (binari precompilati + guida)
- 📖 Zephyr — board holyiot_25008
- 💻 GitHub — firmware Visla beacon (Zephyr, nRF54L15)
- 💻 GitHub — ricevitore campanello (XIAO nRF52840 + OLED + buzzer)
- Banco Visla:
test-moduli/holyiot-beacon-zephyr/