Довелось мне как-то автоматизировать раритетный шлагбаум, как говорится, кривой, косой, но родной. Он уже был моторизован, но этого было мало.
В процессе обсуждения ТЗ получилось следующее:
-
самое главное - возможность открывать по звонку на определенный номер телефона. Причем важно иметь возможность в любой момент редактировать список абонентов, у которых есть доступ, а так же регистрировать, кто и когда открывал шлагбаум
-
работа в автоматическом режиме (по сигналу шлагбаум открывается, ждет заданное время и закрывается)
-
фиксация в открытом положении, либо блокировка в закрытом
-
давать/блокировать возможность открыть с кнопки расположенной прям у шлагбаума (например, для курьеров доставки)
-
на будущее, возможность подключить радиоприемник для управления с радио-брелков
-
необходима обратная связь (положение шлагбаума в текущий момент)
-
контроль связи с управляющим контроллером, и возможность выбирать прям на контроллере шлагбаума сценарий, по которому он будет работать в случае потери связи. Сейчас, по умолчанию, шлагбаум должен сам открыться и остаться в открытом положении до возобновления связи. Это нужно на тот случай, если что‑то произойдет, например с локалкой, а кому-то срочно нужно выехать. Но, забегая вперед, это легко поменять локально прям в меню контроллера шлагбаума
Готового решения не нашлось. Ну что ж, весьма не типичный набор функций.
Сам шлагбаум представляет из себя металлическую сварную конструкцию (возможно родом из СССР). Установлен некий привод для распашных ворот производителя Сomunello серии ABACUS 230V без концевых выключателей, реверсивный двигатель которого управляется простым переключением фазы. Сразу отмечу, что в дальнейшем привод был заменен на аналог с механическими концевиками, что не влияет на проект по своей сути, но добавляет надежности.

Все готовые контроллеры управления моторными приводами, которые мне встречались, не позволяют выполнить поставленные задачи, т. к. для внешнего управления обычно есть только один вход для сухого контакта, который пошагово переключает режимы «ОТКРЫТЬ-СТОП-ЗАКРЫТЬ». А для открытия по звонку обычно используется примитивное GSM-реле с возможностью заливать списки с номерами телефонов через USB.
На объекте уже работает система автоматизации, в которой задействован контроллер Wiren Board, также по всей территории разведена локальная сеть, чем я и решил воспользоваться.
Подбор оборудования
В качестве контроллера выбор пал на программируемое реле Овен ПР200 c RS-485 на борту. У меня это уже довольно отработанная практика использовать подобные программируемые контроллеры для решения локальных задач в системах автоматизации. Мне показалось, где необходим контроль или внешнее управление на базе протокола Modbus, использование подобных устройств позволяет получить довольно распределенную и надежную систему. В случае потери связи контроллер продолжает работать по своей логике локально без внешнего управления. Плюс ко всему, конкретно у этой модели есть дисплей и кнопки, что позволяет создать свое меню для настроек и отображения статусов. (Хотя сейчас бы я заложил ПР205, у него большой цветной экран). Модель выбрана самая простая с интерфейсом RS-485(Modbus), на борту 8 дискретных входов и 6 релейных выходов. В целом, можно было обойтись и более дешевой моделью ПР100, но локальное управление и диагностика были бы не так удобны.
В качестве концевых выключателей было принято решение использовать индуктивные датчики, т.к они прекрасно реагируют на приближающийся метал.
Для безопасности взял барьерный датчик DoorHan Photocell-PRO, у него ответный модуль может достаточно долго питаться от элемента CR123A без проводов, а закладывать кабель под проездом очень не хотелось.
В качестве коммутирующего реле сначала были выбраны реле «F&F» PK-1P-24, но у них, даже при заявленном токе самого мощного двигателя этой серии до 2А, залипали контакты, на замену подобрал модульные контакторы finder 22.32.0.024.4540 с контактами AgSnO2.
Получилась такая вот схема:

Получилась такая вот схема
Сказано — сделано



Пора приступать к программированию
О том, как работать с ПО OWEN Logic особо не буду, т. к. прямо в справке к программе все подробнейшим образом описано. Программа написана на языке функциональных блоков (FBD), что позволяет очень удобно отлаживать программу.
Вот схема из ПО OWEN Logic:

Есть несколько ключевых переменных, значения которых можно запросить или записать в регистры ModBus:
-
nCmdOpen – собственно команда «Открыть шлагбаум»
-
nCmdFixOpen – фиксация шлагбаума в открытом состоянии
-
nBlock — блокировка внешнего управления
-
nButtonAllow – разрешение открыть шлагбаум с установленной рядом с ним кнопки
-
nTimeOfPause – время, которое контроллер выжидает, прежде чем начать закрывать шлагбаум
-
nTimeout — таймаут работы двигателя в случае его превышения снимается питание с привода
-
nReset — сброс ошибки
-
nHbIn — входной heartbeat сигнал для контроля связи с контроллером (пока значение меняется — связь есть)
-
nSeconds — используется в качестве выходного heartbeat сигнала (пока секунды тикают, головной контроллер уверен, что ПР200 на связи).
Также в целях диспетчеризации созданы сетевые переменные:
-
nState – текущее состояние (1 - закрыто, 2 - открывается, 3 - открыто, 4 - закрывается, 5 - повторное открытие в случае срабатывания барьерного датчика после начала закрытия, 9 - ошибка по таймауту работы двигателя)
-
nOpenTimeLeft - прошло времени с момента открытия
-
nOpenTimePassed - осталось времени до начала закрытия
-
nLimOpen - концевик открытого положения
-
nLimClose - концевик закрытого положения
-
nBarrier - сигнал с барьерного датчика
-
nError - ошибка по таймауту привода
Файл проекта OwenLogic
Описывать процесс создания экранного меню не буду, там все очень просто. В меню можно отображать и перезаписывать значения необходимых переменных, которые я описал выше.
Теперь перейдем к управлению всем этим из Wiren Board
В контроллер Wiren Board установил модем WBC2-4G, он вполне способен принимать входящие звонки и отдавать информацию о них посредством AT-команд. Но для этого обязательно нужно отключить ModemManager. Подробная информация о том, как взаимодействовать с модемом есть в Wiki Wiren Board здесь, здесь и здесь, а еще, советую изучить файл , свой скрипт я писал на его основе, т.к. не большой специалист в bash, наверняка можно было написать лучше. lib wb-gsm-common.sh
Собственно, bash-скрипт и взаимодействует с модемом, логирует события в системный журнал и отправляет номер, с которого поступил вызов MQTT брокеру. Скрипт прописал как сервис. При запуске системы скрипт включает АОН (AT+CLIP=1), разрешает завершение вызовов командой ATH (AT+CVHU=0) и запрещает модему отдавать список последних вызовов (AT+CLCC=0). Далее в бесконечном цикле скрипт ожидает от модема информацию о входящем вызове, публикует номер телефона в топик gsm/number и через 2 секунды завершает вызов.
Сам скрипт
#!/bin/bash
PORT="/dev/ttyGSM"
function get_response {
echo "request $1" | systemd-cat -t gsmwait
for (( a = 1; a <= 15; a++ ))
do
REPORT_FILE=`mktemp`
/usr/sbin/chat -s -r $REPORT_FILE TIMEOUT 2 ABORT "ERROR" REPORT "rn" "" "$1" OK "" > $PORT < $PORT 2>/dev/null
RC=$?
if [[ $RC != 0 ]] ; then
rm $REPORT_FILE
echo "$1 step $a" | systemd-cat -t gsmwait
sleep 0.5
continue
# exit $RC;
fi
REPORT=`cat $REPORT_FILE | sed -n 2p`
# echo "REPORT: $REPORT" | systemd-cat -t gsmwait
rm $REPORT_FILE
if [[ $REPORT == "OK" ]] ; then
echo "$1 OK" | systemd-cat -t gsmwait
sleep 0.5
return 0
fi
done
wb-gsm restart_if_broken
}
sleep 10
wb-gsm restart_if_broken
sleep 60
stty -F $PORT 115200 cs8 -cstopb -parenb -icrnl
get_response "AAAAAAAT"
get_response "AT+CLIP=1"
get_response "AT+CVHU=0"
get_response "AT+CLCC=0"
prevnum="+7"
prevtime=0
counter=0
while true
do
if instr=$(grep -m 1 'CLIP:' <$PORT)
then
instr=${instr#+*"}; instr=${instr/",145}
echo "OPEN from $instr" | systemd-cat -t gsmwait
nowtime=$(date "+%s")
deltatime=$((nowtime-prevtime))
echo "deltatime $deltatime" | systemd-cat -t gsmwait
# if [ $prevnum = $instr ] && [ $deltatime -lt 45 ]
# then
# echo "skip" | systemd-cat -t gsmwait
# else
mosquitto_pub -h localhost -t gsm/number -m $instr -u mqtusr -P password
echo "MQTT OK" | systemd-cat -t gsmwait
prevtime=$nowtime
prevnum=$instr
# fi
sleep 2
get_response "ATH"
fi
done
С этого момента можно звонить и проверять, появляются ли в логе и в указанном топике сообщения о принятом входящем вызове.
Для взаимодействия Wiren Board с ранее запрограммированным Овен ПР200 набросал шаблон:
config-pr200-gate
{
"device_type": "Owen PR200_Gate",
"device": {
"name": "PR200_Gate",
"id" : "pr200_gate",
"channels":
[{
"address": "521",
"format": "s8",
"name": "Open",
"offset": 0,
"reg_type": "holding",
"scale": 1,
"type": "switch"
}, {
"address": "518",
"format": "s8",
"name": "FixOpen",
"offset": 0,
"reg_type": "holding",
"scale": 1,
"type": "switch"
}, {
"address": "520",
"format": "s8",
"name": "PauseTime",
"offset": 0,
"reg_type": "holding",
"scale": 1,
"type": "value",
"units": "c"
}, {
"address": "512",
"format": "s8",
"name": "PassedOpen",
"offset": 0,
"readonly": true,
"reg_type": "holding",
"scale": 1,
"type": "value",
"units": "c"
}, {
"address": "513",
"format": "s8",
"name": "OpenTimeLeft",
"offset": 0,
"readonly": true,
"reg_type": "holding",
"scale": 1,
"type": "value",
"units": "c"
}, {
"address": "514",
"enabled": true,
"enum": [1, 2, "3", "4", "5", "9", "0"],
"enum_titles":
[
"Closed",
"Opening",
"Open",
"Closing",
"Reopening",
"Error",
"Unknown"
],
"format": "s8",
"name": "State",
"offset": 0,
"readonly": true,
"reg_type": "holding",
"scale": 1,
"type": "value"
}, {
"address": "516",
"format": "s8",
"name": "LimOpen",
"offset": 0,
"readonly": true,
"reg_type": "holding",
"scale": 1,
"type": "switch"
}, {
"address": "517",
"format": "s8",
"name": "LimClose",
"offset": 0,
"readonly": true,
"reg_type": "holding",
"scale": 1,
"type": "switch"
}, {
"address": "515",
"format": "u8",
"name": "Barrier",
"offset": 0,
"readonly": true,
"reg_type": "holding",
"scale": 1,
"type": "switch"
}, {
"address": "519",
"format": "s16",
"name": "ButtonAllow",
"offset": 0,
"reg_type": "holding",
"scale": 1,
"type": "switch"
}, {
"address": "522",
"format": "s16",
"name": "Error",
"offset": 0,
"readonly": true,
"reg_type": "holding",
"scale": 1,
"type": "switch"
}, {
"address": "523",
"format": "s16",
"name": "Timeout",
"offset": 0,
"reg_type": "holding",
"scale": 1,
"type": "value",
"units": "c"
}, {
"address": "524",
"format": "s16",
"name": "Block",
"offset": 0,
"reg_type": "holding",
"scale": 1,
"type": "switch"
}, {
"address": "525",
"format": "s16",
"name": "Reset",
"offset": 0,
"reg_type": "holding",
"scale": 1,
"type": "pushbutton"
}, {
"address": "526",
"format": "s16",
"name": "Heartbeat",
"offset": 0,
"readonly": true,
"reg_type": "holding",
"scale": 1,
"type": "value"
}, {
"address": "527",
"format": "s16",
"name": "HeartbeatOut",
"offset": 0,
"readonly": false,
"reg_type": "holding",
"scale": 1,
"type": "switch"
}
],
}
}
После настройки связи в wb-mqtt-serial, уже можно управлять шлагбаумом по MQTT.

На wb-rules написано правило, которое раз в 5 секунд инвертирует значение "HeartbeatOut", таким образом ПР200 знает, есть ли связь с Wiren Board. А так же ведется контроль значения "Heartbeat", если оно застыло, то мне придет сообщение о недоступности этого ПР200.
Осталось только организовать логику сверяющую номер телефона с которого поступил вызов с каким-либо списком, и в случае найденного совпадения отправляющую команду на открытие. Я для этого выбрал Node Red а список номеров телефонов можно хранить, например в CSV таблице.

Периодически номера из файла /root/numbers.csv записываются в виде массива в flow.numbers.
Когда в MQTT-топик gsm/number публикуется очередной номер, функция "find number" ищет поступивший номер в flow.numbers, если найден отправляет дальше "1", иначе "0". Код функции:
var msg1 = { payload:"", number: 0 };
msg1.number = Number(msg.payload);
if (msg1.number != 0) {
var numbers = flow.get("numbers");
for (var key in numbers) {
var num = Number(numbers[key].num);
if (num == msg1.number) {
msg1.payload = 1;
break;
} else msg1.payload = 0;
}
}
return msg1;
Итог
В целом, все исправно работает уже два года, не считая момента перехода в прошлом году с WB6.7 с 2G-модемом WBC-2G v.2 на WB8.4 с WBC2-4G, там было пару нюансов с используемыми AT-командами.
Как удобно редактировать список номеров, думаю каждый решит сам в зависимости от того, каким образом он взаимодействует с системой автоматизации. В моем случае это iRidi. Как вариант, можно набросать web - интерфейс прям в Node Red. Подходящий для этого инструмент @flowfuse/node-red-dashboard. В нем есть узел, позволяющий работать с таблицами.
Автор: siplix