Most espresso machines don’t tell you what they’re doing. The Lelit Mara X is the rare exception: there’s a diagnostic UART on the service connector that emits the boiler temperature, the target setpoint, the heat exchanger temperature, the pump state and the heater state every 400 ms in plain ASCII. Read-only - you can listen but you can’t talk back. That’s actually perfect; you get the data without any way to brick the machine.
The frame looks like this:
C1.06,116,124,093,0840,1,0\n
Firmware version, steam boiler temperature, target temperature, HX temperature, an internal timer, heater on/off, pump on/off. There’s one small gotcha: the serial line is inverted relative to standard TTL, so a regular USB-UART cable shows nothing useful until you flip the pin polarity. ESPHome happens to support inverted: true directly on the GPIO config, which saved me a level shifter.
The hardware is one of those all-in-one ESP32-S3 dev boards with a 3.5″ 480×320 QSPI display soldered on top (the JC3248W535, sold under various unbranded “ESP32 S3 LCD Development Board” listings). One I²C touch controller, octal PSRAM at 80 MHz, and enough internal SRAM to keep LVGL’s draw buffer out of PSRAM. I tapped two wires onto the Mara X’s service connector for TX/RX, ran 5 V from the same connector to power the board, and the firmware does the rest.
What ESPHome gives you for free
ESPHome is plain awesome for this kind of thing. The first working version was a YAML file: declare the UART, declare three template sensors, write a small lambda that parses each line and publishes the values, and you’re done. Home Assistant picks the device up automatically over the ESPHome API, and now I can set a phone notification to fire when the brew group is up to temperature:
automation:
- alias: "Mara X ready"
trigger:
platform: numeric_state
entity_id: sensor.hx_temperature
above: 88
for: "00:00:30"
action:
service: notify.mobile_app_your_phone
data:
message: "Brew group is up to temperature."
OTA updates over WiFi, captive portal fallback, time sync from Home Assistant - all of those are one line of YAML each. There’s nothing magical here; it’s just that someone has already done the gluing.
The UI is the bit that needs real code
LVGL via ESPHome covers most widgets declaratively, but I wanted a temperature chart with brew-window and recovery shading drawn behind the trace, plus custom tick labels that reformat themselves when the chart auto-switches between a 50 s high-res window (while brewing) and a 15 min low-res window (otherwise). That’s where the includes/ directory comes in - a couple of C++ headers that hook the chart’s draw events and paint on the layer.
The painful part of that, in hindsight, was LVGL 9. ESPHome 2026.4 transitioned to LVGL 9, which rewrote the per-part draw-event model (no more DRAW_PART_BEGIN/END with a draw_part_dsc) and removed lv_chart_set_axis_tick entirely. The chart drawing pattern in v9 is to register on LV_EVENT_DRAW_MAIN_BEGIN, pull a lv_layer_t* via lv_event_get_layer(e), and lv_draw_rect straight into it. Tick labels go through lv_draw_label calls of your own. More code than v8, but more honest.
Try it
If you’ve got a Mara X and one of these S3 boards, the firmware is on the project page with a one-click web flasher (Chrome or Edge - WebSerial isn’t a thing in Safari or Firefox yet). Plug the board into USB, hit Install, point it at your WiFi via Improv, and you’re done.
Source: github.com/elsbrock/esphome-marax · install: elsbrock.github.io/esphome-marax
Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.