Come funziona BoardPilot
Per chi vuole sapere cosa succede dentro, e come verificarlo. Tutto quello che segue è come funziona il codice oggi, con il link al file che lo fa. Il progetto è source-available su GitHub.
L’architettura in un minuto
BoardPilot è un’app desktop Electron scritta in TypeScript. Il processo principale gestisce tutto ciò che tocca il mondo esterno: porte seriali USB, gli strumenti dei chip (esptool, avrdude, picotool, STM32CubeProgrammer, nrfjprog, Teensy Loader), i file, le chiavi API e le chiamate all’IA. Il renderer è l’interfaccia React con la vista 3D three.js. Si parlano attraverso una piccola API tipizzata (il bridge di preload); l’interfaccia non vede mai una chiave, un percorso che non hai scelto o una porta seriale.
Quasi tutto ciò che l’app sa sono dati, non codice: 13 file di schede, 383 file di componenti, i flussi guidati e i modelli di progetto. Le stesse funzioni pure (scelta dei pin, controlli di cablaggio e del codice, lista della spesa, renderer dei display) girano nell’app, nei test e nella demo nel browser.
Una scheda è un file, scritto dal suo datasheet
Ogni scheda (boards/<id>.json) elenca ogni pin dei connettori: id ed etichetta stampata, numero GPIO, funzioni (canale ADC, UART, SPI, I2C…), la tensione massima che accetta e i flag su cui si basano le regole:
flash: collegato alla memoria flash (ESP32 GPIO da 6 a 11), mai utilizzabile;input_only: non può pilotare un’uscita, spesso senza pull-up interno (ESP32 GPIO da 34 a 39);strapping/strapping_critical: il livello al reset cambia l’avvio del chip (ESP32 GPIO 0, 2, 5, 12, 15);adc1/adc2: canali ADC; sull’ESP32 l’ADC2 smette di funzionare con il Wi-Fi acceso;uart0,usb,swd,reserved: usati dalla scheda stessa;five_volt_tolerant: accetta ingressi a 5 V anche se il chip lavora a 3,3 V.
La scheda contiene anche le sue regole: i pin I2C predefiniti (e se si possono spostare), i pin del bus SPI hardware, un elenco ordinato di pin sicuri per uso generale (capaci di uscita, senza effetti all’avvio, non usati da USB o dal debugger), i pin ADC in ordine di preferenza e i pin di alimentazione con la tensione che forniscono. Ogni dato ha la sua sezione di datasheet in boards/<id>.sources.md, e scripts/check-boards.mjs rifiuta un file di scheda con un pin mancante, un duplicato o una regola che punta a un pin inesistente.
boards/esp32-devkitc-30.json · boards/esp32-devkitc-30.sources.md · scripts/check-boards.mjs
Anche un componente è un file
Ogni componente (parts/<id>.json) elenca i suoi pin nell’ordine stampato sul modulo, con un ruolo per ciascuno: power, ground, i2c_sda, i2c_scl, spi_mosi, spi_miso, spi_sck, spi_cs, digital_in (lo pilota la scheda), digital_out (lo pilota il componente), analog_out, onewire, int e passive (non va alla scheda: un carico, l’alimentazione propria di un modulo). Indica anche l’intervallo di alimentazione, ogni indirizzo I2C e il pin che lo seleziona, se il modulo ha i suoi pull-up, il registro dell’ID del chip e il valore atteso, le trappole note (ognuna con la sua fonte) e, quando un datasheet le riporta, le correnti. Le note dei pin portano dati di cablaggio come “tramite 10 kΩ verso GND” o “aggiungi un diodo di ricircolo sulla bobina”.
Come si scelgono i pin
“Assegna pin sicuri”, i modelli di progetto e le proposte dell’IA usano tutti la stessa funzione. Scorre i componenti in ordine e, per ogni pin di componente ancora senza filo, sceglie un pin della scheda in base al ruolo. I pin già collegati, da te o dall’assistente, non vengono mai spostati.
| Ruolo del pin del componente | Pin della scheda scelto |
|---|---|
power | Un pin di alimentazione la cui tensione rientra nell’intervallo del componente, prima la tensione logica della scheda (3V3 per un sensore 3,3–5 V su un ESP32; 5V per un componente solo a 5 V). |
ground | Un pin GND; più componenti vengono distribuiti sui pin GND della scheda. |
i2c_sda, i2c_scl | I pin I2C predefiniti della scheda. Tutti i componenti I2C condividono quel bus, e quei pin restano liberi per lui. |
spi_mosi, spi_miso, spi_sck | I pin SPI hardware della scheda, condivisi da tutti i componenti SPI. |
spi_cs | Il pin CS del bus per il primo componente SPI, il primo pin sicuro libero per ognuno degli altri. |
analog_out | Il primo pin ADC libero, nell’ordine di preferenza della scheda (sull’ESP32 prima l’ADC1, perché l’ADC2 si ferma con il Wi-Fi). |
int | Prima un pin solo ingresso (vanno bene per gli interrupt e altrimenti sono difficili da usare), poi un pin sicuro. |
| altri segnali | Il primo pin libero dell’elenco dei pin sicuri della scheda. |
passive | Non va alla scheda: un carico o l’alimentazione di un modulo si collegano tra componenti, o a mano. |
Il risultato è deterministico: lo stesso disegno sulla stessa scheda ottiene sempre gli stessi pin. Se la scheda finisce i pin, il pin resta scollegato e l’app lo dice.
Cosa controlla il verificatore di cablaggio
Una funzione pura gira a ogni modifica del disegno, che il filo l’abbia disegnato tu, un modello di progetto o l’IA. Ogni risultato appare sul pin o sul filo in 3D, nel registro con la sua fonte (il file della scheda o del componente) e nel banner degli avvisi, con una correzione quando esiste.
| Regola | Cosa trova |
|---|---|
unknown_pin | Un filo verso un pin che la scheda non ha. |
flash_pin | Qualsiasi cosa collegata a un pin della flash. |
voltage_mismatch | Un componente alimentato sopra la sua tensione (5 V su un sensore a 3,3 V), sotto, o un filo di massa su un pin di alimentazione (un cortocircuito). |
wrong_pin_type | Un componente alimentato da un GPIO, un segnale su un pin di alimentazione o di massa. |
missing_power, missing_ground | Un componente con il pin di alimentazione o di massa scollegato. |
output_on_input_only | Un pin che la scheda deve pilotare (un LED, SDA, un chip select) su un pin solo ingresso. |
not_adc | Un’uscita analogica collegata a un pin senza ADC. |
adc2_wifi | Una lettura analogica su un pin ADC2 dell’ESP32, che fallisce con il Wi-Fi acceso. |
strapping_pin | Un componente che può tirare un pin di strapping al reset e impedire l’avvio. |
reserved_pin, uart0_pin | Pin usati dalla scheda stessa: il collegamento seriale USB, l’USB nativo, la porta di debug. |
shared_pin_conflict | Due componenti che pilotano lo stesso pin (bus e alimentazione esclusi). |
i2c_swapped | SDA e SCL invertiti rispetto ai pin I2C della scheda. |
logic_level | Un componente a 3,3 V su una scheda a 5 V, o un’uscita a 5 V su un pin che non tollera 5 V. |
i2c_address_conflict | Due componenti sullo stesso bus allo stesso indirizzo: un avviso quando uno si può spostare (indica il pin dell’indirizzo), un errore quando nessuno può. |
Le stesse regole producono una lista della spesa: due pull-up da 4,7 kΩ quando nessun modulo I2C ha i suoi, un pull-up 1-Wire quando le note del componente lo chiedono, un partitore da 1 kΩ + 2 kΩ per ogni uscita a 5 V su un pin a 3,3 V, un traslatore di livello per le linee bidirezionali e, dalle note dei componenti, diodi di ricircolo sulle bobine e resistenze di partitore per sensori nudi come un LDR.
Come l’IA costruisce un progetto
L’assistente non collega niente da solo. È un modello linguistico con un insieme di strumenti, e dietro ogni strumento il lavoro vero lo fa l’app:
- Contesto. A ogni domanda il modello riceve i dati e i pin della scheda (con i loro flag), il tuo disegno, le schede della libreria dei componenti presenti, il codice del progetto, gli eventi recenti dell’app e le ultime righe del registro.
- Scelta dei componenti.
search_partscerca nella libreria di 383 componenti e restituisce, per ognuno, ciò che li distingue: cosa misura, bus, alimentazione, come si adatta alla logica della scheda, note dei pin, trappole note, corrente.offer_choicesti mostra da 2 a 4 schede; scegli tu. - Proposta.
propose_projectinvia una lista di id della libreria, eventualmente i fili che il modello vuole e i collegamenti tra componenti (una pompa su un modulo MOSFET, un lettore RFID tramite un traslatore di livello). L’app rifiuta gli id sconosciuti (e suggerisce quelli reali più vicini), verifica ogni filo richiesto rispetto alla scheda e al componente, mette prima i collegamenti tra componenti, poi lascia che le regole dei pin sicuri colleghino il resto, infine esegue il verificatore. - Correzione. Gli errori del verificatore, i pin rimasti scollegati e gli extra da comprare tornano al modello, che deve correggere gli errori e richiamare lo strumento prima di rispondere.
- Risposta. La risposta finale è strutturata: il messaggio, un’etichetta di affidabilità (misurato, dalla documentazione, suggerimento), le fonti e i pin o componenti da evidenziare. Il codice fa rispettare le regole di onestà: “misurato” è permesso solo se una misura è stata fatta in questo turno o è nel registro; le evidenziazioni che non esistono nel disegno vengono scartate.
- Applicazione. Non cambia nulla finché non premi Applica. Un nuovo progetto si apre nella sua scheda se quello aperto ha già componenti; Annulla riporta il disegno di prima. Scrivere sulla scheda dalla chat non è mai possibile: il modello può solo chiedere, e l’app mostra la sua finestra di conferma.
Il modello ha anche strumenti per eseguire le azioni dell’app (backup della scheda, aprire il monitor, cambiare un’impostazione), chiedere codice nel pannello Codice, cercare nella memoria dei tuoi progetti e progettare cosa mostra un display.
app/main/ai/tools.ts · shared/projectBuilder.ts · app/main/ai/prompt.ts · app/main/ai/assistant.ts
Codice che corrisponde al disegno
Il codice di partenza nasce dal disegno: ogni pin diventa un #define con il pin della scheda a cui è collegato, i bus partono sui pin collegati e i progetti di partenza Pico SDK, ESP-IDF e STM32 HAL leggono all’avvio l’ID del chip di ogni componente che nella libreria ne ha uno. Il verificatore del codice confronta qualsiasi sketch (generato, incollato o scritto dall’IA) con il disegno: pin di Wire.begin() diversi dal cablaggio, pinMode(…, OUTPUT) su un pin solo ingresso, analogRead su un pin senza ADC o sull’ADC2 mentre lo sketch avvia il Wi-Fi, un LED collegato a un pin mentre il codice ne pilota un altro, una velocità seriale diversa da quella del monitor. I risultati indicano la riga di codice e il pin in 3D.
shared/sketch.ts · shared/codeCheck.ts · shared/starter/index.ts
Misure, non supposizioni
Per vedere una scheda reale, BoardPilot installa un piccolo firmware, l’agente diagnostico (dopo una conferma e un backup completo della flash). L’app gli parla in seriale con una riga JSON per richiesta e risposta: leggere tutti i pin, verificare i pull-up esterni (pull interni spenti, poi lettura del livello), scansionare il bus I2C, leggere registri, leggere un pin ADC, pilotare un pin (solo dopo la tua conferma). L’agente stesso rifiuta i pin della flash e le uscite sui pin solo ingresso.
Il trucco classico è il test di scambio: sui chip in cui i pin I2C si possono spostare via software, il bus viene scansionato una volta come cablato e una volta con SDA e SCL scambiati. Se un dispositivo risponde solo con lo scambio, i fili sono invertiti, e l’app dice quali. Una lettura dell’ID del chip distingue poi un vero BME280 (0x60) da un BMP280 venduto come tale (0x58). La scheda misura la tensione solo sui pin ADC; altrove l’app riporta un livello digitale e non mostra mai una tensione che non ha misurato.
firmware/agent · shared/protocol.ts · flows/debug-sensor-not-responding.ts
Come funziona il simulatore
La scheda simulata implementa la stessa interfaccia di quella reale, quindi ogni schermata, flusso e strumento dell’IA funziona uguale senza hardware. Uno scenario del simulatore contiene due cose: il disegno che vedi e la verità fisica sul banco, che può essere diversa, per esempio con SDA e SCL invertiti sul sensore, un BMP280 dove è disegnato un BME280, un sensore senza alimentazione, una porta occupata o una velocità seriale sbagliata. L’agente simulato risponde ai comandi dalla verità fisica, con le stesse regole del firmware reale, così i flussi di debug trovano il guasto come farebbero su un banco vero.
I modelli di progetto girano anche come simulazione passo per passo del programma: i pin cambiano, i valori si muovono, i display mostrano testo e ogni passo è collegato alla sua riga di codice. Tutto ciò che è simulato è etichettato come tale e mai mescolato ai dati misurati.
app/main/sim/simDriver.ts · app/main/sim/simWorld.ts · app/main/sim/scenarios/weather-station-swapped.json · shared/templates.ts
Display Studio: pixel per pixel
Il progetto di un display è un dato: schermate di testi, valori, barre, grafici, indicatori a lancetta, icone e pattern di prova, con cambi a tempo o a condizione e valori simulati. L’app lo disegna in un framebuffer alla risoluzione e profondità di colore reali del pannello (RGB565 sui TFT a colori, un bit su OLED ed e-paper), con gli algoritmi di linea e cerchio di Adafruit GFX, un font 5×7 e calcoli dei pattern a interi. Lo sketch Arduino generato porta lo stesso font, le stesse icone e gli stessi calcoli e disegna con Adafruit GFX.
Per dimostrare che coincidono, un test compila sul computer la vera libreria Adafruit GFX con il codice di disegno generato e confronta ogni pixel dei suoi fotogrammi con quelli dell’app, su quattro pannelli e due rotazioni; la valutazione dal vivo dell’IA fa lo stesso per ogni display che l’IA progetta. Gli sketch si compilano anche per ESP32, ESP32-S3, Arduino Uno e Raspberry Pi Pico.
shared/displayRender.ts · shared/displayCode.ts · tests/display.test.ts
Come viene verificato
- Oltre 3.200 test unitari (Vitest): file di schede e componenti, scelta dei pin e regole di cablaggio su ogni modello di progetto su ogni scheda, il verificatore del codice, i flussi contro il simulatore, il protocollo dell’agente, il renderer e il generatore di codice dei display.
- Test end-to-end (Playwright) guidano l’app compilata in modalità simulatore attraverso ogni scenario: collegamento, nuovo progetto, flash con backup, ogni flusso di debug, monitor, test hardware, report, esportazione PDF, un’azione dell’IA, una conversazione registrata.
- Una valutazione dal vivo dell’IA invia 26 richieste all’assistente vero con un modello piccolo che fa l’utente, valuta ogni conversazione con le regole dell’app stessa, compila i display progettati dall’IA per il confronto dei pixel e fa rileggere ogni conversazione a un secondo modello per i consigli pericolosi. L’ultimo giro ha superato 26 su 26 con Claude Sonnet 5.5; ogni giro, con cosa ha trovato e cosa è stato corretto, è pubblicato.