Corso ESP-IDF, puntata 2: installare l’ambiente

Corso ESP-IDF, puntata 2: installare l’ambiente

Io: Cheap, oggi si installa ESP-IDF. Mi preparo a un pomeriggio di download e variabili d’ambiente?

Cheap: Dalla versione 6.0 c’è un installatore apposta, l’EIM. Gratis, con finestra grafica o da riga di comando. Tu scegli, lui scarica.

Io: E l’adattatore USB-seriale da cassetto? Lo tiro fuori?

Cheap: Lascialo dov’è. Il C6 ha la seriale USB dentro il chip: un cavo e basta. Meno pezzi, meno spese.

Io: Poi?

Cheap: Poi idf.py: un comando per creare, configurare, compilare, scrivere e ascoltare la scheda. E una scorciatoia da imparare a memoria: Ctrl+] per uscire dal monitor.

Io: Perché proprio quella?

Cheap: Perché tutti la prima volta premono Ctrl+C. E non succede quello che si aspettano.

In questa puntata

Obiettivi

  • Installare ESP-IDF v6.1 su Windows, Linux o macOS con il metodo che la documentazione raccomanda: l’ESP-IDF Installation Manager (EIM).
  • Attivare l’ambiente nel terminale e sapere a cosa servono l’estensione per VS Code ed Espressif-IDE.
  • Usare idf.py per l’intero ciclo: create-project, set-target, menuconfig, build, flash, monitor, size, fullclean.
  • Capire che cosa produce la build e dove finisce ogni file nella flash.
  • Riconoscere le porte seriali della ESP32-C6-DevKitC-1, compresa la USB Serial/JTAG integrata, e risolvere gli errori più comuni.

Prerequisiti: Puntata 1 “ESP-IDF in una mappa” (pubblicazione in arrivo); un PC con Windows, Linux o macOS; una ESP32-C6-DevKitC-1 e un cavo USB-C che porti anche i dati, non solo l’alimentazione.

Che cosa installiamo davvero

La documentazione v6.1 elenca tre ingredienti:

  • la toolchain, cioè compilatore, linker e debugger per il chip (per il C6, che è RISC-V, gli strumenti riscv32-esp-elf);
  • gli strumenti di build: CMake, che descrive il progetto, e Ninja, che esegue la compilazione;
  • ESP-IDF vero e proprio: librerie, driver, sorgenti e gli script Python che tengono insieme il resto.

Python fa da collante: idf.py, esptool e il monitor sono scritti in Python. Dalla 6.0 servono almeno Python 3.10 e CMake 3.22.1.

Il metodo raccomandato: ESP-IDF Installation Manager

Per la v6.1 la guida “Get Started” indica un solo strumento per installare ESP-IDF, la toolchain e gli strumenti di build: l’ESP-IDF Installation Manager, in breve EIM. Esiste in due forme:

  • GUI (interfaccia grafica): la scelta consigliata per la maggior parte delle persone;
  • CLI (riga di comando): comoda per l’integrazione continua (CI/CD) e le installazioni automatiche.

Entrambe si scaricano dalla pagina di download dell’EIM in versione online (scarica ciò che serve durante l’installazione) oppure offline (pacchetto completo, per PC senza Internet).

Installare l’EIM

Sistema Prerequisiti Installare l’EIM con un gestore di pacchetti
Windows Git e Python: l’EIM li controlla e propone di installarli se mancano winget install Espressif.EIM (GUI) oppure winget install Espressif.EIM-CLI
Linux (Debian, Ubuntu) se usi APT, nessuno da installare a mano aggiungi il repository APT di Espressif, poi sudo apt install eim (GUI e CLI) o sudo apt install eim-cli
Linux (Fedora e simili) quelli elencati nella documentazione dell’EIM aggiungi il repository DNF, poi sudo dnf install eim o sudo dnf install eim-cli
macOS brew install libgcrypt glib pixman sdl2 libslirp dfu-util cmake python brew tap espressif/eim, poi brew install --cask eim-gui o brew install eim

Installare ESP-IDF con l’EIM

Dalla GUI: New Installation → Start Installation, poi Easy Installation → Start Easy Installation; superati i controlli compare Ready to Install e l’EIM installa l’ultima versione stabile con le impostazioni predefinite. Per scegliere la versione o la cartella c’è invece Custom Installation.

