Latest release: v2.2.1 Release Notes | All releases
Fix: Cold-boot peripheral connection failures on multi-peripheral split keyboards. Status advertisement no longer steals radio time from ZMK split scan/connect during peripheral discovery (see issue #20).
Multi-peripheral builds (e.g. central + right half + trackball): set CONFIG_PROSPECTOR_EXPECTED_PERIPHERAL_COUNT=2 (or higher) in your keyboard config so the burst/silent cycle stays engaged until all peripherals are up. Default is 1 (typical 2-half split). Standalone keyboards are unaffected.
New Features:
- 3 new display layouts: Operator, Radii, Field (inspired by carrefinho)
- NVS settings persistence (channel, brightness, layout survive reboot)
- Version protocol: keyboard firmware version displayed on scanner Quick Actions screen
- Boot burst for immediate scanner detection on keyboard startup
- High-priority change detection: layer/modifier/profile changes update instantly, WPM/battery at 1Hz
CONFIG_PROSPECTOR_DEFAULT_LAYOUTfor non-touch startup layout selection- 3-battery bar display (Operator layout auto-switches from arc when 3+ peripherals)
Stability & BLE:
- Scanner freeze fix: mutex + work handler architecture with lock-free BT RX ring buffer
- 3-mode Hybrid ADV: Piggyback / Non-connectable / Connectable Proxy
- Burst on layer, modifier, and profile changes (5×15ms) for reliable delivery
- Activity-based intervals: 1Hz when typing, configurable idle interval
Compatibility:
- Keyboard firmware: ZMK main (Zephyr 4.x) and ZMK 0.3 (Zephyr 3.5) both supported
- Scanner firmware: ZMK main required
- GitHub Actions: updated for ZMK board variant (
xiao_ble/nrf52840/zmk)
Keyboard-side west.yml:
- name: prospector-zmk-module
remote: prospector
revision: v2.2.1
path: modules/prospector-zmk-module- What is Prospector Scanner?
- Key Features
- Touch Mode vs Non-Touch Mode
- Quick Start
- Hardware & Wiring
- Configuration Guide
- Architecture Overview
- Protocol Specification
- Display Features
- Keyboard Integration
- Documentation
Prospector Scanner is an independent BLE status display device for ZMK keyboards. It monitors your keyboard's status (battery, layer, modifiers, WPM, etc.) in real-time without consuming a BLE connection slot.
Prospector is a community hardware platform originally created by carrefinho as a universal ZMK keyboard dongle (keyboard → dongle → PC connection). This project takes the same hardware platform but uses it in a completely different way:
Original Prospector (Dongle Mode):
- Keyboard connects to Prospector via BLE
- Prospector connects to PC via USB or BLE
⚠️ Limitation: Keyboard can only connect to dongle (loses multi-device capability)⚠️ Limitation: Requires keyboard-specific dongle shield configuration⚠️ Limitation: PC can't connect to keyboard directly
This Project (Scanner Mode):
- Keyboard broadcasts status via BLE Advertisement (observer mode)
- Prospector only listens - does NOT connect to keyboard
- ✅ Advantage: Keyboard maintains full 5-device connectivity
- ✅ Advantage: Works with ANY ZMK keyboard (no shield needed)
- ✅ Advantage: Keyboard can connect directly to PC/tablet/phone
Key Benefit: Your keyboard stays fully functional with all devices while Scanner provides visual monitoring.
Both modes use the same hardware:
- MCU: Seeeduino XIAO BLE (nRF52840)
- Display: Waveshare 1.69" Round LCD with touch panel (ST7789V + CST816S)
- 3D Case: Open-source design from original Prospector
Important: Even "non-touch mode" uses the touch-enabled LCD - we simply don't wire the 4 touch pins. Original Prospector uses the same display but doesn't utilize touch functionality.
Ready-made Prospector kits are available at beekeeb.
Kit specifications:
- Touch panel: ✅ Supported (CST816S wired)
- Ambient light sensor: ❌ Not included
- Battery: ❌ Not included (USB powered only)
See Touch Mode Guide for touch configuration.
- ✅ See Everything: Battery, layer, modifiers, WPM, signal strength
- ✅ Multi-Keyboard: Monitor multiple keyboards simultaneously
- ✅ Zero Connection Cost: Uses BLE advertisements (observer mode)
- ✅ Professional UI: YADS-style widget layout with NerdFont icons
- ✅ Split Keyboard Support: Shows both left/right side information
- Multi-Widget Display: Connection status, layer indicators, modifier keys, WPM tracking, battery visualization, and signal strength
- Split Keyboard Support: Unified display showing both left and right side information with intelligent layout
- Color-Coded Status: 5-level battery indicators (Green/Light Green/Yellow/Orange/Red), unique pastel layer colors
- Real-time Updates: Instant response to keyboard state changes with sub-second latency
- WPM Tracking: Real-time Words Per Minute calculation with intelligent decay during idle periods
- NerdFont Icons: Professional modifier key indicators ( Ctrl, Shift, Alt, GUI)
- Activity-Based Intervals: 1Hz (1000ms) when typing, low frequency when idle for maximum battery efficiency
- Automatic Transitions: Seamless switching between active/idle states with configurable timeouts
- WPM-Aware Updates: Higher frequency during active typing sessions with decay algorithm
- Scanner Battery Support: Real-time battery monitoring for scanner device with charging indicator
- Timeout Dimming: Automatic brightness reduction to 5% after configurable inactivity (v2.0)
- USB/Battery Profiles: Different brightness and power settings for USB vs battery operation
- Any ZMK Keyboard: Works with split, unibody, or any ZMK-compatible device
- Non-Intrusive: Keyboards maintain full 5-device connectivity
- Multi-Keyboard: Monitor multiple keyboards simultaneously with channel isolation
- No Pairing Required: Uses BLE advertisements (observer mode) - no connection slot consumed
- Optional CST816S Touch: Enable with
CONFIG_PROSPECTOR_TOUCH_ENABLED=y - 4-Direction Swipe Gestures: Navigate settings screens intuitively
- On-Device Settings: Adjust max layers, channel filter, and more via touch
- Thread-Safe LVGL: Freeze prevention with mutex + work handler architecture
👉 Touch Mode Guide → for complete setup and screen descriptions
- APDS9960 Integration: Ambient light sensor with 4-pin mode (no interrupt required)
- Smooth Transitions: Configurable fade duration and steps (default: 800ms, 12 steps)
- Non-linear Response: Intelligent mapping for comfortable viewing in all lighting conditions
- Auto/Manual Toggle: Switch between sensor control and manual adjustment (touch mode)
- Activity-Based Dimming: Automatic reduction to 5% when keyboards go idle (v2.0)
v2.0 supports two build configurations: Touch Mode and Non-Touch Mode.
| Feature | Non-Touch Mode | Touch Mode |
|---|---|---|
| Display | Waveshare 1.69" Touch LCD | Same display |
| Wiring | 6 display pins + power (touch pins not connected) | +4 touch pins (TP_SDA/SCL/INT/RST) |
| Settings | Kconfig only (rebuild to change) | Interactive on-device adjustment |
| Gestures | Not supported | 4-direction swipe gestures |
| Firmware Size | ~900KB | ~920KB (+20KB) |
| Configuration | Edit prospector_scanner.conf |
Edit prospector_scanner.conf + enable touch |
Note: Both modes use the same Waveshare 1.69" Touch LCD hardware. Non-touch mode simply leaves the 4 touch pins unconnected (same as original Prospector).
- ✅ You want simpler wiring (6 display pins only, no touch pins)
- ✅ You don't need on-device settings adjustment
- ✅ You want maximum firmware simplicity
- ✅ You're following original Prospector hardware setup
- ✅ You want to wire the 4 touch pins for interactive control
- ✅ You want to adjust settings without rebuilding firmware
- ✅ You want swipe gestures for future features
- ✅ You're comfortable with +4 pin wiring
Touch mode requires:
- Same Waveshare 1.69" Round LCD (with CST816S touch controller)
- 4 additional connections: TP_SDA, TP_SCL, TP_INT, TP_RST
- Configuration change: Set
CONFIG_PROSPECTOR_TOUCH_ENABLED=yinprospector_scanner.conf
This guide focuses on Non-Touch Mode (standard Prospector wiring). For touch-specific setup, see the touch mode guide.
- Hardware: Seeeduino XIAO BLE + Waveshare 1.69" Round LCD
- Keyboard: ZMK keyboard with status advertisement enabled (see Keyboard Integration)
-
Fork this repository
- Go to https://github.com/t-ogura/zmk-config-prospector
- Click "Fork" (top-right)
-
Enable GitHub Actions
- In your fork, go to "Actions" tab
- Click "I understand my workflows, enable them"
-
Trigger Build
- Go to "Actions" tab
- Select "Build" workflow
- Click "Run workflow" → "Run workflow"
- Wait ~5-10 minutes
-
Download Firmware
- Click completed workflow run
- Scroll to "Artifacts" section
- Download:
prospector_scanner- Non-touch mode (this guide)prospector_scanner_touch- Touch mode (see Touch Mode Guide)
- Extract and flash the
.uf2file
# Clone and setup
git clone https://github.com/YOUR_USERNAME/zmk-config-prospector.git
cd zmk-config-prospector
python3 -m venv .venv
source .venv/bin/activate
pip install west
# Initialize workspace
west init -l config
west update
# Build (non-touch mode)
west build -b xiao_ble/nrf52840 -s zmk/app -- \
-DSHIELD=prospector_scanner \
-DZMK_CONFIG="$(pwd)/config"
# Build (touch mode) - see Touch Mode Guide for details
west build -b xiao_ble/nrf52840 -s zmk/app -- \
-DSHIELD=prospector_scanner \
-DZMK_CONFIG="$(pwd)/config" \
-DEXTRA_CONF_FILE="$(pwd)/config/prospector_scanner_touch.conf"
# Output: build/zephyr/zmk.uf2Purchased from beekeeb? Skip this step - your kit is pre-wired!
See Hardware & Wiring for detailed pinout.
Minimum connections (6 display pins + power):
LCD_DIN → Pin 10 (MOSI)
LCD_CLK → Pin 8 (SCK)
LCD_CS → Pin 9 (CS)
LCD_DC → Pin 7 (Data/Command)
LCD_RST → Pin 3 (Reset)
LCD_BL → Pin 6 (Backlight PWM)
VCC → 3.3V
GND → GND
Optional: APDS9960 sensor (4-pin, no interrupt needed)
SDA → D4 (P0.04)
SCL → D5 (P0.05)
VCC → 3.3V
GND → GND
-
Enter bootloader:
- Connect XIAO BLE via USB
- Press RESET button twice quickly (within 0.5 seconds)
XIAO-SENSEdrive appears
-
Flash firmware:
- Drag
.uf2file toXIAO-SENSEdrive - Drive disconnects automatically
- Scanner reboots
- Drag
manifest:
remotes:
- name: zmkfirmware
url-base: https://github.com/zmkfirmware
- name: prospector
url-base: https://github.com/t-ogura
projects:
- name: zmk
remote: zmkfirmware
revision: main
import: app/west.yml
# Add this:
- name: prospector-zmk-module
remote: prospector
revision: v2.2.1
path: modules/prospector-zmk-module# Enable status advertisement
CONFIG_ZMK_STATUS_ADVERTISEMENT=y
CONFIG_ZMK_STATUS_ADV_KEYBOARD_NAME="MyKeyboard"
# Split keyboard only: enable peripheral battery fetching
CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING=ywest update
west build -b your_board -- -DSHIELD=your_shield
# Copy .uf2 to bootloader drive- Power on scanner (should show "Waiting for keyboards...")
- Power on keyboard
- Scanner should detect keyboard within a few seconds
- Check display shows: device name, layer, battery, etc.
Success! Your scanner is working.
| Component | Specification | Link |
|---|---|---|
| MCU | Seeeduino XIAO BLE (nRF52840) | Seeed Studio |
| Display | Waveshare 1.69" Round LCD (ST7789V) | Waveshare |
| Component | Purpose | Link |
|---|---|---|
| APDS9960 | Ambient light sensor (auto-brightness) | Adafruit |
| LiPo Battery | Portable operation (400-600mAh) | Generic 3.7V LiPo |
| Display Pin | XIAO BLE Pin | nRF52840 GPIO | Function |
|---|---|---|---|
| LCD_DIN | Pin 10 | P1.15 | SPI MOSI (data out) |
| LCD_CLK | Pin 8 | P1.13 | SPI clock |
| LCD_CS | Pin 9 | P1.14 | Chip select |
| LCD_DC | Pin 7 | P1.12 | Data/Command select |
| LCD_RST | Pin 3 | P0.03 | Display reset |
| LCD_BL | Pin 6 | P1.11 | Backlight PWM |
| VCC | 3.3V | - | Power (3.3V) |
| GND | GND | - | Ground |
| Sensor Pin | XIAO BLE Pin | nRF52840 GPIO | Function |
|---|---|---|---|
| VCC | 3.3V | - | Power (3.3V) |
| GND | GND | - | Ground |
| SDA | D4 | P0.04 | I2C data |
| SCL | D5 | P0.05 | I2C clock |
Note: v2.0 supports 4-pin connection - no interrupt pin needed (polling mode).
| Battery Wire | XIAO BLE Pad | Description |
|---|---|---|
| + (Red) | BAT+ | Positive terminal |
| - (Black) | BAT- | Ground |
Waveshare 1.69" LCD Seeeduino XIAO BLE
┌─────────────────┐ ┌──────────────┐
│ │ │ │
│ Display Pins │ │ 3.3V ───────┼─── VCC
│ ├─ LCD_DIN ────┼─────────────┤ Pin 10 │
│ ├─ LCD_CLK ────┼─────────────┤ Pin 8 │
│ ├─ LCD_CS ─────┼─────────────┤ Pin 9 │
│ ├─ LCD_DC ─────┼─────────────┤ Pin 7 │
│ ├─ LCD_RST ────┼─────────────┤ Pin 3 │
│ ├─ LCD_BL ─────┼─────────────┤ Pin 6 │
│ ├─ VCC ────────┼─────────────┤ 3.3V │
│ └─ GND ────────┼─────────────┤ GND │
│ │ │ │
└─────────────────┘ └──────────────┘
Optional: APDS9960 Sensor
┌─────────────┐
│ APDS9960 │
│ VCC ───────┼─────────────────┤ 3.3V │
│ GND ───────┼─────────────────┤ GND │
│ SDA ───────┼─────────────────┤ D4 │
│ SCL ───────┼─────────────────┤ D5 │
└─────────────┘ └──────────────┘
Optional: LiPo Battery
┌─ BAT+ (Red wire)
Battery ┤
└─ BAT- (Black wire)
- Test Display First: Wire display pins and verify basic operation before adding sensor
- Keep Wires Short: I2C works best with wires < 10cm
- Built-in Pull-ups: XIAO BLE has I2C pull-ups on D4/D5 - no external resistors needed
- Polarity Check: Double-check VCC/GND before powering on
- Clean Solder: Poor solder joints cause intermittent display issues
Configuration file: config/prospector_scanner.conf
# Enable scanner mode
CONFIG_PROSPECTOR_MODE_SCANNER=y
# Multi-keyboard support (enabled by default)
CONFIG_PROSPECTOR_MULTI_KEYBOARD=y# Display subsystem
CONFIG_ZMK_DISPLAY=y
CONFIG_DISPLAY=y
CONFIG_LVGL=y
# Required LVGL widgets
CONFIG_LV_USE_BTN=y
CONFIG_LV_USE_SLIDER=y
CONFIG_LV_USE_SWITCH=y
# Fonts (already configured in default prospector_scanner.conf)
# CONFIG_LV_FONT_MONTSERRAT_12=y
# CONFIG_LV_FONT_MONTSERRAT_16=y
# ... etcNote: Font settings are pre-configured in prospector_scanner.conf. No manual font configuration needed.
# Number of layer indicators shown on screen
CONFIG_PROSPECTOR_MAX_LAYERS=7 # Range: 4-10
# Visual effect:
# - 4 layers: Wide spacing, large indicators
# - 7 layers: Medium spacing (default)
# - 10 layers: Tight spacing, maximum capacityMatch your keyboard: Set this to match your keyboard's layer count for best appearance. If your keyboard has 5 layers, set to 5 for optimal spacing.
# Enable dial-style animated layer display
CONFIG_PROSPECTOR_LAYER_SLIDE_DEFAULT=yWhat it does: Active layer slides to center position with smooth animation. Layer changes animate based on direction (increase = slide from right, decrease = slide from left).
Over-max behavior: When layer exceeds MAX_LAYERS, displays a single large number instead of the layer list.
Touch mode: Can be toggled on-device. See Touch Mode Guide for details.
Channels allow filtering specific keyboards in multi-keyboard environments.
# Channel broadcasting (add to your keyboard config)
CONFIG_PROSPECTOR_CHANNEL=0 # 0 = broadcast to all scanners (default)
# 1-9 = specific channel (recommended)
# 10-255 = also supported but not selectable in touch UITouch mode: Channel can be changed dynamically on-device. See Touch Mode Guide.
Non-touch mode: Set channel via Kconfig:
# Channel filter for scanner (non-touch mode)
CONFIG_PROSPECTOR_SCANNER_CHANNEL=0 # 0 = receive all (default)
# 1-9 = specific channel (recommended)Recommended range: Use channels 0-9 for compatibility with touch mode UI.
Use case examples:
- Home/Office separation: Home keyboards on channel 1, office on channel 2
- Multi-user: Each user's keyboards on different channels
- Testing: Isolate test keyboards from production display
Compatibility: Keyboard side works with v2.0.0+. Scanner side channel filter requires v2.1.0+ for touch mode dynamic switching.
For split keyboards with multiple peripherals (e.g., keyboard half + trackball), you can remap which peripheral appears in which display slot.
# Add to your keyboard's .conf file
CONFIG_ZMK_STATUS_ADV_HALF_PERIPHERAL=0 # Keyboard half slot (default: 0)
CONFIG_ZMK_STATUS_ADV_AUX1_PERIPHERAL=1 # Aux1 slot, e.g., trackball (default: 1)
CONFIG_ZMK_STATUS_ADV_AUX2_PERIPHERAL=2 # Aux2 slot, e.g., numpad (default: 2)When to use: After settings_reset, peripherals may reconnect in different order than expected. Use these settings to fix the display order without re-pairing.
Example: If your trackball (should be Aux1) connected before keyboard half:
CONFIG_ZMK_STATUS_ADV_HALF_PERIPHERAL=1 # Keyboard half is now index 1
CONFIG_ZMK_STATUS_ADV_AUX1_PERIPHERAL=0 # Trackball is now index 0# Disable ambient light sensor
CONFIG_PROSPECTOR_USE_AMBIENT_LIGHT_SENSOR=n
# Set fixed brightness (0-100%)
CONFIG_PROSPECTOR_FIXED_BRIGHTNESS=85Best for: Users without APDS9960 sensor, or who prefer manual control.
# Enable ambient light sensor (requires APDS9960 hardware)
CONFIG_PROSPECTOR_USE_AMBIENT_LIGHT_SENSOR=y
# Brightness range
CONFIG_PROSPECTOR_ALS_MIN_BRIGHTNESS=20 # Minimum (dark rooms)
CONFIG_PROSPECTOR_ALS_MAX_BRIGHTNESS_USB=100 # Maximum (USB power)
# Smooth fade transitions
CONFIG_PROSPECTOR_BRIGHTNESS_FADE_DURATION_MS=800 # 800ms fade
CONFIG_PROSPECTOR_BRIGHTNESS_FADE_STEPS=12 # 12 stepsWhat happens: Scanner reads ambient light every few seconds and smoothly adjusts brightness (20-100%) with 800ms fade. No jarring changes.
Hardware requirement: APDS9960 sensor wired to D4/D5 (4-pin connection, no interrupt pin needed).
# Auto-dim when no keyboard activity
CONFIG_PROSPECTOR_SCANNER_TIMEOUT_MS=480000 # 8 minutes (0=disabled)
CONFIG_PROSPECTOR_SCANNER_TIMEOUT_BRIGHTNESS=5 # Dim to 5%What it does:
- If no keyboard data received for 8 minutes → dim to 5%
- When keyboard sends data again → restore previous brightness
- Works with both USB and battery power
Use case: Battery-powered scanner - extends battery life when keyboards are turned off.
Disable: Set CONFIG_PROSPECTOR_SCANNER_TIMEOUT_MS=0 to disable timeout.
# Enable scanner's own battery monitoring
CONFIG_PROSPECTOR_BATTERY_SUPPORT=y # Requires LiPo connected to XIAO BLE
CONFIG_ZMK_BATTERY_REPORTING=y # ZMK battery subsystemWhat you see: Battery icon (🔋) in top-right corner with percentage and charging indicator.
Hardware requirement: LiPo battery connected to XIAO BLE's BAT+/BAT- pads.
No battery? Set CONFIG_PROSPECTOR_BATTERY_SUPPORT=n - no battery widget shown.
# Enable USB serial logging
CONFIG_LOG=y
CONFIG_ZMK_LOG_LEVEL_DBG=y
# Reduce BT noise
CONFIG_BT_LOG_LEVEL_WRN=y
CONFIG_LOG_DEFAULT_LEVEL=4How to use:
- Enable these settings
- Rebuild firmware
- Connect via USB
- Open serial monitor (e.g.,
screen /dev/ttyACM0 115200) - See debug logs for troubleshooting
Production: Set CONFIG_LOG=n to disable logging and reduce firmware size.
# ===== SCANNER MODE =====
CONFIG_PROSPECTOR_MODE_SCANNER=y
CONFIG_PROSPECTOR_MULTI_KEYBOARD=y
# ===== DISPLAY =====
CONFIG_ZMK_DISPLAY=y
CONFIG_DISPLAY=y
CONFIG_LVGL=y
CONFIG_PROSPECTOR_MAX_LAYERS=7
# ===== LAYER DISPLAY (v2.1) =====
# CONFIG_PROSPECTOR_LAYER_SLIDE_DEFAULT=y # Enable slide animation mode
# ===== CHANNEL FILTER (non-touch mode only) =====
# CONFIG_PROSPECTOR_SCANNER_CHANNEL=0 # 0=all, 1-255=specific channel
# ===== BRIGHTNESS =====
# Option 1: Fixed brightness (simple)
CONFIG_PROSPECTOR_USE_AMBIENT_LIGHT_SENSOR=n
CONFIG_PROSPECTOR_FIXED_BRIGHTNESS=85
# Option 2: Auto-brightness (requires APDS9960)
# CONFIG_PROSPECTOR_USE_AMBIENT_LIGHT_SENSOR=y
# CONFIG_PROSPECTOR_ALS_MIN_BRIGHTNESS=20
# CONFIG_PROSPECTOR_ALS_MAX_BRIGHTNESS_USB=100
# CONFIG_PROSPECTOR_BRIGHTNESS_FADE_DURATION_MS=800
# ===== TIMEOUT =====
CONFIG_PROSPECTOR_SCANNER_TIMEOUT_MS=480000 # 8 min (0=disabled)
CONFIG_PROSPECTOR_SCANNER_TIMEOUT_BRIGHTNESS=5
# ===== BATTERY =====
CONFIG_PROSPECTOR_BATTERY_SUPPORT=n # Enable if LiPo connected
# ===== LOGGING (optional) =====
# CONFIG_LOG=y
# CONFIG_ZMK_LOG_LEVEL_DBG=yCopy this template and customize for your needs.
For keyboard-side v2.1 features (channel, peripheral mapping), see Keyboard Integration section.
Scanner Mode Design (Independent Monitoring):
- Keyboard → Multiple Devices (up to 5 via normal BLE connections)
- Keyboard → Scanner (BLE Advertisement broadcast only - no connection)
- Scanner operates independently without consuming connection slots
┌─────────────┐ BLE Adv ┌──────────────┐
│ Keyboard │ ──────────────→│ Scanner │
│ │ 26-byte │ Display │
│ (1Hz/idle) │ Protocol │ (USB/Battery)│
└─────────────┘ └──────────────┘
│
├── Device 1 (PC)
├── Device 2 (Tablet)
├── Device 3 (Phone)
├── Device 4 (...)
└── Device 5 (...)
Key Points:
- Keyboard broadcasts status via BLE Advertisement (observer mode)
- Scanner only listens - does NOT connect to keyboard
- Keyboard maintains full 5-device connectivity
- Scanner can be powered via USB or battery (optional)
Keyboard Scanner
──────── ────────
[Keypress detected]
│
├─→ Update internal state
│ (layer, modifiers, WPM)
│
├─→ Package into 26-byte
│ advertisement payload
│
└─→ Broadcast BLE Adv ─────→ [BLE Observer Mode]
(1Hz when typing, │
30s when idle, ├─→ Parse advertisement
burst on changes)
│ (battery, layer, etc.)
│
├─→ Update LVGL widgets
│ (battery bars, layer
│ indicators, WPM, etc.)
│
└─→ Display to screen
(YADS-style UI)
The keyboard broadcasts its status using a custom BLE Advertisement payload. Scanner receives this in observer mode (no connection needed).
| Offset | Field | Size | Description | Example |
|---|---|---|---|---|
| 0-1 | Manufacturer ID | 2 bytes | 0xFF 0xFF (Custom/Local use) |
FF FF |
| 2-3 | Service UUID | 2 bytes | 0xAB 0xCD (Prospector Protocol ID) |
AB CD |
| 4 | Version | 1 byte | [7:4]=module major, [3:0]=module minor |
22 (v2.2) |
| 5 | Battery Level | 1 byte | Main battery 0-100% (Central for split) | 5A (90%) |
| 6 | Active Layer | 1 byte | Current layer 0-15 | 02 (Layer 2) |
| 7 | Profile Slot | 1 byte | [6]=dev flag, [5:3]=patch, [2:0]=profile 0-4 |
01 (Profile 1, v*.*.0 release) |
| 8 | Connection Count | 1 byte | Number of connected BLE devices 0-5 | 03 (3 devices) |
| 9 | Status Flags | 1 byte | Caps/Charging/USB/BLE status bits | 18 (USB+BLE) |
| 10 | Device Role | 1 byte | 0=Standalone, 1=Central, 2=Peripheral |
01 (Central) |
| 11 | Device Index | 1 byte | Split keyboard index (0=left, 1=right) | 00 |
| 12-14 | Peripheral Batteries | 3 bytes | Left/Right/Aux battery levels 0-100% | 52 00 00 (82%, none, none) |
| 15-18 | Layer Name | 4 bytes | ASCII layer identifier (optional) | 4C30... ("L0") |
| 19-22 | Keyboard ID | 4 bytes | Hardware-unique ID (HWINFO) | 12345678 |
| 23 | Modifier Flags | 1 byte | L/R Ctrl,Shift,Alt,GUI states | 05 (LCtrl+LAlt) |
| 24 | WPM Value | 1 byte | Words per minute 0-255 | 3C (60 WPM) |
| 25 | Channel | 1 byte | Channel number 0-255 (0=broadcast to all) | 06 (Channel 6) |
Version byte: [7:4] = module major (0-15)
[3:0] = module minor (0-15)
Profile Slot byte: [6] = dev flag (0=release, 1=dev)
[5:3] = patch version (0-7)
[2:0] = BLE profile slot (0-4)
Example: v2.2.0 release, profile 1
version = 0x22, profile_slot = 0x01
Backward compatible: v2.1.0 and earlier send version=0x01 (protocol v1).
Scanner detects major=0 and displays "< v2.2".
Bit 7 6 5 4 3 2 1 0
│ │ │ │ │ │ │ └─ Caps Word (1=active, 0=inactive)
│ │ │ │ │ │ └─── Charging (1=yes, 0=no)
│ │ │ │ │ └───── USB Connected (1=yes, 0=no)
│ │ │ │ └─────── USB HID Ready (1=yes, 0=no)
│ │ │ └───────── BLE Connected (1=yes, 0=no)
│ │ └─────────── BLE Bonded (1=yes, 0=no)
│ └───────────── Reserved (protocol version, future)
└─────────────── Reserved (protocol version, future)
Bit 7 6 5 4 3 2 1 0
│ │ │ │ │ │ │ └─ Left Ctrl
│ │ │ │ │ │ └─── Left Shift
│ │ │ │ │ └───── Left Alt
│ │ │ │ └─────── Left GUI (Win/Cmd)
│ │ │ └───────── Right Ctrl
│ │ └─────────── Right Shift
│ └───────────── Right Alt
└─────────────── Right GUI
The keyboard adjusts broadcast frequency based on activity to save battery:
-
Active Mode (typing detected): 1000ms interval (1 Hz, configurable)
- Triggered by any keypress
- Provides periodic WPM and battery updates
- Layer/modifier/profile changes trigger immediate burst (5×15ms)
-
Idle Mode (no typing): 30000ms interval (configurable)
- Activated after 5 seconds of no keypresses (configurable)
- Battery-friendly for long idle periods
- Layer changes still trigger burst for immediate response
| No. | Element | Description |
|---|---|---|
| ① | Keyboard Name | Keyboard name configured via CONFIG_ZMK_STATUS_ADV_KEYBOARD_NAME |
| ② | WPM | Words Per Minute - typing speed (5 characters = 1 word) |
| ③ | Connection Profile | BLE connection status. Colors: White = Waiting, Blue = Communicating, Green = Connected. Number (0-4): Active BLE profile |
| ④ | Layer | Current active layer index with pastel color coding |
| ⑤ | Modifier Keys | Active modifier keys displayed when held (Ctrl, Shift, Alt, Cmd/Win) |
| ⑥ | Battery Level | Battery bars with percentage. Split keyboards: L = Peripheral, R = Central |
| ⑦ | Communication Status | RX signal strength (dBm) and reception frequency (Hz) |
Note: Scanner's own battery (if LiPo connected) appears at top-right corner.
- Hardware: APDS9960 ambient light sensor (4-pin mode, no interrupt needed)
- Behavior: Smooth fade transitions (800ms, 12 steps)
- Range: 20-100% based on ambient light
- Automatic: Dims to 5% when no keyboard activity detected
- Configurable:
CONFIG_PROSPECTOR_SCANNER_TIMEOUT_MS(default: 8 minutes, 0=disabled) - Wake on Activity: Returns to normal brightness when keyboard sends data
Your ZMK keyboard needs to broadcast status via BLE Advertisement.
Edit your keyboard's config/west.yml:
manifest:
remotes:
- name: zmkfirmware
url-base: https://github.com/zmkfirmware
- name: prospector
url-base: https://github.com/t-ogura
projects:
- name: zmk
remote: zmkfirmware
revision: main
import: app/west.yml
# Add this:
- name: prospector-zmk-module
remote: prospector
revision: v2.2.1
path: modules/prospector-zmk-moduleZMK version compatibility:
v2.2.0: Keyboard-side supports both Zephyr 4.x and 3.x. Scanner requires Zephyr 4.x.v2.0.0/v2.1.0: Zephyr 3.x only
Add to your keyboard's .conf file:
# Required: Enable status advertisement
CONFIG_ZMK_STATUS_ADVERTISEMENT=y
CONFIG_ZMK_STATUS_ADV_KEYBOARD_NAME="MyKeyboard"That's it! Your keyboard will broadcast status at default intervals.
# ===== REQUIRED =====
CONFIG_ZMK_STATUS_ADVERTISEMENT=y
CONFIG_ZMK_STATUS_ADV_KEYBOARD_NAME="MyKeyboard" # Shown on scanner (max 8 chars)
# ===== POWER OPTIMIZATION (all have sensible defaults) =====
# CONFIG_ZMK_STATUS_ADV_ACTIVITY_BASED=y # Default: y (enabled)
# CONFIG_ZMK_STATUS_ADV_ACTIVE_INTERVAL_MS=1000 # Default: 200 (recommended: 1000 = 1Hz)
# CONFIG_ZMK_STATUS_ADV_IDLE_INTERVAL_MS=30000 # Default: 30000 (30s when idle)
# CONFIG_ZMK_STATUS_ADV_ACTIVITY_TIMEOUT_MS=5000 # Default: 5000 (5s before idle)
# ===== SPLIT KEYBOARD =====
CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING=y # Fetch peripheral battery
CONFIG_ZMK_STATUS_ADV_CENTRAL_SIDE="RIGHT" # Which side is central (LEFT/RIGHT)
# ===== PERIPHERAL BATTERY MAPPING (v2.1, for multi-peripheral setups) =====
# Use when connection order doesn't match physical layout (e.g., after settings_reset)
# CONFIG_ZMK_STATUS_ADV_HALF_PERIPHERAL=0 # Keyboard half slot (default: 0)
# CONFIG_ZMK_STATUS_ADV_AUX1_PERIPHERAL=1 # Aux1 slot - e.g., trackball (default: 1)
# CONFIG_ZMK_STATUS_ADV_AUX2_PERIPHERAL=2 # Aux2 slot - e.g., numpad (default: 2)
# ===== WPM TRACKING =====
CONFIG_ZMK_STATUS_ADV_WPM_WINDOW_SECONDS=30 # WPM calculation window (5-120s)
# ===== CHANNEL (v1.1.2+, optional) =====
# For multi-keyboard environments - filter by channel on scanner side
# CONFIG_PROSPECTOR_CHANNEL=0 # 0=broadcast to all (default)
# CONFIG_PROSPECTOR_CHANNEL=1 # 1-9=recommended (touch UI compatible)# In your keyboard config directory
west update
west build -b your_board -- -DSHIELD=your_shield
# Flash to keyboard
# (Copy .uf2 to bootloader drive)- Power on keyboard
- Power on scanner
- Scanner should detect keyboard within 1-2 seconds
- Device name should match
CONFIG_ZMK_STATUS_ADV_KEYBOARD_NAME
- Touch Mode Guide - Touch panel setup, screen navigation, UI element descriptions
- v2.2.0 (2026-04): New layouts, BLE ADV rebuild, version protocol, stability improvements
- v2.1.0 (2026-01): Zephyr 4.x, improved responsiveness, layer slide mode
- v2.0.0 (2025-11): Touch support, USB display fix, thread safety
- v1.1.x (2025-08): Ambient light sensor, power optimization
- v1.0.0 (2025-08): Initial release with YADS-style UI
- Issues - Bug reports and feature requests
- Actions - Automated firmware builds
- Releases - Pre-built firmware downloads
- ZMK Discord - General ZMK support
- Original Prospector (Dongle Mode) - Hardware platform by carrefinho
- Original Prospector Firmware - Dongle mode implementation
Problem: Scanner not detecting keyboard.
Solutions:
- Check keyboard has
CONFIG_ZMK_STATUS_ADVERTISEMENT=yenabled - Verify keyboard firmware rebuilt and flashed after adding module
- Check keyboard is powered on and BLE is active
- Try power cycling both devices
Problem: LVGL font not enabled.
Solution: Use the default prospector_scanner.conf which includes all required font settings. If you're using a custom config, copy the font settings from the default file.
Problem: Display not initializing.
Solutions:
- Check wiring - especially VCC/GND polarity
- Verify backlight pin (LCD_BL → Pin 6)
- Test with settings reset firmware
- Check XIAO BLE has power (LED indicator)
- Verify display module (try with non-touch firmware if you have touch display)
Problem: USB connection not detected (should show "> USB").
This is a known issue in v1.x - Fixed in v2.0.
Solution: Upgrade to v2.0 firmware (both scanner and keyboard).
Problem: Brightness doesn't change with room lighting.
Solutions:
- Check 4-pin wiring: VCC/GND/SDA (D4)/SCL (D5)
- Verify
CONFIG_PROSPECTOR_USE_AMBIENT_LIGHT_SENSOR=yenabled - Check sensor has clear view (not blocked by case)
- Try increasing
CONFIG_PROSPECTOR_ALS_MIN_BRIGHTNESS(sensor might be too dim)
Note: v2.0 does NOT require interrupt pin - 4-pin connection works.
Problem: Display stops updating, becomes unresponsive.
Solution: Upgrade to v2.2.0 or later firmware. This version uses mutex + work handler architecture with data processing separated from display rendering, preventing LVGL thread-safety issues.
Problem: No battery icon in top-right corner.
Solutions:
- Check LiPo battery connected to BAT+/BAT- pads
- Verify
CONFIG_PROSPECTOR_BATTERY_SUPPORT=yenabled - Rebuild firmware after enabling battery support
No battery? Set CONFIG_PROSPECTOR_BATTERY_SUPPORT=n - this is expected behavior.
Scanner can monitor multiple keyboards simultaneously. Display automatically shows keyboards that broadcast status. No pairing needed.
Adjust WPM responsiveness:
# Ultra-responsive (10s window, 6x multiplier)
# CONFIG_ZMK_STATUS_ADV_WPM_WINDOW_SECONDS=10
# Balanced (30s window, 2x multiplier) - DEFAULT
# CONFIG_ZMK_STATUS_ADV_WPM_WINDOW_SECONDS=30
# Stable (60s window, 1x multiplier)
# CONFIG_ZMK_STATUS_ADV_WPM_WINDOW_SECONDS=60Shorter window = more responsive but jumpier. Longer window = more stable but slower to reflect changes.
For split keyboards, specify which side is central (has BLE profiles):
# In keyboard's .conf file
CONFIG_ZMK_STATUS_ADV_CENTRAL_SIDE="LEFT" # or "RIGHT" (default)This tells scanner which side to treat as "main" for connection status display.
Enable debug overlay for development:
CONFIG_PROSPECTOR_DEBUG_WIDGET=yShows technical information overlaid on screen. Disable for production (=n).
Future development may include Periodic Advertising (v2 protocol) for improved power efficiency. See CLAUDE.md for technical design notes.
This project is licensed under the MIT License. See LICENSE file for details.
- License: MIT License
- Source: https://github.com/zmkfirmware/zmk
- License: MIT License
- Source: https://github.com/janpfischer/zmk-dongle-screen
- Attribution: UI widget designs and NerdFont modifier symbols
- License: MIT License
- Source: https://www.nerdfonts.com/
- Usage: Modifier key symbols
This project builds upon the Prospector hardware platform created by carrefinho:
- Original Project: prospector by carrefinho
- Original Firmware: prospector-zmk-module
- Hardware Design: Seeeduino XIAO BLE + Waveshare 1.69" Round LCD with touch panel
- 3D Case Design: Open-source STL files for 3D printing
- License: MIT License
Difference: Original Prospector uses the hardware as a dongle (keyboard connects to it), while this project uses it as an independent status monitor (keyboard stays independent). Both are valid uses of the same excellent hardware platform.
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Test thoroughly
- Commit (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open a Pull Request
For major changes, please open an issue first to discuss what you would like to change.
Questions? Open an issue or join ZMK Discord.
Prospector Scanner v2.2.1 - ZMK Status Display Device


