Make ESPHome node fallback when not connected to HA API

I want to make my ESPHome node have some fallback automations in place during infrastructure problems (wifi down, Home Assistant down, etc.)

Currently I use a firmware I wrote for my Wemos D1 mini, which has a motion sensor and a relay controlling a light in a room. It sends the motion data to Home Assistant, and then HA decided if the light should be turned on or not, based on a bunch of factors (time of day, state of other sensors, etc.)

In case it can’t update the state, I’m putting it in a “fallback” state where the motion sensor directly controls the relay with a 90 second timeout for motion until it’s able to send the state again to Home Assistant. This works fine.

I want to move over to ESPHome, because it’s pretty cool from the HA integration perspective, but I can’t figure out how to get this to work in a similar way.

I suspect the solution will be around Template Lambdas and the api::APIConnection::ConnectionState but I don’t know how to really put something useful together.

Any guidance would be appreciated!

For reference, this is how I’m doing it currently (just the relevant part in the loop()):

void loop() {
  HTTPClient http;
  int http_code;

  motion_sensor.update();
  if (motion_sensor.rose()) {
    motion_counter++;
    //Serial.println("Motion detected");
    http.begin(MOTION_STATE);
    http.addHeader("Content-Type", "application/json");
    sprintf(buff, MOTION_MSG, motion_counter);
    http_code = http.sendRequest("POST", String(buff));
    if (http_code < 0) {
      standalone_mode = true;
      standalone_timer = millis();
    }
    else {
      standalone_mode = false;
    }
    Serial.printf("[HTTP] POST return code: %d\n", http_code);
    http.end();
  }

  if (standalone_mode) {
    if (relay_state && (millis() - standalone_timer > standalone_timeout)) {
      relay_state = false;
      digitalWrite(RELAY_PIN, relay_state);
    }
    else if (!relay_state && (millis() - standalone_timer < standalone_timeout)) {
      relay_state = true;
      digitalWrite(RELAY_PIN, relay_state);
    }
  }

  server.handleClient();
  ArduinoOTA.handle();
}

Try this https://esphome.io/components/binary_sensor/status.html

You should be able to check the state property.

Why make it so complicated ? I had that issue as I use some shelly cloud relay (esp based) for lights control and wanted it to be working even without network/wifi or ha as it’s light in stairs and bedrooms ! so I made the automation (including for timers) in ESP and I can see states or interact/force them in ha but in all matters it works by itself. Isn’t it easier ? and also makes less processing and basic logic to handle in HA !

Thank you for both of the suggestions! I will try the sensor status and mull over whether I can move all the automation logic into the ESP. Both seem very promising ways to solve this.

For the record, the binary_sensor.status method doesn’t work.

I tried it the following way:

binary_sensor:
  - platform: gpio
    pin: D4
    name: "Bath Motion"
    id: bath_motion
    device_class: motion
    on_press:
      then:
        - lambda: |-
            ESP_LOGD("main", "Connection status: %d", id(connected).state);
  - platform: status
    name: "Bathroom Ceiling Status"
    id: connected
    on_press:
      then:
        - logger.log: "Connected mode"
    on_release:
      then:
        - logger.log: "Standalone mode"

The log output from the node:

[12:41:45][D][api:546]: Client 'Home Assistant 0.92.2 (x.x.x.x)' connected successfully!
[12:41:55][D][binary_sensor:037]: 'Bath Motion': Sending state ON
[12:41:55][D][main:043]: Connection status: 1
[12:41:57][D][binary_sensor:037]: 'Bath Motion': Sending state OFF
[12:42:24][D][api:073]: Disconnecting Home Assistant 0.92.2 (x.x.x.x)
[12:42:44][D][binary_sensor:037]: 'Bath Motion': Sending state ON
[12:42:44][D][main:043]: Connection status: 1
[12:42:47][D][binary_sensor:037]: 'Bath Motion': Sending state OFF

So it seems that the disconnection/connection state change doesn’t register on the ESP side, probably would work on the Home Assistant side, but that’s not useful in this case.

As for implementing all my automation rules within the ESP node. I think this, in general, is a great solution. however I think it would get very fragile without actually knowing the connection status (which I’m asking about here).

Let’s say there was a power outage and the router/my home assistant doesn’t recover. Now ESP doesn’t have the proper time from the internet, so time based automation won’t work, also I wouldn’t be able to override the status of the light. For example this light shouldn’t turn on during the night, but during an outage, it would be better to make it turn on. – I suppose I could use the .is_valid() function in all my lambdas to check for this.

But now let’s suppose that the node does have the correct time, but my Home Assistant/Wifi is down, making it impossible to force the light ON during the night… so in the end I still need a clear answer whether the node is connected to Home Assistant or not. Also if I rely on forcing from the HA for certain states (steady ON/OFF instead of motion timeout) and the Home Assistant gets inaccessible, those states get stuck on the last available status, which I have no way of changing without knowing that my node is connected or not to the bigger system.

tl;dr: I still need the knowledge of the connection status to Home Assistant if I want to make a well functioning fallback, because a standalone node should use simpler automation routines.

I’m sorry this did not work for you.
I was looking at the code trying to figure out why it might not work. This is the code I looked into first,
which is the status binary sensor for 1.13 which is dev version. (Dev version is latest bits, and likely buggy)
Then there is the 1.12 version you are running most likely: It’s a bit different where it checks what to report. The old one does not check api
Which version are you running?
Seems like dev version will report this as you’d expect, i.e. if you only use api (not mqtt) then it will report disconnected if HA is down.
v1.12 will report disconnected if you are over mqtt and the broker goes down.
Also seems like both will report disconnected if the wifi router goes down.
How are you testing your disconnect event? (shutting down Home Assistant?)

I’d like to know if there’s a way to do this too.

I’m about to install some sonoff duals which I will wire to light switches - plan is to send a toggle command to Home Assistant to turn my Yeelights on/off. Trouble is, if HA or the network goes down, I could be in a position that I’m flicking switches and I’m stuck in the dark without a fallover option.

Have a look at this as a possible solution:

Perhaps for basic functions, you could have it so that a quick double toggle reverts back to the ESPHome api onboard if HA can’t be contacted.

@glmnet Indeed, failed to mention it, but I’m using 1.12.2 stable from pip with the native api (no mqtt) and I was testing it by shutting down Home Assistant, not wifi (so that I can see the logs easily without connecting to the serial line). Glad to know this will work in the following version. I might give it a go with the dev branch, as according to the code you linked it should indeed start working with that.

I’ll post my code when I have it working. Thanks for the help so far!

@efleming I don’t have any switch connected to the ESP (it’s up in the ceiling with only a motion sensor and a relay connected) so using more complicated click patterns won’t help in this case, but it can be a good solution for fallback indeed, thanks for pointing out this workaround.

May be this will suit you better

Yesss, I this is exactly what I was looking for, but somehow couldn’t find it in the docs! :slight_smile:

Thank you once again.

Oh that’s next docs, i.e. v1.13

I’m still new to ESPhome, so let me put some code in here for critique to see if I understand that correctly. If I have ESPHome send a command to HA to run an action (say turn on yeelight), but it cannot connect, I would use this?

on_switch_toggle: 
  - homeassistant.service:
      service: light.toggle
      data:
         entity_id: light.yeelight101
  if:
    condition:
      api.connected: false
    then:
      - switch.toggle: relay_1

So 1.13.x just came out and I gave a go to trying this, but it doesn’t work for me. As for your example, I think the conditions should be just api.connected:, not including false. If we want false, it should be probably:

if:
  condition:
    not:
      api.connected:
    then:
      - logger.log: disconnected

However I tried this code and it gives me back “connected” both when Home Assistant is connected and when it’s disconnected:

binary_sensor:
  - platform: gpio
    pin: D4
    name: "Bath Motion"
    id: bath_motion
    device_class: motion
    on_press:
      if:
        condition:
          api.connected:
        then:
          - logger.log: connected
        else:
          - logger.log: not connected

Example output (cut out the irrelevant lines and censored the IP):

[20:28:31][D][binary_sensor:033]: 'Bath Motion': Sending state OFF
[20:28:42][D][api:572]: Client 'Home Assistant 0.93.1 (x.x.x.x)' connected successfully!
[20:28:44][D][binary_sensor:033]: 'Bath Motion': Sending state ON
[20:28:44][D][main:220]: connected
[20:28:46][D][binary_sensor:033]: 'Bath Motion': Sending state OFF
[20:29:03][D][api:074]: Disconnecting Home Assistant 0.93.1 (x.x.x.x)
[20:29:11][D][binary_sensor:033]: 'Bath Motion': Sending state ON
[20:29:11][D][main:220]: connected
[20:29:14][D][binary_sensor:033]: 'Bath Motion': Sending state OFF
[20:29:37][D][binary_sensor:033]: 'Bath Motion': Sending state ON
[20:29:37][D][main:220]: connected
[20:29:40][D][binary_sensor:033]: 'Bath Motion': Sending state OFF
[20:30:21][I][ota:046]: Boot seems successful, resetting boot loop counter.
[20:31:52][D][api:572]: Client 'Home Assistant 0.93.1 (x.x.x.x)' connected successfully!
[20:32:08][D][binary_sensor:033]: 'Bath Motion': Sending state ON
[20:32:10][D][main:220]: connected

@glmnet did you try this out? Looks like there’s a bug in this new condition. :confused:

I did not test it but looks ok according to docs.

By any chance do you have several ha instances?

Also I don’t know if the logger via OTA would account as an API client, are you logging via usb?

I can test this tomorrow

You’re right again. Fooled once more by the observation altering the result of the experiment. When I disconnected the logging the condition worked as expected. :sweat_smile:

This should be probably noted in the docs, I’d assume I won’t be the only one who falls into this trap.

So I made it work like this: my node is a simple Wemos D1 mini with a relay and a motion sensor. By default the motion sensor reports to Home Assistant and HA decides if it wants to turn on or off the light by the relay. If there are no clients connected to the API, the light turns on and stays for 90 seconds from the last motion activity, but the node doesn’t touch the light in case an API client connects meanwhile.

I have a watchdog routine in my Home Assistant that regularly checks if the lights are in the correct state, but if you don’t have that, this sketch might leave your light turned on if HA connects to the node during the 90 second timeout, so in that case it’s probably better to remove the if condition from the on_release part.

Here’s my full yaml for reference:

esphome:
  name: bathroom_ceiling
  platform: ESP8266
  board: d1_mini

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

# Enable logging
logger:

# Enable Home Assistant API
api:
  password: !secret api_password

ota:
  password: !secret ota_password

binary_sensor:
  - platform: gpio
    pin: D4
    name: "Bath Motion"
    id: bath_motion
    device_class: motion
    on_press:
      if:
        condition:
          not:
            api.connected:
        then:
          - lambda: |-
              id(failover_control).publish_state(true);
              id(failover_control).publish_state(false);
  - platform: template
    id: failover_control
    filters:
      - delayed_off: 90s
    on_press:
      if:
        condition:
          not:
            api.connected:
        then:
          - light.turn_on: bathroom_ceiling
    on_release:
      if:
        condition:
          not:
            api.connected:
        then:
          - light.turn_off: bathroom_ceiling

output:
  - platform: gpio
    pin: D1
    id: relay

light:
  - platform: binary
    name: "Bathroom Ceiling"
    id: bathroom_ceiling
    output: relay
4 Likes

Nice catch, I filled an issue https://github.com/esphome/issues/issues/394

1 Like

Is there a way to to turn OFF the switch for 5 seconds and turn it back to ON after reboot.
I want to the switch to realy perform OFF/ON after a reboot.

Yes. Please create a new question (new thread)

done