Sprinkler module released

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-05-22 22:44:36 +03:00
parent 641b314218
commit 985d058c50
3 changed files with 202 additions and 50 deletions

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,15 +80,19 @@ 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```
@@ -178,4 +183,128 @@ 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: "root/s_out/sprinkler/trees/val"
- 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
```

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:
@@ -298,7 +313,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 +321,36 @@ 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;
}
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;
}
@@ -370,7 +385,7 @@ 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;
}
@@ -443,7 +458,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 +471,7 @@ int out_sprinkler::Poll(short cause)
if (freeze)
{
shutdown(SP_OFF);
moveToState(SP_OFF);
turnOffValves();
pump(false);
return 0;
@@ -477,7 +492,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 +502,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 +517,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 +538,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 +555,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 +577,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 +586,7 @@ int out_sprinkler::Poll(short cause)
{
state = SP_FULL;
setValToJson(gatesObj, "@state", (long)state);
shutdown(SP_FULL);
moveToState(SP_FULL);
}
break;
@@ -580,7 +595,7 @@ int out_sprinkler::Poll(short cause)
{
state = SP_FULL;
setValToJson(gatesObj, "@state", (long)state);
shutdown(SP_FULL);
moveToState(SP_FULL);
}
break;
}
@@ -609,11 +624,11 @@ int out_sprinkler::Poll(short cause)
turnOffAllZones();
setZoneActive(currentZone, true);
short zonePin = getIntFromJson(currentZone, "pin", PINS_COUNT);
setOutput(zonePin, true);
if (isValidControlPin(zonePin)) setOutput(zonePin, true);
setValToJson(gatesObj, "@flowTimer", (long)now);
}
if (wCtrPin != PINS_COUNT)
if (abs(wCtrPin) < PINS_COUNT)
{
bool curr = readInPin(wCtrPin);
if (curr && !lastWctrlState)
@@ -636,7 +651,9 @@ int out_sprinkler::Poll(short cause)
if (setVal > 0 && valVal >= setVal)
{
setOutput(getIntFromJson(currentZone, "pin", PINS_COUNT), false);
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);
@@ -775,6 +792,9 @@ 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,
@@ -51,6 +53,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 +67,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