Skip to content

E-Ink Display | ESPHome-TRMNL 7.5"

Files

Printables Thingiverse Creality Cloud MakerWorld github


Critical changes

Critical changes have been made in release 2.0.0. The firmware, blueprint, and integration have been updated.

Description

This is an E-Ink display running on ESPHome firmware, designed to display information from your Inker server and managed by Home Assistant. The project was not created by the "ESPHome" or "TRMNL" team and is not affiliated with them.

flowchart LR
    HA["Home <br> Assistant"] -- JSON API --> Inker["Inker"]
    Inker <-- HTTP requests --> ESPH["ESPHome <br> E-Ink Display"]
    Inker -- PNG --> ESPH["ESPHome <br> E-Ink Display"]
    HA <--> Int["Custom Integration"]
    HA --> BP["blueprint"]
    BP --> Int
    Int <-- HTTP requests --> ESPH

img

INKER - create interfaces without a single line of code

Features:

  • Built for Smart Home: Display data on the screen and switch pages using "Home Assistant" automations.
  • Customizable Refresh Rate: Flexibly change the screen refresh interval directly from "Home Assistant", adapting it to current tasks, scenarios, or the time of day.
  • Telemetry: Get real-time display status data in "Home Assistant".
  • Energy Efficiency: The screen pauses updates when you are not at home or in the room.
  • Extensibility: Thanks to "ESPHome", the device supports connecting a huge number of additional sensors.

Case:

  • Compactness: Minimalist case (175.6 x 116.6 x 11.25 mm) with thin bezels, focusing all attention on the screen.
  • Ergonomics: Convenient buttons are located in plain sight—no more fumbling for them on the back panel.
  • Three Mounting Options: kickstand (desktop), hanger (wall-mounted), magnets (wall-mounted).
  • Simple Assembly: The case is assembled with reliable latches—no extra screws or complex mounting required.

Boosty & Multi-Payment Progress Widget
0 $ of 100 $ raised
Development of a 13-inch version
0 $ of 100 $ raised
Developing a 7.5-inch version with modern hardware

Components

Image Component Link Qty Note
ePaper 7.5 ePaper 7.5 bp 7.5" ePaper Display Aliexpress 1 This is a 3-color B/W/R display, but only B/W will work due to RAM limitations.
Supported displays
Max dimensions: 171.20 x 112.20 x 1.50 mm
USB C plug USB C plug Aliexpress 1
USB C socket USB C socket Aliexpress 1
Slide switches Slide switch TLZWLA SS12F15VG4 Aliexpress 1
img Tactile button 6X6X10 Aliexpress 1
img Waveshare E-Paper ESP32 Driver Board (V3) Aliexpress 1
Li-ion Battery 1 Max thickness 7.4 mm. Updating once per hour, a 1400 mAh (5.18 Wh) battery lasts about a month.
img TP4056 charging module Aliexpress 1
100 kOhm Resistor 2
img 8x3mm Neodymium magnet 5 5 magnets securely hold the 150g display on a metal door.
T-7000/B-7000 Glue 1

Estimated cost per screen (including 20% VAT and shipping): ~$50.

Power Consumption

img

Measured with FNIRSI FNAC-28 raw

Assembly

  1. Remove the red PWR LED from the "Waveshare E-Paper ESP32 Driver Board" to save energy.
  2. To make the ESP32 driver board thinner, desolder the pins on its bottom side. (Tip: lift and remove the plastic spacer between pins with a screwdriver first).

  3. Set switch #1 on the ESP32 to position A.

    img

  4. Set switch #2 on the ESP32 to the "ON" position. This switch controls power to the UART module via USB. When the module is not in use, you can manually turn it off to save energy (note: if switch 2 is "OFF", firmware cannot be uploaded).

  5. Assemble all housing parts in the specified order.

    img

  6. Solder the components according to the diagram.

    img
    Project Link

  7. Apply T-7000/B-7000 glue around the magnets to prevent the case from sliding on smooth metal surfaces.

    img


Firmware Installation

You can use the button below to install the pre-compiled firmware onto the "Waveshare E-Paper ESP32 Driver Board (V3)" via USB directly from your browser, or download the configuration file and install the firmware via the ESPHome Device Builder.

  1. Connect the ESP to a USB port. You may need to press and hold the "boot" button while simultaneously pressing the "reset" button.
  2. Click "Connect" and select the correct COM port.
  3. Wait about 2 minutes for the firmware to install.
  4. After installation, you can configure the Wi-Fi.

Changing the URLs for "HA" and "Inker"

The default URLs for "HA" ( http://homeassistant.local:8123 ) and "Inker" ( http://homeassistant.local:8124 ) can be changed in the ESP32 web interface (default login and password for the web interface is "esphome-trmnl"). Press the button to wake up the board, wait one second, then hold the button for 5 seconds to enter setup mode. If the board is connected to Wi-Fi, it will display a QR code with a link to the web interface; if not connected, it will show a QR code with a link to the captive portal.

  1. Install "ESPHome Device Builder" Откройте свой экземпляр  Home Assistant и отобразите панель управления дополнения.

  2. Download the configuration file esphome-trmnl.yaml and change the "substitutions" values to your own.

  3. Add the following to your ESPHome "secrets.yaml":

    wifi_ssid: "Your_wifi_network_name"
    wifi_password: "Your_wifi_network_password"
    trmnl_wifi_ap_password: "Password_for_display_access_point"
    trmnl_web_username: "Username_for_display_web_interface_access"
    trmnl_web_password: "Password_for_display_web_interface_access"
    trmnl_ota_password: "Password_for_OTA_updates"
    

  4. Upload the edited yaml to "ESPHome" and install the firmware on the ESP32.

Installing Inker

You can install the official "Inker" via Docker and change the "Inker" server URL in the ESP32 web interface, or install my fork directly into HA and change nothing.

Important Note

This guide assumes you already have Home Assistant installed. You will also need Docker with the Docker Compose plugin. Instructions are based on Debian OS.

  1. Create a directory

    Create a directory (e.g., ./inker) to store the docker-compose.yml file and enter it.
    mkdir ./inker
    cd ./inker
    

  2. Create "docker-compose.yml" by running the following commands:

    Create docker-compose.yml
    cat <<EOF > docker-compose.yml
    services:
      inker:
        image: wojooo/inker:latest
        container_name: inker
        restart: unless-stopped
        ports:
          - "80:80"
        volumes:
          - postgres_data:/var/lib/postgresql/17/main
          - redis_data:/data
          - uploads_data:/app/uploads
        environment:
          TZ: UTC
          ADMIN_PIN: "1111"
    volumes:
      postgres_data:
      redis_data:
      uploads_data:
    EOF
    
  3. Start the container

Run the following command in the directory you created in step 1 (which should now contain your custom "docker-compose.yml" file) to run "Inker" as a background service:

Start the container
docker compose up -d

Updating "Inker"

When a new version is released, review the release notes for any breaking changes. After that, you can update and restart the application by running the following commands in the directory where your docker-compose.yml file is located:

Update and restart Inker.
docker compose pull && docker compose up -d
To free up disk space, old obsolete container images can be removed with the following command:
Remove unused Docker images.
docker image prune

  • Install the application in "HA" Open your Home Assistant instance and show the dashboard of an app.

Security

It is highly recommended to keep "Inker" accessible exclusively within your local network and not to forward ports to the outside world. If remote access is absolutely necessary, use only trusted and secure channels.

Home Assistant Configuration

  1. Install the "ESPHome-TRMNL" integration

    This method allows you to receive updates directly on the HACS main page. If HACS isn't already installed, download it by following the instructions on the hacs.xyz/docs/setup/download/ page.

    • In HACS, go to the menu in the upper right corner, then select "Custom Repositories."
    • Then add the repository https://github.com/kfattum/esphome-trmnl and select "Integration." After that, click "Add."
    • After adding, click the "Cancel" button next to the "Add" button.
    • Download the integration by clicking Open your Home Assistant instance and open a repository inside the Home Assistant Community Store. or search for ESPHome-TRMNL.
    • Restart Home Assistant for the system to recognize the new files.
    • Add the integration to HA by clicking Open your Home Assistant instance and start setting up a new integration. or go to Settings -> Devices & Services -> Add Integration, search for "ESPHome-TRMNL" -> Add
    • When you turn on the screen, it should automatically appear in the "ESPHome-TRMNL" integration.

    This method requires manually copying files and monitoring updates yourself.

    1. Download the archive esphome_trmnl_custom_integration.zip
    2. Unzip the downloaded archive on your computer.
    3. Find the "custom_components/esphome_trmnl" folder inside and copy the "esphome_trmnl" folder to the "custom_components" directory of your Home Assistant configuration.
    4. Restart Home Assistant for the system to recognize the new files.
    5. Go to Settings -> Devices & Services -> Add Integration, search for "ESPHome-TRMNL" -> Add
    6. When you turn on your screen, it should automatically appear in the "ESPHome-TRMNL" integration.
  2. Install Blueprint Open in Home Assistant and set up the automation following the built-in prompts.


Button Control

  • Holding the button in deep sleep mode resets the forcefully set screen, refresh interval, and display update lock in the "Home Assistant" integration.
  • A long press (more than 5 seconds) in active mode turns on the setup mode. In this mode, if the board is connected to Wi-Fi, it will display a QR code with a link to the web interface (default login and password for the web interface is "esphome-trmnl"); if not connected, it will show a QR code linking to the captive portal. In this mode, the device stays awake for 30 minutes and then goes into deep sleep.
  • A short press wakes up/reboots the device.

Battery Voltage Sensor Calibration (ADC Calibration)

  1. Record readings at battery voltages of 4.20, 4.19, 3.75, and 3.60 V.
  2. Enter the data into the filter. To the left of (->) is the sensor value, and to the right is the true value:
    sensor:
  - platform: adc
    ...
    id: battery_voltage
    ...
       - calibrate_linear:
           - 3.22 -> 3.75
           - 3.64 -> 4.19
           - 3.89 -> 4.20
    ...

Widgets

Creating Custom Widgets

Example 1. Home Assistant "Time and Date"

Phase 1. Data Source Setup. Here we tell Inker where and how to get data from "Home Assistant".

img

  1. Create a "New Data Source" in the "Inker" interface.
  2. In the "Basic Information" section:

    • "Name": Enter a clear name, e.g., "HA Time".
    • "Type": Select "JSON API".
  3. In the "Connection" section:

    • "URL": Provide the API address of your "Home Assistant" for the specific sensor.

      • Format: http://<HA-IP-Address>:<port>/api/states/<sensor_name>
    • "HTTP Method": Select "GET".

    • "Custom Headers": Be sure to add an authorization header for "Home Assistant" (you will need a long-lived access token created in the profile of a non-privileged HA user for this).

      • "Header name": enter Authorization
      • "Header value": enter "Bearer YOUR_TOKEN" (replace "YOUR_TOKEN" with your actual token from HA, note the space after the word Bearer).

      Security

      It is strongly recommended NOT to create a long-lived access token from an administrator account in "Home Assistant".

      • Click the "Add" button.
  4. Click the green "Test URL" button. If everything is configured correctly, Inker will connect to Home Assistant and available fields (e.g., "entity_id", "state", "last_changed", etc.) will appear in the right panel.

  5. In the "Settings" section:

    • "Refresh Interval" (seconds): Set the desired data update interval in seconds.
  6. Click "Save Changes".

Phase 2. Widget Setup. Here we configure exactly how the received data will be displayed on the screen.

img

  1. Create a new Custom Widget in the Inker interface.
  2. Select the "Data Source" we created in the first phase (e.g., "HA Time").
  3. Choose Display Type: JavaScript
  4. In the "Widget Name" field, enter a name for the widget, e.g., "HA time".
  5. In the "JavaScript Transform" section, we configure the formatting of the received data:

    • "Output Mode": Select Single Value.
    • In the "JavaScript Code" field, paste the following code to transform the date and time:
    const state = $.state;
    const dateObj = new Date(state);
    const formatter = new Intl.DateTimeFormat('en-US', {
        weekday: 'short', // Mon, Tue...
        day: 'numeric',   // 15
        month: 'short',   // Mar
        hour: '2-digit',  // 22
        minute: '2-digit' // 13
    });
    const result = formatter.format(dateObj);
    return result;
    
  6. In the "Live Output" block below, you will immediately see the result of your code. If data is arriving correctly from HA, the formatted time will be displayed there (e.g.: Tue, May 5, 11:45). You will also see a preview of the finished widget in the "Live Preview" panel at the top right.

  7. Click "Save Changes" to finish setting up the widget.

Example 2. Home Assistant "Calendar"

This widget collects events from multiple HA calendars and displays them as a single line.

Configured similarly to Example 1, but with a different URL that returns the state of all entities:

http://<HA_IP_ADDRESS>:<PORT>/api/states

img

JavaScript Code
const entities = ["calendar.test1", "calendar.test2"];
const groupedEvents = {}; // Object for grouping: { "Today (05.05)": ["Event 1", "Event 2"] }

entities.forEach(entityId => {
    const calendar = $.find(item => item.entity_id === entityId);

    if (calendar && calendar.attributes && calendar.attributes.start_time) {
        const eventName = calendar.attributes.message;
        const startTime = calendar.attributes.start_time;

        const eventDate = new Date(startTime.replace(' ', 'T'));
        const today = new Date();

        eventDate.setHours(0, 0, 0, 0);
        today.setHours(0, 0, 0, 0);

        const daysUntilEvent = Math.round((eventDate - today) / (1000 * 60 * 60 * 24));

        if (daysUntilEvent === 0 || daysUntilEvent === 1) {
            const dayStr = daysUntilEvent === 0 ? "Today" : "Tomorrow";
            const day = String(eventDate.getDate()).padStart(2, '0');
            const month = String(eventDate.getMonth() + 1).padStart(2, '0');

            const label = `${dayStr} (${day}.${month})`;

            // If this date isn't in the object yet, create an empty array
            if (!groupedEvents[label]) {
                groupedEvents[label] = [];
            }
            // Add the event name to the array for this date
            groupedEvents[label].push(eventName);
        }
    }
});

// Form the final output strings
const result = Object.keys(groupedEvents).map(label => {
    const events = groupedEvents[label].join(", "); // Combine events with a comma
    return `${label} ${events}`;
});

// Return the result separated by a newline (or comma)
return result.join("  |  ");
  • In the "Live Output" block below, you will immediately see the result of your code. If data is received correctly from HA, it will display calendar data for the current or next day (for example: "Today (05.05)": ["Event 1", "Event 2"]).

img


Importing Widgets

Widgets are imported by simply inserting the "screen code" on the "screens" page.

Automation Example

This example demonstrates how to dynamically manage an E-Ink display throughout the day: switching the displayed content in the evening and putting the device into deep power-saving mode at night.

Automation Scenario

  1. Evening (20:00): A command to change the image ("inker" screen) is sent to the display via the text entity. An evening template is displayed on the screen (e.g., a cozy dashboard or clock).
  2. Midnight (00:00): The display switches to night mode to save battery:

  3. The update interval (number) is increased to 17800 seconds (~5 hours).

  4. The update lock mode (switch) is turned on, putting the device to sleep.
  5. The URL is cleared. Now, when the screen wakes up, the "inker" playlist will be displayed.

  6. Morning (05:30): The display wakes up:

  7. Sleep time is reset to 0 (return to the standard polling interval).

  8. The update lock is turned off, and the display updates the data on the screen again.

Automation Code (YAML)

You can copy this code into the text mode of the Home Assistant automation editor:

alias: "ESPHome-TRMNL: Screen management throughout the day"
description: "Automatic screen switching and deep sleep at night"
triggers:
  - trigger: time
    at: "20:00:00"
    id: evening
  - trigger: time
    at: "00:00:00"
    id: midnight
  - trigger: time
    at: "05:30:00"
    id: morning
conditions: []
actions:
  - choose:
      # EVENING MODE
      - conditions:
          - condition: trigger
            id:
              - evening
        sequence:
          - action: text.set_value
            target:
              device_id: ad5052c25da850ff28860ef907f3367e
            data:
              value: http://192.168.1.100:8124/api/device-images/design/2
            note: "Setting the evening screen layout"

      # NIGHT MODE (POWER SAVING)
      - conditions:
          - condition: trigger
            id:
              - midnight
        sequence:
          - action: number.set_value
            target:
              area_id: prikhozhaia
            data:
              value: "17800"
            note: "Increasing sleep interval until morning"
          - action: switch.turn_on
            target:
              area_id: prikhozhaia
            note: "Turning on update lock (deep sleep)"
          - action: text.set_value
            target:
              device_id: ad5052c25da850ff28860ef907f3367e
            data: {}
            note: "Resetting custom URL"

      # MORNING WAKE UP
      - conditions:
          - condition: trigger
            id:
              - morning
        sequence:
          - action: number.set_value
            target:
              area_id: prikhozhaia
            data:
              value: "0"
            note: "Resetting sleep interval to standard"
          - action: switch.turn_off
            target:
              area_id: prikhozhaia
            note: "Turning off lock, starting updates"
mode: single

FAQ

  • How to change Wi-Fi SSID and password? Connect via USB open web.esphome.io CONNECT () Configure Wi-Fi (ESP32 must be in active mode).

Help! Something is not working.

  • Incorrect display: if image quality is low or the device does not work, try toggling switch #1 on the ESP32.
  • Nothing works -> open web.esphome.io, connect the panel via USB, and check the logs.
  • ESPHome hasn't updated but stopped building working firmware: delete the C:\Users\<user_name>\.platformio\ and \.esphome\build folders.
Share with friends
Link copied!

Comments