Tastiera USB HID ESP32: controllare il PC via Wi‑Fi

Vuoi controllare un PC da smartphone senza installare programmi sul computer? Con un ESP32-S2 o ESP32-S3 puoi creare una tastiera USB HID ESP32 controllabile via Wi‑Fi: la scheda viene vista da Windows, Linux o macOS come una normale tastiera USB.
Da un telefono, un tablet o un secondo PC è sufficiente aprire la pagina web che gira sull’ESP32 per inviare una frase di testo, utilizzare i tasti funzione, le scorciatoie da tastiera, i comandi di spegnimento e il Wake USB.

Nella versione finale ho implementato anche la libreria di tzapu/WiFiManager: configuri una rete Wi‑Fi una sola volta dal browser e, ai successivi avvii, l’ESP32 torna automaticamente online senza modificare o ricompilare il codice. Veramente comodo!

Come funziona una tastiera USB HID ESP32

HID significa Human Interface Device: è lo standard USB usato da tastiere, mouse, gamepad e molte altre periferiche che il sistema operativo riconosce senza driver speciali.

L’ESP32 non invia comandi al PC tramite un’app remota, Remote Desktop o software da installare. Fa una cosa molto più semplice: si presenta al computer come una tastiera USB reale. Quando premi un tasto nella pagina web, l’ESP32 riceve il comando via Wi‑Fi, lo converte in un report USB HID e il PC lo interpreta come una normale pressione sulla tastiera. In pratica con questo progetto andiamo a creare un vero emulatore di tastiera usb.

Il flusso del progetto è questo:

Una LOLIN S2 Mini trasformata in una tastiera USB HID controllabile da smartphone tramite Wi-Fi.
Una LOLIN S2 Mini trasformata in una tastiera USB HID controllabile da smartphone tramite Wi-Fi.
  1. Apri dal telefono la pagina web della tastiera.
  2. Premi un tasto, una scorciatoia o invii un testo.
  3. Il browser manda il comando all’ESP32 attraverso la rete Wi‑Fi.
  4. L’ESP32 converte il comando nel relativo codice USB HID.
  5. Il PC collegato via USB riceve il tasto come se fosse stato premuto su una tastiera fisica.

Il vantaggio è notevole: sul PC da controllare non devi installare nulla. Collegando la scheda via USB, Windows, Linux e macOS la trattano come una periferica HID standard.

Perché realizzare una tastiera USB HID ESP32

L’idea nasce da un’esigenza banale ma reale: avere un PC distante, collegato a una TV, infilato in un mobile, montato in un rack o usato come server domestico, e non voler cercare una tastiera fisica ogni volta che devi digitare qualcosa.

Ecco alcuni utilizzi pratici e legittimi:

  • Accendere tramite il Wake USB il proprio PC.
  • Gestire un mini-PC o un server headless nel proprio home-lab.
  • Inviare comandi rapidi a un PC collegato a una Smart TV.
  • Usare shortcut per programmi specifici, presentazioni, media center o automazioni locali.
  • Creare una tastiera touch semplificata per esigenze di accessibilità.
  • Realizzare una postazione kiosk o una demo didattica.
  • Studiare il funzionamento di USB HID.

Non sostituisce Remote Desktop quando devi vedere lo schermo o amministrare un sistema complesso. È invece perfetta quando serve soltanto inviare comandi al computer: una password, una ricerca, un comando rapido, Ctrl+Alt+Canc o una combinazione che normalmente richiederebbe di essere fisicamente davanti al PC.

E sì, può essere molto utile anche per non alzarsi dal divano. L’efficienza energetica del corpo umano è una priorità sottovalutata… 🤣

Hardware per una tastiera USB HID ESP32

L’hardware richiesto è ridotto al minimo:

  • Una scheda ESP32-S2 oppure ESP32-S3.
  • Un cavo USB dati, se la scheda non ha un connettore USB-A maschio integrato.
  • Uno smartphone, tablet o PC da usare per aprire la pagina web.
  • Una rete Wi‑Fi esistente per la versione con WiFiManager.

Per questo progetto ho verificato il funzionamento con due dispositivi ESP32-S3 DevKitC e con LOLIN S2 Mini. Entrambe le schede sono adatte.
L’ESP32-S3 è più potente e offre anche il Bluetooth. L’ESP32-S2 è invece una soluzione molto compatta ed economica, con Wi-Fi e USB OTG nativa. La LOLIN S2 Mini, in particolare, mantiene un formato vicino al D1 Mini, quindi è interessante quando lo spazio conta. Se vi ricordate infatti l’avevo usato anche per realizzare il microfono spia Wi-Fi per le sue ridotte dimensioni

Se l’obiettivo è collegare il dispositivo direttamente alla porta USB del PC senza cavi, conviene valutare una board ESP32-S3 in formato dongle USB-A. Il firmware resta concettualmente lo stesso: cambia solo il formato fisico della scheda.

Quale ESP32 scegliere per USB HID

Non tutti gli ESP32 possono funzionare come tastiera USB. La caratteristica decisiva è la presenza del controller USB OTG nativo, cioè un controller programmabile che può comportarsi come dispositivo USB HID.

Scheda o chipTastiera USB HIDNote
ESP32-S3 DevKitCOttima scelta per sviluppo e test
ESP32-S3 ZeroPiccola e compatta, con USB OTG nativa
ESP32-S3 MiniCompatibile se usa la USB nativa del chip
ESP32-S2 Mini / LOLIN S2 MiniTestata nel progetto, compatta ed economica
ESP32-S3 T-DongleInteressante in formato chiavetta USB
ESP32-C3NoHa USB Serial/JTAG, non USB OTG HID programmabile
ESP32 classicoNoRichiede hardware USB aggiuntivo per emulare una tastiera

ESP32-S2 ed ESP32-S3 supportano USB OTG in modalità Device e sono quindi adatti alla tastiera USB HID ESP32. L’ESP32-C3, nonostante molte schede espongano una porta USB, ha un controller USB Serial/JTAG a funzione fissa: è utile per upload e debug, ma non può trasformarsi in una tastiera HID.
Per questo progetto il nostro caro ESP8266 non va assolutamente bene.

WiFiManager: configurare la rete senza ricompilare

Nelle prime prove che ho realizzato, l’ESP32 creava sempre una rete Wi‑Fi propria, ad esempio ESP32-Keyboard. Funziona, ed è comoda quando non hai un router disponibile, ma obbliga lo smartphone a cambiare rete ogni volta. Dopo il terzo cambio Wi‑Fi per premere Invio, anche il progetto più bello inizia a sembrare un dispetto.

La versione finale usa WiFiManager, una libreria che consente di salvare le credenziali Wi‑Fi direttamente nella memoria della scheda. Non bisogna più scrivere SSID e password nel codice, né ricompilare lo sketch quando cambia la rete. Questa libreria è molto usata nei miei progetti perché rende i progetti più professionali e semplici da utilizzare come il robot o il rilevatore di fumi smart.

Il funzionamento è questo:

  1. Al primo avvio l’ESP32 crea una rete temporanea chiamata ESP32-Keyboard-Setup.
  2. Collegati a questa rete dal telefono o dal PC; non richiede password.
  3. Si apre il portale di configurazione. Se non compare automaticamente, vai su http://192.168.4.1.
  4. Scegli dall’elenco la rete Wi‑Fi desiderata.
  5. Inserisci la password e salva.
  6. L’ESP32 memorizza le credenziali, si riavvia o si riconnette e da quel momento entra automaticamente nella rete scelta.
  7. Nel Serial Monitor trovi l’indirizzo IP assegnato dal router, da inserire nel browser per aprire la tastiera virtuale.

WiFiManager usa il metodo autoConnect() per provare prima le credenziali già memorizzate; se non riesce a collegarsi, crea un access point con portale di configurazione.

Nella pagina web della tastiera è presente anche il pulsante Reset Wi‑Fi (nuova rete). Serve a cancellare le credenziali salvate e far ripartire il portale di configurazione al successivo avvio. È utile se cambi router, modifichi la password Wi‑Fi o fai quel classico errore di scrivere una password sbagliata e poi dubitare dell’intero universo.

Funzioni della tastiera USB HID ESP32

La pagina web della versione finale non contiene soltanto un campo testo. È una tastiera virtuale pensata per essere usata da smartphone e tablet e include:

  • Casella di testo per inviare frasi, comandi o stringhe complete.
  • Lettere, numeri e punteggiatura.
  • Layout italiano con caratteri come à, è, é, ì, ò, ù, ç, °, § e £.
  • Tasti funzione da F1 a F12.
  • Tab, Invio, Backspace, Canc, Ins, Home, End, PagSu, PagGiù e Stamp.
  • Frecce direzionali.
  • Modificatori Ctrl, Alt, Shift, Win e AltGr.
  • Combinazioni già pronte: Ctrl+Alt+Canc, Alt+Tab, Alt+F4, Win+D e Win+L.
  • Comandi per sospendere, spegnere e risvegliare il PC.
  • Pulsanti di emergenza per ripristinare il collegamento USB o riavviare l’ESP32.
  • Pulsante per cancellare la configurazione Wi‑Fi e scegliere una nuova rete.

Per ottenere i caratteri italiani corretti, il PC controllato deve avere il layout tastiera impostato su Italiano. L’ESP32 invia i codici fisici HID; è il sistema operativo a tradurli nei caratteri giusti in base al layout attivo.

Tastiera Virtuale HID ESP32 - Lutritech.it

Sospensione, spegnimento e Wake USB

I tasti rossi della pagina non sono semplici macro di Windows. Lo sketch usa la collezione HID System Control, progettata per comandi come power off, standby e wake. È lo stesso tipo di comando che può essere presente sulle tastiere fisiche dotate dei tasti Power e Sleep.

Il pulsante Sospendi PC invia il comando di standby. Il pulsante Spegni PC invia il comando di spegnimento. Il comportamento dipende comunque dalle impostazioni di alimentazione del sistema operativo: su Windows puoi verificare e modificare l’azione assegnata ai pulsanti nelle Opzioni risparmio energia.

Il pulsante Wake USB è più particolare. Quando il PC entra in sospensione, il bus USB può essere sospeso e un normale tasto HID non arriverebbe al computer. Per questo il firmware dichiara la funzione di Remote Wakeup e usa tud_remote_wakeup(), una funzione TinyUSB che invia il segnale di ripresa USB a basso livello. Il remote wakeup è il meccanismo previsto dallo standard USB per permettere a un dispositivo di risvegliare un host quando il bus è sospeso.

Il Wake USB può funzionare solo se tutte queste condizioni sono soddisfatte:

  • Il PC è in sospensione o stand-by, non completamente spento.
  • La porta USB continua ad alimentare l’ESP32 durante lo stand-by.
  • Windows consente al dispositivo HID di riattivare il computer.
  • Il BIOS/UEFI non blocca la riattivazione da USB.

Su Windows, controlla Gestione dispositivi e cerca la scheda Gestione risparmio energia nelle proprietà del dispositivo HID: quando l’opzione è disponibile, abilita “Consenti a questo dispositivo di riattivare il computer”.

Il Wake USB non è però una soluzione garantita per accendere un computer totalmente spento. Per lo stato S5, le alternative affidabili sono Wake-on-LAN oppure un relè/optoisolatore collegato ai pin del pulsante Power della scheda madre. Alcuni BIOS supportano “Power On by USB Keyboard”, ma non è una funzione universale: dipende dal PC, dall’alimentazione standby e dalle impostazioni firmware. Se ti interessa accendere il pc da remoto avevo scritto un articolo usando un relè sul tasto di accensione della scheda madre comandato da Alexa.

Pulsanti di emergenza e gestione USB

Durante i test di Wake USB può accadere che, dopo il resume, la tastiera HID non invii più correttamente nuovi tasti. Non è divertente svegliare il PC e scoprire di non poterlo più controllare. Per questo la versione finale contiene una sezione separata, visivamente diversa dalla tastiera:
la Zona di emergenza — gestione dispositivo. Include tre pulsanti:

  • Reset USB (soft): tenta una disconnessione e riconnessione software dell’interfaccia USB tramite TinyUSB.
  • Riavvia ESP32: esegue ESP.restart(), riavvia completamente la scheda e forza una nuova inizializzazione del dispositivo USB dal lato PC.
  • Reset Wi‑Fi (nuova rete): cancella le credenziali memorizzate da WiFiManager e riporta la scheda alla modalità configurazione Wi‑Fi.

Il reset USB soft è il primo tentativo da fare. Se non ripristina tastiera e comandi, il riavvio completo dell’ESP32 è più efficace: interrompe l’applicazione, resetta le CPU e riavvia il firmware dal bootloader. Dopo alcuni secondi l’ESP32 torna sulla rete Wi‑Fi e il PC rileva di nuovo la tastiera HID.

Come flashare LOLIN S2 Mini con Arduino IDE

A mio avviso, la LOLIN S2 Mini è la scelta migliore per realizzare una tastiera USB HID ESP32 compatta.
Nel menu Arduino IDE il nome della scheda da selezionare è “LOLIN S2 Mini“, non “ESP32 S2 Mini” generico.

Prima di compilare, installa:

  • Il core esp32 di Espressif dal Gestore schede di Arduino IDE.
  • La libreria WiFiManager di tzapu da Sketch > Include Library > Manage Libraries.

Poi imposta Arduino IDE in questo modo:

ImpostazioneValore consigliato
SchedaLOLIN S2 Mini
USB CDC On BootEnabled
USB DFU On BootDisabled
USB Firmware MSC On BootDisabled
Erase All Flash Before Sketch UploadDisabled
Partition SchemeDefault 4MB with SPIFFS
PortaLa porta COM che compare collegando la scheda
impostazioni compilatore ide arduino per LolinS2Mini - Lutritech.it

Per entrare in modalità bootloader e caricare lo sketch:

  1. Tieni premuto BOOT.
  2. Contemporaneamente collega la LOLIN S2 Mini al computer con un cavo USB-C dati.
  3. Dopo uno o due secondi, rilascia BOOT.
  4. Seleziona la porta COM corretta in Arduino IDE.
  5. Premi il pulsante Carica.
  6. Al termine dell’upload, premi RST se la scheda non si riavvia automaticamente.

Durante la configurazione iniziale, l’ESP32 crea un access point senza password. Questa modalità dovrebbe essere utilizzata solo temporaneamente e in un ambiente controllato.

Codice della tastiera USB HID ESP32

Il firmware completo e aggiornato è disponibile anche nel mio repository di GitHub ProgettiConESP32.
Ecco lo sketch, che utilizza WiFiManager, WebServer e le classi USB HID del core Arduino-ESP32:

/*
  https://www.lutritech.it/tastiera-usb-hid-esp32-wifi
  Tastiera USB HID ESP32: controllare il PC via Wi‑Fi
  
  https://www.youtube.com/@Lutritech
  https://www.facebook.com/lutritech.it
  https://www.instagram.com/Lutritech
  https://github.com/Lutritech

  ============================================================================
  ESP32-S3 DevKitC — Tastiera Virtuale USB HID (Layout ITALIANO)
  VERSIONE 5 — Integrazione WiFiManager (connessione a reti Wi-Fi esistenti)
  ============================================================================

  NOVITA' RISPETTO ALLA V4:
  - Non crea più un Access Point fisso "ESP32-Keyboard" senza password.
  - Al primo avvio (o dopo un reset delle credenziali) crea un AP di
    CONFIGURAZIONE temporaneo chiamato "ESP32-Keyboard-Setup": collegati con
    telefono/PC, si apre un portale (o vai su http://192.168.4.1), scegli la
    tua rete Wi-Fi di casa dall'elenco e inserisci la password.
  - Le credenziali vengono salvate in memoria flash (NVS): ai riavvii
    successivi l'ESP32 si connette DA SOLO alla rete salvata, senza dover
    ricompilare o riconfigurare nulla.
  - Aggiunto mDNS: dopo la connessione, la pagina è raggiungibile anche via
        http://tastiera.local
    (oltre all'IP assegnato dal router, visibile nel Serial Monitor).
  - Aggiunto un terzo pulsante nella "Zona di emergenza": "Reset Wi-Fi
    (nuova rete)", che cancella le credenziali salvate e riavvia l'ESP32 in
    modalità configurazione, utile se vuoi cambiare rete o hai sbagliato
    password.

  LIBRERIA DA INSTALLARE (obbligatoria):
  - In Arduino IDE: Sketch -> Include Library -> Manage Libraries
  - Cerca "WiFiManager" di TZAPU (tzapu/WiFiManager) e installala.
    Compatibile con ESP32/ESP32-S3.

  IMPOSTAZIONI ARDUINO IDE (obbligatorie, invariate):
  - Board:            "ESP32S3 Dev Module"
  - USB Mode:          "USB-OTG (TinyUSB)"
  - USB CDC On Boot:   "Enabled"
  - Per il primo flash: tenere BOOT, collegare USB, rilasciare BOOT.

  PRIMO UTILIZZO DOPO IL FLASH:
  1. Accendi l'ESP32 (collegato via USB al PC).
  2. Dal telefono, connettiti alla rete Wi-Fi "ESP32-Keyboard-Setup"
     (nessuna password).
  3. Si apre automaticamente un portale (o vai su http://192.168.4.1).
  4. Scegli la tua rete Wi-Fi di casa, inserisci la password, salva.
  5. L'ESP32 si riavvia e si connette da solo alla rete scelta.
  6. Controlla il Serial Monitor per l'IP assegnato, oppure vai su
     http://tastiera.local dal telefono connesso alla STESSA rete.

  Tutte le funzionalità già presenti nella versione 4 restano disponibili: layout italiano, tastiera completa,
  Sospendi/Spegni via SystemControl, Wake USB, Reset USB soft, Riavvio ESP32.
  ============================================================================
*/

#include <WiFi.h>
#include <WiFiManager.h>      // libreria tzapu/WiFiManager
#include <ESPmDNS.h>
#include <WebServer.h>
#include "USB.h"
#include "USBHIDKeyboard.h"
#include "USBHIDConsumerControl.h"
#include "USBHIDSystemControl.h"
#include "tusb.h"

// ---------------------------------------------------------------------------
// CONFIGURAZIONE WIFI MANAGER
// ---------------------------------------------------------------------------
const char* AP_SETUP_SSID = "ESP32-Keyboard-Setup"; // AP di configurazione (senza password)
const char* MDNS_NAME     = "tastiera";             // -> http://tastiera.local

WiFiManager wm;

WebServer server(80);
USBHIDKeyboard Keyboard;
USBHIDConsumerControl ConsumerControl;
USBHIDSystemControl SystemControl;

volatile bool usbSuspended = false;

// ---------------------------------------------------------------------------
// HID Usage IDs (pagina 0x07) e modificatori "raw" usati da pressRaw()
// ---------------------------------------------------------------------------
#define HID_MOD_LCTRL   0xE0
#define HID_MOD_LSHIFT  0xE1
#define HID_MOD_LALT    0xE2
#define HID_MOD_LGUI    0xE3
#define HID_MOD_RCTRL   0xE4
#define HID_MOD_RSHIFT  0xE5
#define HID_MOD_RALT    0xE6
#define HID_MOD_RGUI    0xE7

#define HID_KEY_MINUS   0x2D
#define HID_KEY_EQUAL   0x2E
#define HID_KEY_LBRACK  0x2F
#define HID_KEY_RBRACK  0x30
#define HID_KEY_NONUS1  0x32
#define HID_KEY_SEMI    0x33
#define HID_KEY_QUOTE   0x34
#define HID_KEY_COMMA   0x36
#define HID_KEY_PERIOD  0x37
#define HID_KEY_SLASH   0x38
#define HID_KEY_1       0x1E
#define HID_KEY_2       0x1F
#define HID_KEY_3       0x20
#define HID_KEY_4       0x21
#define HID_KEY_5       0x22
#define HID_KEY_6       0x23
#define HID_KEY_7       0x24
#define HID_KEY_8       0x25
#define HID_KEY_9       0x26
#define HID_KEY_0       0x27

struct ItKey {
  uint32_t codepoint;
  uint8_t  usage;
  bool     shift;
  bool     altgr;
};

const ItKey IT_MAP[] = {
  {'!',  HID_KEY_1,      true,  false},
  {'"',  HID_KEY_2,      true,  false},
  {0x00A3 /* £ */, HID_KEY_3, true, false},
  {'$',  HID_KEY_4,      true,  false},
  {'%',  HID_KEY_5,      true,  false},
  {'&',  HID_KEY_6,      true,  false},
  {'/',  HID_KEY_7,      true,  false},
  {'(',  HID_KEY_8,      true,  false},
  {')',  HID_KEY_9,      true,  false},
  {'=',  HID_KEY_0,      true,  false},
  {'\'', HID_KEY_MINUS,  false, false},
  {'?',  HID_KEY_MINUS,  true,  false},
  {0x00EC /* ì */, HID_KEY_EQUAL, false, false},
  {'^',  HID_KEY_EQUAL,  true,  false},
  {0x00E8 /* è */, HID_KEY_LBRACK, false, false},
  {0x00E9 /* é */, HID_KEY_LBRACK, true,  false},
  {'[',  HID_KEY_LBRACK, false, true},
  {'{',  HID_KEY_LBRACK, true,  true},
  {'+',  HID_KEY_RBRACK, false, false},
  {'*',  HID_KEY_RBRACK, true,  false},
  {']',  HID_KEY_RBRACK, false, true},
  {'}',  HID_KEY_RBRACK, true,  true},
  {0x00F9 /* ù */, HID_KEY_NONUS1, false, false},
  {0x00A7 /* § */, HID_KEY_NONUS1, true,  false},
  {0x00F2 /* ò */, HID_KEY_SEMI, false, false},
  {0x00E7 /* ç */, HID_KEY_SEMI, true,  false},
  {'@',  HID_KEY_SEMI,   false, true},
  {0x00E0 /* à */, HID_KEY_QUOTE, false, false},
  {0x00B0 /* ° */, HID_KEY_QUOTE, true,  false},
  {'#',  HID_KEY_QUOTE,  false, true},
  {';',  HID_KEY_COMMA,  true,  false},
  {':',  HID_KEY_PERIOD, true,  false},
  {'-',  HID_KEY_SLASH,  false, false},
  {'_',  HID_KEY_SLASH,  true,  false},
};
const int IT_MAP_LEN = sizeof(IT_MAP) / sizeof(IT_MAP[0]);

void pressUnicodeIT(uint32_t cp) {
  for (int i = 0; i < IT_MAP_LEN; i++) {
    if (IT_MAP[i].codepoint == cp) {
      if (IT_MAP[i].shift) Keyboard.pressRaw(HID_MOD_LSHIFT);
      if (IT_MAP[i].altgr) Keyboard.pressRaw(HID_MOD_RALT);
      Keyboard.pressRaw(IT_MAP[i].usage);
      delay(10);
      Keyboard.releaseRaw(IT_MAP[i].usage);
      if (IT_MAP[i].altgr) Keyboard.releaseRaw(HID_MOD_RALT);
      if (IT_MAP[i].shift) Keyboard.releaseRaw(HID_MOD_LSHIFT);
      return;
    }
  }
  if (cp < 0x80) {
    Keyboard.press((uint8_t)cp);
    delay(10);
    Keyboard.release((uint8_t)cp);
  }
}

void sendUtf8TextIT(const String& text) {
  size_t i = 0;
  size_t len = text.length();
  while (i < len) {
    uint8_t b0 = (uint8_t)text[i];
    uint32_t cp = 0;
    int extra = 0;

    if (b0 < 0x80) { cp = b0; extra = 0; }
    else if ((b0 & 0xE0) == 0xC0) { cp = b0 & 0x1F; extra = 1; }
    else if ((b0 & 0xF0) == 0xE0) { cp = b0 & 0x0F; extra = 2; }
    else if ((b0 & 0xF8) == 0xF0) { cp = b0 & 0x07; extra = 3; }
    else { i++; continue; }

    for (int k = 0; k < extra && (i + 1 + k) < len; k++) {
      uint8_t bx = (uint8_t)text[i + 1 + k];
      cp = (cp << 6) | (bx & 0x3F);
    }

    if (cp == '\n') {
      Keyboard.press(KEY_RETURN); delay(10); Keyboard.release(KEY_RETURN);
    } else if (cp != '\r') {
      pressUnicodeIT(cp);
    }

    i += 1 + extra;
  }
}

// ---------------------------------------------------------------------------
// PAGINA WEB
// ---------------------------------------------------------------------------
const char INDEX_HTML[] PROGMEM = R"HTML(
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ESP32-S3 Tastiera Virtuale (IT)</title>
<style>
  :root{
    --bg:#1b1c1e; --panel:#242528; --key:#33353a; --key-hover:#42454b;
    --key-active:#5b8cff; --text:#eaeaea; --muted:#9a9ca1; --danger:#e5484d;
    --ok:#2fbf71; --warn:#b57b13; --warn-hover:#d99c1f;
  }
  *{box-sizing:border-box;}
  body{margin:0; background:var(--bg); color:var(--text);
       font-family:-apple-system,Segoe UI,Roboto,Arial,sans-serif; padding:16px;}
  h1{font-size:18px; font-weight:600; margin:0 0 12px;}
  h2{font-size:14px; font-weight:700; margin:0 0 8px; letter-spacing:.04em; text-transform:uppercase;}
  .panel{background:var(--panel); border-radius:12px; padding:14px; margin-bottom:14px;}
  textarea{width:100%; min-height:70px; border-radius:8px; border:1px solid #3a3c40;
    background:#111214; color:var(--text); padding:10px; font-size:15px; resize:vertical;}
  .row{display:flex; gap:8px; margin-top:10px; flex-wrap:wrap;}
  button{border:none; border-radius:8px; background:var(--key); color:var(--text);
    padding:10px 12px; font-size:13px; cursor:pointer; user-select:none; transition:background .1s;}
  button:hover{background:var(--key-hover);}
  button:active, button.active{background:var(--key-active); color:#fff;}
  button.wide{flex:1;}
  .send{background:var(--ok); color:#08210f; font-weight:600;}
  .send:hover{background:#38d67f;}
  .danger{background:var(--danger); color:#fff;}
  .danger:hover{background:#ff5b5f;}
  .krow{display:flex; gap:6px; flex-wrap:wrap; margin-top:6px;}
  .krow button{flex:1 1 auto; min-width:38px;}
  .k-wide{flex:2 1 auto !important;}
  .k-space{flex:6 1 auto !important;}
  .muted{color:var(--muted); font-size:12px; margin-top:6px;}
  .status{font-size:12px; color:var(--muted); margin-bottom:10px;}

  .emergency{
    background: repeating-linear-gradient(135deg, #2b2205, #2b2205 10px, #241b04 10px, #241b04 20px);
    border: 2px dashed var(--warn);
    border-radius:12px;
    padding:14px;
    margin-top: 24px;
  }
  .emergency h2{ color: var(--warn); }
  .warn-btn{ background: var(--warn); color:#1a1200; font-weight:700; }
  .warn-btn:hover{ background: var(--warn-hover); }
</style>
</head>
<body>

<h1>ESP32-S3 &mdash; Tastiera Virtuale USB HID (layout Italiano)</h1>
<div class="status" id="status">Pronta. Imposta il PC su layout tastiera "Italiano".</div>

<div class="panel">
  <textarea id="txt" placeholder="Scrivi qui (supporta à è é ì ò ù ç ° § £)..."></textarea>
  <div class="row">
    <button class="wide send" onclick="sendText()">OK &mdash; Invia testo</button>
    <button onclick="document.getElementById('txt').value=''">Pulisci</button>
    <button onclick="sendKey('ENTER')">Invio</button>
  </div>
</div>

<div class="panel">
  <div class="krow">
    <button onclick="sendKey('ESC')">Esc</button>
    <button onclick="sendKey('F1')">F1</button>
    <button onclick="sendKey('F2')">F2</button>
    <button onclick="sendKey('F3')">F3</button>
    <button onclick="sendKey('F4')">F4</button>
    <button onclick="sendKey('F5')">F5</button>
    <button onclick="sendKey('F6')">F6</button>
    <button onclick="sendKey('F7')">F7</button>
    <button onclick="sendKey('F8')">F8</button>
    <button onclick="sendKey('F9')">F9</button>
    <button onclick="sendKey('F10')">F10</button>
    <button onclick="sendKey('F11')">F11</button>
    <button onclick="sendKey('F12')">F12</button>
  </div>

  <div class="krow">
    <button onclick="sendChar('1')">1</button>
    <button onclick="sendChar('2')">2</button>
    <button onclick="sendChar('3')">3</button>
    <button onclick="sendChar('4')">4</button>
    <button onclick="sendChar('5')">5</button>
    <button onclick="sendChar('6')">6</button>
    <button onclick="sendChar('7')">7</button>
    <button onclick="sendChar('8')">8</button>
    <button onclick="sendChar('9')">9</button>
    <button onclick="sendChar('0')">0</button>
    <button onclick="sendChar('\'')">'</button>
    <button onclick="sendChar('ì')">ì</button>
    <button class="k-wide" onclick="sendKey('BACKSPACE')">&larr; Backspace</button>
  </div>

  <div class="krow">
    <button class="k-wide" onclick="sendKey('TAB')">Tab</button>
    <button onclick="sendChar('q')">Q</button>
    <button onclick="sendChar('w')">W</button>
    <button onclick="sendChar('e')">E</button>
    <button onclick="sendChar('r')">R</button>
    <button onclick="sendChar('t')">T</button>
    <button onclick="sendChar('y')">Y</button>
    <button onclick="sendChar('u')">U</button>
    <button onclick="sendChar('i')">I</button>
    <button onclick="sendChar('o')">O</button>
    <button onclick="sendChar('p')">P</button>
    <button onclick="sendChar('è')">è</button>
    <button onclick="sendChar('+')">+</button>
  </div>

  <div class="krow">
    <button class="k-wide" onclick="sendKey('CAPSLOCK')">Caps Lock</button>
    <button onclick="sendChar('a')">A</button>
    <button onclick="sendChar('s')">S</button>
    <button onclick="sendChar('d')">D</button>
    <button onclick="sendChar('f')">F</button>
    <button onclick="sendChar('g')">G</button>
    <button onclick="sendChar('h')">H</button>
    <button onclick="sendChar('j')">J</button>
    <button onclick="sendChar('k')">K</button>
    <button onclick="sendChar('l')">L</button>
    <button onclick="sendChar('ò')">ò</button>
    <button onclick="sendChar('à')">à</button>
    <button class="k-wide" onclick="sendKey('ENTER')">Invio</button>
  </div>

  <div class="krow">
    <button class="k-wide" onclick="toggleMod('SHIFT')" id="shiftBtn">Shift</button>
    <button onclick="sendChar('z')">Z</button>
    <button onclick="sendChar('x')">X</button>
    <button onclick="sendChar('c')">C</button>
    <button onclick="sendChar('v')">V</button>
    <button onclick="sendChar('b')">B</button>
    <button onclick="sendChar('n')">N</button>
    <button onclick="sendChar('m')">M</button>
    <button onclick="sendChar(',')">,</button>
    <button onclick="sendChar('.')">.</button>
    <button onclick="sendChar('-')">-</button>
    <button class="k-wide" onclick="toggleMod('SHIFT')" id="shiftBtn2">Shift</button>
  </div>

  <div class="krow">
    <button id="ctrlBtn" onclick="toggleMod('CTRL')">Ctrl</button>
    <button id="winBtn" onclick="toggleMod('WIN')">Win</button>
    <button id="altBtn" onclick="toggleMod('ALT')">Alt</button>
    <button class="k-space" onclick="sendKey('SPACE')">Spazio</button>
    <button id="altgrBtn" onclick="toggleMod('ALTGR')">AltGr</button>
    <button onclick="sendKey('LEFT')">&larr;</button>
    <button onclick="sendKey('UP')">&uarr;</button>
    <button onclick="sendKey('DOWN')">&darr;</button>
    <button onclick="sendKey('RIGHT')">&rarr;</button>
  </div>

  <div class="krow">
    <button onclick="sendChar('ù')">ù</button>
    <button onclick="sendChar('§')">§</button>
    <button onclick="sendChar('ç')">ç</button>
    <button onclick="sendChar('°')">°</button>
    <button onclick="sendChar('£')">£</button>
    <button onclick="sendChar('@')">@</button>
    <button onclick="sendChar('#')">#</button>
    <button onclick="sendChar('[')">[</button>
    <button onclick="sendChar(']')">]</button>
  </div>

  <div class="krow">
    <button onclick="sendKey('INSERT')">Ins</button>
    <button onclick="sendKey('HOME')">Home</button>
    <button onclick="sendKey('PAGEUP')">PgUp</button>
    <button onclick="sendKey('DELETE')">Del</button>
    <button onclick="sendKey('END')">End</button>
    <button onclick="sendKey('PAGEDOWN')">PgDn</button>
    <button onclick="sendKey('PRINTSCREEN')">PrtSc</button>
  </div>

  <div class="muted">
    Ctrl/Alt/Shift/Win: attivali (diventano blu), poi premi il tasto da
    combinare. AltGr serve per @ # [ ] {} ecc. (già gestiti dai tasti dedicati
    qui sopra, che funzionano anche senza attivare AltGr manualmente).
  </div>
</div>

<div class="panel">
  <div class="row">
    <button class="wide danger" onclick="sendCombo(['CTRL','ALT'],'DELETE')">Ctrl+Alt+Canc</button>
    <button class="wide" onclick="sendCombo(['ALT'],'TAB')">Alt+Tab</button>
    <button class="wide" onclick="sendCombo(['ALT'],'F4')">Alt+F4</button>
    <button class="wide" onclick="sendCombo(['WIN'],'D')">Win+D (Desktop)</button>
    <button class="wide" onclick="sendCombo(['WIN'],'L')">Win+L (Blocca)</button>
  </div>
</div>

<div class="panel">
  <div class="row">
    <button class="wide danger" onclick="powerAction('sleep')">Sospendi PC</button>
    <button class="wide danger" onclick="powerAction('power')">Spegni PC</button>
    <button class="wide danger" onclick="powerAction('wake')">Segnale di Wake (USB)</button>
  </div>
  <div class="muted">
    "Sospendi" e "Spegni" funzionano da PC acceso. "Wake" funziona SOLO se il
    PC è già in stand-by. Dopo aver usato "Wake", se la tastiera smette di
    rispondere, usa i pulsanti ambra qui sotto per sbloccarla.
  </div>
</div>

<div class="emergency">
  <h2>Zona di emergenza &mdash; gestione dispositivo</h2>
  <div class="muted" style="margin-top:0; margin-bottom:10px;">
    Questi pulsanti NON fanno parte della tastiera: servono a sbloccare la
    connessione USB o a riconfigurare la rete Wi-Fi.
  </div>
  <div class="row">
    <button class="wide warn-btn" onclick="usbReset()">&#9881; Reset USB (soft)</button>
    <button class="wide warn-btn" onclick="restartEsp()">&#9888; Riavvia ESP32</button>
  </div>
  <div class="row">
    <button class="wide warn-btn" onclick="wifiReset()">&#128246; Reset Wi-Fi (nuova rete)</button>
  </div>
</div>

<script>
let activeMods = new Set();

function updateModButtons(){
  document.querySelectorAll('#ctrlBtn').forEach(b=>b.classList.toggle('active', activeMods.has('CTRL')));
  document.querySelectorAll('#altBtn').forEach(b=>b.classList.toggle('active', activeMods.has('ALT')));
  document.querySelectorAll('#shiftBtn,#shiftBtn2').forEach(b=>b.classList.toggle('active', activeMods.has('SHIFT')));
  document.querySelectorAll('#winBtn').forEach(b=>b.classList.toggle('active', activeMods.has('WIN')));
  document.querySelectorAll('#altgrBtn').forEach(b=>b.classList.toggle('active', activeMods.has('ALTGR')));
}
function toggleMod(m){
  if(activeMods.has(m)) activeMods.delete(m); else activeMods.add(m);
  updateModButtons();
}
function setStatus(msg){ document.getElementById('status').textContent = msg; }

async function post(path, params){
  try{
    const res = await fetch(path, {
      method:'POST',
      headers:{'Content-Type':'application/x-www-form-urlencoded'},
      body: new URLSearchParams(params).toString()
    });
    const txt = await res.text();
    setStatus(res.ok ? ('OK: ' + txt) : ('Errore: ' + txt));
  }catch(e){ setStatus('Errore di connessione: ' + e); }
}

function sendText(){
  const t = document.getElementById('txt').value;
  if(!t) return;
  post('/api/text', {text:t});
}
function sendChar(ch){ post('/api/text', {text:ch}); }
function sendKey(key){
  const mods = Array.from(activeMods).join(',');
  post('/api/key', {mods:mods, key:key});
  activeMods.clear();
  updateModButtons();
}
function sendCombo(mods, key){ post('/api/key', {mods:mods.join(','), key:key}); }
function powerAction(action){ post('/api/power', {action:action}); }

function usbReset(){
  if(!confirm("Tentare un reset software della connessione USB?")) return;
  post('/api/usbreset', {});
}
function restartEsp(){
  if(!confirm("Riavviare completamente l'ESP32? La pagina si disconnetterà per alcuni secondi.")) return;
  post('/api/restart', {});
  setStatus("Riavvio in corso... attendi 5-10 secondi poi ricarica la pagina.");
}
function wifiReset(){
  if(!confirm("Cancellare le credenziali Wi-Fi salvate e tornare alla modalità di configurazione? Dovrai ricollegarti alla rete 'ESP32-Keyboard-Setup' per scegliere una nuova rete.")) return;
  post('/api/wifireset', {});
  setStatus("Reset Wi-Fi in corso... l'ESP32 si riavvierà in modalità configurazione.");
}
</script>

</body>
</html>
)HTML";

// ---------------------------------------------------------------------------
// UTILITÀ modificatori / tasti nominati
// ---------------------------------------------------------------------------
void applyModifiers(const String& modsCsv, bool press) {
  int start = 0;
  while (start < (int)modsCsv.length()) {
    int comma = modsCsv.indexOf(',', start);
    String m = (comma == -1) ? modsCsv.substring(start) : modsCsv.substring(start, comma);
    m.trim();
    if (m.length() > 0) {
      uint8_t code = 0;
      if (m == "CTRL")  code = HID_MOD_LCTRL;
      else if (m == "ALT")   code = HID_MOD_LALT;
      else if (m == "SHIFT") code = HID_MOD_LSHIFT;
      else if (m == "WIN")   code = HID_MOD_LGUI;
      else if (m == "ALTGR") code = HID_MOD_RALT;
      if (code) {
        if (press) Keyboard.pressRaw(code);
        else Keyboard.releaseRaw(code);
      }
    }
    if (comma == -1) break;
    start = comma + 1;
  }
}

bool sendNamedKey(const String& key) {
  uint8_t code = 0;
  if (key == "ENTER")        code = KEY_RETURN;
  else if (key == "ESC")            code = KEY_ESC;
  else if (key == "TAB")            code = KEY_TAB;
  else if (key == "BACKSPACE")      code = KEY_BACKSPACE;
  else if (key == "DELETE")         code = KEY_DELETE;
  else if (key == "INSERT")         code = KEY_INSERT;
  else if (key == "HOME")           code = KEY_HOME;
  else if (key == "END")            code = KEY_END;
  else if (key == "PAGEUP")         code = KEY_PAGE_UP;
  else if (key == "PAGEDOWN")       code = KEY_PAGE_DOWN;
  else if (key == "LEFT")           code = KEY_LEFT_ARROW;
  else if (key == "RIGHT")          code = KEY_RIGHT_ARROW;
  else if (key == "UP")             code = KEY_UP_ARROW;
  else if (key == "DOWN")           code = KEY_DOWN_ARROW;
  else if (key == "SPACE")          { Keyboard.press(' '); delay(15); Keyboard.release(' '); return true; }
  else if (key == "CAPSLOCK")       code = KEY_CAPS_LOCK;
  else if (key == "PRINTSCREEN")    code = KEY_PRINT_SCREEN;
  else if (key == "F1")             code = KEY_F1;
  else if (key == "F2")             code = KEY_F2;
  else if (key == "F3")             code = KEY_F3;
  else if (key == "F4")             code = KEY_F4;
  else if (key == "F5")             code = KEY_F5;
  else if (key == "F6")             code = KEY_F6;
  else if (key == "F7")             code = KEY_F7;
  else if (key == "F8")             code = KEY_F8;
  else if (key == "F9")             code = KEY_F9;
  else if (key == "F10")            code = KEY_F10;
  else if (key == "F11")            code = KEY_F11;
  else if (key == "F12")            code = KEY_F12;
  else if (key.length() == 1) { sendUtf8TextIT(key); return true; }
  else return false;

  Keyboard.press(code);
  delay(15);
  Keyboard.release(code);
  return true;
}

// ---------------------------------------------------------------------------
// HANDLERS principali
// ---------------------------------------------------------------------------
void handleRoot() { server.send_P(200, "text/html", INDEX_HTML); }

void handleText() {
  if (!server.hasArg("text")) { server.send(400, "text/plain", "missing text"); return; }
  sendUtf8TextIT(server.arg("text"));
  server.send(200, "text/plain", "ok");
}

void handleKey() {
  String mods = server.hasArg("mods") ? server.arg("mods") : "";
  String key  = server.hasArg("key")  ? server.arg("key")  : "";
  if (key.length() == 0) { server.send(400, "text/plain", "missing key"); return; }

  applyModifiers(mods, true);
  delay(10);
  bool ok = sendNamedKey(key);
  delay(10);
  applyModifiers(mods, false);
  Keyboard.releaseAll();

  server.send(ok ? 200 : 400, "text/plain", ok ? "ok" : "unknown key");
}

void handlePower() {
  String action = server.hasArg("action") ? server.arg("action") : "";

  if (action == "sleep") {
    SystemControl.press(SYSTEM_CONTROL_STANDBY);
    delay(100);
    SystemControl.release();
    server.send(200, "text/plain", "ok - sospensione richiesta");

  } else if (action == "power") {
    SystemControl.press(SYSTEM_CONTROL_POWER_OFF);
    delay(100);
    SystemControl.release();
    server.send(200, "text/plain", "ok - spegnimento richiesto");

  } else if (action == "wake") {
    if (usbSuspended) {
      tud_remote_wakeup();
      delay(50);
      server.send(200, "text/plain", "ok - segnale di wakeup inviato");
    } else {
      SystemControl.press(SYSTEM_CONTROL_WAKE_HOST);
      delay(50);
      SystemControl.release();
      server.send(200, "text/plain", "il dispositivo non risulta sospeso: nessuna azione necessaria");
    }

  } else {
    server.send(400, "text/plain", "unknown action");
  }
}

// ---------------------------------------------------------------------------
// HANDLERS di emergenza / gestione dispositivo
// ---------------------------------------------------------------------------
void handleUsbReset() {
  server.send(200, "text/plain", "tentativo di reset USB in corso");
  delay(100);
  tud_disconnect();
  delay(500);
  tud_connect();
}

void handleRestartEsp() {
  server.send(200, "text/plain", "riavvio in corso, attendi qualche secondo");
  delay(300);
  ESP.restart();
}

void handleWifiReset() {
  server.send(200, "text/plain", "credenziali Wi-Fi cancellate, riavvio in modalita' configurazione");
  delay(300);
  wm.resetSettings();
  delay(300);
  ESP.restart();
}

void handleNotFound() { server.send(404, "text/plain", "Not found"); }

// ---------------------------------------------------------------------------
// Eventi USB
// ---------------------------------------------------------------------------
void usbEventCallback(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) {
  if (event_base == ARDUINO_USB_EVENTS) {
    if (event_id == ARDUINO_USB_SUSPEND_EVENT) {
      usbSuspended = true;
      Serial.println("[USB] Sospeso.");
    } else if (event_id == ARDUINO_USB_RESUME_EVENT) {
      usbSuspended = false;
      Serial.println("[USB] Ripreso.");
    }
  }
}

// ---------------------------------------------------------------------------
// SETUP / LOOP
// ---------------------------------------------------------------------------
void setup() {
  Serial.begin(115200);
  delay(300);

  // --- USB HID: inizializzato PRIMA del Wi-Fi, indipendente da esso ---
  USB.onEvent(usbEventCallback);
  USB.usbAttributes(TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP);
  Keyboard.begin();
  ConsumerControl.begin();
  SystemControl.begin();
  USB.begin();

  // --- Wi-Fi Manager: connette a rete salvata, o apre il portale di setup ---
  Serial.println();
  Serial.println("=== ESP32-S3 Tastiera Virtuale HID (layout IT) - v5 ===");
  Serial.println("Avvio WiFiManager...");

  // Timeout del portale di configurazione: se nessuno lo configura entro
  // 180 secondi, l'ESP32 riprova con le credenziali salvate (se esistono)
  // o resta in modalita' configurazione.
  wm.setConfigPortalTimeout(180);

  bool connected = wm.autoConnect(AP_SETUP_SSID); // AP di setup senza password

  if (!connected) {
    Serial.println("Connessione Wi-Fi non riuscita e portale scaduto. Riavvio...");
    delay(3000);
    ESP.restart();
  }

  Serial.println("Connesso alla rete Wi-Fi!");
  Serial.print("Indirizzo IP: "); Serial.println(WiFi.localIP());

  if (MDNS.begin(MDNS_NAME)) {
    Serial.print("mDNS avviato: http://");
    Serial.print(MDNS_NAME);
    Serial.println(".local");
  } else {
    Serial.println("Avvio mDNS non riuscito (non bloccante, si può usare l'IP).");
  }

  // --- Web server ---
  server.on("/", HTTP_GET, handleRoot);
  server.on("/api/text", HTTP_POST, handleText);
  server.on("/api/key", HTTP_POST, handleKey);
  server.on("/api/power", HTTP_POST, handlePower);
  server.on("/api/usbreset", HTTP_POST, handleUsbReset);
  server.on("/api/restart", HTTP_POST, handleRestartEsp);
  server.on("/api/wifireset", HTTP_POST, handleWifiReset);
  server.onNotFound(handleNotFound);
  server.begin();

  Serial.println("Server web avviato.");
}

void loop() {
  server.handleClient();
}

Lo sketch contiene la pagina HTML completa, la gestione del layout italiano, i tasti funzione, le combinazioni, le API HTTP per l’invio dei tasti, la gestione del remote wakeup e i pulsanti di ripristino. Il principio resta comunque questo: Wi‑Fi per ricevere il comando, USB HID per inviarlo al PC.
La pagina web dell’ESP32 utilizza HTTP e non HTTPS. Il progetto è quindi pensato per essere utilizzato all’interno di una rete Wi-Fi considerata affidabile. Non dovrebbe essere esposto direttamente a Internet. Per realizzare questo sketch ho utilizzato Claude SONNET 5 e tanta pazienza.

Usi legali della tastiera USB HID ESP32

Una tastiera USB HID ESP32 è utile in molti contesti perfettamente legittimi, purché venga usata solo su dispositivi propri o per i quali hai ricevuto autorizzazione esplicita:

  • Gestione di home-lab, server domestici, mini-PC e postazioni senza tastiera fissa.
  • Comandi rapidi su un media center o un computer collegato alla TV.
  • Automazioni personali con scorciatoie da tastiera.
  • Progetti di elettronica e formazione su USB, Wi‑Fi e microcontrollori.
  • Test di accessibilità con tastiere touch personalizzate.
  • Kiosk, demo e postazioni tecniche controllate.

BadUSB e keylogging: conoscere il rischio per difendersi

Un dispositivo capace di emulare una tastiera USB è anche un promemoria utile: una porta USB non è solo un posto dove ricaricare il telefono. La stessa tecnologia HID può essere usata in attacchi chiamati BadUSB, nei quali una periferica apparentemente innocua viene collegata a un PC e invia comandi senza consenso.

Il sistema operativo tende a fidarsi di una tastiera perché una tastiera, per definizione, deve poter digitare. Questo è il motivo per cui in ambienti aziendali, sanitari e con dati sensibili è importante limitare le periferiche USB non autorizzate, applicare policy di endpoint security e non collegare dispositivi sconosciuti “solo per vedere cosa fanno”. Spoiler: di solito non fanno niente di buono.

Il keylogging è un argomento distinto ma collegato alla sicurezza delle periferiche: registrare ciò che un utente digita o monitorare un PC senza autorizzazione viola privacy e norme penali. Studiare queste tecniche è utile per difendersi e per progettare ambienti più sicuri, non per usarle su computer altrui.

Disclaimer

Questo progetto e il codice associato sono destinati esclusivamente a uso personale, didattico, di laboratorio e su dispositivi di propria proprietà o per i quali si dispone di autorizzazione esplicita.

Non utilizzare questa soluzione per accedere a computer di terzi, eseguire comandi non autorizzati, intercettare dati, installare software, aggirare misure di sicurezza o compromettere la riservatezza di altre persone. L’uso improprio di dispositivi HID può violare norme su accesso abusivo a sistemi informatici, privacy e trattamento dei dati.

Lutritech e l’autore non si assumono responsabilità per usi illegali o non autorizzati delle informazioni e del firmware descritti nell’articolo.

FAQ sulla tastiera USB HID ESP32

Quale ESP32 può funzionare come tastiera USB?

Per questo progetto puoi usare ESP32-S2 o ESP32-S3, perché dispongono di USB OTG nativa e possono funzionare come dispositivi USB HID. L’ESP32-C3 non è adatto: la sua USB integrata è Serial/JTAG e non può essere programmata come tastiera USB HID.

La tastiera USB HID ESP32 funziona con Windows 11?

Sì. Windows 11 riconosce ESP32-S2 ed ESP32-S3 come tastiere HID standard senza richiedere driver aggiuntivi. Nel firmware devi però abilitare la modalità USB OTG/TinyUSB e usare la porta USB nativa della scheda.

Posso usare LOLIN S2 Mini per questo progetto?

Sì. La LOLIN S2 Mini monta un ESP32-S2 con Wi‑Fi e USB OTG nativa, quindi è compatibile con la tastiera USB HID ESP32. In Arduino IDE seleziona la board LOLIN S2 Mini.

Posso cambiare rete Wi‑Fi senza ricompilare?

Sì. WiFiManager salva SSID e password nella memoria dell’ESP32. Se vuoi cambiare rete usa il pulsante Reset Wi‑Fi, poi riconnettiti alla rete di setup e scegli la nuova rete dal captive portal.

Il Wake USB può accendere un PC spento?

Il Wake USB è pensato per risvegliare un PC in sospensione. L’accensione da computer completamente spento dipende da BIOS/UEFI, alimentazione della porta USB e supporto specifico della scheda madre. Per un risultato affidabile da PC spento usa Wake-on-LAN o un relè/optoisolatore sui pin del pulsante Power.

Perché dopo Wake USB la tastiera può smettere di funzionare?

Dopo un ciclo di sospensione e resume, l’interfaccia HID può occasionalmente restare in uno stato non ottimale. Per questo il progetto include Reset USB soft e Riavvia ESP32, che permettono di ripristinare la connessione senza scollegare fisicamente il dispositivo.

Conclusioni

Realizzare una tastiera USB HID ESP32 controllabile via Wi‑Fi è un progetto semplice nelle dimensioni ma molto versatile. Con una LOLIN S2 Mini o un ESP32-S3, WiFiManager e una pagina web ottieni una periferica USB intelligente: non richiede programmi sul PC, si controlla dal browser e può inviare testo, tasti funzione, shortcut e comandi di alimentazione.

La versione finale con WiFiManager rende il progetto davvero utilizzabile tutti i giorni: configuri il Wi‑Fi una volta, poi l’ESP32 torna automaticamente nella tua rete. È utile per home-lab, mini-PC, automazioni locali, test USB e postazioni remote. E, soprattutto, rende possibile premere Ctrl+Alt+Canc senza iniziare una caccia al tesoro sotto la scrivania. Sì, anche questo si chiama progresso tecnologico 😄.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Dimostra di essere umano: 7   +   8   =  

Torna in alto