The Google TV remote is one of the weakest parts of the Chromecast experience. The buttons are mushy, it's small and easy to lose, and it isn't capable of a whole lot on its own. It's not a bad remote, but it's just... kind of boring. Google has also released at least three different remotes across the Chromecast with Google TV and the newer Google TV Streamer, each of them charmless in slightly different ways.

As a fun project, I decided to build my own. It started with the ESP32 Cheap Yellow Display, but the project ended up being a lot more useful than I had initially thought, so I built it for the IPS-based WT32 SC01 Plus instead. It's running a custom firmware that talks to the TV over the same Android TV Remote v2 protocol the Google TV phone app uses, mimicking the same TLS connection and pairing handshake. I picked up two of these displays a while back for $25 each, and they've been immensely useful.

The CYD's resistive screen meant that the interface didn't feel as comfortable to use or as fast as physical buttons on a remote, but the IPS panel of this display solves that. Even better, it does things the Google remote physically cannot. It launches Netflix and YouTube, just like the official remote can, but I can also add my own custom app launch buttons for apps such as Jellyfin. Plus, when I swipe left, there's a second page with buttons wired up to Home Assistant, where I can toggle lights or trigger automations.

The entire layout is configurable from a web form served by the device itself, so I can rename a button, point it at a new entity, or swap " Jellyfin" for "Plex" without needing to recompile the application.

The WT32-SC01 Plus turned out to be the right choice

Capacitive touch, a real screen, and an ESP32-S3

The Android TV Remote v2 protocol the phone app uses has been reverse-engineered by the community for years, with working implementations in Python, Node.js, and an existing ESP32 port I forked from. The actual work I had to do to build this was very little, as those projects had scaffolded all of the difficult parts already.

The WT32-SC01 Plus pairs an ESP32-S3-WROVER-N16R2 module (16MB flash, 2MB PSRAM) with a 3.5-inch ST7796 480x320 display over an 8-bit parallel bus, and an FT6336 capacitive touch panel. It costs around $25 from Seeed Studio, and the 8-bit parallel interface is fast enough that the entire UI redraws without visible tearing, the touchscreen feels like every other modern touchscreen you've ever used, and the screen is large enough to fit a six-button top row, a five-button d-pad, a six-button app grid, and a full-width media transport row, all at once and big enough to tap reliably.

I'm using LVGL for the UI. It's memory-hungry, sure, but it gives me proper buttons, lists, text input, swipe gesture events, and screen transitions without having to draw pixels by hand. The remote screen is a flat array of button definitions, the home automation screen is another, and a single gesture handler swaps between them.

WT32-SC01 Plus
Weight
96g
Wi-Fi
Yes
Bluetooth
Yes

It's the same protocol as the Google TV phone app

No tricks needed

The Android TV Remote v2 protocol runs over TLS on two ports. Port 6467 handles pairing, and port 6466 handles the live remote session once you're paired. Everything is wrapped in protobuf messages, and the framing is pretty simple. There's a one-byte length prefix followed by the message bytes, both sides have to present a TLS client certificate, and the pairing process exists specifically so that the TV can decide it trusts your certificate going forward.

The pairing handshake is the interesting part. When you connect to port 6467 for the first time, the TV displays a 6-digit hex code on screen. To prove you actually have eyes on the TV, the firmware has to take the public-key moduli and exponents from both the TV's certificate and its own client certificate, hash them together with the middle two bytes of the code, and check that the first byte of the resulting SHA-256 matches the checksum byte from the code. Only then does it send a "pairing secret" back to the TV, which adds the certificate to the trusted remotes list. From that point on, you reconnect on port 6466 with the same certificate and the TV recognises you immediately.

All of this is done with wolfSSL, an embedded TLS library tuned for microcontrollers. I'm using a 5.7.4 release with the FastMath build, pulled from the previously mentioned ESP32 build. I tried using the regular upstream wolfSSL library,, and kept running into obscure errors that I spent far too long trying to fix. WolfSSL handles the TLS session, the certificate parsing, and the RSA modulus extraction that the pairing hash depends on. The protobuf side is handled by nanopb, which generates compact C decoders from .proto files. Together they fit comfortably inside the ESP32-S3's flash, with room left over for the rest of the firmware.

