Flexible table-controlled dialplan
-
- Posts: 14
- Joined: Wed Oct 22, 2008 1:04 am
Thanks for the excellent dial plan. It was very useful for me to get up and going and get used to the ruby dial plans. I'm using it now with some minor modifications, such as a flag to turn enum on and off.
If I wanted to automatically retry the call with a second VSP if the call fails to connect with the primary VSP (because they're down or something), how would you recommend I go about it?
Also, was a bit curious about this section:
If I wanted to automatically retry the call with a second VSP if the call fails to connect with the primary VSP (because they're down or something), how would you recommend I go about it?
Also, was a bit curious about this section:
Why would you want to do that? This seems to do the opposite of what would be useful. It replaces an actual callerid name with the phone number, unless the name is empty or all digits, in which case it leaves it alone.. so you end up with callerid always being a phone number (duplicating what's already displayed on a phone), or empty. I found this was stripping out my callerid names and making it so I just saw the phone number twice, so I changed it to:name = req.Header.From.FromName.to_s # create a copy of FromName
# if FromName isn't empty and isn't all digits (caller's phone #)
# replace it with username from caller's URI
name = req.Header.from.FromURI.User.to_s if name =~ /^$|\D/
which preserves the callerid name, at least in my setup.# if FromName is empty or is all digits/dashes (caller's phone #)
# replace it with username from caller's URI
name = req.Header.from.FromURI.User.to_s if name =~ /^$|^[\d\-]$/
I believe Mike did it to overcome a problem with caller id names containing commas (see this thread).battlehamster wrote:Why would you want to do that? This seems to do the opposite of what would be useful. It replaces an actual callerid name with the phone number, unless the name is empty or all digits, in which case it leaves it alone..
However, later on Aaron fixed the issue by enclosing all caller id names into quotes, so the workaround is no longer needed. Unless of course Mike had something else in mind...
Yes, you're absolutely right, originally I added it as a workaround. I noted Aaron's fix but decided to keep the workaround in place. Reason? I receive incoming calls via SIPBroker and I get either phone number of the caller or something like "Calgary,AB" in the "From" field. When I replace the latter with caller's phone number, my SIP phone will look up for the number in its phone book and replace it with caller's name, bingo! I find this much more informative than "Calgary,AB"I believe Mike did it to overcome a problem with caller id names containing commas
Naturally, what's good for me is not necessarily good for everybody else! You can add some code limiting this workaround only for calls coming from SIPBroker or remove it at all.
There are many different ways. The most logical would be to "remember" dialed number, selected VSP and date/time when this call failed and then route it via an alternative VSP. However, it can't be implemented now because we lack "user storage", a place where we could save our data/variables between the calls (Aaron said he'd add this some time in the future). The other way is to alternate providers at random, it's easy to do in selectVSP. You also can duplicate the "dial" method (say, name it dialWithBackup) and modify callswitch code so that this method would be called instead of original "dial" for automatically routed calls (when VSP is picked by selectVSP, not by #code prefix or directly by URI). In this dialWithBackup you can either use "failover dialing":If I wanted to automatically retry the call with a second VSP if the call fails to connect with the primary VSP (because they're down or something), how would you recommend I go about it?
sys.Dial("number@provider1|number@provider2")
(where provider1 is the one picked by selectVSP and provider2 is your "backup" provider) or "manually" proceed from one provider to the other:
sys.Dial("number@provider1")
... check if returned error code is okay to proceed ...
sys.Dial("number@provider2")
Mike
For failover dialling you could also use:
sys.Dial("1234@provider1&1234@provider2@1234@provider3")
Whichever one answers first will be the one used. However it might cause problems if the phone on the other end supports call waiting and all of a sudden sees three calls arrive at once.
The most annoying problem with this sort of thing is that often a provider will take 10 to 20s to fail meaning you could be sitting there for 20s with dead air thinking something has crashed. If you use a burst failover approach as shown above it's unlikely that will happen.
Regards,
Aaron
sys.Dial("1234@provider1&1234@provider2@1234@provider3")
Whichever one answers first will be the one used. However it might cause problems if the phone on the other end supports call waiting and all of a sudden sees three calls arrive at once.
The most annoying problem with this sort of thing is that often a provider will take 10 to 20s to fail meaning you could be sitting there for 20s with dead air thinking something has crashed. If you use a burst failover approach as shown above it's unlikely that will happen.
Regards,
Aaron
It takes us back to old discussion about "delay" function in multi-forward:Whichever one answers first will be the one used. However it might cause problems if the phone on the other end supports call waiting and all of a sudden sees three calls arrive at once.
sys.Dial("num@provider1&num@provider2(10)&num@provider3(15)",timeout)
This command, if implemented, would send num to provider1 immediately, to provider2 in 10 seconds and to provider3 in 15 seconds. It would significantly decrease the probability of 3 calls arriving at the same time.
Mike
-
- Posts: 14
- Joined: Wed Oct 22, 2008 1:04 am
Thanks. When it comes to setting the timeout for the dial command, is there any way to distinguish between the connecting period (till it starts ringing) separately from when it's answered? Which does it specify?
For example, to reduce dead air time, I might want to use consecutive dial commands, and move on to the next provider if they haven't acknowledged the request within 10-15 seconds. But if it starts ringing, I'd like to wait normally. My goal is just to automatically put through the call through a second provider if the first one can't be reached or other error condition (fast busy, but not normal ringing or normal busy).
If it can't be done with existing commands, not a big deal to have folks in my house try redialing with an alternate prefix, but having it try automatically would be a nice perk.
For example, to reduce dead air time, I might want to use consecutive dial commands, and move on to the next provider if they haven't acknowledged the request within 10-15 seconds. But if it starts ringing, I'd like to wait normally. My goal is just to automatically put through the call through a second provider if the first one can't be reached or other error condition (fast busy, but not normal ringing or normal busy).
If it can't be done with existing commands, not a big deal to have folks in my house try redialing with an alternate prefix, but having it try automatically would be a nice perk.
Here's my table-controlled approach to failover dialing that does exactly that. I do not post my complete dial plan as it's huge and ugly... I ommited all the code for incoming calls, for speed dial processing, additional commands processing etc as there are a lot of samples in the other plans here. My actual table of outbound rules is also much larger (for example, I use different providers as a primary choice for different international area codes) - but you should get the idea from this part of it.battlehamster wrote:For example, to reduce dead air time, I might want to use consecutive dial commands, and move on to the next provider if they haven't acknowledged the request within 10-15 seconds. But if it starts ringing, I'd like to wait normally.
I hope the most of it is self-explanatory. The OutboundRules hashtable is used as a replacement for Mike's selectVSP procedure, and for each regex mask it contains an array of possible "routes". Each route is an array too - the first element is a phone number to dial (could be modified from the original dialed number), and the second one is provider name. When no provider name is specified, the "default" provider is used. If one route fails (each one is tried for up to 25 secs - you can change the timeout value in the OutboundCall method), the attempt is made to use the next route. If no route exists for a particular regex mask, it means the number is disallowed (i.e. USA 900, 976 area codes in the example below). The numbers in {} are replaced by corresponding $-variables available after regex match, those providing a flexible way to modify originally dialed number.
I also use 180 (ringing) response to eliminate dead air pauses, and it even gives you an audible indication when the call is switched to the next route.
The OutboundRouting procedure can also be used for Callback(), but in this case only the first available route is used.
I'm sure the code could be improved a lot, and some Ruby constructions I use are not optimal here. Also, it's probably a good idea to check the status of the call before attempting a next route... But it works, and I just do no have enough time for that... Thanks to Mike, the other Mike, and other people who posted their dial plans here, I was able to learn that much without spending hours and days reading Ruby specs (although that still would be a good thing to do) !
Code: Select all
OutboundRules = {
# Forced routing
/^\*\*(0)(.+)/ => [
["{2}"], # route all calls prefixed with **0 to default provider
],
/^\*\*(2)(.+)/ => [
["{2}", "F9"], # route all calls prefixed with **2 to F9
],
/^\*\*(3)(.+)/ => [
["{2}", "Gizmo5"], # route all calls prefixed with **3 to Gizmo5
],
/^\*\*(5)(.+)/ => [
["{2}", "Voxalot"], # route all calls prefixed with **3 to Voxalot
],
# Forward outgoing calls with 02,03,04 prefix to F9
/^0(2|3|4)(.+)/ => [
[nil, "F9"],
],
# Use F9 starting from Grey Route for international calls
/^0(0|11)(.+)/ => [
["02{2}", "F9"], # F9 Grey
["03{2}", "F9"], # F9 White
["04{2}", "F9"], # F9 Premium
],
# US Toll Free
/^1?(8)\*?((00|66|77|88)(\d{7,7}))/ => [
["*1{1}{2}", "Voxalot"],
["1{1}{2}"],
["1{1}{2}", "F9"],
["1{1}{2}", "SIPBroker"],
],
# Gizmo5 calls
/^1?((222|747)(\d{7,7}))/ => [
["{1}", "Gizmo5"], # 1-222 or 1-747 prefix to Gizmo5
],
# Disable USA Premium
/^1?((900|976)(\d{7,7}))/ => [
],
# North America numbers (add country code, and trim long numbers)
/^1?([2-9])\*?(\d{9,9})/ => [
["1{1}{2}"],
["1{1}{2}", "F9"],
],
}
def OutboundRouting(num, _sys, attempt)
prov = ""
moreAttempts = false
# Process speed dial entries and additional commands (prefixes) here
#...................
enumURI = nil
# Try ENUM Lookup here if requested
#...................
# process outbound rules
if (!num.empty?)
OutboundRules.each { |key, value|
if ((matchData = key.match(num)) && value != nil)
if value.length >= attempt
mask = value[attempt-1][0].to_s
matchData.to_a.each_index { |val|
mask.gsub!("{#{val}}", matchData[val].to_s)
}
num = mask if !(mask.empty?)
prov = value[attempt-1][1] if (value[attempt-1].length > 1)
else
num = ''
end
moreAttempts = (value.length > attempt)
break
end
}
end
# if VSP is not set yet, use the default provider
prov = "Provider1" if (prov.empty?)
if (enumURI != nil)
return enumURI.to_s, moreAttempts
elsif (num =~ /@/)
return num, moreAttempts
else
return (num.empty?) ? num : num + "@" + prov, moreAttempts
end
end
def OutboundCall(num, _sys)
moreAttempts = true
attempt = 0
while moreAttempts
attempt = attempt + 1
dial, moreAttempts = OutboundRouting(num, _sys, attempt)
if (dial.empty?)
# if outbound rules return empty number, it means we do not want to call it ;)
_sys.Log("The number #{num} is not in service.")
_sys.Respond(404, "The number #{num} is not in service")
moreAttempts = false
else
# 180 response eliminates "dead air"
_sys.Respond(180, "Ringing #{dial}")
_sys.Log("Attempt #{attempt} to route call to #{num} as #{dial}.")
if (moreAttempts)
_sys.Dial(dial, 25)
else
_sys.Dial(dial)
end
end
end
end
begin
if sys.In then
# Incoming call processing, dial local client(s)
#...................
else
# Outgoing call processing
#...................
OutboundCall(req.URI.User.to_s, sys)
end
rescue
sys.Log("End of Dialplan: " + $!) # detailed error message
end
Last edited by teddy_b on Thu Oct 23, 2008 9:04 pm, edited 1 time in total.
A-ah, I see your point! Well, since neither my phone, nor ATA can lookup a name from a phone book (even though the phone has the phone book), I use a local CNAM table in my dial plan. So for numbers not listed there I keep the name that came from a provider - even just "Calgary, AB" is sometimes better than the number with an unfamiliar area code .MikeTelis wrote:my SIP phone will look up for the number in its phone book and replace it with caller's name, bingo!