Home shut-off water valve

I have long thought about automating the input of hot and cold water. And not so long ago a flask of a cold water filter burst, flooding my house by 12 centimeters. Everything at home swam :slight_smile:
I ordered electro-water valves on Aliexpress, a board with four relays and an ESP8285 + RF, 4 leakage sensors. This is not advertising, I just write where I bought it.
Board - https://www.aliexpress.com/item/33047968234.html
Water leak sensor - https://www.aliexpress.com/item/32961220211.html
Motorized Valves - https://www.aliexpress.com/item/32960239345.html
I ordered 2 valves with manual override (CR301 + TF15-B2-B + BSP + DC12V) but got one what I ordered and second ( CR301 + TF15-B2-A + BSP + DC12V) with out manual override. Seller sent new valve with manual override! Highly recommend seller!

First I flashed 4CH Relay board with Tasmota, I have noticed that when you reset, 3 relays are activated briefly, I re-flashed on ESPHome, nothing has changed. So i decided to build custom hardware.

I used ESP32 module + RF module
Since the valve is controlled via the + 12V line, the control is assembled on two MOSFETs. N-MOSFET + P-MOSFET
I used the N-channel MOSFET IRLML6244TRPBF and the P-channel MOSFET IRLML2244TRPBF

ESPHome full config:

esphome:
  name: valvectrl
  platform: ESP32
  board: esp32dev

wifi:
  ssid: "ITTC2-WiFi"
  password: !secret
  domain: .mydomain.com.ua
  fast_connect: true

  manual_ip:
    static_ip: 192.168.1.1
    gateway: 192.168.1.254
    subnet: 255.255.255.0
    dns1: 192.168.1.250
    dns2: 192.168.1.251
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "valvectrl"
    password: !secret

time:
  - platform: sntp
    id: sntp_time
    servers: ntp.mydomain.com.ua
    timezone: Europe/Kiev

# web_server:
#   port: 80

# captive_portal:

# Enable logging
logger:
  level: INFO

# Enable Home Assistant API
api:
  password: !secret
  reboot_timeout: 30min

ota:
  password: !secret

# esp32_ble_tracker:
#   scan_interval: 30s

globals:
  - id: var_cold_open
    type: bool
    restore_value: yes
    # initial_value: 'true'
  - id: var_hot_open
    type: bool
    restore_value: yes
    # initial_value: 'false'
  - id: var_run_script
    type: bool
    initial_value: 'false'    
  - id: var_water_leak
    type: bool
    restore_value: yes
    initial_value: 'false'

dallas:
  - pin: GPIO23
    update_interval: 30s

sensor:
  - platform: dallas
    address: 0xD00000067FEC5B28
    id: temp1
    name: "Temp Cold Water"
    accuracy_decimals: 1
    filters:
      - sliding_window_moving_average:
          window_size: 3
          send_every: 3
      - or:
        - delta: 0.5
    on_value:
      then:
        - lambda: |- 
            ESP_LOGD("main", "Value of temp1 %f", id(temp1).state);
            id(temp1).publish_state(x);
  - platform: dallas
    address: 0x4F00000680EC9E28
    id: temp2
    name: "Temp Hot Water"
    accuracy_decimals: 1
    filters:
      # - offset: 2.0
      - sliding_window_moving_average:
          window_size: 3
          send_every: 3
      - or:
        - delta: 0.5
    on_value:
      then:
        - lambda: |- 
            ESP_LOGD("main", "Value of temp2 %f", id(temp2).state);
            id(temp2).publish_state(x);
  - platform: wifi_signal
    name: "WiFi Signal Sensor"
    id: wifi_signal_sensor
    update_interval: 30s
    filters:
      - median:
          window_size: 5
          send_every: 5
          send_first_at: 1
    on_value:
      then:
        - lambda: |-
            id(wifi_signal_sensor).publish_state(x);
  - platform: uptime
    name: Uptime Sensor            
