Dimmable PCF8574 LCD Display with ESPHome

I’ve recently purchased a 20x4 LCD display backed with a PCF8574 board (only only two data PINs in addition to the power). Initially it wouldn’t work with a NodeMCU v3 (no power indication) but after switching to a ESP32 I’ve got it working, but the display was way too bright…

Looking around ESPHome and Home Assistant no one seems to have wanted anything more than the ability to turn the backlight on or off, but elsewhere I found reference to being able to control the backlight with a PWM PIN

Step 1 was to remove the jumper from the pins labelled LED and then connect a jumper cable from the top PIN to D12 on the ESP32 Module (nothing needs to be connected to the bottom PIN, I just placed the jumper there for safe keeping).

Step 2 was to create a new Input Number Helper in Home Assistant with a range of 0 to 100 and set it to a sensible default - I chose 50%

Step 3 was to update the ESPHome config by adding a new output block as follows

  - platform: ledc
    pin: GPIO12
    id: backlight

and also a new Text Sensor
(where entity_id matches that of the numeric_input you created earlier (e.g. input_number.mortara_backlight_level), and the id matches that defined in the output block (e.g. backlight))

text_sensor:
  - platform: homeassistant    
    id: display_backlight
    entity_id: input_number.mortara_backlight_level
    internal: true
    on_value:
      then:
        - output.turn_on: backlight
        - output.set_level:
            id: backlight
            level: !lambda |-
              return atoi(id(display_backlight).state.c_str()) / 100.0;

The on_value block is a script that runs when the value is changed (including on boot), and it takes the value of the input_number and converts this to a percentage.

