esphome / issues

Issue Tracker for ESPHome
https://esphome.io/
290 stars 36 forks source link

SPIDevice not initialised - did you call spi setup()? #4915

Closed erik3rik closed 1 year ago

erik3rik commented 1 year ago

The problem

After update to 2023.9.0 (from 2023.8.3), all my units that has a display with SPI interface are bricked. Not the screen itself, the entire ESP32 refuses to boot. Neither power cycling nor pressing the reset button solves the issue, and the unit is no longer available for OTA updates.

Upon connecting a device using USB, I was able to retrieve the logs.

Rolling back to ESP Home 2023.8.3 and upload the software by USB fixes the issue (with working display). Removing the display: block (see below) also fixes the issue and works with ESP Home 2023.9.0 but for obvious reasons the display does not work in this case.

Logs: Screenshot 2023-09-27 at 19 08 20

The device reboots every second and the logs looks like in the screenshot above.

Which version of ESPHome has the issue?

2023.9.0

What type of installation are you using?

Home Assistant Add-on

Which version of Home Assistant has the issue?

2023.9.3

What platform are you using?

ESP32

Board

az-delivery-devkit-v4

Component causing the issue

display (SPI?)

Example YAML snippet

spi:
  clk_pin: GPIO18
  mosi_pin: GPIO23

display:
  - platform: ssd1306_spi
    model: "SH1106 128x64"
    cs_pin: GPIO05
    dc_pin: GPIO22
    reset_pin: GPIO19
    id: oled_display
    flip_y: true
    pages:
    # Long list of pages that are not relevant to the case

Anything in the logs that might be useful for us?

[E][spi:078]: SPIDevice not initialised - did you call spi setup()?
Guru Meditation Error: Core 1 panic'ed (LoadProhibited). Exception was unhandled.

Core 1 register dump:
...

Backtrace:
...

Additional information

No response

samuelolteanu commented 1 year ago

I was diagnosing a bunch of ip esphome conflicts with eof orors, ping timeouts and of cursed api key changes then got "[Errno 111] Connect call failed ('', 6053) (SocketAPIError)" on an not easily reachable device with a 7 digit display...so yeah, for this one, it was not the wifi, not the display hardware nor its lambda because any other device bootloops with spi configured with max7219 example and no actual display connected. Tested on esp32s2(taken out) and vanilla esp32.

Dependency downloads take unuasually long time after downgrading right now, hopefully servers stay up.

probot-esphome[bot] commented 1 year ago

Hey there @esphome/core, mind taking a look at this issue as it has been labeled with an integration (spi) you are listed as a code owner for? Thanks! (message by CodeOwnersMention)

RudiP commented 1 year ago

Same issue here with ESPHome 2023.9.1, SPI display st7789v

image

clydebarrow commented 1 year ago

Looking into it...

clydebarrow commented 1 year ago

Can you share the whole yaml? The display on its own does not seem to cause an issue - I took your sample and can build and run that no problem. Any other SPI devices in there? Anything that might be trying to access the display before everything is set up?

clydebarrow commented 1 year ago

Same issue here with ESPHome 2023.9.1, SPI display st7789v

I just tested on an ESP32S3 with st7789v (LilyGo T-Embed) with no issues.

How much RAM is free? The new SPI code does use a DMA buffer, it's possible that RAM usage is higher than before.

clydebarrow commented 1 year ago

You want three backticks, on a line by themselves, before and after the yaml.

samuelolteanu commented 1 year ago

Bare board, powered by usb. Will also upload logs when I'll have physical access to it again.

esp32:
  board: wemos_d1_mini32
  framework:
    type: arduino

esphome:
  name: esp32-4
  friendly_name: esp32-4

logger:
  level: very_verbose

api:
  encryption:
    key: "ahvDDjMvo+CtqnrbjYg7PCr2aGZ/BRkdX9nNp5jyg0E="

ota:
  password: "56b68f4eb517a5a1123c39441e9899a3"

wifi:
  ssid: 
  password:
  manual_ip:
        static_ip: 192.168.0.138
        gateway: 192.168.0.1
        subnet: 255.255.255.0

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Esp32-4 Fallback Hotspot"
    password: "RRxZqKsCQdZ4"

captive_portal:

mdns:

spi:
  clk_pin: 22
  mosi_pin: 23

display:
  - platform: max7219
    cs_pin: 21
    num_chips: 1
    lambda: |-
      it.print("01234567");
clydebarrow commented 1 year ago

Ok, I have reproduced it. Stand by...

clydebarrow commented 1 year ago

Ok, for the MAX7219 adding this to the yaml will fix it:

external_components:
  source: github://pr#5446
  components: [ max7219 ]

It's a bug in the MAX7219 component, was not exposed until the new SPI code landed which is a bit stricter.

Don't know about the other reports until I get a full YAML to reproduce.

clydebarrow commented 1 year ago

I figured out why the problem caused a crash, and have pushed to the PR. So adding this to the YAML should work-around any other instances of the problem:

external_components:
  source: github://pr#5446
  components: [ spi ]

However, please report here if you find this necessary and/or if you see the "SPI Device not initialised" error message.

The underlying cause will be an error in another component and it will be nice to fix those rather than just papering over the cracks.

RoelHermus commented 1 year ago

I had a problem with the max7219 dot display. Adding `

external_components: source: github://pr#5446 components: [ spi ]`

fixed the problem. The first solution, with max7219 between the brackets did not fix the problem. The ESP8266 restarted again and again. Thanks so far for this solution

