Asterisk mysql настройка русской кириллицы таблицы cdr. Набор основных функций Asterisk. Создание дополнительных функций

Выполнение манипуляций с базой данных MySQLИнсталляция Команда является частью пакета дополнений , который доступен в Asterisk CVS. Это ДОПОЛНЕНИЕ к asterisk, данный пакет не устанавливается по умолчанию и должен быть скачан и установлен из пакета дополнений .Описание MYSQL(): Выполнение манипуляций с базой данных MySQLСинтаксис MYSQL(Connect connid dhhost dbuser dbpass dbname)

Соединение с базой данных. В аргументах содержаться стандартные параметры для соединения с базой MySQL, которые будут переданы функции mysql_real_connect. Идентификатор соединения будет возвращен в переменной ${connid}

MYSQL(Query resultid ${connid} query-string)

Выполняет стандартный запрос к базе данных MySQL, запрос содержится в параметре query-string, используется идентификатор соединения определенный в ${connid}. Результат запроса будет сохранен в переменной ${resultid}.

MYSQL(Fetch fetchid ${resultid} var1 var2 ... varN)

Если есть доступные для выборки записи в результате запроса к базе данных, ${fetchid} устанавливается в 1 и одна запись будет выбрана из возвращенного результата, хранящегося в ${resultid}. Полученные значения полей будут назначены переменным ${var1}, ${var2} ... ${varN} согласно указанному в запросе порядку.

Если нет доступных записей, то переменная ${fetchid} будет сброшена в 0 и ${var1}, ${var2} ... ${varN} будут возвращены без изменений.

MYSQL(Clear ${resultid})

Очищает память и структуры данных связанных с результатом запроса.

MYSQL(Disconnect ${connid})

Прекращает соединение с базой MySQL с данным идентификатором.

Примеры

exten => _X.,1,MYSQL(Connect connid localhost dbuser dbpass dbname)
exten =>
exten =>
exten => _X.,4,GotoIf($[${AGIScript} = NULL]?5:7)
exten => _X.,5,AGI(${DefaultAGIScript},${EXTEN})
exten => _X.,6,Goto(_X.,8)
exten => _X.,7,AGI(${AGIScript},${EXTEN})
exten => _X.,8,MYSQL(Clear ${resultid})
exten => _X.,9,MYSQL(Disconnect ${connid})
exten => _X.,10,Hangup


В вышеприведенном примере, если звонящий пользователь отсоединится, когда его вызов обрабатывается, где-то в промежутке между 5 и 7 приоритетом, тогда функции MYSQL(Clear...) и MYSQL(Disconnect....) не будут выполнены. В этом случае соединение останется не закрытым, и с каждым новым таким вызовом число незакрытых соединений будет увеличиваться. В конечном счете, в MySQL сервере кончится лимит на количество одновременно открытых соединений с базой. (В зависимости от установленного лимита в конфигурационном файле mysql). Поэтому, для данного случая нужно немного изменить последовательность действий так, как показано ниже:

exten => _X.,1,MYSQL(Connect connid localhost asterisk dbpass asterisk)
exten => _X.,2,MYSQL(Query resultid ${connid} SELECT\ scriptname\ from\ mac2pin\ where\ userid=${CALLERIDNAME})
exten => _X.,3,MYSQL(Fetch fetchid ${resultid} AGIScript)
exten => _X.,4,MYSQL(Clear ${resultid})
exten => _X.,5,MYSQL(Disconnect ${connid})
exten => _X.,6,GotoIf(${fetchid}?7:9)
exten => _X.,7,AGI(${DefaultAGIScript},${EXTEN})
exten => _X.,8,Hangup
exten => _X.,9,AGI(${AGIScript},${EXTEN})
exten => _X.,10,Hangup


Замечание для данного случая. Тут мы очищаем память и отсоединяемся от базы данных сразу же после Fetch. Это гарантирует то, что перед тем как мы запустим на выполнение AGI скрипт, мы будем уверены, что Очистка памяти и отсоединение от базы данных уже было выполнено. Этот метод можно применять, если Вам действительно необходимо получить из базы что-то либо уникальное, либо часто изменяющееся.Другие моменты, которые необходимо учитывать: Экранирование пробелов с помощью символа \ при составлении запроса. Если Вы используете кавычки, то они будут отправлены в приложение, как часть запроса. Другие символы, для которых необходимо экранирование - это кавычки (\" и \"), запятая (\,) и обратный слеш (\\). (Используйте Mysqlscape - смотри нижеприведенный пример) Возвращаемые запросом поля, будут назначены переменным в том же порядке, в каком они были возвращены MySQL. Не рекомендуется использовать запрос типа "SELECT *", потому что нет гарантии того, что возвращаемые поля будут в том порядке, в котором Вы их ожидаете. Вам нет необходимости использовать псевдонимы для выбираемых из базы полей типа: "SELECT (длинное выражение) as короткое_имя", потому что имя поля не влияет на порядок полей, полученных по этому запросу. В описании asterisk для этой команды значится, что ${fetchid} устанавливается в TRUE, если есть доступные записи. Это неправильно. Оно устанавливается в 1, если есть данные после выполнения последней операции fetch и устанавливается в 0, если нет.Пример

exten => 888,1,MYSQL(Connect connid localhost ipcontact passwd ipcontact)
exten => 888,2,MYSQL(Query resultid ${connid} SELECT\ `number`\ FROM\ `phones`\ WHERE\ `channel`=\"${chan}\")
exten => 888,3,MYSQL(Fetch foundRow ${resultid} number) ; fetch row
exten => 888,4,GotoIf($["${foundRow}" = "1"]?7:5) ; leave loop if no row found
exten => 888,5,NoOp(${number})
exten => 888,6,Goto(3) ; continue loop if row found
exten => 888,7,MYSQL(Clear ${resultid})
exten => 888,8,MYSQL(Disconnect ${connid})

Примечания __Обратите внимание на поправку с MYSQL(Fetch). ${fetchid} - не обязательно устанавливается в 1, если есть доступные данные, Эта функция устанавливает это поле в 1, если есть данные только для этого конкретного вызова MYSQL(Fetch). Это было изначально неправильно объяснено, еще с создания описания данной функции.
Если Вы не очистите память и не отсоединитесь от MySQL, соединения останутся открытыми и в конечном итоге их количество достигнет лимита, установленного для MySQL сервера. Тогда, единственный путь исправить эту ситуацию - это перезагрузка Asterisk, или еще можно посмотреть список процессов и прибить эти висящие процессы командой kill.
В дополнение к вышесказанному, Flobi придумал некое извращение для очистки ресурсов. Так как я не уловил логику его действий и нашел ее полным бредом, то переводить я это не стал.

Файл cdr.conf предназначен для записи параметров вызовов в файл или базу данных. Хранение записей вызовов бывает нужно для учета вызовов и времени разговора абонента, предотвращение мошенничества и т.д. . Параметры файла cdr.conf указывают, как asterisk должен обрабатывать эту информацию.
Список некоторых параметров раздела файла cdr.conf:

enable = yes\no - Пишется или нет CDR. По умолчанию – yes.

batch = yes\no - Позволяет записывать данные в буфер, а не в базу данных, чтобы сократить нагрузку на систему.
Если задано значение yes, в случае неожиданного сбоя системы данные могут быть утрачены!

size = 100 Максимальное количество записей CDR, накапливаемых в буфере перед передачей на серверную часть систем хранения CDR. Нужно использовать если batch = yes. По умолчанию – 100 записей.

time = 300 Через какое время в секундах очищается буфер и вносятся данные в базу данных, независимо от количества записей в буфере (определяется параметром size). Значение по умолчанию – 300 с.

scheduleronly = yes/no. При передаче в удаленную базу данных большого объема CDR-записей, нужно задать для scheduleronly значение yes. Эта настройка будет укажет Asterisk обрабатывать запись CDR в новом потоке, по сути, назначая специальный планировщик для этой функции.

safeshutdown = yes/no. Значение yes не даст Asterisk полностью выключиться, пока буфер не будет полностью очищен и вся информация не будет записана в базу данных. Если для этого параметра задано значение no и происходит выключение Asterisk при наличии информации в буфере, эта информация, скорее всего, будет потеряна.

endbeforehexten = yes\no. Значение по умолчанию – no. Если указать yes, CDR будет завершаться перед выполнением добавочного номера h, так что такие значения CDR, как end и billsec, могут быть извлечены в этом добавочном номере.

Более подробную информацию можно получить в шаблоне файла cdr.conf.

Пример файла cdr.conf:
;
; Asterisk Call Detail Record engine configuration
;
; CDR is Call Detail Record, which provides logging services via a variety of
; pluggable backend modules. Detailed call information can be recorded to
; databases, files, etc. Useful for billing, fraud prevention, compliance with
; Sarbanes-Oxley aka The Enron Act, QOS evaluations, and more.
;

; Define whether or not to use CDR logging. Setting this to "no" will override
; any loading of backend CDR modules. Default is "yes".
;enable=yes

; Define whether or not to log unanswered calls. Setting this to "yes" will
; report every attempt to ring a phone in dialing attempts, when it was not
; answered. For example, if you try to dial 3 extensions, and this option is "yes",
; you will get 3 CDR"s, one for each phone that was rung. Default is "no". Some
; find this information horribly useless. Others find it very valuable. Note, in "yes"
; mode, you will see one CDR, with one of the call targets on one side, and the originating
; channel on the other, and then one CDR for each channel attempted. This may seem
; redundant, but cannot be helped.
;
; In brief, this option controls the reporting of unanswered calls which only have an A
; party. Calls which get offered to an outgoing line, but are unanswered, are still
; logged, and that is the intended behaviour. (It also results in some B side CDRs being
; output, as they have the B side channel as their source channel, and no destination
; channel.)
;unanswered = no

; Define whether or not to log congested calls. Setting this to "yes" will
; report each call that fails to complete due to congestion conditions. Default
; is "no".
;congestion = no

; Normally, CDR"s are not closed out until after all extensions are finished
; executing. By enabling this option, the CDR will be ended before executing
; the "h" extension and hangup handlers so that CDR values such as "end" and
; "billsec" may be retrieved inside of of this extension.
; The default value is "no".
;endbeforehexten=no

; Normally, the "billsec" field logged to the backends (text files or databases)
; is simply the end time (hangup time) minus the answer time in seconds. Internally,
; asterisk stores the time in terms of microseconds and seconds. By setting
; initiatedseconds to "yes", you can force asterisk to report any seconds
; that were initiated (a sort of round up method). Technically, this is
; when the microsecond part of the end time is greater than the microsecond
; part of the answer time, then the billsec time is incremented one second.
; The default value is "no".
;initiatedseconds=no

; Define the CDR batch mode, where instead of posting the CDR at the end of
; every call, the data will be stored in a buffer to help alleviate load on the
; asterisk server. Default is "no".
;
; WARNING WARNING WARNING
; Use of batch mode may result in data loss after unsafe asterisk termination
; ie. software crash, power failure, kill -9, etc.
; WARNING WARNING WARNING
;
;batch=no

; Define the maximum number of CDRs to accumulate in the buffer before posting
; them to the backend engines. "batch" must be set to "yes". Default is 100.
;size=100

; Define the maximum time to accumulate CDRs in the buffer before posting them
; to the backend engines. If this time limit is reached, then it will post the
; records, regardless of the value defined for "size". "batch" must be set to
; "yes". Note that time is in seconds. Default is 300 (5 minutes).
;time=300

; The CDR engine uses the internal asterisk scheduler to determine when to post
; records. Posting can either occur inside the scheduler thread, or a new
; thread can be spawned for the submission of every batch. For small batches,
; it might be acceptable to just use the scheduler thread, so set this to "yes".
; For large batches, say anything over size=10, a new thread is recommended, so
; set this to "no". Default is "no".
;scheduleronly=no

; When shutting down asterisk, you can block until the CDRs are submitted. If
; you don"t, then data will likely be lost. You can always check the size of
; the CDR batch buffer with the CLI "cdr status" command. To enable blocking on
; submission of CDR data during asterisk shutdown, set this to "yes". Default
; is "yes".
;safeshutdown=yes

;
;
; CHOOSING A CDR "BACKEND" (what kind of output to generate)
;
; To choose a backend, you have to make sure either the right category is
; defined in this file, or that the appropriate config file exists, and has the
; proper definitions in it. If there are any problems, usually, the entry will
; silently ignored, and you get no output.
;
; Also, please note that you can generate CDR records in as many formats as you
; wish. If you configure 5 different CDR formats, then each event will be logged
; in 5 different places! In the example config files, all formats are commented
; out except for the cdr-csv format.
;
; Here are all the possible back ends:
;
; csv, custom, manager, odbc, pgsql, radius, sqlite, tds
; (also, mysql is available via the asterisk-addons, due to licensing
; requirements)
; (please note, also, that other backends can be created, by creating
; a new backend module in the source cdr/ directory!)
;
; Some of the modules required to provide these backends will not build or install
; unless some dependency requirements are met. Examples of this are pgsql, odbc,
; etc. If you are not getting output as you would expect, the first thing to do
; is to run the command "make menuselect", and check what modules are available,
; by looking in the "2. Call Detail Recording" option in the main menu. If your
; backend is marked with XXX, you know that the "configure" command could not find
; the required libraries for that option.
;
; To get CDRs to be logged to the plain-jane /var/log/asterisk/cdr-csv/Master.csv
; file, define the category in this file. No database necessary. The example
; config files are set up to provide this kind of output by default.
;
; To get custom csv CDR records, make sure the cdr_custom.conf file
; is present, and contains the proper section. The advantage to
; using this backend, is that you can define which fields to output, and in
; what order. By default, the example configs are set up to mimic the cdr-csv
; output. If you don"t make any changes to the mappings, you are basically generating
; the same thing as cdr-csv, but expending more CPU cycles to do so!
;
; To get manager events generated, make sure the cdr_manager.conf file exists,
; and the section is defined, with the single variable "enabled = yes".
;
; For odbc, make sure all the proper libs are installed, that "make menuselect"
; shows that the modules are available, and the cdr_odbc.conf file exists, and

;
; For pgsql, make sure all the proper libs are installed, that "make menuselect"
; shows that the modules are available, and the cdr_pgsql.conf file exists, and
; has a section with the proper variables defined.
;
; For logging to radius databases, make sure all the proper libs are installed, that
; "make menuselect" shows that the modules are available, and the
; category is defined in this file, and in that section, make sure the "radiuscfg"
; variable is properly pointing to an existing radiusclient.conf file.
;
; For logging to sqlite databases, make sure the "cdr.db" file exists in the log directory,
; which is usually /var/log/asterisk. Of course, the proper libraries should be available
; during the "configure" operation.
;
; For tds logging, make sure the proper libraries are available during the "configure"
; phase, and that cdr_tds.conf exists and is properly set up with a category.
;
; Also, remember, that if you wish to log CDR info to a database, you will have to define
; a specific table in that databse to make things work! See the doc directory for more details
; on how to create this table in each database.

Запись статистики, Asterisk, ведет автоматически, если загружен модуль cdr_csv.so, статистика ведется по умолчанию в /var/log/asterisk/cdr-csv/Master.csv. Этой статистики в принципе достаточно, но работать не удобно. Удобней всего обрабатывать статистику хранящуюся в базе. Для того, что бы статистика писалась в MySQL нужно установить из портов:

предварительно добавив в Makefile строку:

CFLAGS+=-DMYSQL_LOGUNIQUEID

cd / usr/ ports/ net/ asterisk-addons/
make install clean

В начале появится конфигурационный диалог с одним пунктом:

┌────────────────────────────────────────────────────────────────────┐ │ Options for asterisk-addons 1.4.6_4 │ │ ┌────────────────────────────────────────────────────────────────┐ │ │ │ [ X] SAMPLE_CONFIG Install sample configuration files │ │ │ │ │ │ │ │ │ │ ├─└────────────────────────────────────────────────────────────────┘─┤ │ [ OK ] Cancel │ └────────────────────────────────────────────────────────────────────┘

Выбираем этот пункт, он позволяет разместить примеры конфигурационных файлов. А примеры нам пригодятся и ждем когда соберется модуль.
Естественно, у вас уже должен быть сервер MySQL, клиента при сборке порт вытащит по депендам.
После того, как все оберется приступаем к настройке.
В первую очередь создадим базу asterisk в MySQL и пользователя asterisk с паролем asterisk-123. Для этого подключаемся к MySQL и выполняем такие команды:

mysql -uroot
create database asterisk;
grant all on asterisk.* to "asterisk" @ "localhost" identified by "asterisk-777" ;

и теперь создадим таблицу в которой будем хранить информацию:


USE asterisk;

CREATE TABLE ` cdr` (
` calldate` datetime NOT NULL default "0000-00-00 00:00:00" ,
` clid` varchar(80 ) NOT NULL default "" ,
` src` varchar(80 ) NOT NULL default "" ,
` dst` varchar(80 ) NOT NULL default "" ,
` dcontext` varchar(80 ) NOT NULL default "" ,
` channel` varchar(80 ) NOT NULL default "" ,
` dstchannel` varchar(80 ) NOT NULL default "" ,
` lastapp` varchar(80 ) NOT NULL default "" ,
` lastdata` varchar(80 ) NOT NULL default "" ,
` duration` int(11 ) NOT NULL default "0" ,
` billsec` int(11 ) NOT NULL default "0" ,
` disposition` varchar(45 ) NOT NULL default "" ,
` amaflags` int(11 ) NOT NULL default "0" ,
` accountcode` varchar(20 ) NOT NULL default "" ,
` userfield` varchar(255 ) NOT NULL default "",

` uniqueid` varchar(32 ) NOT NULL DEFAULT ""

) ;

Как вы можете заметить приведенная таблица совсем без индексов. Почему? Да все очень просто, индексы при каждой вставке новой записи пересчитываются, это замедляет работу с базой. При большой интенсивности звонков это может негативно отразится на работе. Если для вас скорость построения отчета более важна(работа select, выборка статистики), можно добавить индексы.

create index cld_idx on cdr(calldate) ;
create index src_idx on cdr(src) ;
create index dst_idx on cdr(dst) ;
create index acc_idx on cdr(accountcode) ;

Теперь настроим подключение к MySQL, для этого внесем в файл /usr/local/etc/asterisk/cdr_mysql.conf настройки на наш аккаунт MySQL:


[ global]
hostname =localhost
dbname =asterisk
table =cdr
password =asterisk-777
user =asterisk
sock =/ tmp/ mysql.sock
userfield =1

И загрузить модуль в астериск. Для этого подключимся к работающем астериску и загрузим модуль:
exten => _8.,1 ,MYSQL(Connect connid localhost asterisk asterisk-777 asterisk)
exten => _8.,2 ,Dial(SIP/ prov1/ ${EXTEN} )
exten => h,1 ,MYSQL(Disconnect ${connid} )

Последняя строка MYSQL(Disconnect ${connid}) - обязательна, задается она в extention h. h - это обработчик вызываемый при hangup, то есть когда разрывается канал связи, а попросту когда одна из сторон ложит трубку.

Чаще всего вызывается интерфейс SIP. Вызов осуществляется командой Dial().


exten => 100,1,Dial(DAHDI/1,20,tTr)
exten => 100,2,Voicemail(u100@default)
exten => 100,102,Voicemail(b100@default)
;
exten => 105,1,Dial(SIP/105,20,tTr)
exten => 105,n,Voicemail(u100@default)
exten => 105,n,Voicemail(b100@default)

Этот пример иллюстрирует разные варианты действий в случае, если на вызов не ответили. Сначала вызывается канал DAHDI/1, если через 20 секунд никто не ответил вызов перенаправляется на VoiceMail() с объявлением «абонент не отвечает»(u100), если же абонент занят, вызов перейдет на приоритет N+101, в нашем случае это приоритет 102.

2.2.2 Маршрутизация по CallerID в Asterisk

Пример маршрутизации по номеру вызывающего абонента:


exten => 100/1234567,1,Congestion
exten => 100,1,Dial(DAHDI/1,20)
exten => 100,2,Voicemail(u100)
exten => 100,102,Voicemail(b100)

Если вызывается екстеншен 100, то вызов направляется на интерфейс DAHDI/1, кроме случая если вызов осуществляет абонент с номера 1234567. В этом случае вызов отклоняется. На примере видно, что идентификатор вызывающего абонента задается формой "/1234567".

Еще один пример маршрутизации, теперь по отсутствию CallerID:


exten => 100/,1,Zapateller
exten => 100,1,Wait(1)
exten => 100,2,Dial(DAHDI/1)

В данном примере если поступает звонок без CallerID, то вызов блокируется с помощью приложения Zapateller().

2.2.3 Вызов группы телефонов в Asterisk

Часто требуется чтобы вызов по неответу перешел на другой телефон. Рассмотрим как это сделать на примере «оператор».


exten => _X.,1,Dial(DAHDI/1,15)
exten => _X.,2,Dial(DAHDI/1&DAHDI/2&DAHDI/3,15)
exten => _X.,3,Playback(companymailbox)
exten => _X.,4,Voicemail(100)
exten => _X.,5,Hangup

Вызов поступает на DAHDI/1, в случае если телефон занят или не отвечает в течении 15 секунд, звонок переходит на группу телефонов, включая и DAHDI/1. Если и на этот раз никто не поднимает трубку, вызов переходит на голосовую почту.

2.2.4 Вложенные контексты

Один контекст может включать в себя другие контексты, обрабатываемые в порядке перечисления.

include => [||||]

Где - включаемый контекст
опционально может использоваться:

  • - часы в которые действителен контекст (например рабочее время 9:00-17:00)
  • -дни недели (mon-fri)
  • - дни
  • - месяцы


exten => _.,1,Dial(SIP/trunk1/${EXTEN})

exten => _8.,1,Dial(SIP/trunk2/${EXTEN})

include => local
include => long

include => local

В этом примере контекст "local_long" включает два других контекста для городской и междугородней связи, а контекст "local_only" только для городской.

2.2.5 Исходящие вызовы в диалплане Asterisk

Направление исходящей связи можно реализовать определением короткого кода доступа (например "9"), или определить полностью шаблон набираемых номеров.


ignorepat => 9
exten => _9810.,1,Dial(DAHDI/g2/${EXTEN:1})
exten => _9810.,2,Congestion
include => longdistance

ignorepat => 9
exten => _98XXXXXXXXX,1,Dial(DAHDI/g2/${EXTEN:1})
exten => _98XXXXXXXXX,2,Congestion
include => local

ignorepat => 9
exten => _9XXXXXX,1,Dial(DAHDI/g2/${EXTEN:1})
exten => _9XXXXXX,2,Congestion
include => default

В этом примере рассматриваются 3 контекста с различными правами доступа к Телефонной сети Общего Пользования.

Конструкция "ignorepat => 9 " говорит Астериску не отключать тон готовности после набора заданной цифры.

Контекст позволяет набрать международный номер с любым количеством цифр.
Контекст - междугородний номер до 11-ти цифр.
Контекст - городской номер длинной до 7-ми цифр.

Переменная ${EXTEN:1} удаляет префикс:

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

2.2.6 Автоответчик-эхотест

Автоответчик-эхотест необходим, чтобы абонент мог позвонить на него, протестировать качество связи и работоспособность своего клиентского оборудования. При звонке на какой-либо внутренний номер (например 7000) будет происходить следующее:

  • Отвечаем на вызов.
  • Проигрывается заранее подготовленный аудиофайл-привествие.
  • Голос звонившего записывается в течение нескольких секунд, затем воспроизводится позвонившему.
  • Проигрывается заранее подготовленный аудиофайл-прощание.
  • Завершение вызова.

Для реализации этого сценария добавляем в файл extension.conf:

Exten => 7000,1,Answer() ;Поднимаем трубку
exten => 7000,n,Wait(2) ;Ожидание 2 секунды
exten => 7000,n,Playback(/etc/asterisk/sounds/demo-echotest) ;Проигрываем приветствие
exten => 7000,n,Wait(1) ;Ожидание 1 секунду
exten => 7000,n,Set(fname=/etc/asterisk/sounds/records/${STRFTIME(${EPOCH},%Y-%m-%d__%H-%M-%S)}__${CALLERID(number)}) ;Задаем имя и расположение файла-аудиозаписи
exten => 7000,n,Record(${fname}.gsm,0,10,x) ;Записываем голос звонившего в течение 10 секунд
exten => 7000,n,Playback(/etc/asterisk/sounds/beep) ;Уведомляем об окончании записи голоса сигналом
exten => 7000,n,Wait(1) ;Ожидание
exten => 7000,n,Playback(${fname}) ;Воспроизведение записи голоса
exten => 7000,n,Playback(/etc/asterisk/sounds/demo-echodone) ;Проигрываем прощание
exten => 7000,n,Wait(2) ;Ожидание
exten => 7000,n,Hangup() ;Завершаем вызов

2.2.7 Озвучить текущее системное время сервера

Exten = 060,1,PlayBack(welcome)
exten = 060,n,PlayBack(digits/today)
exten = 060,n,SayUnixTime(,a)
exten = 060,n,SayUnixTime(,k)
exten = 060,n,PlayBack(hours)
exten = 060,n,SayUnixTime(,M)
exten = 060,n,PlayBack(minutes)
exten = 060,n,PlayBack(goodbye)
exten = 060,n,Hangup()

2.2.8 Функция диктофон

Exten = 98,1,Answer()
exten = 98,n,Record(/tmp/myrecord%d:wav)
exten = 98,n,Wait(1)
exten = 98,n,Playback(${RECORDED_FILE})
exten = 98,n,Wait(1)
exten = 98,n,Hangup()

2.2.9 Порядок выбора нужного исходящего маршрута при использовании шаблонов

Каждый контекст, определенный в плане набора указывает Asterisk, как обрабатывать телефонные номера, вызовы на которые попадают в данный контекст. Поскольку у Вас есть возможность использования шаблонов при определении екстеншенов, то вполне вероятна ситуация, при которой вызываемому телефонному номеру может быть сопоставлен не один, а несколько екстеншенов в контексте. Asterisk не будет искать подходящие совпадения с шаблонными екстеншенами в том порядке, что Вы описали в плане набора, т.к. вначале шаблонные екстеншены проходят сортировку. Следовательно, Asterisk может обработать вызов телефонного номера совсем не так, как Вам бы хотелось.

В данном примере описывается, как Asterisk сортирует шаблонные екстеншены и как управлять этим порядком сортировки, чтобы быть уверенным, что Asterisk выберет именно тот екстеншен, который Вам нужен, когда с вызываемым номером могут совпадать два или более шаблонных екстеншенов. Рассмотрим контекст «example», например, Вам надо, чтобы вызовы на все номера, которые начинаются с 918, проходили через аналоговую телефонную линию, соединенную с интерфейсом DAHDI/1, а все остальные вызовы проходили через интерфейс SIP/200. Напишем пример:


exten => _918.,1,Dial(DAHDI/1/${EXTEN},tTr)
exten => _.,1,Dial(SIP/200/${EXTEN})
exten => h,1,Hangup

Но такая конструкция не работает как нам надо! Вы обнаружите, что вне зависимости от набранного номера, все вызовы идут через интерфейс SIP/200. Дело в том, что сначала Asterisk сортирует екстеншены, а уже потом ищет первое совпадение. Для того, чтобы увидеть в каком порядке Asterisk производит поиск совпадающего екстеншена, наберите в консоли Asterisk команду: "show dialplan example" . Вы увидите, что екстеншены отсортированы в следующем порядке:

1. _.
2. _918.
3. h

Обратите внимание, что этот порядок совсем не тот, что вы определили в файле extensions.conf. В этом списке запись _. является первой, а она подразумевает совпадение с любым набранным номером, включая и номера, которые начинаются с 918. Стоит отметить, что екстеншен h (hangup), также никогда не будет выбран, т. к. он тоже будет совпадать с шаблоном _.
Итак, как заставить Asterisk проверять шаблонные екстеншены на совпадения в нужном нам порядке? Есть выход - это использовать директиву include, включая в текущий контекст другой контекст, содержащий шаблонные екстеншены. Например:


include => example-sub
exten => h,1,Hangup
exten => _918.,1,Dial(Zap/1/${EXTEN})

exten => _.,1,Dial(Zap/2/${EXTEN})

Теперь Asterisk (для контекста «example») будет проверять номер на совпадение в следующем порядке:

1. _918.
2. h
3. _.

Что в этом случае делает Asterisk:

  • Записи exten внутри контекста "example" будут сохранены первыми и соответственно, проверяться также будут первыми.
  • Включенные контексты каждой директивой include будут сохраняться в порядке их описания.

Включенные контексты будут проверяться на совпадение в порядке их описания в файле extensions.conf

2.2.10 Dynamic Clip Routing

Входящие вызовы на звонившего внутреннего абонента

Внутренний абонент Asterisk выполняет исходящий вызов на городской или мобильный номер. Когда внешний абонент перезванивает на городскую линию Asterisk, его вызов направляется непосредственно на звонившего внутреннего абонента Asterisk. Для определения внутреннего номера звонившего абонента, используется стандартная база данных MySQL asteriskcdrdb, которая создается веб-интерфейсом FreePBX.


exten => _X.,1,Set(CHANNEL(language)=ru)
exten => _X.,n,Set(CALLID=${CALLERID(num):-11})
exten => _X.,n,MYSQL(Connect connidcdr localhost userdb passworddb asteriskcdrdb utf8);; All CALL
exten => _X.,n,MYSQL(Query resultidcdr ${connidcdr} SELECT * FROM cdr WHERE dst LIKE "%${CALLID}%" ORDER BY calldate DESC)
;; NOANSWER CALL ONLY
;;exten => _X.,n,MYSQL(Query resultidcdr ${connidcdr} SELECT * FROM cdr WHERE dst LIKE "%${CALLID}%" AND disposition LIKE "NO ANSWER" ORDER BY calldate DESC)
;;
exten => _X.,n,MYSQL(Fetch fetchid ${resultidcdr} accid calldate clid src dst)
exten => _X.,n,Set(number=${src})
exten => _X.,n,NoOp(caller --> ${clid} callee --> ${dst})
exten => _X.,n,GOTOIF($["${dst}" = ""]?nodst:dst)
exten => _X.,n(dst),MYSQL(Clear ${resultidcdr})
exten =>
exten => _X.,n,Dial(SIP/${number},20,tT)
exten => _X.,n,GotoIf($["${DIALSTATUS}" = "BUSY"]?nodst)
exten => _X.,n,GotoIf($["${DIALSTATUS}" = "NOANSWER"]?nodst)
exten => _X.,n,GotoIf($["${DIALSTATUS}" = "FAILED"]?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()

Выборка данных за последние 120 минут

SELECT * FROM cdr WHERE dst LIKE "%${CALLID}%" AND calldate >= DATE_SUB(NOW(), INTERVAL 120 MINUTE) ORDER BY calldate DESC;

или за три часа:

SELECT * FROM cdr WHERE dst LIKE "%${CALLID}%" AND calldate >= DATE_SUB(NOW(), INTERVAL 03 HOUR) ORDER BY calldate DESC;

Вывод консоли Asterisk при маршрутизации по cdr БД:

Executing [3216111@from-samsung:1 ] Goto("SIP/samsung-00001694", "dynamic_did,3216111,1") in new stack
-- Goto (dynamic_did,1111,1)
-- Executing [3216111v@dynamic_did:1 ] Set("SIP/samsung-00001694", "CHANNEL(language)=ru") in new stack
-- Executing [3216111@dynamic_did:2 ] Set("SIP/samsung-00001694", "CALLID=8129981138 ") in new stack
-- Executing [3216111@dynamic_did:3 ] MYSQL("SIP/samsung-00001694", "Connect connidcdr localhost userdb passworddb asteriskcdrdb utf8") in new stack
-- Executing [3216111@dynamic_did:4 ] MYSQL("SIP/samsung-00001694", "Query resultidcdr 11 SELECT * FROM cdr WHERE dst LIKE "%8129981138%" AND calldate >= DATE_SUB(NOW(), INTERVAL 03 HOUR)") in new stack
-- Executing [3216111@dynamic_did:5 ] MYSQL("SIP/samsung-00001694", "Fetch fetchid 12 accid calldate clid src dst") in new stack
WARNING: app_mysql.c:498 aMYSQL_fetch: ast_MYSQL_fetch: More fields (24) than variables (5)
-- Executing [3216111@dynamic_did:6 ] Set("SIP/samsung-00001694", "number=1000") in new stack
-- Executing [3216111@dynamic_did:7 ] NoOp("SIP/samsung-00001694", "кто звонил --> 1000 кому звонил --> 8129981138") in new stack
-- Executing [3216111@dynamic_did:8 ] GotoIf("SIP/samsung-00001694", "0?nodst:dst") in new stack
-- Goto (dynamic_did,1111,9)
-- Executing [3216111@dynamic_did:9 ] MYSQL("SIP/samsung-00001694", "Clear 12") in new stack
-- Executing [3216111@dynamic_did:10 ] MYSQL("SIP/samsung-00001694", "Disconnect 11") in new stack
-- Executing [3216111@dynamic_did:11 ] Dial("SIP/samsung-00001694", "SIP/1000,20,tT") in new stack

2.2.11 Ввод DTMF сигналов с клавиатуры телефона и запись данных в текстовый файл

Вызывающий абонент вводит dtmf сигналы, набираемые цифры записываются в текстовый файл.

Exten => s,1,Answer()
exten => s,2,Playback(access-code)
exten => s,3,Read(myvar,beep,6,15)
exten => s,n,SayDigits(${myvar})
exten => s,n,Set(CDR(userfield)=${myvar})
exten => s,n,Verbose(${myvar})
exten => s,n,System(echo "${CALLERID(num)}" - "${myvar}" >> /var/log/asterisk/test)

Описание шагов контекста:

  • Answer - ответить на вызов (установить соединение)
  • Playback - проиграть стандартное сообщение (введите код).
  • Read - считать данные вводимые пользователем и сохранить их в переменную $myvar
  • SayDigits - проговорить данные сохраненные в переменной.
  • Set / function "CDR" - сохранить данные из переменной в поле userfield CDR.
  • Verbose - вывести данные переменной в консоль и лог.
  • System - записать данные переменно в текстовый файл с новой строки.

2.2.12 Ввод DTMF сигналов с клавиатуры телефона и сохранение в базу данных MySQL

Exten => s,n,Authenticate(/tmp/pass,a)
exten => s,n,Playback(/var/lib/asterisk/sounds/custom/data)
exten => s,n,Read(data,beep,3,15)
exten => s,n,SayDigits(${data})
exten => s,n,MYSQL(Connect connid localhost test test test)
exten => s,n,MYSQL(Query resultid ${connid} INSERT INTO _${myvar} SET callerid=${CALLERID(name)}, data=${data}, date=${STRFTIME(${EPOCH},%C%y%m%d%H%M)})
exten => s,n,MYSQL(Clear ${connid})

Похоже на предыдущий пример, только данные сохраняются в MYSQL.

MYSQL(Connect connid dhhost[:dbport] dbuser dbpass dbname ) - соединиться с БД
MYSQL(Query resultid ${connid} query-string) - записать данные в БД
MYSQL(Clear ${resultid}) - очистить память

2.2.13 Ограничение количества одновременных вызовов по набранному номеру

  • Набирается номер 810ХХХХХХХ, в консоль выводится сообщение: Asterisk dialing 810ХХХХХХХ
  • функция GROUP() назначает вызовы в группу long
  • В консоль выводится сообщение: Number of concurrent calls are ${GROUP_COUNT(long)}, где ${GROUP_COUNT(long) = порядковый номер вызова.
  • Проверяется условие, если количество одновременных вызовов больше 1, вызов направляется в екстеншен 666 и разъединяется с выводом в консоль: Number of concurrent calls are 2 over limit.
  • Если вызов первый, номер набирается через SIP транк provider.

    Exten => _810.,1,Verbose(1,***** Asterisk dialing ${EXTEN} *******)
    same => n,Set(GROUP()=long)
    same => n,Verbose(1,**** Number of concurrent calls are ${GROUP_COUNT(long)})
    same => n,GotoIf($[${GROUP_COUNT(long)} > 1]?666)
    same => n,Set(CDR(userfield)=unblocked${EXTEN})
    same => n,Dial(SIP/provider/${EXTEN},60)
    same => 666,Verbose(1,***Number of concurrent calls are ${GROUP_COUNT(long)} over limit)
    same => n,Set(DIALSTATUS=CHANUNAVAIL)

    2.2.14 Резервные транки и LCR (выбор направления для исходящих звонков с наименьшей стоимостью)

    Весьма полезно настроить LCR (Least Coast Routing) и перенаправление в случае отказа внешней линии.


    exten => _98XXXXXXXXXX,1,Dial(DAHDI/g2/${EXTEN:1})
    exten => _98XXXXXXXXXX,2,Congestion()

    exten => _98495XXXXXXX,1,Dial(IAX/trunk/${EXTEN:1},tTr)
    exten => _98495XXXXXXX,n,Dial(DAHDI/g2/${EXTEN:1})
    exten => _98495XXXXXXX,n,Congestion()

    include => low_rate_moscow
    include => tolllongdistance

    В этом примере междугородние вызовы направляются на DAHDI интерфейс, но звонки в Москву направляются через более выгодного провайдера на IAX транк. В случае же недоступности IAX транка, вызовы перенаправляются через DAHDI.

    Одной из самых популярных (или, возможно, непопулярных) функций любой современной системы телефонной связи является голосовая почта. Естественно, Asterisk обладает достаточно гибкой системой голосовой почты. Среди ее возможностей можно упомянуть:

    • Неограниченные по размеру защищенные паролем ящики голосовой почты, в каждом из которых содержатся почтовые папки для
    • организации сообщений голосовой почты.
    • Различные приветствия для состояний «занято» и «недоступен».
    • Стандартные и специальные приветствия.
    • Возможность связывать телефоны с несколькими почтовыми ящиками, а почтовые ящики – с несколькими телефонами.
    • Уведомление о поступлении сообщения голосовой почты по электронной почте с возможностью прикрепления сообщения голосовой
    • почты в виде звукового файла.
    • Пересылка и широковещательные рассылки голосовой почты.
    • Индикатор ожидающих сообщений (мигающий световой индикатор
    • или прерывистый тональный сигнал) во многих типах телефонов.
    • Каталог ящиков голосовой почты служащих компании.

    Конфигурация голосовой почты описывается в конфигурационном файле voicemail.conf. Этот файл содержит набор настроек, которые могут использоваться для приведения системы голосовой почты в соответствие конкретным требованиям.

    Создание почтовых ящиков - Внутри каждого контекста голосовой почты определяются разные почтовые ящики. Для описания почтового ящика используется следующий синтаксис:
    почтовый ящик => пароль,имя[,email[,email_пейджера[,опции]]]
    Поясним назначение каждой части описания почтового ящика:

    • почтовый ящик - Это номер почтового ящика. Обычно он соответствует добавочному номеру ассоциированного с ним телефонного аппарата.
    • пароль - Числовой пароль, который будет использоваться владельцем почтово-го ящика для доступа к своей голосовой почте. Если пользователь изменит свой пароль, система обновит это поле в файле voicemail.conf.
    • имя - Это имя владельца почтового ящика. Текст данного поля используется в телефонном справочнике компании для обеспечения возможности абонентам при вызове применять имена пользователей.
    • email - Это адрес электронной почты владельца ящика голосовой почты. Asterisk может посылать уведомления о получении голосовой почты (включая само сообщение голосовой почты) на заданный ящик электронной почты.
    • email_пейджера - Это электронный адрес пейджера или мобильного телефона владельца ящика голосовой почты. Asterisk может посылать короткое сообщение, уведомляющее о получении голосовой почты, на заданный адрес электронной почты.
    • опции - Это список опций, определяющих часовой пояс, в котором находится владелец почтового ящика, и переопределяющих глобальные настройки голосовой почты. Существует девять допустимых опций: attach, serveremail, tz, saycid, review, operator, callback, dialoutи exitcontext. Эти опции определяются парами опция=значение, разделяемыми символами вертикальной черты (|). Опция tzзадает часовой пояс пользователя соответственно часовому поясу, определен-ному ранее в разделе файла voicemail.conf. Остальные восемь опций переопределяют соответствующие их именам глобальные настройки голосовой почты.

    Вот типовое описание почтового ящика:

    101 => 1234,Joe Public,[email protected] ,[email protected] ,tz=central|attach=yes

    Продолжая создание нашего диалплана из последней главы, зададим ящики голосовой почты для Джона и Джейн. Для Джона определим пароль 1234, а для Джейн – 4444 (помните, это делается в файле voicemail.conf, а не в extensions.conf):


    101 => 1234,John Doe,[email protected] ,[email protected]
    102 => 4444,Jane Doe,[email protected] ,[email protected]

    Добавление голосовой почты в диалплан - Теперь, когда созданы почтовые ящики для Джейн и Джона, давайте обеспечим возможность абонентам оставлять сообщения для них в случае, если они не отвечают на звонок. Для этого воспользуемся приложением VoiceMail(). Приложение VoiceMail() направляет вызывающего абонента на заданный почтовый ящик, чтобы он мог оставить сообщение. Почтовый ящик должен быть описан так: почтовый ящик@контекст, где контекст – имя контекста голосовой почты. Сюда также могут быть добавлены буквы b или uдля запроса типа приветствия. Если используется буква b , абонент услышит сообщение о занятости владельца почтового ящика. Если используется буква u , абонент услышит сообщение о недоступности владельца почтового ящика (если таковое существует). Применим это в нашем диалплане. Ранее в контексте располагалась следующая строка, обеспечивающая возможность звонить Джону:

    Exten => 101,1,Dial(${JOHN})

    Теперь давайте добавим сообщение о недоступности, которое услышит вызывающий абонент, если Джон не ответит на звонок в течение 10 секунд.
    Помните, второй аргумент в приложении Dial() – время ожидания. Если до истечения времени ожидания ответа на вызов не последовало, звонок перенаправляется в следующий приоритет. Зададим время ожидания 10 секунд и добавим приоритет для перевода абонента на голосовую почту, в случае если Джон не отвечает на звонок вовремя:

    Exten => 101,1,Dial(${JOHN},10)
    exten => 101,n,VoiceMail(101@default,u)

    Теперь скорректируем это так, чтобы, если Джон занят (в другом звонке), абонент перенаправлялся на голосовую почту, где слышал бы сообщение о занятости. Для этого воспользуемся переменной ${DIALSTATUS}, в которой содержится одно из значений статуса (список всех возможных значений можно получить в консоли Asterisk с помощью команды "core show application dial" ):

    Exten => 101,1,Dial(${JOHN},10)
    exten => 101,n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy:unavail)
    exten => 101,n(unavail),Voicemail(101@default,u)
    exten => 101,n,Hangup()
    exten => 101,n(busy),VoiceMail(101@default,b)
    exten => 101,n,Hangup()

    Теперь абоненты будут получать сообщение голосовой почты Джона (с соответствующим приветствием), если Джон занят или недоступен. Однако осталась небольшая проблема – Джон не имеет возможности извлекать свои сообщения. Исправим это.

    Организация доступа к голосовой почте - Пользователи могут извлекать свои сообщения голосовой почты, менять опции и записывать приветствия голосовой почты с помощью приложения VoiceMailMain(). В своей типовой форме приложение VoiceMailMain() вызывается без аргументов. Добавим в контекст диалплана добавочный номер 700, чтобы внутренние пользователи могли выполнять звонки для доступа к своим сообщениям голосовой почты:

    Exten => 700,1,VoiceMailMain()

    Создание телефонного справочника для набора номера по имени - Осталась последняя заслуживающая внимания функция системы голосовой почты Asterisk – телефонный справочник для набора номера по имени. Создается он с помощью приложения Directory(). Это приложение, используя имена, заданные в описаниях почтовых ящиков в файле voicemail.conf, предоставляет абоненту телефонный справочник абонентов АТС для набора номеров по имени.

    Directory() принимает три аргумента: контекст голосовой почты, из которого считываются имена, контекст диалплана, в котором вызывается
    пользователь (необязательный), и строку опций (также необязательный). По умолчанию Directory() ведет поиск пользователя по фамилии.
    Если передается опция f, поиск осуществляется по имени. Добавим два справочника для набора номера по имени в контекст нашего
    образца диалплана, чтобы абоненты могли выполнять поиск или по имени, или по фамилии:

    Exten => 8,1,Directory(default,incoming,f)
    exten => 9,1,Directory(default,incoming)

    Если абоненты нажмут кнопку 8, они получат справочник, составленный по именам. Если они наберут 9, то получат справочник, составленный по фамилиям.

    2.2.16 Zapateller()

    Zapateller() – это простое приложение Asterisk, которое воспроизводит специальный информационный тон в начале звонка. Устройства автоматического набора (обычно используемые в системах продаж по телефону) принимают этот тон за сигнал разъединения линии. Причем они
    не только прекратят вызов, но также пометят данный номер как не обслуживаемый, что поможет избежать всех видов телемаркетинговых звонков. Чтобы использовать эту функциональность в своем диалплане, вам надо просто вызвать приложение Zapateller().

    Также применим необязательную опцию nocallerid, чтобы тон воспроизводился только в случае, если входящий вызов не предоставляет информации о Caller ID (ID звонящего). Вот пример использования приложения Zapateller()в добавочном номере контекста :


    exten => s,1,Zapateller(nocallerid)
    exten => s,n,Playback(enter-ext-of-person)

    2.2.17 Парковка вызова

    Еще одна удобная функция – парковка вызова. Она обеспечивает возможность перевести вызов в состояние ожидания, поставить его на «парковку», чтобы он мог быть принят на другом добавочном номере. Все параметры парковки вызовов (такие, как используемые добавочные номера, количество мест и т. д.) задаются в конфигурационном файле features.conf . Раздел файла features.conf содержит четыре настройки, касающиеся парковки вызовов:

    • parkext - Это добавочный номер для парковки. Передайте вызов на этот добавочный номер – и система сообщит, в какой парковочный слот он помещен. Добавочный номер для парковки по умолчанию – 700.
    • parkpos - Эта опция определяет количество парковочных слотов. Например, задав номера 701–720, вы создадите 20 парковочных слотов с нумерацией от 701 до 720.
    • context - Это имя контекста парковки. Чтобы иметь возможность парковать вызовы, необходимо включить этот контекст.
    • parkingtime - Если эта опция задана, она определяет, как долго (в секундах) вызов может оставаться на парковке. Если вызов не принят в течение заданного времени, выполняется звонок на добавочный номер, с которого вызов поступил на парковку.

    После редактирования файла features.conf необходимо перезагрузить Asterisk, потому что чтение этого файла выполняется только при запуске системы. Выполнение команды reload не обеспечит чтения файла features.conf.

    Также обратите внимание, что, поскольку пользователю необходимо иметь возможность переводить вызовы на добавочный номер парковки, в приложении Dial() должны использоваться опции t и/или T .

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


    include => parkedcalls
    exten => 103,1,Dial(SIP/Bob,tT)
    exten => 104,1,Dial(SIP/Charlie,tT)

    Проиллюстрируем принцип работы парковки вызовов. Скажем, Элис звонит в систему и набирает добавочный номер 103, чтобы поговорить с Бобом. Через некоторое время Боб переводит вызов на добавочный номер 700, который сообщает ему, что звонок от Элис был припаркован в слот 701. После этого Боб звонит Чарли на добавочный номер 104 и говорит ему, что Элис ожидает по номеру 701. Чарли набирает добавочный номер 701 и разговаривает с Элис. Это простой и эффективный способ обеспечить возможность переключения вызывающих абонентов между пользователями системы.
    Аргументы t и T приложения Dial()нужны не для всех типов каналов. Например, многие SIP-телефоны реализуют это с помощью функциональной или обычной кнопки и обмена сигналами по протоколу SIP.

    2.2.18 Организация конференц-связи с помощью MeetMe()

    Не менее полезной функцией является установление аудио конференц-связи с помощью приложения MeetMe(). Это приложение обеспечивает возможность одновременного общения множества абонентов так, как если бы они все физически находились в одном месте. К основным функциям относятся:

    • Возможность создания защищенных паролем конференций.
    • Администрирование конференции (отключение звука конференции, блокировка конференции, исключение участников).
    • Опция отключения звука всех участников, кроме одного (полезна для объявлений по компании, широковещательных рассылок и т. д.).
    • Создание статических или динамических конференций.

    Давайте поэтапно рассмотрим процесс настройки базового конференц-зала. Конфигурационные опции для системы конференц-связи MeetMe располагаются в файле meetme.conf. В этом конфигурационном файле задаются конференц-залы и необязательные числовые пароли. (Если
    пароль задан, он будет необходим для входа на все конференции, проводимые с использованием этого конференц-зала.) Для нашего примера настроим конференц-зал по добавочному номеру 600. Сначала зададим все настройки в файле meetme.conf. Назовем этот конференц-зал
    600 и на этот раз не будем задавать пароль:


    conf => 600

    Закончив работу с конфигурационным файлом, необходимо перезагрузить Asterisk, чтобы она могла повторно прочитать файл meetme.conf. Далее добавим поддержку конференц-зала в диалплан, используя приложение MeetMe(). MeetMe() принимает три аргумента: имя конференц-зала (заданное в meetme.conf), набор опций и пароль, который пользователь должен ввести, чтобы присоединиться к конференции. Настроим простую конференцию, используя конференц-зал 600, опцию i (которая обеспечивает оповещение о том, что кто-то присоединился или покинул конференцию) и пароль 54321:

    Exten => 600,1,MeetMe(600,i,54321)

    Когда абоненты попадут на добавочный номер 600, им будет предложено ввести пароль. Если они правильно введут 54321, то попадут на конференцию.
    Другое полезное приложение – MeetMeCount() . Как следует из его имени, это приложение подсчитывает, сколько пользователей находится
    в том или ином конференц-зале. Оно принимает два аргумента: конференц-зал, где необходимо подсчитать количество участников, и необязательное имя переменной, в которой нужно сохранить это число. Если второй аргумент, то есть имя переменной, не задан, полученное число воспроизводится вызывающему абоненту:

    Exten => 601,1,Playback(conf-thereare)
    exten => 601,n,MeetMeCount(600)
    exten => 601,n,Playback(conf-peopleinconf)
    Если вторым аргументом в MeetMeCount() передается переменная, итоговое количество участников присваивается этой переменной, а само число
    не воспроизводится. Так можно ограничивать количество участников:
    ; ограничить конференц-зал 10 участниками
    exten => 600,1,MeetMeCount(600,CONFCOUNT)
    exten => 600,n,GotoIf($[${CONFCOUNT} 600,n(meetme),MeetMe(600,i,54321)
    exten => conf_full,1,Playback(conf-full)

    2.2.19 3-х (и более) сторонний вызов в Asterisk с использованием ConfBridge()

    Данная настройка позволяет выполнить n-сторонний вызов в Asterisk с использованием приложения ConfBridge.
    Решение является адаптацией решения для работы с ConfBridge (более новое, чем MeetMe, приложение Asterisk для конференц-связи, к тому же, не зависящее от модуля DAHDI).
    Для работы данной схемы у Asterisk должны быть собраны модули app_confbridge.so иchan_bridge.so .

    Описание функционала

    К сервисным кодам Asterisk добавляется код *0 (его нужно описать в файле features.conf и добавить в переменную DYNAMIC_FEATURES). Сервисный код *0 вызывает выполнение макроса nway-start, который создает конференцию с именем как UNIQUEID у текущего вызова и перенаправляет в созданную конференцию абонента, не являющегося инициатором конференции. После этого абонент, начавший конференцию, получает возможность добавить в конференцию 3-го участника.

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

    • ** - прекратить вызов, не добавлять вызываемого абонента к конференции
    • # - добавить вызываемого абонента к конференции

    После выполнения любого из этих сервисных кодов, абонент, добавлявший нового участника, сам возвращается в конференцию. Всем абонентам, находящимся в конференции, доступен сервисный код *0. После выполнения этого кода, абонент покидает конференцию, получает возможность добавить участника к конференции, после чего, возвращается обратно в конференцию. Количество одновременных n-сторонних вызовов и их участников ограничено только производительностью сервера.

    1) Настройки ConfBridge: confbridge.conf


    type = user
    type = user
    type = menu
    *0 = leave_conference

    2) Диалплан: extensions.conf


    exten => s,1,ChannelRedirect(${BRIDGEPEER},nway-conf,${CONFNO},1)
    ;

    exten => s,1,Read(NEW_CALLEE,dial,20,i)

    same =>

    same =>
    same =>
    same =>
    same =>
    same => n,Dial(Local/${NEW_CALLEE}@from-internal ,gH)
    same =>
    same => n,ChannelRedirect(${CHANNEL(name)},nway-conf,${CONFNO},1)
    ;

    exten => _X.,1,Answer
    same => n,Set(__CONFNO=${EXTEN})
    same => n,Set(DYNAMIC_FEATURES=${DYNAMIC_FEATURES_NWAY})
    same => n,Set(CONFBRIDGE(bridge,language)=${CHANNEL(language)})
    same => n,Set(CONFBRIDGE(user,announce_user_count)=no)
    same => n,Set(CONFBRIDGE(user,announce_join_leave)=no)
    same => n,Set(CONFBRIDGE(user,announce_only_user)=no)
    same => n,Set(CONFBRIDGE(user,music_on_hold_when_empty)=yes)
    same => n,ConfBridge(${CONFNO},nway_menu)
    same => n,Goto(nway-invite,s,1)
    ;

    exten => s,1,Set(__CONFNO=${UNIQUEID})
    same => n,ChannelRedirect(${BRIDGEPEER},nway-conf,${CONFNO},1)
    same => n,Read(NEW_CALLEE,dial,20,i)
    ; Add dynamic features for n-way invite
    same => n,Set(DYNAMIC_FEATURES=${DYNAMIC_FEATURES_NWAYINV})
    ; Determine dialing context and dial
    same => n,ExecIf($[${REGEX("H" ${DIAL_OPTIONS})} = 0]?Set(_DIAL_OPTIONS=${DIAL_OPTIONS}H))
    same => n,Set(CALLER=${CALLERID(num)})
    same => n,Set(DIALOUT_CONTEXT=${SIPPEER(${CALLER},context)})
    same => n,ExecIf($["${DIALOUT_CONTEXT}" = ""]?Set(DIALOUT_CONTEXT=from-internal))
    same => n,Dial(Local/${NEW_CALLEE}@${DIALOUT_CONTEXT} ,gH)
    same => n,Set(DYNAMIC_FEATURES=${DYNAMIC_FEATURES_NWAY}) n,ChannelRedirect(${CHANNEL(name)},nway-conf,${CONFNO},1)

    3) Сервисные коды Asterisk: features.conf


    disconnect=**
    nway-start => *0,self,Macro,nway-start
    nway-ok => #,self/caller,Macro,nway-ok>

    Добавить в файл extensions.conf


    DYNAMIC_FEATURES = feature1#feature2#nway-start
    DYNAMIC_FEATURES_NWAY = feature1
    DYNAMIC_FEATURES_NWAYINV = feature2#nway-ok

    Описание переменных:

    • disconnect - стандартный сервисный код Asterisk, вызывающий завершение текущего вызова.feature1, feature2 - другие пользовательские сервисные коды, настроенные на Asterisk (на разных этапах конференции можно делать доступными разные сервисные коды)
    • DYNAMIC_FEATURES_NWAY - сервисные коды, доступные во время n-стороннего вызова
    • DYNAMIC_FEATURES_NWAYINV - сервисные коды, доступные во время приглашения в n-сторонний вызов

    2.2.20 Запись телефонных разговоров в формате (.wav)

    Запись разговоров – довольно удобный функционал. Можно всегда прослушать кто, что кому говорил. Настройка не сложная, достаточно включить в dialplan на обработку этого направления функции Monitor или MixMonitor . Отличие этих функций заключается в том, что Monitor пишет раздельно голос звонившего и голос звонящего, в разные файлы. А MixMonitor создает один файл, который содержит оба направления разговора.
    В диалплане это выглядит для Monitor так:


    exten => _8X.,1,Set(fname=${STRFTIME(${EPOCH},%Y%m%d%H%M)}-${CALLERID(number)}-${EXTEN})
    exten => _8X.,2,Monitor(wav,/home/share/monitor/${fname},mb)
    exten => _8X.,3,Dial(SIP/trunk1/${EXTEN},tTr)

    В этом примере первой строчкой, Set(fname и т.д. мы описываем имя файла в котором будет хранится запись разговора. В нашем случае имя файла будет состоять из даты и времени когда происходил звонок, номера звонящего абонента и куда он звонил. Запись как уже говорилось будет состоять из двух файлов в конце будут соответственно добавлены цифры 1 и 2 соответственно номерам каналов.
    И второй пример – использование MixMonitor , тут все так же просто:


    exten => _8.,1,Set(fname=${STRFTIME(${EPOCH},%Y%m%d%H%M)}-${CALLERID(number)}-${EXTEN})
    exten => _8.,2,MixMonitor(/home/share/monitor/${fname}.wav)
    exten => _8.,3,Dial(SIP/trunk2/${EXTEN},tTr)

    Тут все то же самое, только меньше флагов в команде MixMonitor, тут вписывается только имя файла с расширением.

    2.2.21 Запись разговоров на Asterisk с простым управлением через CLI

    Данный документ описывает, как создать систему выборочной записи разговоров на базе Asterisk.

    Краткое описание решения

    Каждый выходящий и исходящий звонок направляется в контекст активации записи разговора, в котором на уровне специального диалплана реализован поиск правил. Для хранения правил записи используется внутренняя база данных Asterisk. Данный подход позовляет:

    • использовать встроенные функции DB для поиска значений;
    • использовать консоль Asterisk и команду database для модификации правил.

    В базе существуют две ветки:

    • rec_a - в данной ветке содержатся номера абонента А (callerid), для которых нужно включать запись;
    • rec_b - соответственно, в этой ветке правила номера абоента Б (dnid).

    *CLI> database put rec_a 701 1
    Updated database successfully
    *CLI> database put rec_b 2323956 1
    Updated database successfully
    *CLI> database show
    /rec_a/701: 1
    /rec_b/2323956: 1
    *CLI>

    В вышеприведенном примере было создано два правила. Одно будет писать все звонки от внутреннего пользователя с номером 701, другое будет писать все звонки на номер 2323956. Как можно заметить, используется флаг активности правила - 0 или 1. Это позволяет временно отключать запись без удаления номера из базы.

    Формат записи
    Записанные файлы пишутся в папку /var/spool/asterisk/monitor в следующем формате:

    data/yyyy/mm/dd/hh_MM_ss_a_b_callid, где:

    • yyyy - год записи;
    • mm - месяц записи;
    • dd - день записи;
    • hh - час начала разговора;
    • MM - минута начала разговора;
    • ss - секунда начала разговора;
    • a - номер обонента a или unknown, если callerid скрыт;
    • b - номер абонента b;
    • callid - ID звонка.

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

    Диалплан Asterisk

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

    extensions.conf:



    exten =>
    exten => _X.,n,Dial(DAHDI/g1/${EXTEN},t)

    exten => _X.,1,Gosub(sub-recording,${EXTEN},1}
    exten => _X.,n,Dial(SIP/${EXTEN},T)

    exten => s,1,StopMixMonitor()
    exten => _X.,1,Set(CALLER=unknown)
    exten => _X.,n,GoToIf($?db)
    exten => _X.,n,Set(CALLER=${CALLERID(num)})
    exten => _X.,n(db),GotoIf($?record:stop)
    exten => _X.,n,GotoIf($?record:stop)
    exten => _X.,n(record),MixMonitor(data/${STRFTIME(,%G/%m/%H_%M_%S)}_${CALLER}_${CALLED}.gsm)
    exten => _X.,n(stop),Return

    Недостатки: Время в названии файла - время поступления вызова, но не начала разговора. Для указания времени начала разговора требуется создание симлинка или переименование после окончания разговора

    2.2.22 Будильник на Asterisk

    Реализовать будильник в linux можно далеко не одним способом (наверное, самый простой: «sleep 20m && mpg123 ~/bell.mp3»), но хочется чего-то красивого и нестандартного. Поиск в интернете по запросу «asterisk wakeup» выдаст несколько решений, написанных с использованием разных языков программирования и немного отличающихся как процессом установки, так и возможностями. Самое популярное из них - PHP скрипт wakeup.php, автором которого является Анди Высоцки. Скачиваем по ссылке tar архив, распаковываем php-файл в каталог с AGI скриптами (Asterisk Gateway Interface - шлюзовой интерфейс, посредством которого внешние программы могут управлять диалпланом Asterisk) и делаем его исполняемым: «chmod a+x /var/lib/asterisk/agi-bin/wakeup.php» (нужный каталог можно узнать, просмотрев значение переменной astagidir в конфиге asterisk.conf).
    Скрипт wakeup.php содержит ряд переменных, которые необходимо подправить с учетом настроек системы:

    ; Расположение интерпретатора PHP в разных linux-системах может отличаться
    #!/usr/bin/php -q
    ; Журнал из /tmp лучше убрать
    $parm_error_log = "/var/log/asterisk/wakeup.log";
    ; По умолчанию скрипт создает временные файлы в /tmp,
    ; но если этот каталог находится на отдельном разделе, wakeup.php откажется работать, поэтому:
    $parm_temp_dir = "/var/spool/asterisk/tmp";

    Принцип запуска скрипта аналогичен примеру с Motion - просто заносим в extensions.conf информацию о новом номере:

    Exten => *97,1,Answer()
    exten => *97,n,AGI(wakeup.php)
    exten => *97,n,Hangup()

    Теперь достаточно позвонить на номер *97 и по запросу ввести время, когда система должна произвести обратный звонок. Например, чтобы завести будильник на 17:55 (сегодня финал кубка английской лиги), набираем "0555", а затем "2" (1 - до полудня, 2 - после полудня).
    Если при запуске скрипта возникли проблемы, доустановить пакеты php5-cli и asterisk-sound-extra и используйте утилиту fromdos, чтобы привести wakeup.php к Unix стандартам.

    2.2.23 Текстовые сообщения SIP/SIMPLE в Asterisk

    Для пересылки сообщений используется метод SIP MESSAGE (RFC 3428), известный также как протокол SIMPLE. В Asterisk его поддержка появилась в 10 версии. Конфигурация функционала (используется Asterisk версии 11).
    Вариант №1
    В секцию файла sip.conf добавляются строки:


    outofcall_message_context = messages
    auth_message_requests = no

    А в dialplan extensions.conf добавляется контекст:


    exten => _XXX,1,MessageSend(sip:${EXTEN},"${CALLERID(name)}"${MESSAGE(from)})

    Количество X проставьте в соответствии с количеством цифр в ваших внутренних номерах.

    Вариант №2
    В секцию файла sip.conf добавляются строки:

    Accept_outofcall_message = yes
    outofcall_message_context = astsms
    textsupport = yes
    auth_message_requests = yes

    А в dialplan extensions.conf добавляется контекст:


    exten => _.,1,NoOp(SMS receiving dialplan invoked)
    exten => _.,n,NoOp(To ${MESSAGE(to)})
    exten => _.,n,NoOp(From ${MESSAGE(from)})
    exten => _.,n,NoOp(Body ${MESSAGE(body)})
    exten => _.,n,Set(ACTUALTO=${CUT(MESSAGE(to),@,1)})
    exten => _.,n,MessageSend(${ACTUALTO},${MESSAGE(from)})
    exten => _.,n,NoOp(Send status is ${MESSAGE_SEND_STATUS})
    exten => _.,n,GotoIf($["${MESSAGE_SEND_STATUS}" != "SUCCESS"]?sendfailedmsg)
    exten => _.,n,Hangup()
    exten => h,1,Hangup()
    ;
    exten => _.,n(sendfailedmsg),Set(MESSAGE(body)="[${STRFTIME(${EPOCH},%d%m%Y-%H:%M:%S)}] Your message to ${EXTEN} has failed. Retry later.")
    exten => _.,n,Set(ME_1=${CUT(MESSAGE(from), exten => _.,n,Set(ACTUALFROM=${CUT(ME_1,@,1)})
    exten => _.,n,MessageSend(${ACTUALFROM},ServiceCenter)
    exten => _.,n,Hangup()
    exten => h,1,Hangup()

    2.2.24 Функционал для службы безопасности и руководства компании - Прослушивание разговоров в режиме онлайн

    Вариант №1


    exten => 5555,1,Macro(user-callerid)
    exten => 5555,2,Authenticate(1234)
    exten => 5555,3,Read(SPYNUM,agent-newlocation)
    exten => 5555,4,ChanSpy(SIP/${SPYNUM))

    Вариант №2


    exten => _555XX.,1,chanspy(sip/${EXTEN:3})
    exten => 556,1,ChanSpy(канал,B) - слышат оба канала, типа трехсторонней конференции
    exten => 557,1,ChanSpy(канал,q) - каналы не слышат, ты все слышишь
    exten => 558,1,ChanSpy(канал,w) - тебя слышит только канал, к которому подключаешься, ты слышишь оба канала

    Команда core show application ChanSpy показывает все опции функционала

    Вот как это работает на несколько телефонов

    Exten => *9720,1,ChanSpy(SIP/720,wqv(-1))
    exten => *9701,1,ChanSpy(SIP/701,wqv(-1))
    exten => *9700,1,ChanSpy(SIP/700,wqv(-1))

    Вариант №3

    ;listen - Прослушка разговоров (Вас не слышат)
    exten => _*222x.#,1,Macro(user-callerid,)
    exten => _*222x.#,n,Answer
    exten => _*222x.#,n,NoCDR
    exten => _*222x.#,n,Wait(1)
    exten => _*222x.#,n,ChanSpy(sip/${EXTEN:4},q)
    exten => _*222x.#,n,Hangup ;whisper - Вторжение в разговор
    exten => _*223x.#,1,Macro(user-callerid,)
    exten => _*223x.#,n,Answer
    exten => _*223x.#,n,NoCDR
    exten => _*223x.#,n,Wait(1)
    exten => _*223x.#,n,ChanSpy(sip/${EXTEN:4},qw)
    exten => _*223x.#,n,Hangup ;barge - Слышат все 3 участника
    exten => _*224x.#,1,Macro(user-callerid,)
    exten => _*224x.#,n,Answer
    exten => _*224x.#,n,NoCDR
    exten => _*224x.#,n,Wait(1)
    exten => _*224x.#,n,ChanSpy(SIP/${EXTEN:4},qB)
    exten => _*224x.#,n,Hangup