Как я Asterisk'ом шлагбаумы открывал

    image

    Добрый день, жители хабра.

    Хочу поделиться интересным, на мой взгляд, вариантом использования Asterisk.

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

    Вот с такими исходными данными мне предстояло работать, что из этого вышло — под катом.



    Поразмыслив над вариантами реализации, я в который раз понял, что и здесь Asterisk поможет решить данную проблему. Что в конечном итоге требуется клиенту:
    • Возможность «спрятать» номер сим-карты, установленной в шлагбауме
    • Формирование «белого списка» номеров, которым можно открывать шлагбаум
    • Возможность максимально просто добавлять/удалять/просматривать базу номеров «белого» списка
    • Отчетность по открытиям шлагбаума и звонкам с номеров, которых нет в списке


    Схема реализации проекта получилась следующая:

    1. В шлагбауме меняются сим-карты
    2. У SIP-провайдера покупаются номера, на которые будут звонить владельцы парковочных мест, тем самым мы добавляем новый уровень абстракции: никто не знает номера сим-карт (и так как симка в шлагбауме не делает Answer(), то чарджить деньги за звонки провайдер с нас не будет)
    3. Реализуем функцию «белого» списка, то есть получаем вторую ступеньку защиты: во-первых нужно знать sip-номер, на который звонить, а во-вторых вашему номеру должен быть разрешен звонок на сим-карту
    4. Реализуем возможность манипулирования «белым» списком через простенький веб-интерфейс
    5. Каждое утро отправляем ответственному за контроль доступа сотруднику отчеты на e-mail


    Итак, приступим непосредственно к описанию процесса настройки:

    Установка LAMP

    Описано много раз, можно посмотреть например тут

    Сборка и настройка Asterisk c поддержкой ODBC

    Мануалов по установке Asterisk существует великое множество, повторяться не будем, отмечу лишь, что Asterisk должен быть собран с поддержкой odbc (установка описана для Ubuntu Server 14.04), для этого нам необходимо установить необходимые библиотеки:

    sudo aptitude install unixodbc unixodbc-dev libmyodbc
    


    И собрать Asterisk, не забыв поставить в menuselect галочки напротив res_odbc, func_odbc. Далее необходимо прописать загрузку модуля odbc:

    vim /etc/asterisk/modules.conf
    load => res_odbc.so ;;прописываем загрузку модуля ниже секции [modules]
    


    В файле /etc/odbcinst.ini прописываем пути для драйверов:
    [MySQL]
    Description = MySQL driver
    Driver = /usr/lib/x86_64-linux-gnu/odbc/libmyodbc.so
    Setup = /usr/lib/x86_64-linux-gnu/odbc/libodbcmyS.so
    CPTimeout =
    CPReuse =
    


    В файле /etc/odbc.ini описываем параметры odbc-подключения:

    [asterisk-barrier]
    Driver = MySQL
    Description = Connector/ODBC 3.51 Driver DSN
    Server = localhost
    Port = 3306
    User = root
    Password = VeryStrongPassword
    Database = barrier
    Socket = /var/run/mysqld/mysqld.sock
    Charset = utf8
    


    В файле /etc/asterisk/res_odbc.conf описываем параметры odbc-подключения:
    [asterisk-barrier]
    enabled => yes
    dsn => asterisk-barrier
    username => root
    password => VeryStrongPassword
    pooling => no
    limit => 1
    pre-connect => yes
    
    


    Для корректного отображени русских символов раскомментируем в /etc/php5/apache2/php.ini строчку:
     default_charset = "UTF-8"
    


    Далее в MySQL создаем необходимую нам базу и таблицу:


    mysql -uroot -pVeryStrongPassword
    


    create database barrier;
    use barrier;
    CREATE TABLE `numbers` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `phone` varchar(30) NOT NULL,
      `name` varchar(30) NOT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ;
    
    


    Добавляем в созданную табличку наш «белый» список номеров:

    INSERT INTO `numbers` (`phone`, `name`) VALUES ('79260000000','Иван Иванов'), ('79261111111','Петр Петров'); и так далее
    


    После этого перезапускаем Asterisk:

    /etc/init.d/asterisk restart
    


    И проверяем корректность настройки odbc, для этого в консоли asterisk пишем:

    odbc show all
    


    Если все правильно, то мы должны увидеть нечто подобное:


    Далее настраиваем провайдеров в sip.conf:
    
    ;; строка регистрации указана в общем виде, уточняйте у вашего провайдера вид строки регистрации
    register => login:password@sip.provider.com/74990000000
    
    register => login:password@sip.provider.com/74991111111
    
    [prov1]
    accept_outofcall_messages=no
    host=sip.provider.com
    type=peer
    secret=YourPass
    defaultuser=YourLogin
    fromdomain=sip.provider.com
    fromuser=YourLogin
    canreinvite=no
    jbenable=no
    faxdetect=yes
    t38pt_udptl=yes,fec,maxdatagram=400
    context=from-trunk
    dtmfmode=auto
    port=5060
    disallow=all
    allow=g722
    allow=ulaw
    allow=alaw
    insecure=port,invite
    qualify=yes
    sendrpid=yes
    
    
    [prov2]
    accept_outofcall_messages=no
    host=sip.provider.com
    type=peer
    secret=YourPass
    defaultuser=YourLogin
    fromdomain=sip.provider.com
    fromuser=YourLogin
    canreinvite=no
    jbenable=no
    faxdetect=yes
    t38pt_udptl=yes,fec,maxdatagram=400
    context=from-trunk
    dtmfmode=auto
    port=5060
    disallow=all
    allow=g722
    allow=ulaw
    allow=alaw
    insecure=port,invite
    qualify=yes
    sendrpid=yes
    
    



    В func_odbc.conf пишем функцию, которая будет искать в MySQL номер вызывающего абонента и возвращать нам его имя
    [MOBILE]
    dsn=asterisk-barrier
    readsql=SELECT name FROM numbers  WHERE phone = '${CALLER}'
    



    В extensions.conf пишем диалплан, согласно которому, если номер есть в базе мы будем перенаправлять вызов на номер сим-карты шлагбаума, в противном случае - отбой
    
    [from-trunk]
    
    ;;входящие звонки от провайдера
    exten => 74990000000,1,NoOp(Incoming call from ${CALLERID(num)} to prov1)
    same => n,Set(CALLER=${CALLERID(num)})           ;;кладем номер вызывающего абонента в переменную CALLER, с которой будет работать функция из func_odbc
    same => n,Set(result=${ODBC_MOBILE()})          ;;выполняем sql-запрос, проверяющий наличие номера в базе и возвращающий имя
    same => n,Set(foo=${ISNULL(${result})})         ;;если запрос корректен и номер есть в базе, то функция ISNULL вернет 0, если нет - 1
    same => n,GotoIf($["${foo}" = "0"]?6:7)           ;; проверяем значение переменной, если 0 - идем на приоритет 6, 1 - приоритет 7
    same => n,GoTo(barrier1,s,1)        ;;переходим в контест вызова номера сим-карты
    same =>n,Hangup()          ;;отбиваем вызов, потому что номера нет в базе
    
    ;;аналогично для второго номера
    exten => 74991111111,1,NoOp(Incoming call from ${CALLERID(num)} to prov2)
    same => n,Set(CALLER=${CALLERID(num)})
    same => n,Set(result=${ODBC_MOBILE()})
    same => n,Set(foo=${ISNULL(${result})})
    same => n,GotoIf($["${foo}" = "0"]?6:7)
    same => n,GoTo(barrier2,s,1)
    same => n,Hangup()
    
    exten => h,1,System(echo "Звонок с номера ${CALLERID(NUM)} которого нет в базе  ${STRFTIME(${EPOCH},,%d.%m.%Y в %H:%M)}"  >> /tmp/FAIL.txt) ;; после того как повешена трубка, пишем в текстовый файлик, что была попытка открыть шлагбаум с номера, которого нет в базе
    
    
    [barrier1]
    
    exten => s,1,Dial(SIP/prov1/79150000000) ;;если звонок пришел в этот контекст, значит номер есть в базе и мы звоним на номер сим-карты шлюза
    same => n,Hangup()
    
    exten => h,1,System(echo "Открытие шлагбаума  с номера ${CALLERID(NUM)}  ${result} ${STRFTIME(${EPOCH},,%d.%m.%Y в %H:%M)}"  >> /tmp/SUCCESS.txt) ;;пишем в текстовый файл и удачные попытки открытия, так как они нужны для отчетности
    
    
    [barrier2]
    ;;все то же самое для второй сим-карты
    exten => s,1,Dial(SIP/prov2/7915111111)
    same => n,Hangup()
    
    exten => h,1,System(echo "Открытие шлагбаума  с номера ${CALLERID(NUM)}  ${result} ${STRFTIME(${EPOCH},,%d.%m.%Y в %H:%M)}"  >> /tmp/SUCCESS.txt)
    
    



    Отправка отчетов на e-mail


    Для реализации отправки отчетов воспользуемся почтовым клиентом mutt ввиду того, что он умеет аттачить файлы в почту. Устанавливаем mutt:

    sudo aptitude install mutt
    


    И в cron добавляем действия по отправке файлов отчетности (я делаю это в 9 утра и в 9.01 удаляю их, чтобы в отчетности зановый день были только свежие данные)

    sudo crontab -e
    
    0 9 * * * echo "Cм. отчет в прикрепленном файле" | mutt -a "/tmp/FAIL.txt" -s "Неудачные попытки дозвона" -e "set from="asterisk@domen.ru"" -e "set realname='Asterisk'" -- recipient@client.ru
    1 9 * * * rm -rf /tmp/FAIL.txt
    0 9 * * * echo "См. отчет в прикрепленном файле" | mutt -a "/tmp/SUCCESS.txt" -s "Открытия шлагбаума" -e "set from="asterisk@domen.ru"" -e "set realname='Asterisk'" -- recipient@client.ru
    1 9 * * * rm -rf /tmp/SUCCESS.txt
    
    


    В результате клиенту на почту приходят отчеты вот такого вида:



    Теперь реализуем возможность манипуляции с «белым» списком. Да, безусловно есть phpmyadmin, но стояла задача реализовать максимально простой интерфейс, неперегруженный лишними полями, которые могли бы «напугать» оператора. Для этого на php было написано несколько простейших форм, которые позволяют просматривать номера в базе, удалять и добавлять их (на правильность кода не претендую, на php кодить не умею чуть больше, чем полностью). Что из этого вышло — на скриншотах ниже:

    Главная страница



    Добавление номера в базу



    Удаление номера из базы



    Просмотр списка номеров в базе



    И непосредственно сам код:

    index.php
    <!DOCTYPE HTML>
    <html>
     <head>
      <meta charset="utf-8">
      <title>Управление списком</title>
     </head>
     <body>
      <p><a href="add_form.php">Добавить номер в базу</a></p>
      <p><a href="remove_form.php">Удалить номер из базы</a></p>
      <p><a href="show.php">Показать список номеров</a></p>
     </body>
    </html>
    
    
    



    add_form.php
    <p>
    <table width="300" border="1" bordercolor="#630000" cellpadding="3" cellspacing="0">
    <tr><td>
    <font size="2" face="verdana,arial,georgia" color="#630000">Введите имя и номер.</font>
    <form action="insert.php" method="post">
    <input type="text" placeholder="прим. Иван Петров" name="phonee" value="" />
    <input type="text" placeholder="прим. 79261234567" name="namee" value="" />
    <input type="submit" name="send" value="Отправить" />
    </form>
    </td></tr>
    </table>
    </p>
    <p><a href="index.php">На главную</a></p>
    
    



    insert.php
    <?PHP
    $host = "localhost";
    $user = "root";
    $database = "barrier";
    $pass = "mahapharata";
    $name = $_POST["namee"];
    $phone = $_POST["phonee"];
    // коннектимся к бд
    $connect = mysql_connect($host, $user, $pass);
    mysql_select_db($database);
    mysql_set_charset( 'utf8' );
    mysql_query("INSERT INTO `numbers` (`phone`, `name`) VALUES ('".$name."','".$phone."')");
    mysql_close($connect);
    ?>
    <input type="button" value="Назад" onclick="history.back()">
    
    



    remove_form.php
    <p>
    <table width="300" border="1" bordercolor="#630000" cellpadding="3" cellspacing="0">
    <tr><td>
    <font size="2" face="verdana,arial,georgia" color="#630000">Введите номер, который нужно удалить.</font>
    <form action="delete.php" method="post">
    <input type="text" placeholder="прим. 79261234567" name="phonedel" value="" />
    <input type="submit" name="send" value="Удалить" />
    </form>
    </td></tr>
    </table>
    </p>
    <p><a href="index.php">На главную</a></p>
    
    



    delete.php
    <?php
    $servername = "localhost";
    $username = "root";
    $password = "mahapharata";
    $dbname = "barrier";
    $phone = $_POST["phonedel"];
    
    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
    // Check connection
    if ($conn->connect_error) {
        die("Connection failed: " . $conn->connect_error);
    }
    
    // sql to delete a record
    $sql = "DELETE FROM numbers  WHERE phone='".$phone."'";
    
    if ($conn->query($sql) === TRUE) {
        echo "Запись успешно удалена\r\n";
    } else {
        echo "Проверьте параметры подключения к базе: " . $conn->error;
    }
    
    $conn->close();
    ?>
    <input type="button" value="Назад" onclick="history.back()">
    
    



    show.php
    <?php
    $servername = "localhost";
    $username = "root";
    $password = "mahapharata";
    $dbname = "barrier";
    
    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
    $conn->set_charset("utf8");
    // Check connection
    if ($conn->connect_error) {
        die("Connection failed: " . $conn->connect_error);
    }.
    
    $sql = "SELECT id, phone, name FROM numbers";
    $result = $conn->query($sql);
    
    if ($result->num_rows > 0) {
        // output data of each row
        while($row = $result->fetch_assoc()) {
    echo " " . $row["name"]. " " . $row["phone"]. "<br>";
        }
    } else {
        echo "В базе нет номеров";
    }
    $conn->close();
    ?>
    
    <p><a href="index.php">На главную</a></p>
    
    



    Не забываем прикрутить авторизацию с помощью htaccess:
    htpasswd -c passwordfile username
    


    Вводим пароль в диалоге, который предлагает htpasswd и получаем файл «passwordfile» с юзером «username» и сгенерированным зашифрованным паролем.

    На мой взгяд получилась неплохая реализация, в которой Asterisk используется не как стандартная IP-АТС, а в качестве своеобразного СКУДа. Над проектом было интересно работать, а учитывая популярность подобных шлагбаумов, может кому-то и пригодится:)

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 24

      +1
      значения из POST не стоит конкатенировать в строку запроса MySQL.
        0
        к сожалению моего знания php хватает только на такие запросы, был бы благодарен, если бы подсказали как правильно это делать
      +1
      Попробуйте в удаление номера написать: ' or 1
      :)
      Замените строчки:
      <?php
      // ...
      $name = mysql_escape_string($_POST["namee"]);
      $phone = mysql_escape_string($_POST["phonee"]);
      


      А так, прикольно конечно )
        0
        Большое спасибо за комментарии к коду, очень полезно в моем случае, когда знаешь php чуть меньше, чем совсем не знаешь, потестирую с вашими изменениями:)
          +1
          Ну и следовало бы использовать модуль mysqli вместо deprecated модуля mysql,
          0
          Если симка не будет использовать платные услуги вообще — заблокируют. :)
            0
            Да, спасибо, немаловажный момент. Забыл отметить, что на симке подключены самые дешевые услуги рассылки, снимается порядка 1р в день и симка не блокируется.
            0
            Так надо встроить в шлагбаум функцию отправки одной смс в месяц с «42» и всё решится за 100 рублей в год (не считая разработки) :)
              0
              Одной SMS каждые 89 дней достаточно. Впрочем, куда проще взять корпоративную SIM-карту (да и ещё в M2M исполнении, чтобы не сдохла при минус сорока) — их не отключают.
            0
            Предлагаю расширение функционала. Для номеров, которых нет в списке доступа дать возможность донабрать пин-код для открытия шлагбаума. Может быть так, что ваш телефон разрядился, а заехать нужно. Все вопросы по несанкционированному доступу могут быть решены с помощью отчета.
              0
              Я так понимаю, там где-то есть человек, которого можно вызвать и попросить открыть шлагбаум. Вариант с пинкодом возвращает систему в начальное положение: «Но, как известно, если номер знает один — его знают все, поэтому платная парковка хаотично превращается в бесплатную.»
                0
                Допиливаем в систему ежемесячную смену пин-кода. с рассылкой его в белый список (денег конечно стоит, но всё же)
                  0
                  Но что это изменит? Номер телефона для открытия шлагбаума узнавали явно не перебором. «Свои» из числа оплативших стоянку сообщали номер. Так же будут каждый месяц сообщать ПИН.
                    0
                    я упустил из виду полную платность парковки, я думал это условно ограниченная парковка.
                    тогда только белые списки и звонок диспетчеру в случае нестандартных ситуаций.
              0
              Еще можно добавить систему оплаты — пошлите платное смс и дверка откроется Ж-).
                0
                Не было смысла писать свой редактор, т.к. есть такое.
                  0
                  Спасибо большое, не знал
                  0
                  Клевое применение астериска, реально и практично.

                  Если бы вы ваш код оформили на гитхаб, то я бы его форкнул и немного подправил.

                  Еще для разработки прикладных вещей на астериске мне кажется удобнее использовать AGI, а не ODBC (моя заметка на эту тему).

                  Планируете ли вы еще другим клиентам ставить такие системы?

                    +1
                    Добрый день, спасибо за положительный отзыв.

                    Залил на гитхаб, вот тут.

                    По поводу
                    Еще для разработки прикладных вещей на астериске мне кажется удобнее использовать AGI, а не ODBC

                    имхо, если единственное назначение скрипта коннектиться к базе и брать оттуда одно значение, то AGI становится просто лишним звеном, нецелесообразно. В вашей статье все-таки у скриптов есть логика помимо коннекта к базе, у вас это целесообразно. Ну и немаловажно, что вам, как программисту, удобно вынести логику в программирование, мне, как нубу в программировании — это гораздо сложнее:)
                    Планируете ли вы еще другим клиентам ставить такие системы?
                    в том виде, в котором это сейчас существует, это не коробочный продукт, который заинтересовал бы заказчика, но идеи по развитию проекта безусловно есть:)
                      0
                      У вас логика тоже есть. Я на AGI подсел полгода назад плюс еще быстрота и удобство node.js для разработки и разворачивания. В общем, задумаетесь над продуктом, то можно сделать очень грамотно.

                      Более того может и не продукт для установки заказчику, а система для обслуживания этих заказчиков. Т.е. вы делаете сервис, где добавляете sip-номера и gsm-номера для разных парковок и разных заказчиков. По всей стране. (тут надо остановиться, а то и до Новых Васюков дойду: )

                      На гитхабе есть ссылка на ворота doorhan. К ним такой должен быть GSM модуль: nano-shlagbaum.ru/products/doorhan_gsm? Другие распространенные шлагбаумные системы тоже имеют возможность открывать шлагбаум по звонку?

                        0
                        К сожалению вопрос технического оснащения шлагбаумов gsm-модулями для меня пока темный лес, поинтересуюсь у заказчика, надеюсь они более подкованы, сообщу вам если будет какая-то инфа.

                  Only users with full accounts can post comments. Log in, please.