Хабрахабр

[Из песочницы] Конференц-комнаты на базе Asterisk

Предисловие

Добрый день.

Это я сейчас про РУ-комьюнити. На написание данной статьи меня сподвигло 2 вещи: малое количество или вовсе отсутствие современных рабочих примеров по «фишкам» Asterisk, а так же нежелание специалистов делиться этими самыми «фишками» с остальными. Сами же темы форумов, созданные в 2005-2010 годах, сильно устарели и иногда что то уже выпилено из текущей версии астериска, а что то надо очень сильно переделать, чтобы заработало.
Так вот.
В следствие отказа от CUCM в пользу Астериска, руководством была поставлена задача сохранить особо популярные у пользователей сервисы в максимально первозданном виде, дабы не третировать людей. Всякие «Деды» на форумах скорее обольют тебя помоями и отправят читать книжки десятилетней давности, чем дадут мало мальски полезную информацию. К тому моменту с Астериском я уже был знаком, но не столь глубоко, по этому на перебор всевозможных вариантов конференции у меня ушло около полутора недель, а на окончательное решение натолкнула вообще другая задача. Одним из таких было и создание конференций.

Я же предлагаю кое что не настолько громоздкое. Проблема заключается в том, что из похожего на решение есть статья с устаревшей meetme, а так же какой то монстр, которого у меня так и не удалось заставить работать.

Мякотка

Описывать что такое confbridge, за что отвечают секции в том или ином конфиге и что это за опция такая, я не буду, эта информация как раз имеется и актуальна. Сейчас про решение в целом.

Основная проблема, что функция channelredirect работает не так как хотелось бы. Задача: сделать так, чтобы конференцию можно было создать во время разговора, а затем пригласить туда еще абонентов. И мне совершенно не понятно, почему в большинстве мануалов все пытаются решить задачу только через диалплан, игнорируя возможность астериска работать с внешними скриптами и ami. То есть если выполнять ее из диалплана во время разговора, то один из каналов улетит куда надо, а второй разрушится, а лазать по всему диалплану на 2к строк и прописывать на Dial'ах опцию g было лениво.

Астериск 14. Итак. 4.

Скрипт конференции на 2 варианта(c комментариями):

conference.php

<?php //Готовим коннект к астеру
$host = "192.168.1.1";
$port = "5038";
$timeout = "10"; $user = "conference";
$pass = "1111"; //для того, чтобы не разбивать 1 задачу на 2 скрипта
$kusok = $argv[1]; //Кусок для создания конференции во время разговора
if ($kusok == 1){ //Получаем переменные
$channel = $argv[2];
$bridgepeer = $argv[3];
$confnum = $argv[4]; print_r($bridgepeer);
print_r($confnum); //Коннект $sconn = fsockopen ($host, $port, $timeout) or die ("Connection to $host:$port failed!"); fputs ($sconn, "Action: Login\r\n"); fputs ($sconn, "Username: $user\r\n"); fputs ($sconn, "Secret: $pass\r\n\r\n"); //Задаем переменные канала fputs ($sconn, "Action: Setvar\r\n"); fputs ($sconn, "Channel: $channel\r\n"); fputs ($sconn, "Variable: CONFNUM\r\n"); fputs ($sconn, "Value: $confnum\r\n\r\n"); fputs ($sconn, "Action: Setvar\r\n"); fputs ($sconn, "Channel: $bridgepeer\r\n"); fputs ($sconn, "Variable: CONFNUM\r\n"); fputs ($sconn, "Value: $confnum\r\n\r\n"); //Редиректим fputs ($sconn, "Action: Redirect\r\n"); fputs ($sconn, "Channel: $bridgepeer\r\n"); fputs ($sconn, "ExtraChannel: $channel\r\n"); fputs ($sconn, "Context: service_code-ael\r\n"); fputs ($sconn, "Exten: conference\r\n"); fputs ($sconn, "Priority: 1\r\n\r\n"); fputs($sconn, "Action: Logoff\r\n\r\n");
sleep(2);
fclose ($sconn);
} //Кусок для добавления нового участника
if ($kusok == 2) { //Получаем переменные
$confnum = $argv[2];
$inviten = $argv[3]; $sconn = fsockopen ($host, $port, $errno, $errstr, $timeout) or die ("Connection to $host:$port failed!"); //Подключаемся fputs ($sconn, "Action: Login\r\n"); fputs ($sconn, "Username: $user\r\n"); fputs ($sconn, "Secret: $pass\r\n\r\n"); //Звоним и закидываем в конфу fputs ($sconn, "Action: Originate\r\n"); fputs ($sconn, "Channel: Local/".$inviten."@out-ael\r\n"); fputs ($sconn, "Context: service_code-ael\r\n"); fputs ($sconn, "Exten: conference\r\n"); fputs ($sconn, "Priority: 1\r\n"); fputs ($sconn, "Variable: CONFNUM=".$confnum."\r\n\r\n"); fputs($sconn, "Action: Logoff\r\n\r\n");
sleep(2);
fclose ($sconn);
} }

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

Коротко и не пересекается с основной нумерацией. Для того, чтобы создать конференцию, я выбрал комбинацию *1.

Добавляем в features.conf вызов скрипта с передачей в него требуемых переменных

[applicationmap]
conference => *1,self,System(/usr/bin/php /home/script/conference.php 1 $ ${BRIDGEPEER} ${CALLERID(num)})

Затем, чтобы это срабатывало, создаем в диалплане в секции [globals] переменную и добавляем нашу фичу

DYNAMIC_FEATURES=conference

Для добавления в уже созданную конференцию новых участников, потребуется прописать код в confbridge.conf

[default_menu]
type = menu
*1=dialplan_exec(service_code-ael,conference_add,1)

Ну а теперь самое вкусное, extensions.ael:

Для создание конференции (сюда адресует php скрипт оба разговорных канала):

conference => { ConfBridge(${CONFNUM},,,default_menu); }

Для добавления нового пользователя (сюда адресует dialplan_exec):

conference_add => { Read(INVITEN,dial,11,i); System(/usr/bin/php /home/script/conference.php 2 ${CALLERID(num)} ${INVITEN}); }

Все. Никаких килотонн кода в диалплане. Все емко. *1 в разговоре и вы в конфе, еще раз *1 гудок и набор номера, кого добавить.

Наросты

Гонимый пожеланиями пользователей, я стал развивать данную фичу.

Следующим стала возможность создавать конференции с нуля (не из разговора), а так же заходить в уже созданные конференции по их номеру, а не ждать приглашающего звонка

Добавляем в диалплан:

_*1XXXX => { NoOp(${CONFCHAN}); Set(__CONFNUM=${EXTEN:2}); System(/usr/bin/php /home/script/conference.php 3 ${CONFCHAN} ${CONFNUM} ); }

Добавляем в скрипт:

conference.php

