Глава 7

Погружение в диалплан

Для списка всех способов, по которым технология не улучшила

качество жизни, пожалуйста, нажмите три.

—Элис Кан

Выражения и манипуляции с переменными

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

В этой главе мы используем лучшие практики, которые были разработаны на протяжении многих лет в создании диалплана. Основная из них — вы заметите, что все первые приоритеты начинаются с приложения NoOp(), что означает просто «Нет операции»; ничего функционального не произойдет. Другая заключается в том, что все следующие строки начинаются с same => n, что является ярлыком, который гласит: «Использовать то же расширение, которое было только что определено ранее». Кроме того, отступ составляет четыре пробела.

Базовые выражения

Выражения представляют собой комбинации переменных, операторов и значений, которые вы объединяете вместе для получения результата. Выражение может проверять значения, изменять строки или выполнять математические вычисления. Допустим, у нас есть переменная с именем COUNT. На простом русском языке два выражения, использующие эту переменную, могут быть «COUNT плюс 1» и «COUNT разделить на 2.». Каждое из этих выражений имеет конкретный результат или значение в зависимости от значения данной переменной.

В Asterisk выражения всегда начинаются со знака доллара и открытой квадратной скобки и заканчиваются закрывающей квадратной скобкой, как показано здесь:

$[expression]

Таким образом, мы могли бы написать два наших примера:

$[${COUNT} + 1]

$[${COUNT} / 2]

Когда Asterisk встречает выражение в диалплане, он заменяет всё выражение полученным значением. Важно отметить, что это происходит после подстановки переменной. Чтобы продемонстрировать, давайте рассмотрим следующий код:1

exten => 321,1,NoOp()

same => n,Answer()

same => n,Set(COUNT=3)

same => n,Set(NEWCOUNT=$[${COUNT} + 1])

same => n,SayNumber(${NEWCOUNT})

Во втором приоритете мы присваиваем значение 3 переменной с именем COUNT.

В третьем приоритете задействовано только одно приложение Set() — но на самом деле происходят три вещи:

  1. Asterisk заменяет ${COUNT} числом 3 в выражении. Выражение эффективно становится следующим:
    1. Asterisk оценивает выражение, добавляя от 1 до 3 и заменяя его вычисленным значением 4:
      1. Приложение Set() присваивает значение 4 переменной NEWCOUNT

      Третий приоритет просто вызывает приложение SayNumber(), которое говорит текущее значение переменной ${NEWCOUNT} (устанавливается в значение 4 в приоритете два).

      Попробуйте в своем диалплане.

      Операторы

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

      Булевы (логические) операторы

      Эти операторы оценивают «истину» утверждения. В вычислительных терминах это в основном относится к тому, является ли оператор чем-то или нет (отличным от нуля или нулевым, истинным или ложным, включенным или выключенным и т.д.). Булевы операторы:

      expr1 | expr2

      Этот оператор (называемый оператором «или» или «пайп»(труба)) возвращает оценку expr1, если она истинна (не пустая строка, не нуль). В противном случае он возвращает оценку expr2.

      expr1 & expr2

      Этот оператор (называемый «и») возвращает оценку expr1, если оба выражения истинны (т. е. ни одно выражение не оценивает пустую строку или ноль). В противном случае он возвращает ноль.

      expr1 {=, >, >=, <, <=, !=} expr2

      Эти операторы возвращают результаты целочисленного сравнения, если оба аргумента являются целыми числами; в противном случае они возвращают результаты сравнения строк. Результатом каждого сравнения является 1, если указанное отношение истинно, или 0, если отношение ложно. (Если вы выполняете сравнения строк, они будут выполняться таким образом, чтобы это соответствовало текущим локальным настройкам вашей операционной системы.)

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

      Хотите выполнить расчет? Вам понадобится один из них:

      expr1 {+, -} expr2

      Эти операторы возвращают результаты сложения или вычитания целочисленных аргументов.

      expr1 {*, /, %} expr2

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

      Операторы регулярных выражений

      Вы также можете использовать оператор регулярных выражений в Asterisk:

      Некоторая дополнительная информация об особенностях операторов регулярных выражений в Asterisk содержится на веб-сайте Walter Doekes.

      expr1 : expr2

      Этот оператор сопоставляет expr1 с expr2, где expr2 должно быть регулярным выражением.2 Регулярное выражение привязывается к началу строки с неявным ^ .3

      Если шаблон не содержит подвыражения, возвращается количество совпадающих символов. Это будет 0, если совпадение не выполнено. Если шаблон содержит подэлемент — \⁠(…\) — возвращается строка, соответствующая \1. Если совпадение не выполняется, возвращается пустая строка.

      expr1 =~ expr2

      Этот оператор работает так же, как оператор : кроме того, что он не привязан к началу.

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

      exten => 123,1,Set(TEST=$[2+1])

      Оно присвоило переменной TEST строку 2+1 вместо значения 3. Чтобы исправить это, мы помещаем пробелы вокруг оператора, например:

      exten => 234,1,Set(TEST=$[2 + 1])

      Это больше не требуется в текущих версиях Asterisk, поскольку парсер выражений стал более упрощенным в этих сценариях. Однако, для удобства чтения, мы по-прежнему рекомендуем включать пробелы вокруг ваших операторов.

      Чтобы присоединить текст в начало или конец переменной, просто поместите их вместе, например:

      exten => 234,1,Set(NEWTEST=blah${TEST})

      Функции диалплана

      Функции диалплана позволяют добавлять больше мощи вашим выражениям; вы можете рассматривать их как интеллектуальные переменные. Функции диалплана позволяют вычислять длины строк, даты и время, контрольные суммы MD5 и т. д. все из выражения диалплана.

      Вы увидите использование Playback(silence/1) во всех примерах этой главы. Мы делаем это, так как оно ответит на эту строку, если на нее еще не ответили, и воспроизведёт некоторую тишину на линии. Это позволяет другим приложениям, таким как SayNumber() воспроизводить аудио без пробелов.

      Синтаксис

      Функции диалплана имеют следующий базовый синтаксис:

      FUNCTION_NAME(argument)

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

      ${FUNCTION_NAME(argument)}

      Функции также могут инкапсулировать другие функции, например:

      ${FUNCTION_NAME(${FUNCTION_NAME(argument)})}

      ^ ^ ^ ^ ^ ^^^
      1 2 3 4 4 321

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

      Примеры функций диалплана

      Функции часто используются вместе с приложением Set() для получения или установки значения переменной. В качестве простого примера рассмотрим функцию LEN(). Эта функция вычисляет длину строки аргумента. Давайте вычислим длину строки переменной и вернем ее вызывающей стороне:

      exten => 123,1,NoOp()

      same => n,Set(TEST=example)

      same => n,Playback(silence/1)

      same => n,SayNumber(${LEN(${TEST})})

      В этом примере сначала будет оцениваться $TEST со значением «example». Строка «example» затем передается функции LEN(), которая будет вычислять длину строки, 7. Наконец, 7 передается в качестве аргумента в приложение SayNumber().

      Давайте рассмотрим еще один простой пример. Если бы мы хотели установить один из различных тайм-аутов канала, мы могли бы использовать функцию TIMEOUT(). Функция TIMEOUT() принимает один из трех аргументов: absolute, digit и response. Чтобы установить тайм-аут с помощью функции TIMEOUT(), мы могли бы использовать приложение Set(), например:

      exten => s,1,Set(TIMEOUT(digit)=30)

      Обратите внимание на отсутствие ${ }, окружающего функцию. Так же, как если бы мы присваивали значение переменной, мы присваиваем значение функции без использования инкапсуляции ${ }.

      Полный список доступных функций можно найти, набрав core show functions в интерфейсе командной строки Asterisk.

      Условное ветвление

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

      Приложение GotoIf()

      Ключом к условному ветвлению является приложение GotoIf(). GotoIf() оценивает выражение и отправляет вызывающего абонента в конкретный пункт назначения основываясь на оценке выражения, выраженном в true или false.

      GotoIf() использует специальный синтаксис, который часто называют условным синтаксисом:

      GotoIf(expression?destination1:destination2)

      Если выражение принимает значение true, вызывающий объект отправляется в destination1. Если выражение принимает значение false, вызывающий объект отправляется во второе назначение. Итак, что истинно и что ложно? Пустая строка и число 0 оцениваются как false. Все остальное оценивается как истинное.

      Каждый из пунктов назначения может быть одним из следующих:

      • Метка приоритета в том же расширении, как weasels
      • Расширение и метка приоритета в том же контексте, например, 123,weasels
      • Контекст, внутренний номер и метка приоритета, такая как incoming,123,weasels

      Любой из пунктов назначения может быть опущен, но не оба. Если пропущенный пункт назначения должен соблюдаться, Asterisk просто переходит к следующему приоритету в текущем внутреннем номере.

      Давайте используем GotoIf() в примере:

      exten => 345,1,NoOp()

      same => n,Set(TEST=1)

      same => n,GotoIf($[${TEST} = 1]?weasels:iguanas)

      same => n(weasels),Playback(weasels-eaten-phonesys)

      same => n,Hangup()

      same => n(iguanas),Playback(office-iguanas)

      same => n,Hangup()

      Вы заметите, что мы использовали приложение Hangup() после каждого использования Playback(). Это делается для того, что когда мы переходим на метку weasels, вызов останавливается до того, как выполнение переходит к звуковому файлу office-iguanas. Становится все более распространенным видеть расширения, разбитые на несколько компонентов (защищенных друг от друга командой Hangup()), каждая из которых совершает отдельную последовательность шагов, выполняемых после GotoIf().

      Предоставление только ложного условного пути

      Если бы мы захотели, мы могли бы выполнить предыдущий пример следующим образом:

      exten => 345,1,NoOp()

      same => n,Answer()

      same => n,Set(TEST=1)

      same => n,GotoIf($[${TEST} = 1]?:iguanas) ; у нас больше нет ярлыка

      ; weasels, но это всё равно

      ; работает

      same => n,Playback(weasels-eaten-phonesys)

      same => n,Hangup()

      same => n(iguanas),Playback(office-iguanas)

      same => n,Hangup()

      Ничего нет между ? и : поэтому, если оператор оценивает значение как true, выполнение будет продолжено на следующем шаге. Поскольку это то, чего мы хотим, ярлык не нужен.

      Мы действительно не рекомендуем это делать, потому что это трудно читать, но вы увидите диалпланы, подобные этому, поэтому хорошо знать, что этот синтаксис абсолютно корректный.

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

      exten => 345,1,NoOp()

      same => n,Answer()

      same => n,Set(TEST=1)

      same => n,GotoIf($[${TEST} = 1]?weasels,1:iguanas,1) ; теперь мы идем к

      ; extension,priority

      exten => weasels,1,NoOp()

      same => n,Playback(weasels-eaten-phonesys) ; это НЕ метка

      ; это другое расширение

      same => n,Hangup()

      exten => iguanas,1,NoOp()

      same => n,Playback(office-iguanas)

      same => n,Hangup()

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

      Давайте посмотрим на другой пример условного разветвления. На этот раз мы будем использовать Goto() и GotoIf() для отсчета с 10, а затем повесим трубку:

      exten => 123,1,NoOp()

      same => n,Answer()

      same => n,Set(COUNT=10)

      same => n(start),GotoIf($[${COUNT} > 0]?:goodbye)

      same => n,SayNumber(${COUNT})

      same => n,Set(COUNT=$[${COUNT} — 1])

      same => n,Goto(start)

      same => n(goodbye),Hangup()

      Давайте проанализируем этот пример. Во втором приоритете мы устанавливаем переменную COUNT равным 10. Затем мы проверяем, будет ли COUNT больше 0. Если это так, переходим к следующему приоритету. (Не забывайте, что если мы опускаем пункт назначения в приложении GotoIf(), управление переходит к следующему приоритету.) Оттуда мы проговариваем число, вычитаем 1 из COUNT и возвращаемся к началу приоритета. Если значение COUNT меньше или равно 0, управление переходит на приоритет goodbye, и кладется трубка.

      Кавычки и префиксы переменных в условных ветвлениях

      Сейчас самое подходящее время уделить минутку, чтобы посмотреть на какие-то небрежные вещи с условными ветлениями. В Asterisk недопустимо иметь нулевое значение по обе стороны оператора сравнения. Давайте рассмотрим примеры, которые приведут к ошибке:

      $[ = 0 ]

      $[ foo = ]

      $[ > 0 ]

      $[ 1 + ]

      Любой из наших примеров может вызвать предупреждение:

      WARNING[28400][C-000000eb]: ast_expr2.fl:470 ast_yyerror: ast_yyerror():

      syntax error: syntax error, unexpected ‘=’, expecting $end; Input:

      = 0

      ^

      Маловероятно (если у вас нет опечатки), что вы целенаправленно реализуете что-то вроде наших примеров. Тем не менее, когда вы выполняете математику или сравнение с неустановленной переменной канала, это фактически то, что вы делаете.

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

      В Asterisk строки не обязательно должны быть заключены в двойные или одинарные кавычки, как во многих языках программирования. На самом деле, если вы используете двойную или одинарную кавычку, это будет буквенной конструкцией в строке. Если мы посмотрим на следующие две строки Set() …

      exten => n,Set(TEST_1=foo)

      exten => n,Set(TEST_2=’foo’)

      exten => n,NoOp($[${TEST_1} = ${TEST_2}])

      … тогда значение, возвращаемое нашим сравнением в NoOp() не будет равно 1 (значения совпадают или true), возвращаемое значение будет 0 (значения не совпадают или false).

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

      В следующем примере мы получим ошибку:

      exten => 1000,1,NoOp()

      same => n,GotoIf($[${TEST} = invalid]?error_handling)

      same => n(error_handling),NoOp()

      Однако мы можем обойти это, обернув то, что мы сравниваем в кавычки. Тот же пример, но сделан действительным:

      exten => 1000,1,NoOp()

      same => n,GotoIf($[«${TEST}» = «invalid»]?error_handling)

      same => n(error_handling),NoOp()

      Даже если ${TEST} не был установлен, мы делаем сравнение:

      $[«» = «invalid»]

      Мы можем сделать такой же пример сети безопасности при сравнении, которую выполняют численные сравнения, добавив префикс для наших чисел в виде нуля. Поскольку 01 совпадает с 1 при сравнении чисел, наш потенциально недействительный пример:

      exten => 1001,1,NoOp()

      same => n,GotoIf($[${TEST} < 1]?error_handling)

      same => n(error_handling),NoOp()

      Можно сделать более безопасным:

      exten => 1001,1,NoOp()

      same => n,GotoIf($[0${TEST} < 1]?error_handling)

      same => n(error_handling),NoOp()

      Если вы привыкли распознавать эти ситуации и использовать методы обертывания и префикса, которые мы изложили, вы напишете гораздо более безопасные диалпланы.

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

      В этом примере используется функция CALLERID, которая позволяет нам получать информацию CallerID во входящем вызове. Предположим ради этого примера, что номер телефона жертвы — 888-555-1212:

      exten => 123,1,NoOp()

      same => n,GotoIf($[${CALLERID(num)} = 8885551212]?reject:allow)

      same => n(allow),Dial(DAHDI/4)

      same => n,Hangup()

      same => n(reject),Playback(abandon-all-hope)

      same => n,Hangup()

      В приоритете 1 мы вызываем приложение GotoIf(). Оно сообщает Asterisk, о переходе к метке приоритета reject, если номер CallerID соответствует 8885551212, а в противном случае — присвоить метку приоритета allow (мы могли бы просто опустить имя метки, в результате чего GotoIf() провалится). Если номер CallerID совпадает, управление вызовом переходит к метке приоритета reject, которое воспроизводит недоброжелательное сообщение нежелательному абоненту. В противном случае вызов пытается набрать получателя на канале DAHDI/4.

      Временное условное ветвление с GotoIfTime()

      Другой способ использования условного разветвления в вашем диалплане — это приложение GotoIfTime(). В то время как GotoIf() оценивает выражение, чтобы решить, что делать, GotoIfTime() смотрит на текущее системное время и использует его, чтобы решить, следовать или нет другой ветке в диалплане.

      Наиболее очевидное использование этого приложения — дать вашим абонентам другое приветствие до и после обычных рабочих часов.

      Синтаксис приложения GotoIfTime() выглядит следующим образом:

      GotoIfTime(время,дни_недели,числа_месяца,месяцы?label)

      Короче говоря, GotoIfTime() отправляет вызов указанной метке (label), если текущая дата и время соответствуют критериям, указанным во времени, днях_недели, днях_месяца и месяцах. Давайте рассмотрим каждый аргумент более подробно:

      время

      Это список одного или нескольких диапазонов времени в 24-часовом формате. В качестве примера, с 9:00 Д.П. до 5:00 П.П. будет указано как 09:00-17:00. День начинается в 0:00 и заканчивается в 23:59.

      Стоит отметить, что время будет правильно оборачиваться. Поэтому, если вы хотите указать время закрытия своего офиса, вы можете написать 18:00-9:00 в параметре время и он будет работать как ожидалось. Обратите внимание, что этот метод работает также и для других компонентов GotoIfTime(). Например, вы можете написать sat-sun, чтобы указать выходные дни.

      дни_недели

      Это список одного или нескольких дней недели. Дни должны быть указаны как mon, tue, wed, thu, fri, sat и/или sun. С понедельника по пятницу будет выражаться как mon-fri. Вторник и четверг будут выражены как tue&thu.

      Обратите внимание, что вы можете указать комбинацию диапазонов и отдельных дней, например: sun-mon&wed&fri-sat или, проще говоря: wed&fri-mon.

      числа_месяца

      Это список чисел месяца. Дни обозначаются цифрами от 1 до 31. Седьмое-двенадцатое будет выражаться как 7-12, а 15 и 30 числа месяца будут записаны как 15&30.

      месяцы

      Это список одного или нескольких месяцев в году. Месяцы должны быть записаны как jan-apr для диапазона и разделены амперсандами, когда вы хотите включить непоследовательные месяцы, например jan&mar&jun. Вы также можете комбинировать их так: jan-apr&jun&oct-dec.

      Если вы хотите сопоставить все возможные значения для любого из этих аргументов, просто поместите аргумент * для него.

      Аргумент метки может быть любым из следующих:

      • Метка приоритета в пределах одного расширения, например, time_has_passed
      • Расширение и приоритет в одном контексте, например 123,time_has_passed
      • Контекст, расширение и приоритет, такие как incoming,123,time_has_passed

      Теперь, когда мы рассмотрели синтаксис, давайте рассмотрим несколько примеров. Следующий пример будет соответствовать 9:00 до 17:59 вечера, с понедельника по пятницу, в любой день месяца, в любой месяц в году:

      exten => s,1,NoOp()

      same => n,GotoIfTime(09:00-17:59,mon-fri,*,*?open,s,1)

      Если вызывающий звонит в течение этих часов, вызов будет отправлен на первый приоритет расширения s в контексте с именем open. Если вызов выполняется за пределами указанного времени, он будет отправлен на следующий приоритет текущего расширения. Это позволяет вам легко разветвляться несколько раз, как показано в следующем примере (обратите внимание, что вы всегда должны ставить ваши наиболее конкретные совпадения времени перед наименее конкретными):

      ; Если это любой час дня, любой день недели,

      ; в течение четвертого дня месяца, в июле, мы закрыты

      exten => s,1,NoOp()

      same => n,GotoIfTime(*,*,4,jul?closed,s,1)

      ; В рабочее время отправлять вызовы в контекст open

      same => n,GotoIfTime(09:00-17:59,mon-fri,*,*?open,s,1)

      same => n,GotoIfTime(09:00-11:59,sat,*,*?open,s,1)

      ; В противном случае мы закрыты

      same => n,Goto(closed,s,1)

      Если вы столкнетесь с ситуацией, когда задаете вопрос: «Но я указал 17:58, и сейчас 17:59. Почему он все еще делает то же самое?», Следует отметить, что детализация приложения GotoIfTime() — это двухминутный период. Таким образом, если вы укажете 18:00 в качестве конечного времени периода, система будет продолжать выполнять тот же путь до 18:01:59.

      Макросы

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

      Хотя Macro() кажется универсальной подпрограммой диалплана, у нее есть проблема переполнения стека, что означает, что вы не должны пытаться вложить вызовы Macro() более пяти уровней. Если вы планируете использовать макросы в макросах (и вызывать сложные функции внутри них), вы можете столкнуться с проблемами стабильности. Вы узнаете, что у вас есть проблема только с одним тестовым вызовом, поэтому, если ваш диалплан тестовый, всё хорошо — продолжаем. Мы также рекомендуем вам взглянуть на приложения GoSub() и Return() (см. «GoSub»), так как многие макрофункции могут быть реализованы без фактического использования Macro().

      Начиная с Asterisk 11 приложение Macro() устарело в пользу приложения GoSub(). Однако знание Macro() полезно, так как почти любая существующая система, которую вы поддерживаете или изменяете, скорее всего, содержит хотя бы одно использование Macro().

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

      exten => 101,1,NoOp()

      same => n,Dial(${JOHN},10)

      same => n,GotoIf($[«${DIALSTATUS}» = «BUSY»]?busy:unavail)

      same => n(unavail),VoiceMail(101@default,u)

      same => n,Hangup()

      same => n(busy),VoiceMail(101@default,b)

      same => n,Hangup()

      В нашем примере GotoIf() мы заключаем переменную канала ${DIALSTATUS} в двойные кавычки. Для чего мы это сделали, см. «Кавычки и префиксы переменных в условных ветвлениях».

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

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

      Если вы знакомы с компьютерным программированием, вы поймете, что макросы похожи на подпрограммы во многих современных языках программирования. Если вы не знакомы с компьютерным программированием, не волнуйтесь — мы проведем вас через создание макроса.

      Лучший способ оценить макросы — увидеть их в действии, поэтому давайте двигаться дальше.

      Определение макроса

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

      Определения макросов очень похожи на контексты. (Фактически, вы можете утверждать, что это действительно небольшие, ограниченные контексты.) Вы определяете макрос, помещая команду macro- и имя своего макроса в квадратные скобки, например:

      [macro-voicemail]

      Имена макросов должны начинаться с macro-. Это отличает их от обычных контекстов. Команды внутри макроса построены почти идентично всем остальным в диалплане; единственным ограничивающим фактором является то, что макросы используют только расширение s. Давайте добавим нашу логику голосовой почты в макрос, изменив расширение на s по мере продвижения:

      [macro-voicemail]

      exten => s,1,NoOp()

      same => n,Dial(${JOHN},10)

      same => n,GotoIf($[«${DIALSTATUS}» = «BUSY»]?busy:unavail)

      same => n(unavail),VoiceMail(101@default,u)

      same => n,Hangup()

      same => n(busy),VoiceMail(101@default,b)

      same => n,Hangup()

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

      Вызов макроса из диалплана

      Чтобы использовать макрос в нашем диалплане, мы используем приложение Macro(). Это приложение вызывает указанный макрос и передает ему любые аргументы. Например, чтобы вызвать наш макрос голосовой почты из диалплана, мы можем сделать следующее:

      exten => 101,1,Macro(voicemail)

      Приложение Macro() также определяет несколько специальных переменных для нашего использования. Они включают:

      ${MACRO_CONTEXT}

      Исходный контекст, в котором был вызван макрос.

      ${MACRO_EXTEN}

      Исходное расширение, в котором был вызван макрос.

      ${MACRO_PRIORITY}

      Исходный приоритет, в котором был вызван макрос.

      ${ARG n}

      n-й аргумент передаваемый макросу. Например, первым аргументом будет ${ARG1}, вторым $⁠{ARG2} и т. д.

      Как мы объясняли ранее, способ, которым мы изначально определяли наш макрос, был жестко запрограммирован для Джона, а не был общим. Давайте изменим наш макрос, чтобы использовать $⁠{MACRO_EXTEN} вместо 101 для номера почтового ящика. Таким образом, если мы вызываем макрос из внутреннего номера 101, сообщения голосовой почты будут отправляться в почтовый ящик 101; если мы вызываем макрос из номера 102, сообщения отправляются в почтовый ящик 102; и так далее:

      [macro-voicemail]

      exten => s,1,NoOp()

      same => n,Dial(${JOHN},10)

      same => n,GotoIf($[«${DIALSTATUS}» = «BUSY»]?busy:unavail)

      same => n(unavail),VoiceMail(${MACRO_EXTEN}@default,u)

      same => n,Hangup()

      same => n(busy),VoiceMail(${MACRO_EXTEN}@default,b)

      same => n,Hangup()

      Использование аргументов в макросе

      Теперь мы приближаемся к тому, чтобы макрос был таким, какой мы хотим, но нам остается изменить все: нам нужно пройти в канал для набора, так как он в настоящее время все еще жестко запрограммирован для ${JOHN} (помните, что мы определил переменную JOHN как канал для вызова, когда мы хотим связаться с Джоном). Передим его в канал как аргумент, и тогда наш первый макрос будет завершен:

      [macro-voicemail]

      exten => s,1,NoOp()

      same => n,Dial(${ARG1},10)

      same => n,GotoIf($[«${DIALSTATUS}» = «BUSY»]?busy:unavail)

      same => n(unavail),VoiceMail(${MACRO_EXTEN}@default,u)

      same => n,Hangup()

      same => n(busy),VoiceMail(${MACRO_EXTEN}@default,b)

      same => n,Hangup()

      Теперь, когда наш макрос сделан, мы можем использовать его в нашем диалплане. Вот как мы можем позвонить в наш макрос, чтобы предоставить голосовую почту Джону, Джейн и Джеку:4

      exten => 101,1,Macro(voicemail,${JOHN})

      exten => 102,1,Macro(voicemail,${JANE})

      exten => 103,1,Macro(voicemail,${JACK})

      С 50 или более пользователями этот диалплан по-прежнему будет выглядеть аккуратно и организованно; мы просто будем иметь одну строку для каждого пользователя, ссылаясь на макрос, который может быть более сложным. Мы могли бы даже иметь несколько разных макросов для различных типов пользователей, таких как executives (руководители), courtesy_phones (справочная служба), call_center_agents (агенты колл-центра), analog_sets (аналоговые комплекты), sales_department (отдел продаж) и т.д.

      Более продвинутая версия макроса может выглядеть примерно так:

      [macro-voicemail]

      exten => s,1,NoOp()

      same => n,Dial(${ARG1},20)

      same => n,Goto(s-${DIALSTATUS},1)

      exten => s-NOANSWER,1,VoiceMail(${MACRO_EXTEN}@default,u)

      same => n,Goto(incoming,s,1)

      exten => s-BUSY,1,VoiceMail(${MACRO_EXTEN}@default,b)

      same => n,Goto(incoming,s,1)

      exten => _s-.,1,NoOp()

      same => n,Goto(s-NOANSWER,1)

      Так как теперь мы знаем, как использовать функции диалплана, вот еще один способ контроля того, какая голосовая подсказка (недоступности или занятости) воспроизводится вызывающему. В следующем примере мы будем использовать функцию диалплана IF():

      [macro-voicemail]

      exten => s,1,NoOp()

      same => n,Dial(${ARG1},20)

      same => n,VoiceMail(${MACRO_EXTEN}@default,${IF($[${DIALSTATUS} = BUSY]?b:u)})

      Этот макрос зависит от хорошего побочного эффекта приложения Dial(): когда вы используете приложение Dial(), оно устанавливает переменную DIALSTATUS чтобы указать, был ли вызов успешным или нет. В этом случае мы обрабатываем NOANSWER и BUSY и обрабатываем все остальные результаты как NOANSWER.

      GoSub

      Приложение диалплана GoSub() аналогично приложению Macro(), поскольку цель состоит в том, чтобы разрешить вам вызывать блок функциональности диалплана, передавать информацию этому блоку и возвращать его (необязательно с возвращаемым значением), GoSub() работает иначе, чем Macro(), хотя в нем нет требований к пространству стека, поэтому он эффективно монтируется. По сути, GoSub() действует как Goto() с памятью о том, откуда он появился.

      В этом разделе мы перейдем к тому, что мы узнали в «Макросах». При необходимости вы можете просмотреть этот раздел: он объясняет, почему мы можем использовать подпрограмму и цель, которую мы пытаемся выполнить.

      Определение подпрограмм

      В отличие от Macro(), при использовании GoSub() в диалплане особых требований к именам не существует. Фактически, вы можете использовать GoSub() в том же контексте и расширении, если хотите. В большинстве случаев, однако, GoSub() используется аналогично Macro(), поэтому определение нового контекста является общим. При создании контекста нам нравится добавлять имя с sub, поэтому мы знаем, что контекст обычно вызывается из приложения GoSub() (конечно, нет необходимости, чтобы вы это делали, но это похоже на разумное соглашение).

      Вот простой пример того, как мы можем определить подпрограмму в Asterisk:

      [SubVoicemail]

      Давайте возьмем наш пример из «Макросов» и преобразуем его в подпрограмму. Вот как он определяется для использования с Macro():

      [macro-voicemail]

      exten => s,1,NoOp()

      same => n,Dial(${JOHN},10)

      same => n,GotoIf($[«${DIALSTATUS}» = «BUSY»]?busy:unavail)

      same => n(unavail),VoiceMail(101@default,u)

      same => n,Hangup()

      same => n(busy),VoiceMail(101@default,b)

      same => n,Hangup()

      Если мы собираемся преобразовать это для использования в подпрограмму, то может выглядеть так:

      [subVoicemail]

      exten => start,1,NoOp()

      same => n,Dial(${JOHN},10)

      same => n,GotoIf($[«${DIALSTATUS}» = «BUSY»]?busy:unavail)

      same => n(unavail),VoiceMail(101@default,u)

      same => n,Hangup()

      same => n(busy),VoiceMail(101@default,b)

      same => n,Hangup()

      Не так много изменений, не так ли? Все, что мы изменили в этом примере — это имя контекста, от [macro-voicemail] до [subVoicemail] и расширения от s до start (поскольку нет требования, чтобы расширение было вызвано чем-то конкретным, в отличие от Macro(), который ожидает что расширение будет s).

      Конечно, как и в примере из раздела «Макросы», мы не передали никаких аргументов подпрограмме, поэтому всякий раз, когда мы вызываем [subVoicemail], всегда будет вызываться ${JOHN}, и будет использоваться поле голосовой почты 101. В следующих разделах мы будем копать немного глубже. Сначала рассмотрим, как мы будем называть подпрограмму, а затем узнаем, как передавать аргументы.

      Вызов подпрограмм из диалплана

      Подпрограммы вызываются из диалплана с помощью приложения GoSub(). Аргументы для GoSub() несколько отличаются от аргументов для Macro(), поскольку GoSub() не имеет требований к именам для контекста или расширения (или приоритета), которые используются. Кроме того, никакие специальные переменные канала не задаются при вызове подпрограммы, отличной от переданных аргументов, которые сохраняются в ${ARGn} (где первый аргумент равен ${ARG1}, второй аргумент — ${ARG2} и так далее).

      Теперь, когда мы обновили наш макрос голосовой почты, который вызывается как подпрограмма, давайте посмотрим, как мы его вызываем, используя GoSub():

      exten => 101,1,GoSub(subVoicemail,start,1())

      Вы заметите, что мы разместили набор открывающих и закрывающих круглых скобок в нашем приложении GoSub(). Это заполнители для любых аргументов, которые мы можем передать подпрограмме и, хотя они не обязательно будут существовать, это стиль программирования, который мы предпочитаем использовать.

      Затем давайте посмотрим, как мы можем передавать аргументы нашей подпрограмме, чтобы сделать ее более общей.

      Использование аргументов в подпрограмме

      Возможность использования аргументов является одной из основных особенностей использования Macro() или GoSub(), поскольку позволяет абстрагироваться от кода, который в противном случае был бы дублирован в вашем диалплане. Без необходимости дублировать код, мы можем лучше управлять им, и можем легко добавить функциональность большому числу пользователей, изменив одно местоположение. Вам рекомендуется помещать код в эту форму всякий раз, когда он дублируется.

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

      [subVoicemail]

      exten => start,1,NoOp()

      same => n,Dial(${ARG1},10)

      same => n,GotoIf($[«${DIALSTATUS}» = «BUSY»]?busy:unavail)

      same => n(unavail),VoiceMail(${ARG2}@default,u)

      same => n,Hangup()

      same => n(busy),VoiceMail(${ARG2}@default,b)

      same => n,Hangup()

      Напомним, что ранее мы жестко закодировали переменную канала ${JOHN} в качестве местоположения для набора номера и почтовый ящик 101 в качестве ящика голосовой почты для использования, если ${JOHN} недоступен. В этом коде мы заменили ${JOHN} и 101 на ${ARG1} и $⁠{ARG2} соответственно. В более сложных подпрограммах мы можем даже присваивать переменным ${ARG1} и ${ARG2} нечто вроде ${DESTINATION} и ${VMBOX}, чтобы было ясно, что представляют собой ${ARG1} и ${ARG2}.

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

      [LocalSets]

      exten => 101,1,GoSub(subVoicemail,start,1(${JOHN},${EXTEN}))

      exten => 102,1,GoSub(subVoicemail,start,1(${JANE},${EXTEN}))

      exten => 103,1,GoSub(subVoicemail,start,1(${JACK},${EXTEN}))

      Опять же, наш диалплан хорош и опрятен. Мы могли бы даже изменить нашу подпрограмму всего на три строки:

      [subVoicemail]

      exten => start,1,NoOp()

      same => n,Dial(${ARG1},10)

      same => n,VoiceMail(${ARG2}@default,${IF($[${DIALSTATUS} = BUSY]?b:u)})

      same => n,Hangup()

      Одно из отличий между GoSub() и Macro() заключается в том, что если мы оставим нашу подпрограмму подобным образом, мы никогда не вернемся в исходный диалплан. В этом конкретном примере это не проблема, так как после того, как голосовая почта оставлена, мы ожидаем, что вызывающий абонент повесит трубку в любом случае. В ситуациях, когда мы хотим сделать что-то ещё после выполнения подпрограммы, нам нужно реализовать приложение Return().

      Возврат из подпрограммы

      В отличие от Macro() приложение GoSub() не возвращается автоматически после его выполнения. Чтобы вернуться туда, откуда мы пришли, нам нужно использовать приложение Return(). Теперь, когда мы знаем, как вызвать подпрограмму и передать аргументы, можем посмотреть пример, где нам может понадобиться вернуться из подпрограммы.

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

      [subDialer]

      exten => start,1,NoOp()

      same => n,Dial(${ARG1},${ARG2})

      same => n,Return()

      [subVoicemail]

      exten => start,1,NoOp()

      same => n,VoiceMail(${ARG1}@${ARG2},${ARG3})

      same => n,Hangup()

      Созданный здесь контекст [subDialer] принимает два аргумента: ${ARG1}, который содержит назначение для набора; и ${ARG2} содержащий время звонка, определенное в секундах. Мы завершаем контекст [subDialer] с помощью приложения Return(), которое вернет к приоритету, следующему за вызовом GoSub() (следующая строка диалплана).

      Контекст [subVoicemail] содержит приложение VoiceMail(), которое использует три аргумента, переданных ему: ${ARG1} содержит номер почтового ящика, ${ARG2} содержит контекст голосовой почты, а ${ARG3} содержит значение, указывающее, тип сообщения голосовой почты (недоступно или занято) для воспроизведения вызывающему абоненту.

      Вызов этих подпрограмм может выглядеть так:

      exten => 101,1,NoOp()

      same => n,GoSub(subDialer,start,1(${JOHN},30))

      same => n,GoSub(subVoicemail,start,1(${EXTEN},default,u))

      Здесь мы использовали подпрограмму subDialer, которая пытается вызвать ${JOHN}, набрав его в течение 30 секунд. Если приложение Dial() вернется (например, если линия занята или не было ответа в течение 30 секунд), мы делаем Return() из подпрограммы и выполняем следующую строку нашего диалплана, которая вызывает подпрограмму subVoicemail. Оттуда мы передаем внутренний номер, который был набран (например, 101) в качестве номера почтового ящика, и передаем значения default для контекста голосовой почты и букву u для воспроизведения сообщения о недоступности.

      Наш пример жестко запрограммирован для воспроизведения сообщения голосовой почты о недоступности, но мы можем изменить приложение Return(), чтобы вернуть ${DIALSTATUS}, чтобы мы могли играть сообщение «занято», если его значение BUSY. Для этого мы будем использовать переменную канала ${GOSUB_RETVAL}, которая устанавливается каждый раз, когда мы передаем значение в приложение Return():

      [subDialer]

      exten => start,1,NoOp()

      same => n,Dial(${ARG1},${ARG2})

      same => n,Return(${DIALSTATUS})

      [subVoicemail]

      exten => start,1,NoOp()

      same => n,VoiceMail(${ARG1}@${ARG2},${ARG3})

      same => n,Hangup()

      В этой версии мы сделали только одно изменение: Return() на Return(${DIALSTATUS}).

      Теперь мы можем изменить расширение 101, чтобы использовать переменную канала $⁠{GOSUB_RETVAL}, которая будет установлена через Return():

      exten => 101,1,NoOp()

      same => n,GoSub(subDialer,start,1(${JOHN},30))

      same => n,Set(VoicemailMessage=${IF($[${GOSUB_RETVAL} = BUSY]?b:u)})

      same => n,GoSub(subVoicemail,start,1(${EXTEN},default,${VoicemailMessage}))

      В нашем диалплане теперь есть новая строка, которая устанавливает переменную канала $⁠{VoicemailMessage} в значение u или b, используя функцию диалплана IF() и значение $⁠{GOSUB_RETVAL}. Затем мы передаем значение ${VoicemailMessage} в качестве третьего аргумента в нашу подпрограмму subVoicemail.

      Прежде чем двигаться дальше, вы можете вернуться назад и просмотреть «Макросы» и «GoSub». Мы дали вам массу возможностей чтобы переварить здесь, но эти концепции сэкономят вам много работы и времени как только вы начинаете строить свой диалплан.

      Локальные (Local) каналы

      Локальные каналы (Local) — это метод выполнения других областей диалплана из приложения Dial() (в отличие от отправки канала вызова). Они могут показаться немного странной концепцией когда вы впервые начнете их использовать, но поверьте нам, когда вы узнаете что они являются славной и чрезвычайно полезной функцией, вы почти наверняка захотите использовать, когда начнете писать расширенные диалпланы. В качестве примера можно привести лучший пример использования локальных каналов. Предположим, что у нас есть ситуация, когда нам нужно позвонить нескольким людям, но нужно предоставить задержки разной длины, прежде чем набирать каждого из них. Использование локальных каналов — единственное решение проблемы.

      С помощью приложения Dial() вы можете звонить по нескольким конечным точкам, но все три канала будут звонить одновременно и в один и тот же промежуток времени. Набор нескольких каналов одновременно выполняется следующим образом:

      [LocalSets]

      exten => 107,1,NoOp()

      same => n,Verbose(2,Dialing multiple locations simultaneously)

      same => n,Dial(SIP/0000FFFF0001&DAHDI/g0/14165551212&SIP/MyITSP/

      12565551212,30)

      same => n,Hangup()

      В этом примере набирают трёх адресатов в течение 30 секунд. Если ни один из них не отвечает на вызов в течение 30 секунд, диалплан продолжит на следующей строке и звонок завершится.

      Однако предположим, что мы хотим ввести некоторые задержки и прекратить звонки в разное время. Использование локальных каналов дает нам независимый контроль над каждым каналом, который мы хотим набрать, поэтому мы можем вводить задержки и управлять периодом времени, для которого каждый канал звонит независимо. Мы покажем вам, как это делается в диалплане, как внутри таблицы, которая визуально отображает задержки, так и все вместе в боксе как это было сделано для других частей диалплана. Мы будем строить диалплан в соответствии с началом и остановками времени, описанными на Рисунке 10-1.

      Рисунок 10-1. Набор с задержкой по времени в локальных каналах

      Сначала нам нужно вызвать три локальных канала, которые будут выполнять разные части диалплана. Мы делаем это с помощью приложения Dial(), например:

      [LocalSets]

      exten => 107,1,Verbose(2,Dialing multiple locations with time delay)

      ; *** Это всё должно быть на одной линии

      same => n,Dial(Local/channel_1@TimeDelay&Local/channel_2@TimeDelay

      &Local/channel_3@TimeDelay,40)

      same => n,Hangup()

      Теперь наше приложение Dial() набирает три локальных канала. Целями назначения будут внутренние номера channel_1, channel_2 и channel_3, расположенные в контексте диалплана TimeDelay. Помните, что локальные каналы — это способ выполнения диалплана из приложения Dial(). Наш главный тайм-аут для всех каналов составляет 40 секунд, что означает, что любой локальный канал, который не имеет более короткого тайм-аута, будет завершаться, если он не отвечает на вызов в течение этого периода времени.

      Как и было обещано, Таблица 10-1 иллюстрирует конфигурации задержки.

      Table 10-1. Задержка набора с использованием локальных каналов

      Период времени (в секундах) channel_1 channel_2 channel_3
      0 Dial(SIP/0000FFFF0001,20) Wait(10) Wait(15)
      5


      10
      Dial(DAHDI/g0/14165551212)
      15

      Dial(SIP/MyITSP/12565551212,15)
      20 Hangup()

      25


      30

      Hangup()
      35


      40


      В этой таблице мы видим, что channel_1 сразу начал набирать местоположение SIP/0000FFFF0001 и ждал в течении в 20 секунд. Через 20 секунд этот локальный канал повесил трубку. Наш channel_2 ждал 10 секунд до набора конечной точки DAHDI/g0/14165551212. Максимальное время, связанное с этим Dial() не было достигнуто, поэтому его временной период закончится, когда истечет время ожидания 40 секунд (которое мы установили при изначальном именовании локальных каналов) истечёт. Наконец, channel_3 ждал за 15 секунд до набора номера, затем набрал SIP/MyITSP/12565551212 и ждал 15 секунд перед тем, как повесить трубку.

      Если мы соединим все это, мы получим следующий диалплан:

      [LocalSets]

      exten => 107,1,Verbose(2,Dialing multiple locations with time delay)

      ; *** Это все должно быть на одной линии

      same => n,Dial(Local/channel_1@TimeDelay&Local/channel_2@TimeDelay

      &Local/channel_3@TimeDelay,40)

      same => n,Hangup()

      [TimeDelay]

      exten => channel_1,1,Verbose(2,Dialing the first channel)

      same => n,Dial(SIP/0000FFFF0001,20)

      same => n,Hangup()

      exten => channel_2,1,Verbose(2,Dialing the second channel with a delay)

      same => n,Wait(10)

      same => n,Dial(DAHDI/g0/14165551212)

      exten => channel_3,1,Verbose(2,Dialing the third channel with a delay)

      same => n,Wait(15)

      same => n,Dial(SIP/MyITSP/12565551212,15)

      same => n,Hangup()

      Вы увидите локальные каналы используемые в этой книге для различных целей. Локальные каналы позволяют выполнять логику диалплана из приложений, которые обычно ожидают прямого подключения к каналу. Например, вы можете назначить локальный канал в качестве участника очереди и запускать всякую причудливую логику диалплана всякий раз, когда очередь пытается доставить вызов агенту. Об этом мы поговорим в разделе «Использование локальных каналов» в Главе 13.

      Дополнительные сценарии и информация о локальных каналах и флаги модификатора (/n, /j, /m, /b) доступны в вики Asterisk. Если вы будете регулярно использовать локальные каналы, это очень важный документ для прочтения.

      Использование базы данных Asterisk (AstDB)

      Уже весело? А будет еще лучше!

      Asterisk предоставляет мощный механизм для хранения значений, называемый базой данных Asterisk (AstDB). AstDB предоставляет простой способ хранения данных для использования в диалплане.

      Для тех из вас, кто пользуется реляционными базами данных, такими как PostgreSQL или MySQL, база данных Asterisk не является традиционной базой данных; это база данных с поддержкой SQLite с использованием пар ключ/значение. Существует несколько способов хранения данных из Asterisk в реляционной базе данных. Более подробно о реляционных базах данных вы найдете в Главе 16.

      Первоначально (и на протяжении многих лет) AstDB использовала базу данных Berkeley (которая применительно к диалплану, функциям и командам CLI работает так же, как и новая база данных SQLite). Другими словами, это изменение должно быть прозрачным для вас.

      База данных Asterisk хранит свои данные в группах, называемых семействами (families), со значениями, идентифицируемыми ключами (keys). Внутри семьи ключ может использоваться только один раз. Например, если у нас было семейство, называемое test, мы могли бы сохранить только одно значение с помощью ключа, называемого count. Каждое сохраненное значение должно быть связано с семейством.

      Хранение данных в AstDB

      Чтобы сохранить новое значение в базе данных Asterisk, мы используем приложение Set()5, но вместо того, чтобы использовать его для установки переменной канала, мы используем его для установки переменной AstDB. Например, чтобы присвоить ключу count в семействе test значение 1, мы должны написать следующее:

      exten => 456,1,NoOp()

      same => n,Set(DB(test/count)=1)

      Если в семействе test уже существует ключ с именем count, его значение будет перезаписано новым. Вы также можете сохранить значения из командной строки Asterisk, выполнив команду database put <family> <key> <value>. В нашем примере вы бы набрали database put test count 1.

      Извлечение данных из AstDB

      Чтобы получить значение из базы данных Asterisk и назначить его переменной, мы снова используем приложение Set(). Получим значение count (опять же, из семейства тестов), назначте его переменной COUNT, а затем произнесите значение вызывающему:

      exten => 456,1,NoOp()

      same => n,Set(DB(test/count)=1)

      same => n,Set(COUNT=${DB(test/count)})

      same => n,Answer()

      same => n,SayNumber(${COUNT})

      Вы также можете проверить значение заданного ключа из командной строки Asterisk, запустив команду database get <family> <key>. Чтобы просмотреть все содержимое AstDB, используйте команду database show.

      Удаление данных из AstDB

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

      ; удаляет ключ и возвращает его значение за один шаг

      exten => 457,1,Verbose(0, The value was ${DB_DELETE(test/count)})

      Вы также можете удалить целое семейство ключей с помощью приложения DBdeltree(). Приложение DBdeltree() принимает один аргумент: имя семейства ключей для удаления. Чтобы удалить все семейство test, выполните следующие действия:

      exten => 457,1,DBdeltree(test)

      Чтобы удалить ключи и семейства ключей из AstDB через интерфейс командной строки, используйте команды database del <key> и database deltree <family>, соответственно.

      Использование AstDB в диалплане

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

      Чтобы начать пример подсчета, давайте сначала выберем число (значение ключа count) из базы данных и назначим его переменной с именем COUNT. Если ключ не существует, DB() вернет NULL (нет значения). Поэтому мы можем использовать функцию ISNULL(), чтобы проверить, было ли возвращено значение. Если нет, мы инициализируем AstDB с помощью приложения Set(), где установим значение в базе данных равным 1. Следующий приоритет вернет нам 1. Это произойдет в первый раз, когда мы набираем этот внутренний номер:

      exten => 678,1,NoOp()

      same => n,Set(COUNT=${DB(test/count)})

      same => n,GotoIf($[${ISNULL(${COUNT})}]?:continue)

      same => n,Set(DB(test/count)=1)

      same => n,Goto(1)

      same => n(continue),NoOp()

      Затем мы скажем текущее значение COUNT, а затем увеличим его:

      exten => 678,1,NoOp()

      same => n,Set(COUNT=${DB(test/count)})

      same => n,GotoIf($[${ISNULL(${COUNT})}]?:continue)

      same => n,Set(DB(test/count)=1)

      same => n,Goto(1)

      same => n(continue),NoOp()

      same => n,Playback(silence/1)

      same => n,SayNumber(${COUNT})

      same => n,Set(COUNT=$[${COUNT} + 1])

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

      exten => 678,1,NoOp()

      same => n,Set(COUNT=${DB(test/count)})

      same => n,GotoIf($[${ISNULL(${COUNT})}]?:continue)

      same => n,Set(DB(test/count)=1)

      same => n,Goto(1)

      same => n(continue),NoOp()

      same => n,Playback(silence/1)

      same => n,SayNumber(${COUNT})

      same => n,Set(COUNT=$[${COUNT} + 1])

      same => n,Set(DB(test/count)=${COUNT})

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

      exten => 678,1,NoOp()

      same => n,Set(COUNT=${DB(test/count)})

      same => n,GotoIf($[${ISNULL(${COUNT})}]?:continue)

      same => n,Set(DB(test/count)=1)

      same => n,Goto(1)

      same => n(continue),NoOp()

      same => n,Playback(silence/1)

      same => n,SayNumber(${COUNT})

      same => n,Set(COUNT=$[${COUNT} + 1]

      same => n,Set(DB(test/count)=${COUNT})

      same => n,Goto(1)

      Попробуйте этот пример. Послушайте, чтобы он подсчитал какое-то время, а затем повесьте трубку. Когда вы снова набеёте этот добавочный номер, он должен продолжать отсчет с того места, где был остановлен. Значение, хранящееся в базе данных, будет постоянным даже при перезапуске Asterisk.

      В следующем примере мы создадим логику диалплана вокруг функции BLACKLIST(), которая проверяет, существует ли текущий callerID номера в черном списке. (Черный список — это просто семейство, называемое blacklist в AstDB.) Если BLACKLIST() находит номер в черном списке, она возвращает значение 1; в противном случае вернет 0. Мы можем использовать эти значения в сочетании с GotoIf(), чтобы контролировать, будет ли вызов выполнять приложение Dial():

      exten => 124,1,NoOp()

      same => n,GotoIf($[${BLACKLIST()}]?blocked,1)

      same => n,Dial(${JOHN})

      exten => blocked,1,NoOp()

      same => n,Playback(silence/1)

      same => n,Playback(privacy-you-are-blacklisted)

      same => n,Playback(vm-goodbye)

      same => n,Hangup()

      Чтобы добавить номер в черный список, запустите database put blacklist <номер> 1 из интерфейса командной строки Asterisk.

      Создание приложения горячего стола с AstDB

      Благодаря встроенной базе данных Asterisk вы можете создавать всевозможные приложения без необходимости взаимодействия с чем-либо внешним. В приведенном ниже примере мы объединили все знания, которые обсудили в этой главе, в одном наборе внутренних номеров, которые вы можете включить в свой контекст LocalSets.

      Вы также можете найти версию с реляционной базой данных с использованием SQL и func_odbc в разделе «Веселимся с func_odbc: горячий стол» в Главе 16.

      Hot-desking (горячий рабочий стол) — довольно распространенная функция, которая вызывает повышенный интерес по мере развертывания систем Asterisk из-за присущей гибкости диалплана. Старые традиционные системы УАТС применяют добавочный номер к линии в системе или к самому устройству. С Asterisk у нас есть возможность применять логику диалплана и информацию, хранящуюся в локальной базе данных (или внешней), чтобы определить, где вызывать внутренний номер. Мы могли бы легко разработать систему, в которой добавочный номер ничего не делает, кроме звонка на сотовый телефон или комбинацию устройств (например, в пейджинговой системе или группе торговых агентов).

      В диалплане, предоставленном для этого примера горячего стола, мы разрешили людям регистрироваться на любом устройстве, набрав 71XX, где 1XX является внутренним номером в диапазоне от 100 до 199. Чтобы зарегистрировать внутренний номер вне устройства, пользователь просто набирает 7000 с устройства. Несмотря на то, что диалплан и логика стали более сложными, диалплан также учитывает другие внутренние номера, которые уже подключились к устройству, с которого кто-то хочет войти в систему, и автоматически регистрирует их в первую очередь. Кроме того, если ранее мы вошли с другого устройства и не выходили из системы до изменения местоположения, диалплан будет регистрировать внутренний номер с того устройства, прежде чем записывать его в новом месте.

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

      Чтобы понять логику диалплана, которую мы предоставили, полезно увидеть маршрут потока вызовов. Мы показали его на Рисунке 10-2.

      Рисунок 10-2. Прохождение вызова для приложения hot-desking

      Возможно, если два человека попытаются войти в один и тот же внутренний номер одновременно или если кто-то еще входит в устройство, которое ранее использовалось внутренним номером для hot-desking (и пыталось войти в систему одновременно с другим человеком), что база данных может выйти из синхронизации. Здесь не было блокировки, чтобы логика была максимально чистой и простой. Если существует большая вероятность того, что люди меняются местами и часто входят и выходят друг за друга, возможно, вы захотите изучить возможность добавления блокировки диалплана, которая может быть выполнена с использованием функций диалплана LOCK() и UNLOCK().

      [HotDesking]

      ; Контроль диапазона внутренних номеров для использования совпадения шаблонов

      ; Вход с 71XX выйдет из существующего внутреннего номера в этом месте

      ; и запишет это устройство с новым номером.

      ; Выход с 7000 для любого устройства.

      ;

      exten => 7000,1,Verbose(2,Attempting logoff from device ${CHANNEL(peername)})

      same => n,Set(PeerName=${CHANNEL(peername)})

      same => n,Set(CurrentExtension=${DB(HotDesk/${PeerName})})

      same => n,GoSubIf($[${EXISTS(${CurrentExtension})}]?

      subDeviceLogoff,1(${PeerName},${CurrentExtension}):loggedoff)

      same => n,GotoIf($[${GOSUB_RETVAL} = 0]?loggedoff)

      same => n,Playback(an-error-has-occurred)

      same => n,Hangup()

      same => n(loggedoff),Playback(silence/1&agent-loggedoff)

      same => n,Hangup()

      exten => _71XX,1,Verbose(2,Attempting to login device ${CHANNEL(peername)}

      to extension ${EXTEN:1})

      same => n,Set(NewPeerName=${CHANNEL(peername)})

      same => n,Set(NewExtension=${EXTEN:1})

      ; Проверьте зарегистрировался ли внутр.номер с этого устройства (NewPeerName)

      ; — Если уже существует внутр.номер (ExistingExtension)

      ; — получить имя существующего устройства

      ; — Если устройство не существует

      — (login) поскольку мы перезапишем существующий внутр. номер для этого устройства

      — Если существует имя устройства

      — logoff ExistingExtension + ExistingDevice

      — Переход к check_device ———————————-+

      ; — Если внутр.номер не существует |

      ; — Проверьте, зарегистрировано ли устройство для этого внутр. номера |

      ; (NewExtension) <—————————————————+

      ; — Если устройство присутствует

      ; — Получить существующий внутр.номер

      ; — Если внутр.номер присутствует

      ; — Выйти из устройства и внутр.номера

      ; — Авторизоваться

      ; — Если внутр номер не присутствует

      ; — Удалить устройство из AstDB

      ; — Авторизоваться

      ; — Если для NewExtension нет устройства

      ; — Авторизоваться

      ; Тесты:

      ; * Login 100 to 0000FFFF0001

      ; * Login 101 to 0000FFFF0001 (Результат: Только 101 залогинился)

      ; * Login 101 to 0000FFFF0002 (Результат: Только 101 залогинился в новом

      ; местоположении)

      ; * Login 100 to 0000FFFF0001 (Результат: Оба 100 и 101 залогинились)

      ; * Login 100 to 0000FFFF0002 (Результат: Только 100 залогинился на

      ; 0000FFFF0002 — смена местоположения)

      ; * Login 100 to 0000FFFF0001 (Результат: Только 100 залогинился)

      same => n,Set(ExistingExtension=${DB(HotDesk/${NewPeerName})})

      same => n,GotoIf($[${EXISTS(${ExistingExtension})}]?get_existing_device)

      same => n(check_device),NoOp()

      same => n,Set(ExistingDevice=${DB(HotDesk/${NewExtension})})

      same => n,GotoIf($[${EXISTS(${ExistingDevice})}]?get_existing_extension)

      same => n,NoOp(Nothing to logout)6

      same => n,Goto(login)

      same => n(get_existing_device),NoOp()

      same => n,Set(ExistingDevice=${DB(HotDesk/${ExistingExtension})})

      same => n,GotoIf($[${ISNULL(${ExistingDevice})}]?login)

      same => n,GoSub(subDeviceLogoff,1(${ExistingDevice},${ExistingExtension}))

      same => n,GotoIf($[${GOSUB_RETVAL} = 0]?check_device)

      same => n,Playback(silence/1&an-error-has-occurred)

      same => n,Hangup()

      same => n(get_existing_extension),NoOp()

      same => n,Set(ExistingExtension=${DB(HotDesk/${ExistingDevice})})

      same => n,GoSubIf($[${EXISTS(${ExistingExtension})}]?

      subDeviceLogoff,1(${ExistingDevice},${ExistingExtension}):remove_device)

      same => n,GotoIf($[${GOSUB_RETVAL} = 0]?loggedoff)

      same => n,Playback(silence/1&an-error-has-occurred)

      same => n,Hangup()

      same => n(remove_device),NoOp()

      same => n,Set(Result=${DB_DELETE(HotDesk/${ExistingDevice})})

      same => n,Goto(loggedoff)

      same => n(loggedoff),Verbose(2,Existing device and extensions have

      been logged off prior to login)7

      same => n(login),Verbose(2,Now logging in extension ${NewExtension}

      to device ${NewPeerName})8

      same => n,GoSub(subDeviceLogin,1(${NewPeerName},${NewExtension}))

      same => n,GotoIf($[${GOSUB_RETVAL} = 0]?login_ok)

      same => n,Playback(silence/1&an-error-has-occurred)

      same => n,Hangup()

      same => n(login_ok),Playback(silence/1&agent-loginok)

      same => n,Hangup()

      exten => subDeviceLogoff,1,NoOp()

      same => n,Set(LOCAL(PeerName)=${ARG1})

      same => n,Set(LOCAL(Extension)=${ARG2})

      same => n,ExecIf($[${ISNULL(${LOCAL(PeerName)})} |

      ${ISNULL(${LOCAL(Extension)})}]?Return(-1))

      same => n,Set(PeerNameResult=${DB_DELETE(HotDesk/${LOCAL(PeerName)})})

      same => n,Set(ExtensionResult=${DB_DELETE(HotDesk/${LOCAL(Extension)})})

      same => n,Return(0)

      exten => subDeviceLogin,1,NoOp()

      same => n,Set(LOCAL(PeerName)=${ARG1})

      same => n,Set(LOCAL(Extension)=${ARG2})

      same => n,ExecIf($[${ISNULL(${LOCAL(PeerName)})} | ${ISNULL(${LOCAL(Extension)})}]?Return(-1))

      same => n,Set(DB(HotDesk/${LOCAL(PeerName)})=${LOCAL(Extension)})

      same => n,Set(DB(HotDesk/${LOCAL(Extension)})=${LOCAL(PeerName)})

      same => n,Set(ReturnResult=${IF($[${DB_EXISTS(HotDesk/${LOCAL(PeerName)})}

      & ${DB_EXISTS(HotDesk/${LOCAL(Extension)})}]?0:-1)})

      same => n,Return(${ReturnResult})

      Полезные функции Asterisk

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

      Zapateller()

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

      Мы также будем использовать необязательный параметр nocallerid, чтобы тон воспроизводился только тогда, когда на входящем вызове нет CallerID абонента. Например, вы можете использовать Zapateller() в расширении s вашего контекста [incoming], например:

      [incoming]

      exten => s,1,NoOp()

      same => n,Zapateller(nocallerid)

      same => n,Playback(enter-ext-of-person)

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

      Еще одна удобная функция называется парковкой вызовов (call parking). Парковка вызовов позволяет поместить вызов на удержание на «парковочное место», чтобы его можно было забрать с другого внутреннего номера. Параметры для парковки вызовов (например, расширения для использования, количество мест и т.д.) управляются в файле конфигурации features.conf. Раздел [general] файла features.conf содержит четыре параметра, связанных с парковкой вызовов:

      parkext

      Это внутренний номер для парковки. Передайте вызов этому внутр.номеру, и система сообщит вам, в каком состоянии парковки находится звонок. По умолчанию внутр.номер парковки — 700.

      parkpos

      Этот параметр определяет количество парковочных мест. Например, установка 701-720 создает 20 парковочных позиций с номерами 701-720.

      context

      Это название контекста парковки. Чтобы иметь возможность парковки вызовов, вы должны включить этот контекст.

      parkingtime

      Если установлено, этот параметр определяет как долго (в секундах) может оставаться звонок на парковке. Если вызов не будет принят в течение указанного времени, номер, который припарковал вызов, будет вызван.

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

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

      [incoming]

      include => parkedcalls

      exten => 103,1,Dial(SIP/Bob,,tT)

      exten => 104,1,Dial(SIP/Charlie,,tT)

      Чтобы показать, как работает парковка вызова, предположим что Алиса звонит в систему и набирает номер 103, чтобы связаться с Бобом. Через некоторое время Боб передает вызов на добавочный номер 700, который сообщает ему, что звонок от Алисы был припаркован в позицию 701. Затем Боб набирает Чарли на добавочном номере 104 и сообщает ему, что Алиса находится на добавочном номере 701. Затем Чарли набирает номер 701 и начинает разговаривать с Алисой. Это простой и эффективный способ передачи абонентов между пользователями.

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

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

      Конференц-связь с MeetMe()

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

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

      Вы должны иметь res_timing_dahdi, выбранный в menuselect, чтобы иметь возможность компилировать app_meetme. Это означает, что вы уже установили DAHDI, даже без аппаратуры телефонии, поскольку MeetMe() использует компоненты DAHDI для выполнения микширования звука. Если вы не можете использовать res_timing_dahdi в своей системе, вы должны посмотреть на ConfBridge(), который может использовать разные источники синхронизации. См. «Конференц-связь с Conf-Bridge()».

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

      [rooms]

      conf => 600

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

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

      Вот и все! Когда вызывающие абоненты вводят внутренний номер 600, им будет предложено ввести пароль. Если они правильно вводят 54321, то будут добавлены в конференцию. Вы можете запустить core show application MeetMe из CLI Asterisk для списка всех параметров, поддерживаемых приложением MeetMe().

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

      exten => 601,1,NoOp()

      same => n,Playback(conf-thereare)

      same => n,MeetMeCount(600)

      same => n,Playback(conf-peopleinconf)

      Если вы передадите переменную в качестве второго аргумента в MeetMeCount(), подсчет присваивается переменной, и воспроизведение счетчика пропускается. Вы можете использовать это, чтобы ограничить количество участников, например:

      ; ограничить конференц-зал 10 участниками

      exten => 600,1,NoOp()

      same => n,MeetMeCount(600,CONFCOUNT)

      same => n,GotoIf($[${CONFCOUNT} <= 10]?meetme:conf_full,1)

      same => n(meetme),MeetMe(600,i,54321)

      exten => conf_full,1,Playback(conf-full)

      Разве Asterisk не забава?

      Конференц-связь с ConfBridge()

      Приложение ConfBridge() — это новая горячая точка. Это, по сути, замена приложения MeetMe() для Asterisk 10 и более поздних версий.

      Asterisk 1.8 также содержит ConfBridge(), но его набор функций значительно сокращен от того, что доступно в Asterisk 10 и более поздних версиях.

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

      • Звук высокой четкости, который может быть смешан с частотой дискретизации от 8 кГц до 96 кГц
      • Возможности видео, включая добавление динамически переключаемых видеопотоков на основе громкоговорителя
      • Динамически управляемая система меню для администраторов конференций и пользователей
      • Дополнительные параметры, доступные в файле конфигурации confbridge.conf

      Мы собираемся начать с базовой конфигурации, чтобы настроить ваш мост для конференции. ConfBridge() настроен через файл confbridge.conf, который содержит множество опций, включая расширенные функции для пользователей и мостов. Мы начнем с очень простой конфигурации, использующей настройки по умолчанию. Во-первых, давайте создадим разделы default_user и default_bridge в confbridge.conf:

      $ cat >> confbridge.conf

      [general]

      [default_user]

      type=user

      [default_bridge]

      type=bridge

      Ctrl+D

      После создания файла confbridge.conf нам нужно загрузить модуль app_confbridge.so. Это можно сделать на консоли Asterisk:

      $ asterisk -rx «module load app_confbridge.so»

      С загруженным модулем мы можем построить простой диалплан для доступа к нашему конференц-мосту:

      [ConferenceRooms]

      exten => 602,1,NoOp()

      same => n,ConfBridge(${EXTEN})

      [LocalSets]

      include => ConferenceRooms

      Мы могли бы также определить профили пользователя и моста в приложении ConfBridge(). По умолчанию приложение диалплана ConfBridge() будет использовать профили default_user и default_bridge, поэтому мы не определили их в нашем примере, но эквивалент будет выглядеть так:

      same => n,ConfBridge(${EXTEN},default_bridge,default_user)

      Конечно, теперь нам нужно перезагрузить наш диалплан:

      $ asterisk -r

      *CLI> dialplan reload

      Если вы сейчас наберете добавочный номер 602 со своего телефона, вы войдёте в конферен-мост:

      == Using SIP RTP CoS mark 5

      — Executing [602@LocalSets:1] NoOp(«SIP/0000FFFF0001-00000001», «») in new stack

      — Executing [602@LocalSets:2] ConfBridge(«SIP/0000FFFF0001-00000001», «602») in new stack

      — <SIP/0000FFFF0001-00000001> Playing ‘conf-onlyperson.gsm’ (language ‘en’)

      — <SIP/0000FFFF0001-00000001> Playing ‘confbridge-join.gsm’ (language ‘en’)

      Это только верхушка айсберга. У нас есть базовая конфигурация, но есть гораздо больше функций для настройки. Перейдите к разделу «Расширенные возможности конференц-связи» в Главе 11.

      Вывод

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

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

      2Для получения дополнительной информации о регулярных выражениях, возьмите копию окончательной ссылки, Jeffrey E. F. Friedl’s Mastering Regular Expressions (O’Reilly, 2006) или посетите http://www.regular-expressions.info.

      3Если вы не знаете, что ^ имеет отношение к регулярным выражениям, вы просто должны прочитать «Освоение регулярных выражений». Это изменит вашу жизнь!

      4В начале главы мы сказали, что используем приложение NoOp() в качестве первого приоритета, но в некоторых случаях имеет смысл нарушить эту традицию. В основном случае, когда расширение будет содержать только одну строку. Если после Macro() был бы добавлен дополнительный функционал, мы бы изменили первый приоритет, чтобы начать с NoOp(), как обычно.

      5Предыдущие версии Asterisk имели приложения, называемые DBput() и DBget(), которые использовались для установки и получения значений из AstDB. Если вы используете старую версию Asterisk, вы должны использовать эти приложения.

      6Не удалось выйти из системы (прим. переводчика)

      7Существующие устройства и внутренние номера были отключены до входа в систему (прим. переводчика)

      8Теперь залогинился внутр.номер ${NewExtension} на устройстве ${NewPeerName} (прим. переводчика)

      9В мире проприетарных УАТС этот тип функциональности очень дорог. Либо вы должны платить большие деньги за услугу набора номера, либо вам нужно добавить дорогостоящий мост для конференц-связи в свою собственную АТС.

      Остались вопросы?

      Я - Першин Артём, менеджер компании Voxlink. Хотите уточнить детали или готовы оставить заявку? Укажите номер телефона, я перезвоню в течение 3-х секунд.

      VoIP оборудование


      ближайшие курсы

      10 доводов в пользу Asterisk

      Распространяется бесплатно.

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

      Безопасен в использовании.

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

      Надежен в эксплуатации.

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

      Гибкий в настройке.

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

      Имеет огромный функционал.

      Во многом именно Asterisk показал какой должна быть современная телефонная станция. За многие годы развития функциональность Asterisk расширилась, а все основные возможности по-прежнему доступны бесплатно сразу после установки.

      Интегрируется с любыми системами.

      То, что Asterisk не умеет сам, он позволяет реализовать за счет интеграции. Это могут быть интеграции с коммерческими телефонными станциями, CRM, ERP системами, биллингом, сервисами колл-трекинга, колл-бэка и модулями статистики и аналитики.

      Позволяет телефонизировать офис за считанные часы.

      В нашей практике были проекты, реализованные за один рабочий день. Это значит, что утром к нам обращался клиент, а уже через несколько часов он пользовался новой IP-АТС. Безусловно, такая скорость редкость, ведь АТС – инструмент зарабатывания денег для многих компаний и спешка во внедрении не уместна. Но в случае острой необходимости Asterisk готов к быстрому старту.

      Отличная масштабируемость.

      Очень утомительно постоянно возвращаться к одному и тому же вопросу. Такое часто бывает в случае некачественного исполнения работ или выбора заведомо неподходящего бизнес-решения. С Asterisk точно не будет такой проблемы! Телефонная станция, построенная на Asterisk может быть масштабируема до немыслимых размеров. Главное – правильно подобрать оборудование.

      Повышает управляемость бизнеса.

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

      Снижает расходы на связь.

      Связь между внутренними абонентами IP-АТС бесплатна всегда, независимо от их географического расположения. Также к Asterisk можно подключить любых операторов телефонии, в том числе GSM сим-карты и настроить маршрутизацию вызовов по наиболее выгодному тарифу. Всё это позволяет экономить с первых минут пользования станцией.