Flexible table-controlled dialplan

Catalog of dial plans
MikeTelis
Posts: 1582
Joined: Wed Jul 30, 2008 6:48 am

Post by MikeTelis » Sun Oct 12, 2008 6:59 pm

You don't have a choice. I've checked and a call to "nonexistent@local" returned error code 487. Calling to valid user name like "mtelis@local" when the user is offline yields the same result. So, you can't tell between offline and non-existent user.

Mike

jvwelzen
Posts: 716
Joined: Thu Sep 11, 2008 1:56 pm

Post by jvwelzen » Sun Oct 12, 2008 7:18 pm

Oke Thanks for checking it out ??

Great Script by the way ??

battlehamster
Posts: 14
Joined: Wed Oct 22, 2008 1:04 am

Post by battlehamster » Wed Oct 22, 2008 10:58 pm

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:
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/
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:
# 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\-]$/
which preserves the callerid name, at least in my setup.

teddy_b
Posts: 65
Joined: Fri Aug 15, 2008 3:56 am

Post by teddy_b » Thu Oct 23, 2008 2:17 am

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..
I believe Mike did it to overcome a problem with caller id names containing commas (see this thread).

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...

MikeTelis
Posts: 1582
Joined: Wed Jul 30, 2008 6:48 am

Post by MikeTelis » Thu Oct 23, 2008 6:23 am

I believe Mike did it to overcome a problem with caller id names containing commas
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" :)

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.
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?
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":

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

Aaron
Site Admin
Posts: 4652
Joined: Thu Jul 12, 2007 12:13 am

Post by Aaron » Thu Oct 23, 2008 6:52 am

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

MikeTelis
Posts: 1582
Joined: Wed Jul 30, 2008 6:48 am

Post by MikeTelis » Thu Oct 23, 2008 4:31 pm

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.
It takes us back to old discussion about "delay" function in multi-forward:

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

battlehamster
Posts: 14
Joined: Wed Oct 22, 2008 1:04 am

Post by battlehamster » Thu Oct 23, 2008 7:25 pm

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.

teddy_b
Posts: 65
Joined: Fri Aug 15, 2008 3:56 am

Post by teddy_b » Thu Oct 23, 2008 8:10 pm

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.
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.

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.

teddy_b
Posts: 65
Joined: Fri Aug 15, 2008 3:56 am

Post by teddy_b » Thu Oct 23, 2008 9:00 pm

MikeTelis wrote:my SIP phone will look up for the number in its phone book and replace it with caller's name, bingo!
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 :).

Post Reply