Hardware manual · rev. 2026-07

TRKeys 4-Key Macropad

A DIY four-button keypad built on an ESP32-C3. A single hardware switch picks one of three completely different personalities at power-on — a Bluetooth keyboard, a Wi-Fi button that fires MQTT messages, or a daily habit tracker.

Mode 1

BLE Keyboard

Each key sends a Bluetooth HID keystroke to a paired computer or phone.

switch throw A · A=HIGH B=LOW
Mode 2

Wi-Fi / MQTT

Each key publishes a message to an MQTT broker over Wi-Fi, with LED feedback.

switch throw B · A=LOW B=HIGH
Mode 3

Habit Tracker

Four lit LEDs; press a key to mark a habit done for the day. Auto-resets nightly.

switch throw C · A=HIGH B=HIGH
⚙ Configure it in your browser. Everything below — key mappings, Wi-Fi, and MQTT broker details — is editable at runtime with no reflashing, using the TRKeys Configurator. Plug the board in over USB, open the page in Chrome or Edge, and hit Connect. (Web Serial; desktop Chrome/Edge only.)

Overview

TRKeys is firmware for a four-button macropad running on an ESP32-C3 (RISC-V, Wi-Fi + Bluetooth LE). All three modes are compiled into a single firmware image; the active mode is chosen by a physical mode-select switch that is read once at boot. Changing modes means flipping the switch and power-cycling (or resetting) the board — no reflashing required.

PropertyValue
MicrocontrollerESP32-C3 (QFN32, rev v0.4), 4 MB flash, 40 MHz crystal
FrameworkESP-IDF v5.3.2
Bluetooth stackNimBLE (HID-over-GATT)
Keys / LEDs4 push-buttons, 4 indicator LEDs
Console / flashingNative USB (USB-Serial-JTAG)
BLE device nameTRKeys

Quick start

  1. Pick a mode on the mode-select switch (see the switch map below).
  2. Power the board over USB. On boot the four LEDs blink together to confirm the mode: 1 blink = BLE, 2 blinks = Wi-Fi/MQTT, 3 blinks = Habit tracker.
  3. Use it:
    • BLE — pair with "TRKeys" from your computer/phone Bluetooth menu, then press keys.
    • Wi-Fi/MQTT — the board joins Wi-Fi and connects to the broker; press keys to publish.
    • Habit tracker — all four LEDs light; press a key to switch its LED off for the day.

Board & pinout

Buttons are wired active-high (common leg to 3V3, sense pin pulled down internally). LEDs are wired active-low — 3V3 → 220 Ω resistor → LED → GPIO — so a GPIO driven low turns the LED on.

FunctionKey 1Key 2Key 3Key 4
Button GPIOGPIO0GPIO1GPIO5GPIO6
LED GPIOGPIO4GPIO7GPIO20GPIO21
SignalGPIONotes
Mode-select AGPIO3Input, internal pull-down
Mode-select BGPIO10Input, internal pull-down
Console / USBnative USBUSB-Serial-JTAG — flashing, monitor, and the config console all share this port
Button debounce is 20 ms. Buttons trigger on a GPIO interrupt (any edge) and are confirmed after the debounce delay.

Mode-select switch

The mode is encoded on just two GPIOs using a 4-position switch (OFF plus three "on" throws). Each throw ties the board's 3V3 to a specific 2-bit combination of GPIO3 (A) and GPIO10 (B). The pins are read once at boot, 5 ms after the pull-downs are enabled.

ModeA (GPIO3)B (GPIO10)Boot blinks
1 · BLE KeyboardHIGHLOW1
2 · Wi-Fi / MQTTLOWHIGH2
3 · Habit TrackerHIGHHIGH3
If the switch is unwired (both pins read LOW), the firmware falls back to Mode 2 (Wi-Fi/MQTT). This is a bring-up convenience — with the switch installed, one of the three valid combinations is always selected.

Wiring notes

Mode 1 · BLE Keyboard