erik3rik commented 1 year ago

Is that (5446) really fixing all issues? It seems to fix problems with max7219 as per the example that samuelolteanu showed (platform: max7219), but my original problem that I reported came from platform: ssd1306_spi, is that also fixed by 5446? It only mentions max7219...

If ssd1306_spi is not fixed by this, then this bug report should not be closed. @jesserockz

clydebarrow commented 1 year ago

It should stop the ESP crashing. You may still see the "SPI not initialised error" which should be reported, but is technically a different bug.

Can you try it and see?

erik3rik commented 1 year ago

I will try after work today. Can't really test it remotely as if it does not work the device becomes bricked and needs cable connected to it to update.

kbx81 commented 1 year ago

@erik3rik the issue was auto-closed because it was mentioned in the PR (which was merged). Still, please test latest dev (or you can add the PR directly, as mentioned above) and let us know if the issue persists.

samuelolteanu commented 1 year ago

max7219 7digit did not crash anymore on esp32s2 wih pr#5446 max7219 or spi components separately.

erik3rik commented 1 year ago

Using:

external_components:
  source: github://pr#5446
  components: [ spi ]

While also having the newer ESPHome 2023.9.1, I still get the [E][spi:078]: SPIDevice not initialised - did you call spi_setup()? error, twice. See the log below. But it is at least improving in the sense that the ESP32 no longer panics during boot and thus is no longer bricked after update.

Yet some kind of problem still exist, and I am unsure what to do about it.

Screenshot 2023-09-29 at 17 22 03

clydebarrow commented 1 year ago

Can you provide the entire YAML?

erik3rik commented 1 year ago

Yes, but its quite long and contain a lot of stuff. I'm sorry anyone else but me have to see this. Anyhow, here goes 1300 rows:

substitutions:
  friendly_name: ESP Clock Radio
  fixed_ip: "192.168.0.123"

esphome:
  name: esp-clock-radio   

esp32:
  board: az-delivery-devkit-v4
  framework:
    type: arduino

preferences:
  flash_write_interval: 5min

# Enable logging
logger:

external_components:
  source: github://pr#5446
  components: [ spi ]

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

  - file: "gfonts://Roboto@500"
    id: roboto_xlarge
    size: 50

  - file: "fonts/fira-sans.two.ttf" #"gfonts://Roboto@300"
    id: roboto_xlargeThin
    size: 55 # 50

  - file: "gfonts://Roboto@100"
    id: roboto_small
    size: 18

  - file: "gfonts://Roboto@100"
    id: roboto_xsmall
    size: 12

  - file: "gfonts://Roboto@100"
    id: roboto_xxsmall
    size: 9

#  - file: "gfonts://Carrois Gothic"
#    id: big_value_font
#   size: 27

    #https://pictogrammers.com/library/mdi/
  - file: "fonts/mdi.ttf"
    id: mdi
    size: 38
    glyphs: 
      - "󰀦" # Warning
      - "󰖍" # Drop off
      - "󰖌" # Drop on
      - "󰔏" # Thermometer
      - "󱯶" # Cloud off
      - "󰅠" # Cloud on
      - "󰇵" # Happy
      - "󰇸" # Sad
      - "󰖨" # Sun
      - "󰖔" # Night
      - "󰜷" # Arrow Up
      - "󰁆" # Arrow Down
      - "󰈐" # Fan on
      - "󰠝" # Fan off
      - "󰅐" # Clock
      - "󰞋" # Help
      - "󰄬" # Check
      - "󱒁" # Watering Can
      - "󰖎" #Humidity
      - "󰀠" #Alarm
      - "󰛨" # Lightbulb
      - "󰖜" # Sunset up
      - "󰗢" # Candle

  - file: "fonts/mdi.ttf"
    id: mdi_xs
    size: 15
    glyphs: 
      - "󰀠" #Alarm
      - "󰵱" # forward-10
      - "󰴪" # rewind-10
      - "󰑐" # Reset

globals:
  - id: has_ever_had_wifi
    type: bool
    restore_value: no
    initial_value: 'false'
  - id: error_level
    type: int
    restore_value: no
    initial_value: '0'
  - id: has_custom_page
    type: bool
    restore_value: no
    initial_value: 'false'
  - id: display_sequence
    type: int
    restore_value: no
    initial_value: '0'
  - id: action_mode
    type: int
    restore_value: no
    initial_value: '0'
  - id: is_night_mode
    type: bool
    restore_value: no
    initial_value: 'true'

# Enable Home Assistant API
api:
  encryption:
    key: !secret api_encryption_key
  services:
    - service: set_contrast
      variables:
        target: int
      then:
        - lambda: id(oled_display).set_contrast(target/100.0);
    - service: play_rtttl
      variables:
        song_str: string
      then:
        - rtttl.play:
            rtttl: !lambda 'return song_str;'

ota:
  password: !secret ota_password

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  reboot_timeout: 15min

  use_address: $fixed_ip

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: $friendly_name Fallback HS
    password: !secret wifi_ap_password

captive_portal:

output:
  - platform: ledc
    pin: GPIO13
    id: rtttl_out
  - platform: ledc
    pin: GPIO25
    id: output_red
  - platform: ledc
    pin: GPIO26
    id: output_green
  - platform: ledc
    pin: GPIO27
    id: output_blue

spi:
  clk_pin: GPIO18
  mosi_pin: GPIO23

