ESPHome without WiFi/Server (either not at boot time or disconnect during runtime)

Do have a question about handling of ESPHome devices when either left connection to Wifi or to MQTT broker. For me it is an intended mode of operation that device boots (!) and operates without connectivity and also continues its operation if connection is lost (in it’s limits of course)

I didn’t found much about, mainly the hint to set reboot_timeout= ‘0s’ for both MQTT and WIFI. But isn’t there more? Can someone with more experience give some helping hints?

No - that’s it. I have a few standalone devices. If you are never going to connect to HA you can also get rid of api:.

OK.
A bit OT, but have you ever though of transferring timing params (like do a rollerblind once in morning/evening) via MQTT? I have it fixed programmed timings now, but don’t want to “reprogramm” the thing, just to change the time of opening.

My offline devices are my proofing cabinets - they tend to be switched on then run for 12 hours or so, then off again.

They do work better with some sort of internet access - so that they can display the correct time on the screen. I really should add a RTC to them.

But yes - there is no reason why you couldn’t connect once to them then never again - should work fine.

Mh, my device does following while booting without network:
It does 10x blink the status led. Then for a short moment another LED(s) are on and than blinking starts again. That looks a bit like a bootloop to me. But how to proof with no network to log it?
And yes, I did for boot: wifi and mqtt the “reboot_timeout: ‘0s’”

Edit:
I’ll do some blinking code into esphome: in on_boot. That should tell me that a reboot happens (as I think). But why?

Post your yaml, otherwise we would be just guessing. You should be able to gather serial logs as well if you can plug the device into whatever is running the ESPHome add-on.

I just showdown my mqtt broker and observed following (no complain, just observation):
status led blinks once a second, and it try to reconnect:

WARNING Disconnected from MQTT (7). Trying to reconnect in 1 s
WARNING Disconnected from MQTT (7). Trying to reconnect in 2 s
WARNING Disconnected from MQTT (7). Trying to reconnect in 4 s
WARNING Disconnected from MQTT (7). Trying to reconnect in 8 s
WARNING Disconnected from MQTT (7). Trying to reconnect in 16 s
WARNING Disconnected from MQTT (7). Trying to reconnect in 32 s
WARNING Disconnected from MQTT (7). Trying to reconnect in 64 s
WARNING Disconnected from MQTT (7). Trying to reconnect in 128 s
WARNING Disconnected from MQTT (7). Trying to reconnect in 256 s
WARNING Disconnected from MQTT (7). Trying to reconnect in 300 s

I am wondering how that works for Wifi, as I don’t want to waste energy if Wifi is off for several hours, days or even weeks. For MQTT it does well, with increasing period of attempts of reconnect.

Nevertheless: here the complete rollershutter project so far:

####################################################
# short up/down: cover up/down
# any key if cover is moving: stop
# stop while not moving: cover to pref_pos
#
# up   (1-3s) switch up (overrule cover)
# down (1-3s) switch down (overrule cover)
#
# up   (>3s)  reboot
# down (>3s)  toogle LED
#
# stop (1-3)  toggle timer (middle red)
# stop (>3s)  set a new pref_pos
#
# mosquitto_pub -h 192.168.3.20 -t moes/set_timer -m '{"timers": [[1, 6 , 30, 50], [2, 8, 0, 50], [3, 21, 45, 20]]}'
####################################################

esphome:
  name: moes
  on_boot:
    priority: -10
    then:
      - logger.log: "run on_boot"
      - output.turn_on: gpio_backlight
      - delay: 500ms
      - output.turn_off: gpio_backlight
      - delay: 500ms
      - output.turn_on: gpio_backlight
      - delay: 500ms
      - output.turn_off: gpio_backlight
      - delay: 500ms
      - output.turn_on: gpio_backlight

#      - lambda: 'id(pref_pos) += 1;'
#      - lambda: 'id(backlight_active) = !id(backlight_active);'
#      - logger.log:
#          format: "after boot incremented pref_pos %i backlight %i"
#          args: [ 'id(pref_pos).state', 'id(backlight_active).state' ]
#      - logger.log: "thats it" 

bk72xx:
  board: generic-bk7231n-qfn32-tuya

logger:

web_server:

captive_portal:

mdns:

#api:
#  password: ""

ota:
  password: ""

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  manual_ip:
    static_ip: 192.168.3.250
    gateway: 192.168.3.1
    subnet: 255.255.255.0
  ap:  
    password: !secret wifi_ap_password
  fast_connect: 'true'
  power_save_mode: 'high'   # Optionen: none, light, high
  output_power: 15dB        # Reduziert die Sendeleistung
  reboot_timeout: '0s'

mqtt:
  broker: "192.168.3.20"
  id: mqtt_moes_client
  reboot_timeout: '0s'

  on_json_message:
    topic: moes/set_timer
    then:
      - lambda: |-
          // Definiere ein 10x4-Array für Timer
          id(timers)[10][4] = {0};

          // Überprüfe, ob das JSON-Objekt ein Array "timers" enthält
          if (!x.containsKey("timers")) {
            ESP_LOGE("main", "Kein 'timers'-Array im JSON gefunden.");
            return;
          }
          JsonArrayConst timers_json = x["timers"].as<JsonArrayConst>();

          // Schleife durch das JSON-Array und übertrage die Werte in das Array
          int i = 0;
          for (JsonVariantConst v : timers_json) {
            if (i >= 10) break;
            JsonArrayConst timer = v.as<JsonArrayConst>();
            int j = 0;
            for (JsonVariantConst value : timer) {
              if (j >= 4) break;
              id(timers)[i][j] = value.as<int>();
              j++;
            }
          i++;
          }
          // Optional: Debug-Ausgabe des Arrays
          for (int i = 0; i < 10; i++) {
            ESP_LOGD("main", "Timer %d: [%d, %d, %d, %d]", i, id(timers)[i][0], id(timers)[i][1], id(timers)[i][2], id(timers)[i][3]);
          }


#  on_message:
#    - topic: moes/pref_pos
#      qos: 0
#      then:
#        lambda: |-
#          id(pref_pos) = atoi(x.c_str());
#          ESP_LOGI("mqtt", "pref_pos via mqtt set to %i", id(pref_pos));
#    - topic: moes/timer
#      qos: 0
#      then:
#        lambda: |-
#          id(timer_active) = atoi(x.c_str());
#          ESP_LOGI("mqtt", "timer_active via mqtt set to %i", id(timer_active));
#    - topic: moes/backlight
#      qos: 0
#      then:
#        lambda: |-
#          if (atoi(x.c_str()) == 0) {
#            ESP_LOGI("mqtt", "backlight off");
#            id(gpio_backlight).turn_off();
#          } else {
#            ESP_LOGI("mqtt", "backlight on");
#            id(gpio_backlight).turn_on();
#          }
#

button:
  - platform: restart
    name: Restart

debug:
  update_interval: 30s

text_sensor:
  - platform: debug
    reset_reason:
      name: Reset Reason
  - platform: libretiny
    version:
      name: LibreTiny Version

sensor:
  - platform: uptime
    name: Uptime
    accuracy_decimals: 0

globals:
  - id: pref_pos
    type: int
    initial_value: '55'
#    restore_value: 'true'
  - id: timer_active
    type: bool
    initial_value: 'true'
#    restore_value: 'true'
  - id: backlight_active
    type: bool
    initial_value: 'true'
#    restore_value: 'true'
  - id: timers
    type: int[10][4]
#    restore_value: 'false'
# 0=off, 1 Mo-Fr, 2=Sa, So, 3 Mo-So / hour / minute / action
    initial_value: '{{
                      {1,  6, 30, 100},
                      {3,  8, 00,  55},
                      {3, 21, 30,  20},
                      {0,  8, 00, 100},
                      {2, 20, 10,  40},
                      {3, 20, 12,  45},
                      {3, 20, 14,  50},
                      {0,  0, 00, 0},
                      {0,  0, 00, 0},
                      {2, 20, 16, 55}
                    }}'

cover:
  - platform: time_based
    name: moes1
    id: my_cover
    open_action:
      - switch.turn_on: switch_up
      - output.turn_on: output_led_up
    open_duration: 26000ms
    close_action:
      - switch.turn_on: switch_down
      - output.turn_on: output_led_down
    close_duration: 25000ms
    stop_action:
      - switch.turn_off: switch_up
      - switch.turn_off: switch_down
      - output.turn_off: output_led_up
      - output.turn_off: output_led_down
    on_open:
      - logger.log: "Cover is/should fully open!"
      - switch.turn_on: switch_up
    on_closed:
      - logger.log: "Cover is/should fully closed!"
      - switch.turn_on: switch_down

output:
  - platform: gpio
    id: output_led_up
    pin: P14
  - platform: gpio
    id: output_led_down
    pin: P23
  - platform: gpio
    id: output_led_stop
    pin: P8
  - platform: gpio
    id: gpio_backlight
    pin: P11

binary_sensor:
  - platform: gpio
    id: binary_switch_up
    pin:
      number: P24
      inverted: true
      mode: INPUT_PULLUP
    on_multi_click:
      - timing:
          - ON for at most 1s
          - OFF for at least 0.1s
        then:
        - logger.log: "press up detected"
        - lambda: |-
            ESP_LOGD("main", "cover.current_operation %i", id(my_cover).current_operation);
            if (id(my_cover).current_operation == CoverOperation::COVER_OPERATION_IDLE) {
              // Cover is idle
              auto call = id(my_cover).make_call();
              call.set_command_open();
              call.perform();
              ESP_LOGD("main", "cover.open");
            } else if (id(my_cover).current_operation == CoverOperation::COVER_OPERATION_OPENING) {
              // Cover is currently opening
              auto call = id(my_cover).make_call();
              call.set_command_stop();
              call.perform();
              ESP_LOGD("main", "already opening: cover.stop");
            } else if (id(my_cover).current_operation == CoverOperation::COVER_OPERATION_CLOSING) {
              // Cover is currently closing
              auto call = id(my_cover).make_call();
              call.set_command_stop();
              call.perform();
              ESP_LOGD("main", "cover.stop");
            }
      - timing:
          - ON for 1s to 3s
          - OFF for at least 0.1s
        then:
        - logger.log: "long press up detected"
        - switch.turn_on: switch_up

      - timing:
          - ON for at least 3s
          - OFF for at least 0.1s
        then:
        - logger.log: "very long press up detected"
        - lambda: |-
            App.safe_reboot();
 
  - platform: gpio
    id: binary_switch_down
    pin:
      number: P7
      inverted: true
      mode: INPUT_PULLUP
    on_multi_click:
      - timing:
          - ON for at most 1s
          - OFF for at least 0.1s
        then:
        - logger.log: "press down detected"
        - lambda: |-
            ESP_LOGD("main", "cover.current_operation %i", id(my_cover).current_operation);
            if (id(my_cover).current_operation == CoverOperation::COVER_OPERATION_IDLE) {
              // Cover is idle
              auto call = id(my_cover).make_call();
              call.set_command_close();
              call.perform();
              ESP_LOGD("main", "idle - cover.close");
            } else if (id(my_cover).current_operation == CoverOperation::COVER_OPERATION_OPENING) {
              // Cover is currently opening
              auto call = id(my_cover).make_call();
              call.set_command_stop();
              call.perform();
              ESP_LOGD("main", "already opening - cover.stop");
            } else if (id(my_cover).current_operation == CoverOperation::COVER_OPERATION_CLOSING) {
              // Cover is currently closing
              auto call = id(my_cover).make_call();
              call.set_command_stop();
              call.perform();
              ESP_LOGD("main", "closing - cover.stop");
            }
      - timing:
          - ON for 1s to 3s
          - OFF for at least 0.1s
        then:
        - logger.log: "long press down detected"
        - lambda: |-
            if (id(backlight_active)) {
              ESP_LOGI("stop", "switch backlight off");
              id(gpio_backlight).turn_off();
            } else {
              ESP_LOGI("stop", "switch backlight on");
              id(gpio_backlight).turn_on();
            }
            id(backlight_active) = !id(backlight_active); 
      - timing:
          - ON for at least 3s
          - OFF for at least 0.1s
        then:
        - logger.log: "very long press down detected"
        - switch.turn_on: switch_down

  - platform: gpio
    id: binary_switch_stop
    pin:
      number: P10
      inverted: true
      mode: INPUT_PULLUP
    on_multi_click:
      - timing:
          - ON for at most 1s
          - OFF for at least 0.1s
        then:
          - logger.log: "press stop detected"
          - lambda: |-
                ESP_LOGD("main", "stop: cover.current_operation %i", id(my_cover).current_operation);
                if (id(my_cover).current_operation == CoverOperation::COVER_OPERATION_IDLE) {
                  ESP_LOGD("main", "act pos %i -> pref pos %i", int(id(my_cover).position*100), id(pref_pos));
                    if (abs(int(id(my_cover).position*100) - id(pref_pos))>1) {
                      auto call = id(my_cover).make_call();
                      call.set_position(id(pref_pos)/100.0);
                      call.perform();                  
                    } else {
                      ESP_LOGD("main", "no action needed, already at right pos");
                    }
                } else {
                  auto call = id(my_cover).make_call();
                  call.set_command_stop();
                  call.perform();
                  ESP_LOGD("main", "pressed stop while running - cover.stop");
                }

      - timing:
          - ON for 1s to 3s
          - OFF for at least 0.1s
        then:
          - logger.log: "Long press stop detected"
          - lambda: |-
              id(timer_active) = !id(timer_active);
              ESP_LOGD("main", "timer_active: %i",id(timer_active));
              if (id(timer_active)) {
                id(output_led_stop).turn_off(); // green LED
              } else {
                id(output_led_stop).turn_on();  // red LED
              }
      - timing:
          - ON for at least 3s
          - OFF for at least 0.1s
        then:
          - logger.log: "Very long press stop detected"
          - lambda: |-
              id(pref_pos) = int(id(my_cover).position*100 + 0.5);
              ESP_LOGD("main", "set new pref_pos %i",id(pref_pos));
                