remote_receiver:
  pin: GPIO27
  dump:
    - rc_switch
  # Settings to optimize recognition of RF devices
  tolerance: 50%
  filter: 250us
  idle: 4ms
  buffer_size: 2kb

## 1
# Received RCSwitch Raw: protocol=1 data='100111111111010110011110'
# Received RCSwitch Raw: protocol=1 data='100111111111010110010101' Button Battery Test
## 2
# Received RCSwitch Raw: protocol=1 data='101011000111000010001110'
# Received RCSwitch Raw: protocol=1 data='101011000111000010000101' Button Battery Test
## 3
# Received RCSwitch Raw: protocol=1 data='100010101001110110111110'
# Received RCSwitch Raw: protocol=1 data='100010101001110110110101' Button Battery Test
## 4
# Received RCSwitch Raw: protocol=1 data='100110000111000010001110'
# Received RCSwitch Raw: protocol=1 data='100110000111000010000101' Button Battery Test

binary_sensor:
  - platform: remote_receiver
    # name: "Water Leak 1"
    id: water_leak_1
    rc_switch_raw:
      code: "100111111111010110011110"
      protocol: 1
    on_press:
      then: 
        - script.execute: scr_wvalves_close
        - binary_sensor.template.publish:
            id: bin_wsensor_1
            state: ON
  - platform: remote_receiver
    # name: "Water Leak 2"
    id: water_leak_2
    rc_switch_raw:
      code: "101011000111000010001110"
      protocol: 1
    on_press:
      then: 
        - script.execute: scr_wvalves_close
        - binary_sensor.template.publish:
            id: bin_wsensor_2
            state: ON
  - platform: remote_receiver
    # name: "Water Leak 3"
    id: water_leak_3
    rc_switch_raw:
      code: "100010101001110110111110"
      protocol: 1
    on_press:
      then: 
        - script.execute: scr_wvalves_close
        - binary_sensor.template.publish:
            id: bin_wsensor_3
            state: ON     
  - platform: remote_receiver
    # name: "Water Leak 4"
    id: water_leak_4
    rc_switch_raw:
      code: "100110000111000010001110"
      protocol: 1
    on_press:
      then: 
        - script.execute: scr_wvalves_close
        - binary_sensor.template.publish:
            id: bin_wsensor_4
            state: ON       
  - platform: remote_receiver
    # name: "Water Leak 1 Button"
    id: water_leak_1_button
    rc_switch_raw:
      code: "100111111111010110010101"
      protocol: 1
    on_press:
      then: 
        - script.execute: scr_wvalves_close
        - binary_sensor.template.publish:
            id: bin_wsensor_1
            state: ON
  - platform: remote_receiver
    # name: "Water Leak 2 Button"
    id: water_leak_2_button
    rc_switch_raw:
      code: "101011000111000010000101"
      protocol: 1
    on_press:
      then: 
        - script.execute: scr_wvalves_close
        - binary_sensor.template.publish:
            id: bin_wsensor_2
            state: ON
# Show triggered sensors            
  - platform: template    
    device_class: moisture
    id: 'bin_wsensor_1'
    name: 'WSens1'
  - platform: template    
    device_class: moisture
    id: 'bin_wsensor_2'
    name: 'WSens2'
  - platform: template    
    device_class: moisture
    id: 'bin_wsensor_3'
    name: 'WSens3'
  - platform: template    
    device_class: moisture
    id: 'bin_wsensor_4'
    name: 'WSens4'
  - platform: template
    device_class: opening
    name: 'Cold Valve State'
    id: 'cold_valve_state'
    lambda: |-
      if (id(var_cold_open) == true) {
        return true;
      } else {
        return false;
      }
# Valve states      
  - platform: template
    device_class: opening
    name: 'Hot Valve State'
    id: 'Hot_valve_state'
    lambda: |-
      if (id(var_hot_open) == true) {
        return true;
      } else {
        return false;
      }
  - platform: template
    device_class: safety
    name: 'Water leak'
    id: 'water_leak_alert'
    lambda: |-
      if(id(var_water_leak) == true) {
        return true;
      } else {
        return false;
      }
script:
  - id: scr_wvalves_close
    then:
        - lambda: !lambda |-
            ESP_LOGD("RF-OnPress", "Alert triggered!!! Close Valves!!! Block Open!!!");
            if (id(cov_cold_water).current_operation != COVER_OPERATION_CLOSING) {
              id(cov_cold_water).close();
            } else {
              ESP_LOGD("RF-OnPress", "Already closing, nothing to do!");
            }
            if (id(cov_hot_water).current_operation != COVER_OPERATION_CLOSING) {
              id(cov_hot_water).close();
            } else {
              ESP_LOGD("RF-OnPress", "Already closing, nothing to do!");
            }
            id(water_leak_alert).publish_state(true);
            id(var_water_leak) = true;
# Switches
switch:
  - platform: gpio
    pin: 19
    interlock: &interlock1 [cold_open, cold_close]
    # name: "Cold open"
    id: 'cold_open'
    restore_mode: ALWAYS_OFF
  - platform: gpio
    pin: 21
    interlock: *interlock1
    # name: "Cold close"
    id: 'cold_close'
    restore_mode: ALWAYS_OFF
  - platform: gpio
    pin: 25
    interlock: &interlock2 [hot_open, hot_close]
    # name: "Hot open"
    id: 'hot_open'
    restore_mode: ALWAYS_OFF
  - platform: gpio
    pin: 32
    interlock: *interlock2
    # name: "Hot close"
    id: 'hot_close'
    restore_mode: ALWAYS_OFF    
# Electric door lock
  - platform: gpio
    pin: 18
    #name: "Door Lock"
    id: 'door_lock'
    restore_mode: ALWAYS_OFF    
  - platform: template
    name: "Door Remote"
    icon: "mdi:door"
    turn_on_action:
    - switch.turn_on: door_lock
    - delay: 50ms
    - switch.turn_off: door_lock
# Reset water leak alert    
  - platform: template
    name: "Reset Water leak alert"
    id: "reset_water_leak"
    turn_on_action:
      - lambda: !lambda |-
          id(var_water_leak) = false;
          id(water_leak_alert).publish_state(false);
          id(bin_wsensor_1).publish_state(false);
          id(bin_wsensor_2).publish_state(false);
          id(bin_wsensor_3).publish_state(false);
          id(bin_wsensor_4).publish_state(false);
    turn_off_action:
      - lambda: !lambda |-
          id(var_water_leak) = false;
          id(water_leak_alert).publish_state(false);
          id(bin_wsensor_1).publish_state(false);
          id(bin_wsensor_2).publish_state(false);
          id(bin_wsensor_3).publish_state(false);
          id(bin_wsensor_4).publish_state(false);
# If water leak detected, valves can't be opened util the alert "id(reset_water_leak)" is reset
cover:
  - platform: time_based
    name: "Cold Water Control"
    id: cov_cold_water
    open_action:
      - lambda: |-
          if (id(water_leak_alert).state){
            ESP_LOGD("NotAllowed", "Water LEAK Alert!");
            id(cov_cold_water).position = 0.0;
            id(cov_cold_water).current_operation = COVER_OPERATION_IDLE;
            id(cov_cold_water).publish_state();
          } else {
            id(cold_open).turn_on();
            id(var_cold_open) = true;
          }
    open_duration: 7s
    close_action:
      - switch.turn_on: cold_close
      - lambda: |-
          id(var_cold_open) = false;      
    close_duration: 7s
    stop_action:
      - switch.turn_off: cold_open
      - switch.turn_off: cold_close
  - platform: time_based
    name: "Hot Water Control"
    id: cov_hot_water
    open_action:
      - lambda: |-
          if (id(water_leak_alert).state){
            ESP_LOGD("NotAllowed", "Water LEAK Alert!");
            id(cov_hot_water).position = 0.0;
            id(cov_hot_water).current_operation = COVER_OPERATION_IDLE;
            id(cov_hot_water).publish_state();
          } else {
            id(hot_open).turn_on();
            id(var_hot_open) = true;
          }
    open_duration: 7s
    close_action:
      - switch.turn_on: hot_close
      - lambda: |-
          id(var_hot_open) = false;
    close_duration: 7s
    stop_action:
      - switch.turn_off: hot_open
      - switch.turn_off: hot_close          

