Last active
December 7, 2024 09:36
-
-
Save aver-ua/7f6b01ab3c5fdbb07d808d44234191c7 to your computer and use it in GitHub Desktop.
ESPHome config for Ecosoft Water Softener (Clack WS1) + HC-SR04 Ultrasonic Distance Sensor for salt level measuring
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| esphome: | |
| name: ecosoft-softener | |
| comment: "${device_description}" | |
| friendly_name: Ecosoft Water Softener | |
| on_boot: | |
| priority: 200 | |
| then: | |
| - script.execute: on_boot | |
| substitutions: | |
| device_description: "Ecosoft Clack WS1 meter + ultrasonic saltlevel sensor" | |
| device_name: "ecosoft-softener" | |
| watermeter_pin: "17" # watermeter relay pin | |
| regeneration_pin: "18" # regeneration relay pin | |
| us_trigger_pin: "25" # ultrasonic sensor trigger pin | |
| us_echo_pin: "26" # ultrasonic sensor echo pin | |
| tank_height: "50" # salt tank height in cm | |
| sensor_height: "12" # sensor distance in cm | |
| timezone: "Europe/Kiev" # timezone | |
| watermeter_update_interval: 600s | |
| saltlevel_update_interval: 1h | |
| time: | |
| - platform: sntp | |
| id: clack_sntp_time | |
| timezone: ${timezone} | |
| esp32: | |
| board: esp32dev | |
| framework: | |
| type: arduino | |
| # Enable logging | |
| logger: | |
| ota: | |
| platform: esphome | |
| password: !secret ota_password | |
| wifi: | |
| ssid: !secret wifi_ssid | |
| password: !secret wifi_password | |
| # Enable fallback hotspot (captive portal) in case wifi connection fails | |
| ap: | |
| ssid: "Ecosoft-Water-Softener" | |
| password: !secret ap_password | |
| captive_portal: | |
| # Enable Home Assistant API | |
| # meterstand_clack: service to set global var totalWaterUsage | |
| # meterstand_clack_reg_date: service to set global var regeneration_last | |
| api: | |
| encryption: | |
| key: !secret api_encryption_key | |
| services: | |
| - service: meterstand_clack | |
| variables: | |
| meter_value: float | |
| then: | |
| - globals.set: | |
| id: totalWaterUsage | |
| value: !lambda "return ( meter_value ) ;" | |
| - sensor.template.publish: | |
| id: clack_watermeter | |
| state: !lambda |- | |
| return id(totalWaterUsage); | |
| - sensor.template.publish: | |
| id: clack_m3_left | |
| state: !lambda |- | |
| return float(id(clack_capacity_liters).state - id(clack_watermeter).state) * 0.001; | |
| - sensor.template.publish: | |
| id: clack_l_left | |
| state: !lambda |- | |
| return int(id(clack_capacity_liters).state - id(clack_watermeter).state); | |
| - service: meterstand_clack_reg_date | |
| variables: | |
| meter_value: string | |
| then: | |
| - globals.set: | |
| id: regeneration_last | |
| value: !lambda "return meter_value ;" | |
| - service: meterstand_clack_mnt_date | |
| variables: | |
| meter_value: string | |
| then: | |
| - globals.set: | |
| id: maintenance_last | |
| value: !lambda "return meter_value ;" | |
| # Define a global variable for total water usage | |
| globals: | |
| - id: totalWaterUsage | |
| type: float | |
| restore_value: yes | |
| initial_value: '00' | |
| # Define a global variable for last regeneration time | |
| - id: regeneration_last | |
| type: std::string | |
| restore_value: yes | |
| initial_value: '"Never"' | |
| # Define a global variable for last maintenance time | |
| - id: maintenance_last | |
| type: std::string | |
| restore_value: yes | |
| initial_value: '"Never"' | |
| script: | |
| ### On reboot or powerloss of the esp, set the time left, last regeneration time, and capacity counters | |
| - id: on_boot | |
| then: | |
| - sensor.template.publish: | |
| id: clack_m3_left | |
| state: !lambda |- | |
| return float(id(clack_capacity_liters).state - id(clack_watermeter).state) * 0.001; | |
| - sensor.template.publish: | |
| id: clack_l_left | |
| state: !lambda |- | |
| return int(id(clack_capacity_liters).state - id(clack_watermeter).state); | |
| - text_sensor.template.publish: | |
| id: clack_regeneration_last | |
| state: !lambda |- | |
| return id(regeneration_last); | |
| - text_sensor.template.publish: | |
| id: clack_maintenance_last | |
| state: !lambda |- | |
| return id(maintenance_last); | |
| text_sensor: | |
| # Expose last regeneration information as sensor | |
| - platform: template | |
| name: Regenerated on | |
| id: clack_regeneration_last | |
| icon: mdi:clock-start | |
| update_interval: never | |
| lambda: |- | |
| return id(regeneration_last); | |
| # Expose last maintenance information as sensor | |
| - platform: template | |
| name: Maintenance Last | |
| id: clack_maintenance_last | |
| icon: mdi:wrench-clock | |
| update_interval: never | |
| lambda: |- | |
| return id(maintenance_last); | |
| # Expose WiFi information as sensors. | |
| - platform: wifi_info | |
| ip_address: | |
| name: IP | |
| icon: mdi:ip-network | |
| ssid: | |
| name: SSID | |
| icon: mdi:router-wireless | |
| # Uptime Human Readable | |
| - platform: template | |
| name: Uptime HR | |
| id: uptime_human | |
| icon: mdi:clock-start | |
| # Fill salt sensor based on salt_level_prc | |
| - platform: template | |
| id: salt_level_fill_salt | |
| icon: mdi:basket-fill | |
| name: "Fill Salt" | |
| update_interval: ${saltlevel_update_interval} | |
| lambda: |- | |
| if (id(salt_level_prc).state<id(clack_salt_warn_prc).state) { return {"Yes"}; } else { return {"No"}; } | |
| # Time to Regeneration | |
| - platform: template | |
| id: time_to_regeneration | |
| icon: mdi:calendar-clock-outline | |
| name: "Time 2 Regeneration" | |
| update_interval: 30min | |
| lambda: |- | |
| if (id(regeneration_last)=="Never") return { "" }; | |
| time_t currTime = id(clack_sntp_time).now().timestamp; | |
| struct tm * tm_currTime = localtime(&currTime); | |
| int year = tm_currTime->tm_year+1900; | |
| struct tm tm_reg_last; | |
| if (strlen(id(regeneration_last).c_str())>16) | |
| strptime(id(regeneration_last).c_str(),"%Y %a %d %b %H:%M",&tm_reg_last); | |
| else | |
| strptime((to_string(year)+" "+id(regeneration_last).substr(4,12)).c_str(),"%Y %d %b %H:%M",&tm_reg_last); | |
| if (id(clack_l_left).state<=0) { | |
| tm_reg_last.tm_mday=tm_currTime->tm_mday; | |
| tm_reg_last.tm_mon=tm_currTime->tm_mon; | |
| } else { tm_reg_last.tm_mday += id(clack_capacity_days).state; } | |
| tm_reg_last.tm_sec = 0; | |
| int time = mktime(&tm_reg_last) - currTime; | |
| if ( (id(clack_l_left).state<=0)&&(time<0) ) time += 86400; | |
| int days = time / 86400; | |
| int hours = time % 86400 / 3600; | |
| return { | |
| ((days>0) ? to_string(days) + "d " : "") + | |
| ((hours>0) ? to_string(hours) + "h " : "") | |
| }; | |
| # Time to Maintenance | |
| - platform: template | |
| id: time_to_maintenance | |
| icon: mdi:calendar-clock-outline | |
| name: "Time 2 Maintenance" | |
| update_interval: 12h | |
| lambda: |- | |
| if (id(maintenance_last)=="Never") return { "" }; | |
| time_t currTime = id(clack_sntp_time).now().timestamp; | |
| struct tm * tm_currTime = localtime(&currTime); | |
| struct tm tm_mnt_last; | |
| strptime(id(maintenance_last).c_str(),"%Y %a %d %b",&tm_mnt_last); | |
| tm_mnt_last.tm_mday += id(clack_maintenance_days).state; | |
| tm_mnt_last.tm_sec = 0; | |
| tm_mnt_last.tm_min = 0; | |
| tm_mnt_last.tm_hour = 0; | |
| int time = mktime(&tm_mnt_last) - currTime; | |
| int days = time / 86400; | |
| if (days==0) return { "Today" }; | |
| if (days>id(clack_maintenance_days).state) return { "?" }; | |
| return { to_string(days) + "d" }; | |
| # Exposed switches. | |
| # Switch to restart the salt_level_sensor. | |
| switch: | |
| - platform: restart | |
| name: Restart | |
| number: | |
| # Set capacity liters | |
| - platform: template | |
| id: clack_capacity_liters | |
| name: Capacity in liters | |
| icon: mdi:water-opacity | |
| optimistic: true | |
| mode: slider | |
| step: 50 | |
| entity_category: config | |
| min_value: 0 | |
| max_value: 7200 | |
| initial_value: 2800 | |
| restore_value: yes | |
| unit_of_measurement: L | |
| on_value: | |
| then: | |
| - sensor.template.publish: | |
| id: clack_watermeter | |
| state: !lambda |- | |
| return id(totalWaterUsage); | |
| - sensor.template.publish: | |
| id: clack_m3_left | |
| state: !lambda |- | |
| return float(id(clack_capacity_liters).state - id(clack_watermeter).state) * 0.001; | |
| - sensor.template.publish: | |
| id: clack_l_left | |
| state: !lambda |- | |
| return int(id(clack_capacity_liters).state - id(clack_watermeter).state); | |
| # Set capacity days | |
| - platform: template | |
| id: clack_capacity_days | |
| name: Capacity in days | |
| icon: mdi:calendar-clock | |
| optimistic: true | |
| mode: slider | |
| step: 1 | |
| entity_category: config | |
| min_value: 0 | |
| max_value: 21 | |
| initial_value: 10 | |
| restore_value: yes | |
| unit_of_measurement: days | |
| # Set pulse liters | |
| - platform: template | |
| id: clack_pulse_liters | |
| name: Water liters pulse | |
| icon: mdi:water-plus | |
| optimistic: true | |
| mode: slider | |
| step: 1 | |
| entity_category: config | |
| min_value: 0 | |
| max_value: 100 | |
| initial_value: 10 | |
| restore_value: yes | |
| unit_of_measurement: L | |
| # Set warning percent | |
| - platform: template | |
| id: clack_salt_warn_prc | |
| name: Salt warning percent | |
| icon: mdi:water-percent-alert | |
| optimistic: true | |
| mode: slider | |
| step: 5 | |
| entity_category: config | |
| min_value: 5 | |
| max_value: 100 | |
| initial_value: 20 | |
| restore_value: yes | |
| unit_of_measurement: "%" | |
| # Set Maintenance period | |
| - platform: template | |
| id: clack_maintenance_days | |
| name: Maintenance period | |
| icon: mdi:tools | |
| optimistic: true | |
| mode: slider | |
| step: 1 | |
| entity_category: config | |
| min_value: 0 | |
| max_value: 365 | |
| initial_value: 180 | |
| restore_value: yes | |
| unit_of_measurement: days | |
| sensor: | |
| # Uptime sensor. | |
| - platform: uptime | |
| name: Uptime | |
| entity_category: "diagnostic" | |
| id: esp_uptime | |
| update_interval: 60s | |
| on_raw_value: | |
| then: | |
| - text_sensor.template.publish: | |
| id: uptime_human | |
| state: !lambda |- | |
| int seconds = round(id(esp_uptime).raw_state); | |
| int days = seconds / (24 * 3600); | |
| seconds = seconds % (24 * 3600); | |
| int hours = seconds / 3600; | |
| seconds = seconds % 3600; | |
| int minutes = seconds / 60; | |
| seconds = seconds % 60; | |
| return ( | |
| (days ? String(days) + "d " : "") + | |
| (hours ? String(hours) + "h " : "") + | |
| (minutes ? String(minutes) + "m " : "") + | |
| (String(seconds) + "s") | |
| ).c_str(); | |
| # WiFi Signal sensor. | |
| - platform: wifi_signal | |
| name: "WiFi Signal dB" | |
| id: wifi_signal_db | |
| entity_category: "diagnostic" | |
| update_interval: 60s | |
| - platform: copy # Reports the WiFi signal strength in % | |
| source_id: wifi_signal_db | |
| name: "WiFi Signal Percent" | |
| filters: | |
| - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0); | |
| unit_of_measurement: "%" | |
| entity_category: "diagnostic" | |
| device_class: "" | |
| # Watermeter | |
| - platform: template | |
| id: clack_watermeter | |
| name: Water meter | |
| icon: mdi:water | |
| update_interval: ${watermeter_update_interval} | |
| unit_of_measurement: L | |
| device_class: water | |
| state_class: total_increasing | |
| accuracy_decimals: 1 | |
| lambda: |- | |
| return id(totalWaterUsage); | |
| - platform: template | |
| id: clack_m3_left | |
| name: Water softener m3 left | |
| unit_of_measurement: m³ | |
| device_class: water | |
| state_class: total | |
| accuracy_decimals: 2 | |
| update_interval: ${watermeter_update_interval} | |
| - platform: template | |
| id: clack_l_left | |
| name: Water softener ltr left | |
| state_class: total | |
| unit_of_measurement: L | |
| device_class: water | |
| accuracy_decimals: 0 | |
| update_interval: ${watermeter_update_interval} | |
| # Saltlevel sensor | |
| - platform: ultrasonic | |
| trigger_pin: | |
| number: ${us_trigger_pin} | |
| echo_pin: | |
| number: ${us_echo_pin} | |
| name: "Saltlevel cm" | |
| id: salt_level | |
| update_interval: ${saltlevel_update_interval} | |
| filters: | |
| # sensor works in meters | |
| - lambda: return ${tank_height}+${sensor_height}-x*100; | |
| unit_of_measurement: "cm" | |
| - platform: copy | |
| source_id: salt_level | |
| id: salt_level_prc | |
| name: "Saltlevel percent" | |
| unit_of_measurement: "%" | |
| filters: | |
| - lambda: return x/${tank_height}*100; | |
| ## watermeter pulse / relay 1 | |
| binary_sensor: | |
| - platform: gpio | |
| pin: | |
| number: ${watermeter_pin} | |
| inverted: true | |
| mode: | |
| input: true | |
| pullup: true | |
| id: clack_watermeter_pulse | |
| name: Watermeter pulse | |
| filters: | |
| - delayed_on_off: 50ms | |
| on_press: | |
| then: | |
| - sensor.template.publish: | |
| id: clack_watermeter | |
| state: !lambda |- | |
| return id(totalWaterUsage) += id(clack_pulse_liters).state; | |
| - sensor.template.publish: | |
| id: clack_m3_left | |
| state: !lambda |- | |
| return float(id(clack_capacity_liters).state - id(clack_watermeter).state) * 0.001; | |
| - sensor.template.publish: | |
| id: clack_l_left | |
| state: !lambda |- | |
| return int(id(clack_capacity_liters).state - id(clack_watermeter).state); | |
| ## regeneration pulse / relay 2 | |
| - platform: gpio | |
| pin: | |
| number: ${regeneration_pin} | |
| inverted: true | |
| mode: | |
| input: true | |
| pullup: true | |
| id: clack_regeneration_pulse | |
| name: Regeneration pulse | |
| filters: | |
| - delayed_on_off: 800ms | |
| on_press: | |
| then: | |
| - globals.set: | |
| id: totalWaterUsage | |
| value: '00' | |
| - text_sensor.template.publish: | |
| id: clack_regeneration_last | |
| state: !lambda |- | |
| char str[32]; | |
| time_t currTime = id(clack_sntp_time).now().timestamp; | |
| strftime(str, sizeof(str), "%Y %a %d %b %H:%M", localtime(&currTime)); | |
| id(regeneration_last) = str; | |
| return { str }; | |
| - sensor.template.publish: | |
| id: clack_watermeter | |
| state: !lambda |- | |
| return id(totalWaterUsage); | |
| - sensor.template.publish: | |
| id: clack_m3_left | |
| state: !lambda |- | |
| return float(id(clack_capacity_liters).state - id(clack_watermeter).state) * 0.001; | |
| - sensor.template.publish: | |
| id: clack_l_left | |
| state: !lambda |- | |
| return int(id(clack_capacity_liters).state - id(clack_watermeter).state); | |
| button: | |
| - platform: template | |
| name: "Maintenance Done" | |
| icon: mdi:wrench-check | |
| on_press: | |
| then: | |
| - text_sensor.template.publish: | |
| id: clack_maintenance_last | |
| state: !lambda |- | |
| char str[32]; | |
| time_t currTime = id(clack_sntp_time).now().timestamp; | |
| strftime(str, sizeof(str), "%Y %a %d %b", localtime(&currTime)); | |
| id(maintenance_last) = str; | |
| return { str }; | |
| # Enable Web server. | |
| web_server: | |
| port: 80 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment