Skip to content

Repository files navigation

ESPHome Print Counter Display

A desk-side batch counter for 3D printing. An ESP32 with a 2.8" ILI9341 TFT shows how many parts are still missing from your print run — at a glance, from across the workshop. The count lives in Home Assistant, so it survives power loss and can be updated from your phone, an NFC tag, or automatically when your printer finishes a job.

Printing fifty of the same part and losing track of how many you already have? That was exactly the problem this solves.

The finished counter mounted on a workbench shelf, showing 33 parts still to print out of a batch of 50, with the printer's live status underneath

The label, the caption and the batch size are all yours to set — this one is counting cones in German.

Everything you need is here: the ESPHome configuration, the Home Assistant helpers and automations, and STL files for a printed case — because a bare PCB taped to the workbench gets old fast.

 ┌──────────────────────────────────────────┐
 │           P R I N T   J O B S            │  ← your own label
 ├──────────────────────────────────────────┤
 │                                          │
 │                  1 8                     │  ← how many are LEFT
 │                                          │
 │             STILL TO PRINT               │
 │                                          │
 │   ████████████████████░░░░░░░░░░░░░░░░   │  ← progress bar
 │                                          │
 │            32 / 50  -  64%               │
 └──────────────────────────────────────────┘
              320 × 240, landscape

When the batch is finished, the screen shows the total and ALL DONE!

Runs in greyscale on a plain ESP32 — a deliberate trade-off, explained under The memory problem. Swap in a WROVER module and it renders in full colour.


Contents


Why the count lives in Home Assistant

The ESP32 stores nothing and decides nothing. It subscribes to one Home Assistant helper (input_number) over the encrypted ESPHome native API and renders it.

That is a deliberate design choice:

Power-loss safe Home Assistant persists the value. Unplug the ESP32, pull the printer's power — the number is still right.
Update from anywhere Phone, dashboard, voice assistant, NFC tag, or an automation triggered by the printer itself.
No flash wear No writes to ESP32 flash on every single count.
Trivially extendable Notifications, statistics, history graphs — all the Home Assistant machinery works on the value for free.
Multiple displays Flash the same config to a second ESP32 and both show the same number.

If you'd rather not depend on Home Assistant, see Standalone variants.


What you need

Part Notes
ESP32 dev board Any ESP32-WROOM board (DevKit v1, NodeMCU-32S, …). ~5 €. A WROVER with PSRAM (~7 €) buys you full colour instead of greyscale — see The memory problem.
2.8" SPI TFT, 240×320, ILI9341 Sold as KMRTM28028-SPI, MSP2807, or plain "2.8 inch SPI TFT ILI9341". ~8 €
Jumper wires Female-female, 8 pieces
USB power supply Any 5 V phone charger
Filament ~40 g for the case, any material you like

The touch controller (XPT2046) and the SD card slot on these modules are not used. Boards without touch work fine, and you do not need an SD card.

Electronics come to roughly 13 €.


The case

Three printed parts hold the display and the ESP32 in a compact housing that stands on the bench or screws to a wall.

Exploded view of the case: front bezel, display module, PCB, back shell and side cap

File Part
case/front-bezel.stl Front frame — the display drops in from behind
case/back-shell.stl Back shell holding the ESP32, with mounting tabs
case/side-cap.stl Side cap with the USB cutout

Printing

These are ordinary enclosure parts with no overhangs that need help — orient them as they come out of the slicer and they print without supports.

  • Material: PLA or PETG. PETG if the display sits near the printer and might see some warmth.
  • Layer height: 0.2 mm is plenty. The bezel looks a little crisper at 0.16 mm.
  • Infill: 15–20 %
  • Perimeters: 3, so the screw tabs on the back shell hold properly

The parts clip and screw together; the exploded view above shows the order of assembly. Print the bezel face-down for the cleanest visible surface.

Printed it and tweaked the settings? Pull requests with better print profiles are welcome — see Contributing.


Wiring

Eight connections. Touch and SD stay unconnected.

# Display pin ESP32 pin Purpose
1 VCC 5V (or VIN) Power
2 GND GND Ground
3 CS GPIO5 Chip select
4 RESET / RST GPIO21 Reset
5 DC / RS GPIO22 Data/command
6 SDI (MOSI) GPIO23 Data to display
7 SCK GPIO18 Clock
8 LED 3V3 Backlight, permanently on

Leave unconnected: SDO/MISO and all five T_ pins.

A few things worth knowing before you plug anything in:

Power the ESP32 down while wiring. Plug everything, then switch on.

VCC goes to 5V, not 3V3. These modules carry their own 3.3 V regulator (the small component right next to the VCC pin). If yours genuinely doesn't have one, use 3V3 instead — 5 V without a regulator will damage the panel. Data lines are 3.3 V logic either way.

LED to 3V3, not to a GPIO. Some batches draw more current than a single ESP32 pin should source. Tied to 3V3 the backlight is simply always on, which is what you want here.

DC is the pin people get wrong. On many boards it is labelled RS. If the panel later stays white without a memory error in the log, check this line first.

Why GPIO 5 / 21 / 22? Many tutorials use GPIO 15 / 2 / 4 — all three are ESP32 strapping pins, and GPIO 2 and 15 in particular can stop the board from booting if a peripheral holds them at the wrong level at power-up. GPIO 21 and 22 are not strapping pins at all. GPIO 5 is one (it selects the SDIO timing) but must be high at boot, which is exactly the idle state of a chip-select line — that is why it is the conventional VSPI CS pin and works reliably here. ESPHome prints a generic warning about it; you can ignore that, or move pin_cs to GPIO 27 for a quiet log. Change pins at the top of print-counter.yaml, not in the display block.


Installation

1. Create the Home Assistant helper

Settings → Devices & Services → Helpers → Create helper → Number

Field Value
Name Print counter
Minimum 0
Maximum 50 (your batch size)
Step size 1
Display mode Input field

This creates input_number.print_counter. A YAML equivalent is in homeassistant/helpers.yaml.

One thing to get right: do not set an initial: value. With it, the count resets to zero on every Home Assistant restart. Without it, Home Assistant restores the last value — which is the whole point.

2. Configure the device

git clone https://github.com/LLAlexX78/esphome-print-counter.git
cd esphome-print-counter
cp secrets.yaml.example secrets.yaml

