LUA Dialplan for Asterisk

Welcome all. Once upon a time, the topic of using the lua programming language when writing a dialplan in Asterisk was pretty tough for me. The fact is that I strongly dislike working with various GUIs (such as FreePBX) when setting up the Asterisk.

When I set up everything for the first time, I worked with the usual linear extensions.conf. Time passed, the demand for telephony functionality grew. Lua gradually learned a little. And so I came to work as an administrator in one large company in our city (one large real estate agency) - at that time there were about 45 branches, about 650 - 700 users, including intercity, etc. Asterisk was already there, but everything was set up using FreePBX.

Almost immediately, the leadership began to overwhelm me with various questions on Asterisk bells and whistles. For example, they wanted for an incoming call to a branch, calls within the branch to be distributed randomly. They wanted to have a recording of conversations in mp3, they wanted to create a common group, where it would be possible to include all the branches in general, and when dialing some number, to accidentally get to one of the branches, etc. Tasks seem to be simple, but sitting personally, even solving such issues using the graphical interface, was not very interesting to me personally.

There was another important point - the quality of telephony in general at that time was simply terrible. The voice was constantly gurgling, the calls were breaking, the subscriber was not heard, the aster itself often crashed, etc. I look at the file of the dialplan, and it is 16 MB in size. Opened with a text editor - and what can I do? There are several million lines.

I decided to redo it, throwing everything on lua. About a couple of days after the start of development, I was able to present the first prototype of the dial plan on lua, quite working, but without the existing "features" and "little things". Replaced them with the entire old config and then for another week threw the main bells and whistles that the management wanted to see. I also updated the aster itself to the 11th version (at that time 11.3.0, it seems). Further, in the process of work, sometimes he glanced at the dialplan file and filed up what management himself wanted or wanted. As a result, an aster with a dialplan on lua worked much faster and more stable than the last.

The conditions under which the “station” worked:

cpu: intel xeon e5520 (if I’m not mistaken)
ram: 24gb
and other hardware parameters, including two gigabit network interfaces and reid1
number of ext. subscribers: about 700
number of trunks: about 10 (of which 2 are providers, the rest are addpack gsm gateways).
the number of “city” numbers: about 200 (150 numbers from one provider and about 50 or a little more from the second).

City numbers here were assigned to each branch. Some branches even have two or three numbers. Since all calls from the city flew into the context, then I did the analysis by did and transferred the call to the desired branch.

By means of lua, he realized ring groups, made two options for calling a subscriber - random and in the order of listing in the group (with the exception of busy subscribers). I screwed lua-sql to record my own database of calls (addition to cdr). This was done for this: the employee calls the client on the cell phone, the client now does not want to talk (busy or something); after a while he calls back to the previously defined number and must get to the same employee who had called him before. I recorded the “call to mobile” event in a separate database. When a client calls back from the cell, I pick up the last call on the “call from cell” event and give the client to the right employee. Only one such employee was remembered. Those. if another customer calls this customer. then, accordingly, the call will return to him.

Now I no longer work in that company, and where I am now - I am changing the old PBX to Asterisk and, of course, using my old operating time. I remembered that the topic was interesting not only to me. Well, since there is very little information on this topic, I decided to throw this article here, suddenly it will come in handy for someone.

Now I will turn to the very essence of the topic - coding in lua. I will not describe the stage of switching on the pbx_lua module - there is a lot of information. For example, now I have Centos 6.6, there is already lua in the drain. I just docked the lua-devel package and included the pbx_lua module in menuselect.

Additionally, if anyone is going to use a manual connection to mysql (or to another database), it is better to drop the lua-sql package by installing luarocks first and downloading this add-on from there.

Further in the dialplan itself, you can describe users and set rules, something like this:

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"};
    };

here ["_XXnumber"] is a template. Those. everything is the same as in regular extensions.conf.
call_local - the function referenced by this description. Those. when dialing, say, 14555, the call_local function will be called. Also, this function can be called when an incoming external call.

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

There are several checks for some groups and statuses. For example, 4550 is a technical support group. There is a separate function for it, in which there is processing of employee employment, informing an “external client”, recording a journal, and resetting a missed call warning to tech support via jabber.

If the called party is a group, then mix the list and call the random party.

Why am I using a random method to call subscribers from groups? Branches are, in essence, sales managers. If you include a sequential call of the branch employees, the first ones on the list will always have more sales than others (cheating). The situation is similar with the mem-primari method (it seems), in which the user who answered the last time will be ignored. The random mixing method is more honest, puts all "sales people" on an equal footing. You can make a call-all of course (call everyone at the same time), but then the branches start complaining that in the branch all the phones are "yelling" at the same time it's not convenient, noisy, etc.

For a random call, queues could also be used, but I hardly use them. I don’t know why, it happened.

Further, coming from the city, description:

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"}
    }


Here I am wrapping all the external inbox in 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


In this case, I determine the cellular numbers and external numbers (did). If the caller calls to the local cellular (SIM card in the gsm gateway), then I take the last caller and send this client to him. There is also a definition from the ngs_rec list. Here the numbers are predefined as “advertising”. It was an old method (then redid it, but in this version of the file from which I take the code of this revision is not). I send all the advertising numbers to the special numbers in the office and make a note in the database that there was a call to the number indicated in the advertisement.

For now, I think that's enough code for now. If someone has an interest in switching from the old dialplan to lua, I think I can continue and explain in more detail some things. Although, if someone already knows how to program in Lua, there will be absolutely no problems.

In conclusion, I want to say that, of course, today there is a whole bunch of different tricked-out solutions, such as VoxImplant and the like. Many people are not used to working in the console and coding for something. But, I want to note that when the company’s size is large (from 50 subscribers and above), building the logic of the “station” using buttons and checkmarks in the graphical interface can ultimately lead to problems. Above in the beginning of the article I gave examples about gurgling and cliffs. Dialplan on lua weighs only 24kb, 968 lines.

Almost 700 subscribers worked on it without any problems.

All. Goodbye everyone!

Also popular now: