1. Общий шаблон обработки событий

# Программа демонстрирует общий шаблон обработки событий
# и часть возможностей языка программирования EXT.

# При отпускании кнопки вскрытия корпуса контроллера (тампера)
# будет происходить переключение реле 1.

proc main()                             # основная подпрограмма (точка входа)
{
    var e = $get_event_id();            # получение обрабатываемого события
    if e == $EVT_INIT {                 # обработка инициализации
        $set_event_mask($EM_SYSTEM);    # разрешение системных событий
    } else if e == $EVT_CASE_OPEN {     # обработка вскрытия корпуса контроллера
        toggle(1);                      # переключение реле 1
    }
}

proc toggle(n)                          # подпрограмма управления выходом
{                                       # n - номер выхода
    var s = $get_output_state(n);       # получение текущего состояния
    $set_output_state(n, !s);           # установка инверсного состояния
}

2. Одновременный контроль и управление по одному входу

# Пример программы для одновременного контроля и управления по одному входу.
#
# Управление осуществляется встроенной логикой входа.
#
# В примере предполагается, что ко входу 1 подключен термодатчик RTD.
# Необходимые настройки входа 1:
# - тип входа: RTD-03 или RTD-04
# - тревожный диапазон: низкий гистерезисный (заданы необходимые границы температуры для управления)
# - круглосуточный контроль: включен
# - все реле/выходы управляются: напрямую входом
# - задана реакция реле/выходов на изменение состояния входа
#
# Контроль осуществляется программируемой логикой.
#
# Диапазон тревожного оповещения задается константой DELTA_T в градусах относительно
# нижней и верхней границы управления (см. настройки входа 1).
# Для оповещения о выходе температуры за тревожные границы должно быть настроено соединение:
# - задан номер телефона
# - задан пароль (зависит от типа оповещения)
# - разрешен необходимый тип оповещения, в котором установлена галочка у In1

const INPUT_N = 1; # номер входа с термодатчиком
const EVT_ACTIVE = $EVT_INPUT1_ACTIVE; # идентификатор активного состояния входа
const EVT_PASSIVE = $EVT_INPUT1_PASSIVE; # идентификатор пассивного состояния входа

const POLL_PERIOD = 10; # период опроса входа (1 единица = 100 мс)

const FRACT = 8; # размер дробной части fixed-point в битах
const ONE = 1 << FRACT; # единица (fixed-point)
const FILTER_K = ONE / 8; # коэффициент фильтрации (от 0 до 1 в fixed-point)
const DELTA_T = 5 << FRACT; # диапазон тревожного оповещения в градусах (fixed-point)

const ST_NONE = -1;
const ST_PASSIVE = 0;
const ST_ACTIVE = 1;

var t, state = ST_NONE, self_event;

proc main()
{
    var e = $get_event_id();
    if e == $EVT_INIT {
        init();
    } else if e == $EVT_TIMER1 {
        poll();
    } else if e == EVT_ACTIVE {
        cancel();
    } else if e == EVT_PASSIVE {
        cancel();
    }
}

proc init()
{
    $set_event_mask($EM_INPUT);
    $set_timer(1, POLL_PERIOD);
    t = get_temp();
}

proc poll()
{
    var t_low = $get_sensor_low_limit(INPUT_N, FRACT) - DELTA_T;
    var t_high = $get_sensor_high_limit(INPUT_N, FRACT) + DELTA_T;

    t = filter(get_temp(), t);

    if t <= t_low && state != ST_ACTIVE {
        raise(EVT_ACTIVE);
        state = ST_ACTIVE;
    } else if t >= t_high && state != ST_ACTIVE {
        raise(EVT_ACTIVE);
        state = ST_ACTIVE;
    } else if t > t_low && t < t_high && state != ST_PASSIVE {
        raise(EVT_PASSIVE);
        state = ST_PASSIVE;
    }
}

fun get_temp()
{
    return $get_sensor_value(INPUT_N, FRACT);
}

fun filter(x, y)
{
    return y + (FILTER_K * (x - y) >> FRACT);
}

proc raise(e)
{
    $raise_event(e);
    self_event = 1;
}

proc cancel()
{
    if self_event {
        self_event = 0;
    } else {
        $cancel_event();
    }
}

3. Контроль входа с двумя активными уровнями

# Пример программы для контроля входа с двумя активными уровнями.

# В примере предполагается, что ко входу 1 подключен термодатчик RTD.
# Необходимые настройки входа 1:
# - тип входа: RTD-03 или RTD-04
# - круглосуточный контроль: включен

# Уровни задаются константами T1 и T2 в градусах:
# t < T1 - пассивный
# T1 <= t < T2 - активный 1
# t >= T2 - активный 2
#
# Для оповещения о выходе температуры за тревожные границы должно быть настроено соединение:
# - задан номер телефона
# - задан пароль (зависит от типа оповещения)
# - разрешен необходимый тип оповещения, в котором установлена галочка у In1

const INPUT_N = 1; # номер входа с термодатчиком
const EVT_ACTIVE = $EVT_INPUT1_ACTIVE; # идентификатор активного состояния входа
const EVT_PASSIVE = $EVT_INPUT1_PASSIVE; # идентификатор пассивного состояния входа

const POLL_PERIOD = 10; # период опроса входа (1 единица = 100 мс)

const FRACT = 8; # размер дробной части fixed-point в битах
const ONE = 1 << FRACT; # единица (fixed-point)
const FILTER_K = ONE / 8; # коэффициент фильтрации (от 0 до 1 в fixed-point)
const T1 = 20 << FRACT; # первая граница в градусах (fixed-point)
const T2 = 30 << FRACT; # вторая граница в градусах (fixed-point)

const ST_NONE = -1;
const ST_PASSIVE = 0;
const ST_ACTIVE1 = 1;
const ST_ACTIVE2 = 2;

var t, state = ST_NONE, self_event;

proc main()
{
    var e = $get_event_id();
    if e == $EVT_INIT {
        init();
    } else if e == $EVT_TIMER1 {
        poll();
    } else if e == EVT_ACTIVE {
        cancel();
    } else if e == EVT_PASSIVE {
        cancel();
    }
}

proc init()
{
    $set_event_mask($EM_INPUT);
    $set_timer(1, POLL_PERIOD);
    t = get_temp();
}

proc poll()
{
    t = filter(get_temp(), t);

    if t >= T1 && t < T2 && state != ST_ACTIVE1 {
        raise(EVT_ACTIVE);
        state = ST_ACTIVE1;
    } else if t >= T2 && state != ST_ACTIVE2 {
        raise(EVT_ACTIVE);
        state = ST_ACTIVE2;
    } else if t < T1 && state != ST_PASSIVE {
        raise(EVT_PASSIVE);
        state = ST_PASSIVE;
    }
}

fun get_temp()
{
    return $get_sensor_value(INPUT_N, FRACT);
}

fun filter(x, y)
{
    return y + (FILTER_K * (x - y) >> FRACT);
}

proc raise(e)
{
    $raise_event(e);
    self_event = 1;
}

proc cancel()
{
    if self_event {
        self_event = 0;
    } else {
        $cancel_event();
    }
}

4. Отмена оповещения при изменении состояния входов на время меньше заданного

# Пример программы для отмены оповещения при изменении состояния входов на время меньше заданного.

# Время задается константой INPUT_DELAY и может быть порядка минут или часов.

const POLL_PERIOD = 600; # период опроса входов (600 * 100 мс = 60 с = 1 мин)
const INPUT_DELAY = 120; # длительность задержки оповещения (120 мин = 2 ч)

const FALSE = 0;
const TRUE = 1;

var inputs_state;
var self_events;

var input1_time;
var input2_time;
var input3_time;
var input4_time;
var input5_time;
var input6_time;
var input7_time;
var input8_time;

proc main()
{
    var e = $get_event_id();
    if e == $EVT_INIT {
        init();
    } else if e == $EVT_TIMER1 {
        poll();
    } else if e >= $EVT_INPUT1_ACTIVE && e <= $EVT_INPUT8_ACTIVE {
        var n = e - $EVT_INPUT1_ACTIVE + 1;
        handle_input_event(n, 1);
    } else if e >= $EVT_INPUT1_PASSIVE && e <= $EVT_INPUT8_PASSIVE {
        var n = e - $EVT_INPUT1_PASSIVE + 1;
        handle_input_event(n, -1);
    }
}

proc init()
{
    $set_event_mask($EM_INPUT);
    $set_timer(1, POLL_PERIOD);
    init_inputs();
    poll();
}

proc init_inputs()
{
    var n = 1;
    while n <= 8 {
        var s = $get_input_state(n);
        var t;
        if s {
            t = INPUT_DELAY;
        } else {
            t = -INPUT_DELAY;
        }
        set_input_state(n, !s);
        set_input_time(n, t);
        n = n + 1;
    }
}

proc handle_input_event(n, t)
{
    if is_self_event(n) {
        set_self_event(n, FALSE);
    } else {
        set_input_time(n, t);
        $cancel_event();
    }
}

proc set_input_time(n, t)
{
    if n == 1 {
        input1_time = t;
    } else if n == 2 {
        input2_time = t;
    } else if n == 3 {
        input3_time = t;
    } else if n == 4 {
        input4_time = t;
    } else if n == 5 {
        input5_time = t;
    } else if n == 6 {
        input6_time = t;
    } else if n == 7 {
        input7_time = t;
    } else if n == 8 {
        input8_time = t;
    }
}

proc poll()
{
    input1_time = handle_input_time(1, input1_time);
    input2_time = handle_input_time(2, input2_time);
    input3_time = handle_input_time(3, input3_time);
    input4_time = handle_input_time(4, input4_time);
    input5_time = handle_input_time(5, input5_time);
    input6_time = handle_input_time(6, input6_time);
    input7_time = handle_input_time(7, input7_time);
    input8_time = handle_input_time(8, input8_time);
}

fun handle_input_time(n, t)
{
    if abs(t) <= INPUT_DELAY {
        t = tick(t);
        var s = t > 0;
        if abs(t) == INPUT_DELAY + 1 && is_state_switch(n, s) {
            raise(n, s);
        }
    }
    return t;
}

fun abs(x)
{
    if x < 0 {
        return -x;
    } else {
        return x;
    }
}

fun tick(t)
{
    if t < 0 {
        return t - 1;
    } else {
        return t + 1;
    }
}

fun is_state_switch(n, s)
{
    return get_input_state(n) != s;
}

proc raise(n, s)
{
    var e;
    if s {
        e = $EVT_INPUT1_ACTIVE;
        set_input_state(n, TRUE);
    } else {
        e = $EVT_INPUT1_PASSIVE;
        set_input_state(n, FALSE);
    }
    $raise_event(e + n - 1);
    set_self_event(n, TRUE);
}

fun mask(n)
{
    return 1 << (n - 1);
}

proc set_input_state(n, s)
{
    if s {
        inputs_state = inputs_state | mask(n);
    } else {
        inputs_state = inputs_state & ~mask(n);
    }
}

fun get_input_state(n)
{
    return (inputs_state & mask(n)) != 0;
}

proc set_self_event(n, s)
{
    if s {
        self_events = self_events | mask(n);
    } else {
        self_events = self_events & ~mask(n);
    }
}

fun is_self_event(n)
{
    return (self_events & mask(n)) != 0;
}