Firmware for the M5Stack Tab5 that shows an ESP-KVM device's picture on the Tab5's own screen, and controls the target with its touch screen and keyboard, no browser needed. It is not the KVM itself: the KVM stays plugged into the target, and the Tab5 connects to it over the network like any other viewer. The Tab5 has the same ESP32-P4 as the KVM, so I tried it.
A 26-second video of it running
Experimental. It runs on my Tab5 against my own ESP-KVM devices, and I do not know yet whether anyone else needs it. If you try it, or decide you would not, vote and say why.
Works on hardware:
- Wi-Fi through the Tab5's ESP32-C6: scan, pick a network, type the password. The Tab5 remembers it.
- Sign-in with user and password. On the first visit the device's CA is shown with its SHA-256 fingerprint and trusted only after you accept it.
- The picture: MJPEG from the device, up to about 20 fps at 1280x720. Over Wi-Fi the device usually sends 10 to 20, and with ESP-KVM 0.65.0 or newer the Tab5 acks each frame, so a slow link shows the newest one.
- Touch input to the target, the trackpad mode, two-finger scroll, and zoom with a pinch.
- The bar of extra keys, with Ctrl, Alt, Win and Shift held for the next key.
- Finding ESP-KVM devices on the network by mDNS (other HTTPS devices, such as routers, are left out).
- Reconnecting after the device or the network drops.
- Sleep: the screen goes dark and no picture is sent; a touch wakes it.
- Checking GitHub for a new release of itself.
- Turning the screen over when the Tab5 is held upside down.
- Screen brightness from the menu and the TERMINAL screen.
- The battery level and switching off.
- The target's power, hard off, reset and Wake-on-LAN buttons, and Ctrl+Alt+Del.
- A USB keyboard and mouse in the Tab5's USB-A port.
- Tailscale: a device reached by its tailnet address, about 11 to 14 fps at 720p on my network.
The device's video codec must be set to MJPEG. The P4 has no H.264 decoder, and software decoding manages about 4 fps at 720p.
- Direct mode (the default): the finger is the pointer. Tap = click, hold = right click, drag = left drag.
- Trackpad mode (in the menu): the finger moves the pointer like a laptop's touchpad. Tap = click, hold = right click, hold then move = drag.
- Two fingers: pinch to zoom in (up to 8x), drag to move around the zoomed picture. Not zoomed, a two-finger drag scrolls like a mouse wheel. The chip in the top right corner shows the zoom; tap it to see the whole picture again.
- KEYS in the menu shows a bar with Esc, F1-F12, Tab, the arrows, the page keys and Enter. Ctrl, Alt, Win and Shift stay lit and are held for the next key.
- A USB keyboard and mouse in the USB-A port control the target too, and the keyboard types into the setup screens.
- The tab at the top opens the menu: the target's power buttons, Ctrl+Alt+Del, text mode, brightness, sleep.
- TERMINAL (on the device screen): the version and updates, the screen brightness, turning over by itself, the battery, sleep and switching off.
- The screen dims after 2 minutes without a touch, and on battery goes dark after 10 minutes. The touch that wakes it is not sent to the target.
The TERMINAL screen asks GitHub for the latest release and installs
espkvm-term.bin from it into the other app slot. If the new image does
not reach the network after it starts, the bootloader goes back to the old
one at the next start.
A release is made by pushing a tag (v0.2.0) with its section in
CHANGELOG.md (that becomes the release notes); the workflow in
.github/workflows/build.yml builds it and attaches:
espkvm-term.bin: the app, for updates and for M5 Launcher;espkvm-term-merged.bin: everything at 0x0, for a Tab5 that never ran it (it replaces M5 Launcher, if one is there);sha256sums.txt.
You need ESP-IDF 6.1 and a checkout of
espkvm next to this one. The wire
protocol (components/kvm_proto) and the Tailscale client (microlink) come
from there, so the two sides never drift apart.
git clone https://github.com/espkvm/espkvm.git
git clone https://github.com/espkvm/espkvm-term.git
cd espkvm-term
idf.py set-target esp32p4
idf.py build flash monitorThe app runs on its own (with its own partitions.csv: two app slots for
updates) or from M5 Launcher:
copy espkvm-term.bin to the SD card and install it there. Under Launcher
the flash layout is Launcher's, so updates come through Launcher too, and
the TERMINAL screen says so. Settings live in the nvs partition either way.
- Frames are decoded by the P4's hardware JPEG decoder.
- The PPA scales each frame and turns it into the panel's hidden frame buffer in one pass.
- While the picture is up, LVGL renders but does not write the panel. The menu, the tab and the notes are taken as ARGB pictures (again only when they change) and the PPA blends them over every frame.
- The two panel buffers then swap at the next refresh, so a half-drawn screen is never shown.
In text mode LVGL draws the whole screen again.
- The M5Stack Keyboard for Tab5, over I2C (address 0x6D, SDA G0, SCL G1, INT G50). While it, or a USB keyboard, is attached, the on-screen keyboard stays hidden.
- The USB-A port, as a USB host for a keyboard and a mouse (boot protocol).
- The BMI270 for which way up the Tab5 is held.
- The INA226 (0x41) for the battery voltage, the IP2326 charger's status line on the second IO expander (0x44, P6), and its PWROFF line (P4) to switch off.
Commands over the USB serial port, for development (the port is
/dev/ttyACM0, or set TAB5_PORT):
tools/shot.py out.png [full]saves a screenshot.tools/shot.py tap X Ytouches the screen.tools/shot.py chr TEXTtypes text.tools/shot.py key Nsends one key.
Typed on the port:
heap,stats(frame rates, decode time, zoom),link(sockets, ping, late frames),power(battery and tilt),rssi,zoom Z X Y.wigglemoves the target's pointer round a small circle for ten seconds, so a still screen sends frames and the frame rate can be measured.tcp IP PORTopens a plain TCP connection: it tells a network fault from a device fault.dl URLmeasures a download.host ADDRsigns in to another address with the saved user,trustaccepts the shown certificate,acks 0|1turns frame acks off or on.sleep, andlog TAG LEVELto change a log level.
- Wi-Fi passwords, device logins and Tailscale keys are kept in NVS, which is not encrypted.
- A device is trusted by its pinned CA, not by its host name, so a device
can be reached by IP,
.localname or tailnet name.
Apache-2.0, see LICENSE. Copyright 2026 Evgenij Spitsyn, see NOTICE.
Third-party parts:
- Barlow Condensed and Saira Stencil One fonts: SIL OFL 1.1.
- DejaVu Sans Mono: Bitstream Vera license.
- Font Awesome symbols: SIL OFL 1.1.
- The Tab5 keyboard's key table, from M5Stack's M5Unit-KEYBOARD: MIT.
Each license is in main/fonts and main/LICENSE-M5Unit-KEYBOARD.txt.
