База знаний

Содержание:

Custom DB MySQL

exten => _X.,1,Set(CHANNEL(language)=ru)
exten => _X.,n,Set(CALLID=${CALLERID(num):-7})
exten => _X.,n,MYSQL(Connect connidcdr localhost dbuser dbpassword dbname utf8)
exten => _X.,n,MYSQL(Query resultidcdr ${connidcdr} SELECT * FROM Callers WHERE CID LIKE '%${CALLID}%')
exten => _X.,n,MYSQL(Fetch fetchid ${resultidcdr} CID EXT)
exten => _X.,n,Set(number=${EXT})
exten => _X.,n,NoOp(caller --> ${CID} callee --> ${EXT})
exten => _X.,n,GotoIf($?nodst:dst)
exten => _X.,n(dst),MYSQL(Clear ${resultidcdr})
exten => _X.,n,MYSQL(Disconnect ${connidcdr})
exten => _X.,n,Dial(SIP/${number},20,tT)
exten => _X.,n,GotoIf($?nodst)
exten => _X.,n,GotoIf($?nodst)
exten => _X.,n,GotoIf($?nodst)
exten => _X.,n(nodst),MYSQL(Clear ${resultidcdr})
exten => _X.,n,MYSQL(Disconnect ${connidcdr})
exten => _X.,n,Goto(from-trunk,${DID},1)
exten => h,1,hangup()
 mysql> create database CLIP;
 mysql> use CLIP;
 mysql> CREATE TABLE Callers (CID VARCHAR(20), EXT VARCHAR(4), NAME VARCHAR(35));
 mysql> INSERT Callers (CID,EXT,NAME) values (999000774,1000,'Test');
mysql> select * from Callers limit 100;
+-----------+------+------+
| CID       | EXT  | NAME |
+-----------+------+------+
| 999000774 | 1000 | Test |
+-----------+------+------+
2 rows in set (0.00 sec)

Configuration

Odoo configuration

In Odoo you should set data_dir option.
It is used as a root folder for storing uploaded voice files.
Be sure this folder is accessible for UID which is asterisk running under.

Example configuration file

You can generate default Odoo configuration by running . It will create a default ~/.openerp_serverrc file in home directory.

Warning! Make sure You set up correct dbfilter option and also disabled workers by settings it to zero.

Asterisk settings

ARI settings

ARI is configured in ari.conf. Example of configuration:

Password is generated with mkpasswd -m sha-512 or just use password_format = plain at your risk 😉 and put plain password here.

Remember it will be transfered over the network in plain text and if Asterisk is in internet using plain is a security hole.

Imagine a phone bill for $40,000 for calls to Inmarsat because Asterisk ARI access is sniffed.

Dialplan

Dialer operates using ARI originate method and Local channel. Due to Aterisk limitation to return call status of non-connected calls we have to use Local channel and its context to actually send call to provider and use exten to update call status. Here is an example of such a dial plan:

So you must add the above snippet to your extensions.conf. Replace peers with your provider’s peer from sip.conf and localhost:8069 with your Odoo instance URL.

When creating Call Routing in Odoo Dialer Application name Dial Context exactly like Asterisk context name.

Running Dialer

Dialer operates in 2 modes (dialer type setting):

  • Asterisk dialplan
  • Odoo Stasis app

Asterisk Dialplan

When dialer type is set to Playback the Dialer originates calls and puts connected calls in specified Asterisk context name.

For example if instead of message playback we need to put every connected call in queue, the following dialplan must be created in extensions.conf:

In Dialer configuration field Context name must be set to queue.

providers

id providername currency_id noncli_prefix cli_prefix premcli_prefix cli_allowed dynamic_calls append_plus dynamic_calls_caller_id
3 Zadarma 96 N N 1
4 Multitel 122 N N 1
7 WestCall 96 Y N 74951815283
8 VoxBeam 122 0011103 0011101 0011102 Y Y 1
11 VoiceBuy 122 9991 9992 9993 Y Y 1
16 IPPort 96 N N 1

Итак. Операторы.

