Skip to main content
  1. Posts/

Turning a Cheap Yellow Display into a Home Assistant Control Panel

· David Steeman · Electronics, DIY, ESP32, Home Assistant

Every day my kids come home from school and stand at the front door. Every time I want to know if I should start dinner or if the washing machine is still running, I pull out my phone, open the Home Assistant app, and navigate to the energy dashboard. It works, but it’s friction. What I wanted was a small screen on my workdesk that I could glance at to check the house’s power consumption, and a button I could tap to let the kids in or open the garage — no phone, no app, just look and tap.

I had a Cheap Yellow Display sitting around running NerdMiner firmware — one of those ESP32 boards with a 2.8" colour touchscreen that everyone buys because they’re cheap and then struggles to find a real use for. It turned out to be exactly what I needed. So I repurposed it into a dedicated Home Assistant control panel for my desk.

What is a Cheap Yellow Display?
#

The “Cheap Yellow Display” — CYD for short — is the ESP32-2432S028. It’s an ESP32 development board with a built-in 2.8" 320×240 ILI9341 TFT display, an XPT2046 resistive touchscreen, and a small speaker amplifier. The community gave it the “cheap yellow” nickname because the PCB is yellow and they cost about €8 on AliExpress. There are variations (the “E” sub-variant, the “R” version with RGB LEDs) but they all share the same basic layout: ESP32 dual-core at 240 MHz, 4 MB flash, no PSRAM, and a display that’s just big enough to be useful.

The board is popular in the maker community precisely because everything is on one PCB. No wiring a separate display module, no figuring out pin assignments, no building a custom enclosure to hold the screen and the microcontroller. You plug in USB, flash firmware, and you have a touchscreen device.

What I wanted it to do
#

The requirements were simple:

  1. Show my current household power consumption prominently on screen, updated live.
  2. Provide three touch buttons that trigger Home Assistant switches: open the garage, close the garage, and toggle the front door lock.
  3. Sound an audible alarm when power stays above 3000 W for more than 5 seconds.

All the logic had to run on the device itself — no Home Assistant automations doing the heavy lifting. The CYD connects to Home Assistant via ESPHome’s native API, which means it shows up as a first-class device in HA and can call services directly.

Why ESPHome?
#

ESPHome is a framework that lets you configure ESP32 and ESP8266 devices using YAML instead of writing C/C++ firmware from scratch. It integrates natively with Home Assistant, handles Wi-Fi, OTA updates, and provides components for displays, touchscreens, sensors, and outputs. You describe what you want in YAML, ESPHome compiles it into firmware, and you flash it to the board.

For a project like this — connecting a display, a touchscreen, a speaker, and Home Assistant — ESPHome is the obvious choice. The alternative would be writing ESP-IDF or Arduino code that handles SPI initialization, LVGL integration, touch calibration, Wi-Fi management, and the HA API. That’s weeks of work. With ESPHome it’s one YAML file.

The build
#

Display driver — the first gotcha
#

This is where I lost time, so I’ll save you the same trouble. The CYD’s display chip is the ILI9341, and ESPHome offers two platforms for it: ili9xxx and mipi_spi. I tried mipi_spi first because it’s newer. The result was a garbled screen — wrong colours, shifted pixels, completely unusable. After some digging I found that the mipi_spi platform doesn’t work correctly with this particular display controller on this board.

The fix: use the ili9xxx platform with model: ILI9342. Wait, ILI9342? Yes. The ILI9341 and ILI9342 share the same init sequence, but the ILI9342 model in ESPHome defaults to 320×240 landscape orientation, which is exactly what this board needs. Using ILI9341 as the model works but may require explicit rotation settings. With ILI9342 and rotation: 0 in LVGL, the display is correct out of the box — no transforms, no rotation, no colour inversion.

Touchscreen — the second gotcha
#

The XPT2046 resistive touchscreen sits on a separate SPI bus from the display. The CYD PCB physically swaps the X and Y axes between the touch controller and the display, so the touch transform needs swap_xy: true. That part is well-documented.

What’s less obvious is the mirror_x setting. When my UI had three buttons stacked vertically (full width of the screen), everything worked fine regardless of mirror_x, because only the Y axis determined which button you tapped. It wasn’t until I rearranged the buttons side by side that I noticed left and right were flipped. Adding mirror_x: true to the touch transform fixed it.

The correct transform for this board is:

