2 LuCI - Lua Configuration Interface
3 Asterisk PBX interface library
5 Copyright 2009 Jo-Philipp Wich <xm@subsignal.org>
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
11 http://www.apache.org/licenses/LICENSE-2.0
17 module("luci.asterisk", package.seeall)
18 require("luci.asterisk.cc_idd")
20 local _io = require("io")
21 local uci = require("luci.model.uci").cursor()
22 local sys = require("luci.sys")
23 local util = require("luci.util")
25 AST_BIN = "/usr/sbin/asterisk"
29 --- LuCI Asterisk - Resync uci context
31 uci = luci.model.uci.cursor()
34 --- LuCI Asterisk io interface
35 -- Handles low level io.
37 io = luci.util.class()
39 --- Execute command and return output
40 -- @param command String containing the command to execute
41 -- @return String containing the command output
42 function io.exec(command)
43 local fh = _io.popen( "%s %s %q" %{ AST_BIN, AST_FLAGS, command }, "r" )
44 assert(fh, "Failed to invoke asterisk")
46 local buffer = fh:read("*a")
51 --- Execute command and invoke given callback for each readed line
52 -- @param command String containing the command to execute
53 -- @param callback Function to call back for each line
54 -- @return Always true
55 function io.execl(command, callback)
57 local fh = _io.popen( "%s %s %q" %{ AST_BIN, AST_FLAGS, command }, "r" )
58 assert(fh, "Failed to invoke asterisk")
69 --- Execute command and return an iterator that returns one line per invokation
70 -- @param command String containing the command to execute
71 -- @return Iterator function
72 function io.execi(command)
73 local fh = _io.popen( "%s %s %q" %{ AST_BIN, AST_FLAGS, command }, "r" )
74 assert(fh, "Failed to invoke asterisk")
77 local ln = fh:read("*l")
78 if not ln then fh:close() end
84 --- LuCI Asterisk - core status
85 core = luci.util.class()
87 --- Retrive version string.
88 -- @return String containing the reported asterisk version
89 function core.version(self)
90 local version = io.exec("core show version")
91 return version:gsub(" *\n", "")
95 --- LuCI Asterisk - SIP information.
97 sip = luci.util.class()
99 --- Get a list of known SIP peers
100 -- @return Table containing each SIP peer
101 function sip.peers(self)
105 for line in io.execi("sip show peers") do
108 elseif not line:match(" sip peers ") then
109 local online, delay, id, uid
110 local name, host, dyn, nat, acl, port, status =
111 line:match("(.-) +(.-) +([D ]) ([N ]) (.) (%d+) +(.+)")
113 if host == '(Unspecified)' then host = nil end
114 if port == '0' then port = nil else port = tonumber(port) end
116 dyn = ( dyn == 'D' and true or false )
117 nat = ( nat == 'N' and true or false )
118 acl = ( acl ~= ' ' and true or false )
120 online, delay = status:match("(OK) %((%d+) ms%)")
122 if online == 'OK' then
124 delay = tonumber(delay)
125 elseif status ~= 'Unmonitored' then
133 id, uid = name:match("(.+)/(.+)")
135 if not ( id and uid ) then
157 --- Get informations of given SIP peer
158 -- @param peer String containing the name of the SIP peer
159 function sip.peer(peer)
163 for line in io.execi("sip show peer " .. peer) do
165 local key, val = line:match("(.-) *: +(.*)")
168 key = key:gsub("^ +",""):gsub(" +$", "")
169 val = val:gsub("^ +",""):gsub(" +$", "")
171 if key == "* Name" then
173 elseif key == "Addr->IP" then
174 info.address, info.port = val:match("(.+) Port (.+)")
175 info.port = tonumber(info.port)
176 elseif key == "Status" then
177 info.online, info.delay = val:match("(OK) %((%d+) ms%)")
178 if info.online == 'OK' then
180 info.delay = tonumber(info.delay)
181 elseif status ~= 'Unmonitored' then
190 if val == 'Yes' or val == 'yes' or val == '<Set>' then
192 elseif val == 'No' or val == 'no' then
194 elseif val == '<Not set>' or val == '(none)' then
208 --- LuCI Asterisk - Internal helpers
210 tools = luci.util.class()
212 --- Convert given value to a list of tokens. Split by white space.
213 -- @param val String or table value
214 -- @return Table containing tokens
215 function tools.parse_list(v)
218 v = type(v) == "table" and v or { v }
219 for _, v in ipairs(v) do
220 if type(v) == "string" then
221 for v in v:gmatch("(%S+)") do
222 tokens[#tokens+1] = v
230 --- Convert given list to a collection of hyperlinks
231 -- @param list Table of tokens
232 -- @param url String pattern or callback function to construct urls (optional)
233 -- @param sep String containing the seperator (optional, default is ", ")
234 -- @return String containing the html fragment
235 function tools.hyperlinks(list, url, sep)
238 local function mkurl(p, t)
239 if type(p) == "string" then
241 elseif type(p) == "function" then
252 for _, token in ipairs(list) do
253 html = ( html and html .. sep or '' ) ..
254 '<a href="%s">%s</a>' %{ mkurl(url, token), token }
261 --- LuCI Asterisk - International Direct Dialing Prefixes
263 idd = luci.util.class()
265 --- Lookup the country name for the given IDD code.
266 -- @param country String containing IDD code
267 -- @return String containing the country name
268 function idd.country(c)
269 for _, v in ipairs(cc_idd.CC_IDD) do
270 if type(v[3]) == "table" then
271 for _, v2 in ipairs(v[3]) do
272 if v2 == tostring(c) then
276 elseif v[3] == tostring(c) then
282 --- Lookup the country code for the given IDD code.
283 -- @param country String containing IDD code
284 -- @return Table containing the country code(s)
286 for _, v in ipairs(cc_idd.CC_IDD) do
287 if type(v[3]) == "table" then
288 for _, v2 in ipairs(v[3]) do
289 if v2 == tostring(c) then
290 return type(v[2]) == "table"
294 elseif v[3] == tostring(c) then
295 return type(v[2]) == "table"
301 --- Lookup the IDD code(s) for the given country.
302 -- @param idd String containing the country name
303 -- @return Table containing the IDD code(s)
305 for _, v in ipairs(cc_idd.CC_IDD) do
306 if v[1]:lower():match(c:lower()) then
307 return type(v[3]) == "table"
313 --- Populate given CBI field with IDD codes.
314 -- @param field CBI option object
316 function idd.cbifill(o)
317 for i, v in ipairs(cc_idd.CC_IDD) do
318 o:value("_%i" % i, util.pcdata(v[1]))
321 o.formvalue = function(...)
322 local val = luci.cbi.Value.formvalue(...)
323 if val:sub(1,1) == "_" then
324 val = tonumber((val:gsub("^_", "")))
326 return type(cc_idd.CC_IDD[val][3]) == "table"
327 and cc_idd.CC_IDD[val][3] or { cc_idd.CC_IDD[val][3] }
333 o.cfgvalue = function(...)
334 local val = luci.cbi.Value.cfgvalue(...)
336 val = tools.parse_list(val)
337 for i, v in ipairs(cc_idd.CC_IDD) do
338 if type(v[3]) == "table" then
339 if v[3][1] == val[1] then
343 if v[3] == val[1] then
354 --- LuCI Asterisk - Country Code Prefixes
356 cc = luci.util.class()
358 --- Lookup the country name for the given CC code.
359 -- @param country String containing CC code
360 -- @return String containing the country name
361 function cc.country(c)
362 for _, v in ipairs(cc_idd.CC_IDD) do
363 if type(v[2]) == "table" then
364 for _, v2 in ipairs(v[2]) do
365 if v2 == tostring(c) then
369 elseif v[2] == tostring(c) then
375 --- Lookup the international dialing code for the given CC code.
376 -- @param cc String containing CC code
377 -- @return String containing IDD code
379 for _, v in ipairs(cc_idd.CC_IDD) do
380 if type(v[2]) == "table" then
381 for _, v2 in ipairs(v[2]) do
382 if v2 == tostring(c) then
383 return type(v[3]) == "table"
387 elseif v[2] == tostring(c) then
388 return type(v[3]) == "table"
394 --- Lookup the CC code(s) for the given country.
395 -- @param country String containing the country name
396 -- @return Table containing the CC code(s)
398 for _, v in ipairs(cc_idd.CC_IDD) do
399 if v[1]:lower():match(c:lower()) then
400 return type(v[2]) == "table"
406 --- Populate given CBI field with CC codes.
407 -- @param field CBI option object
409 function cc.cbifill(o)
410 for i, v in ipairs(cc_idd.CC_IDD) do
411 o:value("_%i" % i, util.pcdata(v[1]))
414 o.formvalue = function(...)
415 local val = luci.cbi.Value.formvalue(...)
416 if val:sub(1,1) == "_" then
417 val = tonumber((val:gsub("^_", "")))
419 return type(cc_idd.CC_IDD[val][2]) == "table"
420 and cc_idd.CC_IDD[val][2] or { cc_idd.CC_IDD[val][2] }
426 o.cfgvalue = function(...)
427 local val = luci.cbi.Value.cfgvalue(...)
429 val = tools.parse_list(val)
430 for i, v in ipairs(cc_idd.CC_IDD) do
431 if type(v[2]) == "table" then
432 if v[2][1] == val[1] then
436 if v[2] == val[1] then
447 --- LuCI Asterisk - Dialzone
449 dialzone = luci.util.class()
451 --- Parse a dialzone section
452 -- @param zone Table containing the zone info
453 -- @return Table with parsed information
454 function dialzone.parse(z)
457 trunks = tools.parse_list(z.uses),
459 description = z.description or z['.name'],
460 addprefix = z.addprefix,
461 matches = tools.parse_list(z.match),
462 intlmatches = tools.parse_list(z.international),
463 countrycode = z.countrycode,
464 localzone = z.localzone,
465 localprefix = z.localprefix
470 --- Get a list of known dial zones
471 -- @return Associative table of zones and table of zone names
472 function dialzone.zones()
475 uci:foreach("asterisk", "dialzone",
477 zones[z['.name']] = dialzone.parse(z)
478 znames[#znames+1] = z['.name']
483 --- Get a specific dial zone
484 -- @param name Name of the dial zone
485 -- @return Table containing zone information
486 function dialzone.zone(n)
488 uci:foreach("asterisk", "dialzone",
490 if z['.name'] == n then
491 zone = dialzone.parse(z)
497 --- Find uci section hash for given zone number
498 -- @param idx Zone number
499 -- @return String containing the uci hash pointing to the section
500 function dialzone.ucisection(i)
504 uci:foreach("asterisk", "dialzone",
506 if not hash and index == i then
515 --- LuCI Asterisk - Voicemailbox
517 voicemail = luci.util.class()
519 --- Parse a voicemail section
520 -- @param zone Table containing the mailbox info
521 -- @return Table with parsed information
522 function voicemail.parse(z)
523 if z.number and #z.number > 0 then
525 id = '%s@%s' %{ z.number, z.context or 'default' },
527 context = z.context or 'default',
528 name = z.name or z['.name'] or 'OpenWrt',
529 zone = z.zone or 'homeloc',
530 password = z.password or '0000',
531 email = z.email or '',
536 uci:foreach("asterisk", "dialplanvoice",
538 if s.dialplan and #s.dialplan > 0 and
539 s.voicebox == v.number
541 v.dialplans[#v.dialplans+1] = s.dialplan
549 --- Get a list of known voicemail boxes
550 -- @return Associative table of boxes and table of box numbers
551 function voicemail.boxes()
554 uci:foreach("asterisk", "voicemail",
556 local v = voicemail.parse(z)
558 local n = '%s@%s' %{ v.number, v.context }
560 vnames[#vnames+1] = n
563 return vboxes, vnames
566 --- Get a specific voicemailbox
567 -- @param number Number of the voicemailbox
568 -- @return Table containing mailbox information
569 function voicemail.box(n)
571 n = n:gsub("@.+$","")
572 uci:foreach("asterisk", "voicemail",
574 if z.number == tostring(n) then
575 box = voicemail.parse(z)
581 --- Find all voicemailboxes within the given dialplan
582 -- @param plan Dialplan name or table
583 -- @return Associative table containing extensions mapped to mailbox info
584 function voicemail.in_dialplan(p)
585 local plan = type(p) == "string" and p or p.name
587 uci:foreach("asterisk", "dialplanvoice",
589 if s.extension and #s.extension > 0 and s.dialplan == plan then
590 local box = voicemail.box(s.voicebox)
592 boxes[s.extension] = box
599 --- Remove voicemailbox and associated extensions from config
600 -- @param box Voicemailbox number or table
601 -- @param ctx UCI context to use (optional)
602 -- @return Boolean indicating success
603 function voicemail.remove(v, ctx)
605 local box = type(v) == "string" and v or v.number
606 local ok1 = ctx:delete_all("asterisk", "voicemail", {number=box})
607 local ok2 = ctx:delete_all("asterisk", "dialplanvoice", {voicebox=box})
608 return ( ok1 or ok2 ) and true or false
612 --- LuCI Asterisk - MeetMe Conferences
614 meetme = luci.util.class()
616 --- Parse a meetme section
617 -- @param room Table containing the room info
618 -- @return Table with parsed information
619 function meetme.parse(r)
620 if r.room and #r.room > 0 then
624 adminpin = r.adminpin or '',
625 description = r._description or '',
629 uci:foreach("asterisk", "dialplanmeetme",
631 if s.dialplan and #s.dialplan > 0 and s.room == v.room then
632 v.dialplans[#v.dialplans+1] = s.dialplan
640 --- Get a list of known meetme rooms
641 -- @return Associative table of rooms and table of room numbers
642 function meetme.rooms()
645 uci:foreach("asterisk", "meetme",
647 local v = meetme.parse(r)
650 mnames[#mnames+1] = v.room
653 return mrooms, mnames
656 --- Get a specific meetme room
657 -- @param number Number of the room
658 -- @return Table containing room information
659 function meetme.room(n)
661 uci:foreach("asterisk", "meetme",
663 if r.room == tostring(n) then
664 room = meetme.parse(r)
670 --- Find all meetme rooms within the given dialplan
671 -- @param plan Dialplan name or table
672 -- @return Associative table containing extensions mapped to room info
673 function meetme.in_dialplan(p)
674 local plan = type(p) == "string" and p or p.name
676 uci:foreach("asterisk", "dialplanmeetme",
678 if s.extension and #s.extension > 0 and s.dialplan == plan then
679 local room = meetme.room(s.room)
681 rooms[s.extension] = room
688 --- Remove meetme room and associated extensions from config
689 -- @param room Voicemailbox number or table
690 -- @param ctx UCI context to use (optional)
691 -- @return Boolean indicating success
692 function meetme.remove(v, ctx)
694 local room = type(v) == "string" and v or v.number
695 local ok1 = ctx:delete_all("asterisk", "meetme", {room=room})
696 local ok2 = ctx:delete_all("asterisk", "dialplanmeetme", {room=room})
697 return ( ok1 or ok2 ) and true or false
701 --- LuCI Asterisk - Dialplan
703 dialplan = luci.util.class()
705 --- Parse a dialplan section
706 -- @param plan Table containing the plan info
707 -- @return Table with parsed information
708 function dialplan.parse(z)
713 description = z.description or z['.name']
717 for _, name in ipairs(tools.parse_list(z.include)) do
718 local zone = dialzone.zone(name)
720 plan.zones[#plan.zones+1] = zone
725 plan.voicemailboxes = voicemail.in_dialplan(plan)
727 -- meetme conferences
728 plan.meetmerooms = meetme.in_dialplan(plan)
734 --- Get a list of known dial plans
735 -- @return Associative table of plans and table of plan names
736 function dialplan.plans()
739 uci:foreach("asterisk", "dialplan",
741 plans[p['.name']] = dialplan.parse(p)
742 pnames[#pnames+1] = p['.name']
747 --- Get a specific dial plan
748 -- @param name Name of the dial plan
749 -- @return Table containing plan information
750 function dialplan.plan(n)
752 uci:foreach("asterisk", "dialplan",
754 if p['.name'] == n then
755 plan = dialplan.parse(p)