]> www.infradead.org Git - users/dwmw2/esp32-pool.git/commitdiff
First import
authorDavid Woodhouse <dwmw@amazon.co.uk>
Tue, 8 Aug 2023 19:48:32 +0000 (20:48 +0100)
committerDavid Woodhouse <dwmw@amazon.co.uk>
Tue, 8 Aug 2023 19:48:32 +0000 (20:48 +0100)
pool.yaml [new file with mode: 0644]

diff --git a/pool.yaml b/pool.yaml
new file mode 100644 (file)
index 0000000..8963e4f
--- /dev/null
+++ b/pool.yaml
@@ -0,0 +1,554 @@
+#
+# Olimex ESP32-Gateway.
+#
+# GPIO13: 1Wire sensors for solar in/out temperatures
+# GPIO14: 12v relay for bypass valve: high to close (pump via roof)
+# GPIO16: 5v relay for 1Wire sensors' power (to reset them)
+
+esphome:
+  name: pool
+
+esp32:
+  board: esp32-gateway
+  framework:
+    type: arduino
+
+# Enable logging
+logger:
+  level: DEBUG
+
+ota:
+  password: !secret ota_upgrade_pw
+
+external_components:
+ - source: github://nrandell/dallasng
+ - source:
+     type: local
+     path: ../git/esphome_syslog/components
+   components: [syslog]
+
+#wifi:
+#  ssid: !secret wifi_ssid
+#  password: !secret wifi_pw
+#
+#  # Enable fallback hotspot (captive portal) in case wifi connection fails
+#  ap:
+#    ssid: "Pool Fallback Hotspot"
+#    password: "JWWHJb38UzxY"
+#
+#  manual_ip: !include poolup.yaml
+#
+#captive_portal:
+
+syslog:
+    ip_address: !secret syslog_ip
+
+script:
+  - id: bounce_1w_power
+    then:
+      - switch.turn_off: power_1w
+      - delay: 1s
+      - switch.turn_on: power_1w
+
+  - id: control_valve
+    then:
+      lambda: |-
+        if (id(solar_out_fails) || id(solar_in_fails))
+          return;
+
+        auto outtemp = id(solar_out).state;
+        auto intemp = id(solar_in).state;
+
+        if (isnan(outtemp) || isnan(intemp))
+          return;
+
+        // We consumed this reading pair.
+        //id(solar_out_fails) = id(solar_in_fails) = 1;
+
+        if (outtemp == intemp)
+          return;
+
+        boolean want_state = outtemp > intemp;
+        if (id(valve_output).state == want_state) {
+            ESP_LOGD("control_valve", "Leaving valve %s", want_state ? "ON" : "OFF");
+            return;
+        }
+
+        static time_t last_change = 0;
+        time_t now = ::time(NULL);
+
+        if (now < last_change + 300) {
+            ESP_LOGD("control_valve", "Too soon to turn %s (%d seconds)", want_state ? "ON" : "OFF", now - last_change);
+            return;
+        }
+
+        last_change = now;
+        id(valve_output).toggle();
+
+globals:
+  - id: solar_in_fails # Number of minutes without a reading.
+    type: int
+    restore_value: no
+    initial_value: '1'
+
+  - id: solar_out_fails
+    type: int
+    restore_value: no
+    initial_value: '1'
+
+time:
+  - platform: sntp
+    id: sntp_time
+    servers: !secret ntp_servers
+    on_time:
+        # Turn on BLE client every 30 minutes for 2 minutes
+        # (or until it turns itself off after a successful reading)
+      - seconds: 0
+        minutes: /10
+        then:
+          - switch.turn_on: ble_switch
+          - delay: 2min
+          - switch.turn_off: ble_switch
+      - seconds: 0
+        minutes: /1
+        then:
+          - if:
+              condition:
+                lambda: |-
+                  if (!id(sntp_time).now().is_valid()) {
+                      // Don't count failures while it's turned off anyway.
+                      return false;
+                  }
+                  if (id(power_1w).state) {
+                    // Bump the failure count unconditionally. A successful reading will zero it.
+                    id(solar_in_fails)++;
+                    id(solar_out_fails)++;
+                  }
+                  // Failing for 5 consecutive readings (one per minute)?
+                  return id(solar_in_fails) > 5 || id(solar_out_fails) > 5;
+              then:
+                # Turn them off and on again.
+                script.execute: bounce_1w_power
+
+ethernet:
+  type: LAN8720
+  mdc_pin: GPIO23
+  mdio_pin: GPIO18
+  clk_mode: GPIO17_OUT
+  phy_addr: 0
+  manual_ip: !include poolip.yaml
+
+#network:
+#  enable_ipv6: true
+
+mqtt:
+  broker: !secret mqtt_server
+  port: 1884
+  discovery_prefix: ${mqtt_prefix}/homeassistant
+  log_topic: ${mqtt_prefix}/logs
+  username: "pool"
+  password: !secret pool_mqtt_pw
+  id: mqtt_client
+  on_connect:
+      then:
+        - mqtt.publish_json:
+            topic: domoticz/in
+            payload: |-
+              root["command"] = "udevice";
+              root["idx"] = 512;
+              root["nvalue"] = id(valve_output).state ? 1 : 0;
+
+  on_json_message:
+    - topic: domoticz/out
+      then:
+        - lambda: |-
+            int idx = x["idx"];
+            int nvalue = x["nvalue"].as<int>();
+
+            // ESP_LOGD("on_json_message", x["name"]);
+            switch (idx) {
+               case 512: /* Solar valve switch */
+                  if (nvalue)
+                    id(valve_output).turn_on();
+                  else
+                    id(valve_output).turn_off();
+            }
+
+    - topic: glow/BCDDC2C24DB0/SENSOR/electricitymeter
+      then:
+        - lambda: |-
+            auto cumulative = x["electricitymeter"]["energy"]["import"]["cumulative"];
+            int kwh, watts;
+            if (cumulative) {
+               // { "command" : "udevice", "idx" : 29, "svalue" : $cumulative }
+               kwh = (cumulative.as<double>() * 1000.0f) + 0.1f;
+               id(mqtt_client).publish_json("domoticz/in", [=](JsonObject root) {
+                                                           root["command"] = "udevice";
+                                                           root["idx"] = 29;
+                                                           root["svalue"] =  std::to_string(kwh);
+                                                           });
+            }
+            auto power = x["electricitymeter"]["power"]["value"];
+            if (power) {
+               watts = (power.as<double>() * 1000.0f) + 0.1f;
+               // { "command" : "udevice", "idx" : 25, "svalue" : $power }
+               id(mqtt_client).publish_json("domoticz/in", [=](JsonObject root) {
+                                                           root["command"] = "udevice";
+                                                           root["idx"] = 24;
+                                                           root["svalue"] = std::to_string(watts);
+                                                           });
+            }
+            if (cumulative && power) {
+               id(mqtt_client).publish_json("domoticz/in", [=](JsonObject root) {
+                                                           root["command"] = "udevice";
+                                                           root["idx"] = 514;
+                                                           root["svalue"] = std::to_string(watts) + ";" + std::to_string(kwh);
+                                                           });
+            }
+
+dallasng:
+  - pin: GPIO13
+    update_interval: 60s
+
+esp32_ble_tracker: #### Stop the active scan ####
+  scan_parameters:
+    active: false
+    continuous: true
+  on_ble_advertise:
+    - mac_address:
+        - C0:00:00:01:67:d7
+      then:
+        - lambda: |-
+            ESP_LOGD("ble_adv", "New BLE device");
+            ESP_LOGD("ble_adv", "  address: %s", x.address_str().c_str());
+            ESP_LOGD("ble_adv", "  name: %s", x.get_name().c_str());
+            ESP_LOGD("ble_adv", "  Advertised service UUIDs:");
+            for (auto uuid : x.get_service_uuids()) {
+                ESP_LOGD("ble_adv", "    - %s", uuid.to_string().c_str());
+            }
+            ESP_LOGD("ble_adv", "  Advertised service data:");
+            for (auto data : x.get_service_datas()) {
+                ESP_LOGD("ble_adv", "    - %s: (length %i)", data.uuid.to_string().c_str(), data.data.size());
+            }
+            ESP_LOGD("ble_adv", "  Advertised manufacturer data:");
+            for (auto data : x.get_manufacturer_datas()) {
+                ESP_LOGD("ble_adv", "    - %s: (length %i)", data.uuid.to_string().c_str(), data.data.size());
+            }
+  on_scan_end:
+    - lambda: |-
+         if (id(ble_yc01_ble_connected).state) {
+            ESP_LOGD("ble_yc01", "Scan complete, triggering update");
+            id (ble_yc01_rssi).update();
+            id (ble_yc01_sensor).update();
+         }
+
+button:
+  - platform: template
+    name: "Start Scan"
+    on_press:
+      - esp32_ble_tracker.start_scan:
+
+  - platform: template
+    name: "Stop Scan"
+    on_press:
+      - esp32_ble_tracker.stop_scan:
+
+light:
+  - platform: binary
+    name: "Green LED"
+    id: green_led
+    output: led_output
+
+output:
+  - id: led_output
+    platform: gpio
+    pin: GPIO33
+
+binary_sensor:
+- platform: status
+  name: "ESP32 Status"
+- platform: template
+  id: ble_yc01_ble_connected
+  icon: mdi:bluetooth-connect
+  name: "BLE Connected"
+
+######################################################
+##                                                  ##
+##   To initiate to connection with the BLE device  ##
+##                                                  ##
+######################################################
+
+ble_client:
+  - mac_address: C0:00:00:01:67:d7 #Use the MAC address of your BLE device
+    id: ble_yc01
+    on_connect: #### Actions to perform when connecting to the BLE device ####
+      then:
+        - lambda: |-
+            ESP_LOGD("ble_client_lambda", "Connected to BLE-YC01");
+            id(ble_yc01_ble_connected).publish_state(true);
+
+    on_disconnect:
+      then:
+        - lambda: |-
+            ESP_LOGD("ble_client", "Disconnected from BLE-YC01");
+            id(ble_yc01_ble_connected).publish_state(false);
+
+######################################################
+##                                                  ##
+##     Sensors associated with the BLE device       ##
+##                                                  ##
+######################################################
+
+sensor: #### Template sensor as their values are publish from a lambda or the BLE client ####
+  - platform: dallasng
+    address: 0xd4030497940a0e28
+    name: "Solar in"
+    id: solar_in
+    on_value:
+      then:
+        lambda: |-
+           id(solar_in_fails) = 0;
+           id(mqtt_client).publish_json("domoticz/in", [=](JsonObject root) {
+                                                           root["command"] = "udevice";
+                                                           root["idx"] = 507;
+                                                           root["svalue"] = std::to_string(x);
+                                        });
+           id(control_valve)->execute();
+
+  - platform: dallasng
+    address: 0xa0031397941af528
+    name: "Solar out"
+    id: solar_out
+    on_value:
+      then:
+       - lambda: |-
+           id(solar_out_fails) = 0;
+           id(mqtt_client).publish_json("domoticz/in", [=](JsonObject root) {
+                                                           root["command"] = "udevice";
+                                                           root["idx"] = 508;
+                                                           root["svalue"] = std::to_string(x);
+                                        });
+           id(control_valve)->execute();
+
+#  - platform: dallasng
+#    address: 0xc603156332b8ff28
+#    name: "Temp 3"
+#    id: temp3
+
+#  - platform: dallasng
+#    address: 0x040115636a80ff28
+#    name: "Temp 4"
+#    id: temp4
+
+  - platform: template
+    name: "BLE-YC01 EC"
+    id: ble_yc01_ec_sensor
+    unit_of_measurement: "µS/cm"
+    accuracy_decimals: 0
+    state_class: measurement
+    icon: mdi:water-opacity
+
+  - platform: template
+    name: "BLE-YC01 TDS"
+    id: ble_yc01_tds_sensor
+    unit_of_measurement: "ppm"
+    accuracy_decimals: 0
+    state_class: measurement
+    icon: mdi:water-opacity
+
+  - platform: template
+    name: "BLE-YC01 Temperature"
+    id: ble_yc01_temperature_sensor
+    unit_of_measurement: "°C"
+    accuracy_decimals: 2
+    state_class: measurement
+    device_class: temperature
+    on_value:
+      then:
+        lambda: |-
+           id(mqtt_client).publish_json("domoticz/in", [=](JsonObject root) {
+                                                           root["command"] = "udevice";
+                                                           root["idx"] = 510;
+                                                           root["svalue"] = std::to_string(x);
+                                        });
+
+  - platform: template
+    name: "BLE-YC01 ORP"
+    id: ble_yc01_orp_sensor
+    unit_of_measurement: "mV"
+    accuracy_decimals: 0
+    state_class: measurement
+    device_class: voltage
+
+  - platform: template
+    name: "BLE-YC01 pH"
+    id: ble_yc01_ph_sensor
+    unit_of_measurement: "pH"
+    accuracy_decimals: 2
+    state_class: measurement
+    icon: mdi:ph
+    on_value:
+      then:
+        lambda: |-
+           id(mqtt_client).publish_json("domoticz/in", [=](JsonObject root) {
+                                                           root["command"] = "udevice";
+                                                           root["idx"] = 511;
+                                                           root["svalue"] = std::to_string(x);
+                                        });
+
+  - platform: template
+    name: "BLE-YC01 battery"
+    id: ble_yc01_battery
+    unit_of_measurement: "%"
+    accuracy_decimals: 0
+    state_class: measurement
+    device_class: battery
+    icon: mdi:battery
+
+  - platform: template
+    name: "BLE-YC01 CL"
+    id: ble_yc01_cloro
+    unit_of_measurement: "ppm"
+    accuracy_decimals: 1
+    state_class: measurement
+    icon: mdi:water-opacity
+    on_value:
+      then:
+        lambda: |-
+           id(mqtt_client).publish_json("domoticz/in", [=](JsonObject root) {
+                                                           root["command"] = "udevice";
+                                                           root["idx"] = 509;
+                                                           root["svalue"] = std::to_string(x);
+                                        });
+
+  - platform: ble_client
+    type: rssi
+    id: ble_yc01_rssi
+    ble_client_id: ble_yc01
+    update_interval: never
+    name: "BLE-YC01 RSSI"
+
+  - platform: ble_client ####  Sensor required to manage values coming from the BLE device ####
+    ble_client_id: ble_yc01
+    type: characteristic
+    id: ble_yc01_sensor
+    update_interval: never
+    internal: true
+    service_uuid: FF01
+    characteristic_uuid: FF02
+    #### Lambda to decode values and push to the associated sensors ####
+    lambda: |-
+
+      if (x.size() == 0) return NAN;
+
+      std::string rawmsg;
+      for (int i = 0; i < x.size(); i++) {
+         char buf[7];
+         snprintf(buf, 7, " 0x%02x", (unsigned char) x[i]);
+         rawmsg = rawmsg + buf;
+      }
+      ESP_LOGD("ble_client.receive", "raw value received with %d bytes: [%s]", x.size(), rawmsg.c_str()); // ####  Useful for debugging ####
+
+      // ### DECODING ###
+      uint8_t tmp = 0;
+      uint8_t hibit = 0;
+      uint8_t lobit = 0;
+      uint8_t hibit1 = 0;
+      uint8_t lobit1 = 0;
+      auto message = x;
+
+      for (int i = x.size() -1 ; i > 0; i--) {
+        tmp=message[i];
+        hibit1=(tmp&0x55)<<1;
+        lobit1=(tmp&0xAA)>>1;
+        tmp=message[i-1];
+        hibit=(tmp&0x55)<<1;
+        lobit=(tmp&0xAA)>>1;
+
+        message[i]=~(hibit1|lobit);
+        message[i-1]=~(hibit|lobit1);
+
+      }
+
+      rawmsg = "";
+      for (int i = 0; i < message.size(); i++) {
+         char buf[7];
+         snprintf(buf, 7, " 0x%02x", (unsigned char) message[i]);
+         rawmsg = rawmsg + buf;
+      }
+      ESP_LOGD("ble_client.receive", "value received with %d bytes: [%s]", message.size(), rawmsg.c_str()); // #### For debug ####
+
+      // #### Extraction of individual values ####
+      auto temp = ((message[13]<<8) + message[14]);
+      auto ph = ((message[3]<<8) + message[4]);
+      auto orp = ((message[20]<<8) + message[21]);
+      auto battery = ((message[15]<<8) + message[16]);
+      auto ec = ((message[5]<<8) + message[6]);
+      auto tds = ((message[7]<<8) + message[8]);
+      auto cloro = ((message[11]<<8) + message[12]);
+
+      // #### Sensors updated with new values
+      id(ble_yc01_temperature_sensor).publish_state(temp/10.0);
+      id(ble_yc01_ph_sensor).publish_state(ph/100.0);
+      id(ble_yc01_orp_sensor).publish_state(orp);
+      id(ble_yc01_battery).publish_state(battery/31.9);
+      id(ble_yc01_ec_sensor).publish_state(ec);
+      id(ble_yc01_tds_sensor).publish_state(tds);
+      if (cloro == 65535)
+          id(ble_yc01_cloro).publish_state(NAN);
+      else
+          id(ble_yc01_cloro).publish_state(cloro/10.0);
+
+      // Once we have a single reading, turn off until the next attempt
+      id(ble_switch).turn_off();
+      return NAN; // this sensor isn't actually used other than to hook into raw value and publish to template sensors
+
+
+switch:  #### To switch on and off the communication with the BLE device ####
+  - platform: ble_client
+    id: ble_switch
+    ble_client_id: ble_yc01
+    name: "Enable BLE-YC01"
+    restore_mode: ALWAYS_ON
+    on_turn_on:
+        - esp32_ble_tracker.start_scan:
+            continuous: true
+    on_turn_off:
+        - esp32_ble_tracker.stop_scan:
+
+
+  - platform: gpio
+    id: valve_output
+    name: "Valve switch"
+    pin: GPIO14
+    restore_mode: ALWAYS_ON
+    on_turn_on:
+      then:
+        - light.turn_on: green_led
+        - mqtt.publish_json:
+            topic: domoticz/in
+            payload: |-
+              root["command"] = "udevice";
+              root["idx"] = 512;
+              root["nvalue"] = 1;
+    on_turn_off:
+      then:
+        - light.turn_off: green_led
+        - mqtt.publish_json:
+            topic: domoticz/in
+            payload: |-
+              root["command"] = "udevice";
+              root["idx"] = 512;
+              root["nvalue"] = 0;
+
+  - platform: gpio
+    id: power_1w
+    name: "Power 1Wire"
+    pin:
+      number: GPIO16
+      inverted: true
+    restore_mode: ALWAYS_ON
+    on_turn_on:
+      - lambda: |-
+          id(solar_in_fails) = 0;
+          id(solar_out_fails) = 0;