Problem - ESP Chicken Coop Doors

Hello everyone, I’m having an issue using a Home Assistant automation with my ESPHome.

My idea is to capture the time when the sun reaches the “civil twilight” solar position and use that time to update the “time.esp_chicken_coop_doors_time_close” entity so that the closing time is always adjusted throughout the year.

My esphome code:

substitutions:
  name: esp-chicken-coop-doors
  friendly_name: ESP Chicken Coop Doors
  comment: Controls coop doors
  door_cycle_time: 120s

packages:
  base: !include common/base.yaml
  wifi: !include common/wifi.yaml
  esp32: !include common/devices/esp32_az-delivery-devkit-v4.yaml

external_components:
  - source:
      type: git
      url: https://github.com/trombik/esphome-component-ds1302
    components:
      - ds1302

ota:
  - platform: esphome
    password: "..."

api:
  encryption:
    key: "..."

esphome:
  on_boot:
    - priority: 600
      then:
        - delay: 10s
        - if:
            condition:
              lambda: |-
                ESP_LOGI("main", "BOOT DONE!");

                auto now = id(ds1302_time).now();
                auto timeopen = id(time_open).state_as_esptime();
                auto timeclose = id(time_close).state_as_esptime();

                return (now.hour > timeopen.hour || (now.hour == timeopen.hour && now.minute > timeopen.minute)) && (now.hour < timeclose.hour || (now.hour == timeclose.hour && now.minute < timeclose.minute));

            then:
              - cover.open: door_1
              - cover.open: door_2
              - cover.open: door_3
            else:
              - cover.close: door_1
              - cover.close: door_2
              - cover.close: door_3

datetime:
  - platform: template
    id: time_open
    type: time
    name: "Time Open"
    icon: mdi:sort-clock-descending
    optimistic: yes
    initial_value: "08:30:00"
    restore_value: true
    on_time:
      then:
        - cover.open: door_1
        - cover.open: door_2
        - cover.open: door_3
  - platform: template
    id: time_close
    type: time
    name: "Time Close"
    icon: mdi:sort-clock-ascending
    optimistic: yes
    initial_value: "21:30:00"
    restore_value: true
    on_time:
      then:
        - cover.close: door_1
        - cover.close: door_2
        - cover.close: door_3

time:
  - platform: ds1302
    id: ds1302_time
    cs_pin: GPIO25
    dio_pin: GPIO26
    clk_pin: GPIO27
    update_interval: never

  - platform: sntp
    id: sntp_time
    on_time_sync:
      then:
        ds1302.write_time:

switch:
  - platform: gpio
    id: door_1_open
    pin: GPIO12
    inverted: false
    interlock: [door_1_close]
    restore_mode: RESTORE_DEFAULT_OFF
    on_turn_on:
      - delay: ${door_cycle_time}
      - switch.turn_off: door_1_open

  - platform: gpio
    id: door_1_close
    pin: GPIO13
    inverted: false
    interlock: [door_1_open]
    restore_mode: RESTORE_DEFAULT_OFF
    on_turn_on:
      - delay: ${door_cycle_time}
      - switch.turn_off: door_1_close

  - platform: gpio
    id: door_2_open
    pin: GPIO23
    inverted: false
    interlock: [door_2_close]
    restore_mode: RESTORE_DEFAULT_OFF
    on_turn_on:
      - delay: ${door_cycle_time}
      - switch.turn_off: door_2_open

  - platform: gpio
    id: door_2_close
    pin: GPIO22
    inverted: false
    interlock: [door_2_open]
    restore_mode: RESTORE_DEFAULT_OFF
    on_turn_on:
      - delay: ${door_cycle_time}
      - switch.turn_off: door_2_close

  - platform: gpio
    id: door_3_open
    pin: GPIO19
    inverted: false
    interlock: [door_3_close]
    restore_mode: RESTORE_DEFAULT_OFF
    on_turn_on:
      - delay: ${door_cycle_time}
      - switch.turn_off: door_3_open

  - platform: gpio
    id: door_3_close
    pin: GPIO18
    inverted: false
    interlock: [door_3_open]
    restore_mode: RESTORE_DEFAULT_OFF
    on_turn_on:
      - delay: ${door_cycle_time}
      - switch.turn_off: door_3_close

cover:
  - platform: template
    name: "Door 1"
    id: door_1
    device_class: garage
    optimistic: true
    open_action:
      - switch.turn_on: door_1_open
    close_action:
      - switch.turn_on: door_1_close
    stop_action:
      - switch.turn_off: door_1_open
      - switch.turn_off: door_1_close

  - platform: template
    name: "Door 2"
    id: door_2
    device_class: garage
    optimistic: true
    open_action:
      - switch.turn_on: door_2_open
    close_action:
      - switch.turn_on: door_2_close
    stop_action:
      - switch.turn_off: door_2_open
      - switch.turn_off: door_2_close

  - platform: template
    name: "Door 3"
    id: door_3
    device_class: garage
    optimistic: true
    open_action:
      - switch.turn_on: door_3_open
    close_action:
      - switch.turn_on: door_3_close
    stop_action:
      - switch.turn_off: door_3_open
      - switch.turn_off: door_3_close

I’m using the following automation:

alias: Adjust chicken coop door closing time
description: Closing time when solar elevation reaches civil twilight. (0-6°)
trigger:
  - platform: numeric_state
    entity_id:
      - sensor.sun_solar_elevation
    below: -4
condition: []
action:
  - delay:
      hours: 0
      minutes: 5
      seconds: 0
      milliseconds: 0
  - target:
      entity_id: time.esp_chicken_coop_doors_time_close
    data:
      time: "{{ (now() - timedelta(minutes=5)).strftime('%H:%M:%S') }}"
    action: time.set_value
mode: single

The problem I’m facing is that sometimes the internal automation in ESPHome doesn’t work correctly.

What if you use esphome’s sun platform?

I want to prepare in case I don’t have internet access to check the position of the sun. I was thinking of using this component to update the time_close directly in ESPHome.

In this case, will the Sun integration of Home Assistant have the correct data?

I think so. In my automation, I made sure to update the entity to the new time with a delay to avoid interrupting the automation, since the new time is always earlier than the current time…

Civil twilight is a known variable, you do not need to wait for it to actually happen to bring the value into esphome. You could either create a template sensor to calculate the time or add sun2. That has a civil twilight sensor.

Using the home assistant platform in esphome, bring the sensor into the esp and return it’s id for the time. This way as long as it connects at some point during the day it will have the time value for civil twilight.

Why your fixated on civil twilight, im not even going to try and guess but, the whole purpose here is to make it more reliable, correct?

So, your synchronizing sntp time from the internet, then synchronizing that time and your physical RTC and now you want to use civil twilight I assume as an unnecessary method to increase reliability? 6 degreess below horizon a.k.a civil twilight is just a fancy term for, “the sun set 30 minutes ago” so its just a
30 min extension added onto sun.is_below_horizon: and IMO your just unnecessarily overcomplicating something that shouldnt be complicated. If it were me, the only additional reliability addition would be a lux sensor outside at the chicken coop. If all else fails, it eill see when sunset happens and close the door or vice-versa

Between sntp and the RTC, what are the odds youll have both become unusable and the system fails?

Civil Twighlight is when you know the chickens have come home to roost and nocturnal preditors are starting to roam.

I agree that the sun integration is the way to go here, but the sun sets at different speeds throughout the year. Civil Twilight is when the sun is between 0 to -6 degrees of the horizon, the sun integration has an ‘Elevation’ attribute that I think would make more sense to use as a trigger in this case.

2 Likes

Sunset is specific moment, not related to speed. That can be calculated for all the year if actual time and location is known. And esp32 keeps time reasonably well for several days/weeks for this kind of use case (precision).

Sunset is a specific moment yes, but it sets at different speeds. So the amount of time spent between 0 and -6 degrees (civil twighlight) changes throughout the year. {{ state_attr('sun.sun', 'elevation') }} already calculates that for you.

All I was getting at is that civil twilight changes throughout the year, so giving it a hard 30 minutes after sunset may not give you the desired results depending on time of year, and location on planet.

That’s correct.

I thought you were being kind of irrationally obsessed with that and whoa, was I wrong. I didn’t know that was a thing with chickens and now I do. That was a hefty piece of Humble pie you served me, I still appreciate learning something new tgough.

Speaking of civil twilight, yes its 6 degrees above and below the horizon because it happes at both dusk and dawn and is generally 30min long. Depending on ones latitude it will fluctuate about 8 minutes over the course of a year so, thats why i said its basically equivalent to a 30min delay after sunset.

Using the actual civil twilight time is doable, It just seems kind of overkill when its 30-38min IMO but, to each his own. Nobody wants a fox to get their chickens!

LOL I still might be irrationally obsessed, but I’m not the OP. If you learned something you didn’t know before, then I’ve done good. If we helped the OP then we’ve done better (if we haven’t they should let us know). Cheers.

Hello again, everyone. I apologize for the delay in responding, but I was testing other things.

I’ve read some posts, but I think I clarified from the beginning—if it wasn’t clear, I apologize.

My goal with this project is to control the chicken coop doors, and I didn’t want to have only fixed times for opening/closing. I wanted something that adjusts throughout the year and takes advantage of ESPHome being connected to the internet while also preparing for possible power outages or internet failures.

I’ve been researching other methods, and I’ve already managed to incorporate the automation into ESPHome.


I created two switches to enable/disable the automation for updating the ‘dateTime’ values:

switch:
  - platform: template
    name: "Sunrise Schedule"
    id: sunrise_schedule
    icon: mdi:weather-sunset-up
    optimistic: true
    restore_mode: RESTORE_DEFAULT_OFF

  - platform: template
    name: "Sunset Schedule"
    id: sunset_schedule
    icon: mdi:weather-sunset-down
    optimistic: true
    restore_mode: RESTORE_DEFAULT_OFF


I created entities to know when the next sunrise/sunset will be using the ‘sun’ component:

sun:
  latitude: xx.xxx
  longitude: xx.xxx

sensor:
  - platform: sun
    name: Sun Elevation
    type: elevation

text_sensor:
  - platform: sun
    name: "Next Sunrise +20°"
    id: next_sunrise_plus_20
    type: sunrise
    elevation: +20.0°

  - platform: sun
    name: "Next Sunset -10°"
    id: next_sunset_minus_10
    type: sunset
    elevation: -10.0°

I created two dateTime entities:

datetime:
  - platform: template
    id: time_open_test
    type: time
    name: "Time Open - TEST"
    icon: mdi:sort-clock-descending
    optimistic: yes
    initial_value: "08:30:00"
    restore_value: true
    on_time:
      then:
        - switch.turn_on: door_test

  - platform: template
    id: time_close_test
    type: time
    name: "Time Close - TEST"
    icon: mdi:sort-clock-ascending
    optimistic: yes
    initial_value: "21:30:00"
    restore_value: true
    on_time:
      then:
        - switch.turn_off: door_test

Just for debugging tests, I created a switch template entity to later apply to the cover entities.

This is where the ‘magic’ happens: when they change state (on/off), their state is updated with the values from the sun ‘text_sensor’ sensors:

switch:
  - platform: template
    name: "Door TEST (ON/OPEN OFF/CLOSE)"
    id: door_test
    icon: mdi:window-shutter-cog
    optimistic: true
    restore_mode: RESTORE_DEFAULT_OFF
    turn_on_action:
      - lambda: |-
          std::string sunrise_str = id(next_sunrise_plus_20).state.c_str();
          int hour, minute, second;
          sscanf(sunrise_str.c_str(), "%d:%d:%d", &hour, &minute, &second);
          auto call = id(time_open_test).make_call();
          call.set_time(hour, minute, second);
          call.perform();
    turn_off_action:
      - lambda: |-
          std::string sunset_str = id(next_sunset_minus_10).state.c_str();
          int hour, minute, second;
          sscanf(sunset_str.c_str(), "%d:%d:%d", &hour, &minute, &second);
          auto call = id(time_close_test).make_call();
          call.set_time(hour, minute, second);
          call.perform();

ps: I’m not sure if there’s a better way to write these lambdas, but so far, they work. :rofl:


For power outages, I added this to ‘on_boot’ to check the current time and see if it needs to open/close compared to the ‘dateTime’ values:

esphome:
  on_boot:
    - priority: 600
      then:
        - delay: 10s
        - if:
            condition:
              lambda: |-
                auto now = id(ds1302_time).now();
                auto timeopen = id(time_open).state_as_esptime();
                auto timeclose = id(time_close).state_as_esptime();

                return (now.hour > timeopen.hour || (now.hour == timeopen.hour && now.minute > timeopen.minute)) && (now.hour < timeclose.hour || (now.hour == timeclose.hour && now.minute < timeclose.minute));

            then:
              - switch.turn_on: door_test
            else:
              - switch.turn_off: door_test

Lastly, I’m using this sensor to ensure that the times are saved in case of a power outage and internet failure:

time:
  - platform: ds1302
    id: ds1302_time
    cs_pin: GPIO25
    dio_pin: GPIO26
    clk_pin: GPIO27
    update_interval: never

  - platform: sntp
    id: sntp_time
    on_time_sync:
      then:
        ds1302.write_time:

If you notice any errors, feel free to mention them or suggest other improvements. :slight_smile:

image

Very cool… I just repurposed a run chicken giant door retrofitting with Esphome…
I don’t have limit switches, am closing and opening by timer and so far good… Please note I do NOT want the doors opening or closing automatically, I just wanted to be able to do it remotely via wifi/HA…
But I feel I need limit switches as I can’t tell from the app if the door is close/open confirmed (I can visually check via BlueIris camera)

I actually don’t have chicken, they are geese, which are much larger than chicken… What I can do really interested is in an AI automation (OpenCV?) that can count if all five geese are in the stall… They are white geese so very easy to see… Does anyone know of any or how to use visual AI for this?