Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

...

  • 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

Warning

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

Note

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.

...

Code Block
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;

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:

Code Block
ALTER USER postgres WITH PASSWORD 'password123';

Now edit /var/lib/pgsql/data/pg_hba.conf to be exactly like:

...

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:

Code Block
ALTER USER postgres WITH PASSWORD 'password123';

Now edit /var/lib/pgsql/data/pg_hba.conf to be exactly like:

Code Block
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:

Code Block
    <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}"/>
       all <param name="comfort-noise" value="true"/>
       ::1/128     </profile>
Note

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):

Code Block
   <extension name="Page Test">
     <condition    ident sameuser
local all conftest crypt

Now restart postgresql

FreeSWITCH

Add the following to your FreeSWITCH dialpan:

Code Block
   <extension name="Page Test">field="destination_number" expression="^(3402)$">
      <action application="lua" data="/usr/local/freeswitch/scripts/page_outcall.lua $1"/>
      <condition<action fieldapplication="destination_numberset" expression="^(3402)$"data="ivr_path=/usr/local/freeswitch/scripts/page_int.php"/>
      <action application="luasocket" data="/usr/local/freeswitch/scripts/page_outcall.lua $1"/"127.0.0.1:9090 async full"/>
     </condition>
   </extension>

   <extension name="outbound-socket">
      <action<condition applicationfield="setdestination_number" data="ivr_path=/usr/local/freeswitch/scripts/page_int.php"/expression="^(3403)$">
      <action application="socketlua" data="127.0.0.1:9097 async full/usr/local/freeswitch/scripts/page_time_set.lua"/>
     </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>

Add the following scripts to your /usr/local/freeswitch/scripts folder:

Code Block
titlepage_int.php
#!/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

/extension>

now reload FreeSWITCH

Add the following scripts to your /usr/local/freeswitch/scripts folder:

Code Block
titlepage_int.php
#!/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: setsleep\n";
echo "execute-app-arg: conference_auto_outcall_flags=mute\n\n";
 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: api_hangup_hook=tmp=\${conference $1 kick allplay /usr/local/freeswitch/sounds/tones/norstar.wav}\n\n";

echo "sendmsg\n";
echo "call-command: execute\n";
echo "execute-app-name: conference\n";
echo "execute-app-arg: $1@default\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);

?>
Code Block
titlepage_outcall.lua
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","fail2sxp","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
		fclose($in);

?>
Code Block
titlepage_outcall.lua
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
Code Block
titlepage_time_set.lua
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","fail2sxppassword123","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()