//Для создание конференций с нуля
if ($kusok == 3){ //Получаем переменные
$channel = $argv[2];
$confnum = $argv[3]; //Коннект $sconn = fsockopen ($host, $port, $timeout) or die ("Connection to $host:$port failed!"); fputs ($sconn, "Action: Login\r\n"); fputs ($sconn, "Username: $user\r\n"); fputs ($sconn, "Secret: $pass\r\n\r\n"); //Задаем переменные канала fputs ($sconn, "Action: Setvar\r\n"); fputs ($sconn, "Channel: $channel\r\n"); fputs ($sconn, "Variable: CONFNUM\r\n"); fputs ($sconn, "Value: $confnum\r\n\r\n"); //Редиректим fputs ($sconn, "Action: Redirect\r\n"); fputs ($sconn, "Channel: $channel\r\n"); fputs ($sconn, "Context: service_code-ael\r\n"); fputs ($sconn, "Exten: conference\r\n"); fputs ($sconn, "Priority: 1\r\n\r\n"); fputs($sconn, "Action: Logoff\r\n\r\n");
sleep(2);
fclose ($sconn);

Так же пришлось доработать строчку _*X.

_*X. => { set(__CONFCHAN=${CHANNEL}); Dial(Local/${EXTEN}@service_code-ael);

Теперь чтобы войти в конференцию или создать ее с нуля, просто совершается звонок на *1 и номер, например *15234.

Это когда большим начальникам лень всех добавлять вручную, а хочется нажать одну кнопку и все в сборе. Финальной мутацией этого сервиса пока является так называемая «групповая конференция». Для этого я решил сделать отдельные сервис коды (*1XX), чтобы людям и самому не путаться.

Диалплан:

*100 => { Set(CONFNUM=${CALLERID(num)}); System(/usr/bin/php /home/script/groups.php 0 ${CONFNUM}); ConfBridge(${CONFNUM},,,default_menu); }

Сам скрип сбора участников:

groups.php

//Функция звонка
function call ($group, $confnum) { $many = count($group); //Цикл разбора массива номеров группы на номера for ($i=0; $many>$i; $i++) { //достаем из массива номера $num = trim(array_shift($group[$i])); //Звоним system("asterisk -rx \"channel originate Local/$num@out-ael application ConfBridge $confnum\""); }
} //функция получения массива группы function conf_group ($groupid) { //Коннектим в базу $opt = array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ); $pdo = new PDO("odbc:mssql_asterisk, "asterisk, "121212", $opt); //Селектим из базы $sql = "SELECT extension FROM [asterisk].[dbo].[conf_groups] where groupid = $groupid"; $select = $pdo->query($sql); $result = $select->fetchAll(); //Дисконенктим $pdo = NULL; return $result;
} //Получаем значение группы, которой надо позвонить
$groupid = $argv[1]; if ($groupid == 0){ //Получаем номер конференции $confnum = $argv[2]; //Получаем массив номеров $group=conf_group($groupid); //Звоним call($group, $confnum); }

Все группы хранятся в базе данных по структуре «Группы, номер, имя, описание». Если появляется новая группа, просто копипастится кусок с if в коде и exten в диалплане. Циферка меняется на +1 и все готово.

А как правило у больших боссов большие телефоны. Теперь для сбора, например, всех директоров на совещание, генеральному всего лишь потребуется набрать *100. Кнопку нажал — совещание собрал. Следовательно биндим *100 на любую клавишу speeddial'а, подписываем как «директора» и пользователь вообще не заморачивается, что ему набрать.

Если появляется новая группа, просто копипастится кусок с if в коде и exten в диалплане. Все группы хранятся в базе данных по структуре «Группы, номер, имя, описание». Циферка меняется на +1 и все готово.

Теперь предвосхищая ваши вопросы:

Потому что средствами диалплана у меня так и не получилось сделать вменяемый редирект обоих каналов, не растеряв их по дороге. Почему скрипты и ami? В ami же в функции redirect можно прицепить доп канал + задать ему переменную (например номер конференции, чтобы он тоже мог кого то в нее добавить).

Это удобно, когда всяких фич у вас становится больше пары штук. Так же вы могли заметить, что я вынес фичи в отдельный контекст service_code-ael. и адресую в этот контекст. Я решил делать их через *, следовательно в любом контексте я просто пишу _*X. А данный функционал пришелся пользователям по душе. Возможно, кто то найдет решение изящнее, но я за несколько месяцев так и не нашел.

Ну потому что он структурированней и читать его легче
и понятнее. Почему ael, а не conf? До lua я еще не дорос. Одна функция gotoif чего стоит.

Проблема в том, что выполняя кучу originate подряд через ami, система ждет, пока завершится предыдущий, чтобы дать следующий. Почему в примере массового сбора originate сделаны через bash, а не через AMI? Можно будет до вечера ждать сбора. А если никто не берет трубку, а там 20 секунд no_ans и таких 5 штук?

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть