Remote WebView shows HA web dashboard on ESP32-S3-based screen

Hey folks!

I previously built an “HA Deck” to simplify building HA dashboards on-device with LVGL. It worked, but I’ve always wanted to try an “RDP-style” path — render in a real browser, stream the result to an ESP32 display.

Remote WebView Server/Client renders your HA dashboard in headless Chromium and streams only the changed parts as JPEG tiles over WebSocket to an ESP32-S3 480×480 RGB screen. The device decodes and blits those tiles directly to the panel, with touch events sent back for scrolling and interaction.

The primary reason to try this approach is a real browser experience (fonts, CSS, charts, components) without re-implementing UI in LVGL.

Check demo:

How it works

  • Server opens your HA dashboard in headless Chromium
  • On each screencast frame, it computes diffs, merges adjacent tiles, JPEG-encodes them, and pushes over WS
  • ESP32-S3 decodes JPEG straight into the display buffer
  • Touch from the device is forwarded back to the page

Current status

  • Beta and moving fast. The client firmware is my first attempt with a lot of AI-generated code — good for exploration, but readability/edge-cases still need love.
  • With 1 - 2 dashboards, it’s smooth; more concurrent pages may need moving CPU-heavy work into worker threads.

Try it?

  • Server: Docker container (see repo README, use beta tag)
  • Client: simple ESP32-S3 firmware for a Guition-ESP32-S3-4848S040 (480×480 RGB panel)

Looking for feedback

Would you use this as an always-on HA display? Is an ESPHome component interesting (I plan to prototype and benchmark it vs. the standalone client)?

This is awesome!
Not sure I understand the installation process though but I will give this a try when I have a spare display

Thanks! The super-short install guide:

  • Server (Docker): run the container, expose 8080/8081 (and 9221 for DevTools to log in to HA), set envs for your dashboard URL and tile/JPEG options
  • Client (ESP32-S3-4848S040): set WIFI_SSID/WIFI_PWD, WS_HOST=<server_ip>, DEVICE_ID, build, and flash the firmware
  • Power it up - the screen should connect and start streaming

If anything’s unclear or breaks, drop a message here and I’ll walk you through.

A few updates:

  • Ported the client to ESP-IDF. It can render a 480×480 frame as a single tile. Current performance: ~10 ms for partial updates and ~73 ms for full updates.
  • It could be a bit faster if we split decoding across both cores, but that requires a double buffer, and I haven’t had much luck with that so far.
  • Drafting an ESPHome external component. Plan to move many settings from the server to the client so different clients can use different configurations.

this looks really good, thanks for sharing!!! How would I go about adjusting for a 800×480 7" screen?

The only option available right now is to tune the existing code (it’s also 800×480, but 5″) so that it works with your panel. Alternatively, you can wait until the ESPHome component is ready; however:

  • Your panel must be supported by ESPHome (ESP-IDF framework)
  • I won’t pursue the ESPHome component path unless it can achieve comparable performance (under 100 ms for a full-screen refresh).

Also, consider that a larger screen will be slower, due to the increased amount of data to transfer/decode.

The updated server and the ESPHome external component have been released. The server repo also includes the Home Assistant add-on files, but I have no way to test whether it works (I’d be surprised if it worked on the first try — if anyone is willing to test and troubleshoot, let me know).

The main updates are:

  • ESPHome component: Any display supported by ESPHome (and with the new mipi_rgb platform, that’s a lot) can run Remote WebView.
  • Per-client settings: Each connection can supply its own width, height, tileSize, jpegQuality, maxBytesPerMessage, etc.
  • Client-driven navigation: The client can control which page to open.
  • Many quality-of-life improvements.

The built-in self-test shows ~7 ms (~143 FPS) for partial updates and ~58 ms (~17 FPS) for full updates. Note that this does not include data transmission/receipt time.

Is there any chance to support ESP32-P4? I tried to use the config with my ESP32-P4 based display but I got stuck on jpegdec-esphome, since I saw you modified jpegdec specifically for S3…

Thanks, and I appreciate your awesome project!

I don’t have a P4-based screen, but it would be interesting to try (maybe I’ll order one later).

