pool: Add frost protection master
authorDavid Woodhouse <dwmw@amazon.co.uk>
Mon, 7 Apr 2025 16:22:36 +0000 (17:22 +0100)
committerDavid Woodhouse <dwmw@amazon.co.uk>
Mon, 7 Apr 2025 16:22:36 +0000 (17:22 +0100)
The motor on the bypass valve probably burned out when it was being moved
during the winter while it was frozen. Just open it up at 5°C and leave
it open regardless of the temperature of the roof sensor.

Also fix the 'pump_default' script to run later at boot after the time
zone has actually been set, or it does the wrong thing.

And fix the "turn pump on if it's above 20°C in the morning" logic to
actually operate between 7:30am and 10am as it promises, instead of only
between 7:30 to 8, and 8 to 9. Oops :)

pool.yaml

index 450af065d659aef0c8bf08466feede1848406168..4a3d5974f007fcfc98b2ae9196bb16288f25a772 100644 (file)
--- a/pool.yaml
+++ b/pool.yaml
@@ -60,7 +60,7 @@
 # that the 'solar out' temperature is still higher than the ambient reading
 # from the 'solar in'. So before turning the pump on, ensure the bypass
 # valve is open, to allow water to flow directly back to the pool. By the
-# time the control value is permitted to change state again in five minutes,
+# time the control valve is permitted to change state again in five minutes,
 # both sensors should be reporting the true water temperature.
 
 substitutions:
@@ -69,6 +69,8 @@ substitutions:
 esphome:
   name: pool
   on_boot:
+    # Timezone is set at 220; see https://github.com/esphome/issues/issues/6840#issuecomment-2783475202
+    priority: 210
     then:
       - script.execute:
           id: pump_default
@@ -148,7 +150,15 @@ script:
         time_t now = ::time(NULL);
         bool cur_state = id(valve_output).state;
         bool want_state = cur_state;
-        if (cur_state) {
+        if (intemp < 5.0) {
+            // If approaching freezing, open the bypass valve and leave it open.
+            // Probably best to turn the pump off in that case too, not that it
+            // should ever actually be allowed to freeze in the pipes with the
+            // pump still active!
+            want_state = false;
+            id(frost_protection) = true;
+            id(pool_pump).turn_off();
+        } else if (cur_state) {
             // Only turn off if the output has got all the way down below the input temp
             want_state = outtemp >= intemp;
         } else {
@@ -168,15 +178,27 @@ script:
             if (t.hour >= 17 && t.hour < 19 && !want_state && id(pool_pump).state &&
                 now > id(pump_last_manual_change) + 600 &&
                 now > id(control_valve_last_change) + 600) {
-                    ESP_LOGD("control_value", "Cold for ten minutes. Turning pump off for the night");
+                    ESP_LOGD("control_valve", "Cold for ten minutes. Turning pump off for the night");
                     id(pool_pump).turn_off();
             }
 
             // Turn pump on if it's warm enough, between 7:30am and 10am (when it comes on anyway).
-            if (((t.hour == 7 && t.minute >= 30) || t.hour > 8) &&
-                t.hour < 10 && outtemp >= 20.0 && !id(pool_pump).state) {
-                    ESP_LOGD("control_value", "Roof temperature over 20°C. Turning pump ON and valve OFF.");
+            if (((t.hour == 7 && t.minute >= 30) || t.hour >= 8) &&
+                t.hour < 10 && outtemp >= 20.0 && !id(pool_pump).state &&
+                now > id(pump_last_manual_change) + 600) {
+                    ESP_LOGD("control_valve", "Roof temperature over 20°C. Turning pump ON and valve OFF.");
                     id(pool_pump).turn_on();
+                    id(control_valve_last_change) = now;
+                    id(valve_output).turn_off();
+            }
+
+            // Turn pump on if it was too cold at 10am but is now warm enough, before 5pm.
+            if (id(frost_protection) && t.hour >= 10 && t.hour < 17 && !id(pool_pump).state &&
+                now > id(pump_last_manual_change) + 600 && intemp > 5.0) {
+                    ESP_LOGD("control_valve", "Frost protection cancelled; turning pump ON and valve OFF.");
+                    id(frost_protection) = false;
+                    id(pool_pump).turn_on();
+                    id(control_valve_last_change) = now;
                     id(valve_output).turn_off();
             }
             return;
@@ -199,15 +221,23 @@ script:
     then:
       lambda: |-
         static bool done = false;
-        ESP_LOGD("pump_default", "Invoked with force = %s, done = %s", force ? "true" : "false", done ? "true" : "false");
+        ESP_LOGD("pump_default", "Invoked with force = %s, done = %s, intemp %f", force ? "true" : "false", done ? "true" : "false", id(solar_in).state);
         if (force || !done) {
            auto t = id(sntp_time).now();
+           auto intemp = id(solar_in).state;
            if (t.is_valid()) {
               ESP_LOGD("pump_default", "Setting pump switch at %dh", t.hour);
-              if (t.hour >= 10 && t.hour < 17)
+              if (t.hour >= 10 && t.hour < 17) {
+                if (intemp < 5.0) {
+                   ESP_LOGD("pump_default", "Frost protection at %f °C", intemp);
+                   id(frost_protection) = true;
+                   id(pool_pump).turn_off();
+                   return; // without setting 'done'
+                }
                 id(pool_pump).turn_on();
-              else
+              } else {
                 id(pool_pump).turn_off();
+              }
               done = true;
            } else {
               ESP_LOGD("pump_default", "Time not valid");
@@ -235,6 +265,11 @@ globals:
     restore_value: no
     initial_value: '0'
 
+  - id: frost_protection
+    type: bool
+    restore_value: no
+    initial_value: 'false'
+
 time:
   - platform: sntp
     on_time:
@@ -360,6 +395,7 @@ mqtt:
 
                 case 518: /* Pool pump switch */
                   id(pump_last_manual_change) = ::time(NULL);
+                  id(frost_protection) = false;
                   if (nvalue)
                     id(pool_pump).turn_on();
                   else