Disable and enable sensor in ESPHOME

I have an ADC sensor that publishes data to a template sensor if a relay is on (as it runs off battery, and when coming out of deep sleep it turns relay on, does its probe check and sends data to HA, then turns relay off - to conserve battery) I want the sensor to take 4 samples 2 seconds apart before publishing the average value. I’ve tried the following, but “on_value” gets triggered every 2 seconds, not when the 4 samples are complete and average taken.

# Voltage from probe, only publish if relay is on, internal only
  - platform: adc
    id: raw_adc
    pin: GPIO3
    accuracy_decimals: 4
    update_interval: 2s
    samples: 4
    sampling_mode: avg
    attenuation: 12db
    unit_of_measurement: "V"
    internal: True
    filters:
      - filter_out: nan
      - skip_initial: 1
    on_value: 
      then:
        - lambda: |-
            ESP_LOGI("custom", "Publishing sample if relay is ON...");
            if (id(relay_switch).state)
            {
              id(water_volume_adc).publish_state(id(raw_adc).state);
            }

So I tested using filters, and added as follows:

    filters:
      - filter_out: nan
      - skip_initial: 1
      - sliding_window_moving_average:
          window_size: 5
          send_every: 5

And that kind of works, in that my log message “Publishing sample if relay is ON…” appears every 10 seconds. However if I turn the relay ON (via switch in HA) and it’s already say 8 seconds or so into its sample, then it publishes 2 seconds later.

I guess what may be the easiest method and is my question, is can I enable or disable a GPIO input “via esphome” whilst the ESP32 is running? EG: so psuedo code would be:

  • come out of deep sleep
  • turn on relay
  • only then, enable GPIO and start sampling data
  • 10 seconds later (avg of 5 samples) publish data to HA
  • turn off relay and disable GPIO
  • sleep again

Any ideas?

You are asking to do things that are well outside the norm of what esphome is good at, so it will likely be very hard to do exactly what you want.

If I needed to do this, I would create a platformio project to do exactly what I wanted. Even if I didn’t have some experience with platformio, it would likely still be easier.

What you are asking for seems simple, and it is when you are controlling everything. Esphome makes it easy to do many things. This just isn’t one of them. There might be a way. Just like you can mow your lawn with scissors, how good an idea it is depends on how big your lawn is and how much time you have.

You seem to be missing some of your yaml. I assume the one called water_volume_adc is the adc sensor?

Try this - I haven’t tested it:

  • create a new template sensor that simply copies the value of the sensor above. Make sure the old ADC sensor is internal: true
  • when you turn the relay on (I assume somewhere else in your yaml?) wait 10 secs then publish the template sensor with the sensor.template.publish action.

It would be easier if we could see your complete yaml.

Full code, with irrelevant sections removed

esphome:
  name: water-tank-56
  friendly_name: water-tank-56
  comment: "Returns how many litres are available in water tank"
  # short delay to give time to get switch settings from HA and send log messages
  # also check deep sleep prevention switch after enabling relay incase was enabled then
  on_boot: 
    - priority: -100.0
      then:
        - if:
            condition:
              lambda: "return id(block_sleep);"
            then:
              - deep_sleep.prevent: deep_sleep_esp
              - logger.log: "Preventing deep sleep at boot"
              - delay: 2s
            else:
              - switch.turn_on: relay_switch

esp32:
  board: esp32-s2-saola-1
  framework:
    type: arduino

# Enable logging
logger: 
  level: DEBUG
  baud_rate: 0

globals:
  - id: block_sleep
    type: bool
    initial_value: "false"

deep_sleep:
  id: deep_sleep_esp
  run_duration: 20s
  sleep_duration: 30s

# Voltage from probe, only publish if relay is on, internal only
  - platform: adc
    id: raw_adc
    pin: GPIO3
    accuracy_decimals: 4
    update_interval: 2s
    samples: 4
    sampling_mode: avg
    attenuation: 12db
    unit_of_measurement: "V"
    internal: True
    filters:
      - filter_out: nan
      - skip_initial: 1
      - sliding_window_moving_average:
          window_size: 4
          send_every: 4
    on_value: 
      then:
        - lambda: |-
            ESP_LOGI("custom", "Publishing sample if relay is ON...");
            if (id(relay_switch).state)
            {
              id(water_volume_adc).publish_state(id(raw_adc).state);
            }


# Accept voltage from ADC, publish to water volume percent
  - platform: template
    name: "Water Volume ADC"
    id: water_volume_adc
    unit_of_measurement: "V"
    filters:
      - filter_out: nan          
      - lambda: |-
          ESP_LOGI("custom", "Updating Water Volume ADC");
          float y = id(raw_adc).state;
          id(water_volume_percent).publish_state(x);
          return y;

# Calculate water volume from ADC, compensate for small lowest voltage
  - platform: template
    name: "Water Volume %"
    unit_of_measurement: "%"
    id: water_volume_percent
    filters:
      - filter_out: nan
      - lambda: |-
          ESP_LOGI("custom", "Updating Water Volume %%");
          delay(1);
          auto y = ((x - 0.1) / 3.2) * 100.0; 
          if (y < 0) { y = 0; }
          return y;
    on_value: 
      then:
        - delay: 1s
        - logger.log: "Turning RELAY SWITCH OFF"
        - switch.turn_off: relay_switch

