Monday, March 2, 2015

Call Routing with the Siemens n300ip

Call Routing with the Siemens n300ip

We've had one of these DECT IP phone boxes for quite a while. In fact we've had two, but the first one had a 10Meg half duplex interface which caused a lot of grief, which caused us to get the current one.
Why did we get a DECT VOIP phone? Because I was trying to save money of course! A small amount, granted. The idea was that we would drop our phone line and just rely on cable internet with VOIP for our house phone and mobiles to cover any outages. This worked well for 3 years. The calls were cheaper and the quality better, plus we had features on the VOIP line we would have had to pay for with a land line:
  • Voicemail to email
  • Incoming CLID
  • Itemised billing
Plus we could use our number even when we were away from home by logging in with MicroSIP.
Sadly our cable provider kept hiking up the price in increments, and eventually an offer came along that offered more bandwidth with a land line for less money, and with the added bonus of free outbound weekend calls. (I am aware that Virgin did free weekend calls, but the additional cost for the land line was more than we spend on calls).
How to integrate these free calls into the current network? Well the n300ip has dial plans, they are basic but could be configured to route calls through either VOIP or the land line based on the number dialled. However the one thing it doesn't have is any logic related to dates and times. If BT's calls were cheaper than SIPGATE we might forgo the CLID and the itemised e-billing and the network voicemail, but during the week they aren't. 

Timed call routing on the cheap

The solution is a nifty script I popped together in python, the script uses libCurl, runs on Linux or Windows with suitable addition of libCurl and need python 3.3 (I leave it as an exercise for the more able to convert it so that it runs on all Python version.

The script runs Saturday morning when our server wakes from its slumber and again on Monday morning.
The call plan fields were captured by using wireshark and then edited using VIM, the script has to do session cookie handling for the password cookie, because I couldn't get libCurl (or Curl for that matter) to persist session cookies.
The functionality of the script is login, upload the call plan and then logout. The parameter is to toggle between enable.txt and disable.txt (the two call plans, enable puts UK landlines beginning 01 and 02 through the fixed line and disable.txt undos this by disabling those call plans). The script is very simple, if you change the call plans it will overwrite them.

Python Code

#!/usr/bin/env python3
import pycurl
from io import StringIO
try:
    from io import BytesIO
except ImportError:
    from StringIO import StringIO as BytesIO

import email

import argparse

def enable_disable(string):
     valid_values = ['enable','disable']
     value = str(string)
     if not value in valid_values:
        msg = "action is either enable or disable, you typed " % string
        raise argparse.ArgumentTypeError(msg)
     return value

parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('action', choices=['enable','disable'] , nargs = 1,
                   help='either enable or disable, set the dialplan in enable.txt or disable.txt')

args = parser.parse_args()

def CallPlanFileName(x):
        return {
        "enable" : 'enable.txt' ,
        "disable" : 'disable.txt' }[x]

filename = CallPlanFileName(args.action[0])

#print (args.action[0])


## Callback function invoked when body data is ready
def stdout(buf):
    # Print body data to stdout
    import sys
    sys.stdout.write(buf)
    # Returning None implies that all bytes were written

## Callback function invoked when header data is ready
def stderr(buf):
    # Print header data to stderr
    import sys
    sys.stderr.write(buf)
    # Returning None implies that all bytes were written

body=BytesIO()
header=BytesIO()

c=pycurl.Curl()
c.setopt(c.URL,'http://192.168.XX.XX/login.html')
c.setopt(c.POSTFIELDS, 'language=1&password=XXXX')
c.setopt(c.WRITEFUNCTION, body.write)
c.setopt(pycurl.HEADERFUNCTION, header.write)
c.setopt(c.COOKIEFILE, '')  #Make Curl Cookie aware
c.perform()

with open('header.txt','wb'as h:
    h.write(header.getvalue())

with open('body.txt','wb'as b:
    b.write(body.getvalue())

request_line, headers_alone = ((header.getvalue()).decode('ISO-8859-1')).split('\r\n',1)
mhead = email.message_from_string(headers_alone)

print (mhead.as_string(True))
cookies = mhead['Set-Cookie']
print (cookies)
c.setopt(c.COOKIE, cookies)

with open(filename,'r'as f:
    data=f.read()
    c.setopt(c.POST,1)
    c.setopt(c.POSTFIELDS, data)
    c.setopt(c.URL,'http://192.168.XX.XX/settings_telephony_dialplan.html')
    c.perform()

c.setopt(c.URL, 'http://192.168.XX.XX/logout.html')
c.perform()
c.close()

exit()

Call plans

Enable.txt

del_dprno=30&dpr0_0=50000&dpr1_0=0&dpr2_0=&dpr4_0=0&dpr0_1=999&dpr1_1=7&dpr2_1=&dpr4_1=1&dpr0_2=200&dpr1_2=0&dpr2_2=&dpr4_2=2&dpr0_3=101&dpr1_3=7&dpr2_3=&dpr4_3=3&dpr0_4=111&dpr1_4=7&dpr2_4=&dpr4_4=4&dpr0_5=090&dpr1_5=255&dpr2_5=&dpr4_5=5&dpr0_6=90&dpr1_6=0&dpr2_6=&dpr4_6=6&dpr0_7=01&dpr1_7=7&dpr2_7=&dpr4_7=7&dpr0_8=02&dpr1_8=7&dpr2_8=&dpr4_8=8&dpr0_9=0845&dpr1_9=7&dpr2_9=&dpr4_9=9&dpr0_10=0870&dpr1_10=7&dpr2_10=&dpr4_10=10&dpr0=&dpr6=1&dpr1=0&dpr2=&access_code_isdn=&access_code_mode_isdn=0&access_code_voip=&access_code_mode_voip=0&access_code_min_digits=4

disable.txt

del_dprno=30&dpr0_0=50000&dpr1_0=0&dpr2_0=&dpr4_0=0&dpr0_1=999&dpr1_1=7&dpr2_1=&dpr4_1=1&dpr0_2=200&dpr1_2=0&dpr2_2=&dpr4_2=2&dpr0_3=101&dpr1_3=7&dpr2_3=&dpr4_3=3&dpr0_4=111&dpr1_4=7&dpr2_4=&dpr4_4=4&dpr0_5=090&dpr1_5=255&dpr2_5=&dpr4_5=5&dpr0_6=90&dpr1_6=0&dpr2_6=&dpr4_6=6&dpr0_7=01&dpr1_7=7&dpr2_7=&dpr4_7=7&dpr0_8=02&dpr1_8=7&dpr2_8=&dpr4_8=8&dpr0_9=0845&dpr1_9=7&dpr2_9=&dpr4_9=9&dpr0_10=0870&dpr1_10=7&dpr2_10=&dpr4_10=10&dpr0=&dpr6=1&dpr1=0&dpr2=&access_code_isdn=&access_code_mode_isdn=0&access_code_voip=&access_code_mode_voip=0&access_code_min_digits=4

Cron

My server isn't on all the time, it shuts down in the evening and doesn't start up again until everyone is up and about. Initially I was puzzled why my script wasn't having any effect at the weekends. Of course I set the time of the cron job to be midnight Friday and midnight Sunday, both at times my server was asleep. Here's the solution I use:
#                    Test if it is Saturday
@reboot              test $(date +\%u) -eq 6 && ( cd $HOME/bin; ./BT_CallPlan enable ) >> ~/BT.log
#                    Test if it is Monday
@reboot              test $(date +\%u) -eq 1 && ( cd $HOME/bin; ./BT_CallPlan disable ) >> ~/BT.log

Other details:

Our cable provider was Virgin Media, we replaced this with BT Infinity 1 
  • Virgin (30M down 2.5M up) - usually 20M down 1M up
  • BT Infinity 1 (38M down 9.5M up) - so far 34M down and 9M up (much better for telecommuting)
Our VOIP provider is SIPGATE
I sold the Home Hub 5 (£35) and bought an Huawei FTTC terminal adapter/router (which I put custom firmware on).
We use a Buffalo Airstation running OpenWRT (with Asterisk running on it for VOIP), this also provides IPV6 via Hurricane Electric, 2.5 and 5 GHz wireless N and remote access. 
When we needed analogues phone lines I used an old BT voyager ADSL router (ADSL disabled) as an ATA (logged into Asterisk as an extension), of course we now have a real analogue line :) .
Virgin Media super Hub (very poor piece of kit), was set to Modem mode due to it's appalling WIFI drop out rates, and general lack of configurable options.