article
Автоматический полив
Сценарий algo_irrigation.py поливает три зоны по очереди одним насосом: по расписанию утром и вечером или командой «полить сейчас».
Возможности
- Три линии не пересекаются. Сначала зона 1, потом 2, потом 3. Общий насос на DO3 включается только вместе с текущей линией.
- Длительность каждой линии в минутах задаётся отдельно (по умолчанию 10 / 10 / 5).
- Расписание. Час утра и час вечера. В эту минуту, если включено «Авто», ставится очередь всех трёх линий. Один слот — один запуск (не крутит весь час).
- Полить сейчас: одна линия или все. Команда после приёма сбрасывается в «Нет».
- Запрет полива: нет воды в ёмкости, дождь, воздух холоднее порога, обрыв NTC. Идущий полив останавливается; расписание и ручной пуск в этот момент пропускаются (событие «пропуск»).
- На экране текущая линия, остаток секунд, часы насоса (до перезагрузки), температура воздуха.
Авто, ручная команда, длительности, часы утро/вечер и порог холода — с Управления.
Сферы применения
Три клапана и насос — грядки, теплица, небольшой участок, не поле с десятками секторов.
- теплица или парник с капельными линиями;
- огород, газон, питомник из двух–трёх контуров;
- ферма или дача без постоянного оператора: полив сам, в дождь и мороз не льёт, «нет воды» видно в журнале;
- совместно с баком (
algo_tank.py): один контроллер набирает ёмкость, этот — раздаёт (на двух устройствах; на одном плате не хватит DO, если совмещать как есть).
Не закрывает: учёт влажности почвы, фертигацию, полив по солнцу, больше трёх линий без правки кода и клемм, одновременный полив двух зон.
Клеммы
| Клемма | Назначение |
|---|---|
| AI0 NTC | температура воздуха |
| DO0, DO1, DO2 | линии 1–3 |
| DO3 | насос |
| DI0 НЗ | есть вода в ёмкости (норма = замкнут) |
| DI1 НО | датчик дождя (дождь = замкнут) |
Сценарий
'''
Полив теплицы / грядки, три линии по очереди.
Расписание (утро и вечер), запрет по холоду и дождю, ручной «полить сейчас».
Насос общий, линии не пересекаются.
Клеммы:
AI0 NTC температура воздуха
DO0 линия 1
DO1 линия 2
DO2 линия 3
DO3 насос
DI0 НЗ есть вода в ёмкости (True = вода есть)
DI1 НО датчик дождя (True = дождь, не поливать)
'''
from dev import Ai, Do, Di, Reg, Alarm, Event, count, webconf, run
from time import localtime
from math import isnan
from micropython import const
STEP = const(1)
ZONES = const(3)
count(Reg=12, Ai=1, Do=4, Di=2, Ao=0, Alarm=3, Event=2)
webconf(Reg=True, Pin=True, Display=False, Console=True)
rAuto = Reg(0).conf('bool', 'Авто по расписанию')
rStart = Reg(1).conf('enum', 'Полить сейчас', items=['Нет', 'Линия 1', 'Линия 2', 'Линия 3', 'Все'], default=0)
rDur1 = Reg(2).conf('uint', 'Линия 1, мин', limits=[1, 60], default=10)
rDur2 = Reg(3).conf('uint', 'Линия 2, мин', limits=[1, 60], default=10)
rDur3 = Reg(4).conf('uint', 'Линия 3, мин', limits=[1, 60], default=5)
rMornH = Reg(5).conf('uint', 'Утро, час', limits=[0, 23], default=6)
rEveH = Reg(6).conf('uint', 'Вечер, час', limits=[0, 23], default=20)
rTmin = Reg(7).conf('int', 'Не поливать ниже, °C', limits=[-5, 15], default=5)
rZone = Reg(8).conf('enum', 'Сейчас', items=['Стоп', 'Линия 1', 'Линия 2', 'Линия 3'], access='r')
rLeft = Reg(9).conf('uint', 'Осталось, с', access='r')
rHours = Reg(10).conf('uint', 'Насос, ч', access='r')
rTemp = Reg(11).conf('float', 'Воздух, °C', access='r', dec=1)
aiT = Ai(0).conf(name='Воздух', type='ntc', access='r')
doZ = [Do(0).conf(name='Линия 1', access='r'),
Do(1).conf(name='Линия 2', access='r'),
Do(2).conf(name='Линия 3', access='r')]
doPump = Do(3).conf(name='Насос', access='r')
diWater = Di(0).conf(name='Вода в ёмкости', type='nz', access='r')
diRain = Di(1).conf(name='Дождь', type='no', access='r')
aWater = Alarm(0).conf('Нет воды')
aCold = Alarm(1).conf('Слишком холодно для полива')
aSens = Alarm(2).conf('Ошибка датчика воздуха')
eRun = Event(0).conf('Полив')
eSkip = Event(1).conf('Пропуск полива')
queue = []
left = 0
lastSched = -1
pumpSec = 0
def log(msg):
t = localtime()
print('{0}/{1:02}/{2:02} {3:02}:{4:02}:{5:02} {6}'.format(
t[0], t[1], t[2], t[3], t[4], t[5], msg))
def durations():
return (rDur1.val() * 60, rDur2.val() * 60, rDur3.val() * 60)
def allOff():
for z in doZ:
z.off()
doPump.off()
rZone.val(0)
rLeft.val(0)
def stopRun(reason):
global queue, left
was = doPump.val()
allOff()
queue = []
left = 0
if was:
log('Полив стоп ({})'.format(reason))
def startQueue(zones, reason):
global queue, left
d = durations()
queue = []
for i in zones:
queue.append((i, d[i]))
if not queue:
return
log('Полив {} ({})'.format(zones, reason))
eRun.add(len(queue))
nextZone()
def nextZone():
global queue, left
allOff()
if not queue:
rZone.val(0)
rLeft.val(0)
log('Полив окончен')
return
i, sec = queue[0]
queue = queue[1:]
left = sec
doZ[i].on()
doPump.on()
rZone.val(i + 1)
rLeft.val(left)
def blocked():
t = aiT.val()
if isnan(t):
aSens.on()
rTemp.val(0)
return True, 'датчик'
aSens.off()
rTemp.val(t)
if t < rTmin.val():
aCold.on()
return True, 'холод'
aCold.off()
if not diWater.val():
aWater.on()
return True, 'нет воды'
aWater.off()
if diRain.val():
return True, 'дождь'
return False, ''
def step():
global lastSched, left, pumpSec
bad, why = blocked()
if bad and doPump.val():
stopRun(why)
if rStart.val() != 0:
cmd = rStart.val()
rStart.val(0)
if bad:
eSkip.add()
log('Ручной полив отклонён ({})'.format(why))
elif cmd == 4:
startQueue((0, 1, 2), 'вручную все')
else:
startQueue((cmd - 1,), 'вручную')
t = localtime()
hm = t[3] * 60 + t[4]
slot = -1
if hm == rMornH.val() * 60:
slot = 0
elif hm == rEveH.val() * 60:
slot = 1
if rAuto.val() and slot >= 0 and slot != lastSched:
lastSched = slot
if bad:
eSkip.add()
log('Расписание пропущено ({})'.format(why))
elif not doPump.val():
startQueue((0, 1, 2), 'расписание')
if hm not in (rMornH.val() * 60, rEveH.val() * 60):
lastSched = -1
if doPump.val():
pumpSec += 1
if pumpSec >= 3600:
pumpSec = 0
rHours.inc()
if left > 0:
left -= 1
rLeft.val(left)
if left <= 0:
nextZone()
else:
rZone.val(0)
rLeft.val(0)
allOff()
log('Старт полива')
run(step, STEP)