sensor:
  - platform: uptime
    name: $friendly_name Uptime
    force_update: false
    unit_of_measurement: s
    icon: mdi:timer
    update_interval: 300s
    accuracy_decimals: 0
  - platform: wifi_signal
    name: $friendly_name WiFi Signal
    update_interval: 300s

  - platform: adc
    pin: GPIO36
    attenuation: auto
    id: illuminance
    name: $friendly_name Illuminance
    update_interval: 10s
    accuracy_decimals: 0     
    unit_of_measurement: lx
    device_class: illuminance
    filters:
    - median:
        window_size: 7
        send_every: 6
        send_first_at: 6
    - lambda: |-
        return (x * 320.0);

  - platform: dht
    pin: GPIO21
    model: DHT22
    temperature:
      name: $friendly_name Temperature
      id: internal_temperature
      accuracy_decimals: 1
    humidity:
      name: $friendly_name Humidity
      id: internal_humidity
      accuracy_decimals: 0                                                                                                                                                                                                      
    update_interval: 60s

rtttl:
  output: rtttl_out

display:
  - platform: ssd1306_spi
    model: "SH1106 128x64"
    cs_pin: GPIO05
    dc_pin: GPIO22
    reset_pin: GPIO19
    contrast: 1%
    id: oled_display
    flip_y: true
    pages:
      - id: page_default
        lambda: |-
          if(id(is_night_mode)) {
            // it.printf(64, -10, id(roboto_xlargeThin), TextAlign::TOP_CENTER, "%s", id(current_time).state.c_str());
            it.printf(64, -15, id(roboto_xlargeThin), TextAlign::TOP_CENTER, "%s", id(current_time).state.c_str());
          }else{
            it.printf(64, -10, id(roboto_xlarge),     TextAlign::TOP_CENTER, "%s", id(current_time).state.c_str());
          }

          it.print(1, 62, id(mdi_xs),          TextAlign::BOTTOM_LEFT, "󰀠");
          it.printf(16, 62, id(roboto_xsmall), TextAlign::BOTTOM_LEFT, "%s", id(wake_time).state.c_str()); 

          it.printf(126, 62, id(roboto_xsmall), TextAlign::BOTTOM_RIGHT, "%s", id(room_temperature).state.c_str()); 

      - id: page_alarm
        lambda: |-
          it.printf(0, 0, id(mdi_xs), TextAlign::TOP_LEFT, "%s", "󰴪"); 
          it.printf(64,0 , id(mdi_xs), TextAlign::TOP_CENTER, "%s", "󰵱"); 
          it.printf(127, 0, id(mdi_xs), TextAlign::TOP_RIGHT, "%s", "󰑐"); 

          it.print(1, 63, id(mdi),           TextAlign::BOTTOM_LEFT, "󰀠");
          it.printf(43, 60, id(roboto_large), TextAlign::BOTTOM_LEFT, "%s", id(wake_time).state.c_str()); 

      - id: page_lighting
        lambda: |-
          it.printf(0, 0, id(roboto_xsmall), TextAlign::TOP_LEFT, "%s", "Cozy"); 
          it.printf(64,0 , id(roboto_xsmall), TextAlign::TOP_CENTER, "%s", "Auto"); 
          it.printf(127, 0, id(roboto_xsmall), TextAlign::TOP_RIGHT, "%s", "High"); 

          it.print(1, 63, id(mdi),           TextAlign::BOTTOM_LEFT, "󰛨");
          it.printf(43, 60, id(roboto_large), TextAlign::BOTTOM_LEFT, "%s", id(room_scene).state.c_str()); 

      - id: page_fan
        lambda: |-
          it.printf(0, 0, id(roboto_xsmall), TextAlign::TOP_LEFT, "%s", "Off"); 
          it.printf(64,0 , id(roboto_xsmall), TextAlign::TOP_CENTER, "%s", "On"); 
          it.printf(127, 0, id(roboto_xsmall), TextAlign::TOP_RIGHT, "%s", "Turbo"); 

          it.print(1, 63, id(mdi),           TextAlign::BOTTOM_LEFT, "󰈐");
          it.printf(43, 60, id(roboto_large), TextAlign::BOTTOM_LEFT, "%s", id(room_fan).state.c_str()); 

      - id: page_wifi_ok
        lambda: |-
          it.print(64, -1, id(mdi),          TextAlign::CENTER_HORIZONTAL, "󰅠");
          it.print(64, 36, id(roboto_large), TextAlign::CENTER_HORIZONTAL, "WiFi OK");

      - id: page_wifi_err
        lambda: |-
          it.print(64, -1, id(mdi),          TextAlign::CENTER_HORIZONTAL, "󰇸");
          it.print(64, 36, id(roboto_large), TextAlign::CENTER_HORIZONTAL, "No WiFi");

      - id: page_check
        lambda: |-
          it.print(64, -1, id(mdi),          TextAlign::CENTER_HORIZONTAL, "󰄬");
          it.print(64, 36, id(roboto_large), TextAlign::CENTER_HORIZONTAL, "Done");

      - id: page_sleep
        lambda: |-
          it.print(64, -1, id(mdi),          TextAlign::CENTER_HORIZONTAL, "󰖔");
          it.print(64, 36, id(roboto_large), TextAlign::CENTER_HORIZONTAL, "Sleep");

      - id: page_wake
        lambda: |-
          it.print(64, -1, id(mdi),          TextAlign::CENTER_HORIZONTAL, "󰖜");
          it.print(64, 36, id(roboto_large), TextAlign::CENTER_HORIZONTAL, "Wakeup");

      - id: page_cozy
        lambda: |-
          it.print(64, -1, id(mdi),          TextAlign::CENTER_HORIZONTAL, "󰗢");
          it.print(64, 36, id(roboto_large), TextAlign::CENTER_HORIZONTAL, "Cozy");

      - id: page_unknown_page
        lambda: |-
          it.print(64, -1, id(mdi),           TextAlign::CENTER_HORIZONTAL, "󰞋");
          it.print(64, 36, id(roboto_large),  TextAlign::CENTER_HORIZONTAL, "Unknown");

      - id: page_boot
        lambda: |-
          it.print(64, -1, id(mdi),           TextAlign::CENTER_HORIZONTAL, "󰅐");
          it.print(64, 51, id(roboto_xsmall),  TextAlign::BOTTOM_CENTER, "Clock Radio");
          it.printf(64, 63, id(roboto_xxsmall), TextAlign::BOTTOM_CENTER, "%s", id(version_string).state.c_str()); 

      - id: page_blank
        lambda: ""

#        - OFF for at least 350ms
binary_sensor:
  - platform: gpio
    id: button_1
    pin:
      number: GPIO39
      mode:
        input: true
        #pullup: true (external pull-down)
      inverted: false

    on_multi_click:
      - timing:
          - ON for at most 399ms
        then:
          - if:
              condition:
                - lambda: return !id(display_switch).state;
              then:
                - switch.turn_on: display_switch
              else:
                - script.execute: toggle_mode
      - timing:
          - ON for at least 400ms
        then:
          - script.execute: confirm

  - platform: gpio
    id: button_2
    pin:
      number: GPIO34
      mode:
        input: true
        #pullup: true (external pull-down)
      inverted: false
    on_multi_click:
      - timing:
          - ON for at most 399ms
        then:
          - script.execute: button_2_short_press
      - timing:
          - ON for at least 400ms
        then:
          - script.execute: button_2_long_press

  - platform: gpio
    id: button_3
    pin:
      number: GPIO35
      mode:
        input: true
        #pullup: true (external pull-down)
      inverted: false
    on_multi_click:
      - timing:
          - ON for at most 399ms
        then:
          - script.execute: button_3_short_press
      - timing:
          - ON for at least 400ms
        then:
          - script.execute: button_3_long_press

  - platform: gpio
    id: button_4
    on_press:
      then:
        script.execute: button_4_short_press
    pin:
      number: GPIO32
      mode:
        input: true
        pulldown: true
      inverted: false

  - platform: gpio
    id: button_5
    on_multi_click:
      - timing:
          - ON for at most 399ms
        then:
          - if:
              condition:
                text_sensor.state:
                  id: room_scene
                  state: 'Sleeping'
              then:
                - homeassistant.service:
                    service: script.turn_on
                    data:
                      entity_id: script.trigger_bedroom_wakeup
                - script.execute:
                    id: show_status_page
                    page: page_wake
                - delay: 5s
                - lambda: 'id(action_mode) = 0;'
                - script.execute: show_default_page
              else:

                - homeassistant.service:
                    service: script.turn_on
                    data:
                      entity_id: script.trigger_bedroom_sleep
                - script.execute:
                    id: show_status_page
                    page: page_sleep
                - delay: 5s
                - lambda: 'id(action_mode) = 0;'
                - script.execute: show_default_page
      - timing:
          - ON for at least 400ms
        then:
            - homeassistant.service:
                service: script.turn_on
                data:
                  entity_id: script.trigger_bedroom_cozy
            - script.execute:
                id: show_status_page
                page: page_cozy
            - delay: 5s
            - lambda: 'id(action_mode) = 0;'
            - script.execute: show_default_page

    pin:
      number: GPIO33
      mode:
        input: true
        pulldown: true
      inverted: false

  - platform: gpio
    id: button_6
    name: $friendly_name Extra Button
    pin:
      number: GPIO4
      mode:
        input: true
        pullup: true
      inverted: true