Обычно у зарубежных операторов 2-3 тарифа по которым они доставляют трафик до вызываемого абонента. Про различия CLI и NonCLI техник можно прочитать здесь.

Для выбора тарифного плана обычно используется префикс (noncli_prefix, cli_prefix, premcli_prefix) указываемый перед вызываемым номером в процессе формирования строки вызова.

Описание полей:

  • providername — наименование оператора
  • currency_id — валюта оператора
  • noncli_prefix — тариф без поддержки CLI
  • cli_prefix — тариф с поддержкой CLI
  • premcli_prefix — тариф премиального уровня
  • cli_allowed — указывает на то, что оператор позволяет менять CLI для исходящих вызовов
  • dynamic_calls — указывает, что оператор позволяет осуществлять звонки по любым направлениям с любым CLI.
  • append_plus — добавлять или не добавлять «+» перед вызываемым номером. Часть операторов требует «+», часть нет.
  • dynamic_calls_caller_id — CID для оператора по умолчанию.

В случае с WestCall и другими операторами предоставляющими подключение через SIP транк, вы покупаете пул номеров и осуществляя вызовы можете менять исходящий номер на любой номер из данного пула. IPPort как и прочие операторы, при использовании схемы логин/пароль с единственным купленным номером, не позволяют менять исходящий номер. Операторы VoxBeam и VoiceBuy используются для исходящих звонков для любых направлений и позволяют менять исходящий номер на любой другой. Правда здесь есть одно но! И заключается оно в том, что конечные операторы связи доставляющие звонок до вызываемого абонента, могут отклонить вызов или поменять номер вызывающего абонента на свой, если вызывающий номер соответствует внутреннему номеру страны или местности. Т.е. скажем при звонке из России в Украину(Киев) мы меняем CallerID на украинский номер в Киеве и оператор связи через которого идёт вызов может данный звонок просто «отбить», т.к. он не соответствует внутренней политике обработки звонков «Звонок через внешний входящий международный транк не может содержать внутренних номеров». Операторы Zadarma и Multitel у нас будут использоваться в качестве поставщиков номеров и исходящий трафик мы через них отправлять не будем, хотя при желании могли бы.

Важным аспектом обработки вызова является безопасность. Довольно часто встречаются ситуации, когда неверно настроенный сервер IP телефонии позволяет пропускать сквозь себя чужие звонки. Для обеспечения безопасности контуров обрабатывающих входящие и исходящие звонки, дополнительно к разделению операторов по различным контекстам, стоит создать базу IP адресов операторов связи предоставляющих нам услуги связи. Во первых это позволит настроить экспорт адресов в firewall и убрать лишние попытки «прозвона» через нас, во вторых даст более полную информацию о способах подключения к операторам связи.

Что мы умеем сегодня

  • Прослушивать и скачивать записи совершенных вызовов
    (причем скачать можно все записи, найденные по фильтру поиска, одним архивом).
  • Отслеживать качество отработки звонков, оценивать их эффективность
    (кто сколько принял/пропустил/совершил звонков; их продолжительность).
  • Создавать группы внутренних номеров и просматривать отчеты в разрезе этих групп
    (для исходящих вызовов, в отчетах по входящим абоненты группируются посредством очередей Asterisk).
  • Предоставлять каждому супервизору доступ к группам и очередям, отчеты которых он может просматривать.
  • Смотреть статистику входящих звонков в разрезе каждого «внешнего» номера
    (сколько входящих за период было по каждому номеру, сколько принято/пропущено).
  • Просматривать подробные журналы внешних, внутренних и пропущенных звонков, а также журнал позвонивших вам впервые (табличные отчеты с гибким фильтром поиска).
  • Экспортировать все отчеты в Excel.
  • Добавлять комментарии к звонкам (в табличных отчетах).
  • Осуществлять мониторинг АТС
    (мы интегрировали наш сервис с zabbix и отправим вам email/sms в случае обнаружения проблем).
  • Оценить время работы и перерывов каждого оператора очереди за выбранный период.
  • Добавить логотип вашей компании
    (на страничку авторизации и в хедер для локальной версии и только в хедер для облачной).
  • Задать любой вопрос support’у прямо из интерфейса.
  • Смотреть разбивку по количеству входящих и исходящих звонков по областям/краям и прочим субъектам РФ на Яндекс.Карте.

Road map

Использование переменных в плане набора Asterisk

foo

  • Глобальные переменные могут быть определены или в секции файла конфигурации extensions.conf или используя команду SetGlobalVar в плане набора. Если переменная однажды определена, тогда она может быть использована для любого канала в любое время.
  • Переменные специфичные для каждого канала задаются с помощью команды Set (команда SetVar устарела). Каждый канал использует изолированное пространство для имен переменных, следовательно, для различных вызовов не будет коллизий в значениях переменной с одним и тем же именем и переменная будет автоматически очищена, когда вызов по какому-либо каналу будет завершен.
  • Переменные окружения обеспечивают доступ из Asterisk к переменным окружения unix. Их список можно найти ниже в данной странце.

NoOpSetGlobalVarSet

Настройка плана набора (dialplan)

Отправка звонка в очередь

Чтобы при звонке на нужный нам номер звонящий попадал в нашу очередь, добавляем правило в диалплане:

vi /etc/asterisk/extensions.conf

exten => 111,1,Answer()
exten => 111,n,Ringing
exten => 111,n,Wait(2)
exten => 111,n,Queue(support)
exten => 111,n,Hangup

* где:

  • Answer — установка соединения. С скобках может быть задан период задержки в миллисекундах.
  • Ringing — воспроизводим звонок, сигнализирующий ожидания взятие трубки.
  • Wait — ждем (в данном примере 2 секунды).
  • Queue — добавляем звонок в очередь.
  • HangUp — отбой.

* в данном примере, при звонке на короткий номер 111 мы услышим на 2 секунды звонок, сигнализирующий, что мы дозвонились; после абонент направляется в нашу очередь support.

Перечитываем конфиг:

asterisk -x «dialplan reload»

Приветствие

Для того, чтобы проиграло приветствие, редактируем наш диалплан, добавив функцию Playback после Answer:

exten => 111,1,Answer()
exten => 111,n,Playback(queue-callswaiting)
exten => 111,n,Ringing

* где queue-callswaiting — название файла из каталога /var/lib/asterisk/sounds/<язык>/.

Перечитываем конфигурацию:

asterisk -x «dialplan reload»

Приоритеты звонков

Мы можем создать два входящих номера, звонки на которые обрабатываются одной и той же очередью, но с разными приоритетами. Например, внутренние номера 120 и 130 — диалплан выглядит так:

exten => 120,1,Answer()
exten => 120,1,Playback(welcome)
exten => 120,n,Ringing
exten => 120,n,Wait(2)
exten => 120,2,SetVar(QUEUE_PRIO=100)
exten => 120,3,Queue(support)
exten => 120,n,Hangup
exten => 130,1,Answer()
exten => 130,1,Playback(welcome)
exten => 130,n,Ringing
exten => 130,n,Wait(2)
exten => 130,2,SetVar(QUEUE_PRIO=50)
exten => 130,3,Queue(support)
exten => 130,n,Hangup

* в данном примере, звонки на номер 120 являются более приоритетными; QUEUE_PRIO — собственно, и является переменной, в которой хранится значение приоритета звонка.

@extensions.conf

Первые же три незакомментированные строки содержат инклюды на дополнительные и кастомные файлы.

#include extensions_override_freepbx.conf
#include extensions_additional.conf
#include extensions_custom.conf

Кастомный файл ‘extensions_override_freepbx.conf’, как и следует из названия,

имеет приоритет над настройками FreePBX, в случае совпадения условий.

Дополнительный файл /etc/asterisk/extensions_additional.conf содержит весь диалплан сконфигурированный во FreePBX.

Кастомный файл ‘extensions_custom.conf’ лучшее место для написания собственного диалплана.

Контексты внешних вызовов DID

include => from-pstn


include => from-dahdi


include => from-pstn


include => from-pstn-custom             ; создайте этот контекст в 'extensions_custom.conf'
include => ext-did-post-custom
include => from-did-direct
include => ext-did-catchall             ; THIS MUST COME AFTER ext-did

Контекст для анонимных или неправильно настроенных внешних вызовов.


exten => _.,1,NoOp(Received incoming SIP connection from unknown peer to ${EXTEN})
exten => _.,n,Set(DID=${IF($?s:${EXTEN})})
exten => _.,n,Goto(s,1)
exten => s,1,GotoIf($?checklang:noanonymous)
exten => s,n(checklang),GotoIf($?setlanguage:from-trunk,${DID},1)
exten => s,n(setlanguage),Set(CHANNEL(language)=${SIPLANG})
exten => s,n,Goto(from-trunk,${DID},1)
exten => s,n(noanonymous),Set(TIMEOUT(absolute)=15)
exten => s,n,Log(WARNING,"Rejecting unknown SIP connection from ${CHANNEL(recvip)}")
exten => s,n,Answer
exten => s,n,Wait(2)
exten => s,n,Playback(ss-noservice)
exten => s,n,Playtones(congestion)
exten => s,n,Congestion(5)
exten => h,1,Hangup
exten => i,1,Hangup
exten => t,1,Hangup

Контексты внутренних вызовов

Контекст from-internal назначается по умолчанию всем вновь создаваемым екстеншенам и позволяет набирать все номера, назначенные в модулях FreePBX

девайсам, группам, очередям, маршрутам, кодам абонентских функций итд, итп.

include => from-internal-noxfer
include => from-internal-xfer
include => bad-number ; auto-generated


include => from-internal-noxfer-custom
include => from-internal-noxfer-additional ; auto-generated



include => from-internal-custom
include => from-internal-additional ; auto-generated
exten => s,1,Macro(hangupcall)
exten => h,1,Macro(hangupcall)

Функции для работы со строками

Размер строки

${LEN(foo)}

возвращает размер строки foo. Например,

exten => 100,1,Set(Fruit=pear)
exten => 100,2,NoOp(${LEN(Fruit)})
exten => 100,3,NoOp(${LEN(${Fruit})})

первая команда NoOp должна показать значение 5 (размер строки «fruit»). Вторая команда NoOp должна показать значение 4 (размер строки «pear»).

Этот метод позволяет производить прекрасную проверку на пустые строки и на переменные содержащие NULL.

Подстроки

${foo:offset:length}

возвращает подстроку строки foo, начиная со смещения offset и ограничивая возвращаемую строку размером в length символов.

  • Если значение offset отрицательное, то смещение будет отсчитываться не от начала строки, а от ее конца.
  • Если значение length пропущено или отрицательное, то размер возвращаемой строки не ограничивается и будет возвращена вся строка, начиная со смещения offset.

Примеры:

${123456789:1} - возвращает строку 23456789
${123456789:-4} - возвращает строку 6789
${123456789:0:3} - возвращает строку 123
${123456789:2:3} - возвращает строку 345
${123456789:-4:3} - возвращает строку 678

Пример использования:

exten => _NXX.,1,Set(areacode=${EXTEN:0:3}) - получаем первые 3 цифры 
из переменной ${EXTEN}
exten => _516XXXXXXX,1,Dial(${EXTEN:3}) - получаем значение переменной ${EXTEN} 
после 3 цифры
exten => 100,1,Set(whichVowel=4)
exten => 100,2,Set(foo=AEIOU:${whichVowel}:1) - переменная ${foo} 
будет иметь значение 'U'

Соединение строк

Для соединения двух строк, просто напишите директиву, как показано ниже:

${foo}${bar}
555${theNumber}
${longDistancePrefix}555${theNumber}

Математические операции с переменными

Для математических действий с переменными, например, инкремент, умножение, сложение. Просто напишите:

exten => s,1,Set(SOMEVAR=$) ; инкремент
exten => s,2,Set(SOMEVAR=$) ; умножение и т.д.
Вы должны использовать пробелы в выражениях, как показано выше.

Начиная с релиза Asterisks 1.2, так же доступна функция MATH...

exten => s,1,Set(SOMEVAR=${MATH(${SOMEVAR}+1)}) ; инкремент
exten => s,2,Set(SOMEVAR=${MATH(2*${SOMEVAR})}) ; умножение и т.д.

Оповещение о занятости второй линии

Я буду опираться на указанную статью по настройке астериска. Покажу на ее примере, как оповестить звонящего о том, что абонент уже разговаривает. Там я предлагаю настройку реакции на занятую линию через функцию ChanIsAvail, которая проверяет доступность канала. Эта функция подойдет, только если у вас стоит ограничение на один канал, как приводится у меня в статье. Если вы хотите использовать более одной линии, то указанная функция не подходит. Сейчас я расскажу более красивое решение через проверку статуса экстеншена.

Я не проверял, есть ли в наборе звуков для asterisk готовая фраза на этот случай. Вместо этого просто записал свою, проговорив примерно следующее: «В настоящий момент абонент разговаривает, подождите на линии или перезвоните позже».

Итак, записываем указанную фразу в wav формате и загружаем на сервер. Перекодируем в формат, понятный для астериск:

# sox abonent_zanyat.wav -r 8000 -c 1 -s abonent_zanyat1.wav resample -ql

Кладем новый файл в директорию /var/lib/asterisk/sounds. После этого открываем диалплан и редактируем контекст внутренних звонков. Рассмотрю самый простой случай, когда изначально было вот так:

exten => _XXX,1,Dial(SIP/${EXTEN})

Редактируем и приводим к такому виду:

exten => _XXX,1,Noop(HINT STATUS - ${EXTENSION_STATE(${EXTEN})})
exten => _XXX,n,ExecIf($?Playback(abonent_zanyat1))
exten => _XXX,n,ExecIf($?Dial(SIP/${EXTEN},120,Ttm))
exten => _XXX,n,ExecIf($?Playback(abonent_zanyat1))
exten => _XXX,n,ExecIf($?Dial(SIP/${EXTEN},120,Ttm))
exten => _XXX,n,ExecIf($?Playback(abonent_zanyat1))
exten => _XXX,n,ExecIf($?Dial(SIP/${EXTEN},120,Ttm))
exten => _XXX,n,Dial(SIP/${EXTEN},30,Tt)

Разберем, что здесь происходит:

  1. Noop(HINT STATUS — ${EXTENSION_STATE(${EXTEN})}) — чисто отладочная информация, которую потом можно убрать. Просто выводим статус экстеншена в лог. Эта информация помогла мне решить одну проблему, о которой расскажу позже.
  2. ExecIf($?Playback(abonent_zanyat1)) — если статут экстеншена INUSE, проговариваем записанную фразу.
  3. ExecIf($?Dial(SIP/${EXTEN},120,Ttm)) — после проговоренной фразы звонящий будет 120 секунд ожидать ответ и слушать музыку. Далее идет обработка других статусов — RINGINUSE, BUSY. Ниже приведу описание всех возможных статусов.
  4. Dial(SIP/${EXTEN},30,Tt) — если он так и не ответил, набираем ему еще раз, слышим уже обычные гудки в течении 30 секунд.

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

В общем, это не принципиально, сами придумайте, как поступать в конкретном случае. Теперь про EXTENSION_STATE. Функция может принимать следующие значения:

  • UNKNOWN
  • NOT_INUSE
  • INUSE
  • BUSY
  • INVALID
  • UNAVAILABLE
  • RINGING
  • RINGINUSE
  • HOLDINUSE
  • ONHOLD

Я не знаю точного описания всех значений, не нашел, но по названиям примерно понятно. Подобрал опытным путем те, что меня интересуют. Если пользователь разговаривает, значение BUSY, если он сам набирает кому-то, то INUSE, если ему идет звонок, но он еще не ответил, то RINGINUSE. Остальные значения в рамках указанной задачи меня не интересуют. Можете добавить сами обработку других ситуаций. Я раньше использовал для этого функцию DIALSTATUS, примерно так:

exten => _XXX,n,Goto(num-${DIALSTATUS},1)
exten => num-NOANSWER,1,Wait(2)
exten => num-NOANSWER,n,Playback(noanswer)
exten => num-CHANUNAVAIL,1,Wait(2)
exten => num-CHANUNAVAIL,n,Playback(vm-isunavail)

Изначально я собирался решать вопрос с уведомлением о занятости пира через dialstatus, но эта функция не подходит. Опытным путем узнал, что если первая линия занята, новый звонок идет на вторую, статус линии не будет busy. Эта функция не подходит.

И еще одно важное замечание. Во время тестирования, пока я не добавил явно в свойства пира call-limit=2, я не мог определить статус пира через EXTENSION_STATE

Не помню, какое значение получал при звонке, но оно точно было не BUSY во время разговора по первой линии. После того, как явно указал call-limit, все заработало как надо.

Во время настройки на разных версиях Asterisk замечал, что иногда не работает функция EXTENSION_STATE. Возвращает значение UNKNOWN. В этом случае использовал другую функцию — DEVICE_STATE. Все остальные настройки те же. Не вдавался в подробности, почему это так. Сходу не мог понять, в чем причина.

Introduction

This application is used to manage telemarketing campaigns e.g. call customers and playback a pre-recorded voice message or connect answered calls to operators. Other use cases are also possible as new features can be easily implemented using custom Asterisk dial plan.

This application uses Asterisk RESTful Interface (ARI) and requires Asterisk v12 and newer. It is implemented inside Odoo using Python threads and does not have separate software components.

That’s why You have to run Odoo in threaded mode devoted to one database disabling workers option and settings dbfilter option in Odoo configuration (see example of confoguration file below).

Параметр call-limit в asterisk

У меня есть очень популярная статья про настройку asterisk. Там я немного затронул тему активных линий у абонента, но совершил небольшую ошибку. Я упомянул параметр пира call-limit и порекомендовал поставить значение 1 тому, кто не будет использовать вторую линию. И указал, что в этом случае, если абонент уже разговаривает, звонящий услышит в трубке длинные гудки и поймет, что занято. Но это не так.

При значении call-limit=1, если линия уже занята, то звонящий просто получит сброс звонка, и не поймет в чем дело. А в логе сервера будет ошибка:

ERROR chan_sip.c: Call to peer '101' rejected due to usage limit of 1

Если не настроить обработку этого события, то будет вообще не понятно, в каком состоянии абонент и почему идет сброс. Так что для обычных пиров все же стоит ставить call-limit хотя бы 2, чтобы была возможность работать со второй линией и не сбрасывать звонки. Если двух линий уже не хватает, то надо организовывать очередь. Делать лимит линий выше двух для обычного абонента считаю, что нет смысла.

Сегодня я предложу более элегантное решение при настройке нескольких линий.

Колл Центр исходящих вызовов

Asterisk Predictive Auto Dialer

Система автоматического обзвона абонентов

Мы предлагаем систему предсказывающего (predictive dialer) набора абонентов, для организации исходящего обзвона и обработки отвеченных вызовов в очереди колл центра.

Cистема определяет кол-во свободных агентов и инициирует кол-во вызовов, которые могут быть обработаны в данный момент времени.
Также может учитываться процент успешных дозвонов и уровень обслуживания очереди за установленный период.

Помимо работы с очередью система может быть использована для оповещения абонентов, проверки номеров (Answer Machine Detect), сбора массовых телеконференций и т.д..

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

Отчет о работе системы добавляется в Asterisk Call Center Stats.

Troubleshooting

Enable debug mode.

Run Odoo with »—log-level=debug» and see errors.

Asterisk func curl not found

Install libcurl-devel, re-run ./configure and make menuselect, get sure res_curl and func_curl are selected and recompile and install these modules.

Channel update script Not Found error

This is how this error looks in Odoo’s log:

«Nondb routing» means there is either no database or more then one database available for selection. You have to check dbfilter option to be set correctly.

And this is how this error looks like in Asterisk console:

If you see something like that:

Make sounds folder accessible for Asterisk.

Outdated python requests lib

python-requests package may be outdated. You may need to uninstall it and install a fresh one from python package repo (pip install requests —update).

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

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