Version: 2.40
Custom firmware for the StylesRallyIndustries Bluetooth Navigation / Digital Roadbook Controller.
Author: [email protected]
- NimBLE-Arduino 2.5.0
- HijelHID_BLEKeyboard 0.5.0
- Keypad 3.1.1
- Uses the Keypad library with an event handler for simpler button state handling.
- Supports two controllers in daisy chain, connected to each other or to the controller box.
- Keypad scanning allows daisy chaining using UTP cables (5/6 wires for buttons + 2 wires for LED).
- Up to 8 different profiles:
- Short press of buttons 1β8 β select profiles 1β8
- Long press of buttons 1β4 β select profiles 5β8
- Button mapping tables (normal + media) are split into two halves:
- First half β short press mappings
- Second half β long press mappings
- Long press uses dedicated mapping if defined; otherwise short-press behavior repeats.
- Profile change sequence: press 1-2-3-4 then the button number of desired profile.
- Press sequence 4-3-2-1-4-3-2-1 to execute BLE factory reset. It plays a visual SOS LED signal for user confirmation.
- Selected profile is saved in NVS, surviving controller reboot.
- Profiles can send normal keys (letters, numbers, arrows, etc.) or media keys (volume/playback).
Lookup order: normal β media. - LED support (external or internal):
- Blinking when not connected to Bluetooth
- Optional keepalive blinking (default 10h)
- Blink on each button press
- Profile change feedback: LED blinks number of selected profile after 1-2-3-4 sequence
- Set
DEBUG = 0for production use.
profiles_normal[NUM_PROFILES*2][NUM_KEYS]
profiles_media [NUM_PROFILES*2][NUM_KEYS]
βββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββ
β Short press mappings β Long press mappings β
β (rows 0β7 = profiles) β (rows 8β15 = profiles) β
βββββββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββ
Example (Profile 1):
profiles_normal[0][keyX]β short press normal keyprofiles_media[0][keyX]β short press media keyprofiles_normal[8][keyX]β long press normal keyprofiles_media[8][keyX]β long press media key
Lookup logic:
- On short press β check
normal[profile], fallback tomedia[profile] - On long press β check
normal[profile+NUM_PROFILES], if empty then checkmedia[profile+NUM_PROFILES]
The firmware supports 8 independent profiles, each with:
- 8 button slots (your controller may only use 4 buttons, but the code is scalable).
- Two actions per button:
- Short press
- Long press
Each button can be mapped to:
-
Normal keys (keyboard keys)
β letters, numbers, arrows, function keys ('a','1',KEY_RETURN,KEY_UP_ARROW, β¦).
β defined inprofiles_normal. -
Media keys (consumer/media controls)
β volume, play/pause, next/previous track (KEY_MEDIA_PLAY_PAUSE,KEY_MEDIA_VOLUME_UP, β¦).
β defined inprofiles_media. -
Instant keys (timing control)
β determines when a key event is sent:- Instant (1) β key is sent immediately on button down.
- Non-instant (0) β key is sent on button release.
- Direct (2) β bleKeyboard.press.
β defined in
instant_keys.
-
profiles_normal:- First 8 rows β short press mappings (profiles 1β8).
- Next 8 rows β long press mappings (profiles 9β16).
-
profiles_media:- Same structure as above (short first, then long).
Example:
// Profile 4, short press
{ KEY_F6, KEY_F7, KEY_RETURN, KEY_F5, KEY_UP_ARROW, KEY_LEFT_ARROW, KEY_RIGHT_ARROW, KEY_DOWN_ARROW },
// Profile 4, long press
{ 0, 0, 0, 0, 0, 0, 0, 0 },- Open
keymapings.h. - Find the profile you want to edit.
- Update
profiles_normal(for standard keyboard keys) orprofiles_media(for media keys).- Use
0in one table if you want the key to come from the other table.
- Use
- Adjust
instant_keysif you want to enable long press behavior.- Keys set to
'0'are delayed until button release (non-instant). - Keys set to
'1'are sent immediately (instant).
- Keys set to
Letβs configure Profile 4 like this:
- Button 1 β short press = F6, long press = F6
- Button 2 β short press = F7, long press = F7
- Button 3 β short press = ENTER, long press = ENTER
- Button 4 β short press = F5, long press = F5
- Buttons 5-8 (Controller 2) Up/Left/Right/Down arrows
Code changes:
// profiles_normal (Profile 4 short press)
{ KEY_F6, KEY_F7, KEY_RETURN, KEY_F5, KEY_UP_ARROW, KEY_LEFT_ARROW, KEY_RIGHT_ARROW, KEY_DOWN_ARROW },
// profiles_normal (Profile 4 long press)
{ 0, 0, 0, 0, 0, 0, 0, 0 },
// profiles_media (Profile 4 short press)
{ 0, 0, 0, 0, 0, 0, 0, 0 },
// profiles_media (Profile 4 long press)
{ 0, 0, 0, 0, 0, 0, 0, 0 },
// instant_keys (Profile 4)
{'1', '1', '1', '1', '1', '1', '1', '1'},
// BTDeviceInfo (Profile 4)
{ "BarButtons", "S.R.I. Omadon", 55 }, // Profil 4Letβs configure Profile 3 like this:
- Button 1 β short press = Next, long press = Play/Pause
- Button 2 β short press = Previous, long press = Stop
- Button 3 β short press = VolumeUP, long press = repeat VolumeUP
- Button 4 β short press = VolumeDOWN, long press = repeat VolumeDOWN
- Buttons 5-8 (Controller 2) Up/Left/Right/Down arrows
Code changes:
// profiles_normal (Profile 3 short press) zero in normal mapping says look at the media mapping
{ 0, 0, 0, 0, KEY_UP_ARROW, KEY_LEFT_ARROW, KEY_RIGHT_ARROW, KEY_DOWN_ARROW },
// profiles_media (Profile 3 short press)
{ KEY_MEDIA_NEXT_TRACK, KEY_MEDIA_PREVIOUS_TRACK, KEY_MEDIA_VOLUME_UP, KEY_MEDIA_VOLUME_DOWN, 0, 0, 0, 0 },
// profiles_media (Profile 3 long press)
{ KEY_MEDIA_PLAY_PAUSE, KEY_MEDIA_STOP, 0, 0, 0, 0, 0, 0 },
// instant_keys (Profile 3) buttons 1 and 2 are removed from instant keys
{'0', '0', '1', '1', '1', '1', '1', '1'},Explanation:
profiles_normalis filled with zeros for Button 1 & 2 β they are resolved viaprofiles_media.instant_keysmarks Button 1 & 2 as non-instant (so long press detection works).
| Profile | Short Press (Normal) | Short Press (Media) | Long Press (Normal) | Long Press (Media) | Instant Keys |
|---|---|---|---|---|---|
| 1 | =, -, r, c, β, β, β, β |
β | β | β | All instant |
| 2 | β |
Prev, Next, Vol-, Vol+ |
β | β | All instant |
| 3 | β |
Next, Prev, Vol+, Vol- |
β | Play/Pause, Stop |
Btn 1&2 non-instant |
| 4 | F6, F7, Enter, F5, β, β, β, β |
β | β | β | All instant |
| 5 | F1βF8 |
β | β | β | All instant |
| 6 | =, -, N, C, β, β, β, β |
β | D (Btn3) |
β | Btn 3 non-instant |
| 7 | F1βF8 |
Prev, Next, Play/Pause |
F9βF12 |
β | All non-instant |
| 8 | β, β, β, β, F6, F7, Enter, F5 |
β | β | β | All instant |
RCntrl V2 supports dual-stage Over-The-Air firmware updates.
The controller first attempts to download firmware from GitHub Releases.
If GitHub OTA fails, it automatically falls back to a local HTTP server.
Firmware binaries use the following naming format:
<BOARD_NAME>-<PROFILE>.bin
Examples:
ESP32-C3-12F-1.bin
ESP32-C3-12F-4.bin
LOLIN-C3-MINI-2.bin
RCntrl V2 supports profile-specific firmware images.
This allows hosting different firmware builds with predefined key mappings, so users can install ready-made layouts without compiling firmware themselves.
Example use cases:
| Profile | Example Purpose |
|---|---|
| 1 | Default universal firmware |
| 2 | Media control |
| 3 | Rally navigation |
| 4 | Roadbook tablet control |
| 5-8 | Custom user layouts |
Profile 1 is the mandatory fallback firmware.
This binary should always be available.
Example:
ESP32-C3-12F-1.bin
After boot, OTA always defaults to profile 1.
This ensures a valid firmware image is always available, even if profile-specific firmware files are not hosted.
Firmware for profiles 2-8 is optional.
To request a profile-specific firmware image, you must explicitly select that profile before starting OTA.
This applies even if that profile is already active.
If profile 4 is currently active and you want OTA to download:
ESP32-C3-12F-4.bin
You must reselect profile 4 before starting OTA.
This intentionally prevents OTA failures caused by missing profile-specific firmware files.
This OTA design guarantees:
- Profile 1 always works as universal recovery firmware
- Optional profile firmware can be hosted only when needed
- Users explicitly choose non-default firmware variants
- OTA remains reliable even with partial firmware availability
Firmware is downloaded from the latest GitHub release.
Example:
https://github.com/<repo>/releases/latest/download/ESP32-C3-12F-1.bin
If GitHub OTA fails, RCntrl V2 attempts local OTA using the current gateway IP on port 8080.
Example:
http://192.168.43.1:8080/ESP32-C3-12F-1.bin
Configure hotspot SSID and password to match firmware settings.
Recommended Android app:
Simple HTTP Server
Configure it to serve files on port:
8080
Place the correct firmware file in the HTTP server root directory.
Example:
ESP32-C3-12F-1.bin
The controller will:
- Attempt GitHub OTA
- Automatically fall back to local OTA if GitHub fails
Use profile 1 OTA firmware.
Host additional profile-specific firmware builds for custom key mappings.
If a profile-specific firmware file does not exist, simply reselect profile 1 and retry OTA.
This guarantees recovery using the default firmware image.
- Edit
keymapings.h. - Recompile the firmware.
- Flash the firmware to ESP32.
- Switch to the desired profile and test.
This work is licensed under the Creative Commons Attribution-NonCommercial 4.0 International License.
To view a copy of this license, visit CC BY-NC 4.0
or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.