asterisk расписание по дням и часам
Создание переадресаций по расписанию
Многие используют переадресации на мобильные или стационарные телефоны, возможно еще на автоответчик. Однако иногда требуется создать не абсолютную переадресацию, а переадресацию по времени. Например переадресация по пятницам. Для реализации используем запись расписания в базу данных, в которой будем указывать номер на котором необходимо включать переадресацию, время в которое она должна работать и номер на который […]
Многие используют переадресации на мобильные или стационарные телефоны, возможно еще на автоответчик. Однако иногда требуется создать не абсолютную переадресацию, а переадресацию по времени. Например переадресация по пятницам.
Для реализации используем запись расписания в базу данных, в которой будем указывать номер на котором необходимо включать переадресацию, время в которое она должна работать и номер на который переадресовывать вызов.
Пример заполнения даты и времени можно посмотреть в базе asterisk, таблице timegroups_details.
Соответственно необходимо создать свою таблицу с необходимыми полями:
Для создания расписания необходимо реализовать веб-интерфейс управления, для достпа к нему создадим виртуальный хост в конфигах apache.
В корне указанной директории создаем файл index.php, который будет стартовой страницей.
На странице создаем поля ввода номеров и интервала дней месяца, недели и времени, которые должны быть обязательно заполнены (required).
Предварительная форма ввода
Реализуем обработчик нажатия (для теста можно вывести все, что отправляется через POST запрос:
Сам обработчик дополним проверкой совпадений введенных значений и пустых значений:
Как видно из базовой таблицы freepbx нам необходимо указывать дату и время в определенном формате. Выведем в необходимом виде, чтобы проверить правильность заполнения:
Проверка вывода информации
Пишем и проверяем заполнение базы данных.
Помимо ввода данных, необходимо отображение текущего расписания, добавим его, а также возможность удалять записи. Добавлять будем отдельной страницей:
Вывод списка текущих переадресаций, добавим обработчик удаления из списка и конпку возврата на страницу добавления:
При вызове на указанный номер проверяется наличие записи для вызываемого номера в базе, при нахождении проверяется время и при совпадении выполняется переадресация, если же совпадения по какому либо пункту нет, вызов осуществляется в обычном режиме.
Соответственно сами функции:
Проверяем добавление и переадресации:
Проверка
Настройка голосового меню (IVR) на Asterisk
Интерактивное голосовое меню, или голосовое дерево, IVR меню или просто, IVR используется для возможности позвонившему подключиться к конкретному сотруднику компании или соединиться с линией поддержки определенной категории. Также, с помощью IVR можно получить необходимую информацию без ответа со стороны оператора, выбрав интересующую информацию. Другими словами, это система, взаимодействующая с абонентом и реагирующая на его команды.
Принцип настройки
IVR настраивается в плане набора Aterisk (dialplan) — в конфигурационном файле extensions.conf.
Сначала мы проигрываем запись с голосовым приветствием и предложением ввести номер в тональном режиме. В том же файле мы обрабатываем вводимую информацию, перекидывая абонента на другие диалплан, очередь или конкретного сотрудника.
Базовая настройка
Открываем конфигурационный файл:
[outcaling]
.
exten => 222,1,Answer()
same => n,Background(basic-pbx-ivr-main)
exten => _XXX,1,Dial(SIP/$
* в данном примере, при звонке на номер 222 нам проигрывается голосовая запись basic-pbx-ivr-main (из каталога /var/lib/asterisk/sounds/ ). Когда мы вводим трехзначный добавочный номер, нас соединяет Asterisk с этим номером. Предполагается, что наш основной диалплан называется outcaling.
Перезапускаем настройки диалплана:
Голосовое дерево
Теперь настроим возможность выбора отдела, с которым абонент захочет соединиться.
Приводим наш диалплан к следующему виду:
exten => 333,1,Answer()
exten => 333,2,Background(basic-pbx-ivr-main)
exten => 333,3,WaitExten(5)
exten => 1,1,Goto(managers,s,1)
exten => 2,1,Goto(support,s,1)
[managers]
exten => s,1,Ringing
same => n,Wait(1)
same => n,Background(/var/lib/asterisk/sounds/wav/managers)
same => n,Waitexten(5)
exten => 1,1,Dial(SIP/201)
exten => 2,1,Dial(SIP/202)
exten => 0,1,Goto(outcaling,333,1)
[support]
exten => s,1,Ringing
same => n,Wait(1)
same => n,Background(/var/lib/asterisk/sounds/wav/support)
same => n,Waitexten(5)
exten => 1,1,Dial(SIP/101)
exten => 2,1,Dial(SIP/102)
exten => 0,1,Goto(outcaling,333,1)
Перечитываем настройки диалплана:
Таймаут по времени ожидания и неправильные действия
Добавим обработку неправильно введенной цифры и превышение времени ожидания. В вышеуказанном примере просто происходит отбой звонка.
Данное поведения указывается в диалплане с помощью ключей t и i. Добавим их в наш конфиг:
exten => 333,1,Answer()
same => n,Background(basic-pbx-ivr-main)
same => n,WaitExten(5)
exten => 1,1,Goto(managers,s,1)
exten => 2,1,Goto(support,s,1)
exten => t,1,Dial(SIP/101)
exten => i,1,Background(invalid)
same => n,Goto(outcaling,333,3)
* где t,1,Dial(SIP/101) — позвонить по номеру 101, если будет превышен таймаут ожидания; i,1,Background(invalid) — сообщить о том, что введен неправильный номер.
Если не работает донабор
Если во время тестирования возникнут проблемы при донаборе внутреннего номера, необходимо проверить режим DTMF. В двух словах, это аналоговый сигнал для набора телефонного номера. Он может отправляться разными способами: inband, rfc2833 / rfc4733, info. При возникновении проблем, попробуйте в настройках клиента выставить rfc2833 или inband.
На стороне Asterisk также можно настроить sip-пир на определенный способ передачи DTMF:
* настройка может быть задана на глобальном уровне, уровне шаблона или для конкретного пира.
Примеры настроек
Рассмотрим некоторые дополнительные примеры настроек диалплана, которые будут полезны при конфигурировании автоинформатора.
1. Расписание
а) Мы можем вызывать различные контексты диалплана в зависимости от времени суток. Для этого вызываем приложение GotoIfTime:
* в данном примере при звонке с 08 до 20:00 в будние дни нас перекинет в контекст working_hours, иначе — в not_working_hours.
б) Чтобы во время нашей работы в рабочие часы мы могли проверить работу автоинформатора в нерабочее время, можно задать исключение для определенного номера:
* в данном примере мы добавили строку с проверкой исходящего номера. Если мы звоним с телефона 9062504869, то звонок всегда перекидывать в контекст not_working_hours.
в) Также мы можем менять направление диалплана в зависимости от времени:
exten => s,1,NoOp(Разные пути в зависимости от времени и дня)
same => n,GotoIfTime(10:00-13:59,mon-fri,*,*?true:false)
same => n(true),NoOp(TRUE)
same => n,Background(wav/true)
same => n,Hangup()
same => n(false),NoOp(FALSE)
same => n,Background(wav/false)
same => n,Hangup()
* в данном примере мы проиграем файл wav/true, если звонок будет совершен в будние дни с 10 до 14, и wav/false — в любое другое время.
Голосовой автоинформатор даты и времени, приятным женским голосом, русским языком, на базе asterisk? Легко
В преддверии выходных не чем себя занять, так как по регламенту не позволены грандиозные настройки? На старом, заброшенном сервере запылился asterisk? Абоненту нечем тестировать телефонную линию? Для тех, кому не с кем поговорить и для тех, кто потерялся во времени.
В этой публикации мы будем делать акцент на падежи порядковых числительных, так как произносимый текст должен быть связан и не резать слух. Попытаемся добиться следующего произношения:
Текущее время пятнадцать часов, двадцать одна минута, двадцать секунд. Сегодня среда, пятнадцатое октября.
Текущее время один час, тридцать пять минут, десять секунд. Сегодня четверг, шестнадцатое октября.
Для простоты и прозрачности внедрения мы не будем пользоваться AGI и попросим железную леди сообщать нам дату и время, по большому счету, поработав лишь с dialplan`ом и say.conf`ом. И если ваш asterisk до сих пор не говорит по-русски — не беда, этому мы его научим. Кому стало интересно, добро пожаловать под хабракат.
Который час?
Думаю, не стоит даже упоминать, что ваш сервер должен знать точное время, опираясь, например, на NTP.
Время на серверах имеет свойство рассинхронизироваться, если его не подводить. Пакет ntp потребуется в любом случае. Для себя я выбрал первый вариант с ntpd, так как другие мои сервера подводят время внутри сети.
Вариант №1. Добавляем сервера по вкусу, в зависимости от региона, в котором находится asterisk. Чем быстрее отклик от сервера времени, тем точнее оно выставится, при прочих равных. Я выберу российский пул.
Проверяем, что демон слушает нужные порты:
Если есть желание раздавать время своим серверам, не забудьте проверить файервол, порт UDP/123. Правило должно встать до завершающего REJECT`а. Не забудьте сохранить.
Вариант №2. Для разовой корректировки подойдет утилита ntpdate, входящая в тот же пакет ntp. Такой вариант годен, если раздача времени с сервера не планируется.
Можно добавить в cron:
Обучим леди русскому языку
Далее предположим, что asterisk установлен, но если это не так, то есть многоматериалов на эту тему. Так же будем считать, что первоначальная настройка хотя бы одного SIP-телефона или софтфона уже произведена.
Создадим каталог, если его нет
Загружаем русские звуки:
Копируем на свое место:
Посмотреть, какие фразы записаны можно в следующих файлах:
sip.conf
Укажем asterisk`у, использовать русский язык для SIP, добавив language=ru в [general]:
say.conf
Контекст [ru-base] в say.conf имеет завершающий (!) восклицательный знак в скобках означает, что это шаблон, который мы в дальнейшем включаем в [ru]
Попробуем разобрать одно правило. Первое, на что стоит обратить внимание, это символы X Z N. Они интерпретируются asterisk`ом как специальные и если эти литеры фигурируют в названии правила чтения, их следует взять в квадратные скобки, например mo[n]th.
Синтаксис достаточно прост и правило совпадает, если входные данные XX — две любые цифры. Проигрываем файл digits/mon-(XX-1), где (XX-1) это арифметическая операция. При X=02 (да, «переваривает» даже такие цифры, что нам очень поможет), 02-1=1, digits/mon-1: «Февраля».
Отдельно стоит упомянуть секунды. Во-первых, в записанных фразах есть только единственная запись: «секунд». Это значит, что на вход этой функции должны приходить округленные данные, например 0, 10, 20, и так далее. А во-вторых, по мнению автора, это облегчит восприятие полученной информации.
extensions.conf
В контексте [informer_100] стоит объяснить строчку, где мы в переменной FreezeEPOCH добавляем 15 секунд к unixtime. Сделано это для компенсации времени, потраченного на проигрывание файлов, предшествующих секундам.
$
$
$
$
Возможно, кто-то из читателей захочет самостоятельно сформировать правила, например, для прочтения рублей. Объема готовых примеров должно быть достаточно, чтобы справиться с этой задачей. А для проверки произношения можно воспользоваться нижеприведенным dialplan`ом.
Бонус: прослушать готовый результат в живую можно по телефону:
ИТ База знаний
Полезно
— Онлайн генератор устойчивых паролей
— Онлайн калькулятор подсетей
— Руководство администратора FreePBX на русском языке
— Руководство администратора Cisco UCM/CME на русском языке
— Руководство администратора по Linux/Unix
Навигация
Серверные решения
Телефония
FreePBX и Asterisk
Настройка программных телефонов
Корпоративные сети
Протоколы и стандарты
Временные группы FreePBX13
Маршрутизация вызова в зависимости от календаря
Временная группа (Time Group) – набор временных диапазонов, который можно применять к условиям проверки вызовов. Диапазон настраивается с учетом минут, часов, дней недели, месяца или года. Каждая временная группа может иметь множество временных диапазонов, каждый из которых будет проверяться. Как мы писали ранее, временная группа ассоциируется с временным условием (Time Condition), которое определяет направление для вызова при условии вхождения в указанный временной диапазон, или наоборот, выход за его рамки. Отметим, что временная группа так же может быть назначена на исходящий маршрут, чтобы лимитировать его использование по времени.
Базовый курс по Asterisk
Мы собрали концентрат всех must have знаний в одном месте, которые позволят тебе сделать шаг вперед на пути к экспертному владению Asterisk
Приступим к настройке. Для этого, в меню Applications, выберем Time Groups
Рассмотрим основные параметры настройки временной группы:
Например, указанная ниже настройка обеспечит настройку вызовов с понедельника по пятницу, с 10:00 до 19:00:
Праздничные дни
Чтобы настроить конкретный праздничный день, необходимо в поле Month Day start и Month Day finish выбрать один и тот же день. Например, вот настройка для праздника 8 марта:
По окончанию настройки, нажмите Save и затем Apply Config
Базовый курс по Asterisk
Мы собрали концентрат всех must have знаний в одном месте, которые позволят тебе сделать шаг вперед на пути к экспертному владению Asterisk
Погружение в диалплан
Для списка всех способов, по которым технология не улучшила
качество жизни, пожалуйста, нажмите три.
Выражения и манипуляции с переменными
По мере того, как мы начинаем погружаться в более глубокие аспекты диалплана, настало время познакомить вас с несколькими инструментами, которые значительно повысят мощь, которую вы можете использовать в своем диалплане. Эти конструкции добавляют невероятный интеллект к вашему диалплану, позволяя ему принимать решения на основе разных критериев, которые вы определяете. Наденьте свой мыслительный колпачок, и давайте начнем.
В этой главе мы используем лучшие практики, которые были разработаны на протяжении многих лет в создании диалплана. Основная из них — вы заметите, что все первые приоритеты начинаются с приложения NoOp(), что означает просто «Нет операции»; ничего функционального не произойдет. Другая заключается в том, что все следующие строки начинаются с same => n, что является ярлыком, который гласит: «Использовать то же расширение, которое было только что определено ранее». Кроме того, отступ составляет четыре пробела.
Базовые выражения
Выражения представляют собой комбинации переменных, операторов и значений, которые вы объединяете вместе для получения результата. Выражение может проверять значения, изменять строки или выполнять математические вычисления. Допустим, у нас есть переменная с именем COUNT. На простом русском языке два выражения, использующие эту переменную, могут быть «COUNT плюс 1» и «COUNT разделить на 2.». Каждое из этих выражений имеет конкретный результат или значение в зависимости от значения данной переменной.
В Asterisk выражения всегда начинаются со знака доллара и открытой квадратной скобки и заканчиваются закрывающей квадратной скобкой, как показано здесь:
Таким образом, мы могли бы написать два наших примера:
Когда Asterisk встречает выражение в диалплане, он заменяет всё выражение полученным значением. Важно отметить, что это происходит после подстановки переменной. Чтобы продемонстрировать, давайте рассмотрим следующий код: 1
same => n,Set(NEWCOUNT=$[$
Во втором приоритете мы присваиваем значение 3 переменной с именем COUNT.
В третьем приоритете задействовано только одно приложение Set() — но на самом деле происходят три вещи:
Попробуйте в своем диалплане.
Операторы
Когда вы создаете диалплан Asterisk, вы действительно пишете код на специализированном языке сценариев. Это означает, что диалплан Asterisk, как любой язык программирования, распознает символы, называемые операторами, которые позволяют вам манипулировать переменными. Давайте посмотрим на типы операторов, которые доступны в Asterisk:
Булевы (логические) операторы
Эти операторы оценивают «истину» утверждения. В вычислительных терминах это в основном относится к тому, является ли оператор чем-то или нет (отличным от нуля или нулевым, истинным или ложным, включенным или выключенным и т.д.). Булевы операторы:
Этот оператор (называемый оператором «или» или «пайп»(труба)) возвращает оценку expr1, если она истинна (не пустая строка, не нуль). В противном случае он возвращает оценку expr2.
Этот оператор (называемый «и») возвращает оценку expr1, если оба выражения истинны (т. е. ни одно выражение не оценивает пустую строку или ноль). В противном случае он возвращает ноль.
expr1 <=, >, >=,
Некоторая дополнительная информация об особенностях операторов регулярных выражений в Asterisk содержится на веб-сайте Walter Doekes.
Если шаблон не содержит подвыражения, возвращается количество совпадающих символов. Это будет 0, если совпадение не выполнено. Если шаблон содержит подэлемент — \(…\) — возвращается строка, соответствующая \1. Если совпадение не выполняется, возвращается пустая строка.
Этот оператор работает так же, как оператор : кроме того, что он не привязан к началу.
В версии Asterisk 1.0 синтаксический анализатор был довольно прост, поэтому потребовалось, чтобы вы поместили хотя бы один пробел между оператором и любыми другими значениями. Следовательно, следующее, возможно, не сработало, как ожидалось:
Оно присвоило переменной TEST строку 2+1 вместо значения 3. Чтобы исправить это, мы помещаем пробелы вокруг оператора, например:
exten => 234,1,Set(TEST=$[2 + 1])
Это больше не требуется в текущих версиях Asterisk, поскольку парсер выражений стал более упрощенным в этих сценариях. Однако, для удобства чтения, мы по-прежнему рекомендуем включать пробелы вокруг ваших операторов.
Чтобы присоединить текст в начало или конец переменной, просто поместите их вместе, например:
Функции диалплана
Функции диалплана позволяют добавлять больше мощи вашим выражениям; вы можете рассматривать их как интеллектуальные переменные. Функции диалплана позволяют вычислять длины строк, даты и время, контрольные суммы MD5 и т. д. все из выражения диалплана.
Вы увидите использование Playback(silence/1) во всех примерах этой главы. Мы делаем это, так как оно ответит на эту строку, если на нее еще не ответили, и воспроизведёт некоторую тишину на линии. Это позволяет другим приложениям, таким как SayNumber() воспроизводить аудио без пробелов.
Синтаксис
Функции диалплана имеют следующий базовый синтаксис:
Вы ссылаетесь на имя функции так же, как на имя переменной, но вы ссылаетесь на значение функции с добавлением знака доллара, открывающейся фигурной скобкой и закрывающейся фигурной скобкой:
Функции также могут инкапсулировать другие функции, например:
Как вы, наверное, уже выяснили, вы должны быть очень осторожны, чтобы убедиться, что у вас есть все круглые скобки и фигурные. В предыдущем примере мы обозначили открывающие круглые скобки и фигурные скобки цифрами и их соответствующими закрывающими аналогами с одинаковыми номерами.
Примеры функций диалплана
Функции часто используются вместе с приложением Set() для получения или установки значения переменной. В качестве простого примера рассмотрим функцию LEN(). Эта функция вычисляет длину строки аргумента. Давайте вычислим длину строки переменной и вернем ее вызывающей стороне:
Давайте рассмотрим еще один простой пример. Если бы мы хотели установить один из различных тайм-аутов канала, мы могли бы использовать функцию TIMEOUT(). Функция TIMEOUT() принимает один из трех аргументов: absolute, digit и response. Чтобы установить тайм-аут с помощью функции TIMEOUT(), мы могли бы использовать приложение Set(), например:
Полный список доступных функций можно найти, набрав core show functions в интерфейсе командной строки Asterisk.
Условное ветвление
Теперь, когда вы немного узнали о выражениях и функциях, пришло время их использовать. Используя выражения и функции, вы можете добавить еще более сложную логику вашему диалплану. Чтобы позволить вашему диалплану принимать решения, вы будете использовать условное ветвление. Давайте посмотрим поближе.
Приложение GotoIf()
Ключом к условному ветвлению является приложение GotoIf(). GotoIf() оценивает выражение и отправляет вызывающего абонента в конкретный пункт назначения основываясь на оценке выражения, выраженном в true или false.
GotoIf() использует специальный синтаксис, который часто называют условным синтаксисом:
Если выражение принимает значение true, вызывающий объект отправляется в destination1. Если выражение принимает значение false, вызывающий объект отправляется во второе назначение. Итак, что истинно и что ложно? Пустая строка и число 0 оцениваются как false. Все остальное оценивается как истинное.
Каждый из пунктов назначения может быть одним из следующих:
Любой из пунктов назначения может быть опущен, но не оба. Если пропущенный пункт назначения должен соблюдаться, Asterisk просто переходит к следующему приоритету в текущем внутреннем номере.
Давайте используем GotoIf() в примере:
same => n,GotoIf($[$
same => n(weasels),Playback(weasels-eaten-phonesys)
same => n(iguanas),Playback(office-iguanas)
Вы заметите, что мы использовали приложение Hangup() после каждого использования Playback(). Это делается для того, что когда мы переходим на метку weasels, вызов останавливается до того, как выполнение переходит к звуковому файлу office-iguanas. Становится все более распространенным видеть расширения, разбитые на несколько компонентов (защищенных друг от друга командой Hangup()), каждая из которых совершает отдельную последовательность шагов, выполняемых после GotoIf().
Предоставление только ложного условного пути
Если бы мы захотели, мы могли бы выполнить предыдущий пример следующим образом:
same => n,GotoIf($[$
; weasels, но это всё равно
same => n(iguanas),Playback(office-iguanas)
Мы действительно не рекомендуем это делать, потому что это трудно читать, но вы увидите диалпланы, подобные этому, поэтому хорошо знать, что этот синтаксис абсолютно корректный.
Как правило, когда у вас есть этот тип разбивки, в котором вы хотите избежать перехода Asterisk на следующий приоритет после того, как вы выполнили этот переход, скорее всего, лучше перейти на отдельные расширения вместо ярлыков приоритета. Во всяком случае, это делает его более понятным при чтении. Мы могли бы переписать предыдущий диалплан следующим образом:
same => n,GotoIf($[$
same => n,Playback(weasels-eaten-phonesys) ; это НЕ метка
; это другое расширение
Изменив значение, присвоенное TEST во второй строке, вы сможете заставить сервер Asterisk воспроизводить другое приветствие.
Давайте посмотрим на другой пример условного разветвления. На этот раз мы будем использовать Goto() и GotoIf() для отсчета с 10, а затем повесим трубку:
same => n(start),GotoIf($[$
Давайте проанализируем этот пример. Во втором приоритете мы устанавливаем переменную COUNT равным 10. Затем мы проверяем, будет ли COUNT больше 0. Если это так, переходим к следующему приоритету. (Не забывайте, что если мы опускаем пункт назначения в приложении GotoIf(), управление переходит к следующему приоритету.) Оттуда мы проговариваем число, вычитаем 1 из COUNT и возвращаемся к началу приоритета. Если значение COUNT меньше или равно 0, управление переходит на приоритет goodbye, и кладется трубка.
Кавычки и префиксы переменных в условных ветвлениях
Сейчас самое подходящее время уделить минутку, чтобы посмотреть на какие-то небрежные вещи с условными ветлениями. В Asterisk недопустимо иметь нулевое значение по обе стороны оператора сравнения. Давайте рассмотрим примеры, которые приведут к ошибке:
Любой из наших примеров может вызвать предупреждение:
WARNING[28400][C-000000eb]: ast_expr2.fl:470 ast_yyerror: ast_yyerror():
Маловероятно (если у вас нет опечатки), что вы целенаправленно реализуете что-то вроде наших примеров. Тем не менее, когда вы выполняете математику или сравнение с неустановленной переменной канала, это фактически то, что вы делаете.
Примеры, которые мы использовали чтобы показать вам, как работает условное ветвление, не являются недопустимыми. Поскольку мы сначала инициализировали переменную и можем ясно видеть, что переменная канала, которую мы используем в нашем сравнении, была установлена, мы в безопасности. Но что, если ты не всегда так уверен?
В Asterisk строки не обязательно должны быть заключены в двойные или одинарные кавычки, как во многих языках программирования. На самом деле, если вы используете двойную или одинарную кавычку, это будет буквенной конструкцией в строке. Если мы посмотрим на следующие две строки Set() …
… тогда значение, возвращаемое нашим сравнением в NoOp() не будет равно 1 (значения совпадают или true), возвращаемое значение будет 0 (значения не совпадают или false).
Мы можем использовать это в наших интересах при выполнении сравнений путем заключения переменных канала в одинарные или двойные кавычки. Делая это, мы убеждаемся, что даже когда переменная канала не может быть установлена, наше сравнение действительно.
В следующем примере мы получим ошибку:
same => n,GotoIf($[$
Однако мы можем обойти это, обернув то, что мы сравниваем в кавычки. Тот же пример, но сделан действительным:
same => n,GotoIf($[«$
Мы можем сделать такой же пример сети безопасности при сравнении, которую выполняют численные сравнения, добавив префикс для наших чисел в виде нуля. Поскольку 01 совпадает с 1 при сравнении чисел, наш потенциально недействительный пример:
same => n,GotoIf($[$
Можно сделать более безопасным:
same => n,GotoIf($[0$
Если вы привыкли распознавать эти ситуации и использовать методы обертывания и префикса, которые мы изложили, вы напишете гораздо более безопасные диалпланы.
Классический пример условного ветвления ласково известен как логика анти-подруги. Если номер CallerID входящего вызова соответствует номеру телефона бывшей подруги получателя, Asterisk дает другое сообщение, чем обычно, для любого другого вызывающего абонента. В то время как этот пример несколько простой и примитивный, он хорош изучения условного ветвления в диалплане Asterisk.
В этом примере используется функция CALLERID, которая позволяет нам получать информацию CallerID во входящем вызове. Предположим ради этого примера, что номер телефона жертвы — 888-555-1212:
same => n,GotoIf($[$
В приоритете 1 мы вызываем приложение GotoIf(). Оно сообщает Asterisk, о переходе к метке приоритета reject, если номер CallerID соответствует 8885551212, а в противном случае — присвоить метку приоритета allow (мы могли бы просто опустить имя метки, в результате чего GotoIf() провалится). Если номер CallerID совпадает, управление вызовом переходит к метке приоритета reject, которое воспроизводит недоброжелательное сообщение нежелательному абоненту. В противном случае вызов пытается набрать получателя на канале DAHDI/4.
Временное условное ветвление с GotoIfTime()
Другой способ использования условного разветвления в вашем диалплане — это приложение GotoIfTime(). В то время как GotoIf() оценивает выражение, чтобы решить, что делать, GotoIfTime() смотрит на текущее системное время и использует его, чтобы решить, следовать или нет другой ветке в диалплане.
Наиболее очевидное использование этого приложения — дать вашим абонентам другое приветствие до и после обычных рабочих часов.
Синтаксис приложения GotoIfTime() выглядит следующим образом:
Короче говоря, 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.
Если вы хотите сопоставить все возможные значения для любого из этих аргументов, просто поместите аргумент * для него.
Аргумент метки может быть любым из следующих:
Теперь, когда мы рассмотрели синтаксис, давайте рассмотрим несколько примеров. Следующий пример будет соответствовать 9:00 до 17:59 вечера, с понедельника по пятницу, в любой день месяца, в любой месяц в году:
Если вызывающий звонит в течение этих часов, вызов будет отправлен на первый приоритет расширения s в контексте с именем open. Если вызов выполняется за пределами указанного времени, он будет отправлен на следующий приоритет текущего расширения. Это позволяет вам легко разветвляться несколько раз, как показано в следующем примере (обратите внимание, что вы всегда должны ставить ваши наиболее конкретные совпадения времени перед наименее конкретными):
; Если это любой час дня, любой день недели,
; в течение четвертого дня месяца, в июле, мы закрыты
; В рабочее время отправлять вызовы в контекст open
; В противном случае мы закрыты
Если вы столкнетесь с ситуацией, когда задаете вопрос: «Но я указал 17:58, и сейчас 17:59. Почему он все еще делает то же самое?», Следует отметить, что детализация приложения GotoIfTime() — это двухминутный период. Таким образом, если вы укажете 18:00 в качестве конечного времени периода, система будет продолжать выполнять тот же путь до 18:01:59.
Макросы
Макросы — очень полезная конструкция, разработанная, чтобы избежать повторения в диалплане. Они также помогают вносить изменения в диалплан.
Хотя Macro() кажется универсальной подпрограммой диалплана, у нее есть проблема переполнения стека, что означает, что вы не должны пытаться вложить вызовы Macro() более пяти уровней. Если вы планируете использовать макросы в макросах (и вызывать сложные функции внутри них), вы можете столкнуться с проблемами стабильности. Вы узнаете, что у вас есть проблема только с одним тестовым вызовом, поэтому, если ваш диалплан тестовый, всё хорошо — продолжаем. Мы также рекомендуем вам взглянуть на приложения GoSub() и Return() (см. «GoSub»), так как многие макрофункции могут быть реализованы без фактического использования Macro().
Начиная с Asterisk 11 приложение Macro() устарело в пользу приложения GoSub(). Однако знание Macro() полезно, так как почти любая существующая система, которую вы поддерживаете или изменяете, скорее всего, содержит хотя бы одно использование Macro().
Чтобы проиллюстрировать этот момент, давайте снова рассмотрим наш образец диалплана. Если вы помните изменения, внесенные нами для голосовой почты, мы закончили с внутр.номером Джона:
Теперь представьте, что у вас есть сто пользователей в вашей системе Asterisk, настройка внутренних номеров потребует много копирования и вставки. Затем представьте, что вам нужно внести изменения в способ работы ваших внутренних номеров. Это потребует много редактирования, и вы почти наверняка будете иметь ошибки.
Вместо этого вы можете определить макрос, содержащий список шагов, которые необходимо предпринять, а затем все внутренние номера телефонов относящиеся к этому макросу. Все, что вам нужно изменить — это макрос, и все в диалплане, что ссылается на этот макрос, также изменится.
Если вы знакомы с компьютерным программированием, вы поймете, что макросы похожи на подпрограммы во многих современных языках программирования. Если вы не знакомы с компьютерным программированием, не волнуйтесь — мы проведем вас через создание макроса.
Лучший способ оценить макросы — увидеть их в действии, поэтому давайте двигаться дальше.
Определение макроса
Давайте возьмем логику диалплана, которую мы использовали для настройки голосовой почты для Джона и превратим ее в макрос. Затем мы будем использовать макрос, чтобы дать Джону и Джейн (и остальным их сотрудникам) ту же функциональность.
Определения макросов очень похожи на контексты. (Фактически, вы можете утверждать, что это действительно небольшие, ограниченные контексты.) Вы определяете макрос, помещая команду macro- и имя своего макроса в квадратные скобки, например:
Имена макросов должны начинаться с macro-. Это отличает их от обычных контекстов. Команды внутри макроса построены почти идентично всем остальным в диалплане; единственным ограничивающим фактором является то, что макросы используют только расширение s. Давайте добавим нашу логику голосовой почты в макрос, изменив расширение на s по мере продвижения:
Это начало, но оно не идеально, поскольку это все еще характерно для Джона и его номера почтового ящика. Чтобы создать макрос, который будет работать не только для Джона, но и для всех его сотрудников, мы воспользуемся еще одним свойством макросов: аргументы. Но сначала посмотрим, как мы вызываем макросы в нашем диалплане.
Вызов макроса из диалплана
Чтобы использовать макрос в нашем диалплане, мы используем приложение Macro(). Это приложение вызывает указанный макрос и передает ему любые аргументы. Например, чтобы вызвать наш макрос голосовой почты из диалплана, мы можем сделать следующее:
Приложение Macro() также определяет несколько специальных переменных для нашего использования. Они включают:
Исходный контекст, в котором был вызван макрос.
Исходное расширение, в котором был вызван макрос.
Исходный приоритет, в котором был вызван макрос.
Использование аргументов в макросе
Теперь, когда наш макрос сделан, мы можем использовать его в нашем диалплане. Вот как мы можем позвонить в наш макрос, чтобы предоставить голосовую почту Джону, Джейн и Джеку: 4
С 50 или более пользователями этот диалплан по-прежнему будет выглядеть аккуратно и организованно; мы просто будем иметь одну строку для каждого пользователя, ссылаясь на макрос, который может быть более сложным. Мы могли бы даже иметь несколько разных макросов для различных типов пользователей, таких как executives (руководители), courtesy_phones (справочная служба), call_center_agents (агенты колл-центра), analog_sets (аналоговые комплекты), sales_department (отдел продаж) и т.д.
Более продвинутая версия макроса может выглядеть примерно так:
Так как теперь мы знаем, как использовать функции диалплана, вот еще один способ контроля того, какая голосовая подсказка (недоступности или занятости) воспроизводится вызывающему. В следующем примере мы будем использовать функцию диалплана IF():
Этот макрос зависит от хорошего побочного эффекта приложения Dial(): когда вы используете приложение Dial(), оно устанавливает переменную DIALSTATUS чтобы указать, был ли вызов успешным или нет. В этом случае мы обрабатываем NOANSWER и BUSY и обрабатываем все остальные результаты как NOANSWER.
GoSub
Приложение диалплана GoSub() аналогично приложению Macro(), поскольку цель состоит в том, чтобы разрешить вам вызывать блок функциональности диалплана, передавать информацию этому блоку и возвращать его (необязательно с возвращаемым значением), GoSub() работает иначе, чем Macro(), хотя в нем нет требований к пространству стека, поэтому он эффективно монтируется. По сути, GoSub() действует как Goto() с памятью о том, откуда он появился.
В этом разделе мы перейдем к тому, что мы узнали в «Макросах». При необходимости вы можете просмотреть этот раздел: он объясняет, почему мы можем использовать подпрограмму и цель, которую мы пытаемся выполнить.
Определение подпрограмм
В отличие от Macro(), при использовании GoSub() в диалплане особых требований к именам не существует. Фактически, вы можете использовать GoSub() в том же контексте и расширении, если хотите. В большинстве случаев, однако, GoSub() используется аналогично Macro(), поэтому определение нового контекста является общим. При создании контекста нам нравится добавлять имя с sub, поэтому мы знаем, что контекст обычно вызывается из приложения GoSub() (конечно, нет необходимости, чтобы вы это делали, но это похоже на разумное соглашение).
Вот простой пример того, как мы можем определить подпрограмму в Asterisk:
Давайте возьмем наш пример из «Макросов» и преобразуем его в подпрограмму. Вот как он определяется для использования с Macro():
Если мы собираемся преобразовать это для использования в подпрограмму, то может выглядеть так:
Не так много изменений, не так ли? Все, что мы изменили в этом примере — это имя контекста, от [macro-voicemail] до [subVoicemail] и расширения от s до start (поскольку нет требования, чтобы расширение было вызвано чем-то конкретным, в отличие от Macro(), который ожидает что расширение будет s).
Вызов подпрограмм из диалплана
Теперь, когда мы обновили наш макрос голосовой почты, который вызывается как подпрограмма, давайте посмотрим, как мы его вызываем, используя GoSub():
Вы заметите, что мы разместили набор открывающих и закрывающих круглых скобок в нашем приложении GoSub(). Это заполнители для любых аргументов, которые мы можем передать подпрограмме и, хотя они не обязательно будут существовать, это стиль программирования, который мы предпочитаем использовать.
Затем давайте посмотрим, как мы можем передавать аргументы нашей подпрограмме, чтобы сделать ее более общей.
Использование аргументов в подпрограмме
Возможность использования аргументов является одной из основных особенностей использования Macro() или GoSub(), поскольку позволяет абстрагироваться от кода, который в противном случае был бы дублирован в вашем диалплане. Без необходимости дублировать код, мы можем лучше управлять им, и можем легко добавить функциональность большому числу пользователей, изменив одно местоположение. Вам рекомендуется помещать код в эту форму всякий раз, когда он дублируется.
Прежде чем мы начнем использовать нашу подпрограмму, нам нужно обновить ее для принятия аргументов, чтобы она была общей для использования несколькими пользователями:
same => n(unavail),VoiceMail($ @default,u)
same => n(busy),VoiceMail($ @default,b)
Теперь, когда мы обновили нашу подпрограмму, мы можем использовать ее для нескольких внутренних номеров:
Опять же, наш диалплан хорош и опрятен. Мы могли бы даже изменить нашу подпрограмму всего на три строки:
Одно из отличий между GoSub() и Macro() заключается в том, что если мы оставим нашу подпрограмму подобным образом, мы никогда не вернемся в исходный диалплан. В этом конкретном примере это не проблема, так как после того, как голосовая почта оставлена, мы ожидаем, что вызывающий абонент повесит трубку в любом случае. В ситуациях, когда мы хотим сделать что-то ещё после выполнения подпрограммы, нам нужно реализовать приложение Return().
Возврат из подпрограммы
В отличие от Macro() приложение GoSub() не возвращается автоматически после его выполнения. Чтобы вернуться туда, откуда мы пришли, нам нужно использовать приложение Return(). Теперь, когда мы знаем, как вызвать подпрограмму и передать аргументы, можем посмотреть пример, где нам может понадобиться вернуться из подпрограммы.
Используя наш предыдущий пример, мы можем разбить часть набора и часть голосовой почты на отдельные подпрограммы:
Вызов этих подпрограмм может выглядеть так:
same => n,Return($ )
В этой версии мы сделали только одно изменение: Return() на Return($
same => n,Set(VoicemailMessage=$
same => n,GoSub(subVoicemail,start,1($
Прежде чем двигаться дальше, вы можете вернуться назад и просмотреть «Макросы» и «GoSub». Мы дали вам массу возможностей чтобы переварить здесь, но эти концепции сэкономят вам много работы и времени как только вы начинаете строить свой диалплан.
Локальные (Local) каналы
Локальные каналы (Local) — это метод выполнения других областей диалплана из приложения Dial() (в отличие от отправки канала вызова). Они могут показаться немного странной концепцией когда вы впервые начнете их использовать, но поверьте нам, когда вы узнаете что они являются славной и чрезвычайно полезной функцией, вы почти наверняка захотите использовать, когда начнете писать расширенные диалпланы. В качестве примера можно привести лучший пример использования локальных каналов. Предположим, что у нас есть ситуация, когда нам нужно позвонить нескольким людям, но нужно предоставить задержки разной длины, прежде чем набирать каждого из них. Использование локальных каналов — единственное решение проблемы.
С помощью приложения Dial() вы можете звонить по нескольким конечным точкам, но все три канала будут звонить одновременно и в один и тот же промежуток времени. Набор нескольких каналов одновременно выполняется следующим образом:
same => n,Verbose(2,Dialing multiple locations simultaneously)
В этом примере набирают трёх адресатов в течение 30 секунд. Если ни один из них не отвечает на вызов в течение 30 секунд, диалплан продолжит на следующей строке и звонок завершится.
Однако предположим, что мы хотим ввести некоторые задержки и прекратить звонки в разное время. Использование локальных каналов дает нам независимый контроль над каждым каналом, который мы хотим набрать, поэтому мы можем вводить задержки и управлять периодом времени, для которого каждый канал звонит независимо. Мы покажем вам, как это делается в диалплане, как внутри таблицы, которая визуально отображает задержки, так и все вместе в боксе как это было сделано для других частей диалплана. Мы будем строить диалплан в соответствии с началом и остановками времени, описанными на Рисунке 10-1.
Рисунок 10-1. Набор с задержкой по времени в локальных каналах
Сначала нам нужно вызвать три локальных канала, которые будут выполнять разные части диалплана. Мы делаем это с помощью приложения Dial(), например:
exten => 107,1,Verbose(2,Dialing multiple locations with time delay)
; *** Это всё должно быть на одной линии
Теперь наше приложение 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 |
Если мы соединим все это, мы получим следующий диалплан:
exten => 107,1,Verbose(2,Dialing multiple locations with time delay)
; *** Это все должно быть на одной линии
exten => channel_1,1,Verbose(2,Dialing the first channel)
exten => channel_2,1,Verbose(2,Dialing the second channel with a delay)
exten => channel_3,1,Verbose(2,Dialing the third channel with a delay)
Вы увидите локальные каналы используемые в этой книге для различных целей. Локальные каналы позволяют выполнять логику диалплана из приложений, которые обычно ожидают прямого подключения к каналу. Например, вы можете назначить локальный канал в качестве участника очереди и запускать всякую причудливую логику диалплана всякий раз, когда очередь пытается доставить вызов агенту. Об этом мы поговорим в разделе «Использование локальных каналов» в Главе 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
Извлечение данных из AstDB
Чтобы получить значение из базы данных Asterisk и назначить его переменной, мы снова используем приложение Set(). Получим значение count (опять же, из семейства тестов), назначте его переменной COUNT, а затем произнесите значение вызывающему:
Удаление данных из AstDB
Существует два способа удаления данных из базы данных Asterisk. Чтобы удалить ключ, вы можете использовать приложение DB_DELETE(). Он принимает путь к ключу в качестве своих аргументов, например:
; удаляет ключ и возвращает его значение за один шаг
Вы также можете удалить целое семейство ключей с помощью приложения DBdeltree(). Приложение DBdeltree() принимает один аргумент: имя семейства ключей для удаления. Чтобы удалить все семейство test, выполните следующие действия:
Использование AstDB в диалплане
Существует множество способов использования базы данных Asterisk в диалплане. Чтобы представить AstDB, мы рассмотрим два простых примера. Первый пример — простой подсчет, показывающий, что база данных Asterisk является постоянной (что означает, что она выживает при перезагрузке системы). Во втором примере мы будем использовать функцию BLACKLIST() для оценки того, находится ли номер в черном списке и должен быть заблокирован.
Чтобы начать пример подсчета, давайте сначала выберем число (значение ключа count) из базы данных и назначим его переменной с именем COUNT. Если ключ не существует, DB() вернет NULL (нет значения). Поэтому мы можем использовать функцию ISNULL(), чтобы проверить, было ли возвращено значение. Если нет, мы инициализируем AstDB с помощью приложения Set(), где установим значение в базе данных равным 1. Следующий приоритет вернет нам 1. Это произойдет в первый раз, когда мы набираем этот внутренний номер:
Затем мы скажем текущее значение COUNT, а затем увеличим его:
same => n,Playback(silence/1)
same => n,SayNumber($
same => n,Set(COUNT=$[$
Теперь, когда мы увеличили значение COUNT, вернем новое значение в базу данных. Помните, что сохранение значения для существующего ключа перезаписывает предыдущее значение:
same => n,Set(DB(test/count)=$
Наконец, мы вернемся к первому приоритету. Таким образом, приложение будет продолжать считать:
Попробуйте этот пример. Послушайте, чтобы он подсчитал какое-то время, а затем повесьте трубку. Когда вы снова набеёте этот добавочный номер, он должен продолжать отсчет с того места, где был остановлен. Значение, хранящееся в базе данных, будет постоянным даже при перезапуске Asterisk.
В следующем примере мы создадим логику диалплана вокруг функции BLACKLIST(), которая проверяет, существует ли текущий callerID номера в черном списке. (Черный список — это просто семейство, называемое blacklist в AstDB.) Если BLACKLIST() находит номер в черном списке, она возвращает значение 1; в противном случае вернет 0. Мы можем использовать эти значения в сочетании с GotoIf(), чтобы контролировать, будет ли вызов выполнять приложение Dial():
Чтобы добавить номер в черный список, запустите 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().
; Контроль диапазона внутренних номеров для использования совпадения шаблонов
; Вход с 71XX выйдет из существующего внутреннего номера в этом месте
; и запишет это устройство с новым номером.
; Выход с 7000 для любого устройства.