diff --git a/documentation/Sprinkler_module.md b/documentation/Sprinkler_module.md index dccd8e8..978e7cb 100644 --- a/documentation/Sprinkler_module.md +++ b/documentation/Sprinkler_module.md @@ -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 + + + ``` \ No newline at end of file diff --git a/lighthub/modules/out_sprinkler.cpp b/lighthub/modules/out_sprinkler.cpp index 81f5f75..e39ddaa 100644 --- a/lighthub/modules/out_sprinkler.cpp +++ b/lighthub/modules/out_sprinkler.cpp @@ -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; diff --git a/lighthub/modules/out_sprinkler.h b/lighthub/modules/out_sprinkler.h index 2a1ef2e..dbd2acf 100644 --- a/lighthub/modules/out_sprinkler.h +++ b/lighthub/modules/out_sprinkler.h @@ -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 \ No newline at end of file