Dalla CLI i comandi documentati sono tre:

eim install              # ultima versione stabile, impostazioni predefinite
eim wizard               # procedura guidata interattiva
eim install -i v6.1      # una versione precisa (la guida usa come esempio v5.4.2)

Attivare l’ambiente

Prima di usare idf.py bisogna attivare l’ambiente nel terminale: percorsi della toolchain, ambiente virtuale Python, variabile IDF_PATH. Le strade documentate sono queste:

  • dalla GUI dell’EIM: Manage Installations → Open Dashboard, scegli la versione e premi Open IDF Terminal;
  • su Windows, dopo l’installazione da CLI: l’EIM mette sul desktop un collegamento che apre PowerShell con l’ambiente già pronto (la guida cita come esempio IDF_v5.4.2_Powershell);
  • su Linux e macOS: alla fine l’EIM stampa il comando da eseguire, del tipo source ".../tools/activate_idf_v5.4.2.sh" (anche qui l’esempio della guida è per la 5.4.2).

Il metodo precedente, con gli script install.bat, install.ps1 o install.sh e poi export nella cartella di ESP-IDF, è ancora documentato come legacy e funziona. Per un’installazione nuova, però, la strada è l’EIM.

Gli IDE: VS Code ed Espressif-IDE

Le versioni installate con l’EIM si possono usare in due ambienti grafici:

  • Visual Studio Code con l’ESP-IDF Extension for VS Code, che permette di sviluppare, compilare, scrivere la flash e monitorare la scheda senza uscire dall’editor;
  • Espressif-IDE, basato su Eclipse CDT, con i plugin IDF già inclusi.

Ciascuno ha la sua documentazione, linkata in fondo. Nel corso useremo la riga di comando: è uguale su tutti i sistemi e non cambia quando cambiano i menu.

idf.py: un comando per tutto il ciclo

idf.py guida CMake, Ninja, esptool e il monitor. Va lanciato dentro la cartella del progetto, cioè quella con il CMakeLists.txt principale, oppure indicandola con -C cartella.

Comando Che cosa fa
idf.py create-project nome crea un progetto minimo dal modello tools/templates/sample_project: CMakeLists.txt, main/CMakeLists.txt e main/nome.c con un app_main() vuoto
idf.py set-target esp32c6 sceglie il chip; svuota la cartella build e rigenera sdkconfig (il vecchio diventa sdkconfig.old)
idf.py menuconfig apre il menu di configurazione testuale e salva le scelte in sdkconfig
idf.py build configura e compila; è incrementale: se nulla è cambiato non rifà nulla
idf.py -p PORTA flash compila se serve e scrive bootloader, tabella partizioni e app
idf.py -p PORTA monitor apre il monitor seriale (IDF Monitor)
idf.py size riassume l’occupazione di RAM e flash; size-components e size-files la dettagliano per componente e per file
idf.py fullclean cancella tutto il contenuto della cartella build; la configurazione (sdkconfig) resta

Una sessione tipica, dal nulla alla scheda che parla:

idf.py create-project primo_progetto
cd primo_progetto
idf.py set-target esp32c6
idf.py menuconfig                 # facoltativo: per hello world vanno bene i default
idf.py -p COM5 flash monitor      # COM5 è un esempio: usa la tua porta

Più comandi nella stessa riga vengono eseguiti nell’ordine giusto: flash monitor compila, scrive e apre il monitor. Se la porta è sempre la stessa, puoi metterla nella variabile d’ambiente ESPPORT e omettere -p; se non indichi nessuna porta, idf.py prova da solo quelle USB disponibili.

Il target, prima di tutto

Il target conta più di quanto sembri. Se non lo fissi in nessun modo (né con set-target, né con la variabile IDF_TARGET, né con CONFIG_IDF_TARGET in sdkconfig.defaults), il sistema di build compila per il chip ESP32 originale. Il programma compila senza errori, poi al momento di scriverlo sul C6 esptool si ferma con un messaggio del tipo “This chip is ESP32-C6, not ESP32. Wrong chip argument?”. Per questo, in ogni progetto nuovo, il primo comando è idf.py set-target esp32c6. Nel corso il target non è scritto nei progetti degli esercizi: lo fissi tu, una volta, con quel comando.

sdkconfig e sdkconfig.defaults

Tutte le opzioni di configurazione (Kconfig) finiscono nel file sdkconfig, che menuconfig modifica. Il file sdkconfig.defaults, se c’è, contiene solo le opzioni che vuoi diverse dai valori di ESP-IDF. Attenzione al dettaglio: sdkconfig.defaults viene usato quando sdkconfig si crea da zero, oppure per le opzioni che sdkconfig non contiene ancora. Se cambi un valore in sdkconfig.defaults dopo la prima build, devi cancellare sdkconfig (o rifare set-target) perché abbia effetto. Lo vedrai nell’esercizio 02.2. Anatomia completa del progetto nella Puntata 3 (pubblicazione in arrivo).

Flash veloce

Dalla 6.1 idf.py flash salva nella cartella build una copia di ciò che ha scritto (file *_flashed.bin); la volta dopo riscrive solo i settori cambiati e verifica la flash, e se la verifica fallisce rifà tutto. Per forzare una scrittura completa c’è idf.py flash -a (o --all): la guida la consiglia per schede nuove o appena cancellate con idf.py -p PORTA erase-flash.

Che cosa produce la build

Una build riuscita termina con “Project build complete” e lascia nella cartella build diversi file. Quelli da conoscere subito:

Schema a blocchi: i file del progetto (CMakeLists.txt, main/*.c, sdkconfig) entrano in idf.py build; nella cartella build escono bootloader/bootloader.bin, partition_table/partition-table.bin, primo_progetto.bin, i file .elf e .map e flasher_args.json; idf.py flash scrive nella flash del C6 il bootloader a 0x0, la tabella partizioni a 0x8000 e l'app a 0x10000; idf.py monitor legge la seriale e usa il file .elf per decodificare gli indirizzi
Il ciclo build → flash → monitor e i file che produce. Gli indirizzi sono quelli che esptool usa sul C6 con la tabella partizioni predefinita.
  • bootloader/bootloader.bin: il bootloader di secondo stadio, compilato come un piccolo progetto a parte. Sul C6 va all’indirizzo 0x0 della flash.
  • partition_table/partition-table.bin: la tabella delle partizioni, all’indirizzo predefinito 0x8000. Con la tabella predefinita “Single factory app, no OTA” contiene una partizione nvs, una phy_init e l’app factory a 0x10000.
  • nome_progetto.bin: l’applicazione, a 0x10000. Il nome viene dalla riga project() del CMakeLists.txt.
  • nome_progetto.elf e nome_progetto.map: il programma con tutti i simboli e la mappa del linker. Non si scrivono sulla scheda, ma servono al monitor per tradurre gli indirizzi in file e riga, e a idf.py size per i conti.
  • flash_project_args, flash_app_args, flasher_args.json: dove va ciascun file. Con questi si può scrivere la scheda anche senza idf.py, per esempio con esptool ... write-flash @build/flash_project_args.

Il monitor seriale

idf.py monitor avvia IDF Monitor, un terminale seriale con qualche abilità in più. Si collega a 115 200 baud, la velocità predefinita della console del C6, e colora le righe di log secondo il livello (errore, avviso, informazione…), riconoscendolo dalla lettera iniziale. Quando il chip va in panic e stampa indirizzi di codice, il monitor li cerca nel file .elf e scrive accanto file e numero di riga. Dettagli nella Puntata 5 (pubblicazione in arrivo).

Tutti i tasti premuti vanno alla scheda, tranne due: Ctrl+] e Ctrl+T. Il primo esce; il secondo apre un piccolo menu, cioè va seguito da un altro tasto:

Scorciatoia Azione
Ctrl+] esce dal monitor
Ctrl+T poi Ctrl+X (o X) esce anch’essa: utile se sulla tua tastiera Ctrl+] è scomodo
Ctrl+T poi Ctrl+H (o H) mostra tutte le scorciatoie
Ctrl+T poi Ctrl+R resetta la scheda tramite la linea RTS, se collegata
Ctrl+T poi Ctrl+F sospende il monitor, ricompila e riscrive tutto, poi riprende
Ctrl+T poi Ctrl+A (o A) come sopra, ma solo l’app: più veloce
Ctrl+T poi Ctrl+Y ferma o riprende la stampa a schermo
Ctrl+T poi Ctrl+L avvia o ferma il salvataggio dell’uscita su file
Ctrl+T poi Ctrl+I (o I) aggiunge o toglie l’orario all’inizio di ogni riga

E Ctrl+C? Nel monitor non esce: interrompe l’applicazione e avvia GDB, il debugger, e per farlo richiede un’opzione (CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME) che per impostazione predefinita è disattivata. Quando premi Invio, il monitor manda alla scheda un CR (ritorno carrello): è il valore predefinito per i chip reali, e ci servirà nell’esercizio 02.3.

Porte seriali e USB Serial/JTAG del C6

La ESP32-C6-DevKitC-1 ha due connettori USB-C, e tutti e due servono ad alimentare la scheda e a scrivere la flash:

  • “USB-to-UART”: passa da un ponte USB-seriale montato sulla scheda, collegato alla UART0 del chip;
  • “ESP32-C6 USB”: va direttamente ai piedini USB del chip, GPIO13 (D+) e GPIO12 (D−), dove c’è il controller USB Serial/JTAG.
Schema: sul PC idf.py, OpenOCD e due porte (seriale USB, che appare come COMx, /dev/ttyACM* o /dev/cu.*, e ponte UART, COMy o /dev/ttyUSB*); sulla DevKitC-1 il connettore ESP32-C6 USB va ai piedini GPIO13 e GPIO12 del controller USB Serial/JTAG del chip, che offre seriale CDC-ACM e JTAG; il connettore USB-to-UART passa per il ponte USB-UART e arriva alla UART0, console predefinita
Le due strade dal PC al chip. Con il connettore “ESP32-C6 USB” il ponte non serve: la seriale è dentro il chip.

Il controller USB Serial/JTAG è un dispositivo USB a funzione fissa, realizzato interamente in hardware. Offre tre cose: una console seriale bidirezionale (la parte CDC-ACM, cioè la classe USB delle porte seriali), la scrittura della flash con esptool e idf.py flash, e il debug JTAG, anche in contemporanea con la seriale (Puntata 6, pubblicazione in arrivo). Non si riprogramma per fare altro: per dispositivi USB personalizzati servono i chip con USB OTG.

Come appare sul PC

Il sistema operativo vede la parte seriale come una porta qualsiasi:

  • Windows: una porta COM in Gestione dispositivi; scollega e ricollega la scheda e guarda quale porta sparisce e ricompare.
  • Linux: /dev/ttyACM* (il ponte UART appare invece come /dev/ttyUSB*). Lancia ls /dev/tty* prima e dopo aver collegato la scheda.
  • macOS: /dev/cu*; anche qui ls /dev/cu.* prima e dopo.

Per la seriale di solito non serve installare nulla. Per la parte JTAG, su Windows possono servire i driver USB: la documentazione indica il pulsante Install Drivers nella dashboard dell’EIM, oppure il comando eim install-drivers. Su Linux, per il JTAG, vanno aggiunte le regole udev di OpenOCD.

Quale porta per la console

Nella v6.1 il canale della console si sceglie in Component config → ESP-STDIO → Channel for console output. Sul C6 il predefinito è la UART0, con una console secondaria sulla USB Serial/JTAG (opzione CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG). Quindi i log escono da entrambi i connettori, ma la secondaria è solo in uscita: per leggere tasti dalla USB bisogna fare della USB Serial/JTAG la console principale, con CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y. È quello che farai nell’esercizio 02.3.

Di norma il controller USB Serial/JTAG mette da solo il chip in modalità download. Se non ci riesce (alla primissima scrittura, se l’app ha riconfigurato i piedini USB o se il chip è in deep sleep, perché in quel caso il dispositivo USB sparisce dal PC) si fa a mano: tieni premuto BOOT, premi e rilascia RESET, poi rilascia BOOT. Il pulsante BOOT è collegato a GPIO9, il piedino che sceglie la modalità di avvio.

Errori comuni

Sintomo Causa probabile Rimedio
flash non apre la porta la porta è occupata: un monitor ancora aperto, PuTTY, un altro terminale chiudi ogni programma che usa la porta; esci dal monitor con Ctrl+], non chiudendo la finestra
“Permission denied” su Linux l’utente non può leggere e scrivere la porta seriale sudo usermod -a -G dialout $USER (su Arch il gruppo è uucp), poi esci e rientra nella sessione
“This chip is ESP32-C6, not ESP32” target mai impostato: il progetto è compilato per ESP32 idf.py set-target esp32c6, poi di nuovo flash
“Failed to connect” il chip non entra in modalità download BOOT premuto, RESET, rilascia BOOT; poi ripeti il comando
Errori strani di CMake o di percorso spazi nel percorso del progetto o di ESP-IDF sposta il progetto in una cartella senza spazi
Nessuna riga sul monitor porta sbagliata, console su un altro canale, o livello di log a “No output” controlla la porta, Channel for console output e Log Level in menuconfig; premi RESET
idf.py sconosciuto ambiente non attivato in questo terminale apri il terminale dall’EIM o esegui lo script di attivazione

Esercizi

★ Esercizio 02.1 — Il mio primo progetto

Crea un progetto con idf.py create-project primo_progetto (il modello è tools/templates/sample_project; nella v6.1 tra gli esempi di examples/get-started trovi invece hello_world e blink). Imposta il target esp32c6 e scrivi un app_main() che all’avvio stampi un messaggio personalizzato con il nome del chip, preso da CONFIG_IDF_TARGET, e poi, una volta al secondo, una riga Contatore: N con N che cresce da 0. Compila, scrivi la scheda e apri il monitor con un solo comando. Guarda anche idf.py size e il contenuto della cartella build.

Risultato atteso

I (296) main_task: Calling app_main()
Ciao da Cheap! Primo progetto ESP-IDF su esp32c6.
Contatore: 0
Contatore: 1
Contatore: 2
...

I numeri tra parentesi sono millisecondi dall’avvio e possono essere diversi. Il comportamento sulla scheda è da provare al banco.

Suggerimenti

  • vTaskDelay() vuole tick, non millisecondi: converti con pdMS_TO_TICKS(1000).
  • Per stampare un uint32_t in modo portabile usa PRIu32 da <inttypes.h>.
  • CONFIG_IDF_TARGET sta in sdkconfig.h: includilo.
  • Comando unico: idf.py -p PORTA flash monitor; esci con Ctrl+].

★★ Esercizio 02.2 — Livelli di log

Nello stesso stile, scrivi un ciclo che ogni secondo produce un messaggio INFO (ESP_LOGI) con il numero del giro, ogni cinque giri un WARN (ESP_LOGW), a ogni giro un DEBUG (ESP_LOGD) con i giri che mancano al prossimo avviso, e un VERBOSE (ESP_LOGV). All’avvio stampa il valore di CONFIG_LOG_DEFAULT_LEVEL. Il livello di log predefinito va scelto in sdkconfig.defaults con l’opzione CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y (menu Component config → Log → Log Level → Default log verbosity). Poi prova CONFIG_LOG_DEFAULT_LEVEL_INFO=y e CONFIG_LOG_DEFAULT_LEVEL_WARN=y e annota cosa cambia.

Risultato atteso (livello Debug)

I (310) livelli: Livello di log predefinito: 4
I (310) livelli: Giro 1
D (310) livelli: Dettaglio: prossimo avviso fra 4 giri
I (1310) livelli: Giro 2
D (1310) livelli: Dettaglio: prossimo avviso fra 3 giri
...
I (4310) livelli: Giro 5
W (4310) livelli: Avviso: sono passati altri 5 giri
D (4310) livelli: Dettaglio: prossimo avviso fra 5 giri

Con Debug possono comparire righe D anche dai componenti di sistema durante l’avvio, perché il livello predefinito vale per tutti i tag. Con Info spariscono tutte le righe D; con Warning restano solo le W. La riga Verbose non compare mai, a meno di scegliere Verbose come livello. Il comportamento sulla scheda è da provare al banco.

Suggerimenti

  • sdkconfig.defaults vale solo quando sdkconfig non c’è o non contiene l’opzione: dopo ogni modifica cancella sdkconfig (oppure rifai idf.py set-target esp32c6) e ricompila.
  • Il “livello massimo” (CONFIG_LOG_MAXIMUM_LEVEL) è, per impostazione predefinita, uguale a quello predefinito: i messaggi più dettagliati non entrano nemmeno nel firmware. Confronta idf.py size tra Debug e Warning.
  • Il formato di riga è livello, millisecondi tra parentesi, tag, messaggio: I (56) boot: ....

★★★ Esercizio 02.3 — Eco maiuscolo

Collega il connettore “ESP32-C6 USB” e scrivi un programma che legge i caratteri digitati nel monitor e li rimanda subito convertiti in maiuscolo; quando premi Invio, ristampa la riga completa con la sua lunghezza. Fai della USB Serial/JTAG la console principale con CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y in sdkconfig.defaults. Per leggere segui l’esempio ufficiale examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo: installa il driver con usb_serial_jtag_driver_install() e leggi con usb_serial_jtag_read_bytes(). Poi chiama usb_serial_jtag_vfs_use_driver(), così anche printf e i log passano dal driver.

Risultato atteso (digitando “ciao cheap” e Invio)

I (305) eco: Pronto: scrivi qualcosa e premi Invio.
CIAO CHEAP
Riga (10 caratteri): CIAO CHEAP

Il comportamento sulla scheda è da provare al banco.

Suggerimenti

  • Nel main/CMakeLists.txt aggiungi PRIV_REQUIRES esp_driver_usb_serial_jtag, come nell’esempio di riferimento.
  • Il monitor manda CR quando premi Invio, altri terminali LF o CR+LF: gestisci tutti e due i caratteri e ignora le righe vuote.
  • toupper() e isprint() di <ctype.h> lavorano sui caratteri ASCII; le lettere accentate arrivano come più byte UTF-8 e conviene lasciarle stare.
  • Limita la riga a un buffer fisso (per esempio 64 caratteri) e scarta i caratteri in più.
  • Sfida nella sfida: gestisci il backspace (0x08, e 0x7F che molti terminali mandano al suo posto) rimandando "\b \b".

Le soluzioni complete sono nello ZIP degli esercizi del corso, cartella 02-installare-ambiente.

Verifica

  1. Qual è il metodo d’installazione raccomandato per ESP-IDF v6.1 e in quali due forme si presenta?
  2. Che cosa succede se compili un progetto senza aver mai impostato il target, e come te ne accorgi?
  3. Quali file binari scrive idf.py flash sul C6 e a quali indirizzi, con la tabella partizioni predefinita?
  4. Come si esce da IDF Monitor? E che cosa fa invece Ctrl+C?
  5. Con la configurazione predefinita, perché vedi i log anche sul connettore “ESP32-C6 USB” ma non puoi mandare comandi alla scheda da lì?

Riepilogo

  • Dalla 6.0 si installa con l’EIM, in GUI o CLI, online o offline; servono Python 3.10 o più recente e percorsi senza spazi.
  • L’ambiente va attivato in ogni terminale: dalla dashboard dell’EIM, dal collegamento sul desktop o con lo script activate_idf_....
  • idf.py copre l’intero ciclo; il primo comando di ogni progetto è set-target esp32c6.
  • La build produce bootloader (0x0 sul C6), tabella partizioni (0x8000) e app (0x10000), più .elf e .map per monitor e analisi.
  • Nella 6.1 idf.py flash riscrive solo ciò che è cambiato; -a forza la scrittura completa.
  • Il monitor si chiude con Ctrl+]; Ctrl+T apre il menu delle scorciatoie.
  • Il C6 ha la USB Serial/JTAG integrata: seriale, flash e JTAG con un solo cavo. Per leggere tasti da lì serve CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y.

Nella prossima puntata

Apriamo il progetto e guardiamo dentro: i due CMakeLists.txt, i componenti e il componente main, il rapporto tra sdkconfig, sdkconfig.defaults e menuconfig, e come aggiungere opzioni Kconfig nostre al menu di configurazione.

Per approfondire

Corso ESP-IDF di Web App Portal. Testi CC BY-NC-SA 4.0, codice MIT. ESP-IDF è un marchio di Espressif Systems; gli altri marchi appartengono ai rispettivi proprietari.

Riguardo

Imprenditore, maker, MBSE enthusiast e co-autore di Web App Portal. Collegati su LinkedIn.