My project so far
#include <Arduino.h>
#include "USB.h"
#include "USBHIDMouse.h"
#include "USBHIDKeyboard.h"
#include <esp_display_panel.hpp>
//#include <BleKeyboard.h>
#include <lvgl.h>
#include "lvgl_v8_port.h"
//#include "Keyboard.h"
USBHIDMouse Mouse;
USBHIDKeyboard Keyboard;
//BleKeyboard bleKeyboard("WS-BLE-Keyboard", "Waveshare", 100);
using namespace esp_panel::drivers;
using namespace esp_panel::board;
#define GRID_COLS 6
#define GRID_ROWS 4
#define MAX_BUTTONS (GRID_COLS * GRID_ROWS)
// Helper: explicit "auto-place in this row"
#define AUTO_COL 255
// Default button colors
static const lv_color_t DEFAULT_IDLE = lv_color_hex(0x2B2D31);
static const lv_color_t DEFAULT_PRESSED = lv_color_hex(0x1F6FEB);
// Actions (added ACTION_COMBO for true multi-key combos)
typedef enum { ACTION_TEXT, ACTION_KEY, ACTION_PARAGRAPH, ACTION_NAV, ACTION_CALL, ACTION_COMBO } action_t;
typedef void (*action_fn_t)(uint32_t);
Board *board = nullptr;
// Button + Screen defs (added combo storage)
typedef struct {
const char *label;
const void *img_src;
action_t action_type;
const char *text; // TEXT / PARAGRAPH
uint8_t keycode; // KEY
lv_color_t color_idle;
lv_color_t color_pressed;
uint8_t col_span; // 1..6
uint8_t row_span; // 1..4
uint8_t nav_target; // NAV target idx
uint8_t row; // 0..(GRID_ROWS-1)
uint8_t col; // 0..(GRID_COLS-1) or AUTO_COL
action_fn_t fn; // CALL
uint32_t fn_arg; // CALL
uint8_t combo_len; // COMBO: number of keys used
uint8_t combo_keys[6]; // COMBO: up to 6 keys (modifiers + normal keys)
} button_config_t;
typedef struct {
button_config_t *items;
uint16_t count;
lv_color_t bg_color;
} screen_def_t;
// ---------------- Helper constructors to make configs compact ----------------
static inline button_config_t BtnText(
const char *label,
const char *text,
uint8_t row,
uint8_t col = AUTO_COL,
uint8_t col_span = 1,
uint8_t row_span = 1,
lv_color_t idle = DEFAULT_IDLE,
lv_color_t pressed = DEFAULT_PRESSED
) {
button_config_t b = {label, NULL, ACTION_TEXT, text, 0, idle, pressed, col_span, row_span, 0, row, col, NULL, 0, 0, {0}};
return b;
}
static inline button_config_t BtnParagraph(
const char *label,
const char *text,
uint8_t row,
uint8_t col = AUTO_COL,
uint8_t col_span = 1,
uint8_t row_span = 1,
lv_color_t idle = DEFAULT_IDLE,
lv_color_t pressed = DEFAULT_PRESSED
) {
button_config_t b = {label, NULL, ACTION_PARAGRAPH, text, 0, idle, pressed, col_span, row_span, 0, row, col, NULL, 0, 0, {0}};
return b;
}
static inline button_config_t BtnKey(
const char *label,
uint8_t keycode,
uint8_t row,
uint8_t col = AUTO_COL,
uint8_t col_span = 1,
uint8_t row_span = 1,
lv_color_t idle = DEFAULT_IDLE,
lv_color_t pressed = DEFAULT_PRESSED
) {
button_config_t b = {label, NULL, ACTION_KEY, NULL, keycode, idle, pressed, col_span, row_span, 0, row, col, NULL, 0, 0, {0}};
return b;
}
static inline button_config_t BtnNav(
const char *label,
uint8_t target_screen,
uint8_t row,
uint8_t col = AUTO_COL,
uint8_t col_span = 1,
uint8_t row_span = 1,
lv_color_t idle = lv_color_hex(0x1565C0),
lv_color_t pressed = lv_color_hex(0x0D47A1)
) {
button_config_t b = {label, NULL, ACTION_NAV, NULL, 0, idle, pressed, col_span, row_span, target_screen, row, col, NULL, 0, 0, {0}};
return b;
}
static inline button_config_t BtnCall(
const char *label,
action_fn_t fn,
uint32_t arg,
uint8_t row,
uint8_t col = AUTO_COL,
uint8_t col_span = 1,
uint8_t row_span = 1,
lv_color_t idle = DEFAULT_IDLE,
lv_color_t pressed = DEFAULT_PRESSED
) {
button_config_t b = {label, NULL, ACTION_CALL, NULL, 0, idle, pressed, col_span, row_span, 0, row, col, fn, arg, 0, {0}};
return b;
}
static inline button_config_t BtnCombo(
const char *label,
const uint8_t *keys, // array of HID_KEY_* (include modifiers like HID_KEY_CONTROL_LEFT)
uint8_t len,
uint8_t row,
uint8_t col = AUTO_COL,
uint8_t col_span = 1,
uint8_t row_span = 1,
lv_color_t idle = DEFAULT_IDLE,
lv_color_t pressed = DEFAULT_PRESSED
) {
if (len > 6) len = 6;
button_config_t b = {label, NULL, ACTION_COMBO, NULL, 0, idle, pressed, col_span, row_span, 0, row, col, NULL, 0, len, {0}};
for (uint8_t i = 0; i < len; i++) b.combo_keys[i] = keys[i];
return b;
}
// ---------------- Example functions for ACTION_CALL ----------------
static void exampleFunctionA(uint32_t arg) { Serial0.printf("[EXAMPLE A] Parameter: %lu\n", (unsigned long)arg); }
static void exampleFunctionB(uint32_t arg) { Serial0.printf("[EXAMPLE B] Parameter: %lu\n", (unsigned long)arg); }
// Convenience arrays for Windows combos
static const uint8_t COMBO_CTRL_C[] = { HID_KEY_CONTROL_LEFT, HID_KEY_C };
static const uint8_t COMBO_WIN_SHIFT_S[] = { HID_KEY_GUI_LEFT, HID_KEY_SHIFT_LEFT, HID_KEY_S };
// ---------------- Screen configs (clean + combo examples) ----------------
static button_config_t screen1[] = {
// Row 0: two wide buttons (3 columns each)
BtnText("Hello", "Hello World!", /*row*/0, /*col*/0, /*col_span*/3),
BtnParagraph("Goodbye", "OK, I will leave you to it. Goodbye.", /*row*/0, /*col*/3, /*col_span*/3),
// Row 1: three medium buttons
BtnKey("Copy", HID_KEY_C, /*row*/1, AUTO_COL, /*col_span*/2),
BtnKey("Paste", HID_KEY_V, /*row*/1, AUTO_COL, /*col_span*/2),
BtnKey("Enter", HID_KEY_ENTER, /*row*/1, AUTO_COL, /*col_span*/2),
// Row 2: three medium buttons with custom colors
BtnKey("Up", HID_KEY_ARROW_UP, /*row*/2, AUTO_COL, /*col_span*/2, 1, lv_color_hex(0x2E7D32), lv_color_hex(0x1B5E20)),
BtnKey("Down", HID_KEY_ARROW_DOWN, /*row*/2, AUTO_COL, /*col_span*/2, 1, lv_color_hex(0xC62828), lv_color_hex(0x8E0000)),
BtnKey("Mute", HID_KEY_MUTE, /*row*/2, AUTO_COL, /*col_span*/2, 1, lv_color_hex(0x455A64), lv_color_hex(0x263238)),
// Row 3: add real combos for Windows
BtnCombo("Ctrl+C", COMBO_CTRL_C, sizeof(COMBO_CTRL_C), 3),
BtnCombo("Win+Shift+S", COMBO_WIN_SHIFT_S, sizeof(COMBO_WIN_SHIFT_S), 3),
BtnKey("ESC", HID_KEY_ESCAPE, 3),
BtnKey("Tab", HID_KEY_TAB, 3),
BtnKey("Vol+", HID_KEY_VOLUME_UP, 3),
BtnNav("Go â 2", 1, 3),
};
static const uint16_t SCREEN1_BTN_COUNT = sizeof(screen1)/sizeof(screen1[0]);
static button_config_t screen2[] = {
// Row 0: six small
BtnKey("F1", HID_KEY_F1, 0), BtnKey("F2", HID_KEY_F2, 0), BtnKey("F3", HID_KEY_F3, 0),
BtnKey("F4", HID_KEY_F4, 0), BtnKey("F5", HID_KEY_F5, 0), BtnKey("F6", HID_KEY_F6, 0),
// Rows 1-2: one big 3x2 button (left), two stacked 3x1 (right)
BtnCall("Mixer", exampleFunctionA, 777, /*row*/1, /*col*/0, /*col_span*/3, /*row_span*/2, lv_color_hex(0x4E342E), lv_color_hex(0x5D4037)),
BtnKey("Home", HID_KEY_HOME, /*row*/1, /*col*/3, /*col_span*/3, /*row_span*/1, lv_color_hex(0x1A237E), lv_color_hex(0x283593)),
BtnKey("End", HID_KEY_END, /*row*/2, /*col*/3, /*col_span*/3, /*row_span*/1, lv_color_hex(0x1A237E), lv_color_hex(0x283593)),
// Row 3: two medium + nav
BtnKey("Del", HID_KEY_DELETE, 3, AUTO_COL, 2),
BtnKey("PgUp", HID_KEY_PAGE_UP, 3, AUTO_COL, 2),
BtnNav("â Back 1", 0, 3, AUTO_COL, 2),
};
static const uint16_t SCREEN2_BTN_COUNT = sizeof(screen2)/sizeof(screen2[0]);
static screen_def_t SCREENS[] = {
{ screen1, SCREEN1_BTN_COUNT, lv_color_hex(0x000000) },
{ screen2, SCREEN2_BTN_COUNT, lv_color_hex(0x202020) },
};
static const uint8_t SCREEN_COUNT = sizeof(SCREENS)/sizeof(SCREENS[0]);
static uint8_t current_screen = 0;
static lv_obj_t *btn[MAX_BUTTONS];
static void rebuild_ui();
// ---------------- Events ----------------
static void btn_event_cb(lv_event_t * e) {
lv_event_code_t code = lv_event_get_code(e);
uint16_t id = (uint16_t)(uintptr_t)lv_event_get_user_data(e);
if (current_screen >= SCREEN_COUNT) return;
screen_def_t *sdef = &SCREENS[current_screen];
if (id >= sdef->count) return;
button_config_t *cfg = &sdef->items[id];
if (code == LV_EVENT_SHORT_CLICKED) {
switch (cfg->action_type) {
case ACTION_NAV:
if (cfg->nav_target < SCREEN_COUNT) {
Serial0.printf("[NAV] Screen %u -> %u\n", current_screen, cfg->nav_target);
current_screen = cfg->nav_target;
lvgl_port_lock(-1); rebuild_ui(); lvgl_port_unlock();
}
break;
case ACTION_TEXT:
if (cfg->text) { Serial0.printf("[TEXT] %s\n", cfg->text); Keyboard.print(cfg->text);Mouse.move(40, 0); }
break;
case ACTION_PARAGRAPH:
if (cfg->text) { Serial0.printf("[PARA] %s\n", cfg->text); Keyboard.print(cfg->text); Keyboard.write(HID_KEY_ENTER); }
break;
case ACTION_KEY:
Serial0.printf("[KEY] 0x%02X (%s)\n", cfg->keycode, cfg->label ? cfg->label : "");
Keyboard.write(cfg->keycode);
break;
case ACTION_CALL:
Serial0.printf("[CALL] %s arg=%lu\n", cfg->label ? cfg->label : "(no label)", (unsigned long)cfg->fn_arg);
if (cfg->fn) cfg->fn(cfg->fn_arg);
break;
case ACTION_COMBO:
Serial0.printf("[COMBO] %s keys:", cfg->label ? cfg->label : "(no label)");
for (uint8_t i=0; i<cfg->combo_len; i++) { Serial0.printf(" 0x%02X", cfg->combo_keys[i]); }
Serial0.println();
// Press all keys (modifiers first if provided that way), then release all
for (uint8_t i=0; i<cfg->combo_len; i++) Keyboard.press(cfg->combo_keys[i]);
Keyboard.releaseAll();
break;
}
}
}
// ---------------- UI build ----------------
static void rebuild_ui() {
lv_obj_t *scr = lv_scr_act();
lv_obj_clean(scr);
lv_obj_set_style_bg_color(scr, SCREENS[current_screen].bg_color, LV_PART_MAIN);
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, LV_PART_MAIN);
static lv_coord_t col_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST};
static lv_coord_t row_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST};
lv_obj_set_layout(scr, LV_LAYOUT_GRID);
lv_obj_set_grid_dsc_array(scr, col_dsc, row_dsc);
lv_obj_set_style_pad_row(scr, 10, 0);
lv_obj_set_style_pad_column(scr, 10, 0);
lv_obj_set_style_pad_all(scr, 10, 0);
bool occ[GRID_ROWS][GRID_COLS] = {false};
screen_def_t *sdef = &SCREENS[current_screen];
for (uint16_t i = 0, placed = 0; i < sdef->count && placed < MAX_BUTTONS; i++) {
button_config_t *cfg = &sdef->items[i];
uint8_t span_c = cfg->col_span; if (span_c < 1) span_c = 1; if (span_c > GRID_COLS) span_c = GRID_COLS;
uint8_t span_r = cfg->row_span; if (span_r < 1) span_r = 1; if (span_r > GRID_ROWS) span_r = GRID_ROWS;
uint8_t row = cfg->row; if (row >= GRID_ROWS) row = GRID_ROWS - 1;
int start_col = -1;
if (cfg->col < GRID_COLS) start_col = cfg->col;
else {
for (int c = 0; c <= GRID_COLS - span_c; c++) {
bool fits_auto = true;
for (uint8_t rr = row; rr < row + span_r && rr < GRID_ROWS; rr++) {
for (int cc = c; cc < c + span_c; cc++) { if (occ[rr][cc]) { fits_auto = false; break; } }
if (!fits_auto) break;
}
if (fits_auto) { start_col = c; break; }
}
}
if (start_col < 0) continue;
for (uint8_t rr = row; rr < row + span_r && rr < GRID_ROWS; rr++)
for (int cc = start_col; cc < start_col + span_c; cc++) occ[rr][cc] = true;
lv_obj_t *o = lv_btn_create(scr);
lv_obj_set_style_bg_color(o, cfg->color_idle, LV_PART_MAIN);
lv_obj_set_style_bg_color(o, cfg->color_pressed, LV_PART_MAIN | LV_STATE_PRESSED);
lv_obj_set_grid_cell(o, LV_GRID_ALIGN_STRETCH, start_col, span_c,
LV_GRID_ALIGN_STRETCH, row, span_r);
lv_obj_add_event_cb(o, btn_event_cb, LV_EVENT_SHORT_CLICKED, (void*)(uintptr_t)i);
lv_obj_t *label = lv_label_create(o);
lv_label_set_text(label, cfg->label ? cfg->label : "");
lv_obj_center(label);
btn[i] = o; placed++;
}
}
// ---------------- Setup / Loop ----------------
void setup() {
Serial0.begin(9600);
USB.productName("ES3P32S3 Test KBzz");
USB.manufacturerName("MyDevices");
//bleKeyboard.begin(); // advertises as a BLE HID keyboard
Mouse.begin();
Keyboard.begin();
USB.begin();
board = new Board();
board->init();
#if LVGL_PORT_AVOID_TEARING_MODE
auto lcd = board->getLCD();
lcd->configFrameBufferNumber(LVGL_PORT_DISP_BUFFER_NUM);
#if ESP_PANEL_DRIVERS_BUS_ENABLE_RGB && CONFIG_IDF_TARGET_ESP32S3
auto lcd_bus = lcd->getBus();
if (lcd_bus->getBasicAttributes().type == ESP_PANEL_BUS_TYPE_RGB) {
static_cast<BusRGB *>(lcd_bus)->configRGB_BounceBufferSize(lcd->getFrameWidth() * 10);
}
#endif
#endif
// APPEARS THAT THE FOLLOWING IS CAUSING ISSUES
// removed the following to get rid of assert so just called board->begin
// assert(board->begin());
board->begin();
//tried to disable getTouch but no joy
//lvgl_port_init(board->getLCD(),nullptr);
lvgl_port_init(board->getLCD(), board->getTouch());
lvgl_port_lock(-1); rebuild_ui(); lvgl_port_unlock();
// tried the following to deinitialise the board and then did USB begin with no joy
//lvgl_port_deinit();
}
void loop() {
Keyboard.print("Hello from ESP32-S3zz");
//bleKeyboard.print("hello world");
Serial0.print("Hello ");
delay(5000);
}