Внутренние номера 22.04.2011

В прошлой статье мы создали простейший диалплан для набора внутренних четырехзначных номеров.

Если мы набираем существующий номер, то все в порядке. Но что будет, если мы наберем несуществующий номер?

Verbosity is at least 3
  == Using SIP RTP CoS mark 5
[2011-04-18 10:00:58] WARNING[13884]: acl.c:698 ast_ouraddrfor: Cannot connect
[2011-04-18 10:00:58] WARNING[13884]: chan_sip.c:3115 __sip_xmit: sip_xmit of 0x7019890 (len 800) to 0.0.4.210:5060 returned -1: Invalid argument
    -- Called 1234
[2011-04-18 10:00:58] WARNING[6329]: chan_sip.c:3115 __sip_xmit: sip_xmit of 0x719890 (len 800) to 0.0.4.210:5060 returned -1: Invalid argument
sip_xmit of 0x7019890 (len 800) to 0.0.4.210:5060 returned -1: Invalid argument
[2011-04-18 10:01:30] WARNING[6329]: chan_sip.c:3386 retrans_pkt: Retransmission timeout reached on transmission 609790bc4721a3724c8315b3564ac778@10.62.20.20:50                                                                             60 for seqno 102 (Critical Request) -- See doc/sip-retransmit.txt. Packet timed out after 31999ms with no response
[2011-04-18 10:01:30] WARNING[6329]: chan_sip.c:3415 retrans_pkt: Hanging up cal l 609790bc4721a3724c8315b3564ac778@10.62.20.20:5060 - no reply to our critical packet (see doc/sip-retransmit.txt).
  == Everyone is busy/congested at this time (1:0/0/1)
    -- Executing [1234@internal_phones:4] Hangup("SIP/1260-00000016", "") in new stack
  == Spawn extension (internal_phones, 1234, 4) exited non-zero on 'SIP/1260-00000016'

В итоге телефон получает отбой, но во-первых, это происходит не сразу, во-вторых – это некрасиво.

Запись голосового сообщения

Моя светлая идея в следующем: записать файл с голосовым сообщением номера не существует и давать отбой.

Немного о формате файла.

Разумеется, первым делом рука тянется записать mp3. Именно этого делать и не следует применительно к астериску вообще. Дело в том, что на воспроизведение mp3 требуются мощности процессора на декодирование. И, если с одним файлом он справится, то если у вас идет, скажем, 100 звонков и на каждом воспроизводится аудио – уже нагрузка будет существенной. При этом, mp3 будет сначала декодироваться, а потом снова сжиматься и пересылаться в поток абоненту.

В то же время, суммарное время всех сообщений на вашей АТС вряд ли превысит один час, что соответствует 600Мб (ладно, пусть 1Гб) – согласитесь, это немного с точки зрения стоимости хранения данных на жестком диске по сравнению со стоимостью процессора.

Итак, к делу.

После записи файлов я поместил их в папку /var/lib/asterisk/sounds/rittal. Мои файлы называются number_is_wrong.wav (неверный номер), number_is_busy (номер занят) и number_is_not_connected (телефон не подключен).

Внимание! Астериск воспринимает WAV файл только в формате PCM 8000 16bit mono.

Попробуем для начала его проиграть. Для этого расширим диалплан, добавив в него номер 1234, воспроизводящий наше сообщение.

[internal_phones]
exten => 1234,1,Answer() ; отвечаем
; воспроизводим файл
exten => 1234,n,Playback(rittal/number_is_wrong)
exten => 1234,n,Hangup()

От себя добавлю, что имя файла указывается без расширения. Файл может быть указан как по абсолютному пути, так и относительно /var/lib/asterisk/sounds. Так же один и тот же файл может быть указан с несколькими расширениями, совпадающими с названиями кодеков – тогда астериск будет выбирать наиболее подходящий формат.

Не существует, недоступен, занят

На мой взгляд, у локального телефона может быть три состояния:

  • Номер не существует, т.е. такой хост не будет показан в show users.
  • Номер существует, но сам телефон не подключен. Выявить это можно, например, через sip show peers. В дальнейшем, при такой ситуации мы будем включать переадресацию.
  • Номер занят. С этим сложнее всего, поэтому, рассмотрим в конце.

Для первых двух случаев нам поможет функция SIPPEER, которая возвращает пустую строку для несуществующих номеров и UNKNOWN для номеров, которые прописаны, но телефоны физически не подключены.

Наш диалплан приобретает следующий вид:

[internal_phones]
exten => _XXXX,1,NoOp(internal phones ${EXTEN})
; Если номер не существует - переходим к метке
; номер не существует number_exists
exten => _XXXX,n,GotoIf($["${SIPPEER(${EXTEN},status)}" = ""]?number_exists)

; Если номер существует, но недоступен переходим к метке
; номер не существует number_not_connected
exten => _XXXX,n,GotoIf($["${SIPPEER(${EXTEN},status):0:2}" = "UN"]?number_not_connected)

; наберем номер через SIP, используя переменную
; EXTEN
exten => _XXXX,n,Dial(SIP/${EXTEN})
; в конце - повесим трубку
exten => _XXXX,n,Hangup()

; обработка ошибок
; неверный номер
exten => _XXXX,n(number_exists),Playback(rittal/number_is_wrong)
exten => _XXXX,n(number_exists),Hangup()

; номер есть, но аппарат не подключен
exten => _XXXX,n(number_not_connected),Playback(rittal/number_is_not_connected)
exten => _XXXX,n(number_not_connected),Hangup()

Теперь перейдем к вопросу занято.

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

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

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

Изначально сам собой напрашивался вариант 2, но его поддержка уходит с каждой новой версией астериска. Таким образом – пойдем по пути 1.

Если вам понадобятся очереди звонков, придется все же воспользоваться вариантом 1. Его я описал здесь. А здесь - самая финальная версия, переработанная и дополненная.

Мы реализуем отбой на занято через группы. В сети есть много примеров разной степени работоспособности, я сделал свой.

Наша задача – если происходит звонок, заблокировать для дальнейших звонков и входящий, и исходящий номера.

Итак, после проверки на недоступность и перед Dial вставляем код:

; теперь проверим - нет ли у нашей цели уже статуса "занято"?
; по входящим
exten => _XXXX,n,GotoIF($[${GROUP_COUNT(${EXTEN}@busy_in)} > 0 ]?number_is_busy)
; и исходящим
exten => _XXXX,n,GotoIF($[${GROUP_COUNT(${EXTEN}@busy_out)} > 0 ]?number_is_busy)

; устанавливаем статус "занято" для набираемого
exten => _XXXX,n,Set(GROUP(busy_in)=${EXTEN})

; и для набирающего.
; здесь есть один нюанс
; если набирающий - внешний, то можно
; его заблокировать в итоге.
; поэтому, с этой частью надо быть осторожным
exten => _XXXX,n,Set(GROUP(busy_out)=${CALLERID(NUM)})

; номер занят
exten => _XXXX,n(number_is_busy),Playback(rittal/number_is_busy)
exten => _XXXX,n(number_is_busy),Hangup()

В дальнейшем мы расширим правила, чтобы корректно отрабатывать эти события – переадресовывать звонок, писать на VoiceMail и тд.