light:
  - platform: rgb
    name: $friendly_name RGB
    id: status_light
    restore_mode: ALWAYS_OFF
    red: output_red
    green: output_green
    blue: output_blue
    effects:
      - strobe:
          name: Strobe
          colors:
            - state: true
              brightness: 100%
              red: 100%
              green: 100%
              blue: 100%
              duration: 100ms
            - state: false
              duration: 100ms
            - state: true
              brightness: 100%
              red: 100%
              green: 100%
              blue: 100%
              duration: 100ms
            - state: false
              duration: 100ms
            - state: true
              brightness: 100%
              red: 100%
              green: 100%
              blue: 100%
              duration: 100ms
            - state: false
              duration: 500ms
            - state: true
              brightness: 100%
              red: 100%
              green: 100%
              blue: 100%
              duration: 500ms
            - state: false
              duration: 500ms
  - platform: neopixelbus
    type: GRB
    variant: WS2811
    pin: GPIO16
    num_leds: 8
    name: "$friendly_name NeoPixel"
    id: neopoxel
    effects:
      - addressable_fireworks:
          name: Fireworks Effect
          update_interval: 32ms
          spark_probability: 20%
          use_random_color: false
          fade_out_rate: 120
      - addressable_random_twinkle:
          name: Random Twinkle Effect
          twinkle_probability: 5%
          progress_interval: 32ms
      - strobe:
          name: Strobe
          colors:
            - state: true
              brightness: 100%
              red: 100%
              green: 100%
              blue: 100%
              duration: 50ms
            - state: false
              duration: 50ms
            - state: true
              brightness: 100%
              red: 100%
              green: 100%
              blue: 100%
              duration: 50ms
            - state: false
              duration: 50ms
            - state: true
              brightness: 100%
              red: 100%
              green: 100%
              blue: 100%
              duration: 50ms
            - state: false
              duration: 50ms
            - state: true
              brightness: 100%
              red: 100%
              green: 100%
              blue: 100%
              duration: 50ms
            - state: false
              duration: 500ms
            - state: true
              brightness: 100%
              red: 100%
              green: 100%
              blue: 100%
              duration: 500ms
            - state: false
              duration: 500ms
      - addressable_flicker:
          name: Flicker Effect With Custom Values
          update_interval: 16ms
          intensity: 20%
      - addressable_color_wipe:
          name: Color Wipe Effect With Custom Values
          colors:
            - red: 50%
              green: 50%
              blue: 50%
              num_leds: 1
            - red: 100%
              green: 100%
              blue: 100%
              num_leds: 1
            - red: 50%
              green: 50%
              blue: 50%
              num_leds: 1
            - red: 0%
              green: 0%
              blue: 0%
              num_leds: 7
            - red: 50%
              green: 0%
              blue: 0%
              num_leds: 1
            - red: 100%
              green: 0%
              blue: 0%
              num_leds: 1
            - red: 50%
              green: 0%
              blue: 0%
              num_leds: 1
            - red: 0%
              green: 0%
              blue: 0%
              num_leds: 7
            - red: 0%
              green: 0%
              blue: 50%
              num_leds: 1
            - red: 0%
              green: 0%
              blue: 100%
              num_leds: 1
            - red: 0%
              green: 0%
              blue: 50%
              num_leds: 1
            - red: 0%
              green: 0%
              blue: 0%
              num_leds: 7
            - red: 50%
              green: 50%
              blue: 0%
              num_leds: 1
            - red: 100%
              green: 100%
              blue: 0%
              num_leds: 1
            - red: 50%
              green: 50%
              blue: 0%
              num_leds: 1
            - red: 0%
              green: 0%
              blue: 0%
              num_leds: 7
            - red: 0%
              green: 50%
              blue: 50%
              num_leds: 1
            - red: 0%
              green: 100%
              blue: 100%
              num_leds: 1
            - red: 0%
              green: 50%
              blue: 50%
              num_leds: 1
            - red: 0%
              green: 0%
              blue: 0%
              num_leds: 7
            - red: 50%
              green: 0%
              blue: 50%
              num_leds: 1
            - red: 100%
              green: 0%
              blue: 100%
              num_leds: 1
            - red: 50%
              green: 0%
              blue: 50%
              num_leds: 1
            - red: 0%
              green: 0%
              blue: 0%
              num_leds: 7

          add_led_interval: 150ms
          reverse: false
      - addressable_scan:
          name: Bounce
          move_interval: 100ms
          scan_width: 1
      - automation:
          name: Strobe Automation 1
          sequence:
            - light.addressable_set:
                id: neopoxel
                range_from: 0
                range_to: 3
                red: 0%
                green: 0%
                blue: 0%
            - light.addressable_set:
                id: neopoxel
                range_from: 4
                range_to: 7
                red: 100%
                green: 100%
                blue: 100%
            - delay: 100ms
            - light.addressable_set:
                id: neopoxel
                range_from: 0
                range_to: 7
                red: 0%
                green: 0%
                blue: 0%
            - delay: 100ms
            - light.addressable_set:
                id: neopoxel
                range_from: 0
                range_to: 3
                red: 100%
                green: 100%
                blue: 100%
            - light.addressable_set:
                id: neopoxel
                range_from: 4
                range_to: 7
                red: 0%
                green: 0%
                blue: 0%
            - delay: 100ms
            - light.addressable_set:
                id: neopoxel
                range_from: 0
                range_to: 7
                red: 0%
                green: 0%
                blue: 0%
            - delay: 300ms

            - light.addressable_set:
                id: neopoxel
                range_from: 0
                range_to: 3
                red: 0%
                green: 0%
                blue: 0%
            - light.addressable_set:
                id: neopoxel
                range_from: 4
                range_to: 7
                red: 100%
                green: 100%
                blue: 100%
            - delay: 200ms
            - light.addressable_set:
                id: neopoxel
                range_from: 4
                range_to: 7
                red: 0%
                green: 0%
                blue: 0%
            - light.addressable_set:
                id: neopoxel
                range_from: 0
                range_to: 3
                red: 100%
                green: 100%
                blue: 100%
            - delay: 200ms
            - light.addressable_set:
                id: neopoxel
                range_from: 0
                range_to: 7
                red: 0%
                green: 0%
                blue: 0%
            - delay: 300ms
      - strobe:
          name: Strobe Automation 2
          colors:
            - state: true
              brightness: 100%
              red: 0%
              green: 100%
              blue: 100%
              duration: 50ms
            - state: false
              duration: 50ms
            - state: true
              brightness: 100%
              red: 0%
              green: 100%
              blue: 100%
              duration: 50ms
            - state: false
              duration: 50ms
            - state: true
              brightness: 100%
              red: 0%
              green: 100%
              blue: 100%
              duration: 50ms
            - state: false
              duration: 50ms
            - state: true
              brightness: 100%
              red: 0%
              green: 100%
              blue: 100%
              duration: 50ms
            - state: false
              duration: 500ms
            - state: true
              brightness: 100%
              red: 0%
              green: 100%
              blue: 100%
              duration: 500ms
            - state: false
              duration: 500ms

script:
  - id: toggle_mode
    mode: single
    then:
      - logger.log:
          format: "Mode before %i"
          args: [ 'id(action_mode)']

      - lambda: 'id(action_mode) = (id(action_mode) +1) % 4;'

      - logger.log:
          format: "Mode after %i"
          args: [ 'id(action_mode)']
      - script.execute: update_page
  - id: update_page
    mode: single
    then:
      - if:
          condition:
            - lambda: return id(action_mode) == 0;
          then:
            - script.execute:
                id: show_default_page
      - if:
          condition:
            - lambda: return id(action_mode) == 1;
          then:
            - script.execute:
                id: show_status_page
                page: page_alarm
      - if:
          condition:
            - lambda: return id(action_mode) == 2;
          then:
            - script.execute:
                id: show_status_page
                page: page_lighting

      - if:
          condition:
            - lambda: return id(action_mode) == 3;
          then:
            - script.execute:
                id: show_status_page
                page: page_fan

  - id: show_status_page
    mode: single
    parameters:
      page: string
    then:
      - globals.set:
          id: has_custom_page
          value: 'true'
      - display.page.show: !lambda |-

          if (page  == "page_check") {
            return id(page_check);
          }
          if (page  == "page_sleep") {
            return id(page_sleep);
          }
          if (page  == "page_wake") {
            return id(page_wake);
          }
          if (page  == "page_cozy") {
            return id(page_cozy);
          }
          if (page  == "page_wifi_ok") {
            return id(page_wifi_ok);
          }
          if (page  == "page_wifi_err") {
            return id(page_wifi_err);
          }
          if (page  == "page_alarm") {
            return id(page_alarm);
          }
          if (page  == "page_lighting") {
            return id(page_lighting);
          }
          if (page  == "page_fan") {
            return id(page_fan);
          }
          if (page  == "page_boot") {
            return id(page_boot);
          }
          if (page  == "page_blank") {
            return id(page_blank);
          }
          return id(page_unknown_page);
      - component.update: oled_display

  - id: show_default_page
    mode: single
    then:
      - globals.set:
          id: has_custom_page
          value: 'false'
      - display.page.show: page_default
      - component.update: oled_display

  - id: confirm
    mode: single
    then:
      - light.turn_on:
            id: status_light
            red: 0
            green: 100%
            blue: 0
            brightness: 25%
            transition_length: 100ms
      - script.execute:
          id: show_status_page
          page: page_check
      - delay: 300ms
      - light.turn_off: status_light
      - delay: 1700ms
      - lambda: 'id(action_mode) = 0;'
      - script.execute: show_default_page

  - id: button_2_short_press
    mode: queued
    then:
      - logger.log: "Button 2 short press"
      - if:
          condition:
            - lambda: return id(action_mode) == 1; // Alarm
          then: 
            - logger.log: 'Mode is 1'
            - homeassistant.service:
                service: script.turn_on
                data:
                  entity_id: script.wakeup_earlier
            - delay: 100ms
            - component.update: oled_display   
      - if:
          condition:
            - lambda: return id(action_mode) == 2; // Lighting
          then:
            - logger.log: 'Mode is 2'
            - homeassistant.service:
                service: switch.turn_on
                data:
                  entity_id: switch.bedroom_cozy
      - if:
          condition:
            - lambda: return id(action_mode) == 3; // Fan
          then:
            - logger.log: 'Mode is 3'
            - homeassistant.service:
                service: switch.turn_on
                data:
                  entity_id: switch.ir_electrolux_state_off

      - if:
          condition:
            - lambda: return id(action_mode) == 0; // Main
          then: 
            - logger.log: 'Mode is 0'
            - lambda: 'id(action_mode) = 1;'
            - delay: 100ms
            - script.execute: update_page

  - id: button_2_long_press
    mode: queued
    then:
      - if:
          condition:
            - lambda: return id(action_mode) == 1; // Alarm
          then: 
            - homeassistant.service:
                service: script.turn_on
                data:
                  entity_id: script.wakeup_earlier_5m

  - id: button_3_short_press
    mode: queued
    then:
      - if:
          condition:
            - lambda: return id(action_mode) == 1; // Alarm
          then: 
            - homeassistant.service:
                service: script.turn_on
                data:
                  entity_id: script.wakeup_later
            - delay: 100ms
            - component.update: oled_display                
      - if:
          condition:
            - lambda: return id(action_mode) == 2; // Lighting
          then:
            - homeassistant.service:
                service: switch.turn_on
                data:
                  entity_id: switch.bedroom_auto
      - if:
          condition:
            - lambda: return id(action_mode) == 3; // Fan
          then: 
            - homeassistant.service:
                service: switch.turn_on
                data:
                  entity_id: switch.ir_electrolux_state_normal
      - if:
          condition:
            - lambda: return id(action_mode) == 0; // Main
          then: 
            - logger.log: 'Mode is 0'
            - lambda: 'id(action_mode) = 2;'
            - delay: 100ms
            - script.execute: update_page

  - id: button_3_long_press
    mode: queued
    then:
      - if:
          condition:
            - lambda: return id(action_mode) == 1; // Alarm
          then: 
          - homeassistant.service:
              service: script.turn_on
              data:
                entity_id: script.wakeup_later_5m
      - if:
          condition:
            - lambda: return id(action_mode) == 3; // Fan
          then: 
            - homeassistant.service:
                service: switch.turn_on
                data:
                  entity_id: switch.ir_electrolux_fan_speed

  - id: button_4_short_press
    mode: queued
    then:

      - if:
          condition:
            - lambda: return id(action_mode) == 2; // Lighting
          then:
            - homeassistant.service:
                service: switch.turn_on
                data:
                  entity_id: switch.bedroom_high
      - if:
          condition:
            - lambda: return id(action_mode) == 3; // Fan
          then: 
          - homeassistant.service:
              service: switch.turn_on
              data:
                entity_id: switch.ir_electrolux_state_turbo

      - if:
          condition:
            - lambda: return id(action_mode) == 0; // Main
          then: 
            - logger.log: 'Mode is 0'
            - lambda: 'id(action_mode) = 3;'
            - delay: 100ms
            - script.execute: update_page

