Новости
Обзоры и тесты
Техно
Советы
Разное
Главная » Техно

Перенос соответствия внутренних и внешних номеров в БД MySQL для Asterisk

Добавлено на 23.01.2017 – 20:15

В материале приведён пример переноса соответствий внутренних и внешних номеров из файла extensions.conf в БД MySQL для максимального упрощения работы по перенастройке таких соответствий.
Имеется Asterisk, с более чем 200 внутренними номерами и несколькими десятками внешних номеров. Соответствие внешних и внутренних номеров(экстеншенов) в нём прописывались «дедовским» методом приведённым ниже. В контексте caller-id все правила имеют приоритет 1. Контексты для исходящей связи, например, mn(международный) организуются по видам связи, могут включать контексты: внутренняя (internal-out), местная (local-out), зоновая (mobile-out), междугородняя (mg-out) и международная (mn-out),входящая связь (RTU-IN), в этих контекстах дайлпланы начинаются с приоритета 2. Внутренним SIP-абонентам, присваивается контекст для исходящей связи, например, mn. Для исходящей связи: «ловим» вызовы по маске А-номера, например,  _26[23], и одинаковой маске Б-номеров _9X., затем подставляем соответствующие внешние А-номера и отправляем на внешний SIP-транк с именем RTU.

[caller-id]
exten => _9X./_26[2-3],1,Set(CALLERID(num)=78122223344) ;Внешний АОН 78122223344 для внутреннего номера 262 и 263
exten => _9X./_264,1,Set(CALLERID(num)=78122223355) ;Внешний АОН 78122223355 для внутреннего номера 264
exten => _9X./_31X,1,Set(CALLERID(num)=78122223366)
[mn]
include => caller-id
include => RTU-IN
include => local-out
include => internal-out
include => mobile-out
include => mg-out
include => mn-out

Для входящей связи: в контексте RTU-IN «ловим» Б-номер на который пришёл вызов, затем, подставляем нужное количество внутренних номеров, через запятую, используя функцию массива ARRAY. Затем, переходим в универсальный контекст HG, где вызываем одновременно внутренние номера в группе, полученные из переменных m1-mN, затем переходим в контекст СHECK_BUSY_FORWARD, где проверяем переадресацию по занятости.

[RTU-IN]
exten => 78122223377,1,Set(ARRAY(m1,m2,m3,m4)=257,258,259,260)  ;Групповой вызов на 257 258 259 260
same => n,Goto(HG,${EXTEN},1)
exten => 78122223377,1,Set(ARRAY(m1)=257)  ;Групповой вызов на 257
same => n,Goto(HG,${EXTEN},1)
exten => 500,1,Set(ARRAY(m1,m2,m3,m4,m5,m6,m7,m8,m9,m10)=200,201,202,203,204,205,206,207,208,209)  ;Групповой вызов на 500 группу
same => n,Goto(HG,${EXTEN},1)
[HG]
exten => _X.,1, Answer
same => n,Set(_MONITOR_FILENAME=IN-${EXTEN}-${STRFTIME(${EPOCH},,%Y_%m_%d[%H_%M])}-${CALLERID(num)}-${EXTEN}-${UNIQUEID})
same => n, NoOp(!!!!CALLED=${CALLED})
same => n, Set(H1=${IF($["${m1}" != ""]?SIP/${m1})})
same => n, Set(H2=${IF($["${m2}" != ""]?&SIP/${m2})})
same => n, Set(H3=${IF($["${m3}" != ""]?&SIP/${m3})})
same => n, Set(H4=${IF($["${m4}" != ""]?&SIP/${m4})})
same => n, Set(H5=${IF($["${m5}" != ""]?&SIP/${m5})})
same => n, Set(H6=${IF($["${m6}" != ""]?&SIP/${m6})})
same => n, Set(H7=${IF($["${m7}" != ""]?&SIP/${m7})})
same => n, Set(H8=${IF($["${m8}" != ""]?&SIP/${m8})})
same => n, Set(H9=${IF($["${m9}" != ""]?&SIP/${m9})})
same => n, Set(H10=${IF($["${m10}" != ""]?&SIP/${m10})})
same => n,Dial(${H1}${H2}${H3}${H4}${H5}${H6}${H7}${H8}${H9}${H10},40,TtrM(monext))
same => n,Goto(CHECK_BUSY_FORWARD,${EXTEN},1)
same => n,Hangup

Если таких записей, как для входящей так и для исходящей связи не много и они не часто меняются, то такой подход вполне оправдан, но становится очень неудобно, если внешних и внутренних номеров много и их привязка постоянно меняется, да ещё и нужно постоянно менять номера для переадресаций из групп. Самый простой вариант – перенести всё эти привязки в БД, тогда сразу же будет видно все соответствия номеров без изучения кучи записей в файле extensions.conf, к тому же, можно будет редактировать записи в БД из внешней системы для повышения автоматизации процесса привязки номеров. В Asterisk отдельные контексты дайлплана можно перенести в БД MySQL при помощи realtime, но для простого восприятия и редактирования это не очень удобно. Необходимо сделать максимально простые таблицы соответствий номеров в БД, которые понятны человеку не знающему дайлплан Asterisk.

Сначала перенесем в БД соответствие внутренних и внешних номеров для исходящей связи, затем для входящей связи.

Перенос соответствия внутренних и внешних номеров для исходящей связи в БД MySQL

Создаём таблицу outbound в БД:

CREATE TABLE `outbound` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `internal` int(11) NOT NULL,
  `callerid` varchar(11) NOT NULL,
  `Notes` text CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET= utf8;

Столбцы в таблице: id — номер правила, значение инкриминируется автоматически, internal — внутренний номер, callerid — внешний А номер который будет присваиваться при вызове в ТфОП, Notes — заметка о записи. Далее, заполняем соответствиями номеров в таблице outbound.

INSERT INTO `astcdrdb`.`outbound` (
`id` ,
`internal` ,
`callerid` ,
`Notes`
)
VALUES 
(NULL , '200', '78122429945', ''),
(NULL , '201', '78122429945', ''), 
(NULL , '202', '78122429945', ''),
(NULL , '203', '78122429945', ''), 
(NULL , '204', '78122429945', ''),
(NULL , '205', '78122429945', '');

Теперь таблица outbound будет выглядеть так:

mysql> select * from outbound;
+-----+----------+-------------+-------+
| id  | internal | callerid    | Notes |
+-----+----------+-------------+-------+
|  10 |      200 | 78122429945 |       |
|  11 |      201 | 78122429945 |       |
|  12 |      202 | 78122429900 |       |
|  13 |      203 | 78122429945 |       |
|  14 |      204 | 78122429945 |       |
|  15 |      205 | 78122429945 |       |
+-----+----------+-------------+-------+
6 rows in set (0.00 sec)

К БД MySQL будем подключаться через ODBC коннектор Asterisk. Предполагается, ODBC с Asterisk уже работает.

Далее, создаём запрос в БД в /etc/asterisk/func_odbc.conf:

[GET_OUTBOUND_CID]
dsn=asterisk
readsql=SELECT callerid FROM outbound where internal='${ARG1}'

Подгрузим новую функцию ODBC, выполнив команду в bash:

asterisk -rx 'module reload func_odbc.so'

Теперь вместо десятков строк, подставим одну:

exten => _9X.,1,Set(CALLERID(num)=${ODBC_GET_OUTBOUND_CID(${CALLERID(num)})})

Если дополнительно нужно подставлять внешние А-номера через обычную запись в дайлплане, то для соответствующих А-номеров нужно задать точную маску, в нашем случае в строке с запросом CALLERID из БД маска А-номера может быть любая, в другой строке дайлплана маска А-номера проверяется:

exten => _9X./_6[5-9].,1,Set(CALLERID(num)=78122429999) 

При равном приоритете двух строк в одном контексте дайлплана будет отрабатывать строка с более точной маской /_6[5-9]. для А-номеров. Это полезно, когда есть десятки или сотни внутренних номеров с одинаковым внешним номером, смысла расписывать каждый номер в БД нет, при условии что внешний номер не будет меняться.

Дополнительно, добавим А-номер по-умолчанию = 7812350000, если внешний А-номер по какой-то причине отсутствует в ответе на запрос ODBC_GET_OUTBOUND_CID или БД MySQL не отвечает на запросы.

exten => _9X.,1,Set(CALLERID(num)=${IF($["${ODBC_GET_OUTBOUND_CID(${CALLERID(num)})}" = "" ]?7812350000:${ODBC_GET_OUTBOUND_CID(${CALLERID(num)})})})
exten => _9X./_6[5-9].,1,Set(CALLERID(num)=78120001122)
same => n,Dial(SIP/RTU/${EXTEN:1},,tTr)

Листинг вывода в консоль Asterisk:

  -- Executing [981116275136@ caller-id-odbc:1] Set("SIP/248-00014f4f", "CALLERID(num)=78123500000") in new stack
    -- Executing [981116275136@mn:5] Dial("SIP/248-00014f4f", "SIP/RTU/79627277780,,tT") in new stack
  == Using SIP RTP CoS mark 5
    -- Called SIP/RTU/79627277780
    -- SIP/RTU-00014f50 is making progress passing it to SIP/248-00014f4f

Перенос соответствия внутренних и внешних номеров для входящей связи в БД MySQL

Процесс переноса входящей связи в БД сложнее чем исходящей. Входящий вызов приходит с внешнего транка или от внутреннего абонента и направляется в контекст для входящих вызовов RTU-IN, здесь для любого входящего Б-номера делается запрос в БД для определения внутреннего номера или списка внутренних номеров на которые следует направить вызов. Далее, рассмотрим два случая: Первый — самый простой – после таймаута вызова (устанавливается в дайлплане) на группу номеров вызов завершается. Второй случай – после таймаута вызова(устанавливается в БД для каждого внешнего номера) на группу номеров, в БД проверяется разрешена ли переадресация на внешний номер, если, да то на какой внешний номер и каков таймаут вызова на него.
Используем коннектор ODBC для БД MySQL.
Создаём таблицу inbound в БД, куда мы будем помещать соответствие Б-номеров и членов группы, группа может включить от 1 до 9 номеров, их количество можно настроить в дайлплане. Столбец id – порядковый номер записи с авто инкрементом, столбец did –  Б-номера соответствующие группам, столбец numbers – номера членов в группе с номером did, столбец notes – опциональное описание строки.

mysql> CREATE TABLE `inbound` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `did` varchar(11) CHARACTER SET latin1 NOT NULL,
  `numbers` text NOT NULL,
  `Notes` text CHARACTER SET latin1 NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

Добавляем тестовую запись – номер группы 630, члены группы 390,391,392,393.

mysql> INSERT INTO `inbound` (`did`, `numbers`, `Notes`) VALUES
('630', '390,391,392,393', '');

Пишем функцию в ODBC:

[GET_INBOUND_NUMERS]
dsn=asterisk
readsql=SELECT numbers FROM inbound where did='${ARG1}'

Теперь добавляем в дайлплан контекст для получения номеров группы по входящему номеру:

[RTU-IN]
exten => _[56]XX,1,Set(ARRAY(m1,m2,m3,m4,m5,m6,m7,m8,m9)=${ODBC_GET_INBOUND_NUMERS(${EXTEN})})
same => n,Goto(HG,${EXTEN},1)

И контекст группы поиска, который использует полученные данным, для одновременного вызова на все номера группы:

[HG]
exten => _X.,1, Set(H1=${IF($["${m1}" != ""]?SIP/${m1})})
same => n, Set(H2=${IF($["${m2}" != ""]?&SIP/${m2})})
same => n, Set(H3=${IF($["${m3}" != ""]?&SIP/${m3})})
same => n, Set(H4=${IF($["${m4}" != ""]?&SIP/${m4})})
same => n, Set(H5=${IF($["${m5}" != ""]?&SIP/${m5})})
same => n, Set(H6=${IF($["${m6}" != ""]?&SIP/${m6})})
same => n, Set(H7=${IF($["${m7}" != ""]?&SIP/${m7})})
same => n, Set(H8=${IF($["${m8}" != ""]?&SIP/${m8})})
same => n, Set(H9=${IF($["${m9}" != ""]?&SIP/${m9})})
same => n,Dial(${H1}${H2}${H3}${H4}${H5}${H6}${H7}${H8}${H9},40,Ttr)
same => n,Hangup

Теперь проверим результат – сделаем вызов на 630.
Листинг вывода в консоли Asterisk:

  == Using SIP RTP CoS mark 5
    -- Executing [630@RTU-IN:1] Set("SIP/202-0000011d", "ARRAY(m1,m2,m3,m4,m5,m6,m7,m8,m9,m10,m11,m12,m13,m14,m15)=390\,391\,392\,393") in new stack
    -- Executing [630@RTU-IN:2] Goto("SIP/202-0000011d", "HG,630,1") in new stack
    -- Goto (HG,630,1)
    -- Executing [630@HG:1] Set("SIP/202-0000011d", "H1=SIP/390,391,392,393") in new stack
    -- Executing [630@HG:2] Set("SIP/202-0000011d", "H2=") in new stack
    -- Executing [630@HG:3] Set("SIP/202-0000011d", "H3=") in new stack
    -- Executing [630@HG:4] Set("SIP/202-0000011d", "H4=") in new stack
    -- Executing [630@HG:5] Set("SIP/202-0000011d", "H5=") in new stack
    -- Executing [630@HG:6] Set("SIP/202-0000011d", "H6=") in new stack
    -- Executing [630@HG:7] Set("SIP/202-0000011d", "H7=") in new stack
    -- Executing [630@HG:8] Set("SIP/202-0000011d", "H8=") in new stack
    -- Executing [630@HG:9] Set("SIP/202-0000011d", "H9=") in new stack
    -- Executing [630@HG:10] Dial("SIP/202-0000011d", "SIP/390,391,392,393,40,TtrM(monext)") in new stack
[Jan 12 11:10:23] WARNING[801][C-000000c4]: app_dial.c:2431 dial_exec_full: Unable to create channel of type 'SIP' (cause 20 - Subscriber absent)
  == Everyone is busy/congested at this time (1:0/0/1)

При вызове из дайлплана этой строки, драйвер ODBC Asterisk передаёт в переменную с обратными слешами (backslash) — символ (\) для экранирования запятых, вот пример:

 -- Executing [630@RTU-IN:1] NoOp("SIP/202-00000117", "Numbers for that HG 630 is 390\,391\,392\,393") in new stack

Поэтому использовать функцию ARRAY для того чтобы присвоить переменным значения из массива не получится.
Каким способом можно убрать символ (\), я так и не нашёл, поэтому самое простое решение, это поменять формат записей в БД:

UPDATE `inbound` SET `numbers` = ' SIP/220&SIP/202&SIP/390&SIP/391&SIP/392&SIP/393' WHERE ` did ` =630;

Модифицируем контекст [RTU-IN], Контекст [HG] уже не нужен.:

[RTU-IN]
exten => _6XX.,1,Dial(${ODBC_GET_INBOUND_NUMERS(${EXTEN})},40,TtrM(monext))
same => n,Hangup 

Листинг вывода в консоли Asterisk:

    -- Executing [630@RTU-IN:1] Dial("SIP/202-00000122", "SIP/220&SIP/202&SIP/390&SIP/391&SIP/392&SIP/393,40,TtrM(monext)") in new stack
  == Using SIP RTP CoS mark 5
  == Using SIP RTP CoS mark 5
[Jan 12 11:25:14] WARNING[829][C-000000c7]: app_dial.c:2431 dial_exec_full: Unable to create channel of type 'SIP' (cause 20 - Subscriber absent)
[Jan 12 11:25:14] WARNING[829][C-000000c7]: app_dial.c:2431 dial_exec_full: Unable to create channel of type 'SIP' (cause 20 - Subscriber absent)
[Jan 12 11:25:14] WARNING[829][C-000000c7]: app_dial.c:2431 dial_exec_full: Unable to create channel of type 'SIP' (cause 20 - Subscriber absent)
[Jan 12 11:25:14] WARNING[829][C-000000c7]: app_dial.c:2431 dial_exec_full: Unable to create channel of type 'SIP' (cause 20 - Subscriber absent)
    -- Called SIP/220
    -- Called SIP/202
    -- SIP/220-00000123 is ringing
    -- SIP/202-00000124 is ringing
    -- SIP/202-00000124 is ringing

Но запись в формате SIP/220&SIP/202&SIP/390&SIP/391&SIP/392&SIP/393 не очень понятная, вариант записи 220,202,391,392,393 более читабелен.
Поэтому возвращаем наш пример в первоначальный формат:

mysql>UPDATE `inbound` SET `numbers` = '220,202,391,392,393' WHERE ` did ` =630;

Пример запроса в БД из консоли MySQL:

mysql> select numbers from inbound where did=630;
+-------------------------+
| numbers                 |
+-------------------------+
| 220,202,391,392,393 |
+-------------------------+
1 row in set (0.00 sec)

Для решения проблемы с обратными слешами, я решил использовать вместо ODBC запроса AGI скрипт, написанный на PHP, который также делает запрос в БД по номеру группы и возвращает в дайлплан Asterisk список с номерами телефонов в группе.
Создадим в директории /var/lib/asterisk/agi-bin php скрипт с названием get_hg.php. Предполагается, что библиотека phpagi уже установлена, если её нет, то она доступна для свободного скачивания в интернет – архив, например, phpagi-2.20.tgz нужно разархивировать и задать на него ссылку в скрипте, в примере это /usr/share/php5/phpagi-2.20/phpagi.php.
Листинг скрипта get_hg.php:

#!/usr/bin/php -q
<?php
set_time_limit(0);
include ( "/usr/share/php5/phpagi-2.20/phpagi.php" );
$agi = new AGI;

$conn = mysql_connect("localhost", "asterisk_user", "_iddqd1");

  if (!$conn) {
        echo "Unable to connect to DB: " . mysql_error();
        exit;
    }
    if (!mysql_select_db("astcdrdb")) {
        echo "Unable to select mydbname: " . mysql_error();
        exit;
    }

$sql = "SELECT numbers
        FROM   inbound
        WHERE  did = ".$argv[1];
$result = mysql_query($sql);
//MYSQL_ASSOC<->Результат возвращается в ассоциативном массиве с индексами под именами колонок
$line = mysql_fetch_array($result, MYSQL_ASSOC);
foreach ($line as $col_value);
$agi->set_variable ("HG_NUMS", $col_value);
mysql_free_result($result);
mysql_close($conn);
?>

Скрипт, через приложение AGI, вызванное из дайлплана Asterisk, получит входящий Б номер из переменной ${EXTEN} и вернет список номеров в переменную HG_NUMS назад в дайлплан Asterisk.
Работу скрипта можно проверить запуском его с параметром – входящим номером группы 630:

root@aster-test:/var/lib/asterisk/agi-bin# ./get_hg.php 630

SET VARIABLE HG "220,202,391,392,393"

Теперь модифицируем дайлплан Asterisk:

[RTU-IN]
exten => _6XX,1, AGI(get_hg.php,${EXTEN})
same => n,NoOp(Reult ${HG_NUMS})
same => n,Set(ARRAY(m1,m2,m3,m4,m5,m6,m7,m8,m9)=${HG_NUMS})
same => n,Goto(HG,${EXTEN},1)

[HG]
exten => _X.,1, Set(H1=${IF($["${m1}" != ""]?SIP/${m1})})
same => n, Set(H2=${IF($["${m2}" != ""]?&SIP/${m2})})
same => n, Set(H3=${IF($["${m3}" != ""]?&SIP/${m3})})
same => n, Set(H4=${IF($["${m4}" != ""]?&SIP/${m4})})
same => n, Set(H5=${IF($["${m5}" != ""]?&SIP/${m5})})
same => n, Set(H6=${IF($["${m6}" != ""]?&SIP/${m6})})
same => n, Set(H7=${IF($["${m7}" != ""]?&SIP/${m7})})
same => n, Set(H8=${IF($["${m8}" != ""]?&SIP/${m8})})
same => n, Set(H9=${IF($["${m9}" != ""]?&SIP/${m9})})
same => n,Dial(${H1}${H2}${H3}${H4}${H5}${H6}${H7}${H8}${H9},40,Ttr)
same => n,Hangup

Здесь максимальное ограничение на 9 членов в группе, но их количество легко увеличить или уменьшить до нужного количества. Вообще, если в группе много участников, то лучше использовать очереди – queue и выставлять в очередях алгоритмы распределения вызовов.
Теперь проверим результат – сделаем вызов на 630.
Листинг вывода в консоли Asterisk:

    -- Executing [630@RTU-IN:1] AGI("SIP/202-0000012a", "get_hg.php,630") in new stack
    -- Launched AGI Script /var/lib/asterisk/agi-bin/get_hg.php
    -- <SIP/202-0000012a>AGI Script get_hg.php completed, returning 0
    -- Executing [630@RTU-IN:2] NoOp("SIP/202-0000012a", "Reult 220,202,393,391,392,393") in new stack
    -- Executing [630@RTU-IN:3] Set("SIP/202-0000012a", "ARRAY(m1,m2,m3,m4,m5,m6,m7,m8,m9)=220,202,393,391,392,393") in new stack
    -- Executing [630@RTU-IN:4] Goto("SIP/202-0000012a", "HG,630,1") in new stack
    -- Goto (HG,630,1)
    -- Executing [630@HG:1] Set("SIP/202-0000012a", "H1=SIP/220") in new stack
    -- Executing [630@HG:2] Set("SIP/202-0000012a", "H2=&SIP/202") in new stack
    -- Executing [630@HG:3] Set("SIP/202-0000012a", "H3=&SIP/393") in new stack
    -- Executing [630@HG:4] Set("SIP/202-0000012a", "H4=&SIP/391") in new stack
    -- Executing [630@HG:5] Set("SIP/202-0000012a", "H5=&SIP/392") in new stack
    -- Executing [630@HG:6] Set("SIP/202-0000012a", "H6=&SIP/393") in new stack
    -- Executing [630@HG:7] Set("SIP/202-0000012a", "H7=") in new stack
    -- Executing [630@HG:8] Set("SIP/202-0000012a", "H8=") in new stack
    -- Executing [630@HG:9] Set("SIP/202-0000012a", "H9=") in new stack
    -- Executing [630@HG:10] Set("SIP/202-0000012a", "H11=") in new stack
    -- Executing [630@HG:11] Set("SIP/202-0000012a", "H12=") in new stack
    -- Executing [630@HG:12] Set("SIP/202-0000012a", "H13=") in new stack
    -- Executing [630@HG:13] Set("SIP/202-0000012a", "H14=") in new stack
    -- Executing [630@HG:14] Set("SIP/202-0000012a", "H15=") in new stack
    -- Executing [630@HG:15] Dial("SIP/202-0000012a", "SIP/220&SIP/202&SIP/391&SIP/392&SIP/393,40,Ttr") in new stack
  == Using SIP RTP CoS mark 5
  == Using SIP RTP CoS mark 5
[Jan 12 12:49:33] WARNING[971][C-000000cb]: app_dial.c:2431 dial_exec_full: Unable to create channel of type 'SIP' (cause 20 - Subscriber absent)
[Jan 12 12:49:33] WARNING[971][C-000000cb]: app_dial.c:2431 dial_exec_full: Unable to create channel of type 'SIP' (cause 20 - Subscriber absent)
[Jan 12 12:49:33] WARNING[971][C-000000cb]: app_dial.c:2431 dial_exec_full: Unable to create channel of type 'SIP' (cause 20 - Subscriber absent)
    -- Called SIP/220
    -- Called SIP/202
    -- SIP/220-0000012b is ringing
    -- SIP/202-0000012c is ringing
    -- SIP/202-0000012c is ringing

Второй вариант: Реализация входящей связи, если необходимо добавить таймаут вызова в группе и переадресацию на внешний номер, в случае, если в группе никто не ответил.
Также используем скрипт AGI и ODBC функции для БД MySQL.
Создаём таблицу inbound:

CREATE TABLE `inbound` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `did` varchar(11) NOT NULL,
  `numbers` text NOT NULL,
  `ring_timeout` int(11) NOT NULL DEFAULT '40',
  `forward_after_enable` int(11) NOT NULL DEFAULT '0',
  `forward_after_numer` text NOT NULL,
  `timeout_ring_after` int(11) NOT NULL DEFAULT '30',
  `Notes` text NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

Как и в предыдущем случае, столбец id – порядковый номер записи с авто инкрементом, столбец did –  Б-номера соответствующие группам, столбец numbers – номера членов в группе с номером did, столбец ring_timeout – длительность ожидания ответа в группе, forward_after_enable – разрешить переадресацию на внешний номер если в группе никто не ответил, по умолчанию 0 – переадресация выключена, если 1 – передаресация разрешена, forward_after_numer – номер для переадресации, если в группе никто не ответил timeout_ring_after – время таймаута вызова на номер forward_after_numer, столбец notes – опциональное описание строки.
Теперь последнее, можно добавим возможность переадресации на внешний номер.
Добавим в таблицу inbound новую запись – вызов на группу с городским номером 78120001122:

INSERT INTO `inbound` (`did`, `numbers`, `ring_timeout`, `forward_after_enable`, `forward_after_numer`, `timeout_ring_after`, `Notes`) VALUES 
('78120001122', '240,250', 30, 1, '79627277780', 30, 'Тестовая запись');

И группу с внутренним номером 631:

INSERT INTO `inbound` (`did`, `numbers`, `ring_timeout`, `forward_after_enable`, `forward_after_numer`, `timeout_ring_after`, `Notes`) VALUES 
('631', '240,250', 30, 1, '79627277780', 30, 'Тестовая запись');

Напишем дополнительную функцию ODBC для получения параметров переадресации:

[GET_FORWARD_AFTER_HG]
dsn=asterisk
readsql=SELECT ring_timeout,forward_after_enable,forward_after_numer,timeout_ring_after from inbound where did='${ARG1}'

Теперь модифицируем дайлпан. Если в случае срабатывания переадресациии на внешний номер в ТфОП(например, 79627277780) номер группы является внутренним(например, 631), его нужно закрывать общим внешним А-номером, в отличие от случая, где номером группы является городской номер(например, 78120001122) и в качестве АОНа для переадресации он же и берется из переменной EXTEN. Ниже пример вызова с листингом вывода в консоль Asterisk.

[RTU-IN]
exten => _X.,1, AGI(get_hg.php,${EXTEN})
same => n,NoOp(Reult ${HG_NUMS})
same => n,NoOp(GET FORWARD ring_timeout,forward_after_enable,forward_after_numer,ring_after_timeout ==  ${ODBC_GET_FORWARD_AFTER_HG(${EXTEN})})
same => n,Set(ARRAY(RING_TIMEOUT,FORWARD_AFTER_ENABLE,FORWARD_AFTER_NUMBER,TIMEOUT_RING_AFTER)=${ODBC_GET_FORWARD_HG(${EXTEN})})
same => n,Set(ARRAY(m1,m2,m3,m4,m5)=${HG_NUMS})
same => n,Goto(HG,${EXTEN},1)

[HG]
exten => _X.,1, Set(H1=${IF($["${m1}" != ""]?SIP/${m1})})
same => n, Set(H2=${IF($["${m2}" != ""]?&SIP/${m2})})
same => n, Set(H3=${IF($["${m3}" != ""]?&SIP/${m3})})
same => n, Set(H4=${IF($["${m4}" != ""]?&SIP/${m4})})
same => n, Set(H5=${IF($["${m5}" != ""]?&SIP/${m5})})
same => n,Dial(${H1}${H2}${H3}${H4}${H5},${RING_TIMEOUT},TtrM(monext))
same => n,GotoIf($["${FORWARD_AFTER_ENABLE}" != "1"]?end)
same => n,Set(CALLERID(num)=${IF($["${REGEX("^7812.......$" ${EXTEN})}"]?${EXTEN}:78122429945)})
same => n,Dial(SIP/RTU/${FORWARD_AFTER_NUMBER},${TIMEOUT_RING_AFTER},TtrM(monext))
same => n(end),Hangup

Вызов на 631. Сначала отработал скрипт, get_hg.php, который вернул список номеров 240 и 250, оказалось, далее, на номера был выполнен групповой вызов, абоненты не зарегиристрированы. Далее сработала переадресация на номер 79627277780 с таймаутом 30 секунд, в качестве А-номера бы присвоен номер по умолчанию равный 78127778899. Ниже пример вызова с листингом вывода в консоль Asterisk.

     == Using SIP RTP CoS mark 5
    -- Executing [631@RTU-IN:1] AGI("SIP/202-000001bd", "get_hg.php,631") in new stack
    -- Launched AGI Script /var/lib/asterisk/agi-bin/get_hg.php
    -- <SIP/202-000001bd>AGI Script get_hg.php completed, returning 0
    -- Executing [631@RTU-IN:2] NoOp("SIP/202-000001bd", "Reult 240,250") in new stack
    -- Executing [631@RTU-IN:3] NoOp("SIP/202-000001bd", "GET FORWARD ring_timeout,forward_after_enable,forward_after_numer,timeout_ring_after ==  30,1,SIP/RTU/79627277780,30") in new stack
    -- Executing [631@RTU-IN:4] Set("SIP/202-000001bd", "ARRAY(RING_TIMEOUT,FORWARD_AFTER_ENABLE,FORWARD_AFTER_NUMBER,TIMEOUT_RING_AFTER)=30,1,SIP/RTU/79627277780,30") in new stack
    -- Executing [631@RTU-IN:5] Set("SIP/202-000001bd", "ARRAY(m1,m2,m3,m4,m5,m6,m7,m8,m9,m10,m11,m12,m13,m14,m15)=240,250") in new stack
    -- Executing [631@RTU-IN:6] Goto("SIP/202-000001bd", "HG,631,1") in new stack
    -- Goto (HG,631,1)
    -- Executing [631@HG:1] Answer("SIP/202-000001bd", "") in new stack
       > 0x7fde046867c0 -- Probation passed - setting RTP source address to 192.168.85.148:40032
    -- Executing [631@HG:2] Set("SIP/202-000001bd", "H1=SIP/240") in new stack
    -- Executing [631@HG:3] Set("SIP/202-000001bd", "H2=&SIP/250") in new stack
    -- Executing [631@HG:4] Dial("SIP/202-000001bd", "SIP/240&SIP/250,30,TtrM(monext)") in new stack
[Jan 18 15:43:42] WARNING[11227][C-00000110]: app_dial.c:2431 dial_exec_full: Unable to create channel of type 'SIP' (cause 20 - Subscriber absent)
[Jan 18 15:43:42] WARNING[11227][C-00000110]: app_dial.c:2431 dial_exec_full: Unable to create channel of type 'SIP' (cause 20 - Subscriber absent)
  == Everyone is busy/congested at this time (2:0/0/2)
    -- Executing [631@HG:5] GotoIf("SIP/202-000001bd", "0?end") in new stack
    -- Executing [631@HG:6] Set("SIP/202-000001bd", "CALLERID(num)=78122429945") in new stack
    -- Executing [631@HG:7] Dial("SIP/202-000001bd", "SIP/RTU/79627277780,30,TtrM(monext)") in new stack
  == Using SIP RTP CoS mark 5
    -- Called SIP/RTU/79627277780

Вызов на 7812000112. Сначала отработал скрипт, get_hg.php, который вернул список номеров 240 и 250, оказалось, далее, на номера был выполнен групповой вызов, абоненты не зарегиристрированы. Далее сработала переадресация на номер 79627277780 с таймаутом 30 секунд, в качестве А-номера (переменная CALLERID(num)) использовалось значение переменной EXTEN. Ниже пример такого вызова с листингом вывода в консоль Asterisk.

     -- Executing [78120001122@RTU-IN:1] AGI("SIP/202-000001c1", "get_hg.php,78120001122") in new stack
    -- Launched AGI Script /var/lib/asterisk/agi-bin/get_hg.php
    -- <SIP/202-000001c1>AGI Script get_hg.php completed, returning 0
    -- Executing [78120001122@RTU-IN:2] NoOp("SIP/202-000001c1", "Reult 240,250") in new stack
    -- Executing [78120001122@RTU-IN:3] NoOp("SIP/202-000001c1", "GET FORWARD ring_timeout,forward_after_enable,forward_after_numer,timeout_ring_after ==  30,1,SIP/RTU/79627277780,30") in new stack
    -- Executing [78120001122@RTU-IN:4] Set("SIP/202-000001c1", "ARRAY(RING_TIMEOUT,FORWARD_AFTER_ENABLE,FORWARD_AFTER_NUMBER,TIMEOUT_RING_AFTER)=30,1,SIP/RTU/79627277780,30") in new stack
    -- Executing [78120001122@RTU-IN:5] Set("SIP/202-000001c1", "ARRAY(m1,m2,m3,m4,m5,m6,m7,m8,m9,m10,m11,m12,m13,m14,m15)=240,250") in new stack
    -- Executing [78120001122@RTU-IN:6] Goto("SIP/202-000001c1", "HG,78120001122,1") in new stack
    -- Goto (HG,78120001122,1)
    -- Executing [78120001122@HG:1] Answer("SIP/202-000001c1", "") in new stack
       > 0x7fde046867c0 -- Probation passed - setting RTP source address to 192.168.85.148:40046
    -- Executing [78120001122@HG:2] Set("SIP/202-000001c1", "H1=SIP/240") in new stack
    -- Executing [78120001122@HG:3] Set("SIP/202-000001c1", "H2=&SIP/250") in new stack
    -- Executing [78120001122@HG:4] Set("SIP/202-000001c1", "H3=") in new stack
    -- Executing [78120001122@HG:5] Set("SIP/202-000001c1", "H4=") in new stack
    -- Executing [78120001122@HG:6] Set("SIP/202-000001c1", "H5=") in new stack
    -- Executing [78120001122@HG:7] Dial("SIP/202-000001c1", "SIP/240&SIP/250,30,TtrM(monext)") in new stack
[Jan 18 15:50:14] WARNING[11241][C-00000113]: app_dial.c:2431 dial_exec_full: Unable to create channel of type 'SIP' (cause 20 - Subscriber absent)
[Jan 18 15:50:14] WARNING[11241][C-00000113]: app_dial.c:2431 dial_exec_full: Unable to create channel of type 'SIP' (cause 20 - Subscriber absent)
  == Everyone is busy/congested at this time (2:0/0/2)
    -- Executing [78120001122@HG:8] GotoIf("SIP/202-000001c1", "0?end") in new stack
    -- Executing [78120001122@HG:9] Set("SIP/202-000001c1", "CALLERID(num)=78120001122") in new stack
    -- Executing [78120001122@HG:10] Dial("SIP/202-000001c1", "SIP/RTU/79627277780,30,TtrM(monext)") in new stack
  == Using SIP RTP CoS mark 5
    -- Called SIP/RTU/79627277780

Теперь немного подкорректируем нашу конфигурацию для того чтобы можно было переадресовать вызо в произвольный канал или на голосовую почту:

INSERT INTO `inbound` (`did`, `numbers`, `ring_timeout`, `forward_after_enable`, `forward_after_numer`, `timeout_ring_after`, `Notes`) VALUES 
('78120001122', '240,250', 30, 1, 'SIP/RTU/79627277780', 30, 'Тестовая запись');

В контексте HG:
1) Меняем строку – в приложении Dial удаляем значение «технология/канал» равное «SIP/RTU», так как теперь мы целиком подставляем конструкцию, которая описывает куда нужно позвонить. Таким образом, мы можем переадресовать вызов параллельно на несколько внешних номеров, используя символ (&), например, SIP/79627277780&SIP/78126470011.
2) Если вызов нужно отправить в другой контекст, например, на голосовую почту, то заменять А-номер – переменную CALLERID(num) не нужно, это легко реализовать проверкой переменной  ${FORWARD_AFTER_NUMBER} на наличие символа @, который свидетельствует о использовании канала Local и переводе вызова в другой контекст, например, Local/203@vm.
В итоге, модифицированный контекст:

[HG]
exten => _X.,1, Set(H1=${IF($["${m1}" != ""]?SIP/${m1})})
same => n, Set(H2=${IF($["${m2}" != ""]?&SIP/${m2})})
same => n, Set(H3=${IF($["${m3}" != ""]?&SIP/${m3})})
same => n, Set(H4=${IF($["${m4}" != ""]?&SIP/${m4})})
same => n, Set(H5=${IF($["${m5}" != ""]?&SIP/${m5})})
same => n,Dial(${H1}${H2}${H3}${H4}${H5},${RING_TIMEOUT},TtrM(monext))
same => n,GotoIf($["${FORWARD_AFTER_ENABLE}" != "1"]?end)
same => n,GotoIf($[${REGEX("@" ${FORWARD_AFTER_NUMBER})}]?nochangecid)
same => n,Set(CALLERID(num)=${IF($["${REGEX("^7812.......$" ${EXTEN})}"]?${EXTEN}:78122429945)})
same => n(nochangecid),Dial(SIP/RTU/${FORWARD_AFTER_NUMBER},${TIMEOUT_RING_AFTER},Ttr)
same => n(end),Hangup

Теперь вызов на 7812000112 где переменная NUMBER=SIP/RTU/79627277780. Ниже пример такого вызова с листингом вывода в консоль Asterisk.

      -- Executing [78120001122@RTU-IN:1] AGI("SIP/202-000001d2", "get_hg.php,78120001122") in new stack
    -- Launched AGI Script /var/lib/asterisk/agi-bin/get_hg.php
    -- <SIP/202-000001d2>AGI Script get_hg.php completed, returning 0
    -- Executing [78120001122@RTU-IN:2] NoOp("SIP/202-000001d2", "Reult 240,250") in new stack
    -- Executing [78120001122@RTU-IN:3] NoOp("SIP/202-000001d2", "GET FORWARD ring_timeout,forward_after_enable,forward_after_numer,timeout_ring_after ==  30,1,SIP/RTU/79627277780,30") in new stack
    -- Executing [78120001122RTU-IN:4] Set("SIP/202-000001d2", "ARRAY(RING_TIMEOUT,FORWARD_AFTER_ENABLE,FORWARD_AFTER_NUMBER,TIMEOUT_RING_AFTER)=30,1,SIP/RTU/79627277780,30") in new stack
    -- Executing [78120001122@RTU-IN:5] Set("SIP/202-000001d2", "ARRAY(m1,m2,m3,m4,m5)=240,250") in new stack
    -- Executing [78120001122@RTU-IN:6] Goto("SIP/202-000001d2", "HG,78120001122,1") in new stack
    -- Goto (HG,78120001122,1)
    -- Executing [78120001122@HG:1] Answer("SIP/202-000001d2", "") in new stack
       > 0x7fde046867c0 -- Probation passed - setting RTP source address to 192.168.85.148:40026
    -- Executing [78120001122@HG:2] Set("SIP/202-000001d2", "_MONITOR_FILENAME=IN-78120001122-2017_01_19[10_29]-202-78120001122-1484810974.920") in new stack
    -- Executing [78120001122@HG:3] NoOp("SIP/202-000001d2", "!!!!CALLED=") in new stack
    -- Executing [78120001122@HG:4] Set("SIP/202-000001d2", "H1=SIP/240") in new stack
    -- Executing [78120001122@HG:5] Set("SIP/202-000001d2", "H2=&SIP/250") in new stack
    -- Executing [78120001122@HG:6] Dial("SIP/202-000001d2", "SIP/240&SIP/250,30,Ttr") in new stack
[Jan 19 10:29:34] WARNING[12800][C-0000011f]: app_dial.c:2431 dial_exec_full: Unable to create channel of type 'SIP' (cause 20 - Subscriber absent)
[Jan 19 10:29:34] WARNING[12800][C-0000011f]: app_dial.c:2431 dial_exec_full: Unable to create channel of type 'SIP' (cause 20 - Subscriber absent)
  == Everyone is busy/congested at this time (2:0/0/2)
    -- Executing [78120001122@HG:7] GotoIf("SIP/202-000001d2", "0?end") in new stack
    -- Executing [78120001122@HG:8] GotoIf("SIP/202-000001d2", "0?nochangecid") in new stack
    -- Executing [78120001122@HG:9] Set("SIP/202-000001d2", "CALLERID(num)=78120001122") in new stack
    -- Executing [78120001122@HG:10] Dial("SIP/202-000001d2", "SIP/RTU/79627277780,30,Ttr") in new stack
  == Using SIP RTP CoS mark 5
    -- Called SIP/RTU/79627277780

Заменим номер для переадресации с SIP/RTU/79627277780 на голосовую почту в 203 ящик, используем локальный псевдоканал Local:

INSERT INTO `inbound` (`did`, `numbers`, `ring_timeout`, `forward_after_enable`, `forward_after_numer`, `timeout_ring_after`, `Notes`) VALUES 
('78120001122', '240,250', 30, 1, 'Local/203@vm', 30, 'Тестовая запись');

Запись Local/203@vm означает, что нужно перенаправить вызов контекст vm плана набора.
Аналогично вызовы на voicemail можно направлять в любые контексты. Сам контекст vm может быть таким:

[vm]
exten => _X.,1,VoiceMail(${EXTEN}@voicemail)
same => n,Hangup

В файле voicemail.conf описан ящик 203:

[voicemail]  ; контекст голосовой почты
203 => 0,Ignat_203,ignat203@gmail.com

Теперь, после вызова в группе поиска, сработает переадресация на голосовой ящик абонента 203 для того чтобы оставить ему сообщение. Ниже пример вызова с листингом вывода в консоль Asterisk.

   == Using SIP RTP CoS mark 5
    -- Executing [78120001122@RTU-IN:1] AGI("SIP/202-000001d1", "get_hg.php,78120001122") in new stack
    -- Launched AGI Script /var/lib/asterisk/agi-bin/get_hg.php
    -- <SIP/202-000001d1>AGI Script get_hg.php completed, returning 0
    -- Executing [78120001122@RTU-IN:2] NoOp("SIP/202-000001d1", "Reult 240,250") in new stack
    -- Executing [78120001122@RTU-IN:3] NoOp("SIP/202-000001d1", "GET FORWARD ring_timeout,forward_after_enable,forward_after_numer,timeout_ring_after ==  30,1,Local/203@vm,30") in new stack
    -- Executing [78120001122@RTU-IN:4] Set("SIP/202-000001d1", "ARRAY(RING_TIMEOUT,FORWARD_AFTER_ENABLE,FORWARD_AFTER_NUMBER,TIMEOUT_RING_AFTER)=30,1,Local/203@vm,30") in new stack
    -- Executing [78120001122@RTU-IN:5] Set("SIP/202-000001d1", "ARRAY(m1,m2,m3,m4,m5,m6,m7,m8,m9,m10,m11,m12,m13,m14,m15)=240,250") in new stack
    -- Executing [78120001122@RTU-IN:6] Goto("SIP/202-000001d1", "HG,78120001122,1") in new stack
    -- Goto (HG,78120001122,1)
    -- Executing [78120001122@HG:1] Answer("SIP/202-000001d1", "") in new stack
       > 0x7fde046867c0 -- Probation passed - setting RTP source address to 192.168.85.148:40020
     -- Executing [78120001122@HG:4] Set("SIP/202-000001d1", "H1=SIP/240") in new stack
    -- Executing [78120001122@HG:5] Set("SIP/202-000001d1", "H2=&SIP/250") in new stack
    -- Executing [78120001122@HG:6] Set("SIP/202-000001d1", "H3=") in new stack
    -- Executing [78120001122@HG:7] Set("SIP/202-000001d1", "H4=") in new stack
    -- Executing [78120001122@HG:8] Set("SIP/202-000001d1", "H5=") in new stack
     -- Executing [78120001122@HG:9] Dial("SIP/202-000001d1", "SIP/240&SIP/250,30,Ttr") in new stack
[Jan 19 10:23:24] WARNING[12770][C-0000011e]: app_dial.c:2431 dial_exec_full: Unable to create channel of type 'SIP' (cause 20 - Subscriber absent)
[Jan 19 10:23:24] WARNING[12770][C-0000011e]: app_dial.c:2431 dial_exec_full: Unable to create channel of type 'SIP' (cause 20 - Subscriber absent)
  == Everyone is busy/congested at this time (2:0/0/2)
    -- Executing [78120001122@HG:10] GotoIf("SIP/202-000001d1", "0?end") in new stack
    -- Executing [78120001122@HG:11] GotoIf("SIP/202-000001d1", "1?nochangecid") in new stack
    -- Goto (HG,78120001122,13)
    -- Executing [78120001122@HG:13] Dial("SIP/202-000001d1", "Local/203@vm,30,Ttr") in new stack
    -- Called Local/203@vm
    -- Executing [203@vm:1] VoiceMail("Local/203@vm-0000000a;2", "203@voicemail") in new stack
    -- Local/203@vm-0000000a;1 answered SIP/202-000001d1

Tags:

Комментарии:

Оставить комментарий

Напишите Ваш комментарий ниже. Также Вы можете подписаться на комментарии к материалу через RSS

Вы можете использовать следующие теги:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> 

Мы поддерживаем Gravatar.

Контроль спама: * Лимит времени истёк. Пожалуйста, перезагрузите CAPTCHA.