1 -- Copyright 2009 Jo-Philipp Wich <jow@openwrt.org>
2 -- Licensed to the public under the Apache License 2.0.
4 module("luci.asterisk", package.seeall)
5 require("luci.asterisk.cc_idd")
7 local _io = require("io")
8 local uci = require("luci.model.uci").cursor()
9 local sys = require("luci.sys")
10 local util = require("luci.util")
12 AST_BIN = "/usr/sbin/asterisk"
16 --- LuCI Asterisk - Resync uci context
18 uci = luci.model.uci.cursor()
21 --- LuCI Asterisk io interface
22 -- Handles low level io.
24 io = luci.util.class()
26 --- Execute command and return output
27 -- @param command String containing the command to execute
28 -- @return String containing the command output
29 function io.exec(command)
30 local fh = _io.popen( "%s %s %q" %{ AST_BIN, AST_FLAGS, command }, "r" )
31 assert(fh, "Failed to invoke asterisk")
33 local buffer = fh:read("*a")
38 --- Execute command and invoke given callback for each readed line
39 -- @param command String containing the command to execute
40 -- @param callback Function to call back for each line
41 -- @return Always true
42 function io.execl(command, callback)
44 local fh = _io.popen( "%s %s %q" %{ AST_BIN, AST_FLAGS, command }, "r" )
45 assert(fh, "Failed to invoke asterisk")
56 --- Execute command and return an iterator that returns one line per invokation
57 -- @param command String containing the command to execute
58 -- @return Iterator function
59 function io.execi(command)
60 local fh = _io.popen( "%s %s %q" %{ AST_BIN, AST_FLAGS, command }, "r" )
61 assert(fh, "Failed to invoke asterisk")
64 local ln = fh:read("*l")
65 if not ln then fh:close() end
71 --- LuCI Asterisk - core status
72 core = luci.util.class()
74 --- Retrive version string.
75 -- @return String containing the reported asterisk version
76 function core.version(self)
77 local version = io.exec("core show version")
78 return version:gsub(" *\n", "")
82 --- LuCI Asterisk - SIP information.
84 sip = luci.util.class()
86 --- Get a list of known SIP peers
87 -- @return Table containing each SIP peer
88 function sip.peers(self)
92 for line in io.execi("sip show peers") do
95 elseif not line:match(" sip peers ") then
96 local online, delay, id, uid
97 local name, host, dyn, nat, acl, port, status =
98 line:match("(.-) +(.-) +([D ]) ([N ]) (.) (%d+) +(.+)")
100 if host == '(Unspecified)' then host = nil end
101 if port == '0' then port = nil else port = tonumber(port) end
103 dyn = ( dyn == 'D' and true or false )
104 nat = ( nat == 'N' and true or false )
105 acl = ( acl ~= ' ' and true or false )
107 online, delay = status:match("(OK) %((%d+) ms%)")
109 if online == 'OK' then
111 delay = tonumber(delay)
112 elseif status ~= 'Unmonitored' then
120 id, uid = name:match("(.+)/(.+)")
122 if not ( id and uid ) then
144 --- Get informations of given SIP peer
145 -- @param peer String containing the name of the SIP peer
146 function sip.peer(peer)
150 for line in io.execi("sip show peer " .. peer) do
152 local key, val = line:match("(.-) *: +(.*)")
155 key = key:gsub("^ +",""):gsub(" +$", "")
156 val = val:gsub("^ +",""):gsub(" +$", "")
158 if key == "* Name" then
160 elseif key == "Addr->IP" then
161 info.address, info.port = val:match("(.+) Port (.+)")
162 info.port = tonumber(info.port)
163 elseif key == "Status" then
164 info.online, info.delay = val:match("(OK) %((%d+) ms%)")
165 if info.online == 'OK' then
167 info.delay = tonumber(info.delay)
168 elseif status ~= 'Unmonitored' then
177 if val == 'Yes' or val == 'yes' or val == '<Set>' then
179 elseif val == 'No' or val == 'no' then
181 elseif val == '<Not set>' or val == '(none)' then
195 --- LuCI Asterisk - Internal helpers
197 tools = luci.util.class()
199 --- Convert given value to a list of tokens. Split by white space.
200 -- @param val String or table value
201 -- @return Table containing tokens
202 function tools.parse_list(v)
205 v = type(v) == "table" and v or { v }
206 for _, v in ipairs(v) do
207 if type(v) == "string" then
208 for v in v:gmatch("(%S+)") do
209 tokens[#tokens+1] = v
217 --- Convert given list to a collection of hyperlinks
218 -- @param list Table of tokens
219 -- @param url String pattern or callback function to construct urls (optional)
220 -- @param sep String containing the seperator (optional, default is ", ")
221 -- @return String containing the html fragment
222 function tools.hyperlinks(list, url, sep)
225 local function mkurl(p, t)
226 if type(p) == "string" then
228 elseif type(p) == "function" then
239 for _, token in ipairs(list) do
240 html = ( html and html .. sep or '' ) ..
241 '<a href="%s">%s</a>' %{ mkurl(url, token), token }
248 --- LuCI Asterisk - International Direct Dialing Prefixes
250 idd = luci.util.class()
252 --- Lookup the country name for the given IDD code.
253 -- @param country String containing IDD code
254 -- @return String containing the country name
255 function idd.country(c)
256 for _, v in ipairs(cc_idd.CC_IDD) do
257 if type(v[3]) == "table" then
258 for _, v2 in ipairs(v[3]) do
259 if v2 == tostring(c) then
263 elseif v[3] == tostring(c) then
269 --- Lookup the country code for the given IDD code.
270 -- @param country String containing IDD code
271 -- @return Table containing the country code(s)
273 for _, v in ipairs(cc_idd.CC_IDD) do
274 if type(v[3]) == "table" then
275 for _, v2 in ipairs(v[3]) do
276 if v2 == tostring(c) then
277 return type(v[2]) == "table"
281 elseif v[3] == tostring(c) then
282 return type(v[2]) == "table"
288 --- Lookup the IDD code(s) for the given country.
289 -- @param idd String containing the country name
290 -- @return Table containing the IDD code(s)
292 for _, v in ipairs(cc_idd.CC_IDD) do
293 if v[1]:lower():match(c:lower()) then
294 return type(v[3]) == "table"
300 --- Populate given CBI field with IDD codes.
301 -- @param field CBI option object
303 function idd.cbifill(o)
304 for i, v in ipairs(cc_idd.CC_IDD) do
305 o:value("_%i" % i, util.pcdata(v[1]))
308 o.formvalue = function(...)
309 local val = luci.cbi.Value.formvalue(...)
310 if val:sub(1,1) == "_" then
311 val = tonumber((val:gsub("^_", "")))
313 return type(cc_idd.CC_IDD[val][3]) == "table"
314 and cc_idd.CC_IDD[val][3] or { cc_idd.CC_IDD[val][3] }
320 o.cfgvalue = function(...)
321 local val = luci.cbi.Value.cfgvalue(...)
323 val = tools.parse_list(val)
324 for i, v in ipairs(cc_idd.CC_IDD) do
325 if type(v[3]) == "table" then
326 if v[3][1] == val[1] then
330 if v[3] == val[1] then
341 --- LuCI Asterisk - Country Code Prefixes
343 cc = luci.util.class()
345 --- Lookup the country name for the given CC code.
346 -- @param country String containing CC code
347 -- @return String containing the country name
348 function cc.country(c)
349 for _, v in ipairs(cc_idd.CC_IDD) do
350 if type(v[2]) == "table" then
351 for _, v2 in ipairs(v[2]) do
352 if v2 == tostring(c) then
356 elseif v[2] == tostring(c) then
362 --- Lookup the international dialing code for the given CC code.
363 -- @param cc String containing CC code
364 -- @return String containing IDD code
366 for _, v in ipairs(cc_idd.CC_IDD) do
367 if type(v[2]) == "table" then
368 for _, v2 in ipairs(v[2]) do
369 if v2 == tostring(c) then
370 return type(v[3]) == "table"
374 elseif v[2] == tostring(c) then
375 return type(v[3]) == "table"
381 --- Lookup the CC code(s) for the given country.
382 -- @param country String containing the country name
383 -- @return Table containing the CC code(s)
385 for _, v in ipairs(cc_idd.CC_IDD) do
386 if v[1]:lower():match(c:lower()) then
387 return type(v[2]) == "table"
393 --- Populate given CBI field with CC codes.
394 -- @param field CBI option object
396 function cc.cbifill(o)
397 for i, v in ipairs(cc_idd.CC_IDD) do
398 o:value("_%i" % i, util.pcdata(v[1]))
401 o.formvalue = function(...)
402 local val = luci.cbi.Value.formvalue(...)
403 if val:sub(1,1) == "_" then
404 val = tonumber((val:gsub("^_", "")))
406 return type(cc_idd.CC_IDD[val][2]) == "table"
407 and cc_idd.CC_IDD[val][2] or { cc_idd.CC_IDD[val][2] }
413 o.cfgvalue = function(...)
414 local val = luci.cbi.Value.cfgvalue(...)
416 val = tools.parse_list(val)
417 for i, v in ipairs(cc_idd.CC_IDD) do
418 if type(v[2]) == "table" then
419 if v[2][1] == val[1] then
423 if v[2] == val[1] then
434 --- LuCI Asterisk - Dialzone
436 dialzone = luci.util.class()
438 --- Parse a dialzone section
439 -- @param zone Table containing the zone info
440 -- @return Table with parsed information
441 function dialzone.parse(z)
444 trunks = tools.parse_list(z.uses),
446 description = z.description or z['.name'],
447 addprefix = z.addprefix,
448 matches = tools.parse_list(z.match),
449 intlmatches = tools.parse_list(z.international),
450 countrycode = z.countrycode,
451 localzone = z.localzone,
452 localprefix = z.localprefix
457 --- Get a list of known dial zones
458 -- @return Associative table of zones and table of zone names
459 function dialzone.zones()
462 uci:foreach("asterisk", "dialzone",
464 zones[z['.name']] = dialzone.parse(z)
465 znames[#znames+1] = z['.name']
470 --- Get a specific dial zone
471 -- @param name Name of the dial zone
472 -- @return Table containing zone information
473 function dialzone.zone(n)
475 uci:foreach("asterisk", "dialzone",
477 if z['.name'] == n then
478 zone = dialzone.parse(z)
484 --- Find uci section hash for given zone number
485 -- @param idx Zone number
486 -- @return String containing the uci hash pointing to the section
487 function dialzone.ucisection(i)
491 uci:foreach("asterisk", "dialzone",
493 if not hash and index == i then
502 --- LuCI Asterisk - Voicemailbox
504 voicemail = luci.util.class()
506 --- Parse a voicemail section
507 -- @param zone Table containing the mailbox info
508 -- @return Table with parsed information
509 function voicemail.parse(z)
510 if z.number and #z.number > 0 then
512 id = '%s@%s' %{ z.number, z.context or 'default' },
514 context = z.context or 'default',
515 name = z.name or z['.name'] or 'OpenWrt',
516 zone = z.zone or 'homeloc',
517 password = z.password or '0000',
518 email = z.email or '',
523 uci:foreach("asterisk", "dialplanvoice",
525 if s.dialplan and #s.dialplan > 0 and
526 s.voicebox == v.number
528 v.dialplans[#v.dialplans+1] = s.dialplan
536 --- Get a list of known voicemail boxes
537 -- @return Associative table of boxes and table of box numbers
538 function voicemail.boxes()
541 uci:foreach("asterisk", "voicemail",
543 local v = voicemail.parse(z)
545 local n = '%s@%s' %{ v.number, v.context }
547 vnames[#vnames+1] = n
550 return vboxes, vnames
553 --- Get a specific voicemailbox
554 -- @param number Number of the voicemailbox
555 -- @return Table containing mailbox information
556 function voicemail.box(n)
558 n = n:gsub("@.+$","")
559 uci:foreach("asterisk", "voicemail",
561 if z.number == tostring(n) then
562 box = voicemail.parse(z)
568 --- Find all voicemailboxes within the given dialplan
569 -- @param plan Dialplan name or table
570 -- @return Associative table containing extensions mapped to mailbox info
571 function voicemail.in_dialplan(p)
572 local plan = type(p) == "string" and p or p.name
574 uci:foreach("asterisk", "dialplanvoice",
576 if s.extension and #s.extension > 0 and s.dialplan == plan then
577 local box = voicemail.box(s.voicebox)
579 boxes[s.extension] = box
586 --- Remove voicemailbox and associated extensions from config
587 -- @param box Voicemailbox number or table
588 -- @param ctx UCI context to use (optional)
589 -- @return Boolean indicating success
590 function voicemail.remove(v, ctx)
592 local box = type(v) == "string" and v or v.number
593 local ok1 = ctx:delete_all("asterisk", "voicemail", {number=box})
594 local ok2 = ctx:delete_all("asterisk", "dialplanvoice", {voicebox=box})
595 return ( ok1 or ok2 ) and true or false
599 --- LuCI Asterisk - MeetMe Conferences
601 meetme = luci.util.class()
603 --- Parse a meetme section
604 -- @param room Table containing the room info
605 -- @return Table with parsed information
606 function meetme.parse(r)
607 if r.room and #r.room > 0 then
611 adminpin = r.adminpin or '',
612 description = r._description or '',
616 uci:foreach("asterisk", "dialplanmeetme",
618 if s.dialplan and #s.dialplan > 0 and s.room == v.room then
619 v.dialplans[#v.dialplans+1] = s.dialplan
627 --- Get a list of known meetme rooms
628 -- @return Associative table of rooms and table of room numbers
629 function meetme.rooms()
632 uci:foreach("asterisk", "meetme",
634 local v = meetme.parse(r)
637 mnames[#mnames+1] = v.room
640 return mrooms, mnames
643 --- Get a specific meetme room
644 -- @param number Number of the room
645 -- @return Table containing room information
646 function meetme.room(n)
648 uci:foreach("asterisk", "meetme",
650 if r.room == tostring(n) then
651 room = meetme.parse(r)
657 --- Find all meetme rooms within the given dialplan
658 -- @param plan Dialplan name or table
659 -- @return Associative table containing extensions mapped to room info
660 function meetme.in_dialplan(p)
661 local plan = type(p) == "string" and p or p.name
663 uci:foreach("asterisk", "dialplanmeetme",
665 if s.extension and #s.extension > 0 and s.dialplan == plan then
666 local room = meetme.room(s.room)
668 rooms[s.extension] = room
675 --- Remove meetme room and associated extensions from config
676 -- @param room Voicemailbox number or table
677 -- @param ctx UCI context to use (optional)
678 -- @return Boolean indicating success
679 function meetme.remove(v, ctx)
681 local room = type(v) == "string" and v or v.number
682 local ok1 = ctx:delete_all("asterisk", "meetme", {room=room})
683 local ok2 = ctx:delete_all("asterisk", "dialplanmeetme", {room=room})
684 return ( ok1 or ok2 ) and true or false
688 --- LuCI Asterisk - Dialplan
690 dialplan = luci.util.class()
692 --- Parse a dialplan section
693 -- @param plan Table containing the plan info
694 -- @return Table with parsed information
695 function dialplan.parse(z)
700 description = z.description or z['.name']
704 for _, name in ipairs(tools.parse_list(z.include)) do
705 local zone = dialzone.zone(name)
707 plan.zones[#plan.zones+1] = zone
712 plan.voicemailboxes = voicemail.in_dialplan(plan)
714 -- meetme conferences
715 plan.meetmerooms = meetme.in_dialplan(plan)
721 --- Get a list of known dial plans
722 -- @return Associative table of plans and table of plan names
723 function dialplan.plans()
726 uci:foreach("asterisk", "dialplan",
728 plans[p['.name']] = dialplan.parse(p)
729 pnames[#pnames+1] = p['.name']
734 --- Get a specific dial plan
735 -- @param name Name of the dial plan
736 -- @return Table containing plan information
737 function dialplan.plan(n)
739 uci:foreach("asterisk", "dialplan",
741 if p['.name'] == n then
742 plan = dialplan.parse(p)