A live Victron energy-flow dashboard on the ESP32-2432S028R — the cheap 2.8" touch TFT board widely known as the "Cheap Yellow Display" (CYD).
It reproduces the Victron tile view — Grid · Inverter · AC Loads · Battery · DC Loads · Solar — on a board that costs ~€10.
This doesn't replace your GX device — a Cerbo GX / Venus OS (or a Venus-on-RPi) is still the brain of the system. It replaces the display: a cheap, always-on wall dashboard instead of a ~€350 GX Touch, a Raspberry Pi + monitor, or keeping VictronConnect open on your phone.
| VictronCYD (VRM cloud) | VictronCYD_Modbus (realtime) ⭐ | |
|---|---|---|
| Data source | Victron VRM API (cloud) | GX device on your LAN via Modbus TCP |
| Refresh | ~20 s (VRM logs every ~60 s) | ~2 s, true realtime |
| Needs internet | Yes | No (local network only) |
| Needs a token | Yes (VRM Personal Access Token) | No |
| Library deps | TFT_eSPI, ArduinoJson, StreamUtils | TFT_eSPI only |
Use Modbus if your GX is on the same network (instant, offline-capable). Use VRM cloud if you want to watch a site you're not on the same network as.
- Live Grid / AC Loads / DC Loads / Solar (PV) power and Inverter state
- Battery: SoC %, charge bar, state, voltage / current / power, temperature
- Energy-flow connectors, Victron-style dark theme, NTP clock, WiFi indicator
- Credentials kept out of git via
secrets.h
- ESP32-2432S028R (CYD) — ILI9341 240×320 TFT
- Micro-USB cable, 2.4 GHz WiFi (the ESP32 has no 5 GHz)
- For the Modbus version: a Victron GX device (Cerbo GX / Venus OS) reachable on your LAN
Both versions need TFT_eSPI set up for the CYD. Put this in User_Setup.h (or a custom
User_Setups/Setup_CYD.h selected in User_Setup_Select.h):
#define ILI9341_2_DRIVER // if the screen stays blank, try #define ILI9341_DRIVER
#define TFT_WIDTH 240
#define TFT_HEIGHT 320
#define TFT_MISO 12
#define TFT_MOSI 13
#define TFT_SCLK 14
#define TFT_CS 15
#define TFT_DC 2
#define TFT_RST -1
#define TFT_BL 21
#define TFT_BACKLIGHT_ON HIGH
#define LOAD_GLCD
#define LOAD_FONT2
#define LOAD_FONT4
#define LOAD_FONT6
#define LOAD_FONT7
#define LOAD_FONT8
#define LOAD_GFXFF
#define SMOOTH_FONT
#define SPI_FREQUENCY 55000000
#define SPI_READ_FREQUENCY 20000000
#define SPI_TOUCH_FREQUENCY 2500000Colors inverted / black shows as white? Some CYD panels are inverted. Both sketches call
tft.invertDisplay(true). If your colors look wrong, change it tofalse.
- On the GX: Settings → Services → Modbus TCP = ON. Note the GX IP address (Settings → Ethernet/WiFi).
cd VictronCYD_Modbus && cp secrets.example.h secrets.h, then set WiFi,SECRET_GX_IP,SECRET_SITE_NAME.- Flash. Done — updates every ~2 s, no token, works offline.
GX Modbus register map used (function 3, read holding registers):
| Value | Unit id | Register | Scale |
|---|---|---|---|
| AC Loads (W) | 100 | 817 | ×1 |
| Grid (W) | 100 | 820 | ×1, signed |
| Battery voltage | 100 | 840 | ÷10 |
| Battery current | 100 | 841 | ÷10, signed |
| Battery power (W) | 100 | 842 | ×1, signed |
| Battery SoC (%) | 100 | 843 | ×1 |
| Battery state | 100 | 844 | 0 idle / 1 charging / 2 discharging |
| PV power (W) | 100 | 850 | ×1 |
| DC system (W) | 100 | 860 | ×1, signed |
| VE.Bus state | 228 | 31 | enum |
| Battery temperature | 225 | 262 | ÷10 |
Unit ids can differ per installation (especially 225/228). If battery temp or inverter state read wrong, check your GX's CCGX Modbus-TCP register list for the right unit ids.
- VRM Personal Access Token — VRM Portal → Preferences → Integrations → Access tokens.
- Installation id (
idSite) — in the VRM URL, or:GET https://vrmapi.victronenergy.com/v2/users/{idUser}/installationswith headerx-authorization: Token <token>. cd VictronCYD && cp secrets.example.h secrets.h, fill in the values.
Arduino IDE: open the .ino, select ESP32 Dev Module, Upload.
arduino-cli:
arduino-cli compile --fqbn esp32:esp32:esp32 VictronCYD_Modbus
arduino-cli upload --fqbn esp32:esp32:esp32 -p COM3 VictronCYD_Modbus # your portReading the VRM /diagnostics endpoint on an ESP32 is the tricky bit — ~129 KB of JSON over
HTTPS. Three things are needed together:
http.useHTTP10(true)— otherwise the response is chunked and ArduinoJson reads the first hex chunk-size as a JSON number, "succeeds", and returns 0 records.- ArduinoJson
Filter— the full body won't fit in RAM; the filter keeps only the ~12 codes needed while streaming. - A blocking
Streamwrapper — the defaultStream::read()is non-blocking and returns -1 between TLS records, so ArduinoJson stops at the first ~16 KB. TheBlockingStreamwaits for more bytes until the connection truly closes.
Auth header: x-authorization: Token <PAT> (Bearer returns 401). VRM diagnostics codes:
g1 grid, a1 AC loads, dc DC, bv/bc/bp battery V/A/W, bs SoC, bT temp,
bst battery state, ss system state, PVP PV.
The Modbus client keeps one TCP connection open. To avoid the buffer desync that can freeze the
display, mbRead() drains stale bytes before each request, validates the response transaction id,
and closes the socket on any error so the next read reconnects and re-syncs automatically.
For an always-on display there's also a two-level safety net: a hardware task watchdog (reboots if the loop ever hangs >30 s) and a soft reboot if no valid data arrives for 2 minutes.
Built for the ESP32-2432S028R "Cheap Yellow Display". Uses the Victron VRM API and the GX Modbus-TCP interface. Not affiliated with Victron Energy.
MIT — see LICENSE.
