3 Commits

Author SHA1 Message Date
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
4 changed files with 113 additions and 59 deletions

View File

@@ -91,11 +91,24 @@ DREN\_OPERATE|таймаут 2000 сек |FAULT_DREN|выключить rDren -
Для сброса счетчиков можно использовать как непосредственную установку значения параметра "val" для каждой зоны так и команду RESET, отправленную в нужную зону или в объект sprinkler через суффикс /cmd. Для сброса счетчиков можно использовать как непосредственную установку значения параметра "val" для каждой зоны так и команду RESET, отправленную в нужную зону или в объект sprinkler через суффикс /cmd.
В последнем случае, контроллер итерационно сбросит счетчики в значение 0 для каждой зоны полива.
А также, отключит систему полива, чтобы программа не стартовала в момент сброса счетчиков (например, в полночь)
**Пример:** ```root/name/sprinkler/cmd -> RESET``` **Пример:** ```root/name/sprinkler/cmd -> RESET```
В последнем случае, контроллер итерационно сбросит счетчики в значение 0 для каждой зоны полива.
Это инициирует старт программы полива, если система находится в состоянии ON
Команда RESET не отработает если обьект полива находится в состоянии DISABLE или FREEZE
Также, в этих режимах, в принципе, не отработает запуск полива какой-либо зоны (команды set/RESET), если для нее задано время/обьем полива.
Если обьем полива не задан (set==0) - это например водяная розетка для мытья машины, то такая зона запустится даже в режиме DISABLE
Если в процессе полива придет команда DISABLE (начался дождь) - полив прекратится, программа полива возобновится после ENABLE
## Управление ## Управление
@@ -110,7 +123,9 @@ DREN\_OPERATE|таймаут 2000 сек |FAULT_DREN|выключить rDren -
### Включение/выключение цикла полива: ### Включение/выключение цикла полива:
**Включить** ```root/name/sprinkler/cmd -> ON``` **Включить** ```root/name/sprinkler/cmd -> ON```
Система начнет или продолжит цикл полива, переходя от зоны к зоне по мере завершения работы с каждой предыдущей зоной. После завершения работы со всеми зонами, sprinkler перейдет в состояние OFF Система начнет или продолжит цикл полива, переходя от зоны к зоне по мере завершения работы с каждой предыдущей зоной. После завершения работы со всеми зонами, sprinkler НЕ перейдет в состояние OFF автоматически, но полив прекратится для тех зон, где показатель val достиг параметра set
Если не будет ни одной активной зоны - насос обесточится.
Перед включением полива, система убедится что бак наполнен или до-наполнит его до максимума из водопровода. Перед включением полива, система убедится что бак наполнен или до-наполнит его до максимума из водопровода.
@@ -162,6 +177,19 @@ root/name/sprinkler/val -> -1 //система перейдет в режим FR
``` ```
Так как команда FREEZE блокирует запуск каких либо насосов и открытие клапанов, это делает невозможным слив системы в процессе подготовки к зиме
Когда требуется слить систему, необходимо отключить дренажный насос, перекрыть водопроводный кран
Данный режим реализован при помощи команды DRY
Дополнительно к перекрытию поступления воды в бак (заблокирован автомат пополнения бака из дренажного насоса и водопровода) данная команда включает помпу.
Далее, требуется слить бак включением выбранных зон полива или через водяные розетки.
Только после осушения бака (насос выключится) - систему необходимо перевести в режим FREEZE
Перевод в этот редим сбрасывает режим DRY (а также, состояния ошибки автомата наполнения бака)
### Передача статусных значений ### Передача статусных значений
@@ -225,16 +253,16 @@ sensor:
state_topic: "edem/s_out/sprinkler/trees/val" state_topic: "edem/s_out/sprinkler/trees/val"
- name: "Поливаем юг" - name: "Поливаем юг"
state_topic: "edem/s_out/sprinkler/south/$state" state_topic: "root/s_out/sprinkler/south/$state"
- name: "Поливаем север" - name: "Поливаем север"
state_topic: "edem/s_out/sprinkler/nord/$state" state_topic: "root/s_out/sprinkler/nord/$state"
- name: "Поливаем капельно" - name: "Поливаем капельно"
state_topic: "edem/s_out/sprinkler/trees/$state" state_topic: "root/s_out/sprinkler/trees/$state"
- name: "Полив розетка статус" - name: "Полив розетка статус"
state_topic: "edem/s_out/sprinkler/outlets/$state" state_topic: "root/s_out/sprinkler/outlets/$state"
- name: "Полив блокировки" - name: "Полив блокировки"
state_topic: "root/s_out/sprinkler/ctrl" state_topic: "root/s_out/sprinkler/ctrl"

View File

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

View File

@@ -284,6 +284,10 @@ switch (state) {
strcpy(val,"FULL"); strcpy(val,"FULL");
break; break;
case SP_DRYING:
strcpy(val,"DRYING");
break;
case SP_FAULT_VIN: case SP_FAULT_VIN:
strcpy(val,"FAULT_VIN"); strcpy(val,"FAULT_VIN");
fault = 1; fault = 1;
@@ -347,6 +351,11 @@ int out_sprinkler::moveToState(sprinklerState nextState)
dren(false); dren(false);
vin(false); vin(false);
break; break;
case SP_DRYING:
dren(false);
vin(false);
pump(true);
break;
} }
//publishBooleanState("/$rDren", nextState == SP_DREN_ON || nextState == SP_DREN_OPERATE); //publishBooleanState("/$rDren", nextState == SP_DREN_ON || nextState == SP_DREN_OPERATE);
@@ -624,66 +633,71 @@ int out_sprinkler::Poll(short cause)
pump(false); pump(false);
return 0; return 0;
} }
bool isActiveZone = false;
if (item->getCmd() == CMD_ON && tankReady) if (item->getCmd() == CMD_ON && tankReady)
{ {
currentZone = findNextZone(); currentZone = findNextZone();
if (currentZone) if (currentZone)
{ {
long setVal = getIntFromJson(currentZone, "set", 0); long setVal = getIntFromJson(currentZone, "set", 0);
long valVal = getIntFromJson(currentZone, "val", 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)) if (isActiveZone)
{ {
turnOffAllZones(); if (abs(wCtrPin) < PINS_COUNT)
setZoneActive(currentZone, true); {
short zonePin = getIntFromJson(currentZone, "pin", PINS_COUNT); bool curr = readInPin(wCtrPin);
if (isValidControlPin(zonePin)) setOutput(zonePin, true); if (curr && !lastWctrlState)
setValToJson(gatesObj, "@flowTimer", (long)now); {
} 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 (abs(wCtrPin) < PINS_COUNT) if (setVal > 0 && (valVal >= setVal || item->getFlag(FLAG_DISABLED)))
{ {
bool curr = readInPin(wCtrPin); short zonePin = getIntFromJson(currentZone, "pin", PINS_COUNT);
if (curr && !lastWctrlState) if (isValidControlPin(zonePin)) setOutput(zonePin, false);
{ setZoneActive(currentZone, false);
updateZoneValue(currentZone, 1); currentZone = findNextZone();
} }
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 (currentZone)
if (setVal > 0 && valVal >= setVal) {
{ needPump = true;
short zonePin = getIntFromJson(currentZone, "pin", PINS_COUNT); }
if (isValidControlPin(zonePin)) setOutput(zonePin, 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) if (!needPump)
{ {
pump(false); pump(false);
/*
if (item->getCmd() == CMD_ON) if (item->getCmd() == CMD_ON)
{ {
aJsonObject * resultZone = findNextZone(); aJsonObject * resultZone = findNextZone();
@@ -693,6 +707,7 @@ int out_sprinkler::Poll(short cause)
item->SendStatus(FLAG_COMMAND); item->SendStatus(FLAG_COMMAND);
} }
} }
*/
} }
else else
{ {
@@ -764,6 +779,7 @@ int out_sprinkler::Ctrl(itemCmd cmd, char* subItem, bool toExecute, bool authori
return 1; return 1;
case CMD_RESET: case CMD_RESET:
if (item->getFlag(FLAG_FREEZED | FLAG_DISABLED)) return -1;
setValToJson(zone, "val", (long)0); setValToJson(zone, "val", (long)0);
if (sendStatus) if (sendStatus)
{ {
@@ -788,11 +804,18 @@ int out_sprinkler::Ctrl(itemCmd cmd, char* subItem, bool toExecute, bool authori
case CMD_OFF: case CMD_OFF:
turnOffAllZones(); turnOffAllZones();
pump(false); pump(false);
//dren(false);
return 1; return 1;
case CMD_DRY:
setValToJson(gatesObj, "@state", (long)SP_DRYING);
moveToState(SP_DRYING);
notifyState(SP_DRYING);
return 1;
case CMD_RESET: case CMD_RESET:
{ {
if (item->getFlag(FLAG_FREEZED | FLAG_DISABLED)) return -1;
aJsonObject * zone = gatesObj->child; aJsonObject * zone = gatesObj->child;
while (zone) while (zone)
{ {
@@ -806,9 +829,10 @@ int out_sprinkler::Ctrl(itemCmd cmd, char* subItem, bool toExecute, bool authori
} }
zone = zone->next; zone = zone->next;
} }
turnOffAllZones();
pump(false); // turnOffAllZones();
item->Off(); // pump(false);
// item->Off();
} }
return 1; return 1;

View File

@@ -28,6 +28,7 @@ enum sprinklerState {
SP_DREN_EMPTY = 4, SP_DREN_EMPTY = 4,
SP_VIN = 5, SP_VIN = 5,
SP_FULL = 6, SP_FULL = 6,
SP_DRYING = 7,
SP_FAULT_VIN = -1, SP_FAULT_VIN = -1,
SP_FAULT_DREN = -2 SP_FAULT_DREN = -2
}; };