Статья

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

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

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

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

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

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

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

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

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

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

Прошивка

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

Для её установки необходимо перейти в раздел дополнений Home Assistant и добавить новый репозиторий, после чего установить дополнение ESPHome.

Затем, перейдя в панель управления ESPHome, добавляем новое устройство, где заполняется имя, тип и параметры подключения к Wi-Fi-сети.

Обновив страницу, можно приступать к созданию конфигурации, нажав на кнопку редактирования вновь созданного устройства.

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 # Enable logging logger: # Enable Home Assistant API 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: # Every 5 minutes - 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;' # Moved in "time" section to send accumulated values every 5 minutes # - mqtt.publish: # topic: /pantry/watercounter # retain: true # payload: !lambda |- # return to_string(id(water_count_var)); 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

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

Создаётся глобальная переменная (globals), в которую записываются текущие показания счётчика воды, хранимые в MQTT-брокере.Для счётчика прописана автоматизация (on_press), которая при замыкании контактов, после каждого потреблённого литра воды, увеличивает значение глобальной переменной на этот самый 1 литр (- lambda: 'id(water_count_var) = 1;').Каждые 5 минут текущее значение счётчика публикуется в MQTT-топике.Дополнительно создан сенсор, задача которого - отображать полученное из mqtt-топика значение показаний счетчика, т.к. этот сенсор уже будет виден в Home Assistant. Также используется для заполнения пустой глобальной переменной после перезагрузки модуля. Для этого в начале конфига есть автоматизация, запускаемая при загрузке модуля, которая ожидает успешного подключения к MQTT-брокеру, а затем записывает в переменную данные сенсора, обращаясь к нему по id, указанному в параметрах.

После сборки и скачивания файла прошивки, загружаем её в модуль с помощью программы ESPHome-Flasher.

Перезагрузив модуль, его статус в панели управления ESPHome должен измениться на Online. Дальнейшие обновления прошивки будут производиться по воздуху, нажатием кнопки Upload, после внесения каких-либо изменений в конфигурацию.

Home Assistant

Модуль ESP добавляется в Home Assistant через меню интеграций, где затем появляются два новых объекта.

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

- 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 описаны условия для отображения разных иконок, в зависимости от текущего уровня соли.

Сценарий Node-RED

Здесь будут фиксироваться текущие показания счётчика воды для дальнейших расчётов.

Проверяем, что крышка солевого бака открыта, сбрасываем флаг (input_boolean), и считываем текущее значение счётчика воды, которое публикуем в отдельный MQTT-топик. Также здесь необходимо настроить подключение к своему MQTT-брокеру.

Ниже flow для отправки уведомления, где каждые 12 часов проверяется текущее значение уровня соли, которое записывается в отдельную переменную (msg.salt_level), проверяется состояние флага, который предотвращает повторную отправку уведомлений при следующей проверке. Следом в Function-ноде происходит проверка условий, что флаг ещё не был включен, а остаток соли менее 10%, и далее включается флаг и формируется уведомление в Telegram, в текст которого подставляется переменная со значением остатка.

Flow в виде кода:

[ { "id": "3e539f99.8cbf", "type": "server-state-changed", "z": "34ac6e87.8cbf22", "name": "Крышка солевого бака", "server": "b9490f48.586ed", "version": 1, "exposeToHomeAssistant": false, "haConfig": [ { "property": "name", "value": "" }, { "property": "icon", "value": "" } ], "entityidfilter": "binary_sensor.pantry_tank_cap", "entityidfiltertype": "exact", "outputinitially": false, "state_type": "str", "haltifstate": "on", "halt_if_type": "str", "halt_if_compare": "is", "outputs": 2, "output_only_on_state_change": true, "x": 1360, "y": 100, "wires": [ [ "f194f2c7.316b9", "e7ba4af.fe316b8" ], [] ], "outputLabels": [ "Открыт", "Закрыт" ] }, { "id": "f194f2c7.316b9", "type": "api-current-state", "z": "34ac6e87.8cbf22", "name": "Показания ХВС", "server": "b9490f48.586ed", "version": 1, "outputs": 1, "halt_if": "", "halt_if_type": "str", "halt_if_compare": "is", "override_topic": false, "entity_id": "sensor.pantry_water_count", "state_type": "str", "state_location": "payload", "override_payload": "msg", "entity_location": "data", "override_data": "msg", "blockInputOverrides": false, "x": 1600, "y": 120, "wires": [ [ "13a06820.c56ad8" ] ] }, { "id": "13a06820.c56ad8", "type": "mqtt out", "z": "34ac6e87.8cbf22", "name": "Salt refilled", "topic": "/pantry/watercounter/salt_refill", "qos": "0", "retain": "true", "broker": "338c62b5.037d7e", "x": 1770, "y": 120, "wires": [] }, { "id": "e7ba4af.fe316b8", "type": "api-call-service", "z": "34ac6e87.8cbf22", "name": "Flag Off", "server": "b9490f48.586ed", "version": 1, "debugenabled": false, "service_domain": "input_boolean", "service": "turn_off", "entityId": "input_boolean.pantry_water_tank_refill_flag", "data": "", "dataType": "json", "mergecontext": "", "output_location": "payload", "output_location_type": "msg", "mustacheAltTags": false, "x": 1580, "y": 80, "wires": [ [] ] }, { "id": "473e8189.fd0b5", "type": "comment", "z": "34ac6e87.8cbf22", "name": "Запись показаний счетчика, при которых засыпалась соль", "info": "", "x": 2100, "y": 120, "wires": [] }, { "id": "3fb7bf49.22da4", "type": "interval", "z": "34ac6e87.8cbf22", "name": "interval", "interval": "12", "onstart": false, "msg": "ping", "showstatus": false, "unit": "hours", "statusformat": "YYYY-MM-D HH:mm:ss", "x": 1310, "y": 300, "wires": [ [ "f9d1c91c.f7a5c8" ] ] }, { "id": "f9d1c91c.f7a5c8", "type": "api-current-state", "z": "34ac6e87.8cbf22", "name": "Уровень соли в баке", "server": "b9490f48.586ed", "version": 1, "outputs": 1, "halt_if": "", "halt_if_type": "str", "halt_if_compare": "is", "override_topic": false, "entity_id": "sensor.pantry_tank_salt_left", "state_type": "num", "state_location": "salt_level", "override_payload": "msg", "entity_location": "data", "override_data": "msg", "blockInputOverrides": false, "x": 1560, "y": 300, "wires": [ [ "a0b2c8ce.090858" ] ] }, { "id": "a0b2c8ce.090858", "type": "api-current-state", "z": "34ac6e87.8cbf22", "name": "Флаг поднят?", "server": "b9490f48.586ed", "version": 1, "outputs": 1, "halt_if": "", "halt_if_type": "str", "halt_if_compare": "is", "override_topic": false, "entity_id": "input_boolean.pantry_water_tank_refill_flag", "state_type": "str", "state_location": "refill_flag", "override_payload": "msg", "entity_location": "data", "override_data": "msg", "blockInputOverrides": false, "x": 1760, "y": 300, "wires": [ [ "42a2c038.41383" ] ] }, { "id": "42a2c038.41383", "type": "function", "z": "34ac6e87.8cbf22", "name": "calc", "func": "if ((msg.salt_level < 10) && (msg.refill_flag == \"off\")) {\n return msg;\n}\n", "outputs": 1, "noerr": 0, "x": 1910, "y": 300, "wires": [ [ "3261a9a1.b13ed6" ] ] }, { "id": "3261a9a1.b13ed6", "type": "api-call-service", "z": "34ac6e87.8cbf22", "name": "Flag On", "server": "b9490f48.586ed", "version": 1, "debugenabled": false, "service_domain": "input_boolean", "service": "turn_on", "entityId": "input_boolean.pantry_water_tank_refill_flag", "data": "", "dataType": "json", "mergecontext": "", "output_location": "payload", "output_location_type": "msg", "mustacheAltTags": false, "x": 2040, "y": 300, "wires": [ [ "c366e767.880648" ] ] }, { "id": "c366e767.880648", "type": "function", "z": "34ac6e87.8cbf22", "name": "data", "func": "msg.method = \"sendMessage\";\nmsg.payload = {\n text: \"Уровень соли в баке \" msg.salt_level \"%.\\nПора досыпать 50 кг.\"\n}\nreturn msg;", "outputs": 1, "noerr": 0, "x": 2170, "y": 300, "wires": [ [ "4fec517e.86e1c" ] ] }, { "id": "4fec517e.86e1c", "type": "telegrambot-payload", "z": "34ac6e87.8cbf22", "name": "Notify", "bot": "b4eca378.4bbba", "chatId": "12345678", "sendMethod": "", "payload": "", "x": 2310, "y": 300, "wires": [ [] ] }, { "id": "f49978f0.3277f8", "type": "comment", "z": "34ac6e87.8cbf22", "name": "Уведомление о низком уровне соли в баке", "info": "", "x": 2570, "y": 300, "wires": [] }, { "id": "b9490f48.586ed", "type": "server", "z": "", "name": "Home Assistant", "legacy": false, "hassio": true, "rejectUnauthorizedCerts": true }, { "id": "338c62b5.037d7e", "type": "mqtt-broker", "z": "", "name": "MQTT", "broker": "172.16.1.33", "port": "8883", "tls": "", "clientid": "nodered", "usetls": true, "compatmode": true, "keepalive": "60", "cleansession": true, "birthTopic": "", "birthQos": "0", "birthPayload": "", "closeTopic": "", "closeQos": "0", "closePayload": "", "willTopic": "", "willQos": "0", "willPayload": "" }, { "id": "b4eca378.4bbba", "type": "telegrambot-config", "z": "", "botname": "Telegram Bot", "usernames": "", "chatIds": "12345678", "pollInterval": "300" } ]


Ох какая нужная вещь! А автоматика фильтров не конфликтует с есп? Ведь получается есп подключается на тот же канал, что автоматика фильтров.

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

Понятно. У меня просто расходомер задействован в автоматике фильтров. В моем случае наверное надо будет добавить оптопары.

Можно взвешивать бак, будет точнее чем косвенные измерения, так как не всегда количество соли используется одинаковое, иногда расход прыгает почти вдвое, обычно после полного вымывания соли из бака. И всего этого огорода с контролем открытия крышки можно не городить

У меня количество соли всегда одинаковое. За полгода осечек в расчетах или отклонений по расходу не было.
Контроль веса - годный вариант

Интересно, а как Вы бак будете взвешивать бак?


Я вот применил более простой способ - поставил снаружи на стенку бака с солью датчик (https://aliexpress.ru/item/4001186999735.html?spm=a2g0o.productlist.0.0.63a215fbxueaU1&algo_pvid=4b251ec6-6af1-42fb-beaf-732b3f7515fb&algo_expid=4b251ec6-6af1-42fb-beaf-732b3f7515fb-3&btsid=0b8b15c416004313192258383e6968&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_">https://aliexpress.ru/item/400...) и подключил его к ESP8266 через ESPHome в НА, как у автора статьи. Т.е. если уровень опустился ниже уровня расположения датчика, в ТГ прилетает сообщение - пора засыпать соль. Датчик прекрасно "пробивает" пластик. Работает все надежно. 

Взвешивание происходит с помощью hx711 подключенный к esp8266 (обычный сонофф басик) и прошивки esphome. Если подробнее - взял пвх уголок в леруа, согнул из него квадратный каркас по размеру керамической напольной плитки оставшейся от ремонта гаража, вложил в него плитку, все основательно засоплил термоклеем чтоб не болталось, по 4 углам плитки установил 4 тензодатчика подключенных к hx711, вот такие https://m.aliexpress.ru/item/4000215823944.html">https://m.aliexpress.ru/item/4... и накрыл еще одной такой же плиткой, получилась крепкая аккуратная и компактная конструкция из которой выходит 1 провод с миниджеком на конце, миниджек втыкается в сонофф васик, ну дальше понятно, прошивка, мктт, уд

пвх уголок вокруг плитки? какая его роль?декоративная?

т.е. по сути снизу плитка, потом датчики по углам и сверху плитка, а уже на неё сверху контейнер?

если запостите фото, будет понятнее)))



https://sprut.ai/static/media/cache/00/13/26/5/7278152/74598/1000x_image.jpeg?1612945028" alt="1000x_image.jpeg?1612945028" />
https://sprut.ai/static/media/cache/00/13/26/5/7278152/74599/1000x_image.jpeg?1612945029" alt="1000x_image.jpeg?1612945029" />

Да, плитка - плитка и между ними датчики. А уголок выступает в роли каркаса конструкции. Это не плиточный уголок, это толстый пвх уголок с толщиной стенки 3-4мм и длиной сторон по 4-5см. Он согнут квадратом по размеру плитки, в него как в нишу вложена нижняя плитка, она опирается на нижнюю полочку уголка и приклеена к ней термоклеем, на нее закреплены тензодатчики и сверху так же вложена верхняя плитка. Таким образом уголок делает конструкцию достаточно цельной и монолитной. Изнутри показать не могу, вес бака под 50кг, подымать его ну совсем не вариант :)


https://sprut.ai/static/media/cache/00/13/26/5/7278349/74600/1000x_image.jpg?1612945576" alt="1000x_image.jpg?1612945576" />

Супер! Спасибо за ответ. Были сомнения как делать, с вашей подсказкой сомнений нет), буду делать методом взвешивания.



Ну у меня расход соли не одинаковый. На этом графике не видно, так как показывает только последние 4 промывки, и тепеь в качестве меры экономии я не допускаю уменьшения количества соли в баке более чем на половину. Но если бак вымывается полностью то следующая после засыпки промывка вымывает соли вдвое больше обычного, около 9кг вместо обычных 4кг. Поэтому в моем случае высчитывать количество промывок - не вариант.


но то что бесконтактный датчик работает это интересно, я думал он на воду реагирует, а вода в баке же есть всегда, не зависимо от наличия или отсутствия соли?


спасибо, что поделились методом взвешивания.

Что касается уровня - у меня вода подается в бак с солью в момент промывки/регенерации (у меня стоит система умягчения воды EcoWater).


Вернуться назад

Устройства


Espressif Systems

ESP8266 NodeMcu v3

(16 отзывов)

Espressif Systems

wemos D1 mini

(6 отзывов)

Espressif Systems

ESP8266

(1 отзыв)

Вернуться назад