Как сохранить CDR(dst) при смене контекста (при обработке результатов звонка) / How to keep CDR(dst) when context change
Введение - описание проблемы
Существует достаточно распространенный подход к обработке звонков в диалплане: делается вызов (Dial), после чего (например, в случае неудачного вызова) производится обработка результатов звонка: анализируются переменные ${DIALSTATUS}, ${HANGUPCAUSE} и т.д.. При этом очень удобно выполнять обработку звонка в отдельном контексте, отправляя с помощью goto на определенный экстен. Вот пример такой обработки:
В этой обработке всё просто и понятно: в контексте outgoing-call делается звонок на номер. В случае, если звонок успешен, то hangup делается автоматически самой системой asterisk (и на этом обработка сама завершается). В случае неуспешного звонка происходит переход на следующий приоритет и выполняется команда Goto, перекидывающая обработку в контекст mikhed-hangupcause-context, в котором выполняется что-то вроде switch-case: в зависимости от значения переменной ${HANGUPCAUSE} происходит переход на определенный экстен, и воспроизводится соответствующее сообщение. В случае, если ни один из описанных вариантов обработки не подходит, выполняется экстен "_cause.", и обработка звонка завершается.
Всё в этой обработке звонка было бы хорошо, если бы не требовалось отображение корректной информации в CDR. Из-за CDR проявляются 2 недостатка этой обработки звонка:
- В случае неуспешного звонка (т.е. если на команде Dial выполнение не закончится) в CDR(dst) будет записан не номер телефона, а текст: cause-1, cause-20 или что-то в этом роде (будет записано значение того экстена, на который пойдет обработка).
- В случае неуспешного звонка (т.е. если на команде Dial выполнение не закончится) в поле disposition CDR будет записан статус звонка Answered (поскольку в обработке cause выполняется команда Answer: без этой команды приложения PlayTones и Playback не смогут передать пользователю никаких звуков).
Это (по-видимому) не баг астериска, а фича, поскольку тянется это с самых первых версий asterisk и по-прежнему присутствует в 11-й версии (и полагаю, что в 13-й версии asterisk все так же).
Решение 1: красивое и неправильное
В интернете предлагается простое, красивое и ... неправильное решение: перед командой Dial сохраняем номер, к примеру, в переменной finaldst:
exten => _X.,n, Set(finaldst=${EXTEN})
После чего либо перед Hangup (в каждом из cause), либо в экстене h (в контексте mikhed-hangupcause-context) выполняем команду:
exten => ...,Set(CDR(dst)=${finaldst})
Почему это решение неправильное? Да потому, что в руководствах по asterisk (например здесь) указано: CDR(dst) is read only. То есть поле dst является полем только для чтения, изменить его нельзя.
Решение 2: правильное, но некрасивое
В этом решении необходимо завершать обработку звонка (в любом контексте) в таком экстене, который равен номеру телефона, на который выполняется звонок.
Все операции по обработке звонка выполняются в одном экстене, который равен номеру телефона. Однако, даже в этом случае (к сожалению) приходится делать команду Answer(), поэтому статус ошибочного звонка (в поле disposition) всё равно будет Answered.
Решение 3: красивое и правильное
Есть другой, менее очевидный вариант: зачем вообще сохранять значение CDR(dst)? Наша задача - чтобы в CDR было записано правильное значение dst (и, желательно, чтобы статус звонка в поле disposition CDR соответствовал результату звонка: NO ANSWER, FAILED и т.д.. Для этого мы до того, как сделать любую обработку результатов звонка (но после команды Dial) выполним сохранение CDR куда нужно (хотя бы в базу данных или куда она там пишется - нам всё равно).
Чтобы записать CDR (и тем самым, сбросить / начать заново текущие параметры CDR) существует приложение ResetCDR. Опция w этого приложения позволяет сохранить параметры CDR (а опция v - переменные CDR) до сброса параметров: ResetCDR(wv).
Ну а дальше-то что делать? Новый звонок что ли писаться будет (обработка ведь выполняется, канал открыт)? Да ни в коем случае! Мы просто отключим CDR для этого "нового" звонка: NoCDR().
И вот он, наш пример, исправленный:
Тут есть ньюанс: если Вы выполняете обработку результатов звонка (т.е. других звонков в рамках этой обработки уже не будет) - то всё правильно. Если же Вы по каким-то причинам хотите, чтобы эта обработка тоже считалась в CDR, то тогда воспользуйтесь вторым решением (хоть и не столь изящным, но тоже рабочим), либо попробуйте вариант, описанный ниже.
Решение 4: красивое и правильное, но требующее переделки CDR
Многие специалисты по астериск рекомендуют в описанных выше ситуациях сохранять значение dst в другой переменной (для которой у Вас есть право и на чтение и на запись) и уже пользоваться этой переменной для учета звонков (вместо dst). Хитрость тут состоит в том, что Вы можете записывать в CDR любые свои переменные (сделайте например поиск в интернете по "custom CDR variables" или "custom CDR fields"). Главное при этом - чтобы в базе данных, в которую сохраняется информация CDR, были предусмотрены соответствующие поля. По этому поводу можно прочитать например, здесь.
Вот пример кода с учетом измененных полей CDR (напоминаю, необходимо произвести изменения в структуре таблицы CDR, чтобы туда сохранялись эти поля! А кроме того, произвести изменения в программе, отображающей информацию о звонках, чтобы программа использовала эти дополнительные поля CDR!).
Вот и всё!
|