transform:
  swap_xy: true
  mirror_x: true
  mirror_y: true

Speaker — the third gotcha
#

The CYD has a small speaker amplifier on GPIO26. The ESP32 has a DAC on that pin, so you’d naturally reach for the esp32_dac output platform. But the rtttl component (which plays ring-tone melodies — used for the beep alarm) doesn’t support esp32_dac. It requires a PWM output instead. So I used the ledc platform on GPIO26, and that works perfectly. The gain is set to 10% — the amp is surprisingly loud, and even at that level the beep carries clearly across the room.

Power sensor units
#

My Home Assistant power sensor reports in kilowatts, not watts. The spec said to assume watts, but of course reality disagreed. Every comparison and display format multiplies by 1000. This is easy to miss because the numbers look plausible either way — 0.742 kW displayed as “742” looks right, but “0 W” when it should show “742 W” would be confusing. I caught it early because the display showed fractional values instead of whole watts.

Colours — the fourth gotcha (and the sneakiest)
#

With the panel working, I gave it a proper visual once-over: a dark theme with the power number colour-coded — green when usage is low, blue in the normal range, red when it’s high. Except the colours were all wrong. The value that should have been blue came up orange. The one that should have been red came up blue. Green, mysteriously, looked fine.

The pattern is a dead giveaway once you spot it: red and blue had been swapped, and green was untouched. Green survives a red/blue swap because it sits in the middle of the spectrum — shuffle the red and blue channels of a mostly-green pixel and it still reads as green. Which is exactly why nobody noticed until blue and red were actually on screen at the same time.

The culprit is the same ILI9342 model that fixed the orientation. That model sets a flag (MADCTL with the BGR bit) telling the display to swap the red and blue channels of every pixel it receives. ESPHome hands the display pixels in RGB order; the display dutifully flips them to BGR.

The obvious-looking fix — telling ESPHome to emit BGR with color_order: BGR — did absolutely nothing. As far as I can tell that setting has no effect on the path LVGL uses to drive the display directly. So the fix is blunt but dependable: write every colour with red and blue already swapped, so the display’s swap cancels out. A one-time tax, and now every colour is correct.

The firmware
#

The entire firmware is one YAML file — cyd-ha-control.yaml — plus a secrets.yaml that holds Wi-Fi credentials, the API encryption key, and the Home Assistant entity IDs. The secrets file is git-ignored because the repo is public; a secrets.yaml.example template is committed in its place.

The UI is built with LVGL, which ESPHome supports as a first-class component. At the top of the screen is a large power readout sitting on a slightly lighter rounded card, against a deep blue-black background — a dark, Home Assistant-style theme. The number itself is colour-coded in three bands: green below 200 W, blue across the normal 200–3000 W range, and red at 3000 W and above. I deliberately start the red band at the same point the alarm arms, so the number turns red exactly when it’s about to start beeping. Below it are three tall buttons side by side — easy to tap on a resistive touchscreen, coloured Home Assistant blue — that toggle the garage and door lock switches via homeassistant.action service calls.

The over-power alarm uses a template binary sensor with a delayed_on: 5s filter. When power exceeds 3000 W continuously for 5 seconds, a script starts that plays a short beep every 3 seconds. When power drops back below the threshold, the script stops immediately.

# The core of the beep alarm
binary_sensor:
  - platform: template
    id: power_high
    filters:
      - delayed_on: 5s
    on_press:
      - script.execute: beep_loop
    on_release:
      - script.stop: beep_loop

script:
  - id: beep_loop
    mode: restart
    then:
      - while:
          condition:
            binary_sensor.is_on: power_high
          then:
            - rtttl.play: "beep:d=4,o=5,b=100:16e6"
            - delay: 3s

Building this with Claude Code
#

Here’s the part that surprised me the most: I didn’t write a single line of code for this project. The entire firmware — all 270-odd lines of ESPHome YAML, the LVGL UI layout, the touch calibration, the beep alarm logic — was written by Claude Code , Anthropic’s coding assistant that runs in the terminal. I described what I wanted in plain language, and it wrote the firmware, compiled it, and flashed it to the board. When things didn’t work (and they didn’t, repeatedly), I described the symptom and Claude Code diagnosed and fixed the issue.

The display driver situation is a good example. The first build produced a garbled screen. I told Claude Code what I saw, and it worked through the possible causes: wrong SPI speed, wrong colour inversion, wrong display model. It discovered the ILI9342 model trick, updated the YAML, recompiled, and reflashed over OTA. The same pattern repeated for the touch transform, the speaker output driver, and the power sensor unit mismatch. Each time, I described what I observed and Claude Code iterated on the fix.