switch:
  - platform: gpio
    id: switch_up
    name: Relay up
    pin: P6
    interlock: [switch_down]
  - platform: gpio
    id: switch_down
    name: Relay down
    pin: P9
    interlock: [switch_up]

status_led:
  pin: P26

time:
  - platform: sntp
    id: sntp_time
    servers: 192.168.3.1

interval:
  - interval: 1min
    then:
      - lambda: |-
          auto current_time = id(sntp_time).now();
          int day_of_week = current_time.day_of_week;
          if (id(timer_active)) {
            for (int i = 0; i < 10; i++) {
              // timer off
              if (id(timers)[i][0] == 0) {
                ESP_LOGD("timer", "timer %i is off",i);
                continue;
              }
              ESP_LOGD("timer", "check timer %i %i %02i:%02i -> %i",i,  id(timers)[i][0], id(timers)[i][1], id(timers)[i][2], id(timers)[i][3]);
              if ((current_time.hour == id(timers)[i][1]) && (current_time.minute == id(timers)[i][2] )) {
                // this would be right time, but is it right day?
                 ESP_LOGD("timer", "this would be right time, but is it right day? %i %i", id(timers)[i][0],day_of_week);
                if ((((id(timers)[i][0] & 1) && (day_of_week > 1 && day_of_week < 7 )) || \
                    (((id(timers)[i][0] & 2) && (day_of_week == 1 || day_of_week == 7 ))))) {
                  ESP_LOGD("timer", "timer %i is active - go to pos %i",i, id(timers)[i][3]);
                  auto call = id(my_cover).make_call();
                  call.set_position((float)id(timers)[i][3]/100.0);
                  call.perform();
                }
              }
            } // end of loop over timer array
          } else 
            ESP_LOGD("timer", "timers are inactive");

      - mqtt.publish:
          topic: moes/timer_active
          payload: !lambda 'return String(id(timer_active)).c_str();'

      - mqtt.publish:
          topic: moes/pos
          payload: !lambda 'return String(int(id(my_cover).position*100)).c_str();'

      - mqtt.publish:
          topic: moes/pref
          payload: !lambda 'return String(id(pref_pos)).c_str();'

      - mqtt.publish:
          topic: moes/backlight
          payload: !lambda 'return String(id(backlight_active)).c_str();'

Remark: The restore doesn’t work for my plattform. Filed a bug here.