Edit secrets.yaml with your WiFi credentials and an API encryption key (generate one at https://esphome.io/components/api.html).

Then adjust the substitutions: block at the top of print-counter.yaml:

substitutions:
  title: "PRINT JOBS"                        # header text
  caption: "STILL TO PRINT"                  # caption under the number
  done_text: "ALL DONE!"                     # shown when the batch is complete
  target: "50"                               # batch size
  ha_entity: input_number.print_counter      # your helper

3. Flash

Using the ESPHome CLI:

pip install esphome
esphome run print-counter.yaml

Or with the ESPHome Device Builder add-on in Home Assistant: create a new device, paste the contents of print-counter.yaml into its editor, then Install → Manual download → Modern format. Flash the resulting .bin at https://web.esphome.io in Chrome or Edge (Firefox has no WebSerial).

The first build takes 5–15 minutes — ESPHome downloads the whole toolchain and the fonts. Every update after the first one goes over the air; you never need the USB cable again.

4. Adopt in Home Assistant

The device appears automatically under Settings → Devices & Services → ESPHome. Confirm, and the display starts showing the value within a second.


Counting up automatically

The helper can be incremented by anything Home Assistant can trigger on. Ready-made examples are in homeassistant/automation-examples.yaml:

  • Klipper / Moonraker (Creality K2, Voron, …) — triggers on print state complete
  • OctoPrint — triggers when the job ends at 100 %
  • Bambu Lab — triggers on print status finish
  • NFC tag — stick a tag next to the build plate, tap it when you remove the part
  • Batch-complete notification

Or just tap +1 on your phone: homeassistant/dashboard-card.yaml is a ready Lovelace card with +1 / -1 / Reset buttons.

A trap worth avoiding: it is tempting to guard against double-counting by comparing the filename of the finished print with the last one counted. Don't. In a production run you print the same file fifty times — the names are identical, the condition never matches, and nothing gets counted after the first part. Guard on the previous state instead (only count if the printer was actually printing before), which also catches the restart case where the sensor jumps from unknown straight to complete.


Customising the display

Everything visual is in print-counter.yaml:

  • Colours — the color: block. Given as greyscale by default; the original colour values are noted in a comment for PSRAM boards.
  • Fonts — the font: block. Fonts are downloaded from Google Fonts at compile time, so the machine running ESPHome needs internet once. To build fully offline, drop a .ttf next to the YAML and use file: "MyFont.ttf".
  • Layout — the lambda: in the display: block. Coordinates are plain pixels on a 320 × 240 canvas.
  • Portrait mode — set rotation: 0 and swap the W/H constants at the top of the lambda.

Non-ASCII characters

The glyphs: strings list every character the font can render — German umlauts are already included. If your header uses other characters (accents, Cyrillic, emoji), add them to glyphs: or compilation will fail with an "invalid glyph" error.

Different display controller

Got an ST7789 instead? Change model: ILI9341 to model: ST7789V in the display: block. The ili9xxx component supports a long list of controllers.


The memory problem

Worth understanding before you change anything about the display, because it shapes the whole configuration.

A 320 × 240 panel at 16 bits per pixel needs a 150 KB frame buffer, allocated in one contiguous block. A plain ESP32 has 320 KB of RAM in total; once the WiFi stack, lwIP, the encrypted API and ESPHome itself have taken their share, roughly 170 KB of fragmented heap remains. The allocation fails almost every time. The driver then reports:

[C][ili9xxx]:   => Failed to init Memory: YES!
[E][component]:   display is marked FAILED: unspecified

and the panel stays white — which looks exactly like a wiring fault, but isn't. If you take one thing from this README, let it be this: read the log before you touch a single cable.

This config solves it with color_palette: GRAYSCALE, halving the buffer to 77 KB, plus sram1_as_iram: true and no captive_portal: to free a little more. The result runs reliably on a bog-standard ESP32 — in greyscale. That is also why the colours are defined as true greyscale values: converted automatically, orange and green end up the same brightness and become indistinguishable.

Want full colour? Use an ESP32-WROVER module (~7 €, 4 MB PSRAM), then delete the color_palette: line and restore the colour values noted in the color: block. Nothing else changes.

The config also reports free heap to Home Assistant as a Free memory sensor. If it settles below ~20 KB, you are close to the edge.


Standalone variants

Prefer no Home Assistant dependency? Two straightforward modifications:

  • Physical button + local storage — replace the homeassistant sensor with a binary_sensor on a GPIO and a globals: value with restore_value: true. The count then lives in ESP32 flash.
  • Touch buttons — the module's XPT2046 is supported by ESPHome's xpt2046 touchscreen component; wire T_CS to a free GPIO and add touchscreen: plus binary_sensor touch areas.

Pull requests adding these as separate example configs are welcome.


Troubleshooting

Read the logs first. ESPHome dashboard → your device → ⋮ → Logs. It works over WiFi, no USB needed, and it will usually tell you the answer outright.

Symptom Likely cause
White screen, backlight on, log says Failed to init Memory / display is marked FAILED Not your wiring — out of RAM. See The memory problem. This is the single most common failure on a plain ESP32.
White / blank screen, no such log line Wiring. Re-check CS, DC, RESET. DC is the one people mix up most — it is labelled RS on many boards.
No backlight at all LED pin not connected to 3V3.
Colours inverted Flip invert_colors: in the display: block to true. Since ESPHome 2026.7 this option is mandatory, so it is already present.
Mirrored or sideways Change rotation: to 0, 180, or 270.
"Waiting for Home Assistant …" The device isn't adopted yet, or ha_entity: doesn't match your helper. Check the exact ID under Developer tools → States.
Board won't boot after wiring You used a strapping pin (GPIO 0, 2, 12, 15). Use the pinout in this README.
Compile error about glyphs A character in your title: / caption: isn't in the glyphs: string. Add it.
'invert_colors' is a required option ESPHome 2026.7 made it mandatory. Add invert_colors: false to the display: block.
Count resets on every HA restart Your helper has an initial: value. Remove it.
Number stuck at maximum The helper's max: is lower than your target:. They must match.

Security notes

This repository is deliberately free of anything environment-specific:

  • No credentials, tokens, or API keys — secrets.yaml is gitignored; only secrets.yaml.example with placeholders is tracked.
  • No Home Assistant URLs, internal hostnames, IP addresses, or network topology.
  • No database files, logs, or .storage/ content.
  • No camera originals — phone photos carry EXIF metadata (device, timestamp, often GPS coordinates), so *.heic and *.dng are gitignored. Export to JPG/PNG without metadata before adding images.
  • Entity IDs shown are generic defaults, not anyone's real setup.

If you fork this, keep it that way: never commit your own secrets.yaml. The .gitignore covers it, but check git status before your first push anyway.


Contributing

Issues and pull requests are very welcome — especially:

  • Print profiles and photos of your build
  • Case variants: wall mount, magnetic back, different display sizes
  • Configs for other display controllers or ESP32 variants
  • Automation examples for more printer ecosystems
  • Layout variants (multiple batches on one screen, ETA, filament estimate)
  • Translations of this README

No contribution is too small. A typo fix is a contribution.


License

MIT — see LICENSE. Do whatever you like with it, including the case files.


Keywords

ESPHome · ESP32 · ILI9341 · 2.8 inch TFT · SPI display · Home Assistant · 3D printing · batch counter · print counter · remaining prints · 3D printed case · STL · KMRTM28028 · MSP2807 · Klipper · Moonraker · OctoPrint · Creality K2 · Bambu Lab · smart home · DIY · maker

About

ESP32 + 2.8 inch ILI9341 TFT batch counter for 3D printing. Shows how many parts are left in your print run; the count lives in Home Assistant so it survives power loss. ESPHome config, printable case (STL), fully documented in English and German.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors