article
Щит серверной
Сценарий algo_server.py следит за температурой помещения и стойки, включает охлаждение, ругается на открытую дверь и пропавший шлюз в сети.
Возможности
- Два NTC: помещение и стойка. Уставка сравнивается с более горячим из двух. Если датчик стойки оборван — считается только помещение (авария датчика стойки, охлаждение не глушится).
- Гистерезис охлаждения. Выше уставки + гистерезис — кондиционер и вентилятор вкл. Ниже уставки − гистерезис — выкл.
- Перегрев (уставка + 8 °C): авария и принудительное охлаждение, даже если автоохлаждение снято (пока живо термореле ККБ).
- Дверь. Открыта — счётчик секунд на экране. Дольше 60 с — авария «дверь открыта долго». Открытие и закрытие пишутся в события.
- Авария кондиционера по термореле / сухому контакту ККБ: охлаждение стоп.
- Пинг шлюза
192.168.0.1раз в 30 с (не в секундном шаге). Есть ответ — время в мс; нет — авария и событие. Адрес в коде, при другом шлюзе правят строку. - Автоохлаждение можно выключить: тогда кондиционер не стартует по уставке, но перегрев всё равно попытается включить его.
Уставка и гистерезис — с Управления. Температуры, пинг и секунды двери — только чтение.
Сферы применения
Небольшой закрытый объём с электроникой, не ЦОД и не система пожаротушения.
- серверная, аппаратная, кроссовая, щит связи;
- стойка с ИБП и коммутаторами в офисе или на производстве;
- контейнер / бытовка с оборудованием, куда не ставят полноценную BMS;
- удалённый узел: перегрев, дверь и «шлюз не пингуется» уходят уведомлением, если контроллер на сервере telematic.dev.
Не закрывает: фреоновый контур с ротацией компрессоров (для этого штатный пример фреона), контроль влажности, несколько независимых рядов стоек, СКУД и видеонаблюдение.
Клеммы
| Клемма | Назначение |
|---|---|
| AI0 NTC | температура помещения |
| AI1 NTC | температура стойки |
| DO0 | кондиционер / вентилятор 1 |
| DO1 | вентилятор 2 / резерв (включается вместе с DO0) |
| DI0 НО | дверь открыта |
| DI1 НЗ | авария кондиционера (норма = замкнут) |
Сценарий
'''
Щит серверной / аппаратной.
Охлаждение по температуре, дверь, термореле кондиционера,
пинг шлюза. Два темпа: логика каждую секунду, сеть — раз в 30 с.
Клеммы:
AI0 NTC температура помещения
AI1 NTC температура стойки (опционально)
DO0 кондиционер / вентилятор 1
DO1 вентилятор 2 / резерв
DI0 НО дверь (True = открыта)
DI1 НЗ авария кондиционера (True = норма)
'''
from dev import Ai, Do, Di, Reg, Alarm, Event, count, webconf, run
from net import ping
from time import localtime
from math import isnan
from micropython import const
STEP = const(1)
PING_PERIOD = const(30)
PING_HOST = '192.168.0.1'
DOOR_MAX = const(60)
count(Reg=7, Ai=2, Do=2, Di=2, Ao=0, Alarm=6, Event=3)
webconf(Reg=True, Pin=True, Display=False, Console=True)
rPower = Reg(0).conf('bool', 'Автоохлаждение')
rSet = Reg(1).conf('int', 'Уставка, °C', limits=[15, 35], default=26)
rHist = Reg(2).conf('int', 'Гистерезис', limits=[1, 5], default=2)
rTroom = Reg(3).conf('float', 'Помещение, °C', access='r', dec=1)
rTrack = Reg(4).conf('float', 'Стойка, °C', access='r', dec=1)
rPingMs = Reg(5).conf('uint', 'Пинг, мс', access='r')
rDoorS = Reg(6).conf('uint', 'Дверь открыта, с', access='r')
aiRoom = Ai(0).conf(name='Помещение', type='ntc', access='r')
aiRack = Ai(1).conf(name='Стойка', type='ntc', access='r')
doAc = Do(0).conf(name='Кондиционер', access='r')
doFan = Do(1).conf(name='Вентилятор', access='r')
diDoor = Di(0).conf(name='Дверь открыта', type='no', access='r')
diAc = Di(1).conf(name='Авария кондиционера', type='nz', access='r')
aRoom = Alarm(0).conf('Ошибка датчика помещения')
aRack = Alarm(1).conf('Ошибка датчика стойки')
aHot = Alarm(2).conf('Перегрев')
aAc = Alarm(3).conf('Авария кондиционера')
aDoor = Alarm(4).conf('Дверь открыта долго')
aNet = Alarm(5).conf('Нет пинга шлюза')
eAc = Event(0).conf('Кондиционер')
eNet = Event(1).conf('Связь со шлюзом')
eDoor = Event(2).conf('Дверь')
doorSec = 0
coolOn = False
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 setCool(on, reason):
global coolOn
if coolOn == on:
doAc.val(on)
doFan.val(on)
return
coolOn = on
doAc.val(on)
doFan.val(on)
eAc.add(1 if on else 0)
log('Охлаждение {} ({})'.format('вкл' if on else 'выкл', reason))
def step():
global doorSec
tr = aiRoom.val()
tk = aiRack.val()
if isnan(tr):
aRoom.on()
rTroom.val(0)
setCool(False, 'датчик')
return
aRoom.off()
rTroom.val(tr)
if isnan(tk):
aRack.on()
rTrack.val(0)
tk = tr
else:
aRack.off()
rTrack.val(tk)
t = tr
if tk > t:
t = tk
if not diAc.val():
if not aAc.val():
aAc.on()
log('Авария кондиционера')
setCool(False, 'авария ККБ')
else:
aAc.off()
if diDoor.val():
doorSec += 1
rDoorS.val(doorSec)
if doorSec == 1:
eDoor.add(1)
if doorSec >= DOOR_MAX:
if not aDoor.val():
aDoor.on()
log('Дверь открыта {} с'.format(doorSec))
else:
if doorSec > 0:
eDoor.add(0)
doorSec = 0
rDoorS.val(0)
aDoor.off()
tmax = rSet.val() + 8
if t >= tmax:
if not aHot.val():
aHot.on()
log('Перегрев {:.1f}'.format(t))
if diAc.val():
setCool(True, 'перегрев')
return
if t <= rSet.val():
aHot.off()
if not rPower.val() or aAc.val():
setCool(False, 'выкл')
return
if t >= rSet.val() + rHist.val():
setCool(True, 'температура')
elif t <= rSet.val() - rHist.val():
setCool(False, 'температура')
def netStep():
ms, res = ping(PING_HOST, size=32, timeout=0.2)
if res == 0:
rPingMs.val(ms)
if aNet.val():
aNet.off()
eNet.add(1)
log('Шлюз {} мс'.format(ms))
else:
rPingMs.val(0)
if not aNet.val():
aNet.on()
eNet.add(0)
log('Нет пинга ' + PING_HOST)
doAc.off()
doFan.off()
log('Старт щита серверной')
run((step, STEP), (netStep, PING_PERIOD))