I wrote a functional specification — CYD-HA-Control-SPEC.md — before starting, which gave Claude Code clear requirements and hardware details to work from. That spec plus the iterative debugging sessions were enough to go from a blank board to a working control panel in an afternoon. All the hard-won technical details are documented in CLAUDE.md in the repository so the context survives between sessions.

This isn’t meant as a sales pitch for AI coding tools — it’s a genuine observation that the barrier to building custom hardware projects has dropped dramatically. If you can describe what you want and recognise whether it’s working, you can build it.

What you need to build one
#

The bill of materials is short. Here’s everything:

ItemApproximate costNotes
ESP32-2432S028 (CYD)~€8The “Cheap Yellow Display” — make sure it’s the 2.8" resistive touch version
USB cable (micro USB)For the first flash and power
3D printer (optional)For the wall mount case
Filament (~30 g)PLA, any colour

On the software side:

  • ESPHome — install via pip in a Python virtual environment (developed with version 2026.5.3)
  • Home Assistant — with the ESPHome integration installed
  • Claude Code — optional, but this is how I built it without writing code myself
  • The firmware from GitHub — clone or download the repository

You’ll also need a power sensor and switch entities in Home Assistant that you want the panel to display and control. The firmware is configured to use whatever entities you define in secrets.yaml — it’s not hardcoded to my setup.

Mounting it on the wall (or desk)
#

The CYD sits on my desk, but if you’d rather wall-mount it, there’s a well-designed wall mount case on MakerWorld by kenzoteke. It’s a two-piece design: a base that screws to the wall, and a front cover that snaps over the CYD and screws to the base with M3×10 mm button head screws.

The clever bit is that the screws holding the display and cover are accessible from the front. You mount the base to the wall first with standard wood screws (the holes are countersunk so the screw heads sit flush), then attach the cover. That means you don’t need to fiddle with the CYD while balancing it against the wall — the board just sits inside the case.

I printed it at the default profile — 0.2 mm layer height, 2 walls, 15% infill — in about 52 minutes. The fit is snug and the display is fully visible through the front opening. The touch screen is exposed, which is exactly what you want for a control panel.

One more thing: HA permissions
#

This one nearly drove me crazy. The buttons were logging “action sent” in the ESPHome logs, but nothing was happening in Home Assistant. The switches weren’t toggling. I verified the entity IDs, the API connection, the service call format — everything looked correct.

The issue was a single toggle buried in the Home Assistant UI. When you adopt an ESPHome device in HA, there’s a per-device permission setting: “Allow the device to perform Home Assistant actions.” It’s off by default. ESPHome sends the action, HA receives it, and silently drops it because the device isn’t authorized to execute actions. No error, no warning, nothing in the log. You find it under Settings → Devices → ESPHome → your device. Turn it on, and everything works.

First flash and OTA
#

The first flash has to be over USB because the board was running NerdMiner firmware with no ESPHome OTA endpoint. After that, the CYD joins Wi-Fi and accepts over-the-air updates, so subsequent flashes happen wirelessly:

# First flash (USB)
esphome run cyd-ha-control.yaml --device /dev/ttyUSB0

# All subsequent flashes (OTA)
esphome run cyd-ha-control.yaml --device <device-ip>

I used a Python virtual environment for ESPHome to keep things clean:

python3 -m venv ~/esphome-cyd-venv
source ~/esphome-cyd-venv/bin/activate
pip install esphome

Lessons learned
#

The CYD is a capable little device for this kind of project, but the documentation is scattered across forum posts, GitHub issues, and blog comments. The display driver situation alone cost me an evening of garbled screens before I found the ILI9342 model trick. I’ve documented everything in the project’s CLAUDE.md so the next person (or me, six months from now) doesn’t repeat the same mistakes.

Touch transforms are sneaky. A wrong mirror_x is invisible until you have side-by-side UI elements — vertical layouts mask the bug entirely. Test with horizontal layouts early.

The ESPHome + Home Assistant combination is remarkably powerful for building physical control interfaces. One YAML file, no custom firmware, native HA integration, OTA updates. The gap between “I have an idea” and “it’s sitting on my desk, working” is surprisingly small.

And always check the permissions toggle. Always.

Resources
#