button:
  - platform: template
    name: $friendly_name Play Mario
    on_press:
      then:
       - rtttl.play: 'Mario:d=4,o=5,b=100:16e6,16e6,32p,8e6,16c6,8e6,8g6,8p,8g,8p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,16p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16c7,16p,16c7,16c7,p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16d#6,8p,16d6,8p,16c6'
  - platform: template
    name: $friendly_name Play Simpsons
    on_press:
      then:
       - rtttl.play: 'Simpsons:d=4,o=5,b=160:c.6,e6,f#6,8a6,g.6,e6,c6,8a,8f#,8f#,8f#,2g,8p,8p,8f#,8f#,8f#,8g,a#.,8c6,8c6,8c6,c6'
  - platform: template
    name: $friendly_name Play Starwars
    on_press:
      then:
       - rtttl.play: 'StarWars:d=4,o=5,b=45:32p,32f#,32f#,32f#,8b.,8f#.6,32e6,32d#6,32c#6,8b.6,16f#.6,32e6,32d#6,32c#6,8b.6,16f#.6,32e6,32d#6,32e6,8c#.6,32f#,32f#,32f#,8b.,8f#.6,32e6,32d#6,32c#6,8b.6,16f#.6,32e6,32d#6,32c#6,8b.6,16f#.6,32e6,32d#6,32e6,8c#6'
  - platform: template
    name: $friendly_name Play Bond
    on_press:
      then:
       - rtttl.play: 'Bond:d=4,o=5,b=80:32p,16c#6,32d#6,32d#6,16d#6,8d#6,16c#6,16c#6,16c#6,16c#6,32e6,32e6,16e6,8e6,16d#6,16d#6,16d#6,16c#6,32d#6,32d#6,16d#6,8d#6,16c#6,16c#6,16c#6,16c#6,32e6,32e6,16e6,8e6,16d#6,16d6,16c#6,16c#7,c.7,16g#6,16f#6,g#.6'
  - platform: template
    name: $friendly_name Play Adams Family
    on_press:
      then:
       - rtttl.play: 'AdamsFamily:d=8,o=5,b=160,b=160:c,4f,a,4f,c,4b4,2g,f,4e,g,4e,g4,4c,2f,c,4f,a,4f,c,4b4,2g,f,4e,c,4d,e,1f,c,d,e,f,1p,d,e,f#,g,1p,d,e,f#,g,4p,d,e,f#,g,4p,c,d,e,f'
  - platform: template
    name: $friendly_name Play Popcorn
    on_press:
      then:
       - rtttl.play: 'Popcorn:d=16,o=5,b=160,b=160:a,p,g,p,a,p,e,p,c,p,e,p,8a4,8p,a,p,g,p,a,p,e,p,c,p,e,p,8a4,8p,a,p,b,p,c6,p,b,p,c6,p,a,p,b,p,a,p,b,p,g,p,a,p,g,p,a,p,f,8a,8p,a,p,g,p,a,p,e,p,c,p,e,p,8a4,8p,a,p,g,p,a,p,e,p,c,p,e,p,8a4,8p,a,p,b,p,c6,p,b,p,c6,p,a,p,b,p,a,p,b,p,g,p,a,p,g,p,a,p,b,4c6'
  - platform: template
    name: $friendly_name Play Happy Birthday
    on_press:
      then:
       - rtttl.play: 'HappyBirthday:d=4,o=5,b=125:8g.,16g,a,g,c6,2b,8g.,16g,a,g,d6,2c6,8g.,16g,g6,e6,c6,b,a,8f6.,16f6,e6,c6,d6,2c6,8g.,16g,a,g,c6,2b,8g.,16g,a,g,d6,2c6,8g.,16g,g6,e6,c6,b,a,8f6.,16f6,e6,c6,d6,2c6'

switch:
  - platform: template
    name: "$friendly_name Night Mode"
    id: night_mode_switch
    restore_mode: ALWAYS_ON
    lambda: |-
      return id(is_night_mode);
    turn_on_action:
      - globals.set:
          id: is_night_mode
          value: 'true'
      - lambda: id(oled_display).set_contrast(0.01);
      - component.update: oled_display
    turn_off_action:
      - globals.set:
          id: is_night_mode
          value: 'false'
      - lambda: id(oled_display).set_contrast(0.95);
      - component.update: oled_display
  - platform: template
    id: display_switch
    name: "$friendly_name Display"
    lambda: |-
      return id(oled_display).get_active_page() != id(page_blank);
    turn_on_action:
      - lambda: 'id(action_mode) = 0;'
      - script.execute:
          id: show_default_page
    turn_off_action:
      - script.execute:
          id: show_status_page
          page: page_blank