The board advertises as a standard Bluetooth LE HID keyboard named TRKeys. Pair it from any host's Bluetooth menu; it reconnects automatically and re-advertises if the host disconnects. Each key press sends a modifier + keycode "tap" (press then release) as a standard 8-byte boot-keyboard report.

KeyDefault comboModifier byteKeycode
Key 1Ctrl+10x010x1E
Key 2Ctrl+20x010x1F
Key 3Ctrl+30x010x20
Key 4Ctrl+40x010x21

Key mappings are configurable at runtime over the serial console (see below) — no reflashing needed. In BLE mode each key's LED lights while the button is physically held and turns off on release. The modifier byte follows the USB HID spec: 0x01 = Left Ctrl, 0x02 = Left Shift, 0x08 = Left GUI (⌘ on macOS).

USB HID identity: VID 0x16C0, PID 0x05DF, appearance = Keyboard.

Mode 2 · Wi-Fi / MQTT

The board joins a Wi-Fi network and opens a TLS WebSocket connection to an MQTT broker. Each key press publishes a configurable JSON message to the broker; the board also subscribes to a response topic. Power-save is disabled to keep the long-lived TLS/WebSocket connection stable.

SettingValue
Brokerwss://mqtt.uranus.im/mqtt (WebSocket Secure / TLS)
Client IDdevice1 (broker ACL requires this exact ID)
Publish topicagents/default/device1/messages
Subscribe topicagents/default/device1/responses
Default payloads{"message":"button 1"}button 4
TLS verificationBuilt-in Mozilla CA certificate bundle

LED feedback: after a press the four LEDs run a chase animation while awaiting the broker's response; when a response arrives on the subscribe topic, all four LEDs flash together once.

Wi-Fi credentials, broker details, topics, and per-key payloads are all runtime-editable from the Configurator (stored in NVS) — no reflashing. The compiled-in USER CONFIG values are just the first-boot defaults.

Mode 3 · Habit Tracker

A daily habit tracker. Each of the four LEDs represents a habit and starts lit ("not done yet today"). Pressing a key marks that habit done: its LED turns off, the state is saved to flash, and — because the board also connects to Wi-Fi/MQTT in this mode — a message is published to the broker. A habit can only be marked once per day.

Set your timezone. With the default UTC0, the nightly reset fires at midnight UTC, which may be the middle of your day.

Firmware defaults (compile-time)

Every setting below is editable at runtime via the Configurator and stored in NVS. The values in the USER CONFIG block at the top of main/main.c are only the first-boot defaults (used until something is saved over serial) — edit and reflash only if you want to change those baked-in defaults.

#define WIFI_SSID        "<your-ssid>"
#define WIFI_PASS        "<your-wifi-password>"

#define MQTT_BROKER_URI   "wss://mqtt.uranus.im/mqtt"
#define MQTT_USERNAME     "default"
#define MQTT_PASSWORD     "<broker-password>"
#define MQTT_CLIENT_ID    "device1"
#define MQTT_TOPIC_PUB    "agents/default/device1/messages"
#define MQTT_TOPIC_SUB    "agents/default/device1/responses"

#define COPYPASTE_MOD    0x01     /* Left Ctrl; use 0x08 for macOS Cmd */
#define HID_KEY_1        0x1E     /* keys 1-4 default to Ctrl+1..Ctrl+4 */

#define SNTP_SERVER      "pool.ntp.org"
#define HABIT_TZ         "UTC0"   /* set to your local POSIX TZ string */
Secrets. The real Wi-Fi and MQTT passwords are stored in this block in the firmware source, not on this public page. Keep them out of any public repository or shared build.

Serial config console

Per-key settings can be changed live over the same USB serial port used for flashing — no rebuild required. Open the port (any serial terminal) and use this simple line protocol. Changed values are written to NVS immediately and persist across reboots.

The Configurator speaks this protocol for you, but it's plain text you can drive from any serial terminal too.

CommandEffect
CFG?Dump all config (device + per-key + active MODE); output ends with a CFG:END line.
CFG:REBOOTRestart the board (needed to apply Wi-Fi / broker changes).
Per-key (KEY1–KEY4)
CFG:SET KEY<n>_BLE_MOD=0x01BLE modifier byte for key n (Ctrl 0x01, Shift 0x02, Alt 0x04, GUI 0x08; OR them).
CFG:SET KEY<n>_BLE_KEY=0x1EBLE HID keycode for key n.
CFG:SET KEY<n>_MQTT=<text>Mode 2 MQTT payload for key n (≤63 chars).
CFG:SET KEY<n>_HABIT=<text>Mode 3 habit-tracker payload for key n (≤63 chars).
Device-wide (Wi-Fi / broker / habit cycle)
CFG:SET WIFI_SSID=<text> / WIFI_PASS=<text>Wi-Fi network credentials.
CFG:SET MQTT_URI=<uri>Broker URI, e.g. wss://host/mqtt or mqtt://host:1883.
CFG:SET MQTT_USER= / MQTT_PASS= / MQTT_CLIENTID=Broker username, password, and client ID.
CFG:SET MQTT_PUB=<topic> / MQTT_SUB=<topic>Publish and subscribe topics.
CFG:SET HABIT_CYCLE=<0|1|2>Habit reset cycle: 0 = hourly, 1 = daily, 2 = weekly.

Each CFG:SET replies CFG:OK on success or CFG:ERR … on a bad key/field. Example session:

> CFG?
CFG:KEY1_BLE_MOD=0x01
CFG:KEY1_BLE_KEY=0x1E
CFG:KEY1_MQTT={"message":"button 1"}
... (keys 2-4) ...
CFG:END

> CFG:SET KEY1_BLE_KEY=0x04      # remap key 1 to the letter "a"
CFG:OK

> CFG:SET KEY2_MQTT={"cmd":"lights_on"}
CFG:OK

Stored state (NVS)

Both the per-key config and the habit-tracker state are kept in the ESP32's non-volatile storage under the cfg namespace.

KeyContents
keycfg5Per-key BLE modifiers, BLE keycodes, Mode 2 MQTT payloads, and Mode 3 habit payloads.
devcfg1Device-wide: Wi-Fi SSID/password, MQTT URI/user/password/client ID, publish & subscribe topics, and habit cycle.
habitst2Habit tracker runtime state: last-reset period bucket and the "already done this period" bitmask.

Build & flash

Built with ESP-IDF v5.3.2, target esp32c3. From the project directory:

# one-time: select the target
idf.py set-target esp32c3

# build, flash, and open the serial monitor (replace COM4 with your port)
idf.py -p COM4 build flash monitor

To flash an already-built image without rebuilding (esptool directly):

esptool.py --chip esp32c3 -p COM4 -b 460800 \
  --before=default_reset --after=hard_reset write_flash \
  --flash_mode dio --flash_freq 80m --flash_size 2MB \
  0x0     bootloader/bootloader.bin \
  0x10000 keypress.bin \
  0x8000  partition_table/partition-table.bin

Key build options (from sdkconfig.defaults): NimBLE enabled with the HID service, the large single-app partition table, the mbedTLS CA certificate bundle for the wss:// broker, and the console routed to USB-Serial-JTAG.

Troubleshooting

SymptomCause & fix
Could not open COMx, the port is busy or doesn't exist A serial monitor or another program is holding the port — close it before flashing. If the port truly isn't there, replug the USB cable and confirm the COM number.
Wrong mode after power-on Count the boot blinks (1/2/3). If they don't match the switch, re-check the mode-select wiring on GPIO3/GPIO10. Unwired = falls back to Mode 2.
Wi-Fi connects then stalls Router likely runs WPA2/WPA3 "transition" mode; the firmware advertises PMF-capable to handle this. Confirm SSID/password in USER CONFIG.
MQTT "not connected, dropping" in logs Broker/TLS not connected yet — presses are dropped until the connection is up. Check broker URI, credentials, and that the client ID is device1.
Habit tracker resets at the wrong time Set HABIT_TZ to your local POSIX timezone string and reflash.
BLE keys do nothing Host not paired/connected — presses are dropped when disconnected. Re-pair "TRKeys" from the host Bluetooth menu.