Features:
1. Table-controlled, easy to configure.
2. Speed dialing table.
3. Accepts all number formats (with and without national / international prefix, local numbers, etc.
4. Supports ENUM.
5. Both automatic and manual selection of VoIP providers.
Writing this article, I wanted to concentrate on processing of outbound calls. As to incoming calls, they are merely forwarded to local user (that is, your ATA). If you need more elaborated processing you may want to inspect my other thread in this forum.
Usage:
The idea I had in mind was to write a code that would let you use your ATA / WiFi phone / VoIP-enabled cellphone in a usual manner; you wouldn't need to change your contacts, phonebooks etc. The code will remove all fancy characters like spaces, dashes and brackets, take care of national / international prefixes, convert local numbers into ENUM format (country code followed by area code and phone number). Then, the code will automatically select best VoIP provider for your call.
If you want to override automatic provider selection and manually pick specific provider for a call, dial pound (#) followed by provider code (0 to 9, * or #) and the number you wish to call.
You can have your own speed dial table. You can dial, say, "1" and it will be replaced by full phone number from your speed dial table.
Well, let's get to the code:
Code: Select all
#Ruby
# Speed dial entries. Format: "key" => "number"
Speeddial = {
'0' => '123-4567', # Home
'1' => '1 212 555-1212', # Mom home
'303' => '303@sip.blueface.ie', # Blueface speaking clock.
'612' => '612@fwd.pulver.com', # FWD speaking clock.
'100' => '303@sip.blueface.ie&612@fwd.pulver.com'
}
# 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
'7' => '@ Globe7', # Globe7
'8' => '@ Voxalot', # Voxalot
'9' => '@ Arinos', # Arinos
'*' => '* @ Voxalot', # Voxalot with star prefix
}
# VoIP provider selection
def selectVSP
case @num
when /^\*/ # For *500, *600 and other Voxalot services
@p,@l = 8,"Call to Voxalot services"
when /^7(495|499|812)/, # Moscow, SPB
/^7(496|4012|3532|8632|8212|4232)/ # Moscow region, Kaliningrad, Orenburg, Rostov, Syktyvkar, Vladivostok
@p,@l = 9,"Destination - Russia, discount rates from Arinos"
when /^1[2-9]\d{9,9}$/ # North America
case @num[1,3] # check area code
when "800", "866", "877", "888" # toll free numbers...
@p,@l = '*',"Destination - US toll free"
else
@p,@l = 4,"Destination - North America"
end
else
@p,@l = 0,"Default route applied"
end
end
def dial(*args)
sys.Log(@l) unless @l.empty? # for the record :)
if tpl=VSPtab[@p.to_s] # if provider is in our table
@num = tpl.sub(/\s*@\s*/) {|x| @num+'@'} # Insert number before '@'
end
sys.Dial(@num,*args)
end
# ************************** M A I N *************************************
sys.Log("** Call from #{req.Header.From.ToString()} to #{req.URI.User} **")
@l = @p = '' # Initialize vars
if sys.In # If incoming call...
if sys.IsAvailable() # If my ATA is registered
sys.Dial("#{sys.Username}@local") # forward all incoming calls to it
else
sys.Log("#{sys.Username} is not online.")
sys.Respond(480, "#{sys.Username} Not online")
end
else # Outbound call ...
@num = req.URI.User.to_s # String copy of the number
@num = Speeddial[@num] if (Speeddial[@num]) # Check if speed dial entry exists for the number
unless @num =~ /@/ # If we have URI, skip processing
@num.gsub!(/%../) {|x| x[1,2].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)
# 810 - Russian style international prefix (810)
unless @num.sub!(/^(\+|00|011|810)/,'') # If no international prefix, process special cases below
case @num
when /^82\d{7,7}$/ # Calling to Moscow region, old format (before 496 area code)
@num = '7496' + @num[2..-1]
when /^8/
@num[0] = '7' # Russian style long distance prefix (8), replace 8 with country code (7)
when /^[1-9]\d{6,6}$/
@num = '7495' + @num # Local call (Moscow 495 area)
end
end
sys.Log("Number in ENUM format: #{@num}")
unless @p.empty? # If user explicitly selected a provider
@l = "Forced routing to provider #{@p}, template '#{VSPtab[@p]}'"
dial
return # Comment out this line if you want to try
end # normal routing if forced routing call failed
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
result = sys.Dial(enumuri).ToString() # if call failed, call via regular VSP.
# If this is not what you want, add "return"
sys.Log("Call to #{enumuri} #{result}, will call again via regular VoIP provider")
end
@p, @l = '' # re-initialize variables
selectVSP # Pick appropriate provider for the call
end # URI
dial # dial selected number or URI
end
Speedial
This table contains your speed dial entries. Format is self-explaining, I just wanted to point out that:
1. Speed dial table is checked first hand. If there is a match, dialed number will be replaced with table entry (for example, if you dial "1" it will be substituted with "1 212 555-1212"). You can have speed dial keys in any desired format (1, 2, 3 or *1, *2, *3 etc; you can mix different styles, too!).
2. I do not recommend the use of '#' in speed dial keys (a bit tricky, but still possible - PM for details).
3. The right side of Speed dial table can contain both phone numbers and URIs (as demonstrated in the entry for "303"). URIs are passed unchanged to sys.Dial and therefore, you can use special features like '&' (parallel dialing) or '|' failover (see entry for "100").
4. You can override automatic provider selection using '#' prefix, for example:
'4' => '#4 1 (212) 555-1212'
Alternatively, you can use URI, like this:
'4' => '12125551212@Provider4'
Note that in latter case you can't use fancy characters like spaces, brackets etc.
VSPtab and selectVSP method
VSPtab holds dialing templates for your VoIP providers. Left side (key) is what you need to dial after pound (#) to select this particular provider. Right side contains optional prefix followed by '@' and finally, Provider Name. The code will insert the number right before '@'. Spaces before and after '@' are ignored.
The selectVSP method is called to determine which VoIP provider is the best for this particular call. The decision is entirely upto you It takes ENUM-formatted phone number as input, it's output is stored in @p (provider code) and @l (optional comment that will appear in monitoring window). I deliberately placed selectVSP next to VSPtab to make your editing easy.
I use two providers named F9 and Arinos; Arinos is the best for calls to some cities in Russia (they give the best rates and quality) while F9 is used for all other calls.
Note that F9 lets you select "Grey", "White" or "Premium" routes by dialing 02, 03 or 04 prefixes before the number. I use Premium route for all my calls to the US/Canada (because they charge for "Premium" at the same rate as "Grey"). All other calls are using "Grey" route.
Calls to 1-800 and other free numbers are passed to Voxalot (and Voxalot requires '*' prefix for these calls).
Well, it's my routing scheme. You should design yours, it's not difficult at all! You can have it your way, from selecting by country / area code like I do to picking providers at random using Ruby's rand() method
Note that VSPtab can contain entries not used by selectVSP (like Globe7 in my table). Normally I don't use this provider because their rates are substantially higher. I keep it as a backup. If something goes wrong with my regular provider I can try Globe7 by dialing #7 before the number.
Vice versa, selectVSP can route calls thru VoIP providers not accessible in manual mode. Just use a key which can't be dialed from a phone:
'f' => '@SecretProvider'
Since you can't dial #f on a telephone, your SecretProvider will only be used by automated routing.
National / local numbers substitution
This is done here:
Code: Select all
unless @num.sub!(/^(\+|00|011|810)/,'') # If no international prefix, process special cases below
case @num
when /^82\d{7,7}$/ # Calling to Moscow region, old format (before 496 area code)
@num = '7496' + @num[2..-1]
when /^8/
@num[0] = '7' # Russian style long distance prefix (8), replace 8 with country code (7)
when /^[1-9]\d{6,6}$/
@num = '7495' + @num # Local call (Moscow 495 area)
end
end
when /^[1-9]\d{6,6}$/
@num = '7495' + @num # Local call (Moscow 495 area)
What about national numbers? In Russia they dial 8 followed by area code and then the number. So, we replace leading '8' with '7' - country code. For Europe, you'll need to check on leading '0' and replace it with the country code, like that:
when /^0/
@num = '33' + @num[1..-1] # National call, replace zero with 33 (country code for France)
ENUM processing
This feature is fully implemented in my code but unfortunately, not completely functional due to a bug. Anyway, if ENUM dialing fails, the code will try to call the number in a regular manner. This works fine for me Once the bug is taken care of, the code will become fully operative.
And yet, ENUM works in automated mode only. If you use '#' prefix, the call will be passed to selected provider even though there is a NAPTR record for this number in ENUM database.
Well, I guess that's enough to give you a kick-start. Enjoy!