Wireless BLE numpad with F13 monitor switching — ESP32-C3 based 11-key keyboard with DDC/CI integration for seamless dual-monitor workflows.
SwitchPad started as a solution to a workflow problem: switching monitor input between desktop and laptop required reaching behind the display. The result is a custom wireless numpad where a single F13 keypress toggles HDMI inputs via DDC/CI commands.
- Standard wireless numpad (keys 0-9) via BLE HID
- F13 macro key triggers automated monitor input switching
- Eliminates manual monitor button pressing — one keypress switches between HDMI1 (desktop) and HDMI2 (laptop)
Originally aimed at dual-host BLE switching between devices, but pivoted when ESP32-BLE-Keyboard v0.3.0+ introduced Windows reconnection bugs that made reliable power cycling impossible. Downgrading to v0.2.3 solved reconnection issues but revealed ESP32-C3 RAM constraints that made BLE stack restarts unreliable (BT_HCI: status=0x7 errors). The F13 monitor switching approach proved more elegant: the numpad stays connected to the desktop, but controls which system displays on the monitor.
- ESP32-C3 SuperMini - Low-cost ($2-3), power-efficient MCU with native BLE 5.0
- 11 Cherry MX switches - 10 numpad keys + 1 F13 macro key
- Hardware debouncing - 50ms blocking debounce eliminates ghost typing (solved 6× repeat issue)
- BLE HID keyboard - Standard implementation using ESP32-BLE-Keyboard v0.2.3
- LiPo powered - 3.7V battery (500-1000mAh) with TP4056 USB-C charging
- 3D printed enclosure - Custom STL files included, no PCB required
- Monitor switching service - Optional Go-based Windows service for F13→DDC/CI automation
- Single-host stable - Polling-based approach prevents GPIO 0 boot loops
| Component | Qty | Approx. Cost | Notes |
|---|---|---|---|
| ESP32-C3 SuperMini | 1 | $2-3 | AliExpress, 4MB flash variant |
| Cherry MX Switches | 11 | $0.50 each | Any color (Brown recommended for typing) |
| TP4056 Charging Module | 1 | $0.50 | Type-C variant, 1A charge rate |
| SPDT Slide Switch | 1 | $0.30 | CK OS102011MS2Q or equivalent |
| 3.7V LiPo Battery | 1 | $3-5 | 500-1000mAh, protected cell |
| JST 2-pin Connector | 1 | $0.20 | 2.5mm pitch for battery |
| 22-26 AWG Wire | 2m | $1 | Point-to-point wiring |
Total cost: ~$15-20 USD (excluding 3D printing)
Complete BOM with part numbers →
![]() Complete Assembly |
![]() ESP32-C3 Wiring |
![]() Power System |
GPIO Mapping:
GPIO 0-9 → Numpad keys 0-9
GPIO 10 → F13 macro key
All pins → INPUT_PULLUP (switch to GND)
Critical Design Notes:
- GPIO 0 is boot strapping pin — polling used instead of interrupts to prevent boot loops
- No external pull-ups required (firmware uses internal INPUT_PULLUP resistors)
- Direct GPIO-to-GND wiring = minimal component count, simple assembly
- GPIO 20/21 avoided due to limited breakout on SuperMini variant
Located in Hardware/3d/:
- Enclosure.stl — Main housing (~4hr print @ 0.2mm layer height)
- OneKey.stl — Single keycap (print 11×)
- TwoKey.stl — Double-wide keycap (optional for 0/Enter)
- Arduino IDE 1.8.x or 2.x
- ESP32 Arduino Core v2.0.17
- ESP32-BLE-Keyboard v0.2.3 → Download from Asset folder
🔧 F13 Key Support: The ESP32-BLE-Keyboard library must support F13-F24 keys (Hence HID descriptor extended to LOGICAL_MAXIMUM(0x73)). Stock ESP32-BLE-Keyboard only supports up to F12. Details in FIRMWARE.md
-
Install ESP32 board support in Arduino IDE:
- File → Preferences → Additional Board Manager URLs
- Add:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json - Tools → Board → Board Manager → Search "ESP32" → Install v2.0.17
-
Extract
ESP32-BLE-Keyboard v0.2.3.zipto Arduino libraries folder:- Windows:
Documents\Arduino\libraries\ - macOS:
~/Documents/Arduino/libraries/ - Linux:
~/Arduino/libraries/
- Windows:
-
Open
Firmware/Wireless NUMPAD.ino -
Select board: Tools → Board → ESP32 Arduino → ESP32C3 Dev Module
-
Upload (hold BOOT button if upload fails)
Complete firmware documentation →
// Customize device identity
BleKeyboard bleKeyboard("NavyPad", "Navy Industries", 100);Battery level is hardcoded to 100% (cosmetic reporting only, no ADC monitoring implemented).
Optional Windows service that listens for F13 keypresses and executes DDC/CI commands to switch monitor input.
F13 pressed → Windows service detects via keyboard hook
→ Executes ControlMyMonitor.exe
→ Sends VCP code 60 (Input Select)
→ Monitor switches HDMI1 ⇄ HDMI2
- Download ControlMyMonitor from NirSoft
- Extract
ControlMyMonitor.exetoService/directory - Build service:
cd Service/bat build-service.bat - Run
monitor-switch-service.exe - Press F13 on numpad to test
Complete service documentation →
- Monitor must support DDC/CI (enable in OSD settings) [Tested on Viewsonic VX2779-HD-PRO]
- HDMI cable must support DDC/CI (not all do)
- Windows 10/11 (service uses Windows API keyboard hooks)
- 3D Print enclosure and 11 keycaps (~8 hours total print time)
- Solder switches to ESP32-C3 GPIO pins (one pin per switch)
- Wire all switches to common GND rail
- Connect battery through TP4056 module (OUT+ → ESP32 5V, OUT- → GND)
- Install power switch between battery and ESP32
- Flash firmware via Arduino IDE
- Mount in enclosure, install keycaps
- Pair via Bluetooth (device appears as "NavyPad")
No PCB required — point-to-point wiring works reliably for 11 keys.
Cause: ESP32-BLE-Keyboard v0.3.0+ has Windows-specific BLE reconnection bugs
Fix: Use v0.2.3 (included in Asset folder). If already using v0.3.0+, uninstall and install v0.2.3.
Cause: Mechanical switch contact bounce (~60ms duration)
Fix: Firmware implements blocking 50ms debounce (already solved in current firmware).
Cause: GPIO 0 is boot strapping pin — interrupts cause boot failures
Fix: Firmware uses polling approach instead of interrupts (already implemented).
- Verify DDC/CI is enabled in monitor OSD menu (usually under "System" or "Advanced")
- Test cable supports DDC/CI:
ControlMyMonitor.exe /SetValue PRIMARY 60 17 - Check service is running:
tasklist | findstr monitor-switch - View service logs:
Service/monitor-switch.log
- Hold BOOT button during upload
- Check USB cable supports data (not just charging)
- Try lower upload speed: Tools → Upload Speed → 115200
- Check wiring: GPIO pin → Switch → GND
- Verify INPUT_PULLUP in code (line 8-9)
- Test with multimeter: GPIO should read 3.3V open, 0V when pressed
Complete development iterations preserved in /Dev/ directory (not included in this repo):
- 23 firmware versions tracking feature evolution
- 7 conversation logs documenting design decisions
- Hardware iteration notes and testing results
- Library compatibility testing (v0.2.3 vs v0.3.0 comparison)
-
ESP32 RAM fragmentation prevents reliable BLE stack restart without full reboot
bleKeyboard.end()+bleKeyboard.begin()causesBT_HCI: status=0x7errors- Single-host approach more stable than dual-host switching
-
GPIO 0 interrupt attachment causes device bricking during boot
- Boot strapping pin requires polling, not interrupt-driven scanning
- 50ms polling provides adequate 20Hz scan rate for numpad
-
Library v0.3.0 Windows bug breaks reconnection after power cycling
- Downgrading to v0.2.3 resolves issue
- Tracked in ESP32-BLE-Keyboard issue #115
-
Mechanical switch bounce manifests as exactly 6× character repeat
- 60ms bounce duration ÷ 10ms loop interval = 6 false triggers
- Blocking 50ms debounce eliminates ghost typing
MIT License - See LICENSE for details.
- T-vK/ESP32-BLE-Keyboard - BLE HID library (v0.2.3)
- NirSoft ControlMyMonitor - DDC/CI control utility
- ESP32 community for troubleshooting GPIO and BLE stack issues
Navy - GitHub
Project Status: ✅ Complete and functional
Hardware: Validated and tested
Firmware: Stable (v5, single-host polling)
Monitor Switching: Operational with ControlMyMonitor + Go service




