# Глава 10. Погружение в диалплан > *Для получения списка всех способов, которыми технология не смогла улучшить качество жизни, нажмите три.* > > – Элис Кан Хорошо. Основы диалплана позади, но вы знаете что это еще не все. Если вы еще не разобрались с [Главой 6](chapter-06.md), пожалуйста, вернитесь и прочтите ее еще раз. Мы собираемся перейти к более сложным темам. ## Выражения и манипуляции с переменными Мы начинаем наше погружение в более глубокие аспекты диалпланов: пришло время познакомить вас с несколькими инструментами, которые значительно увеличат мощь, которую вы можете использовать в своем диалплане. Эти конструкции добавляют невероятный интеллект к вашему диалплану, позволяя ему принимать решения на основе различных критериев, которые вы определяете. Наденьте свой мыслительный колпачок и давайте начнем.
В этой главе мы используем лучшие практики, которые были разработаны на протяжении многих лет при создании диалплана. Основное, что вы заметите, это то, что все первые приоритеты начинаются с приложения |
Дополнительную информацию об особенностях работы оператора регулярного выражения в Asterisk можно найти на сайте Уолтера Докса. |
Вы увидите использование функции |
Вы заметите, что мы использовали приложение |
Предоставление только ложного условного пути Любой из пунктов назначения может быть опущен (но не оба). Если выражение оценивается как пустое назначение - Asterisk просто переходит к следующему приоритету в текущем расширении. Мы могли бы выполнить предыдущий пример следующим образом:
Между Мы действительно не рекомендуем делать так, потому что это трудночиемо. Тем не менее - вы увидите такие диалпланы, поэтому хорошо знать, что этот синтаксис технически корректен. |
Кавычки и префиксы переменных в условных ветвлениях Сейчас самое время воспользоваться моментом и посмотреть на некоторые небрежные вещи с условными ветвлениями. В Asterisk недопустимо иметь нулевое значение по обе стороны от оператора сравнения. Давайте рассмотрим примеры, которые могли бы привести к ошибке:
Любой из наших примеров вызовет такое предупреждение:
Это маловероятно (если у вас нет опечатки), что вы целенаправленно реализуете что-то из наших примеров. Однако, когда вы выполняете математическое действие или сравнение с неназначенной переменной канала, это фактически то, что делаете Вы. Примеры, используемые нами чтобы показать вам как работает условное ветвление, являются недопустимыми. Мы сначала инициализировали переменную и можем ясно видеть, что переменная канала, которую мы используем в нашем сравнении, была установлена, поэтому мы в безопасности. Но что, если вы не всегда так уверены? В Asterisk строки необязательно должны быть заключены в двойные или одинарные кавычки, как во многих языках программирования. Фактически, если вы используете двойные или одинарные кавычки, это будет буквенной конструкцией в строке. Если мы посмотрим на следующие фрагменты расширения...
...мы должны отметить, что значение, возвращаемое нашим сравнением в
Однако, мы можем обойти это - обернув то, что мы сравниваем, в дополнительные символы (в данном случае кавычки). Тот же пример, но сделан допустимым:
Даже если
Если вы привыкнете распознавать эти ситуации и использовать методы обертки и префикса, описанные нами, вы напишете гораздо более безопасные диалпланы. Обратите внимание еще раз, что символ кавычки не имеет никакого особого значения здесь. Мы использовали его только потому, что это логический символ для этой цели. Следующее тоже работает:
Не все символы будут работать, так как некоторые могут иметь другие значения для Asterisk и вызвать проблемы. Придерживайтесь кавычек и всё должно быть в порядке. |
Стоит отметить, что время будет правильно оборачиваться. Таким образом, если вы хотите указать время закрытия вашего офиса, то можете указать 18:00-9:00 в параметре |
Обратите внимание, что можно указать совокупность диапазонов и одного дня, как: |
Подпрограммы являются важнейшей способностью в любом языке программирования, и в не меньшей степени в диалплане Asterisk. Для тех, кто новичок в программировании: подпрограмма позволяет создать блок универсального кода, который может быть повторно использован различными частями диалплана для избежания повторения. Подумайте о них как о шаблоне в текстовом документе или пустой форме, и у вас появится представление. Как только вы увидите их в действии - должно стать ясно, насколько полезными они могут быть. |
У вас уже есть подпрограмма в нижней части файла. Добавьте новую туда же, чтобы все ваши подпрограммы были сгруппированы вместе. |
Несмотря на то, что назначение для локального канала на самом деле является просто диалпланом — так же, как вы могли бы перейти с помощью |
Решение, которое мы создали, идеально подходит для изучения локальных каналов, но у него есть несколько проблем, которые нужно понять, если вы когда-нибудь захотите запустить его в продакшен:
|
Если вы проверите образец диалплана, мы добавили решение проблемы тишины на задержанных локальных каналах. |
exten => 216,1,NoOp()
same => n,Set(DB(testkey/count)=1)
Сделайте тестовый вызов на 216 чтобы установить значение. Обратите внимание, что если ключ с именем `count` уже существует в семействе `test`, его значение будет перезаписано новым (в этом случае значение жестко закодировано, поэтому оно будет перезаписано с тем же значением, но позже мы увидим, как можем изменить значение и сохранить его).
Вы также можете сохранять значения из командной строки Asterisk, запустив команду database put family key value. Для нашего примера, вы должны ввести `database put test count 1`. Итак, пока мы это делаем, давайте также добавим значение в базу данных из консоли: ``` *CLI> database put somekey somevalue 42 ``` А теперь запросим базу данных из консоли, чтобы увидеть, какие значения там находятся: ``` *CLI> database show ``` Если все хорошо, вы должны увидеть результат, подобный следующему: ``` /pbx/UUID : d562019a-d2c4-4b88-bcd9-602b3b46fe07 /somekey/count : 1 /somekey/somevalue : 42 /testkey/count : 1 4 results found. localhost*CLI> ``` ### Получение данных из AstDB Чтобы извлечь значение из базы данных Asterisk и присвоить его переменной, мы снова будем использовать приложение `Set()` и функцию `DB()`. Давайте получим значение `somevalue` (из семейства `somekey`), назначим его переменной `THE_ANSWER`, а затем передадим значение вызывающему объекту: ``` exten => 217,1,NoOp() same => n,Set(THE_ANSWER=${DB(somekey/somevalue)}) same => n,Answer() same => n,SayNumber(${THE_ANSWER}) ``` Вы также можете проверить значение данного ключа из командной строки Asterisk, запустив команду
database get family key. Чтобы просмотреть все содержимое базы данных AstDB, используйте команду `database show`. ### Удаление данных из AstDB Существует два способа удаления данных из базы данных Asterisk. Для удаления ключа можно использовать приложение `DB_DELETE()`. Оно принимает путь к ключу в качестве аргументов, например: ``` ; удаляет ключ и возвращает его значение за один шаг exten => 218,1,Verbose(0, We just blew away ${DB_DELETE(somekey/somevalue)}) ``` Вы также можете удалить все семейство ключей с помощью приложения `DBdeltree()`. Приложение `DBdeltree()` принимает один аргумент: имя семейства ключей для удаления. Чтобы удалить все семейство `test`, выполните следующие действия: ``` exten => 219,1,DBdeltree(somekey) ``` Чтобы удалить ключи и семейства ключей из базы данных AstDB через интерфейс командной строки, используйте команды
database del keyи
database deltree familyсоответственно. Если вы сейчас позвоните по номеру 217, то увидите, что ничего не сказано, потому что база данных ничего не возвращает. Вы также можете запустить `database show` из CLI и отметить, что это семейство и ключ были удалены. ### Использование AstDB в диалплане Существует бесконечное количество способов использования базы данных Asterisk в диалплане. Чтобы представить AstDB - мы рассмотрим два простых примера. Первый - простой пример подсчета показывает, что база данных Asterisk является постоянной (она даже переживает перезагрузку системы). Во втором примере мы будем использовать функцию `BLACKLIST()`, чтобы оценить находится ли номер в черном списке и должен ли он быть заблокирован. Чтобы начать пример с подсчетом, давайте сначала извлечем число (значение ключа count) из базы данных и назначим его переменной с именем `COUNT`. Если ключ не существует - `DB()` вернет значение `NULL` (нет значения). Поэтому мы можем использовать функцию `ISNULL()`, чтобы проверить, было ли возвращено значение. Если нет - мы инициализируем AstDB с помощью приложения `Set()`, где установим значение в базе данных равным `1`. Это произойдет только в том случае, если этой записи базы данных не существует: ``` exten => 220,1,NoOp() same => n,Set(COUNT=${DB(test/count)}) ; получаем текущее значение базы данных same => n,GotoIf($[${ISNULL(${COUNT})}]?firstcount:saycount) ; есть ли значение? same => n(firstcount),Set(DB(test/count)=1) ; устанавливаем значение 1 same => n,Goto(saycount) same => n(saycount),NoOp() same => n,Answer same => n,SayNumber(${COUNT}) same => n,Goto(increment) ; не требуется, но хорошая привычка same => n(increment),Set(COUNT=$[${COUNT} + 1]) ; увеличение на единицу same => n,Set(DB(test/count)=${COUNT}) ; и присвоение нового значения в базе ; данных same => n,Goto(saycount) ; вернемся и повторим снова ``` Проверьте это. Послушайте, как он считает какое-то время, а затем повесьте трубку. Когда вы снова наберете этот номер - отсчет продолжится с того места, где остановился. Значение, сохраненное в базе данных, будет сохраняться даже при перезапуске Asterisk. В первое время встроенная база данных Asterisk была необходима. Сегодня, однако, она не так часто используется. Она, вероятно, хороша для установки нескольких семафоров здесь и там, но по большей части, если вы хотите хранить данные - используйте один из бэкэндов реляционной базы данных (мы обсудим интеграцию реляционных баз данных в последующих главах). ## Полезные функции Asterisk Теперь, когда мы рассмотрели некоторые из основ, давайте рассмотрим несколько популярных функций, которые были включены в Asterisk. ### Концеренц-связь с ConfBridge() Приложение `ConfBridge()` позволяет нескольким абонентам общаться друг с другом как если бы они все физически находились в одном месте. Некоторые из основных функций включают в себя: - Возможность создания защищенных паролем конференций - Администрирование конференции (отключение звука, блокировка или выброс участников) - Возможность отключение всех, кроме одного участника (полезно для объявлений компании, радиопередач и др.) - Статическое или динамическое создание конференции - Звук высокой четкости, который может быть микширован при частоте дискретизации от 8 кГц до 96 кГц - Видео-возможности, включая добавление динамического переключения видео-каналов на самого громкого докладчика - Динамически управляемая система меню для администраторов конференций и пользователей - Дополнительные опции доступны в _confbridge.conf_ В этой главе мы сосредоточены на диалплане - поэтому собираемся продемонстрировать только базовый мост аудиоконференции: ``` $ sudo -u asterisk vim /etc/asterisk/confbridge.conf [general] [default_user] type=user [default_bridge] type=bridge ``` После создания файла _confbridge.conf_, нам нужно загрузить модуль `app_confbridge.so`. Это можно сделать в консоли Asterisk: ``` *CLI> module load app_confbridge.so ``` С загруженным модулем мы можем создать простой диалплан для доступа к нашему конференц-мосту: ``` exten => 221,1,NoOp() same => n,ConfBridge(${EXTEN}) ``` Это только верхушка айсберга для проведения конференций. Мы сделали базовую конфигурацию, но есть гораздо больше возможностей для настройки. Мы рассмотрим их более подробно в [Главе 11](glava-11.md). ## Полезные функции диалплана Мы обсуждали функции ранее в этой главе, но у нас есть что сказать ещё. В настоящее время существует около 150 функций, предоставляемых диалпланом Asterisk. Вот небольшой, кураторский список из тех, с которыми стоит поэкспериментировать. ### CALLERID() `CALLERID()` поддерживает множество различных типов данных, но вы обнаружите, что обычно используете одно из name или num. ``` exten => 222,1,Noop(CALLERID function) same => n,Noop(CALLERID currently ${CALLERID(all)}) same => n,Set(CALLERID(num)=4169671111) same => n,Noop(CALLERID now ${CALLERID(all)}) same => n,Set(CALLERID(name)="Somename") same => n,Noop(CALLERID now ${CALLERID(all)}) same => n,Hangup() ``` Об остальных не беспокойтесь. Если они вам понадобятся - вы будете знать, что они обозначают и почему вы хотите их использовать. ### CHANNEL() `CHANNEL()` позволяет взаимодействовать с загрузкой абсолютных данных, относящихся к каналу. Некоторые элементы позволяют изменять их, в то время как другие будут полезны только для справки (например, peerip позволит вам прочитать, но не изменить, IP-адрес узла). Существуют также переменные канала, работающие только с определенными типами каналов (например, элементы pjsip, конечно же могут использоваться только на каналах PJSIP). ``` exten => 223,1,Noop(CHANNEL function) same => n,Answer() same => n,Noop(CHANNEL(name) is ${CHANNEL(name)}) same => n,Noop(CHANNEL(musicclass) is ${CHANNEL(musicclass)}) same => n,Noop(CHANNEL(rtcp,all_jitter) is ${CHANNEL(rtcp,all_jitter)}) same => n,Noop(CHANNEL(rtcp,all_loss) is ${CHANNEL(rtcp,all_loss)}) same => n,Noop(CHANNEL(rtcp,all_rtt) is ${CHANNEL(rtcp,all_rtt)}) same => n,Noop(CHANNEL(rtcp,txcount) is ${CHANNEL(rtcp,txcount)}) same => n,Noop(CHANNEL(rtcp,rxcount) is ${CHANNEL(rtcp,rxcount)}) same => n,Noop(CHANNEL(pjsip,local_uri) is ${CHANNEL(pjsip,local_uri)}) same => n,Noop(CHANNEL(pjsip,remote_uri) is ${CHANNEL(pjsip,remote_uri)}) same => n,Noop(CHANNEL(pjsip,request_uri) is ${CHANNEL(pjsip,request_uri)}) same => n,Noop(CHANNEL(pjsip,local_tag) is ${CHANNEL(pjsip,local_tag)}) ``` ### CURL() `CURL()` - это простая, но мощная функция, предоставляющая однострочный метод разрешения URL-адресов, который во многих случаях является всем необходимым для базового взаимодействия с внешним веб-сервисом. ``` exten => 224,1,Noop(CURL function) same => n,Set(ExternalIP=${CURL(http://whatismyip.akamai.com)}) same => n,Noop(The external IP address is ${ExternalIP}) ``` Если вам нужно более сложное взаимодействие с внешним сервисом - возможно вам понадобится какая-то программа AGI. Тем не менее, вы можете встроить тонну данных в URL и по простоте `CURL()` трудно превзойти. ### CUT() Если вам нужно нарезать ваши переменные - вы найдете функцию `CUT()` весьма полезной. Форма проста: ``` CUT(varname,char-delim,range-spec) ``` Это может быть визуально сложно, так как символ разделителя может быть трудно увидеть вложенным между двумя запятыми (например, если разделитель был точкой/десятичной дробью). Давайте развернем предыдущий пример, чтобы увидеть, для чего он хорош (и дать вам визуальный пример того, как разделитель может потеряться в синтаксисе). ``` exten => 225,1,Noop(CUT function) same => n,Set(ExternalIP=${CURL(http://whatismyip.akamai.com)}) same => n,Noop(The external IP address is ${ExternalIP}) same => n,Answer() same => n,SayDigits(=${CUT(ExternalIP,.,1)}) same => n,Playback(letters/dot) same => n,SayDigits(=${CUT(ExternalIP,.,2)}) same => n,Playback(letters/dot) same => n,SayDigits(=${CUT(ExternalIP,.,3)}) same => n,Playback(letters/dot) same => n,SayDigits(=${CUT(ExternalIP,.,4)}) ```
Обратите внимание, что вы вызываете функцию |
UPDATE asterisk.ps_endpoints SET callerid='8885551212' WHERE id=''
STRFTIME()
, возвращающая текущее время в виде отформатированной строки. Здесь она работает аналогично. Фактически, часть format функции принимает тот же синтаксис, что и функция в C.