Esp32 suddenly instable (after GPIO binary sensor/button activation?)

Hey everyone,
I know, the topic about instable esp32 boards/nodes is a reoccuring, but the scenario I have here I cannot explain with what I found before, except with “sometimes it’s just random and that’s how the world turns”.

Short story: the whole setup worked without issues as long as only 2 GPIO inputs were configured in the code, and since I added 3 additional buttons as binary sensors, the board does random reboots or gets unresponsive.

So, situation at hand:
I built a small weather station for the hallway, with an esp32 devkit v1 board, a waveshare 2.9 epaper display, and last but not least 5 physical pushbuttons which I wanted to use to switch display pages and to trigger automations in HA. Before I built an actual case, I tested the whole setup at the final position in the hallway with a simplified version, everything except the pushbuttons fixed to a piece of cardboard:


That setup worked for weeks without any issues.

2 weeks ago I built a “case” for it, but as the location for the device is very narrow, I just took a thin piece of wood as a front plate, and added holders for the display and the board to the back. Then I also added the physical buttons and wired them to a connector as well:


This “final” setup uses the same esp32 board and display and usb cable and powersource (1A) than the prototype, I just transfered them away from the cardboard to the proper setup.

I first only added the code for the two upper buttons to the YAML file, as I didn’t have a usecase yet for the other 3. That worked flawlessly for 2 days:

binary_sensor:
  - platform: gpio
    pin:
      number: 26
      mode:
        input: true
        pullup: true
      inverted: true
    name: button1
    filters:
      - delayed_on: 10ms
    on_press:
      - display.page.show: page1
      - delay: 2s
      - component.update: eink
      
  - platform: gpio
    pin:
      number: 27
      mode:
        input: true
        pullup: true
      inverted: true
    name: button2
    filters:
      - delayed_on: 10ms
    on_press:
      - display.page.show: page2
      - delay: 2s
      - component.update: eink

After everything worked fine for 2 days, I just added the code for the other 3 buttons:

- platform: gpio
    pin:
      number: 32
      mode:
        input: true
        pullup: true
      inverted: true
    name: button3
    filters:
      - delayed_on: 10ms
      
  - platform: gpio
    pin:
      number: 33
      mode:
        input: true
        pullup: true
      inverted: true
    name: button4
    filters:
      - delayed_on: 10ms

      
  - platform: gpio
    pin:
      number: 25
      mode:
        input: true
        pullup: true
      inverted: true
    name: button5
    filters:
      - delayed_on: 10ms

Since then the board became instable. Using the buttons to switch between display page 1 and 2 sometimes works, sometimes the display turns empty and nothing reacts anymore. Checking the logs I get various errors around the node not being reachable, esphome trying to reconnect, sometimes succeeding, sometimes not. Sometimes the device runs on page 1 of the display for a few hours, but trying to switch to another display page crashes the whole thing again. The power LED of the board is on continuously.

About the setup in the room: as mentioned, the main components of the setup did not change between prototype and final setup. Same board, display, usb cable, power supply. The distance to the wifi access points is 2.5 meters, without anything but air inbetween. No firmware updates to the accesspoint or router, no new electronic devices, no new neighbours clogging wifi channels or anything. I tried a powersupply with 2A, no change in behaviour.

Does adding the code for GPIO inputs change the actual electronic “behaviour” on the board itself? Does that switch something on or changes something?

In case it helps, here is the complete YAML for the project:

esphome:
  name: epaper

esp32:
  board: esp32doit-devkit-v1
  framework:
    type: arduino

# Enable logging
logger:

json:

# Enable Home Assistant API
api:
  password: ""

ota:
  password: ""

wifi:
  ssid: "XXXXXXX"
  password: "XXXXXXX"

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

captive_portal:


font:
  - file: 'arial.ttf'
    id: font1
    size: 24
  - file: 'arial.ttf'
    id: font2
    size: 30
  - file: 'arial.ttf'
    id: fontsmall
    size: 12
  - file: 'arial.ttf'
    id: fontbig
    size: 30
    glyphs:
      ['&', '@', '!', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
       '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
       'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
       'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f',
       'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
       'u', 'v', 'w', 'x', 'y', 'z','å', 'ä', 'ö', '/']   
  - file: 'DejaVuSansMono-Bold.ttf'
    id: dejamonobold
    size: 44
    glyphs:
      ['&', '@', '!', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
       '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
       'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
       'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f',
       'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
       'u', 'v', 'w', 'x', 'y', 'z','å', 'ä', 'ö', '/']
       
  - file: 'DejaVuSansMono-Bold.ttf'
    id: dejamonoboldsmall
    size: 24
    glyphs:
      ['&', '@', '!', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
       '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
       'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
       'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f',
       'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
       'u', 'v', 'w', 'x', 'y', 'z','å', 'ä', 'ö', '/']