The actual key events are tiny. Each button press becomes a RemoteKeyInject message with a keycode (KEYCODE_HOME, KEYCODE_DPAD_UP, KEYCODE_VOLUME_UP, and so on) and a direction (SHORT for a tap). The TV responds with periodic ping requests that the firmware has to answer to keep the session alive, and it sends a remote_configure message at the start that the firmware acknowledges.

mDNS handles discovery. The firmware queries for _androidtvremote2._tcp on the local network at boot, builds a list of any Google TVs it finds, and I simply tap one to start pairing. After pairing, the IP is cached in non-volatile storage (NVS) and subsequent boots skip discovery entirely and jump straight to the remote screen. If the TV ever revokes the pairing, the firmware notices the error_auth and drops back into the pairing flow automatically.

I can launch apps and manage my smart home

Swipe for toggles, automations, and scripts

The physical Chromecast voice remote has dedicated Netflix and YouTube buttons, but those work over Bluetooth HID: the buttons emit a hardcoded keycode and the Android TV firmware catches it and launches the matching package. My firmware doesn't have that channel available, so it uses the IP-protocol equivalent instead. The Google TV phone app, Home Assistant's androidtv_remote integration, and my firmware all send the same protobuf message carrying a single app_link string. The protocol itself is undocumented; the community calls that message RemoteAppLinkLaunchRequest because that's the label given to it in the reverse-engineered protocol that every downstream port has carried forward. The TV hands the URI to Intent.parseUri on the Android side.

Netflix and YouTube register native HTTPS schemes, so a link to the Netflix site or the YouTube home page actually just opens those apps instead. For anything else, the trick I used from the Home Assistant Android TV integration was to send the Play Store details URL, https://play.google.com/store/apps/details?id=

This makes it possible to launch any application installed on the TV with a button. My current build has six app buttons down the right-hand side of the screen: Netflix, YouTube, Jellyfin, Plex, Disney+, and Spotify. Tap "Jellyfin" and, about half a second later, the TV is on the Jellyfin home page. It's a neat time saver.

The next step was to add a second screen, to really drive home the advantage over a regular Chromecast remote. A horizontal swipe across the remote loads a grid of buttons that all call Home Assistant's REST API. Each button is a service call, carrying a domain, service, and an optional entity ID. For example, in my case, I can tap "Living Room" and the firmware sends a POST request to my Home Assistant's light toggle API, carrying the entity ID and the long-lived access token. I can trigger automations and scripts as well, so I could create and configure a movie-time script that I can tap from the display to dim the lights and turn on my speaker.

Deals

Save on maker deals: microcontroller kits & displays

Find discounts on maker essentials and DIY electronics—score savings on ESP32 boards, touch displays, sensors, development modules, plus accessories and tools to build custom remotes, smart-home controllers, and other projects.

I can then swipe back and be back on the remote. I didn't add a transition animation, as the instant swap feels a lot nicer to use. It means that it isn't just a remote relegated to doing the same (but slightly better job) as the official remote, but a way to control my smart home and the things around my TV as well.

A web form on the device, and the gaps that are left

Reconfigure from a browser

Building this project really only required slapping a UI on top of the back-end functions and bolting on a web UI to control the apps and automations. Everything lives in the NVS, and the web server is accessible at any time over the same network. Editing any of the setitngs writes the saved values to flash and reboots into the new config. The Wi-Fi configuration itself goes through WiFiManager's captive portal on first boot and the on-device WebServer comes up after WiFiManager hands control back. The HTML doesn't have any JavaScript or external assets, and the POST handler reads each field against a whitelist of known keys and writes them to a single namespace.

There are still a couple of rough edges, and one of the major ones are inherited from the upstream project I based it off of. It comes with a static client certificate that I haven't replaced yet, so every device flashed from the repo presents the same identity to whatever TV it pairs with. For one remote and one TV that's fine, but if you wanted to build ten of them, you'd want to generate a unique key and cert on first boot and persist them to NVS.

Voice control is the other obvious gap, but that's a hardware one rather than a protocol one. The Android TV Remote v2 protocol does carry voice, and the androidtvremote2 Python library that Home Assistant uses sends 16-bit PCM mono at 8kHz on the same TLS session as the key events. However, the WT32-SC01 Plus doesn't have a microphone, so if you wanted voice search, you could use something like the ESP32-S3-BOX-3 or an M5Stack CoreS3 instead. The ESP32-S3is more than capable of that.

I built this as a fun project to see what I could do with the Android TV remote API, and it turned out to be a lot more useful than I expected.