How do I sync my sensor reading with the sleep loops?

This is my current program (based on this very helpful post):

esphome:
  name: "esp32-adc-vehicle"
  on_boot:
    priority: 100   #Wait for everything(?) to be setup...
    then:
      - script.execute: consider_deep_sleep
  
esp32:
  board: esp32dev  
  framework:
    type: arduino    

  

# Enable logging
logger:

# Enable Home Assistant API
api:
  
ota:
  password: !secret ota_password

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


script:
  - id: consider_deep_sleep
    mode: queued
    then:
      - delay: 10s
      - if:
          condition:
            binary_sensor.is_on: prevent_deep_sleep
          then:
            - output.turn_on: blue_led
            - logger.log: 'Skipping sleep'
          else:
            - output.turn_off: blue_led
            - logger.log: 'Entering sleep'
            - deep_sleep.enter: deep_sleep_control
      - script.execute: consider_deep_sleep

deep_sleep:
  id: deep_sleep_control
  sleep_duration: 50s      

output:
  platform: gpio
  pin: GPIO2      #The only led I've got
  id: blue_led


binary_sensor:
  - platform: homeassistant
    id: prevent_deep_sleep
    entity_id: input_boolean.prevent_deep_sleep



sensor:
  - platform: adc
    pin: GPIO36
    name: "esp32-adc-vehicle"
    update_interval: 1s    
    attenuation: auto
    #filters:
    #- median:
    #    window_size: 10
    #    send_every: 5

How can I get the sensor to take 10 x 0.1sec readings and average and send once within the wake cycle? Then go to sleep for another 59sec?
Playing with the script: - delay:, sleep_duration: and sensor: update_intervals:, produces no logical patterns that I can determine!

Is it possible to sequence the sensor sampling+filtering before entering or after exiting sleep?

Regards, Martin

I think you can reuse a lot from this.

The key thing is to trigger updates on boot and use a moving median filter to aggregate.

I then go to sleep when all the updates are received.

substitutions:
  devicename: irrigation-balcony-quinled
  friendly_name: Bedroom Balcony Irrigation
  device_description: Bedroom Balcony Irrigation
  
  #I find it easier to manage some settings up here.
  sleep_time: 60min
  auto_wake_time: 30s
  pump_run_time: 10s
  # internal_toggle: true

esp32:
  board: mhetesp32devkit  
esphome:
  name: $devicename
  comment: ${device_description}
  on_boot:
    - priority: 900
      then:
        - lambda: |-
            Wire.begin(); 
        #^^Seems to solve bug with BH1750 not waking up from deep sleep. https://community.home-assistant.io/t/bh1750-no-communication-after-switching-it-off-and-on-again/412282/3?u=mahko_mahko
    - priority: -100
      then:
      #Sensor updates are turned off for most sensors and just manually requested on boot. Then the ESP goes back to sleep when they're done (unless told to stay 
        #Reset sensor update counters. These are for debugging.
        - lambda: id(count_irrigation_lux).publish_state(0);
        - lambda: id(count_batt_voltage).publish_state(0);
        - delay: 1s
        - logger.log: "....Starting sensor updates"
        - repeat:
            #Request sensor updates
            count: 5
            then:
              - component.update: batt_voltage #for battery level
              - delay: 100ms
              - component.update: soil_moisture_voltage #for moisture level
              - delay: 100ms
              - component.update: tof #for water tank level
              - delay: 100ms
              - component.update: irrigation_lux #for light level
              - delay: 100ms
            #and again for ADC based sensors which benefit from more samples.
              - component.update: batt_voltage
              - delay: 250ms
              - component.update: soil_moisture_voltage
              - delay: 250ms

  on_shutdown:
    #Turn off 5v peripheral power. It will retore as on when it wakes.
    priority: -100
    then:
      - logger.log: "Turning off peripheral power..."
      - switch.turn_off: power_peripherals
    #Turn off the "Fresh data recieved sensors"
      - binary_sensor.template.publish:
          id: water_tank_level_recieved
          state: OFF
      - binary_sensor.template.publish:
          id: irrigation_lux_recieved
          state: OFF
      - binary_sensor.template.publish:
          id: batt_level_recieved
          state: OFF
      - binary_sensor.template.publish:
          id: solar_plant_moisture_level_recieved
          state: OFF
      - binary_sensor.template.publish:
          id: all_updates_recieved
          state: OFF
    
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  fast_connect: true
  manual_ip:
      static_ip: XXX
      gateway: YYY
      subnet: ZZZ

api:
ota:

logger:
  # level: VERBOSE
  baud_rate: 0 
  
i2c:
#Shared by multiple devices (wires hopped)
  sda: GPIO25 #data > green wire
  scl: GPIO27 #clock > blue wire
  scan: true 
  
text_sensor:
# Reports the ESPHome Version with compile date
  - platform: version
    name: ${friendly_name} ESPHome Version

time:
  - platform: homeassistant
    id: esptime
    on_time:
    #Deep sleep at 8pm and wake up at 5am. No real need to measure overnight.
      - hours: 20
        then:
          - deep_sleep.enter:
              id: deep_sleep_1
              until: "05:00:00"
              time_id: esptime

deep_sleep:
  id: deep_sleep_1
  run_duration: ${auto_wake_time}
  sleep_duration: ${sleep_time}

output:
##################################################
#Pump
##################################################
#5v pump attached to a l298n.
#Pump on/off
- platform: gpio
  pin: GPIO18
  id: pump_on
# #Pump Speed.
# - platform: ledc 
  # pin: GPIO26
  # id: pump_speed

button: 
##################################################
#Pump Control
##################################################
#Run pump for fixed time interval
  - platform: template
    name: ${friendly_name} Run Pump
    id: run_pump_for_interval
    icon: "mdi:water-pump"
    on_press:
      then:
        - logger.log: Running irrigation pump for ${pump_run_time}
        - output.turn_on: pump_on
        # #Set speed
        # - output.set_level:
            # id: pump_speed
            # level: 50%
        # - output.ledc.set_frequency:
            # id: pump_speed
            # frequency: 500Hz
        - delay: ${pump_run_time}
        - output.turn_off: pump_on
        # - output.turn_off: pump_speed
#Stop Pump
  - platform: template
    name: ${friendly_name} Stop Pump
    id: stop_irrigation_pump
    icon: "mdi:water-pump-off"
    on_press:
      then:
        - output.turn_off: pump_on
        # - output.turn_off: pump_speed
        - logger.log: "Stopping Pump"

##################################################
#Deep Sleep
##################################################
# #Prevent Deep Sleep via HA button
  # - platform: template
    # name: ${friendly_name} Sleep Prevent
    # id: prevent_deep_sleep
    # icon: "mdi:sleep-off"
    # on_press:
      # then:
        # - logger.log: "Deep Sleep PREVENT activated via HA button."
        # - deep_sleep.prevent: deep_sleep_1
        
# #Allow Deep Sleep
  # - platform: template
    # name: ${friendly_name} Sleep Allow
    # id: allow_deep_sleep
    # icon: "mdi:sleep"
    # on_press:
      # then:
        # - logger.log: "Deep Sleep ALLOWED activated via HA button."
        # - deep_sleep.allow: deep_sleep_1

#Sleep if allowed.
  - platform: template
    name: ${friendly_name} Sleep If Allowed
    id: sleep_if_allowed
    icon: "mdi:sleep"
    # internal: ${internal_toggle}
    on_press:
      then:
         - if:
            condition:
              or:
                - binary_sensor.is_on: remote_defeat
                # - binary_sensor.is_on: defeat
            then:
              - logger.log: "Sleep requested but STAY AWAKE mode is on. Skipping sleep."
              - deep_sleep.prevent: deep_sleep_1
            else:
              - logger.log: "Sleep requested and ALLOWED. Going to sleep..."     
              - deep_sleep.enter:
                  id: deep_sleep_1
                  sleep_duration: ${sleep_time}
                  
#Force Deep Sleep
  - platform: template
    name: ${friendly_name} Force Sleep
    id: force_deep_sleep
    icon: "mdi:bell-sleep"
    on_press:
      then:
        - logger.log: "FORCE SLEEP requested...Going to sleep"     
        - deep_sleep.enter:
            id: deep_sleep_1
            sleep_duration: ${sleep_time}


binary_sensor:
##################################################
#Data Update Sensors. These were useful during dev. Might remove in prod.
#They are all updated via other sensors.
##################################################
  #Moisture
  - platform: template
    name: ${friendly_name} moisture updated
    id: solar_plant_moisture_level_recieved
    # internal: ${internal_toggle}
  #Battery Level
  - platform: template
    name: ${friendly_name} battery level updated
    id: batt_level_recieved
    # internal: ${internal_toggle}
  #Light
  - platform: template
    name: ${friendly_name} lux updated
    id: irrigation_lux_recieved
    # internal: ${internal_toggle}
  #Water level
  - platform: template
    name: ${friendly_name} water level updated
    id: water_tank_level_recieved
    # internal: ${internal_toggle}
  #All
  #Once all updates are recieved, then chack if you can go back to sleep.
  - platform: template
    name: ${friendly_name} all updates recieved
    id: all_updates_recieved
    lambda: |-
      return
      id(solar_plant_moisture_level_recieved).state &&
      id(batt_level_recieved).state &&
      id(irrigation_lux_recieved).state &&
      id(water_tank_level_recieved).state
      ;
    on_press:
    #Sleep esp if all data recieved 
      then:
        - logger.log:
            format: "All sensors updated after %.1f seconds of uptime. Checking if sleep is allowed"
            args: [ 'id(uptime_sec).state']
        # - delay: 1s
        - button.press: sleep_if_allowed

##################################################
#Deep sleep control
##################################################
#A lot of my logic is remashes of:
  #https://www.wirewd.com/make/blog/esphome_sleep_modes
  #https://tatham.blog/2021/02/06/esphome-batteries-deep-sleep-and-over-the-air-updates/
#Some of it still needs a clean up.

#HA Deep sleep control
  - platform: homeassistant
    name: "Remote Defeat Sleep"
    internal: True
    id: "remote_defeat"
    entity_id: input_boolean.defeat_sleep
    on_press:
      then:
        - logger.log: "remote press defeat"
        - deep_sleep.prevent: deep_sleep_1
        
 # #HA Deep sleep control (not currently used)
  # - platform: gpio
    # name: "Defeat"
    # id: "defeat"
    # internal: True
    # pin:
      # number: 4
      # mode: INPUT_PULLUP
      # inverted: True
    # on_press:
      # then:
        # - logger.log: "press defeat"
        # - deep_sleep.prevent: deep_sleep_1

switch:
##################################################
#Control peripheral power (on solar power manager)
##################################################
  - platform: gpio
    pin:
      number: 17
    name: ${friendly_name} Power Peripherals
    id: power_peripherals
    restore_mode: ALWAYS_ON #This turns on the power on boot/wake in time for sensor set-up.


sensor:
#Uptime sensor
  - platform: uptime
    id: uptime_sec
    name: ${friendly_name} Uptime Sensor
    update_interval: 2s
    accuracy_decimals: 1
    unit_of_measurement: s

##################################################
#For counting data updates recieved for each wake cycle.
#Manually updated via publishing from other sensors.
##################################################
  - platform: template
    name: "Count Lux Updates"
    id: count_irrigation_lux
    unit_of_measurement: count
  - platform: template
    name: "Count Batt V Updates"
    id: count_batt_voltage
    unit_of_measurement: count
    
##########################################################################################
# Time of Flight sensor  - i2c
##########################################################################################
#Powered via 5v
  - platform: vl53l0x
    id: tof
    name: ${friendly_name} ToF
    internal: false 
    address: 0x29
    update_interval: never
    #never
    # enable_pin: GPIO17 #Did not work: https://github.com/esphome/issues/issues/3644
    accuracy_decimals: 1
    unit_of_measurement: 'cm'
    filters:
    #Convert to cm
      - multiply: 100 
    #Then use moving median to smooth noise.  Sample 5 points then send.
      - median:
          window_size: 5
          send_every: 5
          send_first_at: 5
      
#Convert the ToF distance to a water tank level (percent full)
  - platform: copy
    source_id: tof
    id: water_tank_level
    internal: false
    # icon: "mdi:battery"
    name: ${friendly_name} Water Level
    unit_of_measurement: '%'
    accuracy_decimals: 0
    filters:
      # Map from distance to % full. To calibrate.
      - calibrate_linear:
          - 3 -> 100 
          - 19.5 -> 0
      # #Overide values less than 0% and more than 100%
      # - lambda: |
          # if (x < 3) return 100; 
          # else if (x > 20) return 0;
          # else return ceil(x / 0.5) * 0.5;
    on_value:
      then:
       - binary_sensor.template.publish:
          id: water_tank_level_recieved
          state: ON

# # ##########################################################################################
# # # bh1750 Lux/light sensor
# # ##########################################################################################     
  - platform: bh1750
    id: irrigation_lux
    name: ${friendly_name} Illuminance
    address: 0x23
    update_interval: never
    filters:
    #Use moving median to smooth noise. Sample 5 points then send.
      - median:
          window_size: 5
          send_every: 5
          send_first_at: 5
    on_value:
      then:
       - binary_sensor.template.publish:
          id: irrigation_lux_recieved
          state: ON
    on_raw_value:
      then:
      #Sensor update counter.
        - lambda: id(count_irrigation_lux).publish_state(id(count_irrigation_lux).state +1);

#Notes:
#Voltage divider: Used 2 x 300K Ohm resistors
  - platform: adc
    id: batt_voltage
    name: ${friendly_name} Battery Voltage
    internal: false
    pin: GPIO33 #ADC1
    update_interval: never
    accuracy_decimals: 3
    attenuation: auto
    filters:
      # #The scale it back up from voltage divided value 2 x 300K > 2.1. 4.2/2.1 = 2.
      - multiply: 2
    on_raw_value:
      then:
      #Sensor update counter.
        - lambda: id(count_batt_voltage).publish_state(id(count_batt_voltage).state +1);
      
#Intermediate sensor. Might consolidate them later.
  - platform: copy
    source_id: batt_voltage
    id: batt_voltage_filtered
    icon: "mdi:battery"
    internal: false
    name: ${friendly_name} Battery Voltage Filtered  
    unit_of_measurement: V
    accuracy_decimals: 3
    filters:
    #Use moving median to deal with noise.
      - median:
          window_size: 10
          send_every: 10
          send_first_at: 10
       
#Convert the Voltage to a battery  level (%)
  - platform: copy
    source_id: batt_voltage_filtered
    id: batt_level
    internal: false 
    icon: "mdi:battery"
    name: ${friendly_name} Battery Level
    unit_of_measurement: '%'
    accuracy_decimals: 1
    filters:
      # Map from voltage to Battery level
      - calibrate_linear:
          - 3.0 -> 0 #Set 3.0 to 0% even though it can go lower (2.4V), for life extention. There's not much capacity below this anyway.
          - 4.05 -> 100 #Set 4.05 to 100% even though it can go higher (~4.2V), for life extention.
       
      #Overide values less than 0% and more than 100%
      - lambda: |
          if (x < 0) return 0; 
          else if (x > 100) return 100;
          else return ceil(x / 5) * 5;
    on_value:
      then:
       - binary_sensor.template.publish:
          id: batt_level_recieved
          state: ON
        
#Capacitive soil moisture sensor: https://www.aliexpress.com/item/32832538686.html?spm=a2g0o.order_list.0.0.55771802WgNqEA
#Voltage of the Capacitive soil moisture sensor
  - platform: adc
    pin: GPIO34 
    name: ${friendly_name} Soil Moisture Volts
    id: soil_moisture_voltage
    internal: false 
    accuracy_decimals: 1
    update_interval: never
    # never
    attenuation: auto
    filters:
    #Use moving median to deal with noise.
      - median:
          window_size: 10
          send_every: 10
          send_first_at: 10

#Convert the Voltage to a moisture  level (%)
  - platform: copy
    source_id: soil_moisture_voltage
    id: solar_plant_moisture_level
    name: ${friendly_name} Moisture Level
    internal: false     
    icon: "mdi:battery"
    unit_of_measurement: '%'
    accuracy_decimals: 1
    filters:
      #max and min values taken from testing with glass of water. Prob need to do more in situ tests.
      - calibrate_linear:
          - 1.66 -> 100.0
          - 2.90 -> 0.0
      #Handle/cap boundaries
      - lambda: |
          if (x < 0) return 0; 
          else if (x > 100) return 100;
          else return (x);
    on_value:
      then:
       - binary_sensor.template.publish:
          id: solar_plant_moisture_level_recieved
          state: ON