Pull to refresh

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

Reading time9 min
Views46K
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-АТС, а в качестве своеобразного СКУДа. Над проектом было интересно работать, а учитывая популярность подобных шлагбаумов, может кому-то и пригодится:)
Tags:
Hubs:
+9
Comments24

Articles

Change theme settings