mirror of
https://github.com/anklimov/lighthub
synced 2026-05-02 12:06:55 +00:00
MultiAC small fix + SPRINKLER chanell type (initial, unverivied)
This commit is contained in:
181
documentation/Sprinkler_module.md
Normal file
181
documentation/Sprinkler_module.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# Данный модуль реализует многозональную систему полива
|
||||
|
||||
## Система состоит из следующих компонент:
|
||||
|
||||
* Накопительный водяной бак. Снабжен двумя поплавками. Максимум воды и минимум воды. Заведены на входы wMax и wMin
|
||||
* Насос полива высокого давления. Запитан из бака. Включается реле, подключенным к выходу rPump. Датчик тока для контроля того, что насос включен, заведен на вход fbPump
|
||||
* Набор клапанов зон полива. Подключены через оптореле к выходам, заданным в параметре pin соответствующей зоны полива.
|
||||
* Клапан налива из водопровода. Подключен через оптореле к выходу vIn
|
||||
* Насос дренажного колодца. Приоритетный источник для наполнения бака полива. Когда система полива находится в ждущем или активном режиме, бак пытается максимально наполнится из дренажного колодца. Насос дренажа имеет поплавковый выключатель, отключающий насос при осушении дренажного колодца. Насос включается реле, которое подключено к выходу rDren. Для контроля того, что насос включен и момента осушения колодца, используется датчик тока, который подключен в входу fbDren
|
||||
* Опциональный водосчетчик. Контакты подключены к входу wCtr
|
||||
|
||||
## Система налива воды реализована при помощи конечного автомата со следующими состояниями:
|
||||
|
||||
* SP\_UNKNOWN
|
||||
* SP\_OFF
|
||||
* SP\_DREN\_ON - дренажный насос включен
|
||||
* SP\_DREN\_OPERATE - дренажный насос работает
|
||||
* SP\_DREN\_EMPTY - дренажный насос выключился встроенным поплавком - колодец пуст
|
||||
* SP\_VIN - включено наполнение из водопровода
|
||||
* SP\_FULL - бак наполнен
|
||||
|
||||
граф переходов конечного автомата системы налива воды
|
||||
|
||||
Состояние SP\_* | Условие перехода | перейти в состояние | выполнить при переходе |
|
||||
|------|--------|-------|---------|
|
||||
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\_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||
|
||||
|
||||
|
||||
|
||||
## Конфигурирование:
|
||||
|
||||
|
||||
```
|
||||
|
||||
"items":
|
||||
{
|
||||
"sprinkler":[23,
|
||||
|
||||
{
|
||||
"":{
|
||||
"vIn:7,
|
||||
"wMax":15,
|
||||
"wMin:17,
|
||||
"rDren":6,
|
||||
"fbDren":12,
|
||||
"rPump":5,
|
||||
"fbPump":11,
|
||||
"wCtr":19
|
||||
},
|
||||
"garden":{"pin":13,"set":60,"val":15,"cmd":2},
|
||||
"backyard":{"pin":14,"set":60,"val":15,"cmd":2},
|
||||
"trees":{"pin":15,"set":60,"val":15,"cmd":2}
|
||||
|
||||
}]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Алгоритм работы
|
||||
в настройки зон полива задаем интенсивность для каждой зоны.
|
||||
|
||||
Это можно сделать как в конфиге так динамически, (стандартными механизмами управления по MQTT, HTTP, CAN)
|
||||
|
||||
Рассмотрим на примере MQTT:
|
||||
|
||||
**топик** ```root/name/sprinkler/garden/set -> 60```
|
||||
|
||||
Задаем обьем полива 60 отсчетов счетчика воды (если счетчик не сконфигурирован - 60 секунд)
|
||||
|
||||
Контроллер должен передать это значение в выходной топик ```root/name/s_out/sprinkrer/garden/set``` и оно будет восстановлено при перезагрузке контроллера
|
||||
|
||||
отработанный обьем воды или время будет сохраняться в параметре "val" каждой зоны (параметр будет автоматически увеличиваться при работе зоны, передаваться в соответствующий зоне топик для мониторинга и восстановления в случае перезагрузки контроллера)
|
||||
|
||||
**Пример топика:** ```root/s_out/sprinkler/garden/val```
|
||||
|
||||
Когда данный параметр достигнет значения, заданного в параметре "set" контроллер завершит полив данной зоны и перейдет к следующей.
|
||||
|
||||
Для сброса счетчиков можно использовать как непосредственную установку значения параметра "val" для каждой зоны так и команду RESET, отправленную в нужную зону или в объект sprinkler через суффикс /cmd.
|
||||
|
||||
В последнем случае, контроллер итерационно сбросит счетчики в значение 0 для каждой зоны полива.
|
||||
|
||||
**Пример:** ```root/name/sprinkler/cmd -> RESET```
|
||||
|
||||
|
||||
## Управление
|
||||
|
||||
### Включение/выключение полива конкретной зоны:
|
||||
|
||||
**Включить** ```root/name/sprinkler/garden/cmd -> ON```
|
||||
|
||||
**Выключить** ```root/name/sprinkler/garden/cmd -> OFF```
|
||||
|
||||
|
||||
|
||||
### Включение/выключение цикла полива:
|
||||
|
||||
**Включить** ```root/name/sprinkler/cmd -> ON```
|
||||
Система начнет или продолжит цикл полива, переходя от зоны к зоне по мере завершения работы с каждой предыдущей зоной. После завершения работы со всеми зонами, sprinkler перейдет в состояние OFF
|
||||
|
||||
Перед включением полива, система убедится что бак наполнен или до-наполнит его до максимума из водопровода.
|
||||
|
||||
|
||||
|
||||
**Выключить** ```root/name/sprinkler/cmd -> OFF```
|
||||
Система немедленно остановит текущий цикл полива (закроет клапаны зон, выключит насос полива)
|
||||
|
||||
|
||||
Аналогично, будут работать команды XON и XOFF, с одним исключением, что команда XON может быть запрещена и игнорироваться если активирован режим DISABLE. Это базовая функция контроллера и не относится к функционалу данного модуля. Но может быть использована, например, для запрета полива на определенное время после выпадения осадков
|
||||
|
||||
**Пример**
|
||||
|
||||
```
|
||||
root/name/sprinkler/ctrl -> DISABLE
|
||||
root/name/sprinkler/cmd -> XON //Будет проигнорировано
|
||||
|
||||
root/name/sprinkler/ctrl -> ENABLE
|
||||
root/name/sprinkler/cmd -> XON //А вот теперь сработает
|
||||
|
||||
```
|
||||
|
||||
Даже в выключенном состоянии (OFF) , система полива работает в дежурном режиме, поддерживая максимальный уровень воды в баке за счет немедленной перекачки из дренажного колодца
|
||||
|
||||
При попытке включения системы после завершения дневного задания по поливу всех зон (параметр val для всех зон достиг параметра set), система сразу перейдет в состояние OFF
|
||||
|
||||
|
||||
|
||||
### Полная блокировка системы полива (в зимнее время)
|
||||
Ддя перевода канала полива в полностью заблокированное состояние и обратно импользуется системная команда FREEZE/UNFREEZE соответственно
|
||||
|
||||
В режиме FREEZE полностью заблокирована обработка всех команд, кроме UNFREEZE, заблокирован автомат пополнения бака из дренажного насоса и выключены насосы и все клапана
|
||||
|
||||
Рекомендуется задать флаг FREEZE в конфигурации канала (см документ ...) , чтобы избежать разблокировки при утере значений топика /clrl и перезагрузки системы
|
||||
|
||||
Также, на вход /val обЪекта sprinkler можно подать значение уличной температуры. И если значения будут ниже нуля, система автоматически перейдет в режим FREEZE
|
||||
|
||||
**Пример**
|
||||
|
||||
```
|
||||
root/name/sprinkler/ctrl -> FREEZE
|
||||
root/name/sprinkrer/cmd -> ON //Будет проигнорировано
|
||||
|
||||
root/name/sprinkler/ctrl -> UNFREEZE
|
||||
root/name/sprinkrer/cmd -> ON //А вот теперь сработает
|
||||
|
||||
root/name/sprinkler/val -> -1 //система перейдет в режим FREEZE
|
||||
|
||||
|
||||
```
|
||||
|
||||
### Передача статусных значений
|
||||
|
||||
|
||||
**Примеры выдачи в топики:**
|
||||
|
||||
```
|
||||
root/s_out/sprinkler/$fbPump - ON/OFF признак того, что включен основной насос (от датчика тока)
|
||||
root/s_out/sprinkler/$fbDren - ON/OFF признак того, что включен дренажный насос (от датчика тока)
|
||||
root/s_out/sprinkler/$state - состояние конечного автомата Системы налива воды
|
||||
root/s_out/sprinkler/$wMax - ON/OFF достигнут максимум воды в баке (от поплавкового датчика)
|
||||
root/s_out/sprinkler/$wMin - ON/OFF достигнут минимум воды в баке (от поплавкового датчика)
|
||||
root/s_out/sprinkler/$rDren - ON/OFF включено реле дренажного насоса
|
||||
root/s_out/sprinkler/$rPump - ON/OFF включено реле основного насоса
|
||||
root/s_out/sprinkler/set - значение счетчика воды (восстанавливается при перезагрузке из данного топика)
|
||||
root/s_out/sprinkler/$vIN - ON/OFF - признак открытия клапана налива бака из водопровода
|
||||
|
||||
root/s_out/sprinkler/garden/set - требуемый обьем (или время) полива зоны
|
||||
root/s_out/sprinkler/garden/cmd - ON или OFF - признак включения полива зоны
|
||||
root/s_out/sprinkler/garden/$state - ON или OFF - признак того что зона поливается в настоящее время
|
||||
root/s_out/sprinkler/garden/val - текущее время или обьем полива данной зоны
|
||||
|
||||
```
|
||||
@@ -68,6 +68,8 @@ e-mail anklimov@gmail.com
|
||||
#include "modules/out_humidifier.h"
|
||||
#endif
|
||||
|
||||
#include "modules/out_sprinkler.h"
|
||||
|
||||
#ifdef CANDRV
|
||||
#include <candriver.h>
|
||||
extern canDriver LHCAN;
|
||||
@@ -2886,6 +2888,12 @@ switch (itemType)
|
||||
break;
|
||||
#endif
|
||||
|
||||
#ifdef SPRINKLER_ENABLE
|
||||
case CH_SPRINKLER:
|
||||
driver = new out_sprinkler ;
|
||||
break;
|
||||
#endif
|
||||
|
||||
#ifndef COUNTER_DISABLE
|
||||
case CH_COUNTER:
|
||||
driver = new out_counter ;
|
||||
|
||||
@@ -68,7 +68,8 @@ const suffixstr suffix_P[] PROGMEM =
|
||||
#define CH_COUNTER 20
|
||||
#define CH_HUMIDIFIER 21
|
||||
#define CH_MERCURY 22
|
||||
#define CH_MAX 22
|
||||
#define CH_SPRINKLER 23
|
||||
#define CH_MAX 23
|
||||
|
||||
#define POLLING_SLOW 1
|
||||
#define POLLING_FAST 2
|
||||
|
||||
@@ -2717,6 +2717,12 @@ infoSerial<<F("\n(+)MERCURY");
|
||||
infoSerial<<F("\n(-)MERCURY");
|
||||
#endif
|
||||
|
||||
#ifdef SPRINKLER_ENABLE
|
||||
infoSerial<<F("\n(+)SPRINKLER");
|
||||
#else
|
||||
infoSerial<<F("\n(-)SPRINKLER");
|
||||
#endif
|
||||
|
||||
#ifdef CANDRV
|
||||
infoSerial<<F("\n(+)CAN");
|
||||
#else
|
||||
|
||||
@@ -398,8 +398,8 @@ int out_Multivent::Poll(short cause)
|
||||
|
||||
{
|
||||
resetBoost();
|
||||
if (autoRequested) sendACcmd(itemCmd().Cmd(CMD_AUTO));
|
||||
else if (ventRequested) sendACcmd(itemCmd().Cmd(CMD_FAN));
|
||||
if (autoRequested && lastACfan>0) sendACcmd(itemCmd().Cmd(CMD_AUTO));
|
||||
else if (ventRequested && lastACfan>0) sendACcmd(itemCmd().Cmd(CMD_FAN));
|
||||
else noFurtherModes = true; //No AUTO or FAN mode requested - so we can skip sending command to AC at all and save some energy on it
|
||||
}
|
||||
|
||||
|
||||
702
lighthub/modules/out_sprinkler.cpp
Normal file
702
lighthub/modules/out_sprinkler.cpp
Normal file
@@ -0,0 +1,702 @@
|
||||
#ifdef SPRINKLER_ENABLE
|
||||
|
||||
#include "modules/out_sprinkler.h"
|
||||
#include "Arduino.h"
|
||||
#include "options.h"
|
||||
#include "Streaming.h"
|
||||
|
||||
#include "item.h"
|
||||
#include "main.h"
|
||||
#include "utils.h"
|
||||
|
||||
bool out_sprinkler::getConfig()
|
||||
{
|
||||
gatesObj = NULL;
|
||||
vinPin = drenPin = pumpPin = -1;
|
||||
wMaxPin = wMinPin = fbDrenPin = fbPumpPin = wCtrPin = -1;
|
||||
lastWctrState = false;
|
||||
|
||||
if (!item || !item->itemArg) return false;
|
||||
|
||||
aJsonObject * arg = item->itemArg;
|
||||
if (arg->type == aJson_Array && aJson.getArraySize(arg) > 1)
|
||||
{
|
||||
aJsonObject * second = aJson.getArrayItem(arg, 1);
|
||||
if (second && second->type == aJson_Object) gatesObj = second;
|
||||
}
|
||||
else if (arg->type == aJson_Object)
|
||||
{
|
||||
gatesObj = arg;
|
||||
}
|
||||
|
||||
if (!gatesObj) return false;
|
||||
|
||||
aJsonObject * rootCfg = aJson.getObjectItem(gatesObj, "");
|
||||
if (!rootCfg) rootCfg = gatesObj;
|
||||
|
||||
vinPin = getIntFromJson(rootCfg, "vIn", -1);
|
||||
drenPin = getIntFromJson(rootCfg, "rDren", -1);
|
||||
pumpPin = getIntFromJson(rootCfg, "rPump", -1);
|
||||
wMaxPin = getIntFromJson(rootCfg, "wMax", -1);
|
||||
wMinPin = getIntFromJson(rootCfg, "wMin", -1);
|
||||
fbDrenPin = getIntFromJson(rootCfg, "fbDren", -1);
|
||||
fbPumpPin = getIntFromJson(rootCfg, "fbPump", -1);
|
||||
wCtrPin = getIntFromJson(rootCfg, "wCtr", -1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool isValidControlPin(short pin)
|
||||
{
|
||||
return (pin >= 0 && pin < PINS_COUNT && !isProtectedPin(pin));
|
||||
}
|
||||
|
||||
void out_sprinkler::setOutput(short pin, bool value)
|
||||
{
|
||||
if (!isValidControlPin(pin)) return;
|
||||
digitalWrite(pin, value ? HIGH : LOW);
|
||||
}
|
||||
|
||||
int out_sprinkler::Setup()
|
||||
{
|
||||
abstractOut::Setup();
|
||||
|
||||
if (!getConfig())
|
||||
{
|
||||
debugSerial << F("SPRINKLER: config failed") << endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (isValidControlPin(vinPin)) { pinMode(vinPin, OUTPUT); digitalWrite(vinPin, LOW); }
|
||||
if (isValidControlPin(drenPin)) { pinMode(drenPin, OUTPUT); digitalWrite(drenPin, LOW); }
|
||||
if (isValidControlPin(pumpPin)) { pinMode(pumpPin, OUTPUT); digitalWrite(pumpPin, LOW); }
|
||||
|
||||
aJsonObject * zone = gatesObj->child;
|
||||
while (zone)
|
||||
{
|
||||
if (zone->name && *zone->name && zone->type == aJson_Object)
|
||||
{
|
||||
short pin = getIntFromJson(zone, "pin", -1);
|
||||
if (isValidControlPin(pin)) { pinMode(pin, OUTPUT); digitalWrite(pin, LOW); }
|
||||
getCreateObject(zone, "cmd", (long)CMD_OFF);
|
||||
getCreateObject(zone, "val", (long)0);
|
||||
getCreateObject(zone, "set", (long)0);
|
||||
getCreateObject(zone, "@active", (long)0);
|
||||
}
|
||||
zone = zone->next;
|
||||
}
|
||||
|
||||
getCreateObject(gatesObj, "@state", (long)SP_UNKNOWN);
|
||||
getCreateObject(gatesObj, "@timer", (long)0);
|
||||
getCreateObject(gatesObj, "@flowTimer", (long)0);
|
||||
getCreateObject(gatesObj, "@wCtrLast", (long)0);
|
||||
|
||||
if (wCtrPin >= 0 && wCtrPin < PINS_COUNT) pinMode(wCtrPin, INPUT);
|
||||
if (wMaxPin >= 0 && wMaxPin < PINS_COUNT) pinMode(wMaxPin, INPUT);
|
||||
if (wMinPin >= 0 && wMinPin < PINS_COUNT) pinMode(wMinPin, INPUT);
|
||||
if (fbDrenPin >= 0 && fbDrenPin < PINS_COUNT) pinMode(fbDrenPin, INPUT);
|
||||
if (fbPumpPin >= 0 && fbPumpPin < PINS_COUNT) pinMode(fbPumpPin, INPUT);
|
||||
|
||||
lastWctrState = (wCtrPin >= 0) ? getPinVal(wCtrPin) : false;
|
||||
|
||||
item->setExt(millisNZ());
|
||||
setStatus(CST_INITIALIZED);
|
||||
notifyState(SP_UNKNOWN);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int out_sprinkler::Stop()
|
||||
{
|
||||
debugSerial << F("SPRINKLER: stop") << endl;
|
||||
turnOffAllZones();
|
||||
pump(false);
|
||||
setOutput(vinPin, false);
|
||||
setOutput(drenPin, false);
|
||||
setOutput(pumpPin, false);
|
||||
setStatus(CST_UNKNOWN);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int out_sprinkler::Status()
|
||||
{
|
||||
if (!item || !gatesObj) return 0;
|
||||
|
||||
bool wMax = (wMaxPin >= 0) ? getPinVal(wMaxPin) : false;
|
||||
bool wMin = (wMinPin >= 0) ? getPinVal(wMinPin) : false;
|
||||
bool fbDren = (fbDrenPin >= 0) ? getPinVal(fbDrenPin) : false;
|
||||
bool fbPump = (fbPumpPin >= 0) ? getPinVal(fbPumpPin) : false;
|
||||
|
||||
publishBooleanState("$wMax", wMax);
|
||||
publishBooleanState("$wMin", wMin);
|
||||
publishBooleanState("$fbDren", fbDren);
|
||||
publishBooleanState("$fbPump", fbPump);
|
||||
|
||||
int state = getIntFromJson(gatesObj, "@state", SP_UNKNOWN);
|
||||
publishNumericState("$state", state);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool out_sprinkler::isFreeze()
|
||||
{
|
||||
if (!item) return false;
|
||||
return ((item->getFlag(FLAG_FREEZED) & FLAG_FREEZED) == FLAG_FREEZED);
|
||||
}
|
||||
|
||||
bool out_sprinkler::isNeedPump(bool steelNeed)
|
||||
{
|
||||
if (!gatesObj) return false;
|
||||
if (!steelNeed && (!item || item->getCmd() != CMD_ON)) return false;
|
||||
|
||||
aJsonObject * zone = gatesObj->child;
|
||||
while (zone)
|
||||
{
|
||||
if (zone->name && *zone->name && zone->type == aJson_Object)
|
||||
{
|
||||
if (getIntFromJson(zone, "@active", 0)) return true;
|
||||
int cmd = getIntFromJson(zone, "cmd", CMD_OFF);
|
||||
if (cmd == CMD_ON)
|
||||
{
|
||||
long setVal = getIntFromJson(zone, "set", 0);
|
||||
long valVal = getIntFromJson(zone, "val", 0);
|
||||
if (valVal < setVal) return true;
|
||||
}
|
||||
}
|
||||
zone = zone->next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void out_sprinkler::pump(bool state)
|
||||
{
|
||||
if (!isValidControlPin(pumpPin)) return;
|
||||
setOutput(pumpPin, state);
|
||||
publishBooleanState("$rPump", state);
|
||||
}
|
||||
|
||||
void out_sprinkler::turnOffAllZones()
|
||||
{
|
||||
if (!gatesObj) return;
|
||||
aJsonObject * zone = gatesObj->child;
|
||||
while (zone)
|
||||
{
|
||||
if (zone->name && *zone->name && zone->type == aJson_Object)
|
||||
{
|
||||
short pin = getIntFromJson(zone, "pin", -1);
|
||||
setOutput(pin, false);
|
||||
if (getIntFromJson(zone, "@active", 0))
|
||||
{
|
||||
setZoneActive(zone, false);
|
||||
}
|
||||
}
|
||||
zone = zone->next;
|
||||
}
|
||||
}
|
||||
|
||||
void out_sprinkler::turnOffValves()
|
||||
{
|
||||
turnOffAllZones();
|
||||
setOutput(vinPin, false);
|
||||
setOutput(drenPin, false);
|
||||
}
|
||||
|
||||
void out_sprinkler::notifyState(short state)
|
||||
{
|
||||
if (!gatesObj) return;
|
||||
aJsonObject * stateObj = getCreateObject(gatesObj, "@state", (long)state);
|
||||
if (stateObj) stateObj->valueint = state;
|
||||
publishNumericState("$state", state);
|
||||
}
|
||||
|
||||
int out_sprinkler::shutdown(sprinklerState nextState)
|
||||
{
|
||||
if (!gatesObj) return 0;
|
||||
|
||||
switch (nextState)
|
||||
{
|
||||
case SP_OFF:
|
||||
case SP_FULL:
|
||||
setOutput(drenPin, false);
|
||||
setOutput(vinPin, false);
|
||||
break;
|
||||
case SP_DREN_ON:
|
||||
case SP_DREN_OPERATE:
|
||||
setOutput(drenPin, true);
|
||||
setOutput(vinPin, false);
|
||||
break;
|
||||
case SP_VIN:
|
||||
setOutput(vinPin, true);
|
||||
setOutput(drenPin, false);
|
||||
break;
|
||||
case SP_DREN_EMPTY:
|
||||
setOutput(drenPin, false);
|
||||
setOutput(vinPin, false);
|
||||
break;
|
||||
case SP_FAULT_VIN:
|
||||
setOutput(vinPin, false);
|
||||
break;
|
||||
case SP_FAULT_DREN:
|
||||
setOutput(drenPin, false);
|
||||
break;
|
||||
case SP_UNKNOWN:
|
||||
setOutput(drenPin, false);
|
||||
setOutput(vinPin, false);
|
||||
break;
|
||||
}
|
||||
|
||||
publishBooleanState("$rDren", nextState == SP_DREN_ON || nextState == SP_DREN_OPERATE);
|
||||
publishBooleanState("$vIN", nextState == SP_VIN);
|
||||
notifyState(nextState);
|
||||
return 1;
|
||||
}
|
||||
|
||||
inline aJsonObject * out_sprinkler::getZone(const char * name)
|
||||
{
|
||||
if (!gatesObj || !name || !*name) return NULL;
|
||||
aJsonObject * zone = aJson.getObjectItem(gatesObj, name);
|
||||
if (zone && zone->type == aJson_Object) return zone;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
inline aJsonObject * out_sprinkler::findNextZone()
|
||||
{
|
||||
if (!gatesObj) return NULL;
|
||||
|
||||
aJsonObject * zone = gatesObj->child;
|
||||
while (zone)
|
||||
{
|
||||
if (zone->name && *zone->name && zone->type == aJson_Object)
|
||||
{
|
||||
if (getIntFromJson(zone, "@active", 0)) 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);
|
||||
long valVal = getIntFromJson(zone, "val", 0);
|
||||
if (cmd == CMD_ON && valVal < setVal) return zone;
|
||||
}
|
||||
zone = zone->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void out_sprinkler::setZoneActive(aJsonObject * zone, bool active)
|
||||
{
|
||||
if (!zone) return;
|
||||
setValToJson(zone, "@active", (long)(active ? 1 : 0));
|
||||
|
||||
char subItem[48];
|
||||
snprintf(subItem, sizeof(subItem), "%s/$state", zone->name);
|
||||
item->SendStatusImmediate(itemCmd().Cmd(active ? CMD_ON : CMD_OFF).setSuffix(S_CMD), FLAG_COMMAND, subItem);
|
||||
}
|
||||
|
||||
void out_sprinkler::updateZoneValue(aJsonObject * zone, long value)
|
||||
{
|
||||
if (!zone) return;
|
||||
long current = getIntFromJson(zone, "val", 0);
|
||||
current += value;
|
||||
setValToJson(zone, "val", current);
|
||||
item->SendStatusImmediate(itemCmd().Int(current).setSuffix(S_VAL), FLAG_PARAMETERS, zone->name);
|
||||
}
|
||||
|
||||
void out_sprinkler::publishBooleanState(const char * subItem, bool state)
|
||||
{
|
||||
if (!item) return;
|
||||
item->SendStatusImmediate(itemCmd().Cmd(state ? CMD_ON : CMD_OFF).setSuffix(S_CMD), FLAG_COMMAND, (char *)subItem);
|
||||
}
|
||||
|
||||
void out_sprinkler::publishNumericState(const char * subItem, long value)
|
||||
{
|
||||
if (!item) return;
|
||||
item->SendStatusImmediate(itemCmd().Int(value).setSuffix(S_SET), FLAG_PARAMETERS, (char *)subItem);
|
||||
}
|
||||
|
||||
int out_sprinkler::Poll(short cause)
|
||||
{
|
||||
if (!item || !gatesObj) return 0;
|
||||
|
||||
bool freeze = isFreeze();
|
||||
bool wMax = (wMaxPin >= 0) ? getPinVal(wMaxPin) : false;
|
||||
bool wMin = (wMinPin >= 0) ? getPinVal(wMinPin) : false;
|
||||
bool fbDren = (fbDrenPin >= 0) ? getPinVal(fbDrenPin) : false;
|
||||
bool fbPump = (fbPumpPin >= 0) ? getPinVal(fbPumpPin) : false;
|
||||
|
||||
publishBooleanState("$wMax", wMax);
|
||||
publishBooleanState("$wMin", wMin);
|
||||
publishBooleanState("$fbDren", fbDren);
|
||||
publishBooleanState("$fbPump", fbPump);
|
||||
|
||||
uint32_t now = millisNZ();
|
||||
int state = getIntFromJson(gatesObj, "@state", SP_UNKNOWN);
|
||||
uint32_t timer = (uint32_t)getIntFromJson(gatesObj, "@timer", 0);
|
||||
|
||||
if (freeze)
|
||||
{
|
||||
shutdown(SP_OFF);
|
||||
turnOffValves();
|
||||
pump(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case SP_UNKNOWN:
|
||||
case SP_OFF:
|
||||
if (wMax)
|
||||
{
|
||||
state = SP_FULL;
|
||||
getCreateObject(gatesObj, "@state", (long)state)->valueint = state;
|
||||
notifyState(state);
|
||||
}
|
||||
else
|
||||
{
|
||||
state = SP_DREN_ON;
|
||||
getCreateObject(gatesObj, "@timer", (long)now)->valueint = now;
|
||||
getCreateObject(gatesObj, "@state", (long)state)->valueint = state;
|
||||
shutdown(SP_DREN_ON);
|
||||
}
|
||||
break;
|
||||
|
||||
case SP_DREN_ON:
|
||||
if (fbDren)
|
||||
{
|
||||
state = SP_DREN_OPERATE;
|
||||
getCreateObject(gatesObj, "@timer", (long)now)->valueint = now;
|
||||
getCreateObject(gatesObj, "@state", (long)state)->valueint = state;
|
||||
shutdown(SP_DREN_OPERATE);
|
||||
}
|
||||
else if (isTimeOver(timer, now, DRENAGE_TIME))
|
||||
{
|
||||
state = SP_DREN_EMPTY;
|
||||
getCreateObject(gatesObj, "@state", (long)state)->valueint = state;
|
||||
shutdown(SP_DREN_EMPTY);
|
||||
}
|
||||
break;
|
||||
|
||||
case SP_DREN_OPERATE:
|
||||
if (!fbDren)
|
||||
{
|
||||
state = SP_DREN_EMPTY;
|
||||
getCreateObject(gatesObj, "@state", (long)state)->valueint = state;
|
||||
shutdown(SP_DREN_EMPTY);
|
||||
}
|
||||
else if (wMax)
|
||||
{
|
||||
state = SP_FULL;
|
||||
getCreateObject(gatesObj, "@state", (long)state)->valueint = state;
|
||||
shutdown(SP_FULL);
|
||||
}
|
||||
else if (isTimeOver(timer, now, 1200000UL))
|
||||
{
|
||||
state = SP_FAULT_DREN;
|
||||
getCreateObject(gatesObj, "@state", (long)state)->valueint = state;
|
||||
shutdown(SP_FAULT_DREN);
|
||||
}
|
||||
break;
|
||||
|
||||
case SP_DREN_EMPTY:
|
||||
if (wMax)
|
||||
{
|
||||
state = SP_FULL;
|
||||
getCreateObject(gatesObj, "@state", (long)state)->valueint = state;
|
||||
shutdown(SP_FULL);
|
||||
}
|
||||
else if (item->getCmd() == CMD_ON)
|
||||
{
|
||||
state = SP_VIN;
|
||||
getCreateObject(gatesObj, "@timer", (long)now)->valueint = now;
|
||||
getCreateObject(gatesObj, "@state", (long)state)->valueint = state;
|
||||
shutdown(SP_VIN);
|
||||
}
|
||||
break;
|
||||
|
||||
case SP_VIN:
|
||||
if (fbDren)
|
||||
{
|
||||
state = SP_DREN_OPERATE;
|
||||
getCreateObject(gatesObj, "@timer", (long)now)->valueint = now;
|
||||
getCreateObject(gatesObj, "@state", (long)state)->valueint = state;
|
||||
shutdown(SP_DREN_OPERATE);
|
||||
}
|
||||
else if (wMax)
|
||||
{
|
||||
state = SP_FULL;
|
||||
getCreateObject(gatesObj, "@state", (long)state)->valueint = state;
|
||||
shutdown(SP_FULL);
|
||||
}
|
||||
else if (isTimeOver(timer, now, 1200000UL))
|
||||
{
|
||||
state = SP_FAULT_VIN;
|
||||
getCreateObject(gatesObj, "@state", (long)state)->valueint = state;
|
||||
shutdown(SP_FAULT_VIN);
|
||||
}
|
||||
break;
|
||||
|
||||
case SP_FULL:
|
||||
if (!wMax)
|
||||
{
|
||||
state = SP_DREN_ON;
|
||||
getCreateObject(gatesObj, "@timer", (long)now)->valueint = now;
|
||||
getCreateObject(gatesObj, "@state", (long)state)->valueint = state;
|
||||
shutdown(SP_DREN_ON);
|
||||
}
|
||||
break;
|
||||
|
||||
case SP_FAULT_VIN:
|
||||
if (wMax)
|
||||
{
|
||||
state = SP_FULL;
|
||||
getCreateObject(gatesObj, "@state", (long)state)->valueint = state;
|
||||
shutdown(SP_FULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case SP_FAULT_DREN:
|
||||
if (wMax)
|
||||
{
|
||||
state = SP_FULL;
|
||||
getCreateObject(gatesObj, "@state", (long)state)->valueint = state;
|
||||
shutdown(SP_FULL);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
bool tankReady = (state == SP_FULL || wMax);
|
||||
bool needPump = false;
|
||||
aJsonObject * currentZone = NULL;
|
||||
|
||||
if (item->getCmd() == CMD_ON && !tankReady)
|
||||
{
|
||||
turnOffAllZones();
|
||||
pump(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (item->getCmd() == CMD_ON && tankReady)
|
||||
{
|
||||
currentZone = findNextZone();
|
||||
if (currentZone)
|
||||
{
|
||||
long setVal = getIntFromJson(currentZone, "set", 0);
|
||||
long valVal = getIntFromJson(currentZone, "val", 0);
|
||||
|
||||
if (!getIntFromJson(currentZone, "@active", 0))
|
||||
{
|
||||
turnOffAllZones();
|
||||
setZoneActive(currentZone, true);
|
||||
short zonePin = getIntFromJson(currentZone, "pin", -1);
|
||||
setOutput(zonePin, true);
|
||||
getCreateObject(gatesObj, "@flowTimer", (long)now)->valueint = now;
|
||||
}
|
||||
|
||||
if (wCtrPin >= 0)
|
||||
{
|
||||
bool curr = getPinVal(wCtrPin);
|
||||
if (curr && !lastWctrState)
|
||||
{
|
||||
updateZoneValue(currentZone, 1);
|
||||
}
|
||||
lastWctrState = curr;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t flowTimer = (uint32_t)getIntFromJson(gatesObj, "@flowTimer", now);
|
||||
if (isTimeOver(flowTimer, now, 1000UL))
|
||||
{
|
||||
updateZoneValue(currentZone, 1);
|
||||
getCreateObject(gatesObj, "@flowTimer", (long)now)->valueint = now;
|
||||
}
|
||||
}
|
||||
|
||||
long setVal2 = getIntFromJson(currentZone, "set", 0);
|
||||
long valVal2 = getIntFromJson(currentZone, "val", 0);
|
||||
if (setVal2 > 0 && valVal2 >= setVal2)
|
||||
{
|
||||
setOutput(getIntFromJson(currentZone, "pin", -1), 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 (!needPump)
|
||||
{
|
||||
pump(false);
|
||||
if (item->getCmd() == CMD_ON)
|
||||
{
|
||||
aJsonObject * resultZone = findNextZone();
|
||||
if (!resultZone)
|
||||
{
|
||||
item->setCmd(CMD_OFF);
|
||||
item->SendStatus(FLAG_COMMAND);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pump(true);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int out_sprinkler::Ctrl(itemCmd cmd, char* subItem, bool toExecute, bool authorized)
|
||||
{
|
||||
if (!item || !gatesObj) return 0;
|
||||
int suffixCode = cmd.isCommand() ? S_CMD : cmd.getSuffix();
|
||||
|
||||
if (subItem && *subItem)
|
||||
{
|
||||
aJsonObject * zone = getZone(subItem);
|
||||
if (!zone) return 0;
|
||||
|
||||
switch (suffixCode)
|
||||
{
|
||||
case S_SET:
|
||||
if (toExecute)
|
||||
{
|
||||
long value = cmd.getInt();
|
||||
setValToJson(zone, "set", value);
|
||||
item->SendStatusImmediate(itemCmd().Int(value).setSuffix(S_SET), FLAG_PARAMETERS, subItem);
|
||||
}
|
||||
return 1;
|
||||
|
||||
case S_VAL:
|
||||
if (toExecute)
|
||||
{
|
||||
long value = cmd.getInt();
|
||||
setValToJson(zone, "val", value);
|
||||
item->SendStatusImmediate(itemCmd().Int(value).setSuffix(S_VAL), FLAG_PARAMETERS, subItem);
|
||||
}
|
||||
return 1;
|
||||
|
||||
case S_CMD:
|
||||
default:
|
||||
switch (cmd.getCmd())
|
||||
{
|
||||
case CMD_ON:
|
||||
if (toExecute)
|
||||
{
|
||||
setValToJson(zone, "cmd", (long)CMD_ON);
|
||||
item->SendStatusImmediate(itemCmd().Cmd(CMD_ON).setSuffix(S_CMD), FLAG_COMMAND, subItem);
|
||||
}
|
||||
return 1;
|
||||
|
||||
case CMD_OFF:
|
||||
if (toExecute)
|
||||
{
|
||||
setValToJson(zone, "cmd", (long)CMD_OFF);
|
||||
setZoneActive(zone, false);
|
||||
setOutput(getIntFromJson(zone, "pin", -1), false);
|
||||
item->SendStatusImmediate(itemCmd().Cmd(CMD_OFF).setSuffix(S_CMD), FLAG_COMMAND, subItem);
|
||||
}
|
||||
return 1;
|
||||
|
||||
case CMD_RESET:
|
||||
if (toExecute)
|
||||
{
|
||||
setValToJson(zone, "val", (long)0);
|
||||
item->SendStatusImmediate(itemCmd().Int(0).setSuffix(S_VAL), FLAG_PARAMETERS, subItem);
|
||||
}
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (suffixCode)
|
||||
{
|
||||
case S_CMD:
|
||||
switch (cmd.getCmd())
|
||||
{
|
||||
case CMD_ON:
|
||||
return 1;
|
||||
|
||||
case CMD_OFF:
|
||||
turnOffAllZones();
|
||||
pump(false);
|
||||
return 1;
|
||||
|
||||
case CMD_RESET:
|
||||
{
|
||||
aJsonObject * zone = gatesObj->child;
|
||||
while (zone)
|
||||
{
|
||||
if (zone->name && *zone->name && zone->type == aJson_Object)
|
||||
{
|
||||
setValToJson(zone, "val", (long)0);
|
||||
item->SendStatusImmediate(itemCmd().Int(0).setSuffix(S_VAL), FLAG_PARAMETERS, zone->name);
|
||||
}
|
||||
zone = zone->next;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_SET:
|
||||
if (toExecute)
|
||||
{
|
||||
long value = cmd.getInt();
|
||||
if (value < 0)
|
||||
{
|
||||
item->setFlag(FLAG_FREEZED);
|
||||
item->SendStatus(FLAG_FLAGS);
|
||||
}
|
||||
else if (isFreeze())
|
||||
{
|
||||
item->clearFlag(FLAG_FREEZED);
|
||||
item->SendStatus(FLAG_FLAGS);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
|
||||
case S_VAL:
|
||||
if (toExecute)
|
||||
{
|
||||
long value = cmd.getInt();
|
||||
if (value < 0)
|
||||
{
|
||||
item->setFlag(FLAG_FREEZED);
|
||||
item->SendStatus(FLAG_FLAGS);
|
||||
}
|
||||
else if (isFreeze())
|
||||
{
|
||||
item->clearFlag(FLAG_FREEZED);
|
||||
item->SendStatus(FLAG_FLAGS);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int out_sprinkler::getChanType()
|
||||
{
|
||||
return CH_RELAY;
|
||||
}
|
||||
|
||||
#endif // SPRINKLER_ENABLE
|
||||
59
lighthub/modules/out_sprinkler.h
Normal file
59
lighthub/modules/out_sprinkler.h
Normal file
@@ -0,0 +1,59 @@
|
||||
|
||||
#pragma once
|
||||
#include "options.h"
|
||||
#ifdef SPRINKLER_ENABLE
|
||||
|
||||
#include <abstractout.h>
|
||||
#include <item.h>
|
||||
|
||||
#define DRENAGE_TIME 10000
|
||||
|
||||
|
||||
enum sprinklerState {
|
||||
SP_UNKNOWN = 0,
|
||||
SP_OFF = 1,
|
||||
SP_DREN_ON = 2,
|
||||
SP_DREN_OPERATE = 3,
|
||||
SP_DREN_EMPTY = 4,
|
||||
SP_VIN = 5,
|
||||
SP_FULL = 6,
|
||||
SP_FAULT_VIN = -1,
|
||||
SP_FAULT_DREN = -2
|
||||
};
|
||||
|
||||
class out_sprinkler : public abstractOut {
|
||||
public:
|
||||
|
||||
//out_sprinkler(){ /*NO getConfig() here due Poll() optimization*/ };
|
||||
bool getConfig();
|
||||
|
||||
int Setup() override;
|
||||
int Poll(short cause) override;
|
||||
int Stop() override;
|
||||
int Status() override;
|
||||
|
||||
int getChanType() override;
|
||||
int Ctrl(itemCmd cmd, char* subItem=NULL, bool toExecute=true, bool authorized = false) override;
|
||||
|
||||
protected:
|
||||
aJsonObject * gatesObj;
|
||||
short vinPin, drenPin, pumpPin;
|
||||
short wMaxPin, wMinPin, fbDrenPin, fbPumpPin, wCtrPin;
|
||||
bool lastWctrState;
|
||||
|
||||
void pump(bool state);
|
||||
void setOutput(short pin, bool value);
|
||||
bool isNeedPump(bool steelNeed=false);
|
||||
void turnOffValves();
|
||||
void turnOffAllZones();
|
||||
aJsonObject * getZone(const char * name);
|
||||
aJsonObject * findNextZone();
|
||||
void setZoneActive(aJsonObject * zone, bool active);
|
||||
void updateZoneValue(aJsonObject * zone, long value);
|
||||
void publishBooleanState(const char * subItem, bool state);
|
||||
void publishNumericState(const char * subItem, long value);
|
||||
bool isFreeze();
|
||||
void notifyState(short state);
|
||||
int shutdown(sprinklerState nextState);
|
||||
};
|
||||
#endif
|
||||
Reference in New Issue
Block a user