Advertisement
bdnstn

esp-sprinkler-controller.yaml

Nov 24th, 2023
146
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
YAML 20.12 KB | None | 0 0
  1. substitutions:
  2.   friendly_name: Retic
  3.   area_1_name: "North Lawn"
  4.   area_2_name: "West Garden"
  5.   area_3_name: "South Lawn"
  6.   area_4_name: "South Bush"
  7.   area_5_name: "North Bush"
  8.   area_6_name: "Caves Road"
  9.   count_of_valves: "6"
  10.   winter_water_time: "2"
  11.   water_tank_radius_m: "1.17"
  12.   usable_water_head: "1.57"     # Bottom of lower float switch activation to full water level is 1.57m. Measured 2/11/23.
  13.   us_sensor_full_level: "0.461" # Value used to align 100% with full switch. Distance reported by U/S sensor when the water_tank_full_float_switch (float switch) sensor = true.
  14.   bore_flow_rate_lpm: "14"
  15.   retic_flow_rate_lpm: "40"     # 48 estimated by refill time on full run. 40 calculated on new lawn only. Is a fitting blown on caves road or bund?
  16.   phi: "3.14"
  17.  
  18.   # GPIO0, 2, 5, 12 and 15 are strapping pins.
  19.   # GPIO0, 2 and 12 are critical as they can interfere with flashing.
  20.  
  21. globals:
  22.   - id: is_summertime      
  23.     type: bool
  24.     initial_value: "false"
  25.   - id: is_wintertime_water_day      
  26.     type: bool
  27.     initial_value: "false"
  28.   - id: excess_solar
  29.     type: int
  30.     initial_value: "0"
  31.   - id: weather_factor
  32.     type: float
  33.     initial_value: "1"
  34.   - id: total_watering_duration
  35.     type: float
  36.     initial_value: "0"
  37.   - id: next_watering_timestamp
  38.     type: int64_t
  39.   - id: last_watered
  40.     type: ESPTime
  41.     restore_value: true
  42.   - id: tank_full_after_sunset
  43.     type: bool
  44.     restore_value: true
  45.     initial_value: "false"
  46.   - id: tank_was_empty
  47.     type: bool
  48.     restore_value: true
  49.     initial_value: "false"
  50.   - id: odd_day_number
  51.     type: bool
  52.   - id: calculated_water_in_tank
  53.     type: int
  54.     initial_value: "0"
  55.     restore_value: true
  56.  
  57. esphome:
  58.   name: esp-sprinkler-controller
  59.   friendly_name: '${friendly_name}'
  60.    
  61. esp32:
  62.   board: esp32dev
  63.   framework:
  64.     type: arduino
  65.  
  66. preferences:
  67.   flash_write_interval: 10min
  68.  
  69. # Enable logging
  70. logger:
  71.   level: DEBUG
  72.  
  73. # Enable Home Assistant API
  74. api:
  75.   encryption:
  76.     key: "QzppePufFQNLPzutZ74eoo2szcI8EeV5NpXn9e4nha8="
  77.   reboot_timeout: 30 min
  78.  
  79. ota:
  80.   password: "edc41232fc16aba976d8bd2549293b24"
  81.  
  82. wifi:
  83.   networks:
  84.   - ssid: !secret wifi_ssid
  85.     password: !secret wifi_password
  86.   - ssid: !secret ssid2
  87.     password: !secret pw2
  88.   - ssid: !secret ssid3
  89.     password: !secret pw3
  90.   id: wifi_id
  91.   # Enable fallback hotspot (captive portal) in case wifi connection fails
  92.   ap:
  93.     ssid: "Esp-Sprinkler-Controller"
  94.     password: "eAFllu9HjGvM"
  95.   reboot_timeout: 30 min
  96.  
  97. captive_portal:
  98. sprinkler:
  99.   - id: lawn_sprinkler_ctrlr
  100.     main_switch: "Main Switch"
  101.     auto_advance_switch: "Auto Advance"
  102.     reverse_switch: "Reverse order"
  103.     standby_switch: "System standby"
  104.     next_prev_ignore_disabled: true
  105.     pump_stop_valve_delay: 1s
  106.     pump_start_pump_delay: 1s
  107.     valve_overlap: 1s
  108.     valves:
  109.       - valve_switch: "${area_2_name}"      
  110.         enable_switch:
  111.           name: "Enable ${area_2_name}"
  112.           id: valve_2_enable
  113.         pump_switch_id: sprinkler_pump_sw
  114.         run_duration_number:
  115.           name: "${area_2_name} run"
  116.           unit_of_measurement: min
  117.           initial_value: 30
  118.           step: 1
  119.           id: valve_2_duration
  120.         valve_switch_id: valve_sw2
  121.  
  122.       - valve_switch: "${area_3_name}"
  123.         enable_switch:
  124.           name: "Enable ${area_3_name}"
  125.           id: valve_3_enable
  126.         pump_switch_id: sprinkler_pump_sw
  127.         run_duration_number:
  128.           name: "${area_3_name} run"
  129.           unit_of_measurement: min
  130.           initial_value: 30
  131.           step: 1
  132.           id: valve_3_duration
  133.         valve_switch_id: valve_sw3
  134.  
  135.       - valve_switch: "${area_1_name}"
  136.         enable_switch:
  137.           name: "Enable ${area_1_name}"
  138.           id: valve_1_enable
  139.         pump_switch_id: sprinkler_pump_sw
  140.         run_duration_number:
  141.           name: "${area_1_name} run"
  142.           unit_of_measurement: min
  143.           initial_value: 30
  144.           step: 1
  145.           id: valve_1_duration
  146.         valve_switch_id: valve_sw1
  147.  
  148.       - valve_switch: "${area_5_name}"
  149.         enable_switch:
  150.           name: "Enable ${area_5_name}"
  151.           id: valve_5_enable
  152.         pump_switch_id: sprinkler_pump_sw
  153.         run_duration_number:
  154.           name: "${area_5_name} run"
  155.           unit_of_measurement: min
  156.           initial_value: 30
  157.           step: 1
  158.           id: valve_5_duration
  159.         valve_switch_id: valve_sw5
  160.  
  161.       - valve_switch: "${area_4_name}"
  162.         enable_switch:
  163.           name: "Enable ${area_4_name}"
  164.           id: valve_4_enable
  165.         pump_switch_id: sprinkler_pump_sw
  166.         run_duration_number:
  167.           name: "${area_4_name} run"
  168.           unit_of_measurement: min
  169.           initial_value: 30
  170.           step: 1
  171.           id: valve_4_duration
  172.         valve_switch_id: valve_sw4
  173.  
  174.       - valve_switch: "${area_6_name}"
  175.         enable_switch:
  176.           name: "Enable ${area_6_name}"
  177.           id: valve_6_enable
  178.         pump_switch_id: sprinkler_pump_sw
  179.         run_duration_number:
  180.           name: "${area_6_name} run"
  181.           unit_of_measurement: min
  182.           initial_value: 30
  183.           step: 1
  184.           id: valve_6_duration
  185.         valve_switch_id: valve_sw6
  186.  
  187. switch:
  188.   - platform: gpio
  189.     inverted: true
  190.     id: sprinkler_pump_sw
  191.     pin: 19
  192.   - platform: gpio
  193.     inverted: true
  194.     id: valve_sw1
  195.     pin: 18
  196.   - platform: gpio
  197.     inverted: true
  198.     id: valve_sw2
  199.     pin: 17
  200.   - platform: gpio
  201.     inverted: true
  202.     id: valve_sw3
  203.     pin: 16
  204.   - platform: gpio
  205.     inverted: true
  206.     id: valve_sw4
  207.     pin: 23
  208.   - platform: gpio
  209.     inverted: true
  210.     id: valve_sw5
  211.     pin: 22
  212.   - platform: gpio
  213.     inverted: true
  214.     id: valve_sw6
  215.     pin: 21
  216.   - platform: gpio
  217.     inverted: true
  218.     name: "Bore pump"
  219.     id: bore_pump_sw8
  220.     pin: 15                 # Strapping pin, consider changing to different pin
  221.   - platform: template
  222.     name: "New lawn watering"
  223.     id: new_lawn_watering
  224.     optimistic: true
  225.     restore_mode: RESTORE_DEFAULT_OFF
  226.  
  227. button:
  228.   - platform: template
  229.     name: "Start summer cycle"
  230.     on_press:
  231.       then:
  232.          - sprinkler.clear_queued_valves:
  233.             id: lawn_sprinkler_ctrlr
  234.          - sprinkler.start_full_cycle: lawn_sprinkler_ctrlr
  235.  
  236.   - platform: template
  237.     name: "Start winter cycle"
  238.     on_press:
  239.       then:
  240.         - script.execute: run_winter_cycle
  241.  
  242.   - platform: template
  243.     name: "Start new lawn cycle"
  244.     on_press:
  245.       then:
  246.         - script.execute: run_new_lawn_cycle
  247.  
  248. sun:
  249.   latitude: -33.64562625475741
  250.   longitude: 115.18726072719008    
  251.   id: sun_id
  252.   on_sunset:
  253.     lambda: 'id(tank_full_after_sunset) = (id(water_tank_full).state);'
  254.  
  255. time:
  256.   - platform: homeassistant
  257.     id: ha_time
  258.     on_time:
  259.      # - seconds: 0
  260.       #   minutes: 0
  261.       #   hours: 11, 15
  262.       #   then:
  263.       #     - if:
  264.       #         condition:
  265.       #           switch.is_on: new_lawn_watering
  266.       #         then:
  267.       #           script.execute: run_new_lawn_cycle
  268.  
  269.       # Diag debug          
  270.       - seconds: 0
  271.         minutes: 45
  272.         hours: 10
  273.         then:
  274.           - script.execute: do_watering
  275.  
  276. script:
  277.   - id: do_watering
  278.     then:
  279.       - logger.log: "**** Do watering"
  280.       - script.execute: write_diag_log
  281.       - if:
  282.           condition:
  283.             lambda: 'return (is_summertime);'
  284.           then:
  285.             script.execute: run_summer_cycle
  286.       - script.execute: write_diag_log
  287.       - if:
  288.           condition:
  289.             lambda: 'return (is_wintertime_water_day);'
  290.           then:
  291.             script.execute: run_winter_cycle  
  292.       - script.execute: write_diag_log
  293.       - logger.log: "**** Do watering - done"
  294.  
  295.   - id: run_new_lawn_cycle
  296.     then:
  297.       - logger.log: "**** Run new lawn cycle"
  298.       - sprinkler.clear_queued_valves:
  299.           id: lawn_sprinkler_ctrlr
  300.       - sprinkler.set_multiplier:
  301.           id: lawn_sprinkler_ctrlr
  302.           multiplier: !lambda 'return id(weather_factor);'          
  303.       # Valves are numbered in the order they appear in the sprinkler controller’s configuration starting at zero (0).
  304.       - sprinkler.queue_valve:
  305.           id: lawn_sprinkler_ctrlr
  306.           valve_number: 3
  307.       - sprinkler.queue_valve:
  308.           id: lawn_sprinkler_ctrlr
  309.           valve_number: 2
  310.       - sprinkler.queue_valve:
  311.           id: lawn_sprinkler_ctrlr
  312.           valve_number: 1                
  313.       - sprinkler.start_from_queue:
  314.           id: lawn_sprinkler_ctrlr  
  315.       - logger.log: "**** Run new lawn cycle - done"
  316.  
  317.   - id: run_summer_cycle
  318.     then:
  319.       - logger.log: "**** Run summer cycle"
  320.       - sprinkler.clear_queued_valves:
  321.           id: lawn_sprinkler_ctrlr
  322.       - sprinkler.set_multiplier:
  323.           id: lawn_sprinkler_ctrlr
  324.           multiplier: !lambda 'return id(weather_factor);'
  325.       - if:
  326.           condition:
  327.             lambda: 'return id(odd_day_number);'
  328.           then:
  329.             - sprinkler.start_full_cycle: lawn_sprinkler_ctrlr
  330.           else:
  331.             - script.execute: run_new_lawn_cycle    
  332.       - logger.log: "**** Run summer cycle - done"
  333.  
  334.   - id: run_winter_cycle
  335.     then:
  336.       - logger.log: "**** Run winter cycle"
  337.       # Reset multiplier to 1 so we get required winter_water_time duration
  338.       - sprinkler.set_multiplier:
  339.          id: lawn_sprinkler_ctrlr
  340.          multiplier: 1.0  
  341.       # Clear the queue then queue winter_water_time for each
  342.       - sprinkler.clear_queued_valves:
  343.          id: lawn_sprinkler_ctrlr
  344.       - sprinkler.queue_valve:
  345.         # Valves are numbered in the order they appear in the sprinkler controller’s configuration starting at zero (0)
  346.          id: lawn_sprinkler_ctrlr
  347.          valve_number: 0
  348.          run_duration: ${winter_water_time}min
  349.       - sprinkler.queue_valve:
  350.          id: lawn_sprinkler_ctrlr
  351.          valve_number: 1
  352.          run_duration: ${winter_water_time}min
  353.       - sprinkler.queue_valve:
  354.          id: lawn_sprinkler_ctrlr
  355.          valve_number: 2
  356.          run_duration: ${winter_water_time}min
  357.       - sprinkler.queue_valve:
  358.          id: lawn_sprinkler_ctrlr
  359.          valve_number: 3
  360.          run_duration: ${winter_water_time}min
  361.       - sprinkler.queue_valve:
  362.          id: lawn_sprinkler_ctrlr
  363.          valve_number: 4
  364.          run_duration: ${winter_water_time}min
  365.       - sprinkler.queue_valve:
  366.          id: lawn_sprinkler_ctrlr
  367.          valve_number: 5
  368.          run_duration: ${winter_water_time}min
  369.       - sprinkler.start_from_queue:
  370.          id: lawn_sprinkler_ctrlr  
  371.       - logger.log: "**** Run winter cycle - done"
  372.  
  373.   - id: write_diag_log
  374.     then:
  375.       - logger.log: "**** Diagnostic log start ********************************************************************"
  376.       - logger.log:
  377.           format: "is_summertime %d  is_wintertime_water_day %d  tank_full_after_sunset %d  tank_was_empty %d  odd_day_number %d"
  378.           args: [ 'id(is_summertime)', 'id(is_wintertime_water_day)', 'id(tank_full_after_sunset)', 'id(tank_was_empty)', 'id(odd_day_number)' ]
  379.       # - logger.log:
  380.       #     format: "excess_solar %x  weather_factor %f  total_watering_duration %f  next_watering_timestamp %s  last_watered %s  calculated_water_in_tank %x"
  381.       #     args: [ 'id(excess_solar)', id(weather_factor), id(total_watering_duration), id(next_watering_timestamp), id(last_watered), id(calculated_water_in_tank) ]
  382.       - logger.log: "**** Diagnostic log end **********************************************************************"
  383.  
  384. binary_sensor:
  385.   - platform: gpio
  386.     name: "Water tank full float switch" # Diagnostic
  387.     pin:
  388.       number: 14
  389.       mode:
  390.         input: true
  391.         pullup: true
  392.     id: water_tank_full_float_switch
  393.     publish_initial_state: true
  394.     filters:
  395.     - delayed_on_off: 3s
  396.   - platform: template
  397.     name: "Winter water day" # Diagnostic
  398.     lambda: 'return id(is_wintertime_water_day);'
  399.   - platform: template
  400.     name: "Water tank full"
  401.     id: water_tank_full   # Tank full as measured by either the U/S sensor or the float switch
  402.     lambda: 'return (id(water_tank_full_float_switch).state or id(ultrasonic_measure).state <= ${us_sensor_full_level});'
  403.     on_state:            # This is a "fail safe" in case the U/S sensor fails to clear the tank_was_empty state.              
  404.       lambda: 'if (x) {id(tank_was_empty) = false;}'  
  405.   - platform: gpio
  406.     name: "Water tank empty"
  407.     id: water_tank_empty_float_switch
  408.     pin:
  409.       number: 32
  410.       mode:
  411.         input: true
  412.         pullup: true
  413.     publish_initial_state: true
  414.     filters:
  415.     - invert:
  416.     - delayed_on_off: 3s
  417.     on_state:
  418.       lambda: |-
  419.         if ((x) and (id(lawn_sprinkler_ctrlr).active_valve().has_value())) {
  420.           id(lawn_sprinkler_ctrlr).shutdown();
  421.         }
  422.         if (x) {id(tank_was_empty) = true;}
  423. sensor:
  424.   - platform: wifi_signal
  425.     name: "WiFi Signal Sensor"
  426.     update_interval: 60s
  427.   - platform: ultrasonic
  428.     trigger_pin: 26
  429.     echo_pin: 27
  430.     name: "Ultrasonic Sensor"
  431.     id: ultrasonic_measure
  432.     timeout: 4.0 m
  433.     update_interval: 60s
  434.     accuracy_decimals: 3
  435.     filters:
  436.       - quantile:
  437.           window_size: 7
  438.           send_every: 4
  439.           send_first_at: 1
  440.           quantile: 0.9
  441.       - lambda: !lambda |-
  442.           if (x > 1.1) return {};
  443.           return x;
  444.     on_value_range:
  445.       - below: 1.0
  446.         then:
  447.          - lambda: 'id(tank_was_empty) = false;'
  448.  
  449.   - platform: template
  450.     name: "Water tank capacity"
  451.     id: tank_capacity_l
  452.     lambda: 'return ${phi} * ${water_tank_radius_m} * ${water_tank_radius_m} * (${usable_water_head}) * 1000;'
  453.     unit_of_measurement: l
  454.     accuracy_decimals: 0
  455.     update_interval: 24h
  456.   - platform: homeassistant
  457.     name: Excess Solar
  458.     entity_id: sensor.excess_solar
  459.     id: ha_excess_solar
  460.   - platform: homeassistant
  461.     name: Weather factor
  462.     entity_id: sensor.weather_factor
  463.     id: ha_weather_factor
  464.     accuracy_decimals: 2
  465.   - platform: template
  466.     name: Multiplier
  467.     lambda: 'return id(lawn_sprinkler_ctrlr).multiplier();'
  468.   - platform: template
  469.     name: "Calculated water in tank"
  470.     unit_of_measurement: "L"
  471.     lambda: 'return id(calculated_water_in_tank);'
  472.  
  473. text_sensor:
  474.   - platform: wifi_info
  475.     ip_address:
  476.       name: '${friendly_name} - IP Address'
  477.       icon: mdi:wifi
  478.     ssid:
  479.       name: '${friendly_name} - Connected SSID'
  480.       icon: mdi:wifi-strength-2
  481.   - platform: template
  482.     name: "Calculated start time"
  483.     lambda: |-
  484.       char str[25];
  485.       time_t currTime = id(next_watering_timestamp);
  486.       strftime(str, sizeof(str), "%H:%M:%S", localtime(&currTime));
  487.       return (std::string) str;
  488.  
  489.  
  490. interval:
  491.   - interval: 1min
  492.     then:
  493.       - lambda: |-
  494.           if(id(bore_pump_sw8).state) {id(calculated_water_in_tank) += ${bore_flow_rate_lpm};}
  495.           if(id(sprinkler_pump_sw).state) {id(calculated_water_in_tank) -= ${retic_flow_rate_lpm};}
  496.           if(id(water_tank_empty_float_switch).state) {id(calculated_water_in_tank) = 0;}
  497.           if(id(water_tank_full).state) {id(calculated_water_in_tank) = id(tank_capacity_l).state;}
  498.  
  499.       - lambda: 'if(id(tank_was_empty)) {id(bore_pump_sw8).turn_on();}'
  500.  
  501.       - if:
  502.          # get what we need from HA when we are connected. Reasonable defaults are set on global definitions.
  503.           condition:
  504.             and:
  505.              - api.connected
  506.               - wifi.connected
  507.           then:    
  508.             - lambda: |-
  509.                 if(isfinite(id(ha_excess_solar).state)) {id(excess_solar) = id(ha_excess_solar).state;}
  510.                 if(isfinite(id(ha_weather_factor).state)) {id(weather_factor) = id(ha_weather_factor).state;}
  511.  
  512.       - if:
  513.          # Determine winter or summer
  514.           condition:
  515.             time.has_time:
  516.           then:
  517.            # Watering restrictions for bores in Busselton.
  518.             # Every day from 1 Sept to 31 May, once per day, before 9am or after 6pm. Summer = months 9, 10, 11, 12, 1, 2, 3, 4, 5.
  519.             # Pressure testing, once a week, for 2 minutes per station. Pref before 9am or after 6pm. Winter = months 6, 7, 8.
  520.             - lambda: |-
  521.                 auto time = id(ha_time).now();
  522.                 id(is_summertime) = bool(time.month > 8 or time.month < 6);
  523.                 id(is_wintertime_water_day) = ((time.month > 5 and time.month < 9) and (time.day_of_week = 3));
  524.                 id(odd_day_number) = (time.day_of_year % 2 != 0);
  525.      
  526.       - script.execute: write_diag_log
  527.  
  528.         # Calculate watering duration allowing for weather_factor and calculate start time to end at sunrise      
  529.       - lambda: |-
  530.           id(total_watering_duration) = 0;
  531.           if (id(is_wintertime_water_day)) {id(total_watering_duration) = $winter_water_time * $count_of_valves;}
  532.           if (id(is_summertime)) {
  533.             if (id(odd_day_number)) {
  534.               if (id(valve_1_enable).state) { id(total_watering_duration) += id(valve_1_duration).state; }
  535.               if (id(valve_2_enable).state) { id(total_watering_duration) += id(valve_2_duration).state; }
  536.               if (id(valve_3_enable).state) { id(total_watering_duration) += id(valve_3_duration).state; }
  537.               if (id(valve_4_enable).state) { id(total_watering_duration) += id(valve_4_duration).state; }
  538.               if (id(valve_5_enable).state) { id(total_watering_duration) += id(valve_5_duration).state; }
  539.               if (id(valve_6_enable).state) { id(total_watering_duration) += id(valve_6_duration).state; }
  540.             } else {
  541.               if (id(valve_1_enable).state) { id(total_watering_duration) += id(valve_1_duration).state; }
  542.               if (id(valve_3_enable).state) { id(total_watering_duration) += id(valve_3_duration).state; }
  543.               if (id(valve_5_enable).state) { id(total_watering_duration) += id(valve_5_duration).state; }
  544.             }
  545.             id(total_watering_duration) = id(total_watering_duration) * id(weather_factor);
  546.           }
  547.           auto total_watering_duration_sec = id(total_watering_duration) * 60;
  548.           auto next_sunrise = id(sun_id).sunrise(-0.833).value();
  549.           int64_t sunrise_timestamp = next_sunrise.timestamp;
  550.           id(next_watering_timestamp) = sunrise_timestamp - total_watering_duration_sec;
  551.  
  552.       - if:
  553.          # Determine if it's time to start watering
  554.           condition:
  555.             time.has_time:
  556.           then:
  557.             - if:
  558.                 condition:
  559.                   lambda: |-
  560.                     auto now_time = id(ha_time).now();
  561.                     return (((id(next_watering_timestamp) <= now_time.timestamp) and id(last_watered).day_of_year < now_time.day_of_year)
  562.                     and (id(is_wintertime_water_day) or id(is_summertime))
  563.                     and (id(weather_factor) > 0));
  564.                 then:
  565.                   - lambda: 'id(last_watered) = id(ha_time).now();'    
  566.                   - script.execute: write_diag_log                
  567.                   - script.execute: do_watering  
  568.  
  569.   - interval: 10min
  570.     then:
  571.      # Bore water tank refill pump control
  572.       if:
  573.         condition:
  574.          sun.is_above_horizon
  575.         then:
  576.          # Daytime - we might have solar power
  577.           lambda: |-
  578.             if ((id(excess_solar) >= 500) or id(tank_was_empty)) {
  579.               if (id(water_tank_full).state == false) {
  580.                 id(bore_pump_sw8).turn_on();
  581.               } else {
  582.                 id(bore_pump_sw8).turn_off();
  583.                 }
  584.                 // # Run the pump regardless of solar if water getting too low
  585.                 // #if ((id(water_tank_full_float_switch).state == false) and (id(tank_water_percent).state < 33)) {
  586.                 // #  id(bore_pump_sw8).turn_on();
  587.                 // #} else {
  588.                 // #  id(bore_pump_sw8).turn_off();
  589.                 // #  }
  590.               }
  591.         else:
  592.          # Nighttime - top up if we couldn't do it during the day
  593.           lambda: |-
  594.             if ((id(tank_full_after_sunset) == false) and (id(water_tank_full).state == false)) {
  595.               id(bore_pump_sw8).turn_on();
  596.             }
  597.             if (id(water_tank_full).state) {
  598.               id(bore_pump_sw8).turn_off();
  599.               id(tank_full_after_sunset) = true;
  600.             }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement