Flexible table-controlled dialplan, optimized for US, GV

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

Flexible table-controlled dialplan, optimized for US, GV

Post by MikeTelis » Sat Aug 29, 2009 7:35 pm

It's been a while since I published my Flexible table-controlled dialplan. A lot of nice things emerged and I'd like to publish updated version of my dialplan, now with GoogleVoice/Gizmo calling and specially adopted for US dialing habits.

These habits include dialing 7-digit numbers for local calls and 10-digit for long distance (3-digit area code followed by 7-digit number). This dialplan will take care of converting these numbers; it will also accept 11-digit, such as 1-212-555-1212. If you, by chance, try a collect call (0-212-555-1212), it will be replace leading 0 with 1 thus handling it as a regular call.

Needless to say, all domestic calls are routed via GoogleVoice and therefore, they are free.

Should you dial a "long number" (for example, 1-800-CITIBANK), the code will truncate it. This is necessary because, unlike regular ma bell, VoIP operators do not truncate long numbers and in result, can't connect the call.

There is a string attached, though. If you want to place an international call, you must dial 011 before the number (for example, 011-33-6-12-21-33-44). The dialplan will also accept 00 or '+' in lieu of 011.

Okay, let's get to the code:

Code: Select all

#Ruby

Area = '415'                       # My area code
Tz   = -8                          # Time zone (GMT-8)

# Speed dial entries. Format: "key" => "number"

Speeddial = {
  '0'   =>       '234-5678',       # Home
  '1'   => '1 408 234-1212',       # Mom
  '411' => '411@Gizmo',            # Directory
  '911' => '911@F9',               # Emergency
  '303' => '303@sip.blueface.ie',  # Blueface speaking clock
  '612' => '612@fwd.pulver.com',   # FWD speaking clock
}

# Serviced domains, must be in lowercase!

Domains  = ['sipsorcery.com','174.129.236.7']

# Excluded Prefixes. Provides a safeguard against accidentally calling premium
# numbers. Accepts both strings and patterns, spaces ignored

ExcludedPrefixes = [
   ' 1 (900 | 976 | 809)',            # USA Premium
   '44 (9 | 55 | 87 (0|1))',          # UK Premium
   '44 84 (4|5)',                     # UK Local Premium
   '44 70',                           # UK Personal Premium
   '43 (8|9)',                        # Austria Premium
   '32 (7|90)',                       # Belgium Premium
   '45 (1 | 50 (1|2|3) | 70 (1|2))',  # Denmark Premium
   '45 (8|9) 0',                      # Denmark Premium (...)
   '33 (7|9)',                        # France Premium
   '49 (1 | 900)',                    # Germany Premium
   '39 (1 | 84 | 89)',                # Italy Premium (...)
   '31 (14 | 6 (3|8|9) | 8 | 9)',     # Netherlands Premium (...)
   '48 (39 | (2|7|8) 0)',             # Poland Premium
   '46 9 (00 | 39 | 44)',             # Sweden Premium
   '41 90 (0|1|6)',                   # Switzerland Premium
]

# Providers: "key" => "template". Template format: prefix@Provider

VSPtab = {
  '0' => '00 @ F9',                # Future-nine default route
  '2' => '02 @ F9',                # Future-nine grey route
  '3' => '03 @ F9',                # Future-nine white route
  '4' => '04 @ F9',                # Future-nine premium route
  '5' => '@ Gizmo',                # Gizmo5
}

# ********************  G o o g l e V o i c e  *****************************
# !!!!!!!!!!!!    Make sure to enter your credentials below    !!!!!!!!!!!!!

def googleVoice
  sys.GoogleVoiceCall('mygmail@gmail.com','mygmailpass','17471234567',@num,'.*',7)
end

# ********************  s e l e c t   V S P  *******************************

def selectVSP    # VoIP provider selection

  # Reject calls to premium numbers unless VSP was forced

  ExcludedPrefixes.each { |p| p.gsub!(/\s*/,''); sys.Respond(503,"Calls to #{$1}* not allowed") if @num =~ /^(#{p})/ }

  case @num
    when /^1([2-9]\d\d)/                # North America
      case $1                           # check area code
#       when "808", "907", "867"        # AK, HI, Canada Yukon
#         route(4,"Destination - Alaska, Hawaii, Yukon")
        when /(800|866|877|888|747)/    # Toll-free and 1-747 calling
          route(5,"Destination - US toll-free or G5")
        else
          googleVoice
          sys.Log("GoogleVoiceCall failed, routing thru F9")
          route(4,"Destination - North America")
        end
    when /^972(5|6)/                    # Israel mobile
      route(3,"Destination - Israel mobile")
    else
      route(0,"Default route applied")
  end
end

# **************************  C A L L    S W I T C H  **********************

def callswitch(num,*args)
  route                 # Initialize vars

  @num = num unless @num = Speeddial[num]     # If there is speed dial entry for it...

  @l = "URI dialing: #{@num}" # Assume URI dialing
  unless @num =~ /@/          # If we already have URI, skip all number processing
    @num.gsub!(/%(..)/) {$1.to_i(16).chr} # Convert %hh into ASCII

    if @num =~ /^#(.)(.*)/    # If number starts with '#'
      @p = $1; @num = $2      # next char is VSP code
    end

    @num.gsub!(/[^0-9*+]/,'') # Delete all fancy chars (only digits, '+' and '*' allowed)

    # sub! below removes prefixes:
    #  '+' - international format
    #   00 - European style international prefix (00)
    #  011 - US style international prefix (011)

    unless @num.sub!(/^(\+|00|011)/,'')  # If no international prefix, process special cases below
      case @num
        when /^[2-9]\d{6}$/         # Local call, 7-digit number
          @num = "1#{Area}#{@num}"  # prefix it with country and area code
        when /^[01]?([2-9]\d{9})/   # US number with or without "1" country code
          @num = '1' + $1           # add country code and truncate number to 10-digit
        when /^\*/                  # Voxalot voicemail, echotest & other special numbers
          else
            sys.Respond(603,'Wrong number, check & dial again')
      end
    end

    sys.Log("Number in ENUM format: #{@num}")

    @l = "Forced routing to provider #{@p}, template '#{VSPtab[@p]}'" # Assume user explicitly selected VSP

    if @p.empty?        # Automatic VSP selection?

      # Invoke selectVSP prior to ENUM lookup just in case we need to modify @num

      route                 # re-initialize variables
      selectVSP             # Pick appropriate provider for the call

      if enumuri = sys.ENUMLookup("+#{@num}.e164.org") # Check if NAPTR exists for the number
        sys.Log("ENUM entry found: '#{enumuri}'")      # If yes, call that URI
        sys.Dial(enumuri)                              # if call failed, call via regular VSP.
        status()                                       # If this is not what you want, add "return"
        sys.Log("Call to #{enumuri} failed (#{@reason}), will call again via regular VoIP provider")
      end

    end # @p.empty
  end   # URI

  dial(*args)   # dial selected number or URI
end

# *******************************  D I A L  ********************************

def dial(*args)
  sys.Log(@l) unless @l.empty?               # for the record :)
  if tpl=VSPtab[@p.to_s]                     # if provider is in our table
    tpl.gsub!(/\s*/,'')                      # Remove spaces
    @num = tpl.gsub(/@/,@num+'@')            # Insert number before '@'
  end
  sys.Dial(@num,*args) # Dial
  status()             # We shouldn't be here! Get error code...
  sys.Log("Call failed: code #{@code}, #{@reason}")
end

# ******************************  R O U T E  *******************************

def route(p='', l='')
  @p = p; @l = l
end

# *****************************  S T A T U S  ******************************

def status
  if (ptr = sys.LastDialled[0]).nil?
    @code = 487; @reason = 'Cancelled by Sipsorcery'
  else
    ptr = ptr.TransactionFinalResponse
    @code = ptr.StatusCode; @reason = ptr.ReasonPhrase
#   sys.Log("#{ptr.ToString()}")
  end
end

# *******************************  M A I N  ********************************
begin
  sys.Log("** Call from #{req.Header.From.ToString()} to #{req.URI.User} **")

  t = Time.now + ((Tz+8)*60*60)  # Get current time and adjust to local. SS Server is in GMT-8
  sys.Log(t.strftime('Local time: %c'))

  if sys.In               # If incoming call...
    name = req.Header.from.FromURI.User.to_s    # Get caller ID

    # Prepend 10-digit numbers with "1" (US country code) and remove 011 prefix (if present)

    name = ('1' + name) if name =~ /^[2-9]\d\d[2-9]\d{6}$/
    name.sub!(/^(011|\+)/,'')

    sys.Log("FromName: '#{name}'")

    # Set FromName for sys.Dial. Change FromURI when forwarding to @local, or 
    # else Bria won't find contact in its phonebook!

    sys.SetFromHeader(name, nil, nil)

    if sys.IsAvailable()                              # If my ATA is registered
      callswitch("#{sys.Username}@local[fu=#{name}]") # forward all incoming calls to it
    elsif (8..23) === t.hour                          # else forward calls to my home
      sys.Log("#{sys.Username} is not online, forwarding call to home number...")
      callswitch("0",35)                              # Note that '0' is in my speed dial list
    end

    sys.Respond(480, "#{sys.Username} Not online") # switch to voice mail

  else                    # Outbound call ...

    # check if it's URI or phone number.
    # If destination's host is in our domain, it's a phone call

    num = req.URI.User.to_s; reqHost = req.URI.Host.to_s  # Get User and Host
    host = reqHost.downcase.slice(/[^:]+/)                # Convert to lowercase and delete optional ":port"

    num << '@' << reqHost unless Domains.find {|x| x == host} # URI dialing unless host is in our domain list

    callswitch(num)

  end
  sys.Respond(@code,@reason) # Forward error code to ATA
rescue
   # Gives a lot more details at what went wrong (borrowed from Myatus' dialplan)
   sys.Log("** Error: " + $!) unless $!.to_s =~ /Thread was being aborted./
end
The code is well commented but just in case...

... Instructions

1. Change "Area" to your home area code and Tz to your time zone. PST is -8, EST is -6.

2. Create your speed dial entries. Note that all "short" numbers such as 411 and 911 must be listed in this table.

3. Review ExcludedPrefixes table. Each entry contains a regular expression (pattern). If the number you dialed matches the pattern, your call will be rejected with 503 code. This way you won't be charged $$$ for accidental dialing a premium number. I borrowed the list from Myatus' dialplan and reworked it replacing "exact match" with patterns. Hope that all premium numbers are listed and you won't need to change this table but if you do that, please share the change.

4. Review and edit VSPTab. You'll find description of this table and related selectVSP method in my original topic.

5. Enter your GoogleVoice credentials and desired callback number in googleVoice method.

6. Review and edit incoming call handling section of the dialplan. Most certainly you'll need to edit this part:

Code: Select all

    if sys.IsAvailable()                              # If my ATA is registered
      callswitch("#{sys.Username}@local[fu=#{name}]") # forward all incoming calls to it
    elsif (8..23) === t.hour                          # else forward calls to my home
      sys.Log("#{sys.Username} is not online, forwarding call to home number...")
      callswitch("0",35)                              # Note that '0' is in my speed dial list
    end
That's all, now you are ready to place your first call :-)
Last edited by MikeTelis on Thu May 13, 2010 3:57 am, edited 7 times in total.

legendarygizmo
Posts: 5
Joined: Fri Oct 09, 2009 3:33 am

Post by legendarygizmo » Fri Oct 09, 2009 8:45 am

Hi MikeTelis,

Sorry to pull up this old thread, not sure if you still follow this or not, but I ran into a problem with your dialplan. For some reason, when people call my Google Voice number, it only shows my SipGate number as the caller id, instead of the person's phone number.

My current setup is, Google Voice + SipSorcery + SipGate + X-Lite softphone.

Do you you why it's showing only my SipGate number and not showing the person's caller id when they call my Google Voice number?

Any help is appreciated. Thanks. =D

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

Post by MikeTelis » Fri Oct 09, 2009 8:53 am

Seems you have posted this question in two different threads, here and in Technical Support. I have responded in the other thread and I suggest we continue this discussion there, or else the admins will get angry :oops:

trav
Posts: 120
Joined: Tue Sep 08, 2009 8:34 pm

Post by trav » Sat Oct 24, 2009 1:29 am

mike,
thanks for sharing your plan. at first i wanted to start out simple while learning ruby, but as i started trying international calls and using diff VSPs for diff routes, i'm going to your plan a lot to help me optimize my plan.

djon
Posts: 70
Joined: Wed Jun 10, 2009 10:29 am

Post by djon » Sat Nov 07, 2009 2:22 pm

Mike,
How do I modify your elegant dial plan (without breaking it) to do the following?
Dial a UK number with a UK provider. Number format 020 3123 4567. I tried adding it as speed dial but didn't work.

Hopefully I have your attention so couple more question.
How can I integrate your "multi-forward incoming GV calls to Sipsorcery" G5,IPk,Sg etc, (you discussed this in another post) and "Alternating GoogleVoice accounts"? And is it advisable.

Final.
Since you include incoming and outbound in same dial plan (any advantage?), on Sip Accounts this has to be set as IN and OUT dialplans?

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

Post by MikeTelis » Sat Nov 07, 2009 4:26 pm

Dial a UK number with a UK provider. Number format 020 3123 4567. I tried adding it as speed dial but didn't work.
Are you trying to add a speed dial entry for dialing just one particular number in the UK? If so, the number must be in full international format, like this:

'5' => '+44 20 3123 4567',

(you can replace '+' with international prefix (011).

If you're trying to modify my dialplan to suit "UK dialing traditions", it's also possible but I'll need to learn about these traditions. So far, I know that in Europe you dial 0 for long distance and 00 for international calls. And, unlike the U.S., local numbers are not always 7-digit, you have short (like 5 digit) and long (8-digit?) local numbers. Is there anything else I should know?
How can I integrate your "multi-forward incoming GV calls to Sipsorcery" G5,IPk,Sg etc, (you discussed this in another post) and "Alternating GoogleVoice accounts"? And is it advisable.
It's easy, you just replace incoming section of this dialplan with "multi-forward" code and change "googleVoice" line with "gvcall" from the other topic. Some other minor changes, and you're all set.

Is it advisable? I'm using both tricks (multi-forward and alternating GV accounts) and I find them quite useful, because call do fail, especially during rush hours.
Since you include incoming and outbound in same dial plan (any advantage?), on Sip Accounts this has to be set as IN and OUT dialplans?
I share some methods between In and Out dialplans. For example, I use callswitch('0') to forward incoming call to my home landline phone. If I had two separate dialplans, it would be more difficult to make changes (as I switch VoIP providers, for example) and keep both plans up-to-date.

And yes, both "In" and "Out" dialplans in my SIP account settings point to the same dialplan.

djon
Posts: 70
Joined: Wed Jun 10, 2009 10:29 am

Post by djon » Sat Nov 07, 2009 5:22 pm

MikeTelis wrote:
Dial a UK number with a UK provider. Number format 020 3123 4567. I tried adding it as speed dial but didn't work.
Are you trying to add a speed dial entry for dialing just one particular number in the UK? If so, the number must be in full international format, like this:

'5' => '+44 20 3123 4567',

(you can replace '+' with international prefix (011).

If you're trying to modify my dialplan to suit "UK dialing traditions", it's also possible but I'll need to learn about these traditions. So far, I know that in Europe you dial 0 for long distance and 00 for international calls. And, unlike the U.S., local numbers are not always 7-digit, you have short (like 5 digit) and long (8-digit?) local numbers. Is there anything else I should know?
I was trying the Speed Dial approach as a quick work around.
Here's what I would like to do.

When in UK and dialing a UK number you dial 0 then area code then number ie London 0207 xxx xxxx .

I am in the US. I have friends/relatives in UK with SIP accounts. I setup account with same provider. The provider customer to customer calls are free but must be dialed in UK local format 0207 xxx xxxx. (Currently doing with a less sophisticated SS dial plan)
when /^0207148/ then sys.Dial("UKprovider") Provider's block of numbers.

Your dial plan assumes it is a US number (no + 00 011)

Did I make it more confusing?

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

Post by MikeTelis » Sat Nov 07, 2009 5:51 pm

Okay, I got it! If it's just a few friends (and you can fit them all into your Speed dial table), you can use entries like this:

'5' => '0207148xxxx@UKprovider'

My dialplan will detect '@' sign in the string and process it as "URI dialing" (no further substitutions, processing etc).

Would this solve your issue?

djon
Posts: 70
Joined: Wed Jun 10, 2009 10:29 am

Post by djon » Sun Nov 08, 2009 1:52 am

MikeTelis wrote:Okay, I got it! If it's just a few friends (and you can fit them all into your Speed dial table), you can use entries like this:

'5' => '0207148xxxx@UKprovider'

My dialplan will detect '@' sign in the string and process it as "URI dialing" (no further substitutions, processing etc).

Would this solve your issue?
Thanks, got it going.
My problem was, I was using 0207148xxxx@ukProviderServerAddress without success. Obviously there was no way for the UK Provider to get my credentials.
Works when I use 0207148xxxx@SipsorceryProviderName.

If the 'few friends' increase, Is there a more flexible way, beside speed dial, to implement this without major surgery to your dial plan?

Also, where is the most appropriate section in the Table to insert "Alternating GoogleVoice accounts" method.

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

Post by MikeTelis » Sun Nov 08, 2009 4:31 am

Obviously there was no way for the UK Provider to get my credentials.
Why is that? Didn't you add UKProvider to your SIP providers on corresponding Sipsorcery webpage?
If the 'few friends' increase, Is there a more flexible way, beside speed dial, to implement this without major surgery to your dial plan?
Then you'll need to implement a full-fledged processing of UK numbers. The right place to do it is in the selectVSP method.

Code: Select all

  case @num
    when /^44(207148\d{4})/
      @num = $1
       route(9,'SIP-to-SIP call to the other UKProvider user')

    when /^44/
      # process other UK numbers here
Of course, you'll need to add corresponding entry to VSPTab:

'9' => '0 @ UKProvider'

@num = $1 will store the number without country code into @num. Then the number will be prepended with '0' (VSPTab) and routed to UKProvider.

To dial your friends, you'll need to push:

011 44 207148 followed by 4-digit subscriber number within UKProvider. Of course, this is not very convenient, that's why you may want to create a shortcut:

@num.sub!(/^\*\*/,'01144207148')

and place it right before unless @num.sub!(/^(\+|00|011)/,'') in callswitch. In this case you'll be able to dial ** (star, star) followed by 4-digit subscriber number.
Also, where is the most appropriate section in the Table to insert "Alternating GoogleVoice accounts" method.
I thought I have replied this question... you need to replace googleVoice with gvcall.

Post Reply