A custom dashboard for the Beno Reevo hubless ebike. The company abandoned the project and the original phone app is dead — this project replaces the entire app with an ESP32-S3 touchscreen that talks directly to the bike over BLE. No cloud, no SIM, no account.
If you own a Reevo and a soldering iron and twenty bucks, you can have a working dashboard again.
- Live speedometer (green 7-segment), battery %, trip odometer with reset
- Manual control of headlight, badge light, kickstand lock, assist level
- Touch-PIN unlock when you lock the bike
- Brake warn — flashes headlight + badge in alternation when you brake
- PAS auto-timeout — drops pedal assist to 0 after 10 min idle so the bike never takes off when you push it through the garage
- Master code recovery if you forget your PIN
- Built-in Wi-Fi access point + web console for sending raw BLE commands, resetting your PIN, recoloring the screen, and viewing a full setup manual
The default target is the Freenove FNK0104B ESP32-S3 board with 2.8″ capacitive touchscreen.
Specs that matter:
- ESP32-S3 with 16 MB flash, 8 MB PSRAM
- 240×320 ILI9341 IPS panel
- FT6336U capacitive touch
- Native USB-C (programming + power)
- 4-pin UART header (5V, GND, TX, RX)
If you bought a different "CYD" board, see Using a different display below.
The display takes 5V via USB-C. Three ways to power it:
- Recommended (clean): Solder to a labeled 5V and GND pad on the bike's mainboard and run those to the 5V and GND pins of the display's UART header. The board is fully silk-screened, so suitable 5V and GND points are easy to find — just confirm with a multimeter that the pad reads ~5V (not the 12V/48V rail) before you connect it. This is what my (Seth's) build does — the screen comes on when the bike does and goes off when the battery is removed.
- USB-C battery brick — easy, less clean.
- Any other power source, using a cheap buck converter to step down to 5V
The display's 4-pin UART header carries two unrelated things. The 5V/GND pins are just power (above). The TX/RX pins are the optional BLE-debug data tap described below. They're independent — you can wire power without the data tap, or vice versa. Nothing on this cable controls the bike: the firmware only ever listens on RX. It cannot send anything to the bike over this connection.
A two-piece printable case for the Freenove FNK0104B board lives in
screen_enclosure/:
| File | Part |
|---|---|
Reevo_Screen_Top.stl |
Front bezel — frames the screen, snaps onto the bottom |
Reevo_Screen_Bottom.stl |
Rear shell — holds the board, routes the UART cable out |
Both files preview in 3D right on GitHub — click either one to spin it around before you print.
Printing notes. The case is not water-resistant — and honestly you shouldn't ride a Reevo in the rain anyway. Treat the bike like the collector's item it now is: ride it carefully and seldom. Because of that, filament choice isn't critical — PLA or PETG both work fine. (The one exception: don't leave a PLA print baking in direct desert sun, à la Arizona — it'll soften.)
For reference, mine (Seth's) is printed in MatterHackers NylonX at 0.24 mm layer height, 3 outer walls, 25% infill — overkill that'll outlast every other piece of plastic on the bike. NylonX is a carbon-fiber-filled nylon, so it needs a hardened nozzle and an enclosed printer. Don't feel obligated to match that — use the filament and machine you already know and like.
The bike's BLE module (RN4870) ships with passkey 111111. That default
only works if the bike has never been paired with the original Reevo app.
The first time the original app paired with your bike, it rotated the
passkey to a value only the app knew. You can't factory-reset that without
already being able to bond — chicken and egg.
So, in order of likelihood, here's how you actually find the passkey:
- A. You wrote it down when you first set the bike up. Rarest.
- B. It's stored somewhere on the device that originally paired. On macOS, Keychain Access sometimes has BLE pairing data (search for "Reevo" or the bike's MAC). iOS keeps it locked away inside the system. Worth a look, mileage will vary.
- C. Read it off the bike's BLE module debug UART. This sounds intimidating but it's actually how I recovered mine, and it's the reliable path for any bike that was ever paired with the original app. You would need to take the bike apart completely and remove the main board in the motor compartment. It's gnarly. It's scary. It's doable. Details below.
When anything tries to bond, the bike's RN4870 BLE module prints the
real expected passkey to its debug UART. You just have to read it. Solder
two thin wires to the TX and GND pads on the module — see
docs/ble_solder_points.png for the exact
pads. (You only need the module's TX; the connection is read-only, so
its RX is never used.) While you've got the iron out and the bike open
(bullet C above — you'll have the mainboard exposed), it's the natural
moment to also grab a 5V and GND pad for permanent screen power —
see Power.
There are two ways to read what it prints:
If you've already built the screen, it is your serial terminal. The dashboard's Diagnostics page renders whatever the BLE module chatters to its debug UART.
- Run the module's TX → the display's UART-header RX, and tie GND to GND. (That's the data tap; powering the screen is a separate pair of pads — see Power.)
- On the dashboard: Settings → Diagnostics.
- Trigger a pair attempt from anything — the dashboard itself,
LightBlue (iOS), nRF Connect (Android). Enter
111111or any 6-digit number; it'll fail, that's fine. - The 6-digit passkey scrolls onto the Diagnostics page.
- Settings → Command Prompt → Start AP, join the Wi-Fi, browse to
192.168.4.1/, and typesetblepin <the number>. Tap Re-pair bike under Bluetooth. Done — and you never touched a computer.
Handy if you want the passkey before the screen is built.
- Run the module's TX and GND to a USB-UART adapter (CP2102, FTDI, CH340 — any will do; adapter RX ← module TX). Open a serial terminal at 115200 baud, 8N1, no flow control.
- With the terminal listening, attempt to pair from anything and enter any 6-digit number — it'll fail, but the module prints the real passkey.
- Read the 6-digit number off the terminal, then
setblepin <number>on the web prompt and Re-pair bike.
You do not need to do any of this on a new, never-paired Reevo — the
default 111111 works there. But for anything secondhand, plan on the
UART tap.
If you've never touched any of this before:
git clone https://github.com/setha-maker/Reevo-Hubless-E-Bike-Screen.git
cd Reevo-Hubless-E-Bike-Screen
./scripts/install.shThe script installs Homebrew (if needed), Python 3, PlatformIO, and the asset-converter dependencies, then builds and flashes the firmware. Plug the ESP32-S3 board into USB-C before running it.
Already have a working dev environment? Install requirements yourself:
pip install platformio Pillow numpy qrcodeThen:
cd firmware
python3 tools/image_to_rgb565.py # generates src/assets.h bitmaps
pio run -e reevo -t upload # builds and flashesThe repo ships a public template:
The install script copies it to firmware/include/user_config.h on first
run. That copy is gitignored — your personal values never leak into
the public repo. If you're doing the install manually:
cp firmware/include/user_config.example.h firmware/include/user_config.hThen edit user_config.h. The defines:
| Define | Default | What it does |
|---|---|---|
USER_DEFAULT_UNLOCK_PIN |
"1234" |
Initial unlock PIN; rider can change at runtime |
USER_MASTER_PIN |
(commented out) | Optional owner-recovery master code. Uncomment in your personal copy if you want one. |
USER_DEFAULT_BLE_PASSKEY |
"111111" |
RN4870 factory default. Only correct for never-paired bikes — for any used Reevo, recover the real passkey via the UART tap (see above) and set it at runtime with setblepin. |
USER_AP_SSID |
"ReevoConnect" |
Initial Wi-Fi network the dashboard broadcasts |
USER_AP_PSK |
"reevorider" |
Initial hotspot password (min 8 chars) |
USER_SPLASH_TAGLINE |
"world's worst ebike" |
Text under the Reevo logo on the boot splash |
- Unlock PIN — change at runtime via the web
lockresetcommand. - Wi-Fi SSID + password — change at runtime via the web
changewificommand. New values persist in NVS and survive reboots. - BLE pairing passkey — change at runtime via the web
setblepin XXXXXXcommand (e.g., once you've recovered the real passkey via the UART tap). - Splash colors — change at runtime via the web
bgcolor/speedcolorcommands.
The compile-time defaults above are just what a fresh, factory-new flash shows on first boot.
USER_MASTER_PIN is commented out in the public template — the master-code
feature is fully compiled out for public builds. If you want a personal "I
forgot my code" backstop, uncomment the line in your user_config.h (which
is gitignored) and put a 4-digit code only you know. Don't commit it.
If you bought a different ESP32-S3 CYD board, edit:
This is the single source of truth for everything display-related: pin numbers, panel driver, panel rotation, color order, touch chip, SPI frequency. Common alternative profiles (Sunton CYD, generic ST7789, etc.) are listed as commented-out blocks at the bottom — uncomment the one that matches and recompile.
Things that vary between boards:
- SPI pins (MOSI, MISO, SCLK, CS, DC, RST, BL)
- Touch I²C pins (SDA, SCL, INT, RST)
- Panel driver chip (ILI9341 / ST7789 / ILI9488)
- BGR vs RGB color order
- Color inversion
- Backlight active-high vs active-low
- Native panel size and rotation
If your board isn't in the list, find its schematic and fill in the values.
- Power on the bike.
- Power on the dashboard.
- Wait ~10 seconds. The dashboard scans for any BLE device with "REEVO"
in its name and tries to bond with the configured passkey (default
111111).- Bond succeeds → live telemetry appears. You're done.
- Bond fails / dashboard sits on "scan" → your bike's passkey has
been rotated away from the factory default. Use the UART recovery
procedure above to read the real passkey, then
setblepin XXXXXXfrom the web command prompt and tap Re-pair bike.
- The default unlock PIN is
1234. The first time you lock, the numpad will ask for it. Change it with the weblockresetcommand.
After that, everything that needs configuring can be done from the dashboard itself — no recompiling.
The dashboard's web command prompt has three giant text dumps you can read right from your phone after joining the AP:
| Command | Contents |
|---|---|
newreevosetup |
Full end-user manual with troubleshooting tree |
how |
Complete BLE protocol reference (every command & register) |
story |
Claude's narrative of how this firmware came to be |
To get there: tap the gear icon on the main screen → Command Prompt → Start AP → join the SSID (or scan the QR) → open the URL in any browser → tap the chip for the command you want.
Detailed guide: type newreevosetup on the web prompt.
| Problem | First thing to try |
|---|---|
| BLE won't connect | Settings → Bluetooth → Reset Default Pincode → Re-pair bike |
| Forgot your unlock PIN | Type the master code (whatever you set USER_MASTER_PIN to) |
| Screen won't wake | Tap it. Then power-cycle USB-C. |
| PAS won't engage | Tap the PAS pill to raise assist level; the timeout may have dropped it |
| BLE bond fails / "scan" forever | The bike's passkey isn't 111111 anymore. Solder UART tap → trigger a pair attempt from any BLE app → read the real 6-digit passkey from the module's debug output → setblepin XXXXXX on the web prompt. See the BLE pairing section above. |
firmware/
include/
user_config.example.h ← public template (committed)
user_config.h ← your personal copy (gitignored — install
script makes it from the example)
displays/
display_config.h ← edit me if your board isn't a Freenove FNK0104B
config.h, pins.h ← derived from display_config.h
lgfx_board.h ← LovyanGFX panel/touch config
src/ ← the actual firmware
tools/
image_to_rgb565.py ← bakes splash, icons, QR into src/assets.h
source_images/ ← source PNGs the converter reads
docs/
PROTOCOL.md ← full BLE protocol reverse-engineering notes
ble_solder_points.png ← reference for the RN4870 UART tap
screen_enclosure/
Reevo_Screen_Top.stl ← 3D-printable front bezel
Reevo_Screen_Bottom.stl ← 3D-printable rear shell
scripts/
install.sh ← macOS/Linux one-shot installer
MIT. See LICENSE.
This project is unaffiliated with Beno. Reevo is their trademark; the hardware design is referenced only for interoperability after the company's shutdown.
Built by Seth Alvo, with assistance from Claude (Anthropic). Special thanks to the prior Claude session that did the initial BLE reverse-engineering, and to every Reevo owner who's still riding theirs.