Заставляем Asterisk вызвать и скоммутировать два внешних номера

Удалёнка в наши дни — штука необходимая. Вот и операторы call-центров нынче не сидят на месте. Возникла задача предоставить этим операторам возможность выполнять обзвоны клиентов через офисный сервер. Но вот незадача: качество мобильного интернета не всегда позволяет работать через софтфон. И с первой же недели тестирований мы нарвались на кучу жалоб со стороны операторов, у которых то пропадал звук, то появлялись какие-либо артефакты (эхо, отставание и т.д.) то и вовсе ничего не работало.

Из доступных решений этой проблемы у нас было только одно: осуществлять соединение операторов с клиентами через голосовую мобильную связь, используя Asterisk в качестве транзитного коммутатора. Наверное, у кого-то мог возникнуть вопрос: а почему бы просто не позвонить с одного телефона на другой?
Ну, потому, что звонки должны записываться, а клиенты ложны видеть номер call-центра, а не кучу разных номеров операторов.

Самый просто способ это сделать — позволить операторам набирать номер вызываемого абонента после дозвона до коммутатора. Но ручной набор номера каждого клиента вызывал бы у операторов неописуемый баттхёрт. По этой причине такой вариант мы сразу же забраковали, а вместо него было предложено реализовать API, которое будет принимать два внешних номера в качестве аргументов и заставлять Asterisk вызывать и коммутировать их друг с другом.

Далее, разберём реализацию этой задачи.

Сперва, добавим контекст, который будет обрабатывать вызовы, инициализируемые скриптом.

Открываем файл /etc/astersk/extensions_custom.conf (актуально для FreePBX) и добавляем туда следующее:

[commutate]
exten => _X.,1,Dial(SIP/gorod/${EXTEN});
exten => _X.,n,Macro(dialstatus,s,1)
exten => _X.,n,Hangup ;

[macro-dialstatus]
exten => s,1,Answer
exten => s,n,Goto(s-${DIALSTATUS},1)
exten => s-NOANSWER,1,Hangup
exten => s-CONGESTION,1,Congestion
exten => s-CANCEL,1,Hangup
exten => s-BUSY,1,Playtones(425/375,0/375)
exten => s-BUSY,n,Busy(7)
exten => s-BUSY,n,Hangup
exten => s-CHANUNAVAIL,1,Hangup

Где «gorod» — это имя транка, через который будет осуществляться вызов второго абонента.

В случае работы с драйвером PJSIP вторая строка будет выглядеть вот так:

exten => _X.,1,Dial(PJSIP/${EXTEN}@gorod);

Перезагружаем конфиги астериска командой core reload в CLI

Если вы используете FreePBX, то у вас уже включен менеджмент интерфейс. Данные для взаимодействия с ним можно посмотреть в файле /etc/asterisk/manager.conf

cat /etc/asterisk/manager.conf

[general]
enabled = yes
port = 5038
bindaddr = 0.0.0.0
displayconnects=no ;only effects 1.6+

[admin]
secret = supersecretpass
deny=0.0.0.0/0.0.0.0
permit=127.0.0.1/255.255.255.0
read = system,call,log,verbose,command,agent,user,config,command,dtmf,reporting,cdr,dialplan,originate,message
write = system,call,log,verbose,command,agent,user,config,command,dtmf,reporting,cdr,dialplan,originate,message
writetimeout = 5000

#include manager_additional.conf
#include manager_custom.conf

Тут нас в основном интересует логин и пароль менеджера.

Теперь, нужно создать php скритп, который и будет нашим API. Создаём файл с любым удобным именем в веб директории.

vi /var/www/html/call.php

<?php
if($_REQUEST['n1'] >= 100000000 && $_REQUEST['n2'] >= 100000000 && $_REQUEST['n1'] < 1000000000 && $_REQUEST['n2'] < 1000000000){
	$n1 = $_REQUEST['n1'];
	$n2 = $_REQUEST['n2'];
	
	$timeout = 10;
	$socket = fsockopen("localhost","5038", $errno, $errstr, $timeout);
	          fputs($socket, "Action: Login\r\n");
              fputs($socket, "UserName: admin\r\n");
              fputs($socket, "Secret: supersecretpass\r\n\r\n");
			  $wrets=fgets($socket,128);
				
	echo $wrets."\r\n";
	
	            fputs($socket, "Action: Originate\r\n" );
                fputs($socket, "Channel: local/".$n1."@from-internal\r\n" );
				fputs($socket, "MaxRetries: 0\r\n" );
				fputs($socket, "WaitTime: 30\r\n" );
                fputs($socket, "Exten: $n2\r\n" );
                fputs($socket, "Context: commutate\r\n" );
                fputs($socket, "Priority: 1\r\n" );
                fputs($socket, "Async: yes\r\n\r\n" );

                $wrets=fgets($socket,128);
                echo $wrets;
	
	
	//echo "TRUE";
} else {
	echo 'FALSE';
}
?>

Не бросайте в меня тапками за совершенно идиотский способ проверки аргументов. В моём случае, мне нужно вылавливать девятизначные номера, которые никак не могут начинаться с нуля, а городить регулярку мне было тупо лень. ;)) В любых иных случаях можно сделать проверку более кошерным путём и даже добавить разрешённый список номеров операторов.

Пробуем вызвать скрипт по ссылочке, типа https://[адрес сервера]/call.php?n1=XXXXXXXXX&n2=XXXXXXXXX предварительно заменив XXXXXXXXX на номера оператора и абонента.

Если всё настроено верно, то сервер направит звонок на номер n1 и в случае успешного соединения будет вызван номер n2. Далее, если второй вызов будет отвечен, то оператор на канале n1 будет соединён с абонентом на номере n2. В ином случае вся сессия будет прервана сервером.

Этот скрипт так-же можно использовать для виджета web callback, заранее указав номера операторов или групп вызова в качестве аргумента n1.