I have a KVM Switch that has a hotkey to switch between devices. The hotkey works when we press Ctrl then Ctrl and then 1 (or 2,3,4) on a keyboard.
I am not that experienced with ESP IDF but I was able to modify the examples in the repo to something that can press they keys like I want.
Here's my code:
https://gist.github.com/haideralipunjab ... 4a0be8c8c9
The issue is this is not able to trigger the hotkey. I suspect that the Switch doesn't think the connected device is a Keyboard. I did try to modify the Product ID / Vendor ID to one of the keyboards that worked, but that didn't work as well.
The keyboards that worked:
Logitech MX Keys
A random mini keyboard mouse combo (I used the Product ID / Vendor ID from this)
Flipper Zero's USB Keyboard app & Bad USB Script. (I am surprised this work).
I am unsure what am I doing wrong.
Need help emulating a keyboard exactly using ESP32 S2 mini.
-
haideralipunjabi
- Posts: 2
- Joined: Sun May 11, 2025 4:21 pm
Re: Need help emulating a keyboard exactly using ESP32 S2 mini.
Hi haideralipunjabi,
If your KVM switch doesn't respond to the hotkey from the ESP32, the issue is most likely how Ctrl is being sent – it must go in the modifier byte, not as a regular key. You also need to follow the correct press and release sequence.
Here's a working example for the hotkey Ctrl + Ctrl + 2 that should be recognized even by picky KVM switches:
Recommendations:
Make sure your descriptor has boot_protocol = true
Right after USB mounts, send tud_hid_keyboard_report(..., 0, NULL); as an "idle" state
If it still doesn't work, try another VID/PID, but the most important thing is comparing lsusb -v with a working keyboard
If your KVM switch doesn't respond to the hotkey from the ESP32, the issue is most likely how Ctrl is being sent – it must go in the modifier byte, not as a regular key. You also need to follow the correct press and release sequence.
Here's a working example for the hotkey Ctrl + Ctrl + 2 that should be recognized even by picky KVM switches:
Code: Select all
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "tinyusb.h"
#include "class/hid/hid_device.h"
#include "esp_log.h"
#define TAG "KVM_Trigger"
// --- HID Report descriptor ---
const uint8_t hid_report_descriptor[] = {
TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(HID_ITF_PROTOCOL_KEYBOARD)),
};
// --- String descriptors ---
const char *hid_string_descriptor[] = {
(char[]){0x09, 0x04}, // Language: English (0x0409)
"ESP32-S3", // Manufacturer
"Virtual Keyboard", // Product name
"123456", // Serial number
"Keyboard Interface", // Interface description
};
// --- USB Configuration descriptor ---
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN)
const uint8_t hid_configuration_descriptor[] = {
TUD_CONFIG_DESCRIPTOR(1, 1, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
TUD_HID_DESCRIPTOR(0, 4, true, sizeof(hid_report_descriptor), 0x81, 8, 10),
};
// --- USB Device descriptor ---
const tusb_desc_device_t device_descriptor = {
.bLength = sizeof(tusb_desc_device_t),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = 0x00,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0xCafe,
.idProduct = 0x4020,
.bcdDevice = 0x0100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01};
// --- TinyUSB HID callbacks ---
uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance)
{
return hid_report_descriptor;
}
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen)
{
return 0;
}
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize)
{
// Not used
}
// --- Helper functions to send key combos ---
// Send only Ctrl (without any other key)
void send_ctrl_only()
{
tud_hid_keyboard_report(HID_ITF_PROTOCOL_KEYBOARD, KEYBOARD_MODIFIER_LEFTCTRL, NULL);
vTaskDelay(pdMS_TO_TICKS(100));
tud_hid_keyboard_report(HID_ITF_PROTOCOL_KEYBOARD, 0, NULL);
vTaskDelay(pdMS_TO_TICKS(100));
}
// Send Ctrl + a specific key
void send_ctrl_plus_key(uint8_t key)
{
uint8_t keycode[6] = {0};
keycode[0] = key;
tud_hid_keyboard_report(HID_ITF_PROTOCOL_KEYBOARD, KEYBOARD_MODIFIER_LEFTCTRL, keycode);
vTaskDelay(pdMS_TO_TICKS(100));
tud_hid_keyboard_report(HID_ITF_PROTOCOL_KEYBOARD, 0, NULL);
vTaskDelay(pdMS_TO_TICKS(100));
}
// --- Main application ---
void app_main(void)
{
// Configure and install TinyUSB driver
const tinyusb_config_t tusb_cfg = {
.device_descriptor = &device_descriptor,
.string_descriptor = hid_string_descriptor,
.string_descriptor_count = sizeof(hid_string_descriptor) / sizeof(hid_string_descriptor[0]),
.external_phy = false,
#if TUD_OPT_HIGH_SPEED
.fs_configuration_descriptor = hid_configuration_descriptor,
.hs_configuration_descriptor = hid_configuration_descriptor,
#else
.configuration_descriptor = hid_configuration_descriptor,
#endif
};
tinyusb_driver_install(&tusb_cfg);
ESP_LOGI(TAG, "USB HID keyboard ready");
// Wait until the USB host has mounted the device
while (!tud_mounted())
{
vTaskDelay(pdMS_TO_TICKS(100));
}
// Send KVM hotkey: Ctrl + Ctrl + 2
send_ctrl_only();
send_ctrl_only();
send_ctrl_plus_key(HID_KEY_2);
ESP_LOGI(TAG, "KVM hotkey sent");
}
Make sure your descriptor has boot_protocol = true
Right after USB mounts, send tud_hid_keyboard_report(..., 0, NULL); as an "idle" state
If it still doesn't work, try another VID/PID, but the most important thing is comparing lsusb -v with a working keyboard
-
haideralipunjabi
- Posts: 2
- Joined: Sun May 11, 2025 4:21 pm
Re: Need help emulating a keyboard exactly using ESP32 S2 mini.
Thank you for your help. Your exact example didn't work, I had to modify it a bit but now I have working code.
I plan to add MQTT to this and use it with Home Assistant. I think I can figure out that part on my own.
Thank you!
Code: Select all
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "tinyusb.h"
#include "class/hid/hid_device.h"
#include "esp_log.h"
#define TAG "KVM_Trigger"
enum
{
ITF_NUM_KEYBOARD,
ITF_NUM_MOUSE,
ITF_NUM_TOTAL
};
// --- HID Report descriptor ---
const uint8_t hid_report_descriptor[] = {
TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(HID_ITF_PROTOCOL_KEYBOARD)),
};
// --- String descriptors ---
const char *hid_string_descriptor[] = {
(char[]){0x09, 0x04}, // Language: English (0x0409)
"Logitech", // Manufacturer
"Composite Device", // Product
"LOGI123456", // Serial
"Keyboard Interface", // HID interface
};
// --- USB Configuration descriptor ---
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN)
const uint8_t hid_configuration_descriptor[] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
// Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval
TUD_HID_DESCRIPTOR(ITF_NUM_KEYBOARD, 0, HID_ITF_PROTOCOL_KEYBOARD, sizeof(hid_report_descriptor), 0x81, CFG_TUD_HID_EP_BUFSIZE, 10),
};
// --- USB Device descriptor ---
const tusb_desc_device_t device_descriptor = {
.bLength = sizeof(tusb_desc_device_t),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = 0x00,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0x0513, // Example: Logitech
.idProduct = 0x0318, // Example: Known keyboard PID
.bcdDevice = 0x0100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01};
// --- TinyUSB HID callbacks ---
uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance)
{
return hid_report_descriptor;
}
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen)
{
return 0;
}
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize)
{
// Not used
}
// --- Helper functions to send key combos ---
// Send only Ctrl (without any other key)
void send_ctrl_only()
{
tud_hid_keyboard_report(HID_ITF_PROTOCOL_KEYBOARD, KEYBOARD_MODIFIER_LEFTCTRL, NULL);
vTaskDelay(pdMS_TO_TICKS(100));
tud_hid_keyboard_report(HID_ITF_PROTOCOL_KEYBOARD, 0, NULL);
vTaskDelay(pdMS_TO_TICKS(100));
}
void send_key(uint8_t key)
{
uint8_t keycode[6] = {0};
keycode[0] = key;
tud_hid_keyboard_report(HID_ITF_PROTOCOL_KEYBOARD, 0, keycode);
vTaskDelay(pdMS_TO_TICKS(100));
tud_hid_keyboard_report(HID_ITF_PROTOCOL_KEYBOARD, 0, NULL);
vTaskDelay(pdMS_TO_TICKS(100));
}
// --- Main application ---
void app_main(void)
{
// Configure and install TinyUSB driver
const tinyusb_config_t tusb_cfg = {
.device_descriptor = &device_descriptor,
.string_descriptor = hid_string_descriptor,
.string_descriptor_count = sizeof(hid_string_descriptor) / sizeof(hid_string_descriptor[0]),
.external_phy = false,
#if TUD_OPT_HIGH_SPEED
.fs_configuration_descriptor = hid_configuration_descriptor,
.hs_configuration_descriptor = hid_configuration_descriptor,
#else
.configuration_descriptor = hid_configuration_descriptor,
#endif
};
tinyusb_driver_install(&tusb_cfg);
ESP_LOGI(TAG, "USB HID keyboard ready");
// Wait until the USB host has mounted the device
while (!tud_mounted())
{
vTaskDelay(pdMS_TO_TICKS(100));
}
tud_hid_keyboard_report(HID_ITF_PROTOCOL_KEYBOARD, 0, NULL);
vTaskDelay(pdMS_TO_TICKS(100));
// Send KVM hotkey: Ctrl + Ctrl + 2
send_ctrl_only();
send_ctrl_only();
send_key(HID_KEY_2);
ESP_LOGI(TAG, "KVM hotkey sent");
}Thank you!
Who is online
Users browsing this forum: PerplexityBot and 1 guest
