Touch Panel Manager — Dynamic multi-page mobile panel using Waveshare ESP32-S3 3.49" LCD

Hi everyone,

I've been building a smart-home control panel based on the Waveshare ESP32-S3-Touch-LCD-3.49 (that long thin 172×640 portrait display), and I'm at a point where it's stable enough to share for feedback.

The piece I'm most happy with: everything is configured from the Home Assistant UI — no YAML editing, no re-flashing. You pick entities for each slot in a normal HA config flow, save, and the panel updates in real time. Add a new page in HA, swipe on the device → there it is.

What it does

  • :artist_palette: Up to 50 pages × 8 slots per panel, all configured via HA UI
  • :radio_button: Drop in any light, switch, scene, script, automation, fan, cover, media_player — service dispatch is auto (toggle vs turn_on by domain)
  • :thermometer: Drop in any sensor or binary_sensor and the slot becomes a live value display with auto-formatted units (22.3°C, 45%)
  • :bullseye: MDI icons chosen automatically by domain + device_class (lightbulb / switch / thermometer / humidity drop / motion / flash …)
  • :yellow_circle: Buttons turn yellow when entity is on; lights also show brightness % in the sub-line
  • :triangular_ruler: Auto rotation via the onboard QMI8658 IMU — portrait shows the controller, landscape shows a big clock + outdoor/indoor temps dashboard
  • :backhand_index_pointing_up: Tap or swipe between pages (with motion-aware click guard so swipes don't trigger buttons)
  • :battery: Battery operation — 18650 with hardware power latch (TCA9554 pin 6), live voltage + %, color-coded bar (green / yellow / red)
  • :globe_showing_europe_africa: English UI, Turkish translation included, easy to add more

The integration

This is a proper HA custom integration (touch_panel_manager), not just an ESPHome YAML. It exposes:

  • One sensor.<panel>_config with all per-slot attributes (entity, label, icon, text, type)
  • Two temperature proxies that mirror the configured outdoor/indoor sensors
  • Eight binary_sensor.<panel>_slot_N_state that proxy the on/off of whatever entity is in that slot, on the current page
  • A panel_action(entity_id) dispatcher service used by the panel for taps
  • A set_current_page(config_entity_id, page_index) service used by the panel for navigation

ESPHome just subscribes to attributes and renders. The panel never stores config locally → editing in HA propagates in milliseconds.

Screenshots & demo

Repo has SVG mockups of both views and a short demo video showing the live UI:

Install

Full instructions in the GitHub repo. Briefly:

  1. Add the custom_components/touch_panel_manager folder to your HA config (or install via HACS custom repo)
  2. Add a "Touch Panel Manager" device in HA, configure pages
  3. Flash the ESPHome YAML to the Waveshare board, point its panel_config_entity substitution to your device's config sensor
  4. HA auto-discovers the ESPHome device, you're done

Feedback I'd love

  • Anyone else running this hardware — do the AXS15231 display config and the I2C bus assignments work on yours? (The display chip is a bit finicky with 172-wide; I use full_refresh: true to dodge the tearing)
  • HA integration patterns — is exposing the config as sensor.attributes reasonable, or would you do this differently? Considered SubConfigEntry but went with menu-driven options flow for max compatibility
  • UI suggestions — long-press → brightness slider, status bar on portrait, and more dashboard widgets (weather/calendar) are on the roadmap. What would you add?
  • Bugs and edge cases — multi-page with empty slots, page reordering, etc. There's a known minor pixel artifact at column 171 (panel width 172 isn't 8-divisible).

Repo: GitHub - cryptooth/homeassistant-waveshare-lcd349: Multi-page touch control panel for Home Assistant on Waveshare ESP32-S3 3.49 inch display · GitHub

MIT licensed, PRs welcome. Built over a weekend of late-night ESPHome + LVGL + Python wrangling :slightly_smiling_face:

Thanks for taking a look!

I just found your project and am in the process of implementing it. I did have some trouble with some of the configuration steps. In no particular order:

  • The sensor variable for the clock was not setup on my Home Assistant instance. Luckily with the help of google I was able to acertain how to add the date time integration to create the sensor.time entity your code was looking for.
  • I’m still struggling with the implementation of the indoor and outdoor sensors. At present, they present a null value and default to 0.0 degrees. I’m working on trying to build a helper to mirror temperature input from other sensors (i.e. sensor.upstairs_temperature, etc) to the custom attribute you’re looking for with sensor.(panelname)_indoor_temp
  • I’m not quite sure if you’ve implemented the power on and off modes with the press of the power button (middle) instead of the boot (bottom) or reset (top) buttons. If not I would highly recommend adding that function so you can quickly power the device on, do what you need, and power off. I plan to use this as a pocket remote for my workshop to control outlets / lights / etc.
  • I’ve noticed that if a device status is changed elsewhere in home assistant by some other means that the yellow color used to indicate if the device state is on or off can sometimes de-sync and become the opposite (Yellow and OFF, ON and black background). Since you are able to pull the status of the sensors from HA it should be possible to parse the status on / off and tie it to the respective color. This would allow the color status to update based on the entity status that updates normally on the device.

I’m really enjoying it and hope I can contribute in some fashion. This use case is exactly why I wanted the device in the first place. Keep up the amazing work!

This looks awesome! I’ve just tried it out on my ESP32-S3-Touch-LCD-3.49. I managed to get it flashed, but the IMU seems to be stuck on the same values no matter what the orientation:

[00:34:25.236][S][sensor]: 'IMU Accel Y' >> 0.08 g
[00:34:25.236][S][sensor]: 'IMU Accel Z' >> 1.02 g
[00:34:25.236][S][text_sensor]: 'Cihaz Yönü' >> 'landscape'

Any tips for debugging appreciated!

Does this work with both V1 and V2 versions of the board?