Недавно было решено установить в многоквартирном доме шлагбаум с управлением через GSM. Причины и необходимость этого решения остаются за рамками статьи, я же хочу написать про то, как «на коленке» сделал WEB-интерфейс для управления модулем, и даже немного с блэкджеком, базой автомобилей с мобильного и фотографиями с уличной камеры. Возможно кто-то захочет внедрить у себя.
Предупрежу, что в статье не описано готовое решение «из коробки», а скорее proof of concept.
Перед тем, как ставить GSM-модуль был проведен некоторый обзор рынка таких систем. Хотелось получить недорогое, надежное и отработанное решение. Ну и чтобы «в наличии» было у установщиков, имелся какой-то опыт работы с ним и т.д.
Установщики на выбор предлагали либо ESIM 110/120 за ~12000р с доступом в интернет, либо Doorhan GSM за ~6000р с управлением через SMS или настройкой через USB-шнурок.
Вариант «сделать свой девайс из arduino + gsm модуль за $3» не рассматривался, так решение должно быть точно надежным и обкатанным. Представьте, кто-то не смог заехать домой? Потом неприятностей получишь по полной.
Минусом ESIM120, кроме стоимости х2 было то, что для выхода в интернет использовался GPRS-интернет. Для кого-то может это и плюс, но в нашем случае тянет за собой расходы на мобильную связь — придется брать тариф с интернетом. Сейчас же на сим-карте подключен тариф без абонентской платы, и раз в 2-3 месяца планирую подключать из личного кабинета оператора какую нибудь платную подписку за 2-3 рубля в сутки, на одни сутки. Например «погоду» или что там еще полезно для шлагбаума )
Про Doorhan GSM я знал, что помимо управления через SMS (мало интересно) он подключается по USB к компьютеру и через собственное ПО позволяет управлять базой номеров.
Часть 1. Проброс управления
Так как блок управления шлагбаумом планировалось ставить в 20 метрах от помещения, где расположен домовой IP-видеорегистратор и узел связи местного интернет-провайдера, то было решено взять Doorhan и «нарастить» USB через роутер вроде TP-Link MR3020 стоимостью 1200р, OpenWRT и программ из проекта USBIP
С роутером получилось даже несколько проще — нашел в закромах старый ASUS WL500gP, который для интернета не очень годится по нынешним меркам, зато имеет 2 USB порта. Его и использовал.
Для usbip пришлось поставить старую версию OpenWRT, 12.09, потому как на новых не работает этот модуль ядра. Подключение роутера к сети описывать не буду. Если у кого-то он будет не в локальной сети, возможны варианты с пробросом портов, UPNP или настройкой VPN, на ваш вкус.
Устанавливаем kmod-usbip-server и проверяем, что у нас можно экспортировать
root@OpenWrt:~# opkg install kmod-usbip-server
root@OpenWrt:~# usbip list -l
Local USB devices
=================
- busid 1-1 (0424:2502)
1-1:1.0 -> hub
- busid 1-1.1 (1a86:7523)
1-1.1:1.0 -> ch341
Нам нужен busid устройства, 1-1.1 на котором располагается наш подключенный контроллер GSM. Забегая вперед, оказалось что это банальный COM->USB конвертер на чипе CH341
Выполняем:
root@OpenWrt:~#usbipd -D
root@OpenWrt:~#usbip bind -b 1-1.1
bind device on busid 1-1.1: complete
и в dmesg
usbip-host 1-1.1:1.0: usbip-host: register new device (bus 1 dev 57 ifn 0)
На компьютере с Windows устанавливаем драйвера USBIP и запускаем
usbip -a 10.16.19.19 1-1.1
где 10.16.19.19 IP-адрес нашего роутера с OpenWRT. Разумеется, для начала нужно отрыть доступ либо с вашего IP к роутеру в firewall, либо подключаться с локальной сети, либо любой из 1000 других вариантов от VPN до P2P.
Если все прошло успешно, то Windows радостно заявляет, что обнаружено новое устройство USB-to-Serial Converter CH340, даем ей драйвера и в системе появляется COM-порт.
Теперь можем запустить программку из комплекта с контроллером и управлять номерами в базе сидя дома на диване
Часть 2. Контроль въездов
После того, как настроил работу с контроллером на домашнем компьютере и записал номера пользователей, решил немного поковыряться с тем, что доступно по СОМ-порту.
Оказывается, GSM-модуль контроллера периодически шлет AT команды с уровнем сигнала в консоль, а так же при звонке пишет номер телефона, с которого поступает звонок. Управлять модулем AT командами не получилось, видимо они не транслируются от модуля контроллера в модуль модема.
Все равно это довольно интересно. При проработке идеи GSM-контроллером я надеялся, что эти звонки будут фиксироваться в детализации оператора. Но так как соединения не происходит, то записей в детализации нет. Теперь же можно прямо с контроллера собирать логи того, кто открывал шлагбаум. Или кто пытался это сделать.
Для этого на OpenWRT устанавливаем kmod-usb-serial-ch341, отключаем трансляцию USBIP командой usbip unbind -b 1-1.1 и делаем insmod ch341
После чего прямо на роутере можно подключиться к /dev/ttyUSB0 и смотреть что там происходит со звонками в контроллере.
Для обработки данных для начала написал простейший скриптик, отсылал данные о входящем звонке на внешний сервер с PHP для обработки и сохранения в базу. С таким же успехом можно писать в локальный файл, правда на роутере памяти не густо.
#!/bin/sh
cat /dev/ttyUSB0 | while read DATA; do
if echo $DATA | grep CLIP 2>/dev/null >/dev/null; then
curl --silent --output /dev/null --data "data=$DATA" http://1.2.3.4:8081/border.php; >/dev/null 2>/dev/null
fi
done
На сервере создал базу в mysql и пару табличек: с номерами телефонов наших жильцов, и с логом звонков. Стало возможно сопоставить кто в какое время открывал шлагбаум, и пытаются ли им пользоваться с неизвестных номеров.
Вторая идея, которая пришла мне в голову — сделать связку между событием открывания шлагбаума и фото этого события. Дело в том, как я упоминал выше, вокруг дома стоят AHD камеры, которые пишут на регистратор с доступом к потоку по IP. Причем одну из камер специально повернули на шлагбаум, в ожидании что его будут ломать.
Как снять jpeg с нашего китайского регистратора мне найти не удалось, хотя во многих камерах есть Preview URL. Поэтому пошел в лоб — в момент звонка получаю RTSP и делаю из него JPG.
ffmpeg -i "rtsp://2.3.4.5:554/user=user&password=password&channel=1&stream=0.sdp?" -y -f image2 -t 0.001 -ss 00:00:3 -s 1280*720 /tmp/screenshot.jpg
С таким же успехом можно было писать маленькие ролики в mp4, но я посчитал это лишним.
Фотографии решил хранить блобами в MySQL. Решение по производительности так себе, зато «таскать» проектик будет проще, не нужно копировать и базу и файлы, все в базе. А нагрузка на него никакая по сути.
В результате лог въездов выглядит как-то так:
Часть 3. Загрузка номеров из базы
Если вы внимательно читали, то наверное заметили — для регистрации въездов USB порт роутера работал в режиме Serial-to-USB преобразователя, а для работы с базой номеров внутри контроллера приходилось «прокидывать» его через USBIP на домашний компьютер и там через программу для Windows вносить изменения. Это не совсем удобно, нужно было делать unbind/bind да еще на домашнем компьютере запускать usbip консольный. Ну и делать это можно было только из дома (ну либо опять RDP/VPN и проч и проч), и тем более не с телефона мобильного. Вдвойне накаляло то, что базу номеров надо было вести и в mdb-формате (программа для контролера может выгружать данные в Access) и в веб-версии.
Беглый гуглеж протокола работы Doorhan GSM ничего не дал. Не исключаю, что это какая-то китайская девайсина под маркой Doorhan, тем не менее. Поэтому вооружился монитором (сниффером) для COM-порта и снял несколько дампов при работе из оригинального приложения.
Получил что-то вроде:
Команда на очистку памяти aa 02 09 00 00 03 e8 01 00 00 00 00 ee
Ответ что все прошло хорошо aa 20 00 ee
Команда на запись одного номера +79999999999
aa 03 10 00 01 2b 37 39 39 39 39 39 39 39 39 39 39 00 00 ee
Из чего сделал такие выводы:
начало посылки АА, конец посылки ЕЕ
Ответ, что команда принята 20 00
Команда на начало записи номера 03. Потом идет количество номеров в посылке. Может быть от 1 до 5, то есть за одну посылку можно отправить сразу несколько номеров подряд, закончив посылку командой ЕЕ, и получить одно подтверждение на блок.
После команды начала записи идет 2 байта (второй точно, первый возможно будет использоваться при количестве номеров больше 256 но не проверял), указывающие порядковый номер записи в ячейке. То есть если писать в пустую память, то сначала это 1, потом 2 и так далее. После чего идет 14 байт номера телефона. Так как наши номера«входят» в 12 байт (+79999999999) то последние 2 байта заняты нулями. Вероятно для всяких международных форматов типа +10. Это не точно, сильно не вникал, потому что хватило этих данных.
Завершается посылка байтом ЕЕ
С порядковым номером ячейки не совсем понятная ситуация. Допустим, в контроллер записано 10 номеров, память не очищали. Если отправить команду «записать номер в ячейку 5», то номер в этой ячейке после контрольной вычитки не изменится, но в контроллере будет записано 11 номеров, причем 11-й будет пустой. А если дать команду «записать номер в ячейку 11», то он запишется в нее. Вероятно это связано с командами ADD и REPLACE (добавить и изменить), которые можно слать через SMS. Но так как оригинальное приложение делает только полное стирание и последующую перезапись списка, проверить гипотезу не получается. Поэтому свой вариант делал тоже отправкой команды стирания и последовательной записи по одному номеру (для простоты).
Итак, мы подошли к тому, чтобы писать в контроллер через последовательный порт команды на управление списком абонентов. Но порт в роутере, а база с пользователями на внешнем сервере. Переносить ее на роутер конечно можно (получится законченное решение «из коробки») но мне было лень. Пошел другим путем — прокинул /dev/ttyUSB0 по tcp с помощью пакета ser2net, который есть в поставке openwrt.
Конфиг /etc/ser2net.conf простой для неприличия
3333:raw:0:/dev/ttyUSB0:9600,remctl
После запуска ser2net можно подключиться к роутеру telnet-ом на 3333 порт и проверить результат.
Хочется сделать оговорку: после перезапуска роутера внезапно не работал cat /dev/ttyUSB0. То есть конечно работал, но в консоль писал мусор. Вспомнил, что во время экспериментов на роутере я запускал minicom, который вероятно сделал инициализацию порта. Просто установив режим 9600 8n1 результата не дало, поэтому я посмотрел с помощью stty какие настройки порта стоят в «рабочем» состоянии и вписал в rc.local инициализацию
stty -F /dev/ttyUSB0 9600 ignbrk -brkint -icrnl -imaxbel -opost -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke
Наверняка что-то лишнее, читатели поправят возможно. Ну и если параметры порта указать в ser2net.conf то можно не ставить на роутер пакет stty
В результате этих манипуляций на сервере с WEB-интерфейсом у нас стала доступна консоль контроллера. Поэтому немного перепишем код обработки звонков. Писал на PHP. Весь код стыдно показывать (я же не программист в конце концов, а использовал вставки из учебника), поэтому суть:
$re = '/CLIP: ".(7d{10})"/m'; //регэксп на входящий звонок
while (1) {
$f=@fsockopen('tcp://10.16.19.19',3333, $errno, $errstr); //подключимся к роутеру
while ($f && $str=fread($f, 100)) {
$test=preg_match($re, $str, $matches);
if ($test) process_call($matches[1]); //пришел звонок, запишем в базу и сделаем фото
//проверим нужно ли записать телефоны из базы в контроллер и сделаем это, если нужно, используя дескриптор $f
sync_phones($f);
}
if ($f) fclose($f); //что-то пошло не так
sleep(5); //подождем 5 сек и сделаем реконнект
}
process_call каждый думаю реализует как ему нравится, от записи информации в лог-файл, до создания фото/видео въезда и отправки их через Телеграм-бота на телефон супруга/супруги, а так же команды на кофеварку или для разогрева борща.
На sync_phones остановлюсь чуть подробнее, потому как там реализован тот самый «не публичный» алгоритм работы GSM-контроллера Doorhan. И да, рукалицо, я использовал mysql вместо pdo или mysqli.
function sync_phones($f) {
/*
В таблице config хранится переменная update. Если в веб-интерфейсе сделать val=1, то должен запуститься процесс обновления номеров в контроллере. Можно сделать семафор через файл, но раз я все равно использую mysql, то решил сделать так
*/
$result=mysql_query("SELECT * FROM config WHERE name='update' AND val=1");
if (mysql_num_rows($result)==0) return;
$init = pack('H*','aa1100ee'); //какой-то пакет инициализации при подключении программы к контроллеру
$clear = pack('H*','aa0209000003e80100000000ee'); //команда на стирание данных (выдрано с дампа СОМ-порта)
$logfile=fopen("/tmp/border.log","a");
fwrite($logfile,"Update started at ".date('Y-m-d H:i:s')."n");
fwrite($f,$init);
sleep(1);
$ans=fread($f, 40);
fwrite($logfile,"Send init. Answer ".bin2hex ( $ans )."n");
fwrite($f,$clear);
sleep(1);
$ans=fread($f, 40);
fwrite($logfile,"Send clear. Answer ".bin2hex ( $ans )."n");
$result=mysql_query("SELECT * FROM phones ORDER BY pid");
//тут все ясно, select всех номеров из базы пользователей, чтобы записать их в контроллер
$n=1;
while($row=mysql_fetch_array($result)) {
$start = 'aa0310';
$pos = sprintf("%04x",$n);
$phone = bin2hex('+'.$row['phone']).'0000';
$end = 'ee';
//собираем "посылку"
$send=$start.$pos.$phone.$end;
$bin=pack('H*',$send);
fwrite($f,$bin);
$ans=fread($f, 40);
fwrite($logfile,"Write {$n} phone {$row['phone']}. Answer ".bin2hex ( $ans )."n");
$n++;
}
fwrite($logfile,"End updaten");
fclose($logfile);
//запишем в наш "семафор" время, когда обновление завершено, полезная информация
mysql_query("UPDATE config SET val=0,result='".bin2hex ( $ans )."',updated=NOW() WHERE name='update'");
}
?>
В результате я получил (написал) WEB-интерфейс для шлагбаума, куда могу заходить через мобильный браузер, добавлять/удалять/изменять там номера жильцов нашего дома, делать пометки, с привязкой к квартире, ФИО, номеру телефона, номеру авто. Есть фотографии авто, лог въездов через шлагбаум с фото. В будущем, по логам посмотрю кто создает наибольшую нагрузку на шлагбаум — тот и будет оплачивать ремонт :)
Ну и бонусом получил WIFI-точку на парковке возле дома.
Цена вопроса — 0 рублей и выходной за компом.
Автор: trublast