--- /dev/null
+#
+# 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;