You don’t need JPEGDEC at all, since the ESP32-P4 has a very fast hardware JPEG decoder (Full HD — 48 FPS, according to the docs). It should be integrated into the component for this target (via ifdefs).

Which screen are you using? Is it fully supported by ESPHome?

I’m using this one, which is discounted right now:
I just found this on AliExpress: $125.63 | ESP32P4 Development Board 10.1 Inch 1280*800 Screen WiFi BT Type-C for Arduino LVGL ESP-IDE Smart Home Optional Speaker Battery
https://a.aliexpress.com/_mrL6nJx

This is the esphome yaml configuration I’m trying to implement, mixing the working one I was previously using with the remote client sample on your github repo (actually using a fork since your main branch was not working and someone else was able to fork and fix):

esphome:
  name: esp32p4tablet
  friendly_name: esp32p4tablet
esp32:
  board: esp32-p4-evboard
  cpu_frequency: 400MHz
  flash_size: 16MB
  framework:
    type: esp-idf
    sdkconfig_options:
      COMPILER_OPTIMIZATION_SIZE: y
      CONFIG_SPIRAM_FETCH_INSTRUCTIONS: y
      CONFIG_SPIRAM_RODATA: y
    advanced:
      enable_idf_experimental_features: yes
    components:
      - name: "espressif/esp_websocket_client"
        ref: 1.5.0
      - name: "espressif/esp-dsp"
        ref: 1.7.0
      - name: "bitbank2/jpegdec"
        #ref: 1.6.2
        source: https://github.com/strange-v/jpegdec-esphome
api:
  encryption:
    key: <secret>
ota:
  - platform: esphome
    password: <secret>
wifi:
  networks:
    - ssid: <secret>
      password: <secret>
  ap:
    ssid: "Esp32P4Tablet Fallback Hotspot"
    password: <secret>
captive_portal:
logger:
  hardware_uart: USB_SERIAL_JTAG
  level: DEBUG
  logs:
    lvgl: INFO
    display: INFO
    app: DEBUG
external_components:  
  - source: github://willumpie82/esphome@dev
    components: [mipi_dsi]
  - source: github://kvj/esphome@jd9365_gsl3680
    refresh: 0s
    components: [gsl3680]
  - source: github://esphome/[email protected]
    components: [i2c]
  - source:
      type: git
      url: https://github.com/supremelucifer/RemoteWebViewClient
      ref: main
    components: [ remote_webview ]
esp_ldo:
  - channel: 3
    voltage: 2.5V
esp32_hosted:
  variant: ESP32C6
  reset_pin: 54
  cmd_pin: 19
  clk_pin: 18
  d0_pin: 14
  d1_pin: 15
  d2_pin: 16
  d3_pin: 17
  active_high: true
psram:
  speed: 200MHz
switch:
  - id: backlight_
    platform: gpio
    pin: GPIO23
    restore_mode: ALWAYS_ON
color:
  - id: my_red
    red: 100%
    green: 0%
    blue: 0%
# -------------------------------
# LVGL Display
# -------------------------------
display:
  - platform: mipi_dsi
    model: JC8012P4A1
    id: my_display
    update_interval: 1s
    reset_pin: GPIO27
i2c:
  - id: i2c_bus
    sda: GPIO7  
    scl: GPIO8  
    #scan: true
    frequency: 400kHz 
lvgl:
  buffer_size: 100%
  byte_order: little_endian   
  touchscreens:
    - touchscreen_id: touchscreen_
touchscreen:
  - platform: gsl3680
    id: touchscreen_
    reset_pin: GPIO22
    interrupt_pin: GPIO21
    transform:
      swap_xy: false
      mirror_y: false
remote_webview:
  id: rwv
  display_id: my_display
  touchscreen_id: touchscreen_
  device_id: esp32p4tablet
  server: 1192.168.0.208:8081
  url: http://192.168.0.43:8123/dashboard-mobile/0
  full_frame_tile_count: 1
  max_bytes_per_msg: 61440
  jpeg_quality: 85
text:
  - platform: template
    id: rwv_url
    name: "URL"
    optimistic: true
    restore_value: false
    mode: TEXT
    min_length: 1
    set_action:
      - lambda: |-
          if (!id(rwv).open_url(std::string(x.c_str()))) {
            id(rwv).set_url(std::string(x.c_str()));
            ESP_LOGI("remote_webview", "URL queued (not connected): %s", x.c_str());
          }

And you can flash the yaml configuration below as is for testing esphome on a ESP32-P4 (that’s how I started):

I see, thanks. I’ll probably wait a bit until support for the P4 becomes more straightforward.

I doubt I’ll be able to help much, but what’s the current issue? Can’t build/can’t connect/slow rendering/etc.?

UPD: I see issue in the component that was causing a build failure. Will be addressed soon.

No worries, I totally understand.

When I try to install the esphome yaml config (the one that mixes in your remote webview client) it gives me during linking phase:

Linking .pioenvs/esp32p4tablet/firmware.elf
/data/cache/platformio/packages/toolchain-riscv32-esp/bin/../lib/gcc/riscv32-esp-elf/14.2.0/../../../../riscv32-esp-elf/bin/ld: .pioenvs/esp32p4tablet/src/esphome/components/remote_webview/remote_webview.cpp.o: in function `esphome::remote_webview::RemoteWebView::decode_jpeg_tile_to_lcd_(short, short, unsigned char const*, unsigned int)':
/data/build/esp32p4tablet/src/esphome/components/remote_webview/remote_webview.cpp:294:(.text._ZN7esphome14remote_webview13RemoteWebView24decode_jpeg_tile_to_lcd_EssPKhj+0x2c): undefined reference to `JPEGDEC::openRAM(unsigned char*, int, int (*)(jpeg_draw_tag*))'
/data/cache/platformio/packages/toolchain-riscv32-esp/bin/../lib/gcc/riscv32-esp-elf/14.2.0/../../../../riscv32-esp-elf/bin/ld: /data/build/esp32p4tablet/src/esphome/components/remote_webview/remote_webview.cpp:295:(.text._ZN7esphome14remote_webview13RemoteWebView24decode_jpeg_tile_to_lcd_EssPKhj+0x38): undefined reference to `JPEGDEC::getLastError()'
/data/cache/platformio/packages/toolchain-riscv32-esp/bin/../lib/gcc/riscv32-esp-elf/14.2.0/../../../../riscv32-esp-elf/bin/ld: /data/build/esp32p4tablet/src/esphome/components/remote_webview/remote_webview.cpp:299:(.text._ZN7esphome14remote_webview13RemoteWebView24decode_jpeg_tile_to_lcd_EssPKhj+0x72): undefined reference to `JPEGDEC::setMaxOutputSize(int)'
/data/cache/platformio/packages/toolchain-riscv32-esp/bin/../lib/gcc/riscv32-esp-elf/14.2.0/../../../../riscv32-esp-elf/bin/ld: /data/build/esp32p4tablet/src/esphome/components/remote_webview/remote_webview.cpp:300:(.text._ZN7esphome14remote_webview13RemoteWebView24decode_jpeg_tile_to_lcd_EssPKhj+0x7e): undefined reference to `JPEGDEC::setPixelType(int)'
/data/cache/platformio/packages/toolchain-riscv32-esp/bin/../lib/gcc/riscv32-esp-elf/14.2.0/../../../../riscv32-esp-elf/bin/ld: /data/build/esp32p4tablet/src/esphome/components/remote_webview/remote_webview.cpp:302:(.text._ZN7esphome14remote_webview13RemoteWebView24decode_jpeg_tile_to_lcd_EssPKhj+0x8e): undefined reference to `JPEGDEC::decode(int, int, int)'
/data/cache/platformio/packages/toolchain-riscv32-esp/bin/../lib/gcc/riscv32-esp-elf/14.2.0/../../../../riscv32-esp-elf/bin/ld: /data/build/esp32p4tablet/src/esphome/components/remote_webview/remote_webview.cpp:303:(.text._ZN7esphome14remote_webview13RemoteWebView24decode_jpeg_tile_to_lcd_EssPKhj+0x9a): undefined reference to `JPEGDEC::getLastError()'
/data/cache/platformio/packages/toolchain-riscv32-esp/bin/../lib/gcc/riscv32-esp-elf/14.2.0/../../../../riscv32-esp-elf/bin/ld: /data/build/esp32p4tablet/src/esphome/components/remote_webview/remote_webview.cpp:304:(.text._ZN7esphome14remote_webview13RemoteWebView24decode_jpeg_tile_to_lcd_EssPKhj+0xc2): undefined reference to `JPEGDEC::close()'
/data/cache/platformio/packages/toolchain-riscv32-esp/bin/../lib/gcc/riscv32-esp-elf/14.2.0/../../../../riscv32-esp-elf/bin/ld: /data/build/esp32p4tablet/src/esphome/components/remote_webview/remote_webview.cpp:305:(.text._ZN7esphome14remote_webview13RemoteWebView24decode_jpeg_tile_to_lcd_EssPKhj+0xce): undefined reference to `JPEGDEC::close()'
/data/cache/platformio/packages/toolchain-riscv32-esp/bin/../lib/gcc/riscv32-esp-elf/14.2.0/../../../../riscv32-esp-elf/bin/ld: .pioenvs/esp32p4tablet/src/main.cpp.o: in function `std::_Function_handler<void (std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >), setup()::{lambda(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)#1}>::_M_invoke(std::_Any_data const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&)':
/config/esphome/esp32p4tablet.yaml:131:(.text._ZNSt17_Function_handlerIFvNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEEZ5setupvEUlS5_E_E9_M_invokeERKSt9_Any_dataOS5_+0x4c): undefined reference to `esphome::remote_webview::RemoteWebView::open_url(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'

Which I believe it is due to the usage of your modified jpeg library that you made specifically for S3 right? If I use the built-in one provided by ESP-IDF instead, do you know if your code would link into that too, or that would require modifications in the code?

Thanks

Just heads up: I made compilation+linking work with the original bitbank2/[email protected] library. Still fixing some stuff but will post the config and results soon. Cross fingers :slight_smile:

Yeah, the modified fork was only for the ESP32-S3. I’ve already fixed the build issues (you can try loading RWV from the original repo) and added more targets (including P4) to jpegdec-esphome.

Without SIMD, rendering will be slow, but the P4’s faster CPU can compensate.

Ok I will try pulling from your repo without changes to the config.

I also can see the homeassistant login screen but the quality is not good for this tablet.

Some of the messages I get from LOG output, just so you can see the performance:

15:32:15.379][D][Remove_WebView:279][rwv_decode]: frame 3940: tiles 38 (19668 bytes) - 30 ms
[15:32:15.454][D][Remove_WebView:279][rwv_decode]: frame 3941: tiles 38 (19631 bytes) - 33 ms
[15:32:15.481][D][Remove_WebView:279][rwv_decode]: frame 3942: tiles 39 (19942 bytes) - 31 ms
[15:32:15.529][D][Remove_WebView:279][rwv_decode]: frame 3943: tiles 38 (19488 bytes) - 32 ms
[15:32:15.557][D][esp-idf:000][sdio_read]: E (420171) sdmmc_io: sdmmc_io_rw_extended: sdmmc_send_cmd returned 0x109
[15:32:15.557][D][esp-idf:000][sdio_read]: E (420171) H_SDIO_DRV: sdio_read_task: Failed to read data - 265 4590 4590
[15:32:15.557][D][Remove_WebView:279][rwv_decode]: frame 3944: tiles 38 (19518 bytes) - 30 ms
[15:32:15.585][D][Remove_WebView:279][rwv_decode]: frame 3945: tiles 39 (19688 bytes) - 30 ms
[15:32:16.015][D][Remove_WebView:279][rwv_decode]: frame 3946: tiles 37 (19177 bytes) - 33 ms
[15:32:16.075][D][Remove_WebView:279][rwv_decode]: frame 3947: tiles 37 (19158 bytes) - 35 ms
[15:32:16.098][D][Remove_WebView:279][rwv_decode]: frame 3948: tiles 39 (19700 bytes) - 32 ms
[15:32:16.136][D][Remove_WebView:279][rwv_decode]: frame 3949: tiles 41 (20269 bytes) - 35 ms
[15:32:16.152][D][Remove_WebView:279][rwv_decode]: frame 3950: tiles 38 (18910 bytes) - 33 ms
[15:32:16.192][D][Remove_WebView:279][rwv_decode]: frame 3951: tiles 39 (19220 bytes) - 31 ms
[15:32:16.237][D][Remove_WebView:279][rwv_decode]: frame 3952: tiles 39 (19166 bytes) - 34 ms
[15:32:16.295][D][Remove_WebView:279][rwv_decode]: frame 3953: tiles 41 (19928 bytes) - 34 ms
[15:32:16.299][D][Remove_WebView:279][rwv_decode]: frame 3954: tiles 41 (19898 bytes) - 32 ms
[15:32:16.510][D][esp-idf:000][sdio_read]: E (421140) sdmmc_io: sdmmc_io_rw_extended: sdmmc_send_cmd returned 0x109
[15:32:16.516][D][esp-idf:000][sdio_read]: E (421140) H_SDIO_DRV: sdio_read_task: Failed to read data - 265 4729 4729
[15:32:16.516][D][esp-idf:000][sdio_write]: E (421144) sdmmc_req: handle_idle_state_events unhandled: 00000008 00000000
[15:32:16.533][D][Remove_WebView:279][rwv_decode]: frame 3955: tiles 1 (27870 bytes) - 195 ms
[15:32:16.553][D][Remove_WebView:279][rwv_decode]: frame 3956: tiles 43 (20011 bytes) - 29 ms
[15:32:17.118][D][Remove_WebView:279][rwv_decode]: frame 3957: tiles 43 (20343 bytes) - 31 ms
[15:32:17.171][D][Remove_WebView:279][rwv_decode]: frame 3958: tiles 44 (20396 bytes) - 30 ms
[15:32:17.225][D][Remove_WebView:279][rwv_decode]: frame 3959: tiles 43 (20202 bytes) - 31 ms
[15:32:17.262][D][Remove_WebView:279][rwv_decode]: frame 3960: tiles 43 (20056 bytes) - 33 ms
[15:32:17.303][D][Remove_WebView:279][rwv_decode]: frame 3961: tiles 45 (20393 bytes) - 31 ms
[15:32:17.352][D][Remove_WebView:279][rwv_decode]: frame 3962: tiles 46 (20693 bytes) - 34 ms
[15:32:17.392][D][Remove_WebView:279][rwv_decode]: frame 3963: tiles 42 (19387 bytes) - 31 ms
[15:32:17.424][D][Remove_WebView:279][rwv_decode]: frame 3964: tiles 46 (20942 bytes) - 32 ms
[15:32:17.461][D][Remove_WebView:279][rwv_decode]: frame 3965: tiles 44 (20088 bytes) - 32 ms
[15:32:17.508][D][Remove_WebView:279][rwv_decode]: frame 3966: tiles 43 (19433 bytes) - 29 ms
[15:32:17.549][D][Remove_WebView:279][rwv_decode]: frame 3967: tiles 39 (18075 bytes) - 29 ms
[15:32:17.597][D][Remove_WebView:279][rwv_decode]: frame 3968: tiles 40 (18521 bytes) - 28 ms
[15:32:17.628][D][Remove_WebView:279][rwv_decode]: frame 3969: tiles 41 (18909 bytes) - 28 ms
[15:32:17.671][D][Remove_WebView:279][rwv_decode]: frame 3970: tiles 40 (18809 bytes) - 29 ms
[15:32:17.691][D][Remove_WebView:279][rwv_decode]: frame 3971: tiles 40 (18848 bytes) - 28 ms
[15:32:17.966][D][Remove_WebView:279][rwv_decode]: frame 3972: tiles 40 (18908 bytes) - 28 ms
[15:32:17.989][D][Remove_WebView:279][rwv_decode]: frame 3973: tiles 40 (18816 bytes) - 29 ms
[15:32:18.034][D][Remove_WebView:279][rwv_decode]: frame 3974: tiles 42 (19482 bytes) - 28 ms
[15:32:18.186][D][Remove_WebView:279][rwv_decode]: frame 3975: tiles 44 (20395 bytes) - 29 ms
[15:32:18.593][D][Remove_WebView:279][rwv_decode]: frame 3976: tiles 43 (19959 bytes) - 31 ms
[15:32:18.624][D][Remove_WebView:279][rwv_decode]: frame 3977: tiles 43 (19941 bytes) - 31 ms
[15:32:18.676][D][Remove_WebView:279][rwv_decode]: frame 3978: tiles 42 (19584 bytes) - 30 ms
[15:32:18.726][D][Remove_WebView:279][rwv_decode]: frame 3979: tiles 44 (20324 bytes) - 32 ms

Set url: "self-test" in the YAML, and it’ll run the FPS test.

On it, will update shortly. Tweaking some of the display parameters, since it is another display.

Ok did the self-test setting, this is what I got (partially):

[15:48:18.736][C][Remove_WebView:056]:   max_bytes_per_msg: 61440
[15:48:18.745][D][Remove_WebView:279][rwv_decode]: frame 12287: tiles 1 (996 bytes) - 1 ms
[15:48:18.909][D][Remove_WebView:279][rwv_decode]: frame 12288: tiles 2 (1391 bytes) - 1 ms
[15:48:18.933][D][Remove_WebView:279][rwv_decode]: frame 12289: tiles 1 (514 bytes) - 1 ms
[15:48:19.067][D][Remove_WebView:279][rwv_decode]: frame 12290: tiles 1 (667 bytes) - 2 ms
[15:48:19.326][D][Remove_WebView:279][rwv_decode]: frame 12291: tiles 1 (723 bytes) - 3 ms
[15:48:19.328][D][Remove_WebView:279][rwv_decode]: frame 12292: tiles 1 (467 bytes) - 1 ms
[15:48:19.382][D][Remove_WebView:279][rwv_decode]: frame 12293: tiles 2 (1102 bytes) - 1 ms
[15:48:19.833][D][Remove_WebView:279][rwv_decode]: frame 12294: tiles 1 (1012 bytes) - 0 ms
[15:48:20.770][D][Remove_WebView:279][rwv_decode]: frame 12295: tiles 1 (1001 bytes) - 0 ms
[15:48:21.778][D][Remove_WebView:279][rwv_decode]: frame 12296: tiles 1 (992 bytes) - 0 ms
[15:48:21.978][D][Remove_WebView:279][rwv_decode]: frame 12297: tiles 1 (2047 bytes) - 4 ms
[15:48:21.994][D][Remove_WebView:279][rwv_decode]: frame 12298: tiles 4 (4061 bytes) - 6 ms
[15:48:22.200][D][Remove_WebView:279][rwv_decode]: frame 12299: tiles 4 (4328 bytes) - 6 ms
[15:48:22.250][D][Remove_WebView:279][rwv_decode]: frame 12300: tiles 4 (4428 bytes) - 6 ms
[15:48:22.301][D][Remove_WebView:279][rwv_decode]: frame 12301: tiles 4 (4445 bytes) - 6 ms
[15:48:22.506][D][Remove_WebView:279][rwv_decode]: frame 12302: tiles 1 (2401 bytes) - 4 ms
[15:48:22.903][D][Remove_WebView:279][rwv_decode]: frame 12303: tiles 2 (2108 bytes) - 2 ms
[15:48:23.833][D][Remove_WebView:279][rwv_decode]: frame 12304: tiles 2 (2128 bytes) - 2 ms
[15:48:24.752][D][Remove_WebView:279][rwv_decode]: frame 12305: tiles 2 (2131 bytes) - 2 ms
[15:48:25.887][D][Remove_WebView:279][rwv_decode]: frame 12306: tiles 2 (2118 bytes) - 2 ms
[15:48:26.793][D][Remove_WebView:279][rwv_decode]: frame 12307: tiles 2 (2124 bytes) - 2 ms
[15:48:27.831][D][Remove_WebView:279][rwv_decode]: frame 12308: tiles 2 (2131 bytes) - 2 ms
[15:48:28.853][D][esp-idf:000][sdio_read]: E (51108) sdmmc_io: sdmmc_io_rw_extended: sdmmc_send_cmd returned 0x109
[15:48:28.855][D][esp-idf:000][sdio_read]: E (51108) H_SDIO_DRV: sdio_read_task: Failed to read data - 265 4518 4518
[15:48:29.506][D][Remove_WebView:279][rwv_decode]: frame 12309: tiles 1 (25982 bytes) - 190 ms
[15:48:29.874][D][Remove_WebView:279][rwv_decode]: frame 12310: tiles 1 (654 bytes) - 0 ms
[15:48:29.927][D][Remove_WebView:279][rwv_decode]: frame 12311: tiles 1 (2544 bytes) - 4 ms
[15:48:30.115][D][Remove_WebView:279][rwv_decode]: frame 12312: tiles 1 (2787 bytes) - 4 ms
[15:48:30.147][D][Remove_WebView:279][rwv_decode]: frame 12313: tiles 1 (2829 bytes) - 4 ms
[15:48:30.319][D][Remove_WebView:279][rwv_decode]: frame 12314: tiles 1 (2847 bytes) - 4 ms
[15:48:30.372][D][Remove_WebView:279][rwv_decode]: frame 12315: tiles 1 (2848 bytes) - 4 ms
[15:48:30.797][D][Remove_WebView:279][rwv_decode]: frame 12316: tiles 1 (1291 bytes) - 1 ms
[15:48:31.918][D][Remove_WebView:279][rwv_decode]: frame 12317: tiles 1 (1293 bytes) - 1 ms
[15:48:31.957][D][Remove_WebView:279][rwv_decode]: frame 12318: tiles 1 (2232 bytes) - 3 ms
[15:48:32.179][D][Remove_WebView:279][rwv_decode]: frame 12319: tiles 1 (707 bytes) - 4 ms
[15:48:32.179][D][Remove_WebView:279][rwv_decode]: frame 12320: tiles 1 (556 bytes) - 2 ms
[15:48:32.330][D][Remove_WebView:279][rwv_decode]: frame 12321: tiles 1 (397 bytes) - 0 ms
[15:48:32.894][D][Remove_WebView:279][rwv_decode]: frame 12322: tiles 2 (2148 bytes) - 2 ms
[15:48:33.790][D][Remove_WebView:279][rwv_decode]: frame 12323: tiles 2 (2149 bytes) - 2 ms
[15:48:34.816][D][Remove_WebView:279][rwv_decode]: frame 12324: tiles 2 (2151 bytes) - 2 ms
[15:48:35.945][D][Remove_WebView:279][rwv_decode]: frame 12325: tiles 2 (2152 bytes) - 2 ms
[15:48:36.904][D][Remove_WebView:279][rwv_decode]: frame 12326: tiles 2 (2146 bytes) - 2 ms
[15:48:37.861][D][Remove_WebView:279][rwv_decode]: frame 12327: tiles 2 (2151 bytes) - 2 ms
[15:48:37.896][D][Remove_WebView:279][rwv_decode]: frame 12328: tiles 3 (4721 bytes) - 6 ms
[15:48:38.066][D][Remove_WebView:279][rwv_decode]: frame 12329: tiles 1 (2488 bytes) - 4 ms
[15:48:38.093][D][Remove_WebView:279][rwv_decode]: frame 12330: tiles 1 (2386 bytes) - 4 ms
[15:48:38.403][D][Remove_WebView:279][rwv_decode]: frame 12331: tiles 1 (2184 bytes) - 4 ms
[15:48:38.403][D][Remove_WebView:279][rwv_decode]: frame 12332: tiles 1 (2150 bytes) - 4 ms
[15:48:38.785][D][Remove_WebView:279][rwv_decode]: frame 12333: tiles 1 (651 bytes) - 0 ms
[15:48:39.806][D][Remove_WebView:279][rwv_decode]: frame 12334: tiles 1 (646 bytes) - 0 ms
[15:48:40.859][D][Remove_WebView:279][rwv_decode]: frame 12335: tiles 1 (952 bytes) - 0 ms
[15:48:41.746][D][Remove_WebView:279][rwv_decode]: frame 12336: tiles 1 (646 bytes) - 0 ms
[15:48:42.770][D][Remove_WebView:279][rwv_decode]: frame 12337: tiles 1 (941 bytes) - 0 ms
[15:48:43.805][D][Remove_WebView:279][rwv_decode]: frame 12338: tiles 1 (655 bytes) - 0 ms
[15:48:44.183][I][safe_mode:042]: Boot seems successful; resetting boot loop counter
[15:48:44.188][D][esp32.preferences:143]: Writing 1 items: 0 cached, 1 written, 0 failed

The graphics still look awful kind of like the previous picture.

This is my full esphome yaml so far:

esphome:
  name: esp32p4tablet
  friendly_name: esp32p4tablet
esp32:
  board: esp32-p4-evboard
  cpu_frequency: 400MHz
  flash_size: 16MB
  framework:
    type: esp-idf
    sdkconfig_options:
      COMPILER_OPTIMIZATION_SIZE: y
      CONFIG_SPIRAM_FETCH_INSTRUCTIONS: y
      CONFIG_SPIRAM_RODATA: y
    advanced:
      enable_idf_experimental_features: yes
    components:
      - name: "espressif/esp_websocket_client"
        ref: 1.5.0
      - name: "espressif/esp-dsp"
        ref: 1.7.0
      #- name: "bitbank2/jpegdec"
      #  ref: 1.6.2
      #  source: https://github.com/bitbank2/JPEGDEC
      - name: "bitbank2/jpegdec"
        source: https://github.com/strange-v/jpegdec-esphome
api:
  encryption:
    key: <secret>
ota:
  - platform: esphome
    password: <secret>
wifi:
  networks:
    - ssid: <secret>
      password: <secret>
  ap:
    ssid: "Esp32P4Tablet Fallback Hotspot"
    password: <secret>
captive_portal:
logger:
  hardware_uart: USB_SERIAL_JTAG
  level: DEBUG
  logs:
    lvgl: INFO
    display: INFO
    app: DEBUG
external_components:  
  - source: github://willumpie82/esphome@dev
    components: [mipi_dsi]
  - source: github://kvj/esphome@jd9365_gsl3680
    refresh: 0s
    components: [gsl3680]
  - source: github://esphome/[email protected]
    components: [i2c]
  - source: github://strange-v/RemoteWebViewClient@main
    refresh: 0s
    components: [ remote_webview ]
  #- source:
  #    type: git
  #    url: https://github.com/supremelucifer/RemoteWebViewClient
  #    ref: main
  #  components: [ remote_webview ]
esp_ldo:
  - channel: 3
    voltage: 2.5V
esp32_hosted:
  variant: ESP32C6
  reset_pin: 54
  cmd_pin: 19
  clk_pin: 18
  d0_pin: 14
  d1_pin: 15
  d2_pin: 16
  d3_pin: 17
  active_high: true
psram:
  speed: 200MHz
switch:
  - id: backlight_
    platform: gpio
    pin: GPIO23
    restore_mode: ALWAYS_ON
color:
  - id: my_red
    red: 100%
    green: 0%
    blue: 0%
# -------------------------------
# LVGL Display
# -------------------------------
display:
  - platform: mipi_dsi
    model: JC8012P4A1
    id: my_display
    reset_pin: GPIO27
    show_test_card: False
    update_interval: never
    auto_clear_enabled: False
    color_order: RGB
    invert_colors: False
    dimensions:
      width: 800
      height: 1280

i2c:
  - id: i2c_bus
    sda: GPIO7  
    scl: GPIO8  
    #scan: true
    frequency: 400kHz 
#lvgl:
#  buffer_size: 100%
#  byte_order: little_endian   
#  touchscreens:
#    - touchscreen_id: touchscreen_
touchscreen:
  - platform: gsl3680
    id: touchscreen_
    reset_pin: GPIO22
    interrupt_pin: GPIO21
    transform:
      swap_xy: false
      mirror_x: false
      mirror_y: false
    i2c_id: i2c_bus
remote_webview:
  id: rwv
  display_id: my_display
  touchscreen_id: touchscreen_
  device_id: esp32p4tablet
  server: 192.168.0.208:8081
  #url: http://192.168.0.43:8123/dashboard-home/home
  url: "self-test"
  full_frame_tile_count: 1
  max_bytes_per_msg: 61440
  jpeg_quality: 85
text:
  - platform: template
    id: rwv_url
    name: "URL"
    optimistic: true
    restore_value: false
    mode: TEXT
    min_length: 1
    set_action:
      - lambda: |-
          if (!id(rwv).open_url(std::string(x.c_str()))) {
            id(rwv).set_url(std::string(x.c_str()));
            ESP_LOGI("remote_webview", "URL queued (not connected): %s", x.c_str());
          }

UPD: added esphome config

Final result shown in the tablet:

FPS
Partial: 200 (5ms)
Full: 5 (190ms)