# Read from sensor when turned on
switch:
  - platform: gpio
    name: "Sample Water Level"
    id: relay_switch
    pin: 
      number: GPIO16
      inverted: true
      mode:
        output: True
        open_drain: True
    restore_mode: ALWAYS_OFF
    on_turn_on:
      - deep_sleep.prevent: deep_sleep_esp
      - logger.log: "Relay Switch turned ON, reading sensor"
      - delay: 1s
    on_turn_off:
      - logger.log: "Relay Switch turned OFF"
      - switch.turn_off: relay_switch
      - delay: 1s
      - if:
          condition:
            lambda: "return !id(block_sleep);"
          then:
            - deep_sleep.allow: deep_sleep_esp

  - platform: template
    name: "Disable Deep Sleep"
    optimistic: True
    restore_mode: RESTORE_DEFAULT_ON
    lambda: |-
        return id(block_sleep);
    turn_on_action:
      - deep_sleep.prevent: deep_sleep_esp
      - logger.log: "Deep sleep DISABLED"
      - globals.set:
          id: block_sleep
          value: "true"
      - switch.turn_off: relay_switch
    turn_off_action:
      - logger.log: "Deep sleep ENABLED"
      - globals.set:
          id: block_sleep
          value: "false"
      - deep_sleep.allow: deep_sleep_esp

So you have almost done the work.

Set update_interval: never on both your template sensors. They will still calculate correctly when you publish them.

Remove the publish portion of the ADC sensor. It will go down in the switch.

Then in your switch add a 10 sec delay and then publish the template sensors:

# Read from sensor when turned on
switch:
  - platform: gpio
    name: "Sample Water Level"
    id: relay_switch
    pin: 
      number: GPIO16
      inverted: true
      mode:
        output: True
        open_drain: True
    restore_mode: ALWAYS_OFF
    on_turn_on:
      - deep_sleep.prevent: deep_sleep_esp
      - logger.log: "Relay Switch turned ON, reading sensor"
      - delay: 10s
      - lambda: |-
          id(water_volume_adc).publish_state(id(raw_adc).state);

    on_turn_off:
      - logger.log: "Relay Switch turned OFF"
      - switch.turn_off: relay_switch
      - delay: 1s
      - if:
          condition:
            lambda: "return !id(block_sleep);"
          then:
            - deep_sleep.allow: deep_sleep_esp

Ahh champion thanks! So obvious when I see it now :slight_smile:

Sometimes I think it’s best to walk away from a issue and tackle it fresh the next day.

Cheers!

1 Like

Further to this, I added a bit of validation and tested with different timings, specifically to ensure the sensor does not take a sample probe reading when the relay (and therefore probe) is turned off (eg: 0V from probe) To allow some probe “start up time” I changed settings to take 3 samples at 2 seconds apart (6 seconds), then changed the switch delay to 20s between on and off to compensate for the 3 x 6 second readings (18 seconds total). Deep sleep awake time is 21s. So it effectively waits for 3 data samples to be taken from the probe 2 seconds apart 3 times, but only uses the last one to avoid any 0V issues. It also does a quick check to ensure positive voltage is received, filters out nan also, and checks to ensure wifi is still connected before attempting to send (in case it drops off whilst it’s waiting for samples) Seems to work nicely!

  - platform: adc
    id: raw_adc
    pin: GPIO3
    accuracy_decimals: 4
    update_interval: 2s
    samples: 3
    sampling_mode: avg
    attenuation: 12db
    unit_of_measurement: "V"
    internal: True
    filters:
      - filter_out: nan
      - skip_initial: 1
      - sliding_window_moving_average:
          window_size: 3
          send_every: 3
    on_value:
      - logger.log:
          format: "Sample received %.5f"
          args: [ 'id(raw_adc).state' ]

# Accept voltage from ADC, publish to water volume percent
  - platform: template
    name: "Water Volume ADC"
    id: water_volume_adc
    accuracy_decimals: 4
    unit_of_measurement: "V"
    update_interval: never
    filters:
      - filter_out: nan
      - filter_out: 0
      - lambda: |-
          if (id(relay_switch) && !(isnan(x)))
          {
            ESP_LOGI("custom", "Updating Water Volume ADC");
            id(water_volume_percent).publish_state(x);
            return x;
          }
          else
          {
            return {};
          }

# Calculate water volume from ADC, compensate for small lowest voltage
  - platform: template
    name: "Water Volume %"
    unit_of_measurement: "%"
    id: water_volume_percent
    update_interval: never
    filters:
      - filter_out: nan
      - lambda: |-
          ESP_LOGI("custom", "Updating Water Volume %%");
          auto y = ((x - 0.1) / 3.2) * 100.0; 
          if (y < 0) { y = 0; }
          return y;

# Read from sensor when turned on
switch:
  - platform: gpio
    name: "Sample Water Level"
    id: relay_switch
    pin: 
      number: GPIO16
      inverted: true
      mode:
        output: True
        open_drain: True
    restore_mode: ALWAYS_OFF
    on_turn_on:
      - deep_sleep.prevent: deep_sleep_esp
      - logger.log: "Relay Switch turned ON, reading sensor"
      - delay: 20s
      - if:
          condition:
            lambda: "return (id(raw_adc).state > 0);"
          then:
            - lambda: |-
                if (id(wifi_connection).is_connected())
                {
                  float y = id(raw_adc).state;
                  id(water_volume_adc).publish_state(id(raw_adc).state);
                }
      - switch.turn_off: relay_switch
    on_turn_off:
      - logger.log: "Relay Switch turned OFF"
      - switch.turn_off: relay_switch
      - if:
          condition:
            lambda: "return !id(block_sleep);"
          then:
           - deep_sleep.allow: deep_sleep_esp

Log output: