Cценарий реализует систему управления электрическим нагревателем. При выполнении он регулярно (с шагом в 1 секунду) считывает данные с температурного датчика, осуществляет контроль температуры в заданных пределах и включает или выключает сам нагреватель в соответствии с алгоритмом управления, который использует заданную уставку, определяемую пользователем или установленную планировщиком, а также учитывает гистерезис и задержку переключения. Кроме того, скрипт внедряет механизм планировщика, позволяющий автоматически изменять параметры работы (например, включение питания, установка желаемой температуры) в определённое время суток согласно заранее заданному расписанию.

Скриншот панели управления Скриншот панели управления

Возможности

  • Гибкая настройка: Пользователь может задавать уставку температуры (rSetPoint), гистерезис (rHist), задержку переключения (rDelay) и включать/выключать планировщик (rSched) через веб-интерфейс или регистры.
  • Автоматизация по расписанию: Поддерживает временные задачи (например, включение нагрева в 7:30 с температурой 23°C).
  • Защита системы: Автоматическое отключение при сбоях датчика или выходе температуры за допустимые пределы.
  • Логирование: Помогает в диагностике и отладке благодаря подробным сообщениям.

Область применения

Сценарий подходит для автоматизации систем отопления в бытовых или небольших промышленных условиях, например:

  • Управление обогревателями в жилых помещениях или теплицах.
  • Поддержание температуры в инкубаторах или террариумах.
  • Автоматизация климат-контроля в небольших зданиях с использованием расписания (например, прогрев помещений утром и вечером).

Производительность

Замеры осуществлялись на тестовом образце

Время компиляции 110 мс
Среднее время выполнения тела цикла <3 мс
Использование стека 408 bytes (4.9%)
Использование кучи 4560 bytes (7.4%)

Сценарий

  '''
Сценарий для управления электрическим нагревателем.
'''

from dev import Ai, Do, Reg, Alarm, Event, run, count, webconf
from time import time, localtime
from math import isnan

# Объявление констант
STEP     = const(1)          # Интервал выполнения шага в секундах
TEMP_MIN = const(15)         # Минимальная допустимая температура
TEMP_MAX = const(45)         # Максимальная допустимая температура

# Расписание планировщика (должно быть отсортировано по времени)
TASKS = [
    {'hour': 0, 'minute': 0, 'power': True,  'temp': 19},
    {'hour': 7, 'minute': 30, 'power': True, 'temp': 23},
    {'hour': 10, 'minute': 0, 'power': False},
    {'hour': 17, 'minute': 0, 'power': True, 'temp': 23},
]

def log(msg):
    """Функция для логирования сообщений с отметкой времени."""
    current_time = localtime()
    print('{0}/{1:02}/{2:02} {3:02}:{4:02}:{5:02} - {6}'.format(
        current_time[0], current_time[1], current_time[2],
        current_time[3], current_time[4], current_time[5], msg))

# Конфигурация панели управления через функцию count
count(Ai=1, Reg=5, Do=1, Di=0, Ao=0, Alarm=2)

# Дополнительная конфигурация через webconf
webconf(Reg=True, Display=False, Pin=True, Console=True)

# Пользовательские регистры
rPower    = Reg(0).conf(name='Питание', type='bool')                             # Режим питания
rSetPoint = Reg(1).conf(name='Уставка', type='int', limits=[10, 45], step=1, default=25, web='input')  # Уставка температуры
rHist     = Reg(2).conf(name='Гистерезис +/-', type='uint', limits=[0, 10], access='w', web='select')           # Гистерезис
rDelay    = Reg(3).conf(name='Задержка, сек', type='uint', limits=[0, 600], step=30, web='select')              # Задержка включения/выключения
rSched    = Reg(4).conf(name='Планировщик', type='bool', access='w')                                                # Включение планировщика

# Аналоговые и цифровые порты
iSensor   = Ai(0).conf(name='Температура', type='ntc')  # Температурный датчик
oHeater   = Do(0).conf(name='ТЭН', type='do', access='r')  # Выход для управления нагревателем

# Аварийные сигналы
aSensor   = Alarm(0).conf(name='Ошибка датчика температуры')  # Авария: неисправность датчика температуры
aTemp     = Alarm(1).conf(name='Недопустимая температура')   # Авария: превышение допустимых температур

# События
ePower    = Event(0).conf(name='Питание')  # Событие изменения питания
eHeater   = Event(1).conf(name='ТЭН')      # Событие изменения состояния нагревателя

# Локальная переменная для отслеживания задержки переключения нагревателя
timeSwitch = 0

def setPower(value, reason):
    """
    Устанавливает состояние питания.
    :param value: Новое значение питания (True/False).
    :param reason: Причина изменения состояния.
    """
    if value == rPower.val():
        return
    rPower.val(value)
    ePower.add(value)
    if not value:
        setHeater(False, "Остановка питания")
    log('Установка Питания: {} ({})'.format("Вкл" if value else "Выкл", reason))

def setHeater(value, reason):
    """
    Устанавливает состояние нагревателя.
    :param value: Новое значение нагревателя (True/False).
    :param reason: Причина изменения состояния.
    """
    global timeSwitch
    if value == oHeater.val():
        return
    oHeater.val(value)
    eHeater.add(value)
    timeSwitch = time() + rDelay.val()
    log('ТЭН: {} ({})'.format("Вкл" if value else "Выкл", reason))
    
def setAlarm(alarm, msg, par=None):
    """
    Устанавливает аварийный сигнал и отключает питание.
    :param alarm: Объект аварийного сигнала.
    :param msg: Сообщение об аварии.
    :param par: Дополнительный параметр (опционально).
    """
    if alarm.val():
        return
    if par is None:
        alarm.on()
    else:
        alarm.on(par)
    log("Авария: " + msg)
    setPower(False, "Авария")
    
def step():
    """Основная функция шага, выполняемая с заданным интервалом."""
    global timeSwitch
    
    # Проверка изменения состояния планировщика
    if rSched.changed():
        log("Планировщик " + ("Включен" if rSched.val() else "Выключен"))
    
    # Работа планировщика, если он включен
    if rSched.val():
        current_time = localtime()
        current_hour = current_time[3]
        current_minute = current_time[4]
        myTime = current_hour * 60 + current_minute
        
        current_task = TASKS[0]
        for task in TASKS:
            task_time = task['hour'] * 60 + task['minute']
            if myTime >= task_time:
                current_task = task
            else:
                break  # Так как TASKS отсортирован, дальше задачи не актуальны
        
        setPower(current_task['power'], "Планировщик")
        if current_task.get('power') and 'temp' in current_task:
            rSetPoint.val(current_task['temp'])
    
    # Проверка изменения состояния питания пользователем
    if rPower.changed():
        setPower(rPower.val(), "Пользователь")
    
    # Считывание значения температурного датчика
    temp = iSensor.val()
    if isnan(temp):
        setAlarm(aSensor, 'Ошибка датчика температуры')
        return

    # Работа нагревателя, если питание включено
    if rPower.val():
        if temp < TEMP_MIN or temp > TEMP_MAX:
            setAlarm(aTemp, 'Недопустимая температура: {}'.format(temp))
            return
        # Проверка задержки перед переключением нагревателя
        if time() < timeSwitch:
            return
        # Управление нагревателем на основе уставки и гистерезиса
        if temp < rSetPoint.val() - rHist.val():
            setHeater(True, "Алгоритм управления")
        elif temp > rSetPoint.val() + rHist.val():
            setHeater(False, "Алгоритм управления")

# Стартовая инициализация
log('Старт системы управления нагревателем')
run(step, STEP)