Настройка VOIP шлюза Audiocodes MP-104FXO с MikoPBX

Голосовой шлюз Audiocodes MP-104FXO предназначен для подключения к внутригородским АТС PSTN и имеет 4 порта FXO для подключения городских аналоговых линий.

При переходе с «чистого» Asterisk на MikoPBX (версия 2024.1.114) стал вопрос сопряжения.

Успешного сабжа для подключения данного шлюза найти не получилось, пришлось изобретать свой велосипед.

Осторожно! Много букаф…

Ранее шлюз был настроен и работал с Asterisk для организации входящей и исходящей связи.

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

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

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

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

MikoPbx имеет три типа регистрации внешних провайдеров - «входящая», «исходящая», «по IP адресу без логина и пароля». Для регистрации шлюза на PBX вполне логично должна использоваться «входящая» регистрация. Регистрация производится по логину-паролю, но при этом логин задать невозможно, он жестко связан с именем провайдера. К счастью MP-104FXO поддерживает длинные логины. Т.е. внешние линии под видом экстеншенов SIP-TRUNK-3BF3D958 регистрируются на Мико. Но здесь возникает проблема первого названного ограничения — на телефонном аппарате с с цифрами от 0 до 9 абсолютно невозможно набрать номер провайдера SIP-TRUNK-3BF3D958.

Данный вопрос решается созданием «приложения диалплана», скажем с номером 8001.

В приложении указываем название «Исходящий вызов через CO1», задаем номер для вызова приложения - «8001», тип кода «Диалплан Asterisk» и вставляем следующий код:

1,NoOp(Outgoing from CO1)
n,Dial(PJSIP/SIP-TRUNK-3BF3D958,40,Tt)
n,Hangup()
n,return

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

Выход в «город» через 9

Для выхода в «город» по набору «9», необходимо создать «приложение диалплана» с номером 9.

В приложении указываем название «Выход на PSTN через 9», задаем номер для вызова приложения - «9», тип кода «Диалплан Asterisk» и вставляем следующий код:

1,NoOp(Outgoing to PSTN from key 9 )
; SIP-TRUNK-3BF3D958 CO1
; SIP-TRUNK-3F209A80 CO2
; SIP-TRUNK-18652C21 CO3

n,GotoIF($["${FROM_PEER}"="103"}"="10"]?from103)
n,GotoIF($["${FROM_PEER}"="105"]?from105)
n,GotoIF($["${FROM_PEER}"="106"]?from106)
n,Playback(cannot-complete-as-dialed)
n,Goto(exit)

n(from103),Noop()
n,Playback(vm-enter-num-to-call)
n,Dial(PJSIP/SIP-TRUNK-3F209A80,40,Tt)
n,Dial(PJSIP/SIP-TRUNK-18652C21,40,Tt)
n,Dial(PJSIP/SIP-TRUNK-3BF3D958,40,Tt)
n,Playback(all-circuits-busy-now)
n,Goto(exit)

n(from105),Noop()
n,Playback(vm-enter-num-to-call)
n,Dial(PJSIP/SIP-TRUNK-3BF3D958,40,Tt)
n,Dial(PJSIP/SIP-TRUNK-3F209A80,40,Tt)
n,Playback(all-circuits-busy-now)
n,Goto(exit)

n(from106),Noop()
n,Playback(vm-enter-num-to-call)
n,Dial(PJSIP/SIP-TRUNK-3F209A80,40,Tt)
n,Playback(all-circuits-busy-now)
n,Goto(exit)

n(exit),Busy()
n,Hangup()

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

Прямой набор городского номера

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

Для этого необходимо создать «приложение диалплана» с номером 5XXXX. То есть если мы к примеру имеем внутригородские номера начинающиеся на 5 и имеющие 5 цифр в номере.

В приложении указываем название «Прямой вызов городских номеров», задаем номер для вызова приложения - «5XXXX», тип кода «Диалплан Asterisk» и вставляем следующий код:

1,NoOp(Outgoing to PSTN)
; SIP-TRUNK-3BF3D958 CO1
; SIP-TRUNK-3F209A80 CO2
; SIP-TRUNK-18652C21 CO3

n,GotoIF($["${FROM_PEER}"="103"]?from103)
n,GotoIF($["${FROM_PEER}"="105"]?from105)
n,GotoIF($["${FROM_PEER}"="106"]?from106)
n,Playback(cannot-complete-as-dialed)
n,Goto(exit)
n(from103),Noop()

n,Dial(PJSIP/SIP-TRUNK-3F209A80,40,TtD(W${cleanNumber}))
n,Dial(PJSIP/SIP-TRUNK-18652C21,40,TtD(W${cleanNumber}))
n,Dial(PJSIP/SIP-TRUNK-3BF3D958,40,TtD(W${cleanNumber}))
n,Playback(all-circuits-busy-now)
n,Goto(exit)

n(from105),Noop()
n,Dial(PJSIP/SIP-TRUNK-3BF3D958,40,TtD(W${cleanNumber}))
n,Dial(PJSIP/SIP-TRUNK-3F209A80,40,TtD(W${cleanNumber}))
n,Playback(all-circuits-busy-now)
n,Goto(exit)

n(from106),Noop()
n,Dial(PJSIP/SIP-TRUNK-3F209A80,40,TtD(W${cleanNumber}))
n,Playback(all-circuits-busy-now)
n,Goto(exit)
n(exit),Busy()

n,Hangup()
n,return

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

Входящие вызовы

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

При создании одного провайдера для 1-й линии CO1 в конфигурации появляется контекст обработки входящих вызовов для данного провайдера

[SIP-TRUNK-3BF3D958-incoming]
exten => _X!,1,NoOp(--- Incoming call ---)
…
exten => SIP-TRUNK-3BF3D958,1,NoOp(--- Incoming call ---)
…

И вроде как звонки на 1-ю линию поступают на Мико и даже начинается обработка очереди вызова. Но как выясняется довольно странно. То есть вызов отрабатывается по цепочке «Входящий маршрут по умолчанию» указанный в целом в настройках «Входящей маршрутизации».

Это естественно, так как в шлюзе мы можем указать только цифровой номер внутреннего телефона, причем не очень большой длины (15 цифр). То есть указать шлюзу номер экстеншена SIP-TRUNK-3BF3D958 мы не можем. Соответственно вызов попадая в контекст SIP-TRUNK-3BF3D958-incoming идет по цепочке _X!. Эту проблему решаем, как принято в Мико, следующим костылем. В кастомизации системных файлов, в файл extensions.conf добавляем в конец файла код:

[SIP-TRUNK-3BF3D958-incoming][+]
exten => _X.,1,NoOp(--- Fix CID Incoming call ---)
same => n,Set(__contextID=${CONTEXT})
same => n,Set(CALLER=${CALLERID(num)})
same => n,Set(CALLERID(num)=CO1 X-XX-XX)
same => n,Goto(${CALLER},1)
same => n,Hangup()

Теперь, при поступлении вызова в данный транк с любым номером экстеншена (возможно конечно указать и конкретный номер), вызов будет перенаправлен в экстеншн SIP-TRUNK-3BF3D958 и дальнейшая его обработка пройдет по всей цепочке заданной во входящей маршрутизации.

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

И здесь проявляется следующий баг. При изменениях в настройках провайдеров, использовании типа «Исходящая регистрация», так как шлюз один, то в поле IP адрес и заполнен для всех провайдеров один IP адрес. И после пристального изучения имени непонятного контекста становится понятно природа его возникновения и его «странного» имени. То есть контекст вида 19216844505060-incoming это не что иное как IP адрес шлюза и номер порта без разделителей. А баг заключается в том, что провайдеры с одинаковым ip адресом и номером порта по какой-то причине «заворачиваются» в общий контекст. При этом, следующий баг, из которого так же вытекает предыдущий, заключается в том, что при типе провайдера «Входящая регистрация» где отсутствуют в настройке поля IP адрес и номер порта, эти поля все равно участвуют в формировании конфигурации контекстов. И даже если ip будет пустым, то конструкция провайдеров с типов «Входящая регистрация» «обернется» в контекст 5060-incoming.

Это явно существенные баги неверно выстроенного кода. Но без правки основного кода это можно решить используя следующий баг системы, так сказать используя багу за фитчу. Баг заключается в том, что в поле IP адрес можно ввести все что угодно, а не только IP адрес — отсутствует жесткая маска поля ввода. Таким образом, сменив тип провайдера на «Исходящая регистрация», в поле IP адрес для первой линии и первого провайдера указываем «1», для второй линии «2» и так далее. После меняем тип на «Входящая регистрация» и сохраняем настройки. После чего, для каждого провайдера мы получаем в конфигурации отдельный контекст.

[SIP-TRUNK-3BF3D958-incoming]
…
[SIP-TRUNK-3F209A80-incoming]
…
[SIP-TRUNK-18652C21-incoming]
…

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

Но появляется еще одно маленькое но. Криво работающее распределение нерабочего времени. Т.е. если задано глобальное правило, то оно действует в соответствии с заданными маршрутами обработки. Но стоит сделать правило «Действует для выбранных маршрутов» все ломается. Решается это исправлением значения переменной диалплана contextID на значение текущего контекста, для этого в правке контекста для провайдера добавлена строка

same => n,Set(__contextID=${CONTEXT})

По непонятным причинам (хотя уже ясно) в данную переменную попадает строка «15060-incoming».

Еще она правка позволяет получить более осязаемые записи в «Истории вызовов». После произведенных модификаций, в истории мы видим записи вида SIP-TRUNK-3F209A80 => SIP-TRUNK-3F209A80. Загадочно и не наглядно. Немного исправить ситуацию можно добавив строку в «правку»

same => n,Set(CALLERID(num)=CO1 X-XX-XX)

Где X-XX-XX номер городской линии с которой пришел вызов.

Теперь мы имеем в истории записи вида CO1 X-XX-XX => SIP-TRUNK-3F209A80. Уже чуть лучше.