Hi everybody,
finally "the coin dropped into the box" 
all characters that are accessed through pressing AltGr on a german keyboard have the scancode pure Key + modifier Ctrl-Alt
I did a half automated brute-force analysis of the keycodes.
This way I found the keycodes for äöü.
Walking through all keycodes in a loop of course I came across some keys like window-key, page-up etc. which are executed as always.
That was the reason to use a half-automtated function which waits for a button-press after each send-keystroke.
So final question where to post the working demo-code?
Just here? or is there a better suited place for it?
The complete project is a device that automates a login that requires
- typing in a looong emailadress as login-name
- a pretty loong password
- and a OTP-password based on a secret and actual time
So I have all parts working now.
Next step is to add code for configuration of the login/password/secret over a serial connection or maybe a webinterface
here is the demo-code with deleted password and secret
#define dbg(myFixedText, variableName) \
Serial.print( F(#myFixedText " " #variableName"=") ); \
Serial.println(variableName);
// usage: dbg("1:my fixed text",myVariable);
// myVariable can be any variable or expression that is defined in scope
#define dbgi(myFixedText, variableName,timeInterval) \
do { \
static unsigned long intervalStartTime; \
if ( millis() - intervalStartTime >= timeInterval ){ \
intervalStartTime = millis(); \
Serial.print( F(#myFixedText " " #variableName"=") ); \
Serial.println(variableName); \
} \
} while (false);
// usage: dbgi("2:my fixed text",myVar,myInterval);
// myVar can be any variable or expression that is defined in scope
// myInterval is the time-interval which must pass by before the next
// print is executed
#define US_KEYBOARD 1
#include <Arduino.h>
#include "BLEDevice.h"
#include "BLEHIDDevice.h"
#include "HIDTypes.h"
#include "HIDKeyboardTypes.h"
char KeyCode = 0;
// Change the below values if desired
#define BUTTON_PIN 0 //2
#define Button_D2 15
const byte Ctrl = 1;
const byte Shift = 2;
const byte Alt = 4;
const byte CtrlAlt = Ctrl + Alt;
const byte AltCtrl = CtrlAlt;
const byte aeoeue_1stByte = 195;
const byte ae_2ndByte = 164;
const byte oe_2ndByte = 182;
const byte ue_2ndByte = 188;
const byte Ae_2ndByte = 132;
const byte Oe_2ndByte = 150;
const byte Ue_2ndByte = 156;
const byte SharpS_2ndByte = 159;
const byte mue23_1stByte = 194;
const byte mue_2ndByte = 181;
const byte mue2_2ndByte = 178;
const byte mue3_2ndByte = 179;
const byte degree_2ndByte = 176;
const byte paragraph_2ndByte = 167;
const byte euro_1stByte = 226;
const byte euro_2ndByte = 130;
const byte euro_3rdByte = 172;
const int SendDelay = 20;
#define MESSAGE "Hello from ESP32\n"
#define DEVICE_NAME "ESP32 Keyboard"
// Forward declarations
void bluetoothTask(void*);
void typeText(const char* text);
bool isBleConnected = false;
#include <WiFi.h>
#include <NTPClient.h>
#include <TOTP.h>
#include <SafeString.h>
createSafeString(myDemo_SS, 256);
createSafeString(KeyBoardText, 256);
// change the following settings according to your WiFi network
char ssid[] = "FRITZ!Box 7490";
char password[] = "";
uint8_t hmacKey[] = {}
TOTP totp = TOTP(hmacKey, 16);
char code[17];
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);
String totpCode = String("");
void PrintFileNameDateTime() {
Serial.println( F("Code running comes from file ") );
Serial.println( F(__FILE__) );;
Serial.print( F(" compiled ") );
Serial.print( F(__DATE__) );
Serial.print( F(" ") );
Serial.println( F(__TIME__) );
}
boolean TimePeriodIsOver (unsigned long &expireTime, unsigned long TimePeriod) {
unsigned long currentMillis = millis();
if ( currentMillis - expireTime >= TimePeriod )
{
expireTime = currentMillis; // set new expireTime
return true; // more time than TimePeriod) has elapsed since last time if-condition was true
}
else return false; // not expired
}
unsigned long MyTestTimer = 0; // variables MUST be of type unsigned long
const byte OnBoard_LED = 2;
void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
static unsigned long MyBlinkTimer;
pinMode(IO_Pin, OUTPUT);
if ( TimePeriodIsOver(MyBlinkTimer,BlinkPeriod) ) {
digitalWrite(IO_Pin,!digitalRead(IO_Pin) );
}
}
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println("Setup-Start");
PrintFileNameDateTime();
// configure pin for button
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(Button_D2, INPUT_PULLUP);
// start Bluetooth task
xTaskCreate(bluetoothTask, "bluetooth", 20000, NULL, 5, NULL);
// connect to the WiFi network
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Establishing connection to WiFi...");
}
Serial.print("Connected to WiFi with IP: ");
Serial.println(WiFi.localIP());
Serial.println();
// start the NTP client
timeClient.begin();
Serial.println("NTP client started");
Serial.println();
}
unsigned long myEpocTime;
// Message (report) sent when a key is pressed or released
struct KeyMessage_t {
uint8_t modifiers; // bitmask: CTRL = 1, SHIFT = 2, ALT = 4
uint8_t reserved; // must be 0
uint8_t pressedKeys[6]; // up to six concurrently pressed keys
};
// Message (report) received when an LED's state changed
struct OutputReport {
uint8_t leds; // bitmask: num lock = 1, caps lock = 2, scroll lock = 4, compose = 8, kana = 16
};
// The report map describes the HID device (a keyboard in this case) and
// the messages (reports in HID terms) sent and received.
static const uint8_t REPORT_MAP[] = {
USAGE_PAGE(1), 0x01, // Generic Desktop Controls
USAGE(1), 0x06, // Keyboard
COLLECTION(1), 0x01, // Application
REPORT_ID(1), 0x01, // Report ID (1)
USAGE_PAGE(1), 0x07, // Keyboard/Keypad
USAGE_MINIMUM(1), 0xE0, // Keyboard Left Control
USAGE_MAXIMUM(1), 0xE7, // Keyboard Right Control
LOGICAL_MINIMUM(1), 0x00, // Each bit is either 0 or 1
LOGICAL_MAXIMUM(1), 0x01,
REPORT_COUNT(1), 0x08, // 8 bits for the modifier keys
REPORT_SIZE(1), 0x01,
HIDINPUT(1), 0x02, // Data, Var, Abs
REPORT_COUNT(1), 0x01, // 1 byte (unused)
REPORT_SIZE(1), 0x08,
HIDINPUT(1), 0x01, // Const, Array, Abs
REPORT_COUNT(1), 0x06, // 6 bytes (for up to 6 concurrently pressed keys)
REPORT_SIZE(1), 0x08,
LOGICAL_MINIMUM(1), 0x00,
LOGICAL_MAXIMUM(1), 0xE7, // full range 0xE7 // 101 keys 0x65
USAGE_MINIMUM(1), 0x00,
USAGE_MAXIMUM(1), 0xE7,
HIDINPUT(1), 0x00, // Data, Array, Abs
REPORT_COUNT(1), 0x05, // 5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana)
REPORT_SIZE(1), 0x01,
USAGE_PAGE(1), 0x08, // LEDs
USAGE_MINIMUM(1), 0x01, // Num Lock
USAGE_MAXIMUM(1), 0x05, // Kana
LOGICAL_MINIMUM(1), 0x00,
LOGICAL_MAXIMUM(1), 0x01,
HIDOUTPUT(1), 0x02, // Data, Var, Abs
REPORT_COUNT(1), 0x01, // 3 bits (Padding)
REPORT_SIZE(1), 0x03,
HIDOUTPUT(1), 0x01, // Const, Array, Abs
END_COLLECTION(0) // End application collection
};
BLEHIDDevice* hid;
BLECharacteristic* input;
BLECharacteristic* output;
const KeyMessage_t NO_KEY_PRESSED = { };
// Callbacks related to BLE connection
class BleKeyboardCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* server) {
isBleConnected = true;
// Allow notifications for characteristics
BLE2902* cccDesc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
cccDesc->setNotifications(true);
Serial.println("Client has connected");
}
void onDisconnect(BLEServer* server) {
isBleConnected = false;
// Disallow notifications for characteristics
BLE2902* cccDesc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
cccDesc->setNotifications(false);
Serial.println("Client has disconnected");
}
};
/*
Called when the client (computer, smart phone) wants to turn on or off
the LEDs in the keyboard.
bit 0 - NUM LOCK
bit 1 - CAPS LOCK
bit 2 - SCROLL LOCK
*/
class OutputCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic* characteristic) {
OutputReport* report = (OutputReport*) characteristic->getData();
Serial.print("LED state: ");
Serial.print((int) report->leds);
Serial.println();
}
};
void bluetoothTask(void*) {
// initialize the device
BLEDevice::init(DEVICE_NAME);
BLEServer* server = BLEDevice::createServer();
server->setCallbacks(new BleKeyboardCallbacks());
// create an HID device
hid = new BLEHIDDevice(server);
input = hid->inputReport(1); // report ID
output = hid->outputReport(1); // report ID
output->setCallbacks(new OutputCallbacks());
// set manufacturer name
hid->manufacturer()->setValue("Maker Community");
// set USB vendor and product ID
hid->pnp(0x02, 0xe502, 0xa111, 0x0210);
// information about HID device: device is not localized, device can be connected
hid->hidInfo(0x00, 0x02);
// Security: device requires bonding
BLESecurity* security = new BLESecurity();
security->setAuthenticationMode(ESP_LE_AUTH_BOND);
// set report map
hid->reportMap((uint8_t*)REPORT_MAP, sizeof(REPORT_MAP));
hid->startServices();
// set battery level to 100%
hid->setBatteryLevel(100);
// advertise the services
BLEAdvertising* advertising = server->getAdvertising();
advertising->setAppearance(HID_KEYBOARD);
advertising->addServiceUUID(hid->hidService()->getUUID());
advertising->addServiceUUID(hid->deviceInfo()->getUUID());
advertising->addServiceUUID(hid->batteryService()->getUUID());
advertising->start();
Serial.println("BLE ready");
delay(portMAX_DELAY);
};
void SendReturn() {
PureInputReport(88,0);
}
void PureInputReport(byte p_KeyCode, byte p_modifier) {
KeyMessage_t report = { // create input report
.modifiers = p_modifier,
.reserved = 0,
.pressedKeys = {
p_KeyCode,
0, 0, 0, 0, 0
}
};
// send the input report
input->setValue((uint8_t*)&report, sizeof(report));
input->notify();
delay(5);
// release all keys between two characters; otherwise two identical
// consecutive characters are treated as just one key press
input->setValue((uint8_t*)&NO_KEY_PRESSED, sizeof(NO_KEY_PRESSED));
input->notify();
delay(5);
}
void typeText(const char* text) {
KeyMessage_t myKeyMessage;
myKeyMessage.reserved = 0;
uint8_t val;
uint8_t SecondByte;
uint8_t ThirdByte;
int len = strlen(text);
for (int i = 0; i < len; i++) {
for (int i = 0; i < 6; i++) {
myKeyMessage.pressedKeys[i] = 0;
}
// translate character to key combination
val = (uint8_t)text[i];
if (val > KEYMAP_SIZE) { // character has a multibyte representation
switch (val) {
case aeoeue_1stByte: // if it's an äöü
i++;
SecondByte = text[i];
if (SecondByte == ae_2ndByte) { // if it's an ä
myKeyMessage.pressedKeys[0] = 52;
myKeyMessage.modifiers = 0;
break;
}
if (SecondByte == oe_2ndByte) { // if it's an ü
myKeyMessage.pressedKeys[0] = 51;
myKeyMessage.modifiers = 0;
break;
}
if (SecondByte == ue_2ndByte) { // if it's an ü
myKeyMessage.pressedKeys[0] = 47;
myKeyMessage.modifiers = 0;
break;
}
if (SecondByte == Ae_2ndByte) { // if it's an ä
myKeyMessage.pressedKeys[0] = 52;
myKeyMessage.modifiers = Shift;
break;
}
if (SecondByte == Oe_2ndByte) { // if it's an ü
myKeyMessage.pressedKeys[0] = 51;
myKeyMessage.modifiers = Shift;
break;
}
if (SecondByte == Ue_2ndByte) { // if it's an ü
myKeyMessage.pressedKeys[0] = 47;
myKeyMessage.modifiers = Shift;
break;
}
if (SecondByte == SharpS_2ndByte) { // if it's an ü
myKeyMessage.pressedKeys[0] = 45;
myKeyMessage.modifiers = 0;
break;
}
case mue23_1stByte: // if it's a µ²³
i++;
SecondByte = text[i];
if (SecondByte == mue_2ndByte) { // if it's an µ
myKeyMessage.pressedKeys[0] = 16;
myKeyMessage.modifiers = CtrlAlt;
break;
}
if (SecondByte == mue2_2ndByte) { // if it's an ²
myKeyMessage.pressedKeys[0] = 31;
myKeyMessage.modifiers = CtrlAlt;
break;
}
if (SecondByte == mue3_2ndByte) { // if it's an ³
myKeyMessage.pressedKeys[0] = 32;
myKeyMessage.modifiers = CtrlAlt;
break;
}
if (SecondByte == degree_2ndByte) { // if it's an °
myKeyMessage.pressedKeys[0] = 53;
myKeyMessage.modifiers = Shift;
break;
}
if (SecondByte == paragraph_2ndByte) { // if it's an §
myKeyMessage.pressedKeys[0] = 32;
myKeyMessage.modifiers = Shift;
break;
}
case euro_1stByte: // if it's a €-symbol
i++;
SecondByte = text[i];
if (SecondByte == euro_2ndByte) {
i++;
ThirdByte = text[i];
if (ThirdByte == euro_3rdByte) {
myKeyMessage.pressedKeys[0] = 8;
myKeyMessage.modifiers = CtrlAlt;
break;
}
}
default:
myKeyMessage.pressedKeys[0] = 0;
myKeyMessage.modifiers = 0;
break;
}
Serial.println("val > KEYMAP_SIZE special treatment");
}
else { // character has a single-byte representation
KEYMAP myKeyAndMod = keymap[val];
myKeyMessage.modifiers = myKeyAndMod.modifier;
myKeyMessage.pressedKeys[0] = myKeyAndMod.usage;
}
// send the input report
input->setValue((uint8_t*)&myKeyMessage, sizeof(myKeyMessage));
input->notify();
delay(SendDelay);
// release all keys between two characters; otherwise two identical
// consecutive characters are treated as just one key press
input->setValue((uint8_t*)&NO_KEY_PRESSED, sizeof(NO_KEY_PRESSED));
input->notify();
delay(SendDelay);
}
}
#define KEY_CODE_PAGE_UP 0x4b
#define KEY_CODE_PAGE_DOWN 0x4e
void loop() {
BlinkHeartBeatLED(OnBoard_LED,500);
if (isBleConnected && digitalRead(BUTTON_PIN) == LOW) {
// button has been pressed: type message
Serial.println(MESSAGE);
typeText(MESSAGE);
}
if (isBleConnected && digitalRead(Button_D2) == LOW) {
delay(200);
KeyBoardText = "12345678901234567890";
dbg("btn-press",KeyBoardText);
//Serial.println(KeyBoardText);
typeText(KeyBoardText.c_str());
SendReturn();
delay(500);
KeyBoardText = "abcdefghijklmnopqrstuvwxyzäöü";
dbg("btn-press",KeyBoardText);
//Serial.println(KeyBoardText);
typeText(KeyBoardText.c_str());
//SendReturn();
delay(500);
KeyBoardText = "ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜ";
Serial.println(KeyBoardText);
typeText(KeyBoardText.c_str());
//SendReturn();
delay(500);
KeyBoardText = "1234567890!§$%&/()=?^°<>|@µ²³#',;.:-_߀";
Serial.println(KeyBoardText);
typeText(KeyBoardText.c_str());
SendReturn();
delay(500);
}
/*
// update the time
timeClient.update();
// generate the TOTP code and, if different from the previous one, print to screen
myEpocTime = timeClient.getEpochTime();
String newCode = String(totp.getCode(myEpocTime));
if(totpCode!= newCode && false) {
totpCode = String(newCode);
Serial.print("TOTP code: ");
Serial.println(newCode);
}
if ( TimePeriodIsOver(MyTestTimer,5000) && false) {
dbg("1:",myEpocTime);
dbg("2:",timeClient.getFormattedTime() );
dbg("3:",newCode);
}
*/
}
I have tested this democode with all almost all characters available on a german keyboard
abcdefghijklmnopqrstuvwxyzäöüABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜ1234567890!§$%&/()=?^°<>|@µ²³#',;.:-_߀
I have modified the array of struct in the HIDKeyboardTypes.h-library
as I don't use any US-keyboard
#ifdef US_KEYBOARD
/* German keyboard (as HID standard) */
#define KEYMAP_SIZE (127)
const KEYMAP keymap[KEYMAP_SIZE] = {
{0x00, 0 }, /* 0 */
{0x00, 0 }, /* 1 */
{0x00, 0 }, /* 2 */
{0x00, 0 }, /* 3 */
{0x00, 0 }, /* 4 */
{0x00, 0 }, /* 5 */
{0x00, 0 }, /* 6 */
{0x00, 0 }, /* 7 */
{0x00, 0 }, /* 8 */
{0x00, 0 }, /* 9 */
{0x00, 0 }, /* 10 */
{0x00, 0 }, /* 11 */
{0x00, 0 }, /* 12 */
{0x00, 0 }, /* 13 */
{0x00, 0 }, /* 14 */
{0x00, 0 }, /* 15 */
{0x00, 0 }, /* 16 */
{0x00, 0 }, /* 17 */
{0x00, 0 }, /* 18 */
{0x00, 0 }, /* 19 */
{0x00, 0 }, /* 20 */
{0x00, 0 }, /* 21 */
{0x00, 0 }, /* 22 */
{0x00, 0 }, /* 23 */
{0x00, 0 }, /* 24 */
{0x00, 0 }, /* 25 */
{0x00, 0 }, /* 26 */
{0x00, 0 }, /* 27 */
{0x00, 0 }, /* 28 */
{0x00, 0 }, /* 29 */
{0x00, 0 }, /* 30 */
{0x00, 0 }, /* 31 */
{0x00, 0 }, /* 32 */
{0x1E, KEY_SHIFT }, /* 33 ! */
{0x0 , 0 }, /* 34 */
{0x31, 0 }, /* 35 # */
{0x21, KEY_SHIFT }, /* 36 $ */
{0x22, KEY_SHIFT }, /* 37 % */
{0x23, KEY_SHIFT }, /* 38 & */
{0x31, KEY_SHIFT }, /* 39 ' */
{0x25, KEY_SHIFT }, /* 40 ( */
{0x26, KEY_SHIFT }, /* 41 ) */
{0x30, KEY_SHIFT }, /* 42 * */
{0x30, 0 }, /* 43 + */
{0x36, 0 }, /* 44 , */
{0x38, 0 }, /* 45 - */
{0x37, 0 }, /* 46 . */
{0x24, KEY_SHIFT }, /* 47 / */
{0x27, 0 }, /* 48 0 */
{0x1E, 0 }, /* 49 1 */
{0x1F, 0 }, /* 50 2 */
{0x20, 0 }, /* 51 3 */
{0x21, 0 }, /* 52 4 */
{0x22, 0 }, /* 53 5 */
{0x23, 0 }, /* 54 6 */
{0x24, 0 }, /* 55 7 */
{0x25, 0 }, /* 56 8 */
{0x26, 0 }, /* 57 9 */
{0x37, KEY_SHIFT }, /* 58 : */
{0x36, KEY_SHIFT }, /* 59 ; */
{0x64, 0 }, /* 60 < */
{0x27, KEY_SHIFT }, /* 61 = */
{0x64, KEY_SHIFT }, /* 62 > */
{0x2D, KEY_SHIFT }, /* 63 ? */
{0x14, KEY_CTRL_ALT }, /* 64 @ */
{0x04, KEY_SHIFT }, /* 65 A */
{0x05, KEY_SHIFT }, /* 66 B */
{0x06, KEY_SHIFT }, /* 67 C */
{0x07, KEY_SHIFT }, /* 68 D */
{0x08, KEY_SHIFT }, /* 69 E */
{0x09, KEY_SHIFT }, /* 70 F */
{0x0A, KEY_SHIFT }, /* 71 G */
{0x0B, KEY_SHIFT }, /* 72 H */
{0x0C, KEY_SHIFT }, /* 73 I */
{0x0D, KEY_SHIFT }, /* 74 J */
{0x0E, KEY_SHIFT }, /* 75 K */
{0x0F, KEY_SHIFT }, /* 76 L */
{0x10, KEY_SHIFT }, /* 77 M */
{0x11, KEY_SHIFT }, /* 78 N */
{0x12, KEY_SHIFT }, /* 79 O */
{0x13, KEY_SHIFT }, /* 80 P */
{0x14, KEY_SHIFT }, /* 81 Q */
{0x15, KEY_SHIFT }, /* 82 R */
{0x16, KEY_SHIFT }, /* 83 S */
{0x17, KEY_SHIFT }, /* 84 T */
{0x18, KEY_SHIFT }, /* 85 U */
{0x19, KEY_SHIFT }, /* 86 V */
{0x1A, KEY_SHIFT }, /* 87 W */
{0x1B, KEY_SHIFT }, /* 88 X */
{0x1D, KEY_SHIFT }, /* 89 Y */
{0x1C, KEY_SHIFT }, /* 90 Z */
{0x25, KEY_CTRL_ALT }, /* 91 [ */
{0x2D, KEY_CTRL_ALT }, /* 92 \ */
{0x26, KEY_CTRL_ALT }, /* 93 ] */
{0x35, 0 }, /* 94 ^ */
{0x38, KEY_SHIFT }, /* 95 _ */
{0x2E, KEY_SHIFT }, /* 96 ` */
{0x04, 0 }, /* 97 a */
{0x05, 0 }, /* 98 b */
{0x06, 0 }, /* 99 c */
{0x07, 0 }, /* 100 d */
{0x08, 0 }, /* 101 e */
{0x09, 0 }, /* 102 f */
{0x0A, 0 }, /* 103 g */
{0x0B, 0 }, /* 104 h */
{0x0C, 0 }, /* 105 i */
{0x0D, 0 }, /* 106 j */
{0x0E, 0 }, /* 107 k */
{0x0F, 0 }, /* 108 l */
{0x10, 0 }, /* 109 m */
{0x11, 0 }, /* 110 n */
{0x12, 0 }, /* 111 o */
{0x13, 0 }, /* 112 p */
{0x14, 0 }, /* 113 q */
{0x15, 0 }, /* 114 r */
{0x16, 0 }, /* 115 s */
{0x17, 0 }, /* 116 t */
{0x18, 0 }, /* 117 u */
{0x19, 0 }, /* 118 v */
{0x1A, 0 }, /* 119 w */
{0x1B, 0 }, /* 120 x */
{0x1D, 0 }, /* 121 y */
{0x1C, 0 }, /* 122 z */
{0x0 , 0 }, /* 123 { */
{0x64, KEY_CTRL_ALT }, /* 124 | */
{0x0 , 0 }, /* 125 } */
{0x30, KEY_CTRL_ALT }, /* 126 ~ */
};
best regards Stefan