image:
  - file: "otter.bmp"
    id: otter_image
    resize: 296x128
  - file: "otterbitmap2.bmp"
    id: otter_icon
  - file: "otter_complete.bmp"
    id: otter_complete
  - file: "puppy30.bmp"
    id: puppy
  - file: "moonthick.bmp"
    id: moonthick
  - file: "moonthin.bmp"
    id: moonthin
  - file: "clear-night.bmp"
    id: clear_night
  - file: "cloudy.bmp"
    id: cloudy
  - file: "exceptional.bmp"
    id: exceptional
  - file: "fog.bmp"
    id: fog
  - file: "hail.bmp"
    id: hail
  - file: "lightning.bmp"
    id: lightning
  - file: "lightning-rainy.bmp"
    id: lightning_rainy
  - file: "partlycloudy.bmp"
    id: partlycloudy
  - file: "pouring.bmp"
    id: pouring
  - file: "rainy.bmp"
    id: rainy
  - file: "snowy.bmp"
    id: snowy
  - file: "snowy-rainy.bmp"
    id: snowy_rainy
  - file: "sunny.bmp"
    id: sunny
  - file: "windy.bmp"
    id: windy
  - file: "windyvariant.bmp"
    id: windy_variant
  - file: "clearnight45.bmp"
    id: clear_night45
  - file: "cloudy45.bmp"
    id: cloudy45
  - file: "exceptional45.bmp"
    id: exceptional45
  - file: "fog45.bmp"
    id: fog45
  - file: "hail45.bmp"
    id: hail45
  - file: "lightning45.bmp"
    id: lightning45
  - file: "lightningrainy45.bmp"
    id: lightning_rainy45
  - file: "partlycloudy45.bmp"
    id: partlycloudy45
  - file: "pouring45.bmp"
    id: pouring45
  - file: "rainy45.bmp"
    id: rainy45
  - file: "snowy45.bmp"
    id: snowy45
  - file: "snowy-rainy45.bmp"
    id: snowy_rainy45
  - file: "sunny45.bmp"
    id: sunny45
  - file: "windy45.bmp"
    id: windy45
  - file: "windy-variant45.bmp"
    id: windy_variant45

globals:
  - id: dogmeter
    type: int
    restore_value: no
    initial_value: '1'

time:
  - platform: homeassistant
    id: esptime
    on_time:
      - hours: 6
        minutes: 0
        seconds: 0
        then:
          - globals.set:
              id: dogmeter
              value: '2'
      - hours: 9
        minutes: 0
        seconds: 0
        then:
          - globals.set:
              id: dogmeter
              value: '1'
      - hours: 17
        minutes: 0
        seconds: 0
        then:
          - globals.set:
              id: dogmeter
              value: '2'
      - hours: 21
        minutes: 0
        seconds: 0
        then:
          - globals.set:
              id: dogmeter
              value: '0'


sensor:
  - platform: homeassistant
    id: outside_temperature
    entity_id: sensor.lumi_lumi_weather_a5f3bb07_temperature
    internal: true
  - platform: homeassistant
    id: windspeed
    entity_id: sensor.openweathermap_wind_speed
    internal: true
  - platform: homeassistant
    id: maxtemp
    entity_id: sensor.openweathermap_forecast_temperature_2
    internal: true
  - platform: homeassistant
    id: mintemp
    entity_id: sensor.openweathermap_forecast_temperature_low_2
    internal: true

text_sensor:
  - platform: homeassistant
    id: outside_weather
    entity_id: sensor.openweathermap_condition
    internal: true
  - platform: homeassistant
    id: owm_forecast
    entity_id: weather.openweathermap
    internal: true
    attribute: forecast
  - platform: homeassistant
    id: owm_forecast_days
    entity_id: weather.openweathermap_2
    internal: true
    attribute: forecast
  - platform: homeassistant
    id: pageshelper
    entity_id: input_select.epaper_page
    on_value:
      then:
        - display.page.show: !lambda |-
            if (id(pageshelper).state == "page1") {
              return id(page1);
            }
            else if (id(pageshelper).state == "page2") {
              return id(page2);
            }
            else {
              return id(page3);
            }
        - delay: 2s
        - component.update: eink
    internal: true

binary_sensor:
  - platform: gpio
    pin:
      number: 26
      mode:
        input: true
        pullup: true
      inverted: true
    name: button1
    filters:
      - delayed_on: 10ms
    on_press:
      - display.page.show: page1
      - delay: 2s
      - component.update: eink
      
  - platform: gpio
    pin:
      number: 27
      mode:
        input: true
        pullup: true
      inverted: true
    name: button2
    filters:
      - delayed_on: 10ms
    on_press:
      - display.page.show: page2
      - delay: 2s
      - component.update: eink
      
      
  - platform: gpio
    pin:
      number: 32
      mode:
        input: true
        pullup: true
      inverted: true
    name: button3
    filters:
      - delayed_on: 10ms
      
  - platform: gpio
    pin:
      number: 33
      mode:
        input: true
        pullup: true
      inverted: true
    name: button4
    filters:
      - delayed_on: 10ms

      
  - platform: gpio
    pin:
      number: 25
      mode:
        input: true
        pullup: true
      inverted: true
    name: button5
    filters:
      - delayed_on: 10ms


spi:
  clk_pin: 19
  mosi_pin: 18

display:
  - platform: waveshare_epaper
    cs_pin: 21
    dc_pin: 3
    busy_pin: 22
    reset_pin: 1
    model: 2.90inv2
    rotation: 90°
    update_interval: 300s
    full_update_every: 1
    #reset_duration: 2ms
    id: eink
    
    pages:
      - id: page1
        lambda: |-
          std::map<std::string, Image *> imgDict;
          imgDict["clear-night"] = id(clear_night);
          imgDict["cloudy"] = id(cloudy);
          imgDict["fog"] = id(fog);
          imgDict["hail"] = id(hail);
          imgDict["lightning"] = id(lightning);
          imgDict["lightning-rainy"] = id(lightning_rainy);
          imgDict["partlycloudy"] = id(partlycloudy);
          imgDict["pouring"] = id(pouring);
          imgDict["rainy"] = id(rainy);
          imgDict["snowy"] = id(snowy);
          imgDict["snowy-rainy"] = id(snowy_rainy);
          imgDict["sunny"] = id(sunny);
          imgDict["windy"] = id(windy);
          imgDict["windy-variant"] = id(windy_variant);
          imgDict["exceptional"] = id(exceptional);
        
        
          it.image(0, 0, id(otter_complete));
          
          
          if (id(outside_temperature).has_state()) {
          it.printf(80, 0, id(dejamonobold), TextAlign::TOP_LEFT, "%2.0f", id(outside_temperature).state);
          it.printf(150, 0, id(dejamonobold), TextAlign::TOP_RIGHT, "°");
          it.printf(190, 3, id(dejamonoboldsmall), TextAlign::TOP_RIGHT, "%.0f°", id(maxtemp).state);
          it.printf(190, 23, id(dejamonoboldsmall), TextAlign::TOP_RIGHT, "%.0f°", id(mintemp).state);
          }

          
          
          if (id(outside_weather).has_state()) {
          it.image(206, 2, imgDict[id(outside_weather).state]);
          }
                    
          it.printf(206, 60, id(fontsmall), "chance of");
          it.printf(206, 70, id(fontsmall), "puppies");
          int dogstart = 208;
          for (int dog=0; dog <= id(dogmeter); ++dog)
          {
          it.image(dogstart, 89, id(puppy));
          dogstart += 30;
          }      
          it.rectangle(206, 85, 90, 40);
          
          
          it.printf(80, 50, id(fontsmall), "chance of rain");
          it.rectangle(80, 65, 105, 60);
          if (id(owm_forecast).has_state())
          {
          DynamicJsonDocument doc(5048);
          deserializeJson(doc, (id(owm_forecast).state.c_str()));
          JsonArray root = doc.as<JsonArray>();
          int linestart = 82;
          for (int d=0; d <= 9; ++d)
          {
          JsonObject root_1 = root[d];
          float root_1_precipitation_probability = root_1["precipitation_probability"];
          int heighthelper = (2 + (int)root_1_precipitation_probability) / 2;
          int offsethelper = 120 - heighthelper;
          it.filled_rectangle(linestart, offsethelper, 8, heighthelper);
          linestart += 10;
          }
          
          }

      - id: page2
        lambda: |-
          std::map<std::string, Image *> imgDict;
          imgDict["clear-night"] = id(clear_night45);
          imgDict["cloudy"] = id(cloudy45);
          imgDict["fog"] = id(fog45);
          imgDict["hail"] = id(hail45);
          imgDict["lightning"] = id(lightning45);
          imgDict["lightning-rainy"] = id(lightning_rainy45);
          imgDict["partlycloudy"] = id(partlycloudy45);
          imgDict["pouring"] = id(pouring45);
          imgDict["rainy"] = id(rainy45);
          imgDict["snowy"] = id(snowy45);
          imgDict["snowy-rainy"] = id(snowy_rainy45);
          imgDict["sunny"] = id(sunny45);
          imgDict["windy"] = id(windy45);
          imgDict["windy-variant"] = id(windy_variant45);
          imgDict["exceptional"] = id(exceptional45);
          
          std::map<int, const char *> dayDict;
          dayDict[1] = "MO";
          dayDict[2] = "TU";
          dayDict[3] = "WE";
          dayDict[4] = "TH";
          dayDict[5] = "FR";
          dayDict[6] = "SA";
          dayDict[7] = "SU";
        
          int day = id(esptime).now().day_of_week;

          if (id(owm_forecast_days).has_state())
          {
          if (day == 7)
          {
          day = 1;
          }
          DynamicJsonDocument doc(5048);
          deserializeJson(doc, (id(owm_forecast_days).state.c_str()));
          JsonArray root = doc.as<JsonArray>();
          int linestarticon = 3;
          for (int i=1; i <= 6; ++i)
          {
          JsonObject root_0 = root[i];
          float maxtempforecast = root_0["temperature"];
          
          float chanceofrainforecast = root_0["precipitation_probability"];
          it.printf(linestarticon, -2, id(dejamonoboldsmall), TextAlign::TOP_LEFT, "%s", dayDict[day]);
          it.image(linestarticon, 22, imgDict[root_0["condition"]]);
          it.printf(linestarticon, 70, id(dejamonoboldsmall), TextAlign::TOP_LEFT, "%.0f°", maxtempforecast);
          if (chanceofrainforecast == 100)
          {
          it.printf(linestarticon, 93, id(dejamonoboldsmall), TextAlign::TOP_LEFT, "%.0f", chanceofrainforecast);
          }
          else
          {
          it.printf(linestarticon, 93, id(dejamonoboldsmall), TextAlign::TOP_LEFT, "%.0f%%", chanceofrainforecast);
          }
          linestarticon += 48;
          day += 1;
          }
          
          }
      - id: page3
        lambda: |-
          std::map<std::string, Image *> imgDict;
          imgDict["clear-night"] = id(clear_night);
          imgDict["cloudy"] = id(cloudy);
          imgDict["fog"] = id(fog);
          imgDict["hail"] = id(hail);
          imgDict["lightning"] = id(lightning);
          imgDict["lightning-rainy"] = id(lightning_rainy);
          imgDict["partlycloudy"] = id(partlycloudy);
          imgDict["pouring"] = id(pouring);
          imgDict["rainy"] = id(rainy);
          imgDict["snowy"] = id(snowy);
          imgDict["snowy-rainy"] = id(snowy_rainy);
          imgDict["sunny"] = id(sunny);
          imgDict["windy"] = id(windy);
          imgDict["windy-variant"] = id(windy_variant);
          imgDict["exceptional"] = id(exceptional);
        
        
          it.image(-10, 60, id(otter_complete));
          
          
          if (id(outside_temperature).has_state()) {
          it.printf(5, 5, id(fontbig), "%.0f°", id(outside_temperature).state);
          }
          
          
          if (id(outside_weather).has_state()) {
          it.printf(80, 62, id(fontsmall), "%s", id(outside_weather).state.c_str());
          it.image(80, 2, imgDict[id(outside_weather).state]);
          }
          
          
          if (id(windspeed).has_state()) {
          it.printf(200, 5, id(fontbig), "%.0f m/s", id(windspeed).state);
          }
          
          
          it.printf(206, 60, id(fontsmall), "chance of");
          it.printf(206, 70, id(fontsmall), "puppies");
          int dogstart = 208;
          for (int dog=0; dog <= id(dogmeter); ++dog)
          {
          it.image(dogstart, 89, id(puppy));
          dogstart += 30;
          }      
          it.rectangle(206, 85, 90, 40);
          
          
          it.printf(80, 50, id(fontsmall), "chance of rain");
          it.rectangle(80, 65, 105, 60);
          if (id(owm_forecast).has_state())
          {
          DynamicJsonDocument doc(5048);
          deserializeJson(doc, (id(owm_forecast).state.c_str()));
          JsonArray root = doc.as<JsonArray>();
          int linestart = 82;
          for (int d=0; d <= 9; ++d)
          {
          JsonObject root_1 = root[d];
          float root_1_precipitation_probability = root_1["precipitation_probability"];
          int heighthelper = (2 + (int)root_1_precipitation_probability) / 2;
          int offsethelper = 120 - heighthelper;
          it.filled_rectangle(linestart, offsethelper, 8, heighthelper);
          linestart += 10;
          }
          
          }

Anyone got an idea? I can of course get another board and try that, but if there is a general “design flaw” in my setup I’d rather get rid of that instead.

Thanks in advance!

Okay, doing my “Shame! Shame! Shame!” walk here myself: I had placed the check if the “day of week” counter reached 7 OUTSIDE the loop that creates the 6 days of forecast.

So on any weekday after monday, the day of the week went beyond 7, and therefore tried to address an entry in the day name lookup dictionary that didn’t exist, and probably created some kind of overflow or whatever. Anyway, putting the check INTO the loop, at the beginning, fixed the issue.