July 24, 2021

Умягчитель воды в доме: считаем остаток соли в баке | ESP8266

В загородных домах часто используются умягчители воды, внутри которых находится фильтрующий материал (в моём случае — это ионообменная
смола), и который необходимо периодически промывать раствором
поваренной соли.

Основной процедурой обслуживания умягчителя является — пополнение солевого бака. Сейчас я расскажу о том, как я сделал отображение в Home Assistant примерного остатка соли и настроил уведомление о необходимости пополнения бака.

Мой умягчитель настроен на промывку смолы через каждые 5 кубов потреблённой воды. И 50-ти кг соли, которые я засыпаю в бак, хватает примерно на 5-6 циклов промывки. Поэтому, первым делом, я думал подключиться к контроллеру для подсчета этих циклов, но в нём не нашлось никакой штатной возможности для этого.

Дальше я захотел применить ультразвуковой датчик для измерения расстояния. Но опыт использования во влажной среде показал, что работают они не дольше
нескольких месяцев, после чего выходят из строя из-за коррозии. А бак с солью – это очень даже влажная среда.

Поэтому я остановился на варианте с косвенным подсчетом уровня соли через мониторинг потребления воды. Благо, у счетчика оказался в наличии импульсных выход, и можно вести подсчёт с точностью до 1 литра. Опытным путём я выяснил, что соль нужно досыпать в бак примерно через каждые 30 кубов потреблённой воды.

Идея следующая: когда я открываю крышку для пополнения бака, показания счетчика воды фиксируются, и начинается отсчёт 30-ти кубов, по достижении
которых я получаю уведомление. При следующем открывании крышки цикл повторяется вновь.

Для реализации задуманного я задействовал устройство на базе ESP8266, которое спаял несколько лет назад для подключения двух счётчиков
воды. Ранее на нём была прошивка NodeMCU с написанным мною кодом. Теперь я загрузил в него прошивку ESPHome, которая изначально была создана для тесного взаимодействия с Home Assistant через API.

Схема подключения

Один из проводов счетчика и датчика открытия вместе подключаются к пину Ground. Каждый оставшийся провод подключается к свободному цифровому пину платы, кроме GPIO0 и 2, чтобы не возникла проблема с загрузкой. Также добавляется подтягивающий резистор по схеме. Для дополнительной защиты от дребезга контактов можно добавить конденсатор.

Вообще для упрощения подключения питания и прошивки модуля я бы рекомендовал использовать готовую плату вроде Wemos D1 mini.

Прошивка

ESPHome — это система для настройки модулей ESP8266 и ESP32 с помощью простых и мощных конфигурационных файлов, и удаленного управления ими с помощью систем домашней автоматизации.

Код конфигурации модуля ESP представлен ниже:

esphome:
  name: esp-watercounter
  platform: ESP8266
  board: d1_mini
  on_boot:
    - logger.log: "Wait for MQTT connected"
    - wait_until:
        mqtt.connected:
    - delay: 3s
    - if:
        condition:
          mqtt.connected:
        then:
        - globals.set:
            id: water_count_var
            value: !lambda |-
              return id(water_counter).state;

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  fast_connect: true
  manual_ip:
    static_ip: 172.16.1.120
    subnet: 255.255.255.0
    gateway: 172.16.1.1
    dns1: 172.16.1.1

logger:
api:
ota:

mqtt:
  broker: !secret mqtt_host
  username: !secret mqtt_user
  password: !secret mqtt_pass
  discovery: false

globals:
  - id: water_count_var
    type: int
    restore_value: no

time:
  - platform: sntp
    on_time:
      - seconds: 0
        minutes: /5
        then:
          - if:
              condition:
                lambda: |-
                  return id(water_count_var) > 0;
              then:
              - mqtt.publish:
                  topic: /pantry/watercounter
                  retain: true
                  payload: !lambda |-
                    return to_string(id(water_count_var));

binary_sensor:
  - platform: gpio
    name: "Pantry Tank cap"
    device_class: window
    pin:
      number: 5
      mode: INPUT_PULLUP
    filters:
      - delayed_on: 150ms
      - delayed_off: 150ms

  - platform: gpio
    name: "Pantry Water usage"
    id: water_usage
    internal: true
    pin:
      number: 4
      mode: INPUT_PULLUP
    filters:
      - delayed_on: 100ms
      - delayed_off: 100ms
    on_press:
      then:
        - if:
            condition:
              lambda: |-
                return id(water_count_var) > 0;
            then:
              - lambda: 'id(water_count_var) += 1;'

sensor:
  - platform: mqtt_subscribe
    name: "Pantry Water count (L)"
    icon: "mdi:water-pump"
    id: water_counter
    accuracy_decimals: 0
    unit_of_measurement: 'L'
    topic: /pantry/watercounter

В секции wifi указываем статический IP-адрес, который будет назначен устройству после загрузки в него прошивки, либо позволяем DHCP-серверу назначить IP-адрес автоматом. Главное, не забыть затем зарезервировать его за конкретным устройством, во избежание возможной потери связи с Home Assistant.

Имя точки доступа и пароль к ней записываю в виде секретов в отдельном окне редактора.

Далее следуют параметры подключения к MQTT-брокеру. И основная часть — описание подключенных к модулю датчиков.

Геркон на крышке бака подключен к пятому цифровому пину, device_class: window будет передавать статус Открыто/Закрыто, вместо on/off.

Счётчик воды подключен к 4-му пину, у него также есть защита от дребезга,
а сам компонент помечен как внутренний, т. к. его отображение в Home Assistant не требуется, и его использование не выходит за пределы модуля ESP.

Суть конфигурации сводится к следующему:

  • создаётся глобальная переменная, в которую записываются текущие показания счетчика, хранимые в mqtt-брокере.
  • для счетчика прописана автоматизация, которая при замыкании контактов
    после каждого потреблённого литра воды, увеличивает значение глобальной переменной на этот самый 1 литр, а затем публикует обновлённое значение в mqtt-топике.
  • дополнительно создан сенсор, задача которого — отображать полученное из mqtt-топика значение показаний счетчика, т. к. этот сенсор уже будет виден в Home Assistant.

После сборки и загрузки прошивки в модуль, его можно добавить в Home Assistant через меню интеграций:

Смотрим, что появились два новых устройства. И можно написать первый
небольшой сценарий, в котором будут фиксироваться текущие показания счётчика воды для дальнейших расчётов:

Код всего Node-RED-flow доступен на GitHub.

Проверяем, что крышка открыта, сбрасываем флаг, и считываем текущее значение показаний счетчика, которое публикуем в отдельный MQTT-топик. Сам счётчик добавлен как сенсор в Home Assistant:

- platform: template
  sensors:
    pantry_water_count:
      friendly_name: "Pantry Water count"
      unit_of_measurement: 'm³'
      icon_template: "mdi:water-pump"
      value_template: >-
          {% if states('sensor.pantry_water_count_l') in ['unavailable', 'unknown', 'none'] %}
            unavailable
          {% else %}
            {{ states('sensor.pantry_water_count_l')|float / 1000 }}
          {% endif %}

Для отображения зафиксированных показаний счётчика, при которых была открыта крышка бака, я создал другой сенсор:

- platform: mqtt
  name: Pantry Tank last refill
  icon: mdi:basket-fill
  state_topic: "/pantry/watercounter/salt_refill"
  unit_of_measurement: "m³"

Непосредственно для расчёта остатка соли создал еще один сенсор на основе шаблона:

- platform: template
  sensors:
    pantry_tank_salt_left:
      friendly_name: "Pantry Tank salt left"
      unit_of_measurement: "%"
      value_template: >-
        {% if 0 < ((30 - (states('sensor.pantry_water_count')|float - states('sensor.pantry_tank_last_refill')|float))/30*100) | round(0) <= 100 %}
          {{ ((30 - (states('sensor.pantry_water_count')|float - states('sensor.pantry_tank_last_refill')|float))/30*100) | round(0) }}
        {% elif ((30 - (states('sensor.pantry_water_count')|float - states('sensor.pantry_tank_last_refill')|float))/30*100) | round(0) <= 0 %}
          0
        {% endif %}
      icon_template: >-
        {% if 60 < (((30 - (states('sensor.pantry_water_count')|float - states('sensor.pantry_tank_last_refill')|float))/30*100) | round(0)) < 100 %}
          mdi:delete
        {% elif 30 < (((30 - (states('sensor.pantry_water_count')|float - states('sensor.pantry_tank_last_refill')|float))/30*100) | round(0)) < 60 %}
          mdi:delete-outline
        {% elif 10 < (((30 - (states('sensor.pantry_water_count')|float - states('sensor.pantry_tank_last_refill')|float))/30*100) | round(0)) < 30 %}
          mdi:delete-alert-outline
        {% elif (((30 - (states('sensor.pantry_water_count')|float - states('sensor.pantry_tank_last_refill')|float))/30*100) | round(0)) < 10 %}
          mdi:delete-forever-outline
        {% endif %}

В строке value_template происходит следующее:

30 — полученный опытным путём примерный объем потреблённой воды, на который хватает одной засыпки соли. Сначала из этого объема вычитается разница между текущим значением показаний счётчика воды (sensor.pantry_water_count) и фиксированным значением (sensor.pantry_tank_last_refill), при котором засыпалась новая порция соли, а затем полученное округлённое значение преобразуется в проценты.

Т.к. в MQTT-топике значения хранятся в виде строк, то в этих местах они преобразуются в числа с плавающей точкой (float).

В icon_template описаны условия для отображения разных иконок в зависимости от текущего уровня соли.

Сценарий для отправки уведомления делает следующее:

Каждые 12 часов проверяется текущее значение уровня соли, которое записывается в отдельную переменную (msg.salt_level), проверяется состояние
флага, который предотвращает повторную отправку уведомлений при следующей проверке, а сам является обычным виртуальным переключателем input_boolean:

pantry_water_tank_refill_flag:
  name: Pantry Tank refill flag
  icon: mdi:delete-alert-outline

Следом в Function- ноде происходит проверка условий, что флаг (input_boolean) ещё не был включен, а остаток соли менее 10%, и далее включается флаг и формируется уведомление в Telegram, в текст которого подставляется переменная со значением остатка:

Вот так всё это выглядит в Home Assistant:

Эту систему я тестировал и дорабатывал в течение нескольких месяцев, и сейчас она прекрасно справляется со своей задачей.


Все конфигурации, описанные в статье, доступны на GitHub.