Оповещение о занятости второй линии в asterisk

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

Углубленный онлайн-курс по MikroTik

Научиться настраивать MikroTik с нуля или систематизировать уже имеющиеся знания можно на углубленном онлайн-курcе по администрированию MikroTik. Автор курcа – сертифицированный тренер MikroTik Дмитрий Скоромнов. Более 40 лабораторных работ по которым дается обратная связь. В три раза больше информации, чем в MTCNA.

Параметр call-limit в asterisk

У меня есть очень популярная статья про настройку asterisk. Там я немного затронул тему активных линий у абонента, но совершил небольшую ошибку. Я упомянул параметр пира call-limit и порекомендовал поставить значение 1 тому, кто не будет использовать вторую линию. И указал, что в этом случае, если абонент уже разговаривает, звонящий услышит в трубке длинные гудки и поймет, что занято. Но это не так.

При значении call-limit=1, если линия уже занята, то звонящий просто получит сброс звонка, и не поймет в чем дело. А в логе сервера будет ошибка:

ERROR chan_sip.c: Call to peer '101' rejected due to usage limit of 1

Если не настроить обработку этого события, то будет вообще не понятно, в каком состоянии абонент и почему идет сброс. Так что для обычных пиров все же стоит ставить call-limit хотя бы 2, чтобы была возможность работать со второй линией и не сбрасывать звонки. Если двух линий уже не хватает, то надо организовывать очередь. Делать лимит линий выше двух для обычного абонента считаю, что нет смысла.

Сегодня я предложу более элегантное решение при настройке нескольких линий.

Для отладки и тестирования работы voip я рекомендую сервис Zadarma. Плюс его в том, что после регистрации вы получите настройки пира для внутренней сети оператора. И внутри этой сети вы можете бесплатно звонить. Например, я одного пира регистрирую на sip клиенте смартфона и с него звоню на второй аккаунт, пир от которого настроен в астериске. Таким образом эмулирую внешний звонок. Удобно отлаживать различные конфигурации звонков, не требуя платного подключения.

Оповещение о занятости второй линии

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

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

Итак, записываем указанную фразу в wav формате и загружаем на сервер. Перекодируем в формат, понятный для астериск:

# sox abonent_zanyat.wav -r 8000 -c 1 -s abonent_zanyat1.wav resample -ql

Кладем новый файл в директорию /var/lib/asterisk/sounds. После этого открываем диалплан и редактируем контекст внутренних звонков. Рассмотрю самый простой случай, когда изначально было вот так:

exten => _XXX,1,Dial(SIP/${EXTEN})

Редактируем и приводим к такому виду:

exten => _XXX,1,Noop(HINT STATUS - ${EXTENSION_STATE(${EXTEN})})
exten => _XXX,n,ExecIf($["${EXTENSION_STATE(${EXTEN})}" = "INUSE"]?Playback(abonent_zanyat1))
exten => _XXX,n,ExecIf($["${EXTENSION_STATE(${EXTEN})}" = "INUSE"]?Dial(SIP/${EXTEN},120,Ttm))
exten => _XXX,n,ExecIf($["${EXTENSION_STATE(${EXTEN})}" = "RINGINUSE"]?Playback(abonent_zanyat1))
exten => _XXX,n,ExecIf($["${EXTENSION_STATE(${EXTEN})}" = "RINGINUSE"]?Dial(SIP/${EXTEN},120,Ttm))
exten => _XXX,n,ExecIf($["${EXTENSION_STATE(${EXTEN})}" = "BUSY"]?Playback(abonent_zanyat1))
exten => _XXX,n,ExecIf($["${EXTENSION_STATE(${EXTEN})}" = "BUSY"]?Dial(SIP/${EXTEN},120,Ttm))
exten => _XXX,n,Dial(SIP/${EXTEN},30,Tt)

Разберем, что здесь происходит:

  1. Noop(HINT STATUS - ${EXTENSION_STATE(${EXTEN})}) - чисто отладочная информация, которую потом можно убрать. Просто выводим статус экстеншена в лог. Эта информация помогла мне решить одну проблему, о которой расскажу позже.
  2. ExecIf($["${EXTENSION_STATE(${EXTEN})}" = "INUSE"]?Playback(abonent_zanyat1)) - если статут экстеншена INUSE, проговариваем записанную фразу.
  3. ExecIf($["${EXTENSION_STATE(${EXTEN})}" = "INUSE"]?Dial(SIP/${EXTEN},120,Ttm)) - после проговоренной фразы звонящий будет 120 секунд ожидать ответ и слушать музыку. Далее идет обработка других статусов - RINGINUSE, BUSY. Ниже приведу описание всех возможных статусов.
  4. Dial(SIP/${EXTEN},30,Tt) - если он так и не ответил, набираем ему еще раз, слышим уже обычные гудки в течении 30 секунд.

Насчет пункта 4 есть сомнения. Я просто не знаю, что лучше делать после того, как человек 2 минуты провисел на линии и ему не ответили. Можно направить звонок на секретаря, можно сразу сбросить. Можно еще раз ему проговорить, что абонент занять и опять повесить на ожидание и так по кругу. Я оставил стандартное правило, как было изначально. Мне почему-то кажется, что не так много людей будут висеть 2 минуты на трубке.

В общем, это не принципиально, сами придумайте, как поступать в конкретном случае. Теперь про EXTENSION_STATE. Функция может принимать следующие значения:

  • UNKNOWN
  • NOT_INUSE
  • INUSE
  • BUSY
  • INVALID
  • UNAVAILABLE
  • RINGING
  • RINGINUSE
  • HOLDINUSE
  • ONHOLD

Я не знаю точного описания всех значений, не нашел, но по названиям примерно понятно. Подобрал опытным путем те, что меня интересуют. Если пользователь разговаривает, значение BUSY, если он сам набирает кому-то, то INUSE, если ему идет звонок, но он еще не ответил, то RINGINUSE. Остальные значения в рамках указанной задачи меня не интересуют. Можете добавить сами обработку других ситуаций. Я раньше использовал для этого функцию DIALSTATUS, примерно так:

exten => _XXX,n,Goto(num-${DIALSTATUS},1)
exten => num-NOANSWER,1,Wait(2)
exten => num-NOANSWER,n,Playback(noanswer)
exten => num-CHANUNAVAIL,1,Wait(2)
exten => num-CHANUNAVAIL,n,Playback(vm-isunavail)

Изначально я собирался решать вопрос с уведомлением о занятости пира через dialstatus, но эта функция не подходит. Опытным путем узнал, что если первая линия занята, новый звонок идет на вторую, статус линии не будет busy. Эта функция не подходит.

И еще одно важное замечание. Во время тестирования, пока я не добавил явно в свойства пира call-limit=2, я не мог определить статус пира через EXTENSION_STATE. Не помню, какое значение получал при звонке, но оно точно было не BUSY во время разговора по первой линии. После того, как явно указал call-limit, все заработало как надо.

Во время настройки на разных версиях Asterisk замечал, что иногда не работает функция EXTENSION_STATE. Возвращает значение UNKNOWN. В этом случае использовал другую функцию - DEVICE_STATE. Все остальные настройки те же. Не вдавался в подробности, почему это так. Сходу не мог понять, в чем причина.

Заключение

Не понравилась статья и хочешь научить меня администрировать? Пожалуйста, я люблю учиться. Комментарии в твоем распоряжении. Расскажи, как сделать правильно!

Буду рад любым подсказкам и замечаниям к статье. Сам последнее время практически не занимаюсь телефонией. По привычке поддерживаю некоторые серверы, но отдалился от темы. Не слежу за ней и не делаю новых установок.

Другие материалы по asterisk:

Онлайн-курс по устройству компьютерных сетей.

На углубленном курсе "Архитектура современных компьютерных сетей" вы с нуля научитесь работать с Wireshark и «под микроскопом» изучите работу сетевых протоколов. На протяжении курса надо будет выполнить более пятидесяти лабораторных работ в Wireshark.

Помогла статья? Подписывайся на telegram канал автора

Анонсы всех статей, плюс много другой полезной и интересной информации, которая не попадает на сайт.

Автор Zerox

Владимир, системный администратор, автор сайта. Люблю настраивать сервера, изучать что-то новое, делиться знаниями, писать интересные и полезные статьи. Открыт к диалогу и сотрудничеству. Если вам интересно узнать обо мне побольше, то можете послушать интервью. Запись на моем канале - https://t.me/srv_admin/425 или на сайте в контактах.

17 комментариев

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

  2. Николай

    Подскажите пожалуйста как сделать чтобы эти правила не применялись например к экстеншенам 222 и 333.

    • Очень просто. Правила применяются по маске. В данном случае _XXX, это правило охватывает все трехзначные номера. Вы выше этого правила можете поставить любое другое с маской 222 или 333. Диалплан читается по порядку, поэтому как только правило для номера 222 будет найдено, все остальные правила к нему не будут действовать. То есть примерно так должно получиться:

      exten => 222,1,Dial(SIP/${EXTEN},30,Tt)
      exten => 333,1,Dial(SIP/${EXTEN},30,Tt)

      exten => _XXX,1,Noop(HINT STATUS - ${EXTENSION_STATE(${EXTEN})})
      exten => _XXX,n,ExecIf($["${EXTENSION_STATE(${EXTEN})}" = "INUSE"]?Playback(abonent_zanyat1))
      exten => _XXX,n,ExecIf($["${EXTENSION_STATE(${EXTEN})}" = "INUSE"]?Dial(SIP/${EXTEN},120,Ttm))
      exten => _XXX,n,ExecIf($["${EXTENSION_STATE(${EXTEN})}" = "RINGINUSE"]?Playback(abonent_zanyat1))
      exten => _XXX,n,ExecIf($["${EXTENSION_STATE(${EXTEN})}" = "RINGINUSE"]?Dial(SIP/${EXTEN},120,Ttm))
      exten => _XXX,n,ExecIf($["${EXTENSION_STATE(${EXTEN})}" = "BUSY"]?Playback(abonent_zanyat1))
      exten => _XXX,n,ExecIf($["${EXTENSION_STATE(${EXTEN})}" = "BUSY"]?Dial(SIP/${EXTEN},120,Ttm))
      exten => _XXX,n,Dial(SIP/${EXTEN},30,Tt)

      • Николай

        Так и думал с самого начала, но почему-то не работает. Экстеншен 222 переадресовывает на группу обзвона. Но с такими правилами сразу после набора идёт сброс.

  3. Спасибо вам! Но как сделать чтобы эти настройки не влияли на группы и конференции? После активации правил с _XXX на конференции и группы не дозвониться.

  4. капец конечн, по проще чё нельзя всё ето дело замутить, переключатель: разрешить concurrent call да/нет и сё, нет бл простыня из этих экстеншонов

  5. Сергей

    Написал такую байду.
    работает на А18PBX15 с SIP и PJSIP
    [from-internal-custom]
    include => macro-dialout-one-predial-hook

    [macro-dialout-one-predial-hook]
    exten => s,1,Set(numberLT=$[${LEN(${DEXTEN})}+4])
    exten => s,n,Set(numberLS=${LEN(${DSTRING})})
    exten => s,n,ExecIf([${numberLS} > ${numberLT}]?Set(fooor=${DSTRING:0:$[${numberLT}+2]}):Set(fooor=${DSTRING}))
    exten => s,n,Set(foost=${DEVICE_STATE(${fooor}})
    exten => s,n,Noop(HINT ${fooor} - ${foost})
    exten => s,n,ExecIf($["${foost}"="INUSE"]?Playback(custom/user-on-call))
    exten => s,n,ExecIf($["${foost}"="INUSE"]?Set(D_OPTIONS=Tt))
    exten => s,n,ExecIf($["${foost}"="RINGINUSE"]?Playback(custom/user-on-call))
    exten => s,n,ExecIf($["${foost}"="RINGINUSE"]?Set(D_OPTIONS=Tt))

    LOG:
    NoOp("SIP/206-00000c3d", "HINT SIP/203 - NOT_INUSE") in new stack
    NoOp("SIP/247-00000c3f", "HINT SIP/203 - INUSE") in new stack
    NoOp("SIP/203-00000c41", "HINT SIP/247 - NOT_INUSE") in new stack

    • Владимир

      Попробовал, не работает.
      A18 PBX15
      Вообще звонки не проходят, ошибка Extention not found in internal-custom

  6. Сергей

    Все здорово, вот только после обновления на версию freepbx 15, "поломалась" настройка оповещения. В логах упорно показывает что playback отыгрывается, но в трубке идут стандартные гудки. Уже не знаю куда ткнуться.

    • Сергей

      VERBOSE[28334][C-00000067] app_dial.c: Called SIP/333
      VERBOSE[28334][C-00000067] app_dial.c: SIP/333-000000ca is ringing
      VERBOSE[28334][C-00000067] app_dial.c: SIP/333-000000ca answered SIP/334-000000c9
      VERBOSE[28335][C-00000067] bridge_channel.c: Channel SIP/333-000000ca joined 'simple_bridge' basic-bridge
      pbx.c: Executing [333@from-internal:1] NoOp("SIP/211-000000cd", "HINT STATUS - INUSE") in new stack
      128357[2021-03-04 12:18:10] VERBOSE[28352][C-00000069] pbx.c: Executing [333@from-internal:2] ExecIf("SIP/211-000000cd", "0?Playback(/var/lib/asterisk/sounds/ru/custom/busytest)") in new stack
      128358[2021-03-04 12:18:10] VERBOSE[28352][C-00000069] pbx.c: Executing [333@from-internal:3] ExecIf("SIP/211-000000cd", "0?Dial(SIP/333,120,Ttm)") in new stack
      128359[2021-03-04 12:18:10] VERBOSE[28352][C-00000069] pbx.c: Executing [333@from-internal:4] ExecIf("SIP/211-000000cd", "0?Playback(/var/lib/asterisk/sounds/ru/custom/busytest)") in new stack
      128360[2021-03-04 12:18:10] VERBOSE[28352][C-00000069] pbx.c: Executing [333@from-internal:5] ExecIf("SIP/211-000000cd", "0?Dial(SIP/333,120,Ttm)") in new stack
      128361[2021-03-04 12:18:10] VERBOSE[28352][C-00000069] pbx.c: Executing [333@from-internal:6] ExecIf("SIP/211-000000cd", "0?Playback(/var/lib/asterisk/sounds/ru/custom/busytest)") in new stack
      128362[2021-03-04 12:18:10] VERBOSE[28352][C-00000069] pbx.c: Executing [333@from-internal:7] ExecIf("SIP/211-000000cd", "0?Dial(SIP/333,120,Ttm)") in new stack
      128363[2021-03-04 12:18:10] VERBOSE[28352][C-00000069] pbx.c: Executing [333@from-internal:8] Dial("SIP/211-000000cd", "SIP/333,30,Tt") in new stack
      128364[2021-03-04 12:18:10] VERBOSE[28352][C-00000069] app_dial.c: Called SIP/333
      128365[2021-03-04 12:18:10] VERBOSE[28352][C-00000069] app_dial.c: SIP/333-000000ce is ringing
      128374[2021-03-04 12:18:39] VERBOSE[28352][C-00000069] app_dial.c: SIP/333-000000ce redirecting info has changed, passing it to SIP/211-000000cd
      128375[2021-03-04 12:18:39] VERBOSE[28352][C-00000069] app_dial.c: SIP/333-000000ce is busy
      128376[2021-03-04 12:18:39] VERBOSE[28352][C-00000069] pbx.c: Executing [s@macro-hangupcall:4] NoOp("SIP/211-000000cd", "SIP/333-000000ce montior file= ") in new stack
      128402[2021-03-04 12:18:46] VERBOSE[28334][C-00000067] pbx.c: Spawn extension (from-internal, 333, 8) exited non-zero on 'SIP/334-000000c9'
      128403[2021-03-04 12:18:46] VERBOSE[28335][C-00000067] bridge_channel.c: Channel SIP/333-000000ca left 'simple_bridge' basic-bridge
      128404[2021-03-04 12:18:46] VERBOSE[28334][C-00000067] pbx.c: Executing [s@macro-hangupcall:4] NoOp("SIP/334-000000c9", "SIP/333-000000ca montior file= ") in new stack

    • Сергей

      Тёзка.
      Попробуй то что выше.
      На самом деле то что в сети ходит одно и тоже, оно реально не работает.

  7. Виталий

    Здравствуйте. Хотел у Вас спросить, у меня только FreePBX.... Сделал следующее - добавил класс музыки moh1 и через extensions_custom.conf реализовал следующее:

    [from-internal-custom]
    include => macro-dialout-one-predial-hook
    [macro-dialout-one-predial-hook]
    exten => s,1,Noop(HINT STATUS - ${EXTENSION_STATE(${DEXTEN})})
    exten => s,n,ExecIf($["${EXTENSION_STATE(${DEXTEN})}" = "INUSE"]?Playback(D_OPTIONS=${D_OPTIONS}m(moh1)))
    exten => s,n,ExecIf($["${EXTENSION_STATE(${DEXTEN})}" = "INUSE"]?Set(D_OPTIONS=${D_OPTIONS}m(moh1)))
    exten => s,n,ExecIf($["${EXTENSION_STATE(${DEXTEN})}" = "RINGINUSE"]?Playback(D_OPTIONS=${D_OPTIONS}m(moh1)))
    exten => s,n,ExecIf($["${EXTENSION_STATE(${DEXTEN})}" = "RINGINUSE"]?Set(D_OPTIONS=${D_OPTIONS}m(moh1))
    exten => s,n,ExecIf($["${EXTENSION_STATE(${DEXTEN})}" = "BUSY"]?Playback(D_OPTIONS=${D_OPTIONS}m(moh1)))
    exten => s,n,ExecIf($["${EXTENSION_STATE(${DEXTEN})}" = "BUSY"]?Set(D_OPTIONS=${D_OPTIONS}m(moh1))

    Все работает, все норм. Но, звонки теряются, если человек не взял трубку :( Как можно реализовать так, что бы после проигрывания уведомления - вызов уходил в очередь или группу? Дело в том, что настройки, которые в follow me - не отрабатывают. С оной стороны, это хорошо - когда абонент занят, не переадресовывается вызов на сотовый. Ну а с другой - человек подождал на линии таймаут номера и все, происходит отбой. Не сталкивались с таким моментом?

  8. В общем не работает у меня. Почему не могу понять
    При использовании exten => _XXX,1,Noop(HINT STATUS - ${EXTENSION_STATE(${EXTEN})})

    - Executing [172@call-out-tradehall:1] NoOp("SIP/101-00000038", "HINT STATUS - UNKNOWN") in new stack

    При использовании DEVICE_STATE

    Executing [172@call-out-tradehall:1] NoOp("SIP/101-0000003a", "HINT STATUS - INVALID") in new stack

    • Поменял на такую конструкцию

      exten => _XXX,1,Noop(HINT STATUS - ${DEVICE_STATE(SIP/${EXTEN})})

      Заработало -_-

  9. Константин

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

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Нажимая кнопку "Отправить комментарий" Я даю согласие на обработку персональных данных.
Используешь Telegram? Подпишись на канал автора →
This is default text for notification bar