Currently config and custom board under development
I will update post when I create new config.

Hardware:


UPDATE 1:

  • New code.

  • New photos :grinning:

  • Now hardware can open electric door lock

The idea is to use WiFi Presence detection + ESP32 BLE tracker to open door when user just showed up on home WiFi network and BLE device near the door (not yet implemented)

7 Likes

I really like your solution and your build - that’s some great work!

For what it is worth, I have also had some trouble with relay boards all coming on, just for a second, at startup. I posted about it here, and @glmnet posted a solution :Using PCA9685 to control relays: unit defaults to On

Basically his approach is to wire the relays in such a way that only one can ever be on at the same time. In the example he shows 3 relays, but there is no reason why you couldn’t keep going.

Just thought I’d share that info with you, because it seems to be a common problem with those relay boards.

1 Like

Yes it is good solution if you need only 3 relays on 4 relay board
As I understand relay1 become an input on/off and devices connected to relay 2-4

I need all 4 relays for two valves

Building custom board was good learning curve :slight_smile:

Privet @ittchmh,

It looks like you’ve had your config for a little while now. How are you liking it? Anything you would change?

Zdarova @poldim

There were no leaks, config works good nothing changed.

I need to dismount it and re-flash over COM port to add bluetooth presence detection module, because flash FS must be extended, I can’t re-flash over the air.

If you plan to assemble hardware like me - buy valve model CR301 or CR702 and use motor driver like L293D

My plan was on using these valves config’d as DC24V / TF25-B2-B / CR702 / NPT. Then I was debating between using one or two relays to switch the inputs to the valve.

Your suggestion to use the motor shield is interesting. I’ll take a look at it in more detail.

@ittchmh,

Quick question - Are you doing anything for battery monitoring of the sensors?
I see you have Button Battery Test in your code but the code you have there is tied to the Water Leak # Button. I guess each sensor is going to be a bit different, but I’m wondering if they output anything on low battery warning.

I have opened Valve case, where is 2 button stop switches inside

Button Battery Test - my plan was to record sensor last button press and send alert each 3 or 6 month to press button ageing and reset battery test timer. So alert wont be reset if sensor battery dead

Best option is CR702 and DC motor driver, also you can take 5V version, with 5V power supply you don’t need step down DC-DC if you will use Dev board
If I have to build this ageing - will use CR702 5V and DC motor driver

I had a spare L293D motor driver so I used that with CR702 (12V version). It was very easy to set up since all you need is a 12V power supply, DC-DC step down converter to get 5V for nodemcu, and a motor driver.

esphome:
  name: "esp_motorized_valves"
  platform: ESP8266
  board: nodemcuv2

  # Seems like motor driver wakes up at boot for a split second. Maybe something has to be pulled up/down but we just do this check in code.
  on_boot:
    priority: 250
    then:
      - delay: 1s
      - lambda: |-
          if (id(valve1_open_signal).state) {
            id(valve_state_text).publish_state("Open fully");
          } else if (id(valve1_closed_signal).state) {
            id(valve_state_text).publish_state("Closed fully");
          } else {
            id(valve_state_text).publish_state("Partially open");
          }
      - if:
          condition:
            and:
              - binary_sensor.is_off: "valve1_open_signal"
          then:
            - switch.turn_off: "water_valve"

substitutions:
  hostname: "esp_motorized_valves"

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

# Enable Web
web_server:
  port: 80

# Enable logging
logger:
  level: DEBUG

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

ota:
  safe_mode: True
  password: !secret ota_password

time:
  - platform: sntp
    id: my_time

switch:
  - platform: restart
    name: $hostname restart

  - platform: gpio
    pin: D0
    name: "Enable valve switch"
    id: "valve1_enable"
    restore_mode: RESTORE_DEFAULT_OFF
    internal: True

  - platform: gpio
    pin: D1
    name: "Valve open pin"
    id: "valve1_open_switch"
    restore_mode: RESTORE_DEFAULT_OFF
    interlock: &interlock_group [valve1_open_switch, valve1_close_switch]
    internal: True

  - platform: gpio
    pin: D4
    name: "Valve close pin"
    id: "valve1_close_switch"
    restore_mode: RESTORE_DEFAULT_OFF
    interlock: *interlock_group
    internal: True

  - platform: template
    name: "Water valve"
    icon: "mdi:valve"
    assumed_state: True
    id: "water_valve"
    lambda: |-
      if (id(valve1_open_signal).state) {
        return true;
      } else if (id(valve1_closed_signal).state) {
        return false;
      } else {
        return {};
      }
    turn_on_action:
      if:
        condition:
          - binary_sensor.is_off: "valve1_open_signal"
        then:
          - switch.turn_on: "valve1_enable"
          - switch.turn_on: "valve1_open_switch"
          - switch.turn_off: "valve1_close_switch"
    turn_off_action:
      if:
        condition:
          - binary_sensor.is_off: "valve1_closed_signal"
        then:
          - switch.turn_on: "valve1_enable"
          - switch.turn_on: "valve1_close_switch"
          - switch.turn_off: "valve1_open_switch"

text_sensor:
  - platform: version
    id: "valve_state_text"
    icon: "mdi:water"
    name: "Water valve state"

binary_sensor:

  - platform: gpio
    pin:
      number: D2
      inverted: True
      mode: INPUT_PULLUP
    id: "valve1_open_signal"
    name: "Valve fully open"
    internal: True
    on_state:
      then:
        - if:
            condition:
              and:
                - binary_sensor.is_on: "valve1_open_signal"
            then:
              - delay: 1s
              - switch.turn_off: "valve1_enable"
              - switch.turn_off: "valve1_open_switch"
              - lambda: |-
                 id(valve_state_text).publish_state("Open fully");
                 id(water_valve).publish_state(true);
        - if:
            condition:
              and:
                - binary_sensor.is_off: "valve1_open_signal"
                - binary_sensor.is_off: "valve1_closed_signal"
            then:
              - lambda: |-
                 id(valve_state_text).publish_state("Partially open");
    
  - platform: gpio
    pin:
      number: D3
      inverted: True
      mode: INPUT_PULLUP
    id: "valve1_closed_signal"
    name: "Valve fully closed"
    internal: True
    on_state:
      then:
        - if:
            condition:
              and:
                - binary_sensor.is_on: "valve1_closed_signal"
            then:
              - delay: 1s
              - switch.turn_off: "valve1_enable"
              - switch.turn_off: "valve1_close_switch"
              - lambda: |-
                 id(valve_state_text).publish_state("Closed fully");
                 id(water_valve).publish_state(false);
        - if:
            condition:
              and:
                - binary_sensor.is_off: "valve1_open_signal"
                - binary_sensor.is_off: "valve1_closed_signal"
            then:
              - lambda: |-
                 id(valve_state_text).publish_state("Partially open");
    
1 Like

There is only one feature missing and it is this one

Like local ocr for the esp32 cam component.

Sounds nice - did you open a feature-request in the esphome github?

pumping up this cool project.
I need to put this valve on a single pipe coming into the house from the water supply company.
Will be adding info when I get the items.
My hot water pipe starts after the water heater. I will need to add some kind of protection in case the heater decided to spill its entire content in the basement. My immediate concern is a pipe/tube bursting anywhere in the house. So I think one single valve at the main water entry with several water sensors will do the trick.
I will be ordering the CR702 V5 version.