If you are already using the existing backlight() no_backlight() functions to turn the backlight off these are respected (e.g. if you change the input helper to 100% while your backlight binary sensor has the display turned off, it will remain off.

ezgif-3-fe1b3cf39d2a

My full ESP Home config is as follows, it’s based on the ESPHome documentation but I found that the display address was different (look at the logs) and the wiring diagram I used had different PIN numbers.

On the display itself, the first line is generated by ESPHome, but the other three lines are set to the output of Input Helpers in Home Assistant.

esphome:
  name: mortara
  platform: ESP32
  board: nodemcu-32s

wifi:
  ssid: "MySSID"
  password: "MyWiFiPassword"

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Mortara Fallback Hotspot"
    password: "MyFallbackPassword"

captive_portal:

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:

sensor:
  - platform: wifi_signal
    name: "Mortara - WiFi Signal Sensor"
    update_interval: 60s
  - platform: uptime
    name: "Mortara - Uptime Sensor"

text_sensor:
  - platform: version
    name: "Mortara - ESPHome Version"
  - platform: homeassistant    
    id: display_line_2
    entity_id: input_text.mortara_line_2
    internal: true
  - platform: homeassistant    
    id: display_line_3
    entity_id: input_text.mortara_line_3
    internal: true    
  - platform: homeassistant    
    id: display_line_4
    entity_id: input_text.mortara_line_4
    internal: true
  - platform: homeassistant    
    id: display_backlight
    entity_id: input_number.mortara_backlight_level
    internal: true
    on_value:
      then:
        - output.turn_on: backlight
        - output.set_level:
            id: backlight
            level: !lambda |-
              return atoi(id(display_backlight).state.c_str()) / 100.0;

binary_sensor:
  - platform: homeassistant    
    id: backlight_on
    entity_id: input_boolean.mortara_backlight_on
    internal: true

output:
  - platform: ledc
    pin: GPIO12
    id: backlight

i2c:
  sda: GPIO21
  scl: GPIO22
  scan: True 
  
time:
- platform: homeassistant
  id: my_time
  timezone: "Europe/London"

display:
  - platform: lcd_pcf8574
    dimensions: 20x4
    address: 0x27
    id: mortara_lcd
    lambda: |-
      if(id(backlight_on).state) {
        id(mortara_lcd).backlight();
        fflush(stdout);
        it.strftime(0,0," %H:%M  %d-%b-%Y", id(my_time).now());
        it.printf(0, 1, "%s", id(display_line_2).state.c_str());
        it.printf(0, 2, "%s", id(display_line_3).state.c_str());
        it.printf(0, 3, "%s", id(display_line_4).state.c_str());
      } else {
        id(mortara_lcd).no_backlight();
      }
5 Likes

This doesn’t seem to work for me. I am using an esp-8266 so ‘platform: ledc’ isn’t an option, validation tells me as much. I tried using ‘platform: esp8266_pwm’, but still get conflict messages about ‘backlight’.

Failed config

output.esp8266_pwm: 
  platform: esp8266_pwm
  
  ID backlight redefined! Check binary_sensor->1->id.
  id: backlight
  pin: 
    number: 13
    mode: OUTPUT
    inverted: False
  frequency: 1000.0
text_sensor.homeassistant: 
  platform: homeassistant
  id: display_backlight
  entity_id: input_number.backlight_dimmer
  internal: True
  on_value: 
    - then: 
        - output.turn_on: 
            
            ID 'backlight' of type template_::TemplateBinarySensor doesn't inherit from output::BinaryOutput. Please double check your ID is pointing to the correct value.
            id: backlight
        - output.set_level: 
            
            Couldn't find ID 'backlight_dimmer'. Please check you have defined an ID with that name in your configuration. These IDs look similar: "backlight".
            id: backlight_dimmer
            level: !lambda |-
              return atoi(id(display_backlight).state.c_str()) / 100.0;
  name: display_backlight

What next???

I got it working on ESP8266 (NodeMCU v3).
Using pin D4 with success (other pins occupied).
To be added in YAML for ESPhome configuration.

output:
  - platform: esp8266_pwm
    pin: D4
    id: pwm_backlight

text_sensor:
  - platform: homeassistant    
    id: display_backlight
    entity_id: input_number.backlight_level
    internal: true
    on_value:
      then:
        - output.turn_on: pwm_backlight
        - output.set_level:
            id: pwm_backlight
            level: !lambda |-
              return atoi(id(display_backlight).state.c_str()) / 100.0;

To be added in Home Assistant configuration.yaml

input_number:
  backlight_level:
    name: Backlight level
    initial: 50
    min: 5
    max: 100
    step: 5
3 Likes

@kevjs1982 great job with this project! I just added it to a character display that I’m putting in another ESPHome project!

Do you know a good way to add a toggle switch to the slider? like if it’s in a group and the group is turned off, the value of the slider would go to 0. However, when the group is turned on, the value of the slider would be restored back to what it previously was? Maybe there’s a template switch for this…

Also, I’m going to play around with trying to get the slider to match the circadian rhythm of other lights in my house (bright during the day and dim at night)

You should be able to add an automation - set the trigger to be the group state changing to the appropriate value (e.g. off) and update the slide based on that. Not sure how you’d do the restore without another sensor to update it as you set it to 0.

I figured it out!

set the output: and then reference the output in the light below it.

full details here: ESP32 LEDC Output — ESPHome

output:
  - platform: ledc
    pin: GPIO12
    id: backlight
    
light:
  - platform: monochromatic
    output: backlight
    name: "LCD Display Backlight"

I also added it to the Circadian Lighting Custom Component so it dims the light according to the time of day!

In my configuration.yaml file:

### Circadian Lighting
  - platform: circadian_lighting
    lights_brightness:
      - light.ble01_lcd_display_backlight
    min_brightness: 20
2 Likes

Hey folks! Thanks for providing the YAML here, I’d like to give back to this thread by placing my code which utilises @LUNZ 's light-based backlight control in handling the lambda code to draw text updates, which then does away with the need for the binary helper and number helper to store/set brightness or screen on/off states (the light’s native settings itself will handle those).

text_sensor:
  - platform: homeassistant    
    id: display_line_2
    entity_id: input_text.mortara_line_2
    internal: true
  - platform: homeassistant    
    id: display_line_3
    entity_id: input_text.mortara_line_3
    internal: true    
  - platform: homeassistant    
    id: display_line_4
    entity_id: input_text.mortara_line_4
    internal: true

time:
  - platform: homeassistant
    id: my_time
    timezone: "Asia/Singapore"

output:
  - platform: ledc
    pin: GPIO12
    id: backlight

light:
  - platform: monochromatic
    output: backlight
    name: "LCD Display Backlight"
    id: light_backlight
    restore_mode: ALWAYS_ON

display:
  - platform: lcd_pcf8574
    dimensions: 20x4
    address: 0x27
    id: lcd
    update_interval: 1s
    lambda: |-
      if(id(light_backlight).current_values.is_on()) {
        id(lcd).backlight();
        it.strftime(0, 0, "%I:%M:%S %p  %d %b", id(my_time).now());
        it.printf(0, 1, "%s", id(display_line_2).state.c_str());
        it.printf(0, 2, "%s", id(display_line_3).state.c_str());
        it.printf(0, 3, "%s", id(display_line_4).state.c_str());
      } else {
        id(lcd).no_backlight();
      }

Since the backlight is now exposed as a light, I was able to group it with my room’s ceiling lights and turn them on/off as a group!

Hope that helps the next person building highly integrated Home Assistant LCD displays with ESPHome!

3 Likes

Hi!

I bought few 4x20 displays to my home automation project and succesfully getting these work with this threads instructions.

I´ve got one question:

Is it possible to show multiple entity values in the same line?
Examples that I found showing only one entity per line.

Example:
Line 1: temperature1, temperature2, temperature3
Line 2: test1, test2, test3
etc.

Thanks for help and especially for sharing your project and giving inspiration to me :slightly_smiling_face:

Any ideas?

I have tried few different methods without success :frowning_face_with_open_mouth:

Sure, assuming you have enough space on the display line.

I use an automation in HA to set the value of an input_text which the ESPHome device displays.

Here is a quick example.

alias: LCD - Update Temp and Humidity on LCD
description: Update Temp and Humidity on LCD
trigger:
  - platform: time_pattern
    seconds: "1"
    alias: 1st Second of every minute (i.e. every minute)
condition: []
action:
  - service: input_text.set_value
    data:
      value: "Temp: {{states.sensor.my_sensor_temperature.state|int}}F Humi: {{states.sensor.my_sensor_humidity.state|int}}%"
    target:
      entity_id: input_text.1602_lcd_page2_line1
mode: single

If it is not too late, I hope it helps.

That´s helps lot, using automations to update values didn´t come to my mind.

Thank you so much Derek3Rosen! :slight_smile: