How to turn Display (SSD1306) on, when needed

Dear community,

I would like to build an Air Quality sensor for my kids. It should show some Air Quality Data on a Display. The Display should only be on, when the kid is nearby.

Firmware:

  • ESPhome

Hardware available:

  • D1mini board (ESP8266)
  • VL53L0X (laser distance sensor)
  • SSD1306 Display
  • BME680 (temp, humidity, preassure, VOC/CO2)

Issue:
I have put everything together and everythings works fine - for a minute or so. Then, the distance sensor trips over something and starts sending nan-values.

[23:45:06][D][sensor:093]: 'VL53L0x Distanz': Sending state nan m with 2 decimals of accuracy
[23:45:06][W][vl53l0x:265]: VL53L0x Distanz - update called before prior reading complete - initiated:0 waiting_for_interrupt:1

I have found these two, old and unresolved issues:

Questions:

  • Is there anybody out there, who has successfully implemented a VL53L0X-sensor with a D1mini?
  • If not, what are other options to turn on/off a SSD1306 display, when somebody is nearby?

any help is very much appreciated.

current ESPhome code
# Variablen
substitutions:
  device_name: "esphome-test"
  friendly_name: "Test"
  node_name: "esphome_test"
  device_description: "Test Konfiguration"


# ESPHome Core Configuration
esphome:
  name: '${device_name}'
  friendly_name: '${friendly_name}'
  comment: '${device_description}'
  platformio_options:
    build_flags:
      - -DPIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48_SECHEAP_SHARED
  # Loop, der nach erfolgreicher WLAN-Verbindung und Zeitabfrage ausgefĂŒhrt wird
  on_boot:
    priority: -100  # Setze die PrioritĂ€t niedriger als die WLAN-Verbindung (standardmĂ€ĂŸig -10)
    then:
      - while:
          condition:
            not:
              lambda: 'return id(homeassistant_time).now().is_valid();'
          then:
            - delay: 1s # Warte eine Sekunde, bevor erneut ĂŒberprĂŒft wird
      - lambda: |-
          // Sobald die Zeit verfĂŒgbar ist, berechne den Boot-Zeitpunkt
          id(${node_name}_last_boot_time).publish_state(
            id(homeassistant_time).now().timestamp - (int) id(${node_name}_uptime_seconds).state
          );





esp8266:
  board: d1_mini


# Enable logging
logger:
  level: DEBUG

# Enable Home Assistant API
api:
  encryption:
    key: "xxx1"

ota:
  - platform: esphome
    password: "xxx2"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  manual_ip:
    # Set this to the IP of the ESP
    static_ip: 192.168.30.5
    # Set this to the IP address of the router. Often ends with .1
    gateway: 192.168.30.1
    # The subnet of the network. 255.255.255.0 works for most home networks.
    subnet: 255.255.255.0
  use_address: ${device_name}.internal
  domain: .internal

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: '${device_name}'
    password: "xxx3"


captive_portal:


# enable web interface on device
#web_server:
# port: 80


font:
  # gfonts://family[@weight]
  - file: "gfonts://Roboto"
    id: roboto_10
    size: 10




# Zeit von HomeAssistant abfragen -> fĂŒr Last Boot nötig
time:
  - platform: homeassistant
    id: homeassistant_time



switch:
  - platform: restart
    name: "ESPHome Wohnzimmer neu starten"




i2c:
  sda: D2
  scl: D1
  scan: true
  id: bus_a


# Display SSD1306 einbinden
display:
  - platform: ssd1306_i2c
    model: "SSD1306 128x32"
    reset_pin: D0
    address: 0x3C
    lambda: |-
      it.print(0, 0, id(roboto_10), "Hello World!");



# BME680 Sensor einbinden
bme68x_bsec2_i2c:
  address: 0x77
  model: bme680
  operating_age: 28d
  sample_rate: ULP
  supply_voltage: 3.3V
  temperature_offset: 0    # dieser Wert wird abgezogen
  state_save_interval: 6h





sensor:

# Last Boot Sensor definieren (Berechnung findet einmalig beim Boot statt)
  - platform: uptime
    id: ${node_name}_uptime_seconds
    name: "Uptime Sekunden"
    internal: true

  - platform: template
    name: "Last Boot"
    id: ${node_name}_last_boot_time
    device_class: timestamp
    entity_category: diagnostic
    accuracy_decimals: 0
    update_interval: never # Kein regelmĂ€ĂŸiges Update nötig


# VL53LOX Distanzsensor
  - platform: vl53l0x
    name: "VL53L0x Distanz"
    id: "distance"
    address: 0x29
    timeout: 200us
    update_interval: 500ms
    unit_of_measurement: "m"
    device_class: distance
    state_class: measurement



  # Konfiguration fĂŒr BME680 Sensor
  - platform: bme68x_bsec2
    temperature:
      name: "BME680 Temperature"
      id: bme680_temp
#      filters:
#        - offset: -0.9
    pressure:
      name: "BME680 Pressure"
      accuracy_decimals: 0
    humidity:
      name: "BME680 Humidity"
      id: bme680_humidity
      accuracy_decimals: 0
      filters:
        - offset: -5
    iaq:
      name: "BME680 IAQ"
      id: iaq
    co2_equivalent:
      name: "BME680 CO2 Equivalent"
      accuracy_decimals: 0
    breath_voc_equivalent:
      name: "BME680 Breath VOC Equivalent original"
      id: voc_source
      internal: false



  - platform: template
    name: "BME680 Breath VOC Equivalent"
    id: BME680_voc_calc
    lambda: |-
      return ( (id(voc_source).state) * 1000) ;
    device_class: volatile_organic_compounds_parts
    state_class: measurement
    unit_of_measurement: ppb
    accuracy_decimals: 0
    update_interval: 5min
    icon: 'mdi:molecule'


# BME680 Wohnzimmer: Taupunkt berechnen 
  - platform: template
    name: "BME680 Taupunkt"
    lambda: |-
      return (243.5*(log(id(bme680_humidity).state/100)+((17.67*id(bme680_temp).state)/
      (243.5+id(bme680_temp).state)))/(17.67-log(id(bme680_humidity).state/100)-
      ((17.67*id(bme680_temp).state)/(243.5+id(bme680_temp).state))));
    device_class: temperature
    state_class: measurement
    unit_of_measurement: °C
    update_interval: 5min
    icon: 'mdi:thermometer-water'


text_sensor:
  - platform: bme68x_bsec2
    iaq_accuracy:
      name: "BME680 IAQ Accuracy"
  
  
  # Air Quality-Angaben
  - platform: template
    name: "BME680 IAQ Classification"
    update_interval: 5min
    lambda: |-
      if ( int(id(iaq).state) <= 50) {
        return {"extrem gut"};
      }
      else if (int(id(iaq).state) >= 51 && int(id(iaq).state) <= 100) {
        return {"sehr gut"};
      }
      else if (int(id(iaq).state) >= 101 && int(id(iaq).state) <= 150) {
        return {"gut"};
      }
      else if (int(id(iaq).state) >= 151 && int(id(iaq).state) <= 200) {
        return {"mittelmÀssig"};
      }
      else if (int(id(iaq).state) >= 201 && int(id(iaq).state) <= 250) {
        return {"schlecht"};
      }
      else if (int(id(iaq).state) >= 251 && int(id(iaq).state) <= 350) {
        return {"sehr schlecht"};
      }
      else if (int(id(iaq).state) >= 351) {
        return {"extrem schlecht"};
      }
      else {
        return {"Fehler"};
      }


  # Send IP Address
  - platform: wifi_info
    ip_address:
      name: IP Address

Hi, you might be able to control the brightness, but I don’t see an option to enable or disable the display as a whole.

I did start working on a lambda to control the brightness dynamically, but I didn’t go ahead with it in my project.

You could use some “simple” sensor, like PIR. Oled display is off in practice if you write empty page to it.

hmm if the laser is causing you problems, swap it out for a MMwave or PIR sensor that you know works with ESPHome?

If you want to power off the SSD1306, you’d need to figure a way to control the GPIO providing power/VCC.

This is entirely untested, but AI suggests this:

[AI generated code removed due to forum rules]

Hi @PharkieB,

thank you for the effort. AI generated code for Powershell and all sorts of other syntax is usually very helpful. Not true for ESPHome and Nunjucks. Please don’t provide untested code, as it is more confusing, then helping. :slight_smile:

I will think about testing the whole thing with an ESP32 board or switching to PIR or MMwave sensor.

It sounds like you might be asking two different questions.

  1. How do I turn off/on a ssd1306 display?
  2. Why is my ToF sensor returning NaN sometimes?

Don’t know that much about the display, but there are some posts about it and contrast is the key, set it to 0 and there is no display from what I recall.

I do have experience with the ToF sensors. My experience is that you get NaN when they don’t sense something. I have one in my grey water sump tank, but have not been impressed with it. It might be okay as a presence sensor, but I would use either a PIR or mmwave presence sensor. There are quite a few posts of people using both of those types of sensors. If you want to stick with the ToF sensor, use NaN to indicate you need to turn the display off.

Thank you for your input. Unfortunately, the sensor doesn’t recover from that state. You have to reboot. If you follow the two links above, they ‘solved’ it by automatically rebooting, as soon as nan values appear. Not a good option, if it reboots every other minute


I made a contrast change on OLED display. After sunset it is set to 1%, and during the day (at 10 AM) it changes to 20%:

  # Konfiguracja czujnika wewnetrznego skojarzonego z encja HA, ktora dostarcza poziom kontrastu
  - platform: homeassistant
    name: "OLED Kontrast z HA"
    id: oled_contrast_from_ha
    accuracy_decimals: 2
    entity_id: input_number.oled_contrast
    internal: true
    # Gdy zmieni sie wartosc encji w HA zmien kontrast wyswietlacza
    on_value:
      then:
        - lambda: |-
            float contrast_value = id(oled_contrast_from_ha).state;
            if (contrast_value >= 0.0 && contrast_value <= 1.0) {
              id(wyswietlacz_OLED).set_contrast(contrast_value);
              ESP_LOGD("OLED_contrast", "Kontrast ustawiony na %.0f%%", contrast_value * 100);
            } else {
              ESP_LOGW("OLED_contrast", "Niewlasciwa wartosc kontrastu: %.2f. Musi byc miedzy 0.0 a 1.0.", contrast_value);
            }

I achieved this using the SUN integration in Home Assistant and the appropriate automation. The automation looks like this:

alias: OLED zmiana kontrastu
description: Wyƛwietlacz na piętrze
triggers:
  - trigger: time
    at: "10:00:00"
    id: czas
  - trigger: state
    entity_id:
      - sun.sun
    to: below_horizon
conditions: []
actions:
  - if:
      - condition: trigger
        id:
          - czas
    then:
      - action: input_number.set_value
        target:
          entity_id: input_number.oled_contrast
        data:
          value: 0.2
    else:
      - action: input_number.set_value
        target:
          entity_id: input_number.oled_contrast
        data:
          value: 0.01
mode: single

Below is the input_number configuration that I added to configuration.yaml, although it can also be done using the Helper. I will change this in the future because it seems that a new “{REST}” integration was added in the integrations, which is probably related to this code.

# Na potrzeby sterowania kontrastem wyswietlacza  
input_number:
  oled_contrast:
    name: OLED Kontrast
    min: 0
    max: 1
    step: 0.01
    unit_of_measurement: '%'

Unfortunately setting contrast to 0 will not turn off the display but you can try “component.turn_off “ . I didn’t need it, so I didn’t test it and have no experience with it.

1 Like

I am using Tasmota with the ToF sensor and it never needs a reboot, so must some difference in the driver code.

I would use a motion or presence sensor for your use case.

I did some more testing yesterday night. I have to admit that the soldering on my ToF sensor was - well - not very professional. :thinking: I have redone my soldering and now, no NAN values anymore. :partying_face: (Received values are very inaccurate, but I only need to know, if it falls below a certain value. At the moment, I don’t care about the actual value provided by the sonsor, as long as it is not drifting.)

For this project, I will probably keep the ToF-sensor, but I will keep in mind the PIR sensor.
Thank you all for providing inputs on how to activate/deactivate the display. I will work myself into it in the next nights.