Â
Potential paging system replacement
Intro
Due to limitations of the current paging server in sipXecs there have been discussions on how to replace it with a FreeSWITCH based system. I've posted rough code and basic instructions on how to implement this proof of concept paging system.
Some advantages of this setup over the existing paging system:
- Two-way paging, meaning those being paged can talk back to the pager
- WARNING: this can cause nasty feedback if phones are too close together
- End users can enable a limited "do not disturb" so they are not paged. This is limited to 99 minutes.
- Can scale to much more extensions than the current paging system since FreeSWITCH conference is used.
TO-DO:
- Write this in a sane language with proper ESL support. Lua + PHP + fs_ivrd isn't really suited for tasks this big.
- Integrate with sipXconfig, etc.
- Multi-tenant/multi-server
- Call-Info field for compatibility with snom, others
- Probably a lot more
Setup
THIS GUIDE ASSUMES YOU ARE INSTALLING ON A FRESH CentOS 5.5 x86_64 SERVER WITHOUT sipXecs ON IT AND THAT YOU'VE ALREADY BUILT FreeSWITCH FROM SOURCE
WAV files used in this example are attached
To start fs_ivrd you'll need to use this init script. Place it in /etc/init.d and mark it executable.
I'm not going to go through the particulars of setting up FreeSWITCH or installing the supporting database. I will give as much detail as possible.
You'll need to install the epel YUM repo. Install it by following the instructions here: http://fedoraproject.org/wiki/EPEL
Once you've got that repo set up you'll need to install lua and some supporting libraries, as well as postgresql
yum install lua lua-sql-postgresql postgresql postgresql-server
After those packages are installed you'll need to add a line to /usr/local/freeswitch/conf/autoload_configs/lua.conf.xml
<param name="module-directory" value="/usr/lib64/lua/5.1/?.so"/>
This is so the built in FreeSWITCH lua interpreter can utilize the postgresql library
Now, you need to execute the following SQL to create the database and table that the paging system utilizes:
CREATE DATABASE conftest; CREATE TABLE page_data (entry_id integer NOT NULL, page_group character varying(255) NOT NULL, sip_uri character varying(255) NOT NULL, page_timeout timestamp without time zone NOT NULL); CREATE USER conftest WITH PASSWORD 'password123'; GRANT ALL PRIVILEGES ON DATABASE conftest to conftest;
Here is an example of the data that should be entered in this database:
entry_id |
page_group |
sip_uri |
page_timeout |
---|---|---|---|
1 |
3402 |
7001@sipx.domain.tld |
2011-05-10 14:19:30 |
2 |
3402 |
7002@sipx.domain.tld |
2011-05-10 14:19:30 |
3 |
3402 |
7003@sipx.domain.tld |
2011-05-10 14:19:30 |
Now you need to allow conftest to log in to the database. To do this you will need to set a password on the posgresql user:
ALTER USER postgres WITH PASSWORD 'password123';
Now edit /var/lib/pgsql/data/pg_hba.conf to be exactly like:
local all all password host all all 127.0.0.1/32 password host all all ::1/128 ident sameuser local all conftest crypt
Now restart postgresql
FreeSWITCH
Create a new conference profile called intercom in 8/usr/local/freeswitch/conference.conf.xml* and set it to the following:
<profile name="intercom"> <param name="domain" value="$${domain}"/> <param name="rate" value="16000"/> <param name="interval" value="20"/> <param name="energy-level" value="300"/> <param name="sound-prefix" value="$${sounds_dir}/en/us/callie"/> <param name="muted-sound" value="conference/conf-muted.wav"/> <param name="unmuted-sound" value="conference/conf-unmuted.wav"/> <param name="alone-sound" value="conference/conf-alone.wav"/> <param name="moh-sound" value="$${hold_music}"/> <param name="enter-sound" value=""/> <param name="exit-sound" value=""/> <param name="kicked-sound" value=""/> <param name="locked-sound" value="conference/conf-locked.wav"/> <param name="is-locked-sound" value="conference/conf-is-locked.wav"/> <param name="is-unlocked-sound" value="conference/conf-is-unlocked.wav"/> <param name="pin-sound" value="conference/conf-pin.wav"/> <param name="bad-pin-sound" value="conference/conf-bad-pin.wav"/> <param name="caller-id-name" value="$${outbound_caller_name}"/> <param name="caller-id-number" value="$${outbound_caller_id}"/> <param name="comfort-noise" value="true"/> </profile>
Be sure to create a gateway pointing to you FreeSWITCH server in sipXecs and point the appropriate numbers to it
Add the following to your FreeSWITCH dialpan (change expressions to meet your needs):
<extension name="Page Test"> <condition field="destination_number" expression="^(3402)$"> <action application="lua" data="/usr/local/freeswitch/scripts/page_outcall.lua $1"/> <action application="set" data="ivr_path=/usr/local/freeswitch/scripts/page_int.php"/> <action application="socket" data="127.0.0.1:9090 async full"/> </condition> </extension> <extension name="outbound-socket"> <condition field="destination_number" expression="^(3403)$"> <action application="lua" data="/usr/local/freeswitch/scripts/page_time_set.lua"/> </condition> </extension>
now reload FreeSWITCH
Add the following scripts to your /usr/local/freeswitch/scripts folder:
#!/usr/bin/php -q <?php // set a couple of things so we dont kill the system ob_implicit_flush(true); set_time_limit(30); // Open stdin so we can read the data in $in = fopen("php://stdin", "r"); // Connect to conference echo "sendmsg\n"; echo "call-command: execute\n"; echo "execute-app-name: set\n"; echo "execute-app-arg: conference_auto_outcall_flags=mute\n\n"; echo "sendmsg\n"; echo "call-command: execute\n"; echo "execute-app-name: set\n"; echo "execute-app-arg: api_hangup_hook=conference $1 kick all\n\n"; echo "sendmsg\n"; echo "call-command: execute\n"; echo "execute-app-name: conference\n"; echo "execute-app-arg: $1@intercom\n\n"; echo "sendmsg\n"; echo "call-command: execute\n"; echo "execute-app-name: sleep\n"; echo "execute-app-arg: 350\n"; echo "event-lock:true\n\n"; // Play a prompt at the beginning of the page/conference echo "sendmsg\n"; echo "call-command: execute\n"; echo "execute-app-name: set\n"; echo "execute-app-arg: tmp=\${conference $1 play /usr/local/freeswitch/sounds/tones/norstar.wav}\n\n"; fclose($in); ?>
require "luasql.postgres" -- Exit if no argument if argv[1] == nil then print ("One argument is required") os.exit(0) end -- Get current epoch today = os.time() -- Connect to DB, get page info env = assert (luasql.postgres()) con = assert (env:connect("conftest","conftest","password123","localhost")) cur = assert (con:execute("SELECT entry_id, page_group, sip_uri, extract(epoch FROM page_timeout) FROM page_data WHERE page_group = " .. argv[1])) row = cur:fetch ({}, "a") page_table = {} i = 1 -- iterate through list of extensions to be paged and discard those on timeout while row do if tonumber(row.date_part) > today then print ("Skipping") -- keeps system from paging the pager :-P elseif row.sip_uri == sipuri then print ("Skipping") else page_table[i] = row.sip_uri end row = cur:fetch(row, "a") i = i + 1 end -- Close DB connection as we won't be needing it anymore cur:close() con:close() env:close() session:answer() cidname = session:getVariable("caller_id_name") session:execute("export", "sip_invite_params=intercom=true") session:execute("export", "sip_auto_answer=true") session:execute("set", "conference_auto_outcall_caller_id_name=Page From " .. cidname) session:execute("set", "conference_auto_outcall_caller_id_number=" .. argv[1]) session:execute("set", "conference_auto_outcall_timeout=60") -- Make the calls for i,v in pairs (page_table) do session:execute("conference_set_auto_outcall", "{alert_info=sipXpage}sofia/custom_dialplan/" .. v .. ";sipx-noroute=VoiceMail;sipx-userforward=false+flags") end
require "luasql.postgres" -- Get caller's SIP URI sipuri = session:getVariable("sip_from_uri") -- Connect to DB env = assert (luasql.postgres()) con = assert (env:connect("conftest","conftest","password123","localhost")) cur = assert (con:execute("SELECT sip_uri FROM page_data WHERE sip_uri = '" .. sipuri .. "'")) row = cur:fetch ({}, "a") uri_table = {} i = 1 while row do uri_table[i] = row.sip_uri row = cur:fetch(row, "a") i = i + 1 end -- WAKE UP! :-) session:answer() session:sleep(1000) -- If user isn't in DB then they don't need to set a timeout, now do they? if uri_table[1] == nil then session:streamFile("/usr/local/freeswitch/sounds/en/us/callie/voicemail/16000/vm-that_was_an_invalid_ext.wav") else -- Get amount of minutes user wants to be on timeout for digits = session:playAndGetDigits(1, 2, 1, 3000, "#", "/usr/local/freeswitch/sounds/tones/enter-minutes.wav", "/usr/local/freeswitch/sounds/en/us/callie/voicemail/16000/vm-abort.wav", ".+") end -- Get current epoch today = os.time() -- If user doesn't enter anything then why continue? if tonumber(digits) == nil then session:hangup() -- Update DB, split out at 20 to make file playback easier elseif tonumber(digits) < 20 then new_time = today + (tonumber(digits) * 60) session:streamFile("/usr/local/freeswitch/sounds/en/us/callie/digits/16000/" .. digits .. ".wav") session:streamFile("/usr/local/freeswitch/sounds/en/us/callie/time/16000/minutes.wav") res = assert (con:execute("UPDATE page_data SET page_timeout=to_timestamp(" .. new_time .. ") WHERE sip_uri = '" .. sipuri .. "'")) else new_time = today + (tonumber(digits) * 60) digsplit = {} for dig in digits:gmatch("%d") do table.insert(digsplit, dig) end session:streamFile("/usr/local/freeswitch/sounds/en/us/callie/digits/16000/" .. digsplit[1] .. "0.wav") session:streamFile("/usr/local/freeswitch/sounds/en/us/callie/digits/16000/" .. digsplit[2] .. ".wav") session:streamFile("/usr/local/freeswitch/sounds/en/us/callie/time/16000/minutes.wav") res = assert (con:execute("UPDATE page_data SET page_timeout=to_timestamp(" .. new_time .. ") WHERE sip_uri = '" .. sipuri .. "'")) end session:sleep(500) session:hangup() -- Kill DB cur:close() con:close() env:close()