interval:
  - interval: 6sec
    then:
      - globals.set:
          id: display_sequence
          value: !lambda |-
            return (id(display_sequence) + 1);
      - if:
          condition:
            wifi.connected:
          then: 
            - if:
                condition:
                  - lambda: |-    
                      return !id(has_ever_had_wifi);
                then:
                  - logger.log: "WiFi OK!"
                  - light.turn_on:
                      id: neopoxel
                      red: 0
                      green: 100%
                      blue: 0
                      brightness: 50%
                  - globals.set:
                      id: has_ever_had_wifi
                      value: 'true'
                  - script.execute:
                      id: show_status_page
                      page: page_wifi_ok
                  - delay: 2s
                  - light.turn_off:
                      id: neopoxel
                  - if:
                      condition:
                        - lambda: |-    
                            return id(display_sequence) < 3;
                      then:
                        - script.execute:
                            id: show_status_page
                            page: page_boot
                      else:
                        - script.execute:
                            id: show_default_page
                else:
                  - if:
                      condition:
                        - lambda: |-    
                            return !id(has_custom_page);
                      then:
                        - script.execute:
                            id: show_default_page

          else:
            - logger.log: No WiFi"

            - if:
                condition:
                  - lambda: |-    
                      return id(display_sequence) < 2;
                then:
                  - light.turn_on:
                      id: neopoxel
                      red: 100%
                      green: 0
                      blue: 100%
                      brightness: 50%
                  - script.execute:
                      id: show_status_page
                      page: page_boot
                else:
                  - light.turn_on:
                      id: neopoxel
                      red: 100%
                      green: 0
                      blue: 0
                      brightness: 60%
                  - script.execute:
                      id: show_status_page
                      page: page_wifi_err
            - globals.set:
                id: has_ever_had_wifi
                value: 'false'

text_sensor:
  - platform: version
    id: version_string
    name: $friendly_name Version
  - platform: wifi_info
    ip_address:
      name: $friendly_name IP
    ssid:
      name: $friendly_name SSID    
  - platform: homeassistant
    id: current_time
    entity_id: sensor.clock_radio_time
  - platform: homeassistant
    id: wake_time
    entity_id: sensor.clock_radio_alarm
  - platform: homeassistant
    id: room_fan
    entity_id: sensor.clock_radio_fan
  - platform: homeassistant
    id: room_temperature
    entity_id: sensor.clock_radio_temperature
  - platform: homeassistant
    id: room_scene
    entity_id: sensor.clock_radio_scene
clydebarrow commented 1 year ago

Ok, found the problem. In your YAML:

switch:
  - platform: template
    name: "$friendly_name Night Mode"
    id: night_mode_switch
    restore_mode: ALWAYS_ON
    lambda: |-
      return id(is_night_mode);
    turn_on_action:
      - globals.set:
          id: is_night_mode
          value: 'true'
      - lambda: id(oled_display).set_contrast(0.01);           <-------------
      - component.update: oled_display

The switch gets turned on as part of after-boot restore, so set_contrast call is done before the display has been setup, so the SPI is not initialised at that time.

It might be argued that the ssd1306 driver is structured incorrectly, as it probably should not write directly to the SPI when values are changed, but simply store the value and update it when its update() method is called (which will be after setup.)

For your YAML you might want to defer setting contrast or any other direct call to the display in lambdas until after setup is complete.

clydebarrow commented 1 year ago

I would note that the initial on-restore set_contrast() probably never worked anyway, it just silently failed.

erik3rik commented 1 year ago

I tried commenting out that line with set_contrast() and the error disappeared.

I would note that the initial on-restore set_contrast() probably never worked anyway, it just silently failed.

Perhaps.

Yet, I want the behaviour of that switch to adjust the contrast of the display. Both the turn_on_action and the turn_off_action adjusts the contrast of the display. And that is how I want it to be. And since the state is binary (it is either on or off) the button will be restored to any of those two states, and both of them will set the contrast --> error message.

I could of course wrap that row in an additional if-clause to prevent that row to happen too early, yet be there in all other situations. It would not really have an impact on the behaviour of the device as it is essentially always connected to power (it typically only reboots to bump the ESPHome software). But it would make an already messy code even messier.

It might be argued that the ssd1306 driver is structured incorrectly, as it probably should not write directly to the SPI when values are changed, but simply store the value and update it when its update() method is called (which will be after setup.)

I totally agree. And since that is unfortunately not the current behaviour then I guess I would have to go for that if-clause...

clydebarrow commented 1 year ago

Or you can just leave things as they are and ignore the messages in the log. Other than that message, nothing now behaves differently to what it did before (now that the crashing, which was totally my fault, has been fixed.)

That's probably what I'd do for now at least.

clydebarrow commented 1 year ago

Aha, I found another solution - add a line to your yaml to make the display have higher setup priority than the switch.

display:
  - platform: ssd1306_spi
    setup_priority: 950

The display by default gets set up after the switches. Template switches have priority HARDWARE which seems odd since they are not hardware (if I were King I would call them "virtual" switches, not templates :-) Displays, or at least the ssd1306, have priority PROCESSOR which also makes little sense to me.

Anyway, in your case you want the display to have a higher priority than anything that touches it.