6 Commits

Author SHA1 Message Date
565195d60a core - freese precedence over dizable, sprinkler - ON then DRY, HA docs
Co-authored-by: Copilot <copilot@github.com>
2026-05-25 12:14:43 +03:00
84ce56fdde Sprinkler reset blocking when disable 2026-05-24 23:44:12 +03:00
c1937a045b RESET command fix
Co-authored-by: Copilot <copilot@github.com>
2026-05-24 23:30:06 +03:00
fd23dc3567 Sprinkler improvement
Co-authored-by: Copilot <copilot@github.com>
2026-05-24 22:54:21 +03:00
898dca260c Sprinkler - outlet not blocking sprinkling + docs 2026-05-23 16:04:21 +03:00
985d058c50 Sprinkler module released
Co-authored-by: Copilot <copilot@github.com>
2026-05-22 22:44:36 +03:00
8 changed files with 1226 additions and 106 deletions

View File

@@ -0,0 +1,894 @@
# Исчерпывающее руководство по настройке системы полива в Home Assistant
Это руководство шаг за шагом проведёт вас через установку и настройку многоканальной системы полива на базе [lighthub](https://github.com/anklimov/lighthub), интегрированной с Home Assistant через MQTT. В результате вы получите красивую и функциональную панель управления, которая включает:
- визуализацию уровня воды в баке (пусто / наполовину / полный),
- индикацию работы водопровода, дренажного и основного насосов,
- управление тремя зонами полива с заданием времени и прогресс-баром,
- интерактивную корректировку выполненного времени через проценты,
- историю ключевых параметров по клику на иконки.
---
## 1. Предварительные требования
- Установленный и настроенный [Home Assistant](https://www.home-assistant.io/) (любая версия, поддерживающая Lovelace).
- Настроенный MQTT-брокер (например, Mosquitto) и интеграция MQTT в Home Assistant.
- [HACS](https://hacs.xyz/) (Home Assistant Community Store) для установки кастомных карт.
> **Примечание:** Конфигурация MQTTдатчиков и устройств уже готова и будет приведена ниже. Предполагается, что ваш контроллер lighthub уже отправляет данные в соответствующие топики.
---
## 2. Установка кастомных карт через HACS
Для корректной работы панели необходимо установить следующие карты. Все они доступны в HACS.
1. Откройте **HACS****Frontend****Explore & Download Repositories**.
2. Поочерёдно найдите и установите каждую из перечисленных ниже карт.
3. После установки **обновите страницу** браузера (или перезагрузите интерфейс Home Assistant).
### Список обязательных карт
| Название | Репозиторий (для ручного добавления, если не находится) | Назначение |
|------------------------------|----------------------------------------------------------|------------|
| **Vertical Stack In Card** | `ofekashery/vertical-stack-in-card` | Объединение нескольких карточек в одну с общим фоном |
| **Button Card** | `custom-cards/button-card` | Гибкая карточка с динамическими иконками, анимацией и состояниями |
| **Bar Card** | `spacerokk/bar-card` (активный форк) | Прогресс-бар для отображения выполнения полива |
| **Mushroom Cards** | `piitaya/lovelace-mushroom` | Компактные и стильные карточки для датчиков и переключателей |
| **Slider Entity Row** | `thomasloven/lovelace-slider-entity-row` | Ползунок для настройки времени в moreinfo окнах (не обязателен, но используется в стандартном окне `number`) |
| **Card Mod** | `thomasloven/lovelace-card-mod` | Позволяет добавлять произвольные CSSстили к карточкам |
> **Важно:** Если какая‑то карта не отображается в HACS, добавьте её вручную через **HACS → ⋮ → Пользовательские репозитории**. В поле URL вставьте ссылку на GitHubрепозиторий, категорию выберите **Lovelace**.
---
## 3. Настройка MQTTсущностей
Вся логика обмена данными с контроллером lighthub заложена в MQTTконфигурации. Скопируйте приведённый ниже блок в файл `configuration.yaml` (или в отдельный файл, подключённый через `mqtt: !include mqtt.yaml`).
```yaml
# Пример размещения в configuration.yaml
mqtt:
switch:
- name: "Полив"
state_topic: "edem/s_out/sprinkler/cmd"
command_topic: "edem/air/sprinkler/cmd"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив север"
state_topic: "edem/s_out/sprinkler/nord/cmd"
command_topic: "edem/air/sprinkler/nord/cmd"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив юг"
state_topic: "edem/s_out/sprinkler/south/cmd"
command_topic: "edem/air/sprinkler/south/cmd"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив капельный"
state_topic: "edem/s_out/sprinkler/trees/cmd"
command_topic: "edem/air/sprinkler/trees/cmd"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив розетки"
state_topic: "edem/s_out/sprinkler/outlets/cmd"
command_topic: "edem/air/sprinkler/outlets/cmd"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
button:
- name: "Полив сброс"
command_topic: "edem/air/sprinkler/cmd"
payload_press: "RESET"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив блокировка"
command_topic: "edem/air/sprinkler/cmd"
payload_press: "FREEZE"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив разблокировка"
command_topic: "edem/air/sprinkler/cmd"
payload_press: "UNFREEZE"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив разрешить"
command_topic: "edem/air/sprinkler/cmd"
payload_press: "ENABLE"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив запретить"
command_topic: "edem/air/sprinkler/cmd"
payload_press: "DISABLE"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив осушение бака"
command_topic: "edem/air/sprinkler/cmd"
payload_press: "DRY"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
number:
- name: "Полив юг"
state_topic: "edem/s_out/sprinkler/south/set"
command_topic: "edem/air/sprinkler/south/set"
min: 0
max: 6000
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив север"
state_topic: "edem/s_out/sprinkler/nord/set"
command_topic: "edem/air/sprinkler/nord/set"
min: 0
max: 6000
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив капельный"
state_topic: "edem/s_out/sprinkler/trees/set"
command_topic: "edem/air/sprinkler/trees/set"
min: 0
max: 6000
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив юг выполнено"
state_topic: "edem/s_out/sprinkler/south/val"
command_topic: "edem/air/sprinkler/south/val"
min: 0
max: 60000
mode: slider
icon: "mdi:timer-sand"
unit_of_measurement: 'sec'
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив север выполнено"
state_topic: "edem/s_out/sprinkler/nord/val"
command_topic: "edem/air/sprinkler/nord/val"
min: 0
max: 60000
mode: slider
icon: "mdi:timer-sand"
unit_of_measurement: 'sec'
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив капельный выполнено"
state_topic: "edem/s_out/sprinkler/trees/val"
command_topic: "edem/air/sprinkler/trees/val"
min: 0
max: 60000
mode: slider
icon: "mdi:timer-sand"
unit_of_measurement: 'sec'
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
sensor:
- name: "Полив бак Макс"
state_topic: "edem/s_out/sprinkler/$wMax"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив бак Мin"
state_topic: "edem/s_out/sprinkler/$wMin"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив водопровод"
state_topic: "edem/s_out/sprinkler/$vIN"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив дренаж вкл"
state_topic: "edem/s_out/sprinkler/$rDren"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив дренаж качает"
state_topic: "edem/s_out/sprinkler/$fbDren"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив насос вкл"
state_topic: "edem/s_out/sprinkler/$rPump"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив насос качает"
state_topic: "edem/s_out/sprinkler/$fbPump"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив состояние"
state_topic: "edem/s_out/sprinkler/$state"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив ошибка"
state_topic: "edem/s_out/sprinkler/$fault"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Поливаем юг"
state_topic: "edem/s_out/sprinkler/south/$state"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Поливаем север"
state_topic: "edem/s_out/sprinkler/nord/$state"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Поливаем капельно"
state_topic: "edem/s_out/sprinkler/trees/$state"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив розетка статус"
state_topic: "edem/s_out/sprinkler/outlets/$state"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив блокировки"
state_topic: "edem/s_out/sprinkler/ctrl"
availability_topic: "edem/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
```
> **Пояснение:**
> - `switch` переключатели для запуска/остановки зон и общий переключатель.
> - `button` кнопки для сброса, блокировки, разрешения и осушения бака.
> - `number` задание времени полива (уставки) и выполненное время (с возможностью ручной корректировки).
> - `sensor` датчики состояния поплавков (`$wMax`, `$wMin`), водопровода, насосов, статуса полива, ошибок и т.д.
После добавления конфигурации **перезагрузите YAML** (Настройки → Сервер → Перезагрузить конфигурацию YAML).
---
## 4. Настройка templateсущностей
Для работы панели необходимы два типа вычислимых объектов:
- **Уровень бака в процентах** (на основе поплавков).
- **Прогресс полива в процентах** для каждой зоны (с возможностью двусторонней синхронизации: проценты ↔ секунды).
Добавьте следующий блок в `configuration.yaml` (если у вас уже есть секция `template:`, объедините её с этим содержимым, сохранив правильные отступы):
```yaml
template:
# Сенсор уровня бака
- sensor:
- name: "Полив бак уровень"
state: >
{% if states('sensor.poliv_bak_maks') == 'ON' %}
100
{% elif states('sensor.poliv_bak_min') == 'ON' %}
50
{% else %}
0
{% endif %}
# Числовые шаблоны прогресса (проценты)
- number:
- name: "Полив юг прогресс"
state: >
{% set zadanie = states('number.poliv_iug') | float(0) %}
{% set vypolneno = states('number.poliv_iug_vypolneno') | float(0) %}
{{ ((vypolneno / zadanie * 100) | round(0)) if zadanie > 0 else 0 }}
set_value:
- service: number.set_value
target:
entity_id: number.poliv_iug_vypolneno
data:
value: >
{% set zadanie = states('number.poliv_iug') | float(0) %}
{{ ((value | float(0)) * zadanie / 100) | round(0) }}
min: 0
max: 100
step: 1
unit_of_measurement: "%"
icon: mdi:progress-clock
- name: "Полив север прогресс"
state: >
{% set zadanie = states('number.poliv_sever') | float(0) %}
{% set vypolneno = states('number.poliv_sever_vypolneno') | float(0) %}
{{ ((vypolneno / zadanie * 100) | round(0)) if zadanie > 0 else 0 }}
set_value:
- service: number.set_value
target:
entity_id: number.poliv_sever_vypolneno
data:
value: >
{% set zadanie = states('number.poliv_sever') | float(0) %}
{{ ((value | float(0)) * zadanie / 100) | round(0) }}
min: 0
max: 100
step: 1
unit_of_measurement: "%"
icon: mdi:progress-clock
- name: "Полив капельный прогресс"
state: >
{% set zadanie = states('number.poliv_kapelnyi') | float(0) %}
{% set vypolneno = states('number.poliv_kapelnyi_vypolneno') | float(0) %}
{{ ((vypolneno / zadanie * 100) | round(0)) if zadanie > 0 else 0 }}
set_value:
- service: number.set_value
target:
entity_id: number.poliv_kapelnyi_vypolneno
data:
value: >
{% set zadanie = states('number.poliv_kapelnyi') | float(0) %}
{{ ((value | float(0)) * zadanie / 100) | round(0) }}
min: 0
max: 100
step: 1
unit_of_measurement: "%"
icon: mdi:progress-clock
```
**Как это работает:**
- `state` динамически вычисляет процент выполнения на основе заданного времени (`number.poliv_...`) и выполненного (`number.poliv_..._vypolneno`).
- `set_value` при ручном изменении процента (через ползунок или ввод) пересчитывает его в секунды и отправляет команду в контроллер через `number.set_value` для выполненного времени. Таким образом, можно вручную скорректировать прогресс, и значение сразу уйдёт в MQTT.
- Бак определяется по поплавкам: если активен `$wMax` → 100%, если только `$wMin` → 50%, иначе 0%.
Снова **перезагрузите YAMLконфигурацию**.
---
## 5. Создание панели управления (Dashboard)
### 5.1. Создание новой панели
1. Перейдите в **Настройки****Панели** (Dashboards).
2. Нажмите **Создать панель** (Add Dashboard).
- Название: `💧 Полив`
- Режим панели: **Панель (одна карточка)** *Panel (1 card)*.
3. Нажмите **Создать**.
### 5.2. Переход в редактор YAML
Сразу после создания панель откроется в графическом редакторе.
Нажмите на три точки **⋮** в правом верхнем углу и выберите **Редактор YAML**.
### 5.3. Вставка финального YAML
Удалите всё содержимое (обычно там `type: panel\npath: ""\ncards: []`) и вставьте следующий код:
```yaml
views:
- type: panel
path: ''
cards:
- type: custom:vertical-stack-in-card
title: 💧 Система полива
cards:
# ── Общее состояние ──
- type: horizontal-stack
cards:
- type: custom:mushroom-entity-card
entity: sensor.poliv_sostoianie
name: Статус
primary_info: state
icon: mdi:state-machine
- type: custom:mushroom-entity-card
entity: sensor.poliv_blokirovki
name: Режим
primary_info: state
icon: mdi:lock
# ── Бак ──
- type: custom:mushroom-template-card
primary: Бак воды
secondary: |
{% set lvl = states('sensor.poliv_bak_uroven') %}
{% if lvl == '100' %} Полный
{% elif lvl == '50' %} Наполовину
{% else %} Пустой {% endif %}
icon: mdi:battery
icon_color: |
{% set lvl = states('sensor.poliv_bak_uroven') %}
{% if lvl == '100' %} green
{% elif lvl == '50' %} orange
{% else %} grey {% endif %}
tap_action:
action: more-info
entity: sensor.poliv_bak_uroven
card_mod:
style: |
ha-card {
--mush-icon-size: 24px;
height: 60px;
}
- type: divider
# ── Водопровод ──
- type: custom:mushroom-template-card
primary: Водопровод
secondary: |
{% if states('sensor.poliv_vodoprovod') == 'ON' %} Вкл
{% else %} Выкл {% endif %}
icon: mdi:water-pump
icon_color: |
{% if states('sensor.poliv_vodoprovod') == 'ON' %} blue
{% else %} grey {% endif %}
tap_action:
action: more-info
entity: sensor.poliv_vodoprovod
card_mod:
style: |
ha-card {
--mush-icon-size: 24px;
height: 60px;
}
# ── Насосы ──
- type: horizontal-stack
cards:
- type: custom:mushroom-template-card
primary: Дренаж
secondary: |
{% set pwr = states('sensor.poliv_drenazh_vkl') %}
{% set pump = states('sensor.poliv_drenazh_kachaet') %}
{% if pwr == 'ON' and pump == 'ON' %} Качает
{% elif pwr == 'ON' %} Вкл
{% else %} Выкл {% endif %}
icon: mdi:pump
icon_color: |
{% set pwr = states('sensor.poliv_drenazh_vkl') %}
{% set pump = states('sensor.poliv_drenazh_kachaet') %}
{% if pwr == 'ON' and pump == 'ON' %} red
{% elif pwr == 'ON' %} green
{% else %} grey {% endif %}
tap_action:
action: more-info
entity: sensor.poliv_drenazh_kachaet
card_mod:
style: |
ha-card {
--mush-icon-size: 24px;
height: 65px;
}
- type: custom:mushroom-template-card
primary: Помпа
secondary: |
{% set pwr = states('sensor.poliv_nasos_vkl') %}
{% set pump = states('sensor.poliv_nasos_kachaet') %}
{% if pwr == 'ON' and pump == 'ON' %} Качает
{% elif pwr == 'ON' %} Вкл
{% else %} Выкл {% endif %}
icon: mdi:pump
icon_color: |
{% set pwr = states('sensor.poliv_nasos_vkl') %}
{% set pump = states('sensor.poliv_nasos_kachaet') %}
{% if pwr == 'ON' and pump == 'ON' %} red
{% elif pwr == 'ON' %} green
{% else %} grey {% endif %}
tap_action:
action: more-info
entity: sensor.poliv_nasos_kachaet
card_mod:
style: |
ha-card {
--mush-icon-size: 24px;
height: 65px;
}
- type: divider
# ── Управление циклом ──
- type: entities
title: Управление циклом
show_header_toggle: false
entities:
- entity: switch.poliv
name: Полив
icon: mdi:sprinkler
- entity: switch.poliv_rozetki
name: Водяная розетка
icon: mdi:pipe-valve
- entity: button.poliv_sbros
name: Запуск всех зон
icon: mdi:play
- entity: button.poliv_blokirovka
name: Заморозить
icon: mdi:snowflake
- entity: button.poliv_razblokirovka
name: Разморозить
icon: mdi:snowflake-melt
- entity: button.poliv_zapretit
name: Запретить полив
icon: mdi:cancel
- entity: button.poliv_razreshit
name: Разрешить полив
icon: mdi:check-circle
- entity: button.poliv_osushenie_baka
name: Осушение бака
icon: mdi:hair-dryer
- type: divider
# ── Северная зона ──
- type: custom:vertical-stack-in-card
title: 🌿 Северная зона
cards:
- type: horizontal-stack
cards:
# Выключатель
- type: custom:mushroom-entity-card
entity: switch.poliv_sever
show_name: false
show_icon: false
show_state: false
layout: horizontal
# Время
- type: custom:mushroom-entity-card
entity: number.poliv_sever
name: Время
icon: mdi:timer-edit
layout: horizontal
card_mod:
style: |
ha-card {
--mush-icon-size: 24px;
height: 48px;
flex: 1 1 0;
}
- type: conditional
conditions:
- entity: number.poliv_sever
state_not: '0'
card:
type: custom:vertical-stack-in-card
title: ''
cards:
- type: horizontal-stack
cards:
- type: custom:button-card
entity: sensor.polivaem_sever
show_name: false
show_state: false
icon: mdi:sprinkler
tap_action:
action: more-info
entity: sensor.polivaem_sever
state:
- value: 'ON'
color: green
styles:
icon:
- animation: pulse 1.5s infinite
- value: 'OFF'
color: grey
styles:
icon:
- animation: none
styles:
card:
- height: 40px
- width: 40px
icon:
- width: 24px
- height: 24px
- type: custom:mushroom-template-card
primary: ''
secondary: >
Выполнено: {{ states('number.poliv_sever_vypolneno')
}} / {{ states('number.poliv_sever') }} с
icon: ''
tap_action:
action: more-info
entity: number.poliv_sever_progress
card_mod:
style: |
ha-card {
height: 40px;
margin-top: 0px;
}
- type: custom:bar-card
entities:
- entity: number.poliv_sever_progress
name: Прогресс
icon: mdi:progress-clock
max: 100
severity:
- from: 0
to: 25
color: red
- from: 26
to: 75
color: orange
- from: 76
to: 100
color: green
unit_of_measurement: '%'
- type: divider
# ── Южная зона ──
- type: custom:vertical-stack-in-card
title: 🌿 Южная зона
cards:
- type: horizontal-stack
cards:
- type: custom:mushroom-entity-card
entity: switch.poliv_iug
show_name: false
show_icon: false
show_state: false
layout: horizontal
- type: custom:mushroom-entity-card
entity: number.poliv_iug
name: Время
icon: mdi:timer-edit
layout: horizontal
card_mod:
style: |
ha-card {
--mush-icon-size: 24px;
height: 48px;
flex: 1 1 0;
}
- type: conditional
conditions:
- entity: number.poliv_iug
state_not: '0'
card:
type: custom:vertical-stack-in-card
title: ''
cards:
- type: horizontal-stack
cards:
- type: custom:button-card
entity: sensor.polivaem_iug
show_name: false
show_state: false
icon: mdi:sprinkler
tap_action:
action: more-info
entity: sensor.polivaem_iug
state:
- value: 'ON'
color: green
styles:
icon:
- animation: pulse 1.5s infinite
- value: 'OFF'
color: grey
styles:
icon:
- animation: none
styles:
card:
- height: 40px
- width: 40px
icon:
- width: 24px
- height: 24px
- type: custom:mushroom-template-card
primary: ''
secondary: >
Выполнено: {{ states('number.poliv_iug_vypolneno')
}} / {{ states('number.poliv_iug') }} с
icon: ''
tap_action:
action: more-info
entity: number.poliv_iug_progress
card_mod:
style: |
ha-card {
height: 40px;
margin-top: 0px;
}
- type: custom:bar-card
entities:
- entity: number.poliv_iug_progress
name: Прогресс
icon: mdi:progress-clock
max: 100
severity:
- from: 0
to: 25
color: red
- from: 26
to: 75
color: orange
- from: 76
to: 100
color: green
unit_of_measurement: '%'
- type: divider
# ── Капельная зона ──
- type: custom:vertical-stack-in-card
title: 🌿 Капельная зона
cards:
- type: horizontal-stack
cards:
- type: custom:mushroom-entity-card
entity: switch.poliv_kapelnyi
show_name: false
show_icon: false
show_state: false
layout: horizontal
- type: custom:mushroom-entity-card
entity: number.poliv_kapelnyi
name: Время
icon: mdi:timer-edit
layout: horizontal
card_mod:
style: |
ha-card {
--mush-icon-size: 24px;
height: 48px;
flex: 1 1 0;
}
- type: conditional
conditions:
- entity: number.poliv_kapelnyi
state_not: '0'
card:
type: custom:vertical-stack-in-card
title: ''
cards:
- type: horizontal-stack
cards:
- type: custom:button-card
entity: sensor.polivaem_kapelno
show_name: false
show_state: false
icon: mdi:watering-can
tap_action:
action: more-info
entity: sensor.polivaem_kapelno
state:
- value: 'ON'
color: green
styles:
icon:
- animation: pulse 1.5s infinite
- value: 'OFF'
color: grey
styles:
icon:
- animation: none
styles:
card:
- height: 40px
- width: 40px
icon:
- width: 24px
- height: 24px
- type: custom:mushroom-template-card
primary: ''
secondary: >
Выполнено: {{
states('number.poliv_kapelnyi_vypolneno') }} / {{
states('number.poliv_kapelnyi') }} с
icon: ''
tap_action:
action: more-info
entity: number.poliv_kapelnyi_progress
card_mod:
style: |
ha-card {
height: 40px;
margin-top: 0px;
}
- type: custom:bar-card
entities:
- entity: number.poliv_kapelnyi_progress
name: Прогресс
icon: mdi:progress-clock
max: 100
severity:
- from: 0
to: 25
color: red
- from: 26
to: 75
color: orange
- from: 76
to: 100
color: green
unit_of_measurement: '%'
```
Нажмите **Сохранить** панель готова к использованию.
---
## 6. Как пользоваться панелью
![sprinkler_1.jpeg](sprinkler_1.jpeg)
![sprinkler_2.jpeg](sprinkler_2.jpeg)
- **Общее состояние:** Отображает статус системы и режим (заморозка/разрешено/запрещено). Ошибка не показывается, но иконка станет красной при неисправности.
- **Бак:** Иконка батарейки меняет цвет и текст в зависимости от уровня воды (пусто/наполовину/полный). Нажатие открывает историю уровня.
- **Водопровод и насосы:** Показывают текущий статус (Выкл/Вкл/Качает) с цветовой индикацией. Клик история соответствующего датчика.
- **Управление циклом:** Общий переключатель «Полив», кнопки запуска всех зон, заморозки, запрета, осушения бака. Работают как обычные MQTTкоманды.
- **Зоны полива:**
1. **Включение зоны** крайний левый переключатель (тумблер).
2. **Время** поле с кнопками +/- для задания уставки полива в секундах.
3. Если время > 0, под строкой появятся:
- Индикатор полива (спринклер/лейка) цветной, с анимацией пульсации при работе. Нажатие открывает историю активности зоны.
- Строка «Выполнено: X / Y с» абсолютное значение. Нажатие открывает окно редактирования прогресса в процентах.
- Прогресс-бар визуальное отображение процента выполнения (красный → оранжевый → зелёный).
- **Корректировка прогресса:** Кликните на строку «Выполнено…» → появится слайдер 0-100%. Измените процент, и соответствующее количество секунд сразу отправится в контроллер (автоматический пересчёт).
---
## 7. Возможные проблемы и их решение
| Проблема | Вероятная причина | Решение |
|----------|-------------------|---------|
| Не отображаются прогресс‑бары | Отсутствует `bar-card` или не созданы `template number` | Проверьте HACS, установите карту. Убедитесь, что templateчисла добавлены и конфигурация перезагружена. |
| Иконка полива не меняет цвет | Неправильное имя датчика `sensor.polivaem_...` | Проверьте точное название в разделе **Состояния**. Поправьте в YAML панели. |
| Тумблер включения зоны не видим | Не установлена карта `mushroom-entity-card` или перекрыт стилями | Убедитесь, что `mushroom-cards` установлены. В нашем финальном YAML тумблер видим всегда. |
| Ошибка «expected float» при движении слайдера прогресса | Используется `slider-entity-row` в активном режиме, а динамический `max` задан некорректно | В нашем решении прогресс‑бар только визуальный (`bar-card`), ручная корректировка через `template number` с собственным `set_value`. Ошибки быть не должно. |
| Бак всегда показывает «Пустой» | Неправильно работают поплавки или датчики `$wMax`/`$wMin` не обновляются | Проверьте в MQTTтопиках фактические значения. Убедитесь, что поплавки подключены и отправляют ON/OFF. |
---
## 8. Дополнительная информация
- Все команды управления (вкл/выкл зон, сброс, заморозка и т.д.) отправляются напрямую в контроллер lighthub через MQTT.
- Система использует `availability_topic` для отслеживания связи с контроллером. При пропадании сигнала все элементы станут недоступны (серые).
- При желании можно расширить панель новыми зонами, копируя структуру существующих и добавляя соответствующие `number`, `switch` и `sensor` в MQTTконфигурацию и `template`.
---
## Приложение: полный финальный YAML панели
Код уже приведён в разделе 5.3. Используйте его как окончательную версию.
---
Теперь ваша система полива полностью интегрирована в Home Assistant. Вы можете управлять поливом с мобильного телефона, отслеживать историю и быть уверенными, что всё под контролем!

View File

@@ -28,13 +28,13 @@ INIT | true | OFF | выключить клапана
OFF | vMax && !FREEZE | FULL | выключить vIN, rDren|
OFF | ! vMax && !FREEZE | DREN\_ON|включить rDren|
DREN\_ON|fbDren (насос дренажа реально работает)|DREN\_OPERATE|
DREN\_ON|таймаут 10 сек|DREN\_EMPTY|
DREN\_ON|таймаут 10 сек|DREN\_EMPTY| насос так и не заработал - видимо в колодце пусто
DREN\_EMPTY|включен цикл полива и бак не полон|VIN|включить клапан vIN для набора бака из водопровода|
DREN\_OPERATE|fbDren (насос дренажа более не работает)|DREN\_EMPTY||
VIN|fbDren|DREN\_OPERATE|вылючить клапан vIN для набора бака из водопровода|
VIN, DREN\_OPERATE|vMax|FULL | выключить vIN, rDren|
VIN|таймаут 1200 сек | FAULT\_VIN| выключить vIN|
DREN\_OPERATE|таймаут 1200 сек |FAULT_DREN||
DREN\_OPERATE| ! fbDren (насос дренажа более не работает)|DREN\_EMPTY||
VIN|fbDren|DREN\_OPERATE|вылючить клапан vIN для набора бака из водопровода - заработал дренажный насос|
VIN, DREN\_OPERATE|vMax|FULL | выключить vIN, rDren - бак полон|
VIN|таймаут 1200 сек | FAULT\_VIN| выключить vIN - бак так и не наполнился|
DREN\_OPERATE|таймаут 2000 сек |FAULT_DREN|выключить rDren - не смотря на продолжительную работу насоса бак не наполнен|
@@ -59,7 +59,8 @@ DREN\_OPERATE|таймаут 1200 сек |FAULT_DREN||
},
"nord":{"pin":6,"set":60,"cmd":1},
"south":{"pin":7,"set":100,"cmd":1},
"trees":{"pin":10,"set":60,"cmd":2}
"trees":{"pin":10,"set":60,"cmd":2},
"outlets:{}
}]
}
@@ -79,18 +80,35 @@ DREN\_OPERATE|таймаут 1200 сек |FAULT_DREN||
Контроллер должен передать это значение в выходной топик ```root/name/s_out/sprinkrer/garden/set``` и оно будет восстановлено при перезагрузке контроллера
отработанный обьем воды или время будет сохраняться в параметре "val" каждой зоны (параметр будет автоматически увеличиваться при работе зоны, передаваться в соответствующий зоне топик для мониторинга и восстановления в случае перезагрузки контроллера)
**Пример топика:** ```root/s_out/sprinkler/garden/val```
Когда данный параметр достигнет значения, заданного в параметре "set" контроллер завершит полив данной зоны и перейдет к следующей.
Когда данный параметр достигнет значения (или времени), заданного в параметре "set" контроллер завершит полив данной зоны и перейдет к следующей.
Важно: если set=0 (по умолчанию) то время работы зоны не лимитируется. Если такая зона включена - система полива не будет отключаться после окончания полива прочих зон и насос не будет обесточиваться. Это удобно, если в системе полива есть водяная розетка для подключения поливочного шланга, которая всегда должна находиться под давлением. Такую зону конфигурируйте последней в списке.
Для сброса счетчиков можно использовать как непосредственную установку значения параметра "val" для каждой зоны так и команду RESET, отправленную в нужную зону или в объект sprinkler через суффикс /cmd.
В последнем случае, контроллер итерационно сбросит счетчики в значение 0 для каждой зоны полива.
**Пример:** ```root/name/sprinkler/cmd -> RESET```
В последнем случае, контроллер итерационно сбросит счетчики в значение 0 для каждой зоны полива.
Это инициирует старт программы полива, если система находится в состоянии ON
Команда RESET не отработает если обьект полива находится в состоянии DISABLE или FREEZE
Также, в этих режимах, в принципе, не отработает запуск полива какой-либо зоны (команды set/RESET), если для нее задано время/обьем полива.
Если обьем полива не задан (set==0) - это например водяная розетка для мытья машины, то такая зона запустится даже в режиме DISABLE
Если в процессе полива придет команда DISABLE (начался дождь) - полив прекратится, программа полива возобновится после ENABLE
## Управление
@@ -105,7 +123,9 @@ DREN\_OPERATE|таймаут 1200 сек |FAULT_DREN||
### Включение/выключение цикла полива:
**Включить** ```root/name/sprinkler/cmd -> ON```
Система начнет или продолжит цикл полива, переходя от зоны к зоне по мере завершения работы с каждой предыдущей зоной. После завершения работы со всеми зонами, sprinkler перейдет в состояние OFF
Система начнет или продолжит цикл полива, переходя от зоны к зоне по мере завершения работы с каждой предыдущей зоной. После завершения работы со всеми зонами, sprinkler НЕ перейдет в состояние OFF автоматически, но полив прекратится для тех зон, где показатель val достиг параметра set
Если не будет ни одной активной зоны - насос обесточится.
Перед включением полива, система убедится что бак наполнен или до-наполнит его до максимума из водопровода.
@@ -157,6 +177,19 @@ root/name/sprinkler/val -> -1 //система перейдет в режим FR
```
Так как команда FREEZE блокирует запуск каких либо насосов и открытие клапанов, это делает невозможным слив системы в процессе подготовки к зиме
Когда требуется слить систему, необходимо отключить дренажный насос, перекрыть водопроводный кран
Данный режим реализован при помощи команды DRY
Дополнительно к перекрытию поступления воды в бак (заблокирован автомат пополнения бака из дренажного насоса и водопровода) данная команда включает помпу.
Далее, требуется слить бак включением выбранных зон полива или через водяные розетки.
Только после осушения бака (насос выключится) - систему необходимо перевести в режим FREEZE
Перевод в этот редим сбрасывает режим DRY (а также, состояния ошибки автомата наполнения бака)
### Передача статусных значений
@@ -178,4 +211,140 @@ root/s_out/sprinkler/garden/cmd - ON или OFF - признак включен
root/s_out/sprinkler/garden/$state - ON или OFF - признак того что зона поливается в настоящее время
root/s_out/sprinkler/garden/val - текущее время или обьем полива данной зоны
```
### Пример конфигурации Home Assistant
```
sensor:
- name: "Полив бак Макс"
state_topic: "root/s_out/sprinkler/$wMax"
- name: "Полив бак Мin"
state_topic: "root/s_out/sprinkler/$wMin"
- name: "Полив водопровод"
state_topic: "root/s_out/sprinkler/$vIN"
- name: "Полив дренаж вкл"
state_topic: "root/s_out/sprinkler/$rDren"
- name: "Полив дренаж качает"
state_topic: "root/s_out/sprinkler/$fbDren"
- name: "Полив насос вкл"
state_topic: "root/s_out/sprinkler/$rPump"
- name: "Полив насос качает"
state_topic: "root/s_out/sprinkler/$fbPump"
- name: "Полив состояние"
state_topic: "root/s_out/sprinkler/$state"
- name: "Полив ошибка"
state_topic: "root/s_out/sprinkler/$fault"
- name: "Полив юг выполнено"
state_topic: "root/s_out/sprinkler/south/val"
- name: "Полив север выполнено"
state_topic: "root/s_out/sprinkler/nord/val"
- name: "Полив капельный выполнено"
state_topic: "edem/s_out/sprinkler/trees/val"
- name: "Поливаем юг"
state_topic: "root/s_out/sprinkler/south/$state"
- name: "Поливаем север"
state_topic: "root/s_out/sprinkler/nord/$state"
- name: "Поливаем капельно"
state_topic: "root/s_out/sprinkler/trees/$state"
- name: "Полив розетка статус"
state_topic: "root/s_out/sprinkler/outlets/$state"
- name: "Полив блокировки"
state_topic: "root/s_out/sprinkler/ctrl"
switch:
- name: "Полив"
state_topic: "root/s_out/sprinkler/cmd"
command_topic: "root/air/sprinkler/cmd"
availability_topic: "root/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив север"
state_topic: "root/s_out/sprinkler/nord/cmd"
command_topic: "root/air/sprinkler/nord/cmd"
availability_topic: "root/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив юг"
state_topic: "root/s_out/sprinkler/south/cmd"
command_topic: "root/air/sprinkler/south/cmd"
availability_topic: "root/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив капельный"
state_topic: "root/s_out/sprinkler/trees/cmd"
command_topic: "root/air/sprinkler/trees/cmd"
availability_topic: "root/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
- name: "Полив розетки"
state_topic: "root/s_out/sprinkler/outlets/cmd"
command_topic: "root/air/sprinkler/outlets/cmd"
availability_topic: "root/air/$state"
payload_available: "ready"
payload_not_available: "disconnected"
button:
- name: "Полив сброс"
command_topic: "root/air/sprinkler/cmd"
payload_press: "RESET"
- name: "Полив блокировка"
command_topic: "root/air/sprinkler/cmd"
payload_press: "FREEZE"
- name: "Полив разблокировка"
command_topic: "root/air/sprinkler/cmd"
payload_press: "UNFREEZE"
- name: "Полив разрешить"
command_topic: "root/air/sprinkler/cmd"
payload_press: "ENABLE"
- name: "Полив запретить"
command_topic: "root/air/sprinkler/cmd"
payload_press: "DISABLE"
number:
- name: "Полив юг"
state_topic: "root/s_out/sprinkler/south/set"
command_topic: "root/air/sprinkler/south/set"
min: 0
max: 6000
- name: "Полив север"
state_topic: "root/s_out/sprinkler/nord/set"
command_topic: "root/air/sprinkler/nord/set"
min: 0
max: 6000
- name: "Полив капельный"
state_topic: "root/s_out/sprinkler/trees/set"
command_topic: "root/air/sprinkler/trees/set"
min: 0
max: 6000
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 KiB

View File

@@ -2171,18 +2171,11 @@ int Item::SendStatus(long sendFlags, char * subItem) {
// Send ctrl
if (sendFlags & FLAG_FLAGS)
{
if (getFlag(FLAG_DISABLED))
strcpy_P(cmdstr, DISABLE_P);
else if (getFlag(FLAG_FREEZED) == FLAG_FREEZED)
strcpy_P(cmdstr, FREEZE_P);
if (getFlag(FLAG_FREEZED)) strcpy_P(cmdstr, FREEZE_P);
else if (getFlag(FLAG_DISABLED)) strcpy_P(cmdstr, DISABLE_P);
else strcpy_P(cmdstr, ENABLE_P);
//else strcpy_P(cmdstr, UNFREEZE_P);
setTopic(addrstr,sizeof(addrstr),T_OUT);
strncat(addrstr, itemArr->name, sizeof(addrstr)-1);
if (subItem)

View File

@@ -1250,6 +1250,7 @@ bool itemCmd::saveItem(Item * item, uint16_t optionsFlag)
case CMD_ENABLE:
case CMD_FREEZE:
case CMD_UNFREEZE:
case CMD_RESET:
break;
default:
item->setCmd(cmd.cmdCode);

View File

@@ -99,7 +99,7 @@ int out_sprinkler::Setup()
debugSerial << F("SPRINKLER: ") << " wMax=" << wMaxPin << " wMin=" << wMinPin << " fbDren=" << fbDrenPin << " fbPump=" << fbPumpPin << " wCtr=" << wCtrPin << endl;
uint16_t lastVals = 0;
if (wCtrPin != PINS_COUNT)
if (abs(wCtrPin) < PINS_COUNT)
{
setupInPin(wCtrPin);
lastVals |= (readInPin(wCtrPin) ? LASTWCTRLSTATE : 0);
@@ -165,7 +165,7 @@ bool out_sprinkler::isNeedPump(bool steelNeed)
{
long setVal = getIntFromJson(zone, "set", 0);
long valVal = getIntFromJson(zone, "val", 0);
if (valVal < setVal) return true;
if (!setVal || (valVal < setVal)) return true;
}
}
zone = zone->next;
@@ -199,6 +199,20 @@ void out_sprinkler::dren(bool state)
}
}
void out_sprinkler::vin(bool state)
{
if (!isValidControlPin(vinPin)) return;
uint32_t lastVals = getIntFromJson (gatesObj, "@lastVals", 0);
setOutput(vinPin, state);
if (state != (bool)(lastVals & LASTVINSTATE))
{
if(state) lastVals |= LASTVINSTATE; else lastVals &= ~LASTVINSTATE;
setValToJson(gatesObj, "@lastVals", (long)lastVals);
publishBooleanState("/$rVIN", state);
}
}
void out_sprinkler::turnOffAllZones()
{
if (!gatesObj) return;
@@ -208,7 +222,7 @@ void out_sprinkler::turnOffAllZones()
if (zone->name && *zone->name && zone->type == aJson_Object)
{
short pin = getIntFromJson(zone, "pin", PINS_COUNT);
setOutput(pin, false);
if (isValidControlPin(pin)) setOutput(pin, false);
if (getIntFromJson(zone, "@active", 0))
{
setZoneActive(zone, false);
@@ -241,8 +255,9 @@ long fault = 0;
if (!gatesObj) return;
setValToJson(gatesObj, "@state", (long)state);
aJsonObject *faultObj = aJson.getArrayItem(item->itemArg, 3);
aJsonObject * rootCfg = aJson.getObjectItem(gatesObj, "");
if (!rootCfg) rootCfg = gatesObj;
aJsonObject * faultObj = aJson.getObjectItem(rootCfg, "onFault");
switch (state) {
case SP_OFF:
@@ -269,6 +284,10 @@ switch (state) {
strcpy(val,"FULL");
break;
case SP_DRYING:
strcpy(val,"DRYING");
break;
case SP_FAULT_VIN:
strcpy(val,"FAULT_VIN");
fault = 1;
@@ -298,7 +317,7 @@ publishTopic(item->itemArr->name,val,"/$state");
int out_sprinkler::shutdown(sprinklerState nextState)
int out_sprinkler::moveToState(sprinklerState nextState)
{
if (!gatesObj) return 0;
@@ -306,36 +325,42 @@ int out_sprinkler::shutdown(sprinklerState nextState)
{
case SP_OFF:
case SP_FULL:
setOutput(drenPin, false);
setOutput(vinPin, false);
dren(false);
vin(false);
break;
case SP_DREN_ON:
case SP_DREN_OPERATE:
setOutput(drenPin, true);
setOutput(vinPin, false);
dren(true);
vin(false);
break;
case SP_VIN:
setOutput(vinPin, true);
setOutput(drenPin, false);
vin(true);
//setOutput(drenPin, false);
break;
case SP_DREN_EMPTY:
setOutput(drenPin, false);
setOutput(vinPin, false);
//setOutput(drenPin, false);
vin(false);
break;
case SP_FAULT_VIN:
setOutput(vinPin, false);
vin(false);
break;
case SP_FAULT_DREN:
setOutput(drenPin, false);
dren(false);
break;
case SP_UNKNOWN:
setOutput(drenPin, false);
setOutput(vinPin, false);
dren(false);
vin(false);
break;
case SP_DRYING:
dren(false);
vin(false);
pump(true);
item->On();
break;
}
publishBooleanState("/$rDren", nextState == SP_DREN_ON || nextState == SP_DREN_OPERATE);
publishBooleanState("/$vIN", nextState == SP_VIN);
//publishBooleanState("/$rDren", nextState == SP_DREN_ON || nextState == SP_DREN_OPERATE);
//publishBooleanState("/$vIN", nextState == SP_VIN);
notifyState(nextState);
return 1;
}
@@ -357,7 +382,8 @@ inline aJsonObject * out_sprinkler::findNextZone()
{
if (zone->name && *zone->name && zone->type == aJson_Object)
{
if (getIntFromJson(zone, "@active", 0)) return zone;
long setVal = getIntFromJson(zone, "set", 0);
if (getIntFromJson(zone, "@active", 0) && setVal) return zone;
}
zone = zone->next;
}
@@ -370,10 +396,23 @@ inline aJsonObject * out_sprinkler::findNextZone()
int cmd = getIntFromJson(zone, "cmd", CMD_OFF);
long setVal = getIntFromJson(zone, "set", 0);
long valVal = getIntFromJson(zone, "val", 0);
if (cmd == CMD_ON && valVal < setVal) return zone;
if (cmd == CMD_ON && (setVal && valVal < setVal)) return zone;
}
zone = zone->next;
}
zone = gatesObj->child;
while (zone)
{
if (zone->name && *zone->name && zone->type == aJson_Object)
{
int cmd = getIntFromJson(zone, "cmd", CMD_OFF);
long setVal = getIntFromJson(zone, "set", 0);
if (cmd == CMD_ON && (!setVal )) return zone;
}
zone = zone->next;
}
return NULL;
}
@@ -443,7 +482,7 @@ int out_sprinkler::Poll(short cause)
bool lastWctrlState = lastVals & LASTWCTRLSTATE;
bool lastWctrlStateAll = lastVals & LASTWCTRLSTATE_ALL;
if (wCtrPin != PINS_COUNT)
if (abs(wCtrPin) < PINS_COUNT)
{
bool curr = readInPin(wCtrPin);
if (curr && !lastWctrlStateAll)
@@ -456,7 +495,7 @@ int out_sprinkler::Poll(short cause)
if (freeze)
{
shutdown(SP_OFF);
moveToState(SP_OFF);
turnOffValves();
pump(false);
return 0;
@@ -477,7 +516,7 @@ int out_sprinkler::Poll(short cause)
state = SP_DREN_ON;
setValToJson(gatesObj, "@timer", (long)now);
setValToJson(gatesObj, "@state", (long)state);
shutdown(SP_DREN_ON);
moveToState(SP_DREN_ON);
}
break;
@@ -487,13 +526,13 @@ int out_sprinkler::Poll(short cause)
state = SP_DREN_OPERATE;
setValToJson(gatesObj, "@timer", (long)now);
setValToJson(gatesObj, "@state", (long)state);
shutdown(SP_DREN_OPERATE);
moveToState(SP_DREN_OPERATE);
}
else if (isTimeOver(timer, now, DRENAGE_ON_TIME))
{
state = SP_DREN_EMPTY;
setValToJson(gatesObj, "@state", (long)state);
shutdown(SP_DREN_EMPTY);
moveToState(SP_DREN_EMPTY);
}
break;
@@ -502,19 +541,19 @@ int out_sprinkler::Poll(short cause)
{
state = SP_DREN_EMPTY;
setValToJson(gatesObj, "@state", (long)state);
shutdown(SP_DREN_EMPTY);
moveToState(SP_DREN_EMPTY);
}
else if (wMax)
{
state = SP_FULL;
setValToJson(gatesObj, "@state", (long)state);
shutdown(SP_FULL);
moveToState(SP_FULL);
}
else if (isTimeOver(timer, now, 1200000UL))
else if (isTimeOver(timer, now, DRENAGE_MAX_TIME))
{
state = SP_FAULT_DREN;
setValToJson(gatesObj, "@state", (long)state);
shutdown(SP_FAULT_DREN);
moveToState(SP_FAULT_DREN);
}
break;
@@ -523,14 +562,14 @@ int out_sprinkler::Poll(short cause)
{
state = SP_FULL;
setValToJson(gatesObj, "@state", (long)state);
shutdown(SP_FULL);
moveToState(SP_FULL);
}
else if (item->getCmd() == CMD_ON)
{
state = SP_VIN;
setValToJson(gatesObj, "@timer", (long)now);
setValToJson(gatesObj, "@state", (long)state);
shutdown(SP_VIN);
moveToState(SP_VIN);
}
break;
@@ -540,19 +579,19 @@ int out_sprinkler::Poll(short cause)
state = SP_DREN_OPERATE;
setValToJson(gatesObj, "@timer", (long)now);
setValToJson(gatesObj, "@state", (long)state);
shutdown(SP_DREN_OPERATE);
moveToState(SP_DREN_OPERATE);
}
else if (wMax)
{
state = SP_FULL;
setValToJson(gatesObj, "@state", (long)state);
shutdown(SP_FULL);
moveToState(SP_FULL);
}
else if (isTimeOver(timer, now, VIN_MAX_TIME))
{
state = SP_FAULT_VIN;
setValToJson(gatesObj, "@state", (long)state);
shutdown(SP_FAULT_VIN);
moveToState(SP_FAULT_VIN);
}
break;
@@ -562,7 +601,7 @@ int out_sprinkler::Poll(short cause)
state = SP_DREN_ON;
setValToJson(gatesObj, "@timer", (long)now);
setValToJson(gatesObj, "@state", (long)state);
shutdown(SP_DREN_ON);
moveToState(SP_DREN_ON);
}
break;
@@ -571,7 +610,7 @@ int out_sprinkler::Poll(short cause)
{
state = SP_FULL;
setValToJson(gatesObj, "@state", (long)state);
shutdown(SP_FULL);
moveToState(SP_FULL);
}
break;
@@ -580,7 +619,7 @@ int out_sprinkler::Poll(short cause)
{
state = SP_FULL;
setValToJson(gatesObj, "@state", (long)state);
shutdown(SP_FULL);
moveToState(SP_FULL);
}
break;
}
@@ -595,64 +634,71 @@ int out_sprinkler::Poll(short cause)
pump(false);
return 0;
}
bool isActiveZone = false;
if (item->getCmd() == CMD_ON && tankReady)
{
currentZone = findNextZone();
if (currentZone)
{
long setVal = getIntFromJson(currentZone, "set", 0);
long valVal = getIntFromJson(currentZone, "val", 0);
{
long setVal = getIntFromJson(currentZone, "set", 0);
long valVal = getIntFromJson(currentZone, "val", 0);
// if not active - activate
if (!getIntFromJson(currentZone, "@active", 0))
{
turnOffAllZones();
if (!item->getFlag(FLAG_DISABLED) || !setVal)
{
setZoneActive(currentZone, true);
short zonePin = getIntFromJson(currentZone, "pin", PINS_COUNT);
if (isValidControlPin(zonePin)) setOutput(zonePin, true);
setValToJson(gatesObj, "@flowTimer", (long)now);
isActiveZone = true;
}
}
else isActiveZone = true;
if (!getIntFromJson(currentZone, "@active", 0))
{
turnOffAllZones();
setZoneActive(currentZone, true);
short zonePin = getIntFromJson(currentZone, "pin", PINS_COUNT);
setOutput(zonePin, true);
setValToJson(gatesObj, "@flowTimer", (long)now);
}
if (isActiveZone)
{
if (abs(wCtrPin) < PINS_COUNT)
{
bool curr = readInPin(wCtrPin);
if (curr && !lastWctrlState)
{
updateZoneValue(currentZone, 1);
}
if (curr) lastVals |= LASTWCTRLSTATE; else lastVals &= ~LASTWCTRLSTATE;
setValToJson(gatesObj, "@lastVals", (long)lastVals);
}
else
{
uint32_t flowTimer = (uint32_t)getIntFromJson(gatesObj, "@flowTimer", now);
if (isTimeOver(flowTimer, now, 1000UL))
{
updateZoneValue(currentZone, 1);
setValToJson(gatesObj, "@flowTimer", (long)now);
}
}
}
if (wCtrPin != PINS_COUNT)
{
bool curr = readInPin(wCtrPin);
if (curr && !lastWctrlState)
{
updateZoneValue(currentZone, 1);
}
if (curr) lastVals |= LASTWCTRLSTATE; else lastVals &= ~LASTWCTRLSTATE;
setValToJson(gatesObj, "@lastVals", (long)lastVals);
}
else
{
uint32_t flowTimer = (uint32_t)getIntFromJson(gatesObj, "@flowTimer", now);
if (isTimeOver(flowTimer, now, 1000UL))
{
updateZoneValue(currentZone, 1);
setValToJson(gatesObj, "@flowTimer", (long)now);
}
}
if (setVal > 0 && (valVal >= setVal || item->getFlag(FLAG_DISABLED)))
{
short zonePin = getIntFromJson(currentZone, "pin", PINS_COUNT);
if (isValidControlPin(zonePin)) setOutput(zonePin, false);
setZoneActive(currentZone, false);
currentZone = findNextZone();
}
if (setVal > 0 && valVal >= setVal)
{
setOutput(getIntFromJson(currentZone, "pin", PINS_COUNT), false);
setZoneActive(currentZone, false);
//////setValToJson(currentZone, "cmd", (long)CMD_OFF);
//item->SendStatusImmediate(itemCmd().Cmd(CMD_OFF).setSuffix(S_CMD), FLAG_COMMAND, currentZone->name);
currentZone = findNextZone();
}
if (currentZone)
{
needPump = true;
}
if (currentZone)
{
needPump = true;
}
}
}
if (!needPump)
{
pump(false);
/*
if (item->getCmd() == CMD_ON)
{
aJsonObject * resultZone = findNextZone();
@@ -662,6 +708,7 @@ int out_sprinkler::Poll(short cause)
item->SendStatus(FLAG_COMMAND);
}
}
*/
}
else
{
@@ -733,6 +780,7 @@ int out_sprinkler::Ctrl(itemCmd cmd, char* subItem, bool toExecute, bool authori
return 1;
case CMD_RESET:
if (item->getFlag(FLAG_FREEZED | FLAG_DISABLED)) return -1;
setValToJson(zone, "val", (long)0);
if (sendStatus)
{
@@ -757,11 +805,18 @@ int out_sprinkler::Ctrl(itemCmd cmd, char* subItem, bool toExecute, bool authori
case CMD_OFF:
turnOffAllZones();
pump(false);
//dren(false);
return 1;
case CMD_DRY:
setValToJson(gatesObj, "@state", (long)SP_DRYING);
moveToState(SP_DRYING);
notifyState(SP_DRYING);
return 1;
case CMD_RESET:
{
if (item->getFlag(FLAG_FREEZED | FLAG_DISABLED)) return -1;
aJsonObject * zone = gatesObj->child;
while (zone)
{
@@ -775,6 +830,10 @@ int out_sprinkler::Ctrl(itemCmd cmd, char* subItem, bool toExecute, bool authori
}
zone = zone->next;
}
// turnOffAllZones();
// pump(false);
// item->Off();
}
return 1;

View File

@@ -8,6 +8,7 @@
#define DRENAGE_ON_TIME 10000
#define VIN_MAX_TIME 1200000UL
#define DRENAGE_MAX_TIME 2000000UL
#define LASTWCTRLSTATE 1
#define LASTWCTRLSTATE_ALL 2
@@ -17,6 +18,7 @@
#define LASTFBPUMPSTATE 32
#define LASTPUMPSTATE 64
#define LASTDRENSTATE 128
#define LASTVINSTATE 256
enum sprinklerState {
SP_UNKNOWN = 0,
@@ -26,6 +28,7 @@ enum sprinklerState {
SP_DREN_EMPTY = 4,
SP_VIN = 5,
SP_FULL = 6,
SP_DRYING = 7,
SP_FAULT_VIN = -1,
SP_FAULT_DREN = -2
};
@@ -51,6 +54,7 @@ protected:
void pump(bool state);
void dren(bool state);
void vin(bool state);
void setOutput(short pin, bool value);
bool isNeedPump(bool steelNeed=false);
void turnOffValves();
@@ -64,7 +68,7 @@ protected:
void publishBooleanStateIfChanged(const char * subItem, bool state, uint32_t flag, uint32_t & lastState);
bool isFreeze();
void notifyState(short state);
int shutdown(sprinklerState nextState);
int moveToState(sprinklerState nextState);
void updateCounterValue();
};
#endif