Hardware manual · rev. 2026-07

TeerakKeys 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

Overview

TeerakKeys 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 nameTeerakKeys

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 "TeerakKeys" 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 TeerakKeys. 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 and MQTT credentials, topics, and payloads are set in the firmware's USER CONFIG block. Per-key MQTT payloads can also be overridden at runtime via the serial console.

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 configuration (compile-time)

The tunable settings live in the USER CONFIG block at the top of main/main.c. Edit and reflash to change them.

#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.

CommandEffect
CFG?Dump the current config; output ends with a CFG:END line.
CFG:SET KEY<n>_BLE_MOD=0x01Set the BLE modifier byte for key n (1–4).
CFG:SET KEY<n>_BLE_KEY=0x1ESet the BLE HID keycode for key n.
CFG:SET KEY<n>_MQTT=<text>Set the MQTT payload string for key n (up to 63 chars).

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
keycfg4Per-key BLE modifiers, BLE keycodes, and MQTT payloads.
habitstHabit tracker: last-reset day (YYYYMMDD) and the "already done today" 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 "TeerakKeys" from the host Bluetooth menu.