AZ Delivery D1 mini button intermittently fails to trigger on_click action

I have an AZ Delivery D1 mini running a DHT22 sensor and a SSD 1306 0.96" OLED display. The general idea is that pressing the button should display temperature, humidity and time for a short period, then a screensaver for a few minutes, and then blank the display. further button presses should then start the whle thing over, no matter where the ESP in the cycle I just described.

YAML is as follows (apologies for the length):

###############################################################################

substitutions:
  device_name: masterbedthermohygrometer
  yaml_file_name: ${device_name}.yaml
  friendly_name: "Master Bedroom Thermohygrometer"
  device_description: "Thermohygrometer using DHT22 temperature and humidity sensor and SSD 1306 display."

globals:
  - id: dispNumMode
    type: int
    restore_value: no
    initial_value: '1'
  - id: screenSaverNum
    type: int
    restore_value: no
    initial_value: '0'

esphome:
  name: ${device_name}
  comment: '${device_description}'
  platform: ESP8266
  board: d1_mini

wifi:
  ssid: !secret iot_wifi_ssid
  password: !secret iot_wifi_pass

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: ${friendly_name}
    password: !secret esphome_admin_pass

captive_portal:

# Enable logging
logger:

# Enable Home Assistant API
api:
  reboot_timeout: 15min
  password: !secret esphome_admin_pass
  encryption:
    key: !secret esphome_noise_key

ota:
  password: !secret esphome_admin_pass

######################
# the web_server & sensor components can be removed
# without affecting core functionaility.

web_server:
  port: 80
  auth:
    username: !secret esphome_admin_user
    password: !secret esphome_admin_pass
    
#######################################
# Device specific Config Begins Below #
#######################################

# Get time from Home Assistant...
time:
  - platform: homeassistant
    id: ha_time

i2c:
  sda: D5
  scl: D6

text_sensor:
  - platform: version
    hide_timestamp: true
    name: "${device_name} - ESPHome Version"
  - platform: wifi_info
    ip_address:
      name: "${device_name} - IP Address"
    ssid:
      name: "${device_name} - Connected SSID"
    bssid:
      name: "${device_name} - Connected BSSID"
    mac_address:
      name: "${device_name} - Mac Wifi Address"
      
sensor:
  - platform: wifi_signal
    name: "${device_name} - Wifi Signal Strength"
    update_interval: 60s
    
  - platform: uptime
    name: "${device_name} - Uptime"

  - platform: dht
    model: DHT22
    pin: D7
    temperature:
      name: "${friendly_name} - Temperature"
      id: temp
      filters:
        - multiply: 1.0          # Calibration.  In this case, to
        - offset: -2.5           # a Wiser room stat.  Unsure of
                                 # he Wiser's accuracy, but all need
                                 # to read the same.
    humidity:
      name: "${friendly_name} - Humidity"
      id: hum
    update_interval: 60s

binary_sensor:
  - platform: gpio
    pin: 
      number: D4
      inverted: true
      mode:
         input: true
         pullup: true
    name: "${device_name} - Button"
    id: button
    on_click:
      then:
        - switch.turn_on: ${device_name}_screensaver
        - switch.turn_on: ${device_name}_display
    
font:
  - file: "fonts/Rabelo-Free/Rabelo-Regular.ttf"
    id: rabelo
    size: 15
    
display:
  - platform: ssd1306_i2c
    model: "SSD1306 128x64"
    address: 0x3C
    lambda: |-
    
      const int screenSizeX = 128;          // Screen size; X, Y
      const int screenSizeY = 64;
      
      const int lineCount = 20;             // The number of lines we are
                                            // going to draw

      const int lineStepSize = 10;          // The size of the step by which
                                            // we will move eack line end

      const float pi = 3.14159265;          // The constant Pi.

      static int genNewLines = 1;           // True.  Flag to signal that new
                                            // lines need to be generated.
      
      static int line1xStart = 0;           // this is where we are starting
      static int line1yStart = 0;           // to draw (the first of) the
      static int line2xStart = 0;           // lines.
      static int line2yStart = 0;
      
      int line1x = line1xStart;             // Variables to hold line starts
      int line1y = line1yStart;             // and end as we draw each in
      int line2x = line2xStart;             // the sequence
      int line2y = line2yStart;
      
      static int line1deltaX = 0;           // These are the deltas in X/Y
      static int line1deltaY = 0;           // as we move the line-ends for
      static int line2deltaX = 0;           // each subsequent line in
      static int line2deltaY = 0;           // sequence
      
      int thisLine = 0;                     // simple counter for use to
                                            // keep track of lines as we
                                            // draw them

      float startAngle = 0;                 // The angle we are moving the
                                            // start of the line by

      float endAngle = 0;                   // The angle we are moving the
                                            // end of the line by

      if (genNewLines) {

        // We are effectively in a loop already as ESPHome will
        // repeatedly call our code.  This bit is for initialisations
        // that happen on the first time through (so only once, after
        // power-on.
        
        ESP_LOGD("${yaml_file_name}", "Seeding PRNG...");
        
        // seed PRNG, generate QIX line start points  ...

        srand(1);                           // will produce the same sequence
                                            // each run.  Will sort out later.
                                            
        genNewLines = 0;                    // False
        
        ESP_LOGD("${yaml_file_name}", "Generating 4 random screen points...");
        
        line1x = rand() % screenSizeX;      // line start initial
        line1y = rand() % screenSizeY;
        
        line2x = rand() % screenSizeX;      // line end initial
        line2y = rand() % screenSizeY;
        
        startAngle = (rand() % 360) * (pi / 180);          // start direction
                                                           // for start of line

        endAngle = (rand() % 360) * (pi / 180);            // start direction
                                                           // for end of line

        line1deltaX = lineStepSize * cos(startAngle);      // Start of line
        line1deltaX = lineStepSize * sin(startAngle);      // X, Y movement.

        line2deltaX = lineStepSize * cos(endAngle);        // End of line
        line2deltaY = lineStepSize * sin(endAngle);        // X, Y movement.
      }

      // decode global integers into enums ...
    
      enum screenMode {
        STATS,
        SCREENSAVE
      };
      
      enum screenSaveMode {
        BLANK,
        QIX,
      };

      screenMode dispMode = STATS;
      dispMode = (screenMode) id(dispNumMode);
      
      screenSaveMode screenSaver = BLANK;
      screenSaver = (screenSaveMode) id(screenSaverNum);
      
      // Render the display...
      
      if (dispMode == STATS) {

        // Code for displaying stats ...
      
        auto timeNow = id(ha_time).now();
        
        // ESP_LOGD("${yaml_file_name}", "Display is on; displaying stats.");
        
        it.printf  ( 0,  0, id(rabelo), "Temperture: %.1f", id(temp).state);
        it.printf  ( 0, 20, id(rabelo), "Humidity: %.1f",   id(hum).state);
        it.printf  ( 0, 40, id(rabelo), "Time:");
        it.strftime(45, 40, id(rabelo), "%H:%M:%S", timeNow);
        
        // genNewLines = 1;           // True

      } else if (dispMode == SCREENSAVE) {
      
        // Code for screensavers ...
        
        if (screenSaver == BLANK) {

          // Blank the screen ...

          // ESP_LOGD("${yaml_file_name}", "Display is off; blanking.");
          
          it.clear();
          
          // genNewLines = 1;           // True

        } else if (screenSaver == QIX) {

          // "QIX" screensaver.  We are drawing a series of lines that progress
          // around the screen.  We accomplish this by picking a direction for
          // each line-end in the sequence to move, and keep moving in that
          // direction for each successive line.  When one line end or other
          // hits the edge of the screen, we pick a new direction.

          // There is an added wrinkle, here, in that it seems ESPHome clears
          // the screen between invokations of this code.  The way we have to
          // work, therefore, is remember where the first line was drawn last
          // time round, advance by one, and then start drawing from there
          // (redrawing the entire sequence of lines again, having stepped on
          // by one.)
 
          // ESP_LOGD("${yaml_file_name}", "Screensaver is on; processing.");
          
          // ESP_LOGD("${yaml_file_name}",
          //          "X1: %i, Y1: %i, X2: %i, Y2: %i",
          //          line1xStart,
          //          line1yStart,
          //          line2xStart,
          //          line2yStart);
                   
          // Now draw the lines ...

          for ( thisLine = 0; thisLine <= lineCount ; thisLine++ ) {

            line1x += line1deltaX;                   // Move to the next line
            line1y += line1deltaY;
            line2x += line2deltaX;
            line2y += line2deltaY;

            // Did any of the line ends fall off the edge of the screen?
            // If so, clamp it to the edge of the screen and chose and new
            // direction (based on what edge of the screen we fell off.
          
            if (line1x <= 0) {

              startAngle = (270 + (rand() % 180)) * (pi / 180); // start
                                                                // direction
                                                                // for start of
                                                                // line

              line1deltaX = int(lineStepSize * cos(endAngle)); // Start of line
              line1deltaY = int(lineStepSize * sin(endAngle)); // X, Y movement.

              line1x = 0;
              
            } else if (line1x >= screenSizeX) {

              startAngle = (90 + (rand() % 180)) * (pi / 180); // start
                                                               // direction
                                                               // for start of
                                                               // line

              line1deltaX = int(lineStepSize * cos(endAngle)); // Start of line
              line1deltaY = int(lineStepSize * sin(endAngle)); // X, Y movement.

              line1x = screenSizeX;
            }

            if (line1y <= 0) {

              startAngle = (rand() % 180) * (pi / 180);    // start direction
                                                           // for start of line

              line1deltaX = int(lineStepSize * cos(endAngle)); // Start of line
              line1deltaY = int(lineStepSize * sin(endAngle)); // X, Y movement.

              line1y = 0;
              
            } else if (line1y >= screenSizeY) {

              startAngle = (180 + (rand() % 180)) * (pi / 180); // start
                                                                // direction
                                                                // for start of
                                                                // line

              line1deltaX = int(lineStepSize * cos(endAngle)); // Start of line
              line1deltaY = int(lineStepSize * sin(endAngle)); // X, Y movement.

              line1y = screenSizeY;
            }

            if (line2x <= 0) {

              endAngle = (270 + (rand() % 180)) * (pi / 180); // start
                                                              // direction
                                                              // for start of
                                                              // line

              line2deltaX = int(lineStepSize * cos(endAngle)); // Start of line
              line2deltaY = int(lineStepSize * sin(endAngle)); // X, Y movement.

              line2x = 0;
              
            } else if (line2x >= screenSizeX) {

              endAngle = (90 + (rand() % 180)) * (pi / 180);  // start
                                                              // direction
                                                              // for start of
                                                              // line

              line2deltaX = int(lineStepSize * cos(endAngle)); // Start of line
              line2deltaY = int(lineStepSize * sin(endAngle)); // X, Y movement.

              line2x = screenSizeX;
            }

            if (line2y <= 0) {

              endAngle = (rand() % 180) * (pi / 180);   // start direction
                                                        // for start of line

              line2deltaX = int(lineStepSize * cos(endAngle)); // Start of line
              line2deltaY = int(lineStepSize * sin(endAngle)); // X, Y movement.

              line2y = 0;
              
            } else if (line2y >= screenSizeY) {

              endAngle = (180 + (rand() % 180)) * (pi / 180); // start
                                                              // direction
                                                              // for start of
                                                              // line

              line2deltaX = int(lineStepSize * cos(endAngle)); // Start of line
              line2deltaY = int(lineStepSize * sin(endAngle)); // X, Y movement.

              line2y = screenSizeY;
            }
          
            // Draw the current line
          
            it.line(line1x, line1y, line2x, line2y, COLOR_ON);

            // Save the position of the first line we draw so that we
            // can go back to it when we render the next screen.
            
            if (thisLine == 0) {
              line1xStart = line1x;
              line1yStart = line1y;
              line2xStart = line2x;
              line2yStart = line2y;
            }
          }
        }
      }
      
switch:
  - platform: template
    name: "${device_name} - Display"
    id: ${device_name}_display
    lambda: |-
      return {};
    turn_on_action:
      - logger.log: "Turning on display"
      - switch.template.publish:
          id: ${device_name}_display
          state: ON
      - lambda: |-
          id(dispNumMode) = 0;
      - delay: 10s
      - switch.turn_off: ${device_name}_display
    turn_off_action:
      - logger.log: "Turning off display"
      - switch.template.publish:
          id: ${device_name}_display
          state: OFF
      - lambda: |-
          id(dispNumMode) = 1;
  - platform: template
    name: "${device_name} - Screensaver"
    id: ${device_name}_screensaver
    lambda: |-
      return {};
    turn_on_action:
      - logger.log: "Turning on Screensaver"
      - switch.template.publish:
          id: ${device_name}_screensaver
          state: ON
      - lambda: |-
          id(screenSaverNum) = 1;
      - delay: 10 min
      - switch.turn_off: ${device_name}_screensaver
    turn_off_action:
      - logger.log: "Turning off Screensaver"
      - switch.template.publish:
          id: ${device_name}_screensaver
          state: OFF
      - lambda: |-
          id(screenSaverNum) = 0;

Sometimes, pressing the button works as expected. Something like the following is logged:

[11:46:44][D][binary_sensor:036]: 'masterbedthermohygrometer - Button': Sending state ON
[11:46:44][D][binary_sensor:036]: 'masterbedthermohygrometer - Button': Sending state OFF
[11:46:44][D][switch:013]: 'masterbedthermohygrometer - Screensaver' Turning ON.
[11:46:44][D][main:458]: Turning on Screensaver
[11:46:44][D][switch:013]: 'masterbedthermohygrometer - Display' Turning ON.
[11:46:44][D][main:437]: Turning on display
[11:46:44][D][switch:037]: 'masterbedthermohygrometer - Display': Sending state ON

Sometimes, pressing the button does not work (does nothing); something like the following is logged:

[11:57:37][D][binary_sensor:036]: 'masterbedthermohygrometer - Button': Sending state ON
[11:57:38][D][binary_sensor:036]: 'masterbedthermohygrometer - Button': Sending state OFF

The above suggests that the ESP is seeing the button press, however the on_click action in the config for the button isn’t being triggered.

Worth noting is that multiple presses on the button in quick succession will fire the on_click action.

Does anyone have any idea what might be going on? …and thoughts on what I should do to solve?

Thanks,

Paul.

Apologies for the truncated logs above; have tried editing and seem to be unable.

Logs for successful button press:

[11:46:44][D][binary_sensor:036]: 'masterbedthermohygrometer - Button': Sending state ON
[11:46:44][D][binary_sensor:036]: 'masterbedthermohygrometer - Button': Sending state OFF
[11:46:44][D][switch:013]: 'masterbedthermohygrometer - Screensaver' Turning ON.
[11:46:44][D][main:458]: Turning on Screensaver
[11:46:44][D][switch:013]: 'masterbedthermohygrometer - Display' Turning ON.
[11:46:44][D][main:437]: Turning on display
[11:46:44][D][switch:037]: 'masterbedthermohygrometer - Display': Sending state ON

Logs for an unsuccessful button press:

[11:57:37][D][binary_sensor:036]: 'masterbedthermohygrometer - Button': Sending state ON
[11:57:38][D][binary_sensor:036]: 'masterbedthermohygrometer - Button': Sending state OFF

Use “on_press” instead, “on_click” is expecting the button to be held for a period of time.

Thanks Coolie. Weirdly, it first made things better, and then the ESP dropped off my network entirely. This makes me think that maybe I have some sort of memory leak??? It could just be a WiFi signal thing, though… I’ll crack my head over the screensaver lambda, and see where that leads me; if I find anything in there, I will try your on_press thing again.