Опрос


Что для Вас Умный Дом?


Результаты

Beckhoff. Управление освещением

06/09/2011 16:56:17

1. Преимущество кнопки

Прежде чем писать код, хочу немного написать вообще про кнопку. Хочу сделать некую преамбулу, чтобы двигаться дальше (на этом форуме много уже обсуждалось про это)

Итак на картинке переход от стандартной схемы освещения помещения на технологию умный дом.

Какие бонусы мы получаем:
- мы исключили выключатель из линии подачи напряжения на лампочку. Теперь она включается и выключается только контроллером посредством реле, согласно заложенного в контроллер алгоритма (наступило нужное время "Ч", команда от кнопки, команда пользователя через Интернет и прочие варианты, которые ограничиваются только нашим воображением)
- мы разорвали связь ВЫКЛЮЧАТЕЛЬ - ЛАМПОЧКА, то есть любая кнопка может произвести действие с любой лампочкой, другим прибором, с самим контроллером, согласно алгоритма в контроллере
- кнопка теперь у нас командный пункт, то есть контроллер может отслеживать всевозможные команды посланные с кнопки, такие как: одинарное нажатие, двойное нажатие, N-крантное нажатие (актуально в дурдоме), нажатие и удержание кнопки, ругательства в адрес контроллера набитые языком Морзе и другие вариации.

2. Программируем простую кнопку

Когда кнопка не нажата, то состояние входа на контроллере равно FALSE (0), и равно TRUE (1) когда мы нажали кнопку (см рисунок). Таким образом, у кнопки мы можем отлавливать два состояния - КНОПКА НАЖАТА (или говоря по PLC-шному "ловить передний фронт") и КНОПКА ОТПУЩЕНА ("задний фронт"). Для нашей стандартной кнопки мы будем ловить передний фронт, чтобы лампочка зажигалась сразу именно в момент нажатия. Для этого напишем функциональный блок управления простой кнопкой.

Замечание: функциональный блок - это некоторая конструкция в языке программирования контроллеров, похожая на функцию (как говорилось в фильме "По замашкам вроде фраер, но не фраер - это точно!"). Если проводить аналогию с привычными языками программирования, то функциональный блок - это класс, со своими свойствами, но с одним единственным методом. думаю, что по коду будет всем понятно, как этим пользоваться.

Итак:

    FUNCTION_BLOCK SM_BUTT            (* ФУНКЦИОНАЛЬНЫЙ БЛОК ДЛЯ ПРОСТОЙ КНОПКИ *)
    VAR_INPUT
       IN: BOOL;           (*ВХОД ЗА КОТОРЫМ МЫ СЛЕДИМ *)
    END_VAR
    VAR_OUTPUT
       Q:BOOL:=FALSE;        (*СОСТОЯНИЕ ВЫХОДА НА ЛАМПОЧКУ *)
    END_VAR
    VAR
       MEM : BOOL := FALSE;    (* ВНУТРЕННЯЯ ПЕРЕМЕННАЯ ДЛЯ ХРАНЕНИЯ СОСТОЯНИЯ ВХОДА *)
    END_VAR

    IF IN AND NOT MEM THEN (* ПОЙМАЛИ ПЕРЕДНИЙ ФРОНТ - НАЖАЛИ НА КНОПКУ *)
       Q:= NOT Q;    (* МЕНЯЕМ СОСТОЯНИЕ ВЫХОДА НА ПРОТИВОПОЛОЖНОЕ *)
    END_IF

    MEM := IN;                        (* ЗАПОМИНАЕМ ТЕКУЩЕЕ ЗНАЧЕНИЕ ВХОДА *)
    END_FUNCTION_BLOCK

Ну здесь собственно ни убавить ни прибавить. Следим за входом и как только он поменял состояние из FALSE в TRUE меняем состояние выхода на лампочку. Один раз нажали - лампочка включилась, второй нажали - выключилась. Вот вам бистабильное реле в действии.

3. Программируем длинное нажатие

Меня всегда бесит вентилятор в туалете... Понятное дело, что он иногда необходим, так как цели посещения туалета бывают разные, но все равно - бесит... Так как у меня подключены к контроллеру и лампочка в ванной комнате и вентилятор, то было принято решение сделать так, чтобы вентилятор включался только по необходимости.. Простейший алгоритм был такой: если на выключатель нажать стандартно, то включается только лампочка, если на выключатель нажать и удерживать в течение одной секунды, то включается и вентилятор тоже, выключение света выключает и вентилятор тоже.

Как мы видим, здесь уже мы оперируем понятием времени ("в течение одной секунды"). Для таких целей в языках стандарта 61131-3 есть функциональные блоки-таймеры. Для данной задачи я использую блок-таймер TON (IN, PT). На входе мы даем ему вход, который контролируем и время задержки в миллисекундах. На выходе мы имеем переменную Q. Работает он так - пока IN=FALSE, Q=FALSE. Как только IN нажали (передний фронт), начинается отсчет времени (в миллисекундах) до значения, равного PT. После этого Q=TRUE, пока IN=TRUE (см рисунок). Каждое новое нажатие перезапускает таймер.

    FUNCTION_BLOCK SM_BUTT_BATH (* БЛОК ДЛЯ ВЫКЛЮЧАТЕЛЯ В ВАННОЙ *)
    VAR_INPUT
       IN : BOOL;       (*ВХОД ЗА КОТОРЫМ МЫ СЛЕДИМ *)
       TC : TIME;       (* ВРЕМЯ ЗАДЕРЖКИ  *)
    END_VAR
    VAR_OUTPUT
       Q1 : BOOL;      (*СОСТОЯНИЕ ВЫХОДА НА ЛАМПОЧКУ *)
                   Q2 : BOOL;      (*СОСТОЯНИЕ ВЫХОДА НА ВЕНТИЛЯТОР *)
    END_VAR
    VAR
       TX : TON;       (* БЛОК ТАЙМЕР *)
       MEM1, MEM2 : BOOL;  (* ВНУТРЕННИЕ ПЕРЕМЕННЫЕ ДЛЯ ХРАНЕНИЯ СОСТОЯНИЙ*)
    END_VAR

    TX(IN := IN, PT := TC); (* ПЕРЕДАЕМ ДАННЫЕ БЛОКУ-ТАЙМЕРУ *)
    IF IN AND NOT MEM1  THEN   (* ПОЙМАЛИ ПЕРЕДНИЙ ФРОНТ - НАЖАЛИ НА КНОПКУ *)
          Q1:= NOT Q1; (* СРАЗУ РЕАГИРУЕМ ЛАМПОЧКОЙ *)
          IF NOT Q1 THEN  (* ЕСЛИ ЛАМПОЧКУ ВЫКЛЮЧИЛИ, ТО И ВЫКЛЮЧАЕМ ВЕНТИЛЯТОР *)
             Q2:= FALSE;
          END_IF
    END_IF

    IF TX.Q AND NOT MEM2 AND Q1 THEN   (* СРАБОТАЛ ТАЙМЕР ПРИ ВКЛЮЧЕННОЙ ЛАМПОЧКЕ *)
          Q2 := TRUE;              (* ВКЛЮЧИЛИ ВЕНТИЛЯТОР *)
    END_IF;

    (* ЗАПОМИНАЕМ ТЕКУЩЕЕ СОСТОЯНИЕ *)
    MEM1 := IN;
    MEM2 := TX.Q;
    END_FUNCTION_BLOCK

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

4. Централизованное выключение света

Одна из функций, которую очень хотелось иметь при внедрение умного дома - это централизованное выключение всего света при уходе из дома. Из дома я выхожу через одну и туже дверь и у двери расположена кнопка, которая включает свет в прихожей. Ее я нажимаю последней, когда выхожу из дома. Эта кнопка - отличный кандидат, чтобы подать команду на полное отключение всего света в доме. Итак простейший алгоритм такой - при стандартном нажатии кнопки она работает как выключатель света в прихожей. Если же нажать и держать ее в течении 1 секунды, то во всем доме отключаются все лампочки, включая и прихожую.
Сказано - сделано. Пишем:

    FUNCTION_BLOCK SM_BUTT_OUT (* БЛОК ДЛЯ ВЫКЛЮЧАТЕЛЯ В ПРИХОЖЕЙ *)
    VAR_INPUT
       IN : BOOL;         (*ВХОД ЗА КОТОРЫМ МЫ СЛЕДИМ *)
       TC : TIME;         (* ВРЕМЯ ЗАДЕРЖКИ  *)
    END_VAR
    VAR_OUTPUT
       Q1: BOOL;       (*СОСТОЯНИЕ ВЫХОДА НА ЛАМПОЧКУ В ПРИХОЖЕЙ *)
       Q2 : BOOL;      (*ФЛАГ НА ВЫКЛЮЧЕНИЕ ВСЕГО СВЕТА *)
    END_VAR
    VAR
       TX : TON;          (* БЛОК ТАЙМЕР *)
       MEM1, MEM2 : BOOL;  (* ВНУТРЕННИЕ ПЕРЕМЕННЫЕ ДЛЯ ХРАНЕНИЯ СОСТОЯНИЙ*)
    END_VAR

    TX(IN := IN, PT := TC);    (* ПЕРЕДАЕМ ДАННЫЕ БЛОКУ-ТАЙМЕРУ *)
    Q2 := FALSE;         (* ФЛАГ ПОДНИМАЕМ ТОЛЬКО НА ОДИН ЦИКЛ КОНТРОЛЛЕРА *)

    IF IN AND NOT MEM1  THEN   (* ПОЙМАЛИ ПЕРЕДНИЙ ФРОНТ - НАЖАЛИ НА КНОПКУ *)
          Q1:= NOT Q1; (* СРАЗУ РЕАГИРУЕМ ЛАМПОЧКОЙ *)
    END_IF

    IF TX.Q AND NOT MEM2 THEN   (* СРАБОТАЛ ТАЙМЕР *)
          Q2 := TRUE;     (* ВЫСТАВЛЯЕМ ФЛАГ НА ВЫКЛЮЧЕНИЕ ВСЕГО СВЕТА *)
    END_IF;

    (* ЗАПОМИНАЕМ ТЕКУЩЕЕ СОСТОЯНИЕ *)
    MEM1 := IN;
    MEM2 := TX.Q;
    END_FUNCTION_BLOCK

Из улучшений, которые я хочу позже заложить в этот блок - чтобы контроллер как то пискнул или моргнул лампочкой в прихожей, когда произошло отключение всего света, чтобы я понимал что все ок. Также, так как у меня все герконы на окнах подключены на контроллер, то хотелось бы чтобы контроллер меня предупреждал перед выходом, что у меня остались открытыми окна. В дальнейшем это конечно надо выводить на какой то маленький мониторчик на выходе, чтобы сразу показать какое окно открыто, но пока хотя бы пусть предупредит светом или писком.

5. Окончательный код

Теперь заводим все в нашу тестовую программу. Вот у нас глобальные переменные, которые описывают входы-выходы нашего контроллера

    VAR_GLOBAL
       IN_H2_R1          AT %IX0.0:   BOOL; (*ГОСТЕВОЙ ДОМ - ВЫКЛЮЧАТЕЛЬ - СПАЛЬНЯ*)
       IN_H2_R2         AT %IX0.1:   BOOL; (*ГОСТЕВОЙ ДОМ - ВЫКЛЮЧАТЕЛЬ - ВАННАЯ*)
       IN_H2_R3         AT %IX0.2:   BOOL; (*ГОСТЕВОЙ ДОМ - ВЫКЛЮЧАТЕЛЬ - ПРИХОЖАЯ*)

       OUT_H2_R1_L       AT %QX0.0:   BOOL; (*ГОСТЕВОЙ ДОМ - ЛАМПА - СПАЛЬНЯ*)
       OUT_H2_R2_L       AT %QX0.1:   BOOL; (*ГОСТЕВОЙ ДОМ - ЛАМПА - ВАННАЯ*)
       OUT_H2_R2_M       AT %QX0.2:   BOOL; (*ГОСТЕВОЙ ДОМ - ВЕНТИЛЯТОР - ВАННАЯ*)
       OUT_H2_R3_L       AT %QX0.3:   BOOL; (*ГОСТЕВОЙ ДОМ - ЛАМПА - ПРИХОЖАЯ*)
    END_VAR

Объявляем в области локальных переменных экземпляры функциональных блоков для всех выходов

    VAR
       BUTT_H2_R1   :   SM_BUTT; (*ГОСТЕВОЙ ДОМ - КНОПКА  - СПАЛЬНЯ*)
       BUTT_H2_R2   :   SM_BUTT_BATH; (*ГОСТЕВОЙ ДОМ - КНОПКА  - ВАННАЯ*)
       BUTT_H2_R3   :   SM_BUTT_OUT;   (*ГОСТЕВОЙ ДОМ - КНОПКА  - ПРИХОЖАЯ*)
    END_VAR

Теперь пишем основной код

       BUTT_H2_R1 (IN:=IN_H2_R1);  (*КОНТРОЛИРУЕМ ВЫКЛЮЧАТЕЛЬ В СПАЛЬНЕ КАК СТАНДАРТНУЮ КНОПКУ*)
       BUTT_H2_R2 (IN:=IN_H2_R2, TC:=t#1000ms); (*КОНТРОЛИРУЕМ ВЫКЛЮЧАТЕЛЬ В ВАННОЙ С ЗАДЕРЖКОЙ 1000MS*)
       BUTT_H2_R3 (IN:=IN_H2_R3, TC:=t#1000ms); (*КОНТРОЛИРУЕМ ВЫКЛЮЧАТЕЛЬ В ПРИХОЖЕЙ С ЗАДЕРЖКОЙ 1000MS*)

       IF BUTT_H2_R3.Q2 THEN  (* ПОСТУПИЛА КОМАНДА ВСЕ ВЫКЛЮЧИТЬ *)
           OUT_H2_R1_L := FALSE;
           OUT_H2_R2_L := FALSE;
           OUT_H2_R2_M := FALSE;
           OUT_H2_R3_L := FALSE;
       ELSE
           OUT_H2_R1_L := BUTT_H2_R1.Q;
           OUT_H2_R2_L := BUTT_H2_R2.Q1;
           OUT_H2_R2_M := BUTT_H2_R2.Q2;
           OUT_H2_R3_L := BUTT_H2_R3.Q1;
       END_IF;

Компилирум и отправляем в контроллер...

Пока у меня нет жизненного примера для обработки двойного-тройного клика, так как не для всех домочадцев, особенно пожилых и маленьких это удобно. Скорее всего эти команды я оставлю для каких нибудь сервисных функций. Программируется все аналогично.

Автор: Али
Любое использование материалов сайта возможно только с разрешения автора и с обязательным указанием источника.



Добавить комментарий:



Сортировка комментариев: Последние сверху | Первые сверху

2016-01-10 10:30:00 | Владимир
Ali, спасибо за информацию для старта.

С примерами есть небольшое дополнение:
1. Для общего выключения переменная MEM2 не нужна, т.к. там мы не переключаем состояние, а всегда возвращаем истину.

Ещё не понимаю как в существующем примере без сброса состояния всех переменных работает общее выключение света. Пример: включаем SM_BUTT (BUTT_H2_R1.Q == TRUE), затем долгим нажатием на SM_BUTT_OUT добиваемся BUTT_H2_R3.Q2 == TRUE и работает первая ветка условия, но после того, как отпустим кнопку BUTT_H2_R3, у нас снова заработает вторая ветка, в которой BUTT_H2_R1.Q == TRUE также как и раньше и свет опять включится.

У себя решил такое выносом флага BUTT_H2_R3.Q2 в глобальные переменные и принудительной установкой всех выходов в FALSE при его наличии в блоках SM_BUTT , но не нравится такое решение.


2014-10-08 00:17:50 | WPagan
Покажите пожалуйста как эти блоки можно реализовать с помощью CFC... Не получается с ST...CFC нагляднее, а с ST путаюсь постоянно.


2014-04-02 06:48:41 | Али
Сергей, заходите на наш форум - там есть раздел по BECKHOFF - с удовольствием отвечу


2014-03-31 19:26:24 | Сергей
А как сделать так, чтобы при нажатии лампочка мигала, а при следующем нажатии переставала мигать