/* Bluetooth BLE Keyboard - Deutsches Tastaturlayout (QWERTZ) ESP32 wird per USB an den Sender-PC angeschlossen und sendet Tastenanschlaege per BLE an den Ziel-Rechner. Benutzung mit minicom (Linux) als Sender: minicom -D /dev/ttyUSB0 Hardware: ESP32 WROOM Framework: Arduino ESP32 Bibliotheken: https://github.com/HijelHub/HijelHID_BLEKeyboard https://github.com/h2zero/NimBLE-Arduino 2026-08-17 mchris (Claude support), Version für deutsche Tastatur */ #include HijelHID_BLEKeyboard keyboard("ESP32 Tastatur", "Deutsch", 100); // Sendet einen Unicode-Codepoint so, dass am Host mit deutschem // QWERTZ-Layout das richtige Zeichen ankommt. void typeGerman(uint32_t cp) { // Steuerzeichen if (cp == '\n' || cp == '\r') { keyboard.tap(KEY_RETURN); return; } if (cp == '\t') { keyboard.tap(KEY_TAB); return; } if (cp == ' ') { keyboard.tap(KEY_SPACE); return; } // Terminals senden fuer die Backspace-Taste meist 0x7F (DEL) oder 0x08 (BS) if (cp == 0x7F || cp == 0x08) { keyboard.tap(KEY_BACKSPACE); return; } if (cp == 0x1B) { keyboard.tap(KEY_ESCAPE); return; } // einzelnes ESC (kein Folgebyte) // Kleinbuchstaben a-z (y und z sind auf DE-Layout physisch vertauscht) if (cp >= 'a' && cp <= 'z') { char c = (char)cp; if (c == 'y') keyboard.tap((uint8_t)KEY_Z); else if (c == 'z') keyboard.tap((uint8_t)KEY_Y); else keyboard.tap((uint8_t)(KEY_A + (c - 'a'))); return; } // Grossbuchstaben A-Z if (cp >= 'A' && cp <= 'Z') { char c = (char)cp; if (c == 'Y') keyboard.tap((uint8_t)KEY_Z, KEY_MOD_LSHIFT); else if (c == 'Z') keyboard.tap((uint8_t)KEY_Y, KEY_MOD_LSHIFT); else keyboard.tap((uint8_t)(KEY_A + (c - 'A')), KEY_MOD_LSHIFT); return; } // Ziffern 0-9, Grundebene ist identisch zu US if (cp >= '1' && cp <= '9') { keyboard.tap((uint8_t)(KEY_1 + (cp - '1'))); return; } if (cp == '0') { keyboard.tap((uint8_t)KEY_0); return; } switch (cp) { // --- Umlaute und Eszett --- case 0x00E4: keyboard.tap((uint8_t)KEY_APOSTROPHE); break; // ä case 0x00C4: keyboard.tap((uint8_t)KEY_APOSTROPHE, KEY_MOD_LSHIFT); break; // Ä case 0x00F6: keyboard.tap((uint8_t)KEY_SEMICOLON); break; // ö case 0x00D6: keyboard.tap((uint8_t)KEY_SEMICOLON, KEY_MOD_LSHIFT); break; // Ö case 0x00FC: keyboard.tap((uint8_t)KEY_LEFTBRACE); break; // ü case 0x00DC: keyboard.tap((uint8_t)KEY_LEFTBRACE, KEY_MOD_LSHIFT); break; // Ü case 0x00DF: keyboard.tap((uint8_t)KEY_MINUS); break; // ß // --- Ziffernreihe, Shift-Ebene (weicht vom US-Layout ab!) --- case '!': keyboard.tap((uint8_t)KEY_1, KEY_MOD_LSHIFT); break; case '"': keyboard.tap((uint8_t)KEY_2, KEY_MOD_LSHIFT); break; case 0x00A7: keyboard.tap((uint8_t)KEY_3, KEY_MOD_LSHIFT); break; // § case '$': keyboard.tap((uint8_t)KEY_4, KEY_MOD_LSHIFT); break; case '%': keyboard.tap((uint8_t)KEY_5, KEY_MOD_LSHIFT); break; case '&': keyboard.tap((uint8_t)KEY_6, KEY_MOD_LSHIFT); break; case '/': keyboard.tap((uint8_t)KEY_7, KEY_MOD_LSHIFT); break; case '(': keyboard.tap((uint8_t)KEY_8, KEY_MOD_LSHIFT); break; case ')': keyboard.tap((uint8_t)KEY_9, KEY_MOD_LSHIFT); break; case '=': keyboard.tap((uint8_t)KEY_0, KEY_MOD_LSHIFT); break; // --- weitere Satzzeichen --- case ',': keyboard.tap((uint8_t)KEY_COMMA); break; case ';': keyboard.tap((uint8_t)KEY_COMMA, KEY_MOD_LSHIFT); break; case '.': keyboard.tap((uint8_t)KEY_DOT); break; case ':': keyboard.tap((uint8_t)KEY_DOT, KEY_MOD_LSHIFT); break; case '-': keyboard.tap((uint8_t)KEY_SLASH); break; case '_': keyboard.tap((uint8_t)KEY_SLASH, KEY_MOD_LSHIFT); break; case '?': keyboard.tap((uint8_t)KEY_MINUS, KEY_MOD_LSHIFT); break; case '\'': keyboard.tap((uint8_t)KEY_BACKSLASH, KEY_MOD_LSHIFT); break; case '#': keyboard.tap((uint8_t)KEY_BACKSLASH); break; case '+': keyboard.tap((uint8_t)KEY_RIGHTBRACE); break; case '*': keyboard.tap((uint8_t)KEY_RIGHTBRACE, KEY_MOD_LSHIFT); break; // --- AltGr-Ebene (Beispiele, bei Bedarf erweitern) --- case '@': keyboard.tap((uint8_t)KEY_Q, KEY_MOD_RALT); break; case 0x20AC: keyboard.tap((uint8_t)KEY_E, KEY_MOD_RALT); break; // € default: Serial.print(" [kein Mapping fuer U+"); Serial.print(cp, HEX); Serial.print("]"); break; } } // Wertet eine CSI-Escape-Sequenz aus (alles nach "ESC [", inkl. Endzeichen) // und tippt die passende Sondertaste. Deckt die gaengigsten VT100/ANSI- // Sequenzen ab, wie sie minicom fuer Pfeiltasten, Entf, Pos1, Ende etc. sendet. void handleCSI(const String &seq) { if (seq == "A") { keyboard.tap(KEY_UP); return; } if (seq == "B") { keyboard.tap(KEY_DOWN); return; } if (seq == "C") { keyboard.tap(KEY_RIGHT); return; } if (seq == "D") { keyboard.tap(KEY_LEFT); return; } if (seq == "H") { keyboard.tap(KEY_HOME); return; } // Pos1 (Variante 1) if (seq == "F") { keyboard.tap(KEY_END); return; } // Ende (Variante 1) if (seq == "1~") { keyboard.tap(KEY_HOME); return; } // Pos1 (Variante 2) if (seq == "3~") { keyboard.tap(KEY_DELETE); return; } // Entf if (seq == "4~") { keyboard.tap(KEY_END); return; } // Ende (Variante 2) if (seq == "5~") { keyboard.tap(KEY_PAGE_UP); return; } if (seq == "6~") { keyboard.tap(KEY_PAGE_DOWN); return; } Serial.print(" [unbekannte CSI-Sequenz: ESC["); Serial.print(seq); Serial.print("]"); } // Wartet bis zu ~1 Sekunde auf das naechste Byte (fuer Folgebytes von // UTF-8- oder Escape-Sequenzen, die kurz nacheinander eintreffen). bool waitForByte() { uint32_t waited = 0; while (!Serial.available() && waited < 1000) { delay(1); waited++; } return Serial.available(); } void setup() { Serial.begin(115200); keyboard.begin(); Serial.println("BLE Keyboard gestartet"); } void loop() { if (Serial.available()) { uint8_t b0 = Serial.read(); // --- Escape-Sequenzen (Pfeiltasten, Entf, Pos1, Ende, Bild hoch/runter) --- if (b0 == 0x1B) { if (!waitForByte()) { Serial.print(" ESC"); if (keyboard.isPaired()) keyboard.tap(KEY_ESCAPE); Serial.println(""); delay(10); return; } uint8_t b1 = Serial.read(); if (b1 == '[' || b1 == 'O') { // 'O' = manche Terminals im "Application Mode" String seq = ""; uint8_t bn; do { if (!waitForByte()) break; bn = Serial.read(); seq += (char)bn; } while (bn < 0x40 || bn > 0x7E); // CSI-Sequenz endet mit Byte 0x40-0x7E Serial.print(" ESC["); Serial.print(seq); if (keyboard.isPaired()) { handleCSI(seq); } else { Serial.print(" not connected"); } } else { Serial.print(" ESC + unbekannt"); } Serial.println(""); delay(10); return; } // --- normale Zeichen / UTF-8 --- uint32_t cp; uint8_t extraBytes = 0; if (b0 < 0x80) { cp = b0; // 1 Byte, reines ASCII } else if ((b0 & 0xE0) == 0xC0) { cp = b0 & 0x1F; extraBytes = 1; // 2 Byte, deckt äöüÄÖÜß ab } else if ((b0 & 0xF0) == 0xE0) { cp = b0 & 0x0F; extraBytes = 2; // 3 Byte } else if ((b0 & 0xF8) == 0xF0) { cp = b0 & 0x07; extraBytes = 3; // 4 Byte } else { cp = b0; // ungueltiges/einzelnes Byte } for (uint8_t i = 0; i < extraBytes; i++) { if (!waitForByte()) break; // Timeout, Byte fehlt uint8_t bn = Serial.read(); cp = (cp << 6) | (bn & 0x3F); } Serial.print(" U+"); Serial.print(cp, HEX); if (keyboard.isPaired()) { typeGerman(cp); } else { Serial.print(" not connected"); } Serial.println(""); } delay(10); }