Приветствую всех. Когда-то тема использования языка программирования lua при написании диалплана в Астериске для меня стояла довольно жёстко. Дело в том, что мне сильно не нравится работать с различными GUI (типа FreePBX) при настройке Астериска.
Когда я всё настраивал в первый раз, работал с обычным линейным extensions.conf. Время шло, потребности в функционале телефонии росли. Язык lua постепенно немного изучил. И вот пришёл я работать админом в одну крупную компанию в нашем городе (одно крупное агентство недвижимости) — около 45 филиалов на тот момент было, примерно 650 — 700 пользователей, включая межгород и т.д. Там уже стоял Asterisk, но всё настроено было с использованием FreePBX.
Почти сразу руководство начало меня заваливать различными вопросами по наворотам Астериска. Например, хотели, чтобы при входящем звонке в какой-то филиал, звонки внутри филиала были распределены случайным образом. Хотели иметь запись разговоров в mp3, хотели сделать общую группу, куда можно было бы включить вообще все филиалы и при наборе какого-то номера, чтобы случайно попасть на один из филиалов и т.д. Задачи вроде простые, однако сидеть решать даже такие вопросы средствами графического интерфейса лично мне было не очень интересно.
Был ещё один важный момент — качество работы телефонии в целом на тот момент было просто ужасным. Голос постоянно булькал, звонки разрывались, абонента не слышно, сам астер часто крашился и т.д. Смотрю на файлик диалплана, а он размером в 16 мб. Открыл редактором текста — и что тут делать? Там строк в несколько миллионов.
Решил переделать, перекинув всё на lua. Примерно через пару дней после начала разработки я уже смог представить первый прототип диалплана на lua, вполне рабочий, но без существующих «фишечек» и «рюшечек». Заменил им весь старый конфиг и далее ещё в течение недели накидал основные навороты, которые хотело видеть руководство. Так же обновил самого астера до 11-й версии (на тот момент 11.3.0, кажется). Далее в процессе работы иногда поглядывал в файл диалплана и подпиливал то, что сам хотел или хотело руководство. В итоге астер с диалпланом на lua работал значительно быстрее и более стабильно, чем прошлый.
Условия в которых работала «станция»:
cpu: intel xeon e5520 (если не ошибаюсь)
ram: 24gb
и другие «железные» параметры, включая два гигабитных сетевых интерфейса и рэйд1
количество вн.абонентов: около 700
количество транков: около 10 (из которых 2 это провайдеры, остальные это gsm шлюзы addpack).
количество «городских» номеров: около 200 (150 номеров от одного провайдера и примерно50 или чуть больше от второго).
Городские номера тут были закреплены за каждым филиалом. За некоторыми филиалами даже по два или три номера. Поскольку все звонки из города прилетали в контекст, далее я делал разбор по did и передавал звонок на нужный филиал.
Средствами lua реализовал ring groups, сделал два варианта вызова абонента — случайное и по порядку перечисления в группе (за исключением занятых абонентов). Прикрутил lua-sql для записи собственной базы звонков (дополнение к cdr). Это было сделано вот для чего: сотрудник звонит клиенту на сотовый, клиент сейчас не захотел разговаривать (занят или ещё что); через некоторое время он перезванивает на определённый ранее номер и должен попасть к тому же сотруднику, который ему до этого звонил. Я сделал запись события «звонок на мобильный» в отдельную базу. Когда клиент с сотового перезванивает, я по событию «звонок с сотового» поднимаю прошлый звонок и отдаю клиента на нужного сотрудника. Запоминался только один такой сотрудник. Т.е. если этому клиенту позвонит ещё один сотрудник. то соответственно, звонок вернётся к нему.
Сейчас я уже не работаю в той компании, а там где сейчас — меняю старую АТС на Астериска и, конечно, использую старую свою наработку. Вспомнил, что тема интересна была не только мне. Ну а поскольку по этой теме информации крайне мало, решил накидать вот эту статью, вдруг кому-то пригодится.
Теперь перейду к самой сути темы — кодинг на lua. Описывать стадию включения модуля pbx_lua не буду — информации тут много. Например, сейчас у меня стоит Centos 6.6, там в стоке уже есть lua. Я докинул только пакет lua-devel и включил модуль pbx_lua в menuselect.
Дополнительно, если кто собирается использовать ручное подключение к mysql (или к другой базе), то лучше докинуть пакет lua-sql, предварительно установив luarocks и оттуда закачав это дополнение.
Далее в самом диалплане можно описать пользователей и правила набора, что-то типа того:
тут ["_XXномер"] — шаблон. Т.е. всё тоже самое, что и в обычном extensions.conf.
call_local — функция на которую ссылается данное описание. Т.е. при наборе номера, скажем, 14555, будет вызываться функция call_local. Так же эта функция может вызываться при входящем внешнем звонке.
Тут есть несколько проверок на некоторые группы и статусы. Например, 4550 — это группа технической поддержки. Для неё есть отдельная функция, в которой есть обработка занятости сотрудников, информирование «вн.клиента», запись журнала и сброс предупреждения о пропущенном звонке в тех.поддержку через jabber.
Если вызываемый абонент является группой, тогда смешать список и вызвать случайного абонента.
Почему я использую случайный метод вызова абонентов из групп? Филиалы — это, по сути своей, менеджеры продаж. Если включать последовательный вызов сотрудников филиала, то всегда у первых в списке будет продаж больше, чем у других (читинг). Аналогично обстоят дела и с методом mem-primari (кажется), при котором пользователь ответивший в прошлый раз будет игнорирован. Метод случайного смешивания более честный, ставит всех «продажников» в равные условия. Можно сделать конечно call-all (звонить всем одновременно), но тогда филиалы начинают жаловаться, что в филиале все телефоны «орут» одновременно это не удобно, шумно и т.д.
Для случайного вызова можно было бы так же использовать очереди, но я их почти не использую. Не знаю почему, так получилось.
Далее, входящие из города, описание:
Тут я все внешние входящие я заворачиваю в foo.
В данном случае определяю сотовые номера и внешние номера (did). Если звонящий звонит на местный сотовый (симка в gsm шлюзе), тогда беру последнего звонившего абонента и отправляю этого клиента к нему. Так же есть определение из списка ngs_rec. Тут номера заранее определены как «рекламные». Это был старый метод (потом переделал, но в этой версии файла из которой беру код данной доработки нет). Все рекламные номера отправляю на спец.номера в конторе и делаю отметку в базе, что был звонок на номер, указанный в рекламе.
Пока, думаю, хватит кода на данный момент. Если у кого-то появится интерес к переходу от старого диалплана к lua, думаю, смогу продолжить и разъяснить более детально некоторые вещи. Хотя, если кто-то уже знает, как программировать на Lua, проблем совершенно не будет.
В завершении хочу сказать, что, конечно, на сегодняшний день существует целая пачка разных навороченных решений, типа VoxImplant и подобных. Многим вообще не привычно работать в консоли и что-то своё кодить. Но, хочу заметить, что когда размеры компании большие (от 50 абонентов и выше), то выстраивание логики работы «станции» при помощи кнопочек и галочек в графическом интерфейсе в итоге могут приводить к проблемам. Выше в начале статья я приводил примеры про бульканье и обрывы. Диалплан на lua весит всего 24кб, 968 строк.
На нём работали почти 700 абонентов без каких-то проблем.
Всё. Всем до свиданья!
Когда я всё настраивал в первый раз, работал с обычным линейным extensions.conf. Время шло, потребности в функционале телефонии росли. Язык lua постепенно немного изучил. И вот пришёл я работать админом в одну крупную компанию в нашем городе (одно крупное агентство недвижимости) — около 45 филиалов на тот момент было, примерно 650 — 700 пользователей, включая межгород и т.д. Там уже стоял Asterisk, но всё настроено было с использованием FreePBX.
Почти сразу руководство начало меня заваливать различными вопросами по наворотам Астериска. Например, хотели, чтобы при входящем звонке в какой-то филиал, звонки внутри филиала были распределены случайным образом. Хотели иметь запись разговоров в mp3, хотели сделать общую группу, куда можно было бы включить вообще все филиалы и при наборе какого-то номера, чтобы случайно попасть на один из филиалов и т.д. Задачи вроде простые, однако сидеть решать даже такие вопросы средствами графического интерфейса лично мне было не очень интересно.
Был ещё один важный момент — качество работы телефонии в целом на тот момент было просто ужасным. Голос постоянно булькал, звонки разрывались, абонента не слышно, сам астер часто крашился и т.д. Смотрю на файлик диалплана, а он размером в 16 мб. Открыл редактором текста — и что тут делать? Там строк в несколько миллионов.
Решил переделать, перекинув всё на lua. Примерно через пару дней после начала разработки я уже смог представить первый прототип диалплана на lua, вполне рабочий, но без существующих «фишечек» и «рюшечек». Заменил им весь старый конфиг и далее ещё в течение недели накидал основные навороты, которые хотело видеть руководство. Так же обновил самого астера до 11-й версии (на тот момент 11.3.0, кажется). Далее в процессе работы иногда поглядывал в файл диалплана и подпиливал то, что сам хотел или хотело руководство. В итоге астер с диалпланом на lua работал значительно быстрее и более стабильно, чем прошлый.
Условия в которых работала «станция»:
cpu: intel xeon e5520 (если не ошибаюсь)
ram: 24gb
и другие «железные» параметры, включая два гигабитных сетевых интерфейса и рэйд1
количество вн.абонентов: около 700
количество транков: около 10 (из которых 2 это провайдеры, остальные это gsm шлюзы addpack).
количество «городских» номеров: около 200 (150 номеров от одного провайдера и примерно50 или чуть больше от второго).
Городские номера тут были закреплены за каждым филиалом. За некоторыми филиалами даже по два или три номера. Поскольку все звонки из города прилетали в контекст, далее я делал разбор по did и передавал звонок на нужный филиал.
Средствами lua реализовал ring groups, сделал два варианта вызова абонента — случайное и по порядку перечисления в группе (за исключением занятых абонентов). Прикрутил lua-sql для записи собственной базы звонков (дополнение к cdr). Это было сделано вот для чего: сотрудник звонит клиенту на сотовый, клиент сейчас не захотел разговаривать (занят или ещё что); через некоторое время он перезванивает на определённый ранее номер и должен попасть к тому же сотруднику, который ему до этого звонил. Я сделал запись события «звонок на мобильный» в отдельную базу. Когда клиент с сотового перезванивает, я по событию «звонок с сотового» поднимаю прошлый звонок и отдаю клиента на нужного сотрудника. Запоминался только один такой сотрудник. Т.е. если этому клиенту позвонит ещё один сотрудник. то соответственно, звонок вернётся к нему.
Сейчас я уже не работаю в той компании, а там где сейчас — меняю старую АТС на Астериска и, конечно, использую старую свою наработку. Вспомнил, что тема интересна была не только мне. Ну а поскольку по этой теме информации крайне мало, решил накидать вот эту статью, вдруг кому-то пригодится.
Теперь перейду к самой сути темы — кодинг на lua. Описывать стадию включения модуля pbx_lua не буду — информации тут много. Например, сейчас у меня стоит Centos 6.6, там в стоке уже есть lua. Я докинул только пакет lua-devel и включил модуль pbx_lua в menuselect.
Дополнительно, если кто собирается использовать ручное подключение к mysql (или к другой базе), то лучше докинуть пакет lua-sql, предварительно установив luarocks и оттуда закачав это дополнение.
Далее в самом диалплане можно описать пользователей и правила набора, что-то типа того:
extensions = { }; local_ext = { -- когда вн.абонент поднял трубку и набрал другого вн.абонента h = function() -- обработчик конца разговора (hangup) app.stopmixmonitor() d_status = channel["DIALSTATUS"]:get() if d_status ~= nil then app.noop("Dial over with status:"..d_status) -- например, если абонент не дозвонился, тогда затираем имя файла в базе cdr if d_status ~= "ANSWER" then channel["CDR(recordingfile)"]:set("") end app.noop("Good buy!") app.hangup() end; app.hangup() end; ["_14XXX"] = call_local; ["_21XX"] = call_local; ["_4595"] = call_all; -- это описание не номера, а группы номеров. при наборе звоним на случайны номер из группы ["_*99"] = function() -- это специально добавлял для принудительного включения dnd (занятно). local cid, dnd app.answer() cid = channel["CALLERID(num)"]:get() dnd = channel["DB(DND/"..cid.."/)"]:get() app.noop("DND:"..dnd) if dnd == "1" then channel["DB_DELETE(DND/"..cid.."/)"]:get() app.playback("beep") app.playback("beep") app.hangup() else channel["DB(DND/"..cid.."/)"]:set("1") app.playback("beep") app.wait(1) app.hangup() end end; include = {"mobile_out"}; };
тут ["_XXномер"] — шаблон. Т.е. всё тоже самое, что и в обычном extensions.conf.
call_local — функция на которую ссылается данное описание. Т.е. при наборе номера, скажем, 14555, будет вызываться функция call_local. Так же эта функция может вызываться при входящем внешнем звонке.
function call_local(ctx,ext) local callerid,cf,uniq,chn local n,j,i n = string.sub(ext,3) -- взяли последние 2 символа номера if n == "90" or n == "79" or n == "80" then -- если оканчивается на 90 и т.д. тогда это звонок на одну из групп филиалов j = channel["CALLERID(num)"]:get() app.noop(string.format("Using ring group %s from %s",ext,j)) dial_rg(shuffle(r_group[ext],nil)) -- смешать номера в группе и вызвать end -- если пользователь включил режим "отсутствую", тогда звонок полетит к нему на его сотовый cf = channel["DB(CF/"..ext.."/"..")"]:get() app.noop("CF:"..cf) if cf ~= "" then app.noop(string.format("Call forward detected from %s to %s",ext,cf)) app.goto("mobile_out",cf,"1") end callerid = channel["CALLERID(num)"]:get() app.noop(string.format("Trying to local call %s from %s",ext,callerid)) if ext ~= "4550" and (CheckChannel(ext)) ~= NOT_INUSE then return end uniq = channel.UNIQUEID:get() chn = channel["CHANNEL"]:get() app.noop(string.format("UNIQUEID: %s",uniq)) app.noop(string.format("CHANNEL: %s",chn)) app.noop(string.format("CALLERID_name: %s",callerid)) app.noop(string.format("EXTEN: %s",ext)) app.noop(string.format("CONTEXT: %s",ctx)) record(string.format("%s-%s-%s",callerid,ext,uniq)) if ext == "4550" then local support = CallSupport(callerid) if support == "failed" then return end end if ext == "4514" or ext == "4592" then app.noop("Redirect!!!") app.dial("SIP/4591,60,tT") end app.dial(string.format("SIP/%s,60,tT",ext)) end
Тут есть несколько проверок на некоторые группы и статусы. Например, 4550 — это группа технической поддержки. Для неё есть отдельная функция, в которой есть обработка занятости сотрудников, информирование «вн.клиента», запись журнала и сброс предупреждения о пропущенном звонке в тех.поддержку через jabber.
Если вызываемый абонент является группой, тогда смешать список и вызвать случайного абонента.
Почему я использую случайный метод вызова абонентов из групп? Филиалы — это, по сути своей, менеджеры продаж. Если включать последовательный вызов сотрудников филиала, то всегда у первых в списке будет продаж больше, чем у других (читинг). Аналогично обстоят дела и с методом mem-primari (кажется), при котором пользователь ответивший в прошлый раз будет игнорирован. Метод случайного смешивания более честный, ставит всех «продажников» в равные условия. Можно сделать конечно call-all (звонить всем одновременно), но тогда филиалы начинают жаловаться, что в филиале все телефоны «орут» одновременно это не удобно, шумно и т.д.
Для случайного вызова можно было бы так же использовать очереди, но я их почти не использую. Не знаю почему, так получилось.
Далее, входящие из города, описание:
from_trunk = { h = function() app.noop("BBBBBBBLLLLAAAAHHHHHH!!!!!!!") app.stopmixmonitor() if d_status ~= nil then d_status = channel["DIALSTATUS"]:get() app.noop("Dial over with status:"..d_status) if d_status ~= "ANSWER" then channel["CDR(recordingfile)"]:set("") end exten = "" uniqid = "" app.noop("Good buy!") app.hangup() end app.noop("Some problem!!!") app.hangUP() end; ["f1"] = function(e) -- если честно, не помню что я тут делал... app.goto("local_ext",e,1) end; ["_."] = foo; -- да да, это функция называется типа foobar... include = {"local_ext"} }
Тут я все внешние входящие я заворачиваю в foo.
function foo(ctx,ext) local chn tmptab.did = ext tmptab.rg = g_tab[ext] if tmptab.did == "99051000227736" then -- тут я делал эксперимент с входящими со Скайпа. работают. app.noop("Skype TEST!!!") app.dial("SIP/14553,,tT,M(bar)") end tmptab.callerid = channel["CALLERID(num)"]:get() if string.find(tmptab.callerid,"88005550678",1) then app.hungup() end -- кого-то забанил... if string.find(tmptab.callerid,"79",1) then -- тут я тоже подзабыл, что-то связанное с определением сотовых номеров tmptab.callerid = "8"..string.sub(tmptab.callerid,2) channel["CALLERID(all)"]:set(tmptab.callerid) end chn = channel["CHANNEL"]:get() app.noop("CHANNEL:"..chn) if string.find(chn,"SIP/gsm_",1) then -- тут я вылавливаю входящие через gsm шлюзы app.noop("Found channel "..chn) -- через ранее созданную простейшую базу на mysql выловил абонента и отправил ему клиента num = sql.mobile_get(tmptab.callerid) if num then app.goto("local_ext",num,1) end end app.noop("CallerID(num):"..tmptab.callerid) app.noop("by context:"..ctx) app.noop("DID:"..tmptab.did) app.set("CDR(did)="..tmptab.did) if tmptab.did == "4595" then call_all(tmptab.did) end -- 4595 это глобальная группа по всем филиалам app.answer() app.wait(1) j = channel["DB(ENUM/"..tmptab.did.."/)"]:get() app.noop("tag = "..j) if j == "ngs_rec" then dial_rg(shuffle(tmptab.rg),1) else if tmptab.did ~= "3471234" then -- имейте ввиду, все номера тут вымышленные!!! app.playback(mhold.comp_hello) if tmptab.did == "3472345" then app.goto("local_ext","4591",1) end ivr(tmptab.did) -- да, голосовое меню тут тоже есть, но показывать его не буду... dial_rg(shuffle(tmptab.rg),nil) else app.noop("BLAH DETECTED") dial_rg(tmptab.rg,nil) end end app.noop("hungup?") end
В данном случае определяю сотовые номера и внешние номера (did). Если звонящий звонит на местный сотовый (симка в gsm шлюзе), тогда беру последнего звонившего абонента и отправляю этого клиента к нему. Так же есть определение из списка ngs_rec. Тут номера заранее определены как «рекламные». Это был старый метод (потом переделал, но в этой версии файла из которой беру код данной доработки нет). Все рекламные номера отправляю на спец.номера в конторе и делаю отметку в базе, что был звонок на номер, указанный в рекламе.
Пока, думаю, хватит кода на данный момент. Если у кого-то появится интерес к переходу от старого диалплана к lua, думаю, смогу продолжить и разъяснить более детально некоторые вещи. Хотя, если кто-то уже знает, как программировать на Lua, проблем совершенно не будет.
В завершении хочу сказать, что, конечно, на сегодняшний день существует целая пачка разных навороченных решений, типа VoxImplant и подобных. Многим вообще не привычно работать в консоли и что-то своё кодить. Но, хочу заметить, что когда размеры компании большие (от 50 абонентов и выше), то выстраивание логики работы «станции» при помощи кнопочек и галочек в графическом интерфейсе в итоге могут приводить к проблемам. Выше в начале статья я приводил примеры про бульканье и обрывы. Диалплан на lua весит всего 24кб, 968 строк.
На нём работали почти 700 абонентов без каких-то проблем.
Всё. Всем до свиданья!