Friday, December 28, 2012

Raspberry Pi WAP & PBX


UPDATE:
I just installed FreePBX/Asterisk on the Raspberry Pi and, using the configuration from the version on my "big PBX",  I configured the RPi.  I just made my first outgoing call.  The setup and config took about an hour including downloading the RPi image and burning it to an SD card.  The System Statistics showed about a 2% CPU load during the call.  Amazing, just amazing...

Now I'll have to try out the RPi as a WAP - these devices are cool, but the support community and developers are truly astounding.




For those of you who may not be in the Raspberry Pi loop, here's a link to an interesting development.  This project turns a raspberry pi and a cheap WiFi adapter into a Wireless Access Point.





Last week I read about using an RPi as a PBX with Asterisk.   I've been enjoying working with my PBX in a Flash box and maybe shrinking it to the RPi form factor will be its next iteration!


The more people have time to play with this little device, the more cool things they dream up.  After all: if you can think it, you can do it!


Sunday, December 23, 2012

Home PBX Project - part three

I installed PBX in a Flash on an Intel D945GCLF2 mini ITX computer that I used to use as a mini XP box - the performance with XP was only OK, but running FreePBX/Asterisk with CentOS on it is great.  

It can handle multiple incoming and outgoing calls without breaking a sweat (I tested 3 concurrent connections so far with processor load under 50%.)

I'm running it "headless", with just an ethernet and power cable plugged in.  

To the left is picture of the new home PBX.

I've loaded up Ubuntu on the PC I was using for the PBX - everybody wins!








Friday, December 21, 2012

Home PBX Project - part two

I made some good progress last night configuring FreePBX/Asterisk.  

Alex Robar has written a very useful ebook called FreePBX 2.5 Powerful Telephony Solutions that I read through prior to attempting to configure FreePBX and used as a reference manual.

There are a half dozen things that need to be done in order to get incoming and outgoing calls going:

  • Configure a trunk - this is the connection to the SIP provider (who actually connects to the phone network.)
  • Create extensions - this sets up the connections that endpoints / handsets on the network use to call or receive calls
  • Set up outbound rules - this provides the connection between end-points and the trunk.
  • Create a "ring group" - this is how to cause incoming calls to ring end-points
  • Create inbound rules - this is how to route calls


There are less than a couple of dozen options to set up across all of these functions to get things running.  I used a great tutorial from Mark Berry to make sure I had all the critical components set up correctly.

I already knew how to set up an endpoint - I used X-Lite on my PC and on my wife's laptop for testing and Bria on my iPad and iPhone. After that worked, I set up an OBi302 adapter (it converts an analog phone to an IP phone).  I set up the OBi with a wireless USB adapter so I could connect it to a phone in the living room for stress testing (my wife uses that phone for long distance and international calls to her sister and mother.)

It worked - I set up incoming calls to ring all of the extensions I set up and also routed calls to ring to my cell phone as well.

Because of some drop outs using the old Dell desktop as a server, I "upgraded" and installed PIAF on my son's old tower PC.  It's a 3 GHz dual core box that performs very well after fixing a hard disk problem.  Again, it took a couple of hours to install.  And again, I had the problem entering the master password (requiring two keystrokes for each character - annoying, that.)

The new PBX is working much better (no dropouts, snappy PIAF/FreePBX interface.)

Now, all the "tuning" to get things set up just right.

So, what's my point in telling you all this?  It's just to let you know that it is possible - and not really that hard.  I had a little Linux background, which helped.  But no telephony background at all, and yet I was still able to pull it off and get something useful out of the effort using Internet resources and some equipment I had laying around.

The point is: if you can think it, you can do it!

Thursday, December 20, 2012

Home PBX Project - part one

This project is about installing a PBX and connecting to an external SIP provider for domestic and international calling.  Eventually, I plan to replace our current landline and home phone system.

Here's the high level "plan" for the project:

  • Learn about Asterisk
  • Choose a SIP provider
  • Set up a couple of SIP clients to just to get my feet wet
  • Select the hardware
  • Install and configure Asterisk, and various "helper" software
  • Set up an outgoing connection
  • Set up an incoming connection
  • Set up voice-mail and explore the features
  • Hook up a SIP phone or two along with a couple of softphones

Progress so far

I did some searching on the interwebs for someone who could connect me to the world of phones.  I ultimately selected AXVoice - I had read a couple of reviews that said they were easy to work with and their pricing was good.  I wanted unlimited domestic and international calling and they had a good package for about $17 a month.  I opted for a new number (so I could keep my existing land line while I experimented with this.)  

Just to get some experience with SIP based clients, I installed Bria on my iPad and iPhone and hooked it up to AXVoice.  I wanted to be sure that I had something working correctly before trying to configure Asterisk.  The iPad and iPhone version work well and they're relatively cheap.  The Mac version is $50, and I thought I could find something cheaper to test with.  I ultimately installed X-Lite and it seems to work fine - though you have to put up with some advertising at the bottom of the screen.

Hardware


For hardware, I had an old Dell Desktop that I had retired a few years ago.  It had a half a Gig of RAM and a 40G hard drive and a 2 GHz processor. I had already set it up with an internal static IP address on an internal switch, inside a router connected to a cable modem for access to the Internet.


Software

I thought I'd start by trying the method outlined in "Asterisk The Definitive Guide": compiling Asterisk.  

I know, I know... I figured this probably wouldn't work, since the book was based on an earlier version and things change quickly.  And, I was right.  I ran into several stoppers around missing libraries, things that didn't compile correctly, old instructions, old advice, etc., etc.

But my "Plan B" was to install PBX in a Flash.  There are some good getting-started instructions at Nerd Vittles.  So I grabbed the ISO image from SourceForge (PIAF-2.0.6.2.4-CentOS-6.2-32bit) and burned it to a DVD.  It's pretty much a PBX-in-a-box including the CentOS operating system (a Red Had Linux derivative), Asterisk and FreePBX - a really (really) handy front-end to Asterisk.


I installed the "purple" version which loads the OS, Asterisk 1.8 and FreePBX 2.10.  This took several hours and a few reboots.  The only problem I had was entering a "master password" - for some reason the app would only "hear" the keys from my keyboard (2 that I tried) if I pressed them twice in rapid succession.  This took a while to figure out... I still don't know what the problem was, but I managed to get the password established.  That problem never occurred anywhere else in the installation or at any other time on that machine.

Once it was installed and running, I connected via SSH, just to see if I could. I then connected to the FreePBX GUI via a browser connected to the web server that is included in the configuration.  No windows manager gets installed on top of CentOS, so you do everything using CLI and the web app.   The web interface is well done and functions well. 

Next time

I'll be working with Mabel to set up an outgoing connection!

It's fun, so far!

Wednesday, December 19, 2012

Next project: A Home PBX

Telephony has come a long way.

It used to be that a PBX cost thousands of dollars and required staff to set up and manage it.

Now, all it takes is a PC, a connection to an ISP and a SIP service provider (along with some free software) and you're up and running.

Right...  That's all it takes...  Should be a half-hour's work...



OK - I've heard this kind of thing before - and I've learned that whenever someone starts a sentence with "It's really very easy. All you have to do is...", they've:
    A.) overestimated my ability;
    B.) read about it but have never actually done it;
    C.) already mastered the subject in question and forgotten what it's like starting at the beginning.

I'll be starting this project using an easy-to-use version of Asterisk (PIAF) on an old PC running CentOS (a version of Linux) and reporting on my progress here.

Monday, December 17, 2012

Caller ID Project - part eight

Testing is complete - works great.  I've created a shortcut on my iPad so that whenever the phone rings, I can immediately go to the web page to see who's calling.  The only thing that would be better would be for it to send me an immediate text message - I'll put that on the enhancement list.

Regarding a metal chassis for the project - since it uses a wireless connection to my network, I can't really encase the project in metal... (Duh...)  so I'll put the modem and RPi in a plastic box with power and phone jacks on one side. 

Also, I've decided not to include the LCD display.  Since the output is available to any browser on the network and since it's likely to be hidden behind a cabinet somewhere close to an otherwise unused phone jack, it just doesn't make sense.  Also, the LCD display (the one I was testing with) is an energy hog. Less heat, fewer parts -> more reliability.

plastic project box



Saturday, December 15, 2012

Caller ID Project - part seven

Above is a screen print of the Caller ID web page 
produced by the app.
The integration test went well - a couple of days reporting various caller ID's with no problems.  This morning I made some minor code changes - did some cleanup and comment editing.  I then added code to start the app when the Raspberry Pi boots.  Thanks to Martin O'Hanlon for his post in StuffAboutCode that explains starting up a user app at boot time.

The app starts at init.d time.  The debugging output goes to the console and the Caller ID goes to the web and the LCD.  Very handy.




Here's an example of a portable digital TV I built into a 
lunchbox a couple of years ago.






I'm thinking about putting the whole thing in a metal tin just to "dress it up".

Metal tins and metal lunchboxes make great electronics projects boxes:  they're very easy to cut, they shield the contents and keep them from emitting lots of noisy RF.

Now on to wiring it up!


Thursday, December 13, 2012

Caller ID Project - part six

I got the LCD code integrated with the Caller ID app and began integration testing of the Version 0.9 code this evening.  Looking good!

Next steps

  • Complete integration testing (just let it run for a couple of days...)
  • Wire everything together and do the final testing


























Here's the code with the LCD routines integrated (see Phil Bambridge's blog for complete details on the LCD routines.)

/*
 *    main.c
 *
 *    M. Amos
 *    (LCD code by Phil Bambridge)
 *
 *      Listens for Caller ID info from USB modem, decodes it, writes it to the console, formats the info onto a
 *      web page and writes the caller name and number to an LCD display
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>

#include <arpa/inet.h>
#include <net/if.h>

#include <sys/sysinfo.h>
#include <sys/vfs.h>

#include <time.h>

#include <unistd.h>

#include <signal.h>

#include "lcd.h"

struct lcdmodule module;

int get_fld(char *, int);
int hex_to_ascii(int, int);
int hex_to_int(int);
int write_web_page(char *, char *, char *);

int main(int argc, char **argv) {
    struct lcdmodule module2;
    
    // Variables for Caller ID
    int  msg_len;
    int  msg_end;
    long fld_type;
    long fld_len;
    char fld_val [64] = "";
    int  str_ptr;
    char str_date_time [20] = "  /     :  ";
    char str_nbr [20] = "   -   -    ";
    char str_nam [64] = "";
    char str_input [256] = "\0";
    
    // Here we create and initialise two LCD modules.
    // The arguments are, in order, EN, RS, D4, D5, D6, D7.
    module = lcdInit(17, 18, 22, 23, 24, 25);
    module2 = lcdInit(21, 18, 22, 23, 24, 25);
    
    // Send configuration strings to modem
    // On Raspberry Pi, leaving echo on and status messages on causes problems with some kind of "loop back" on the
    // /dev/ttyACM0 port.  So, we have to turn them off.
    FILE *out_file;
    out_file = fopen("/dev/ttyACM0", "w");
    if (out_file == NULL) {
        fprintf(stderr, "Can't open output file\n");
    }
    fprintf(out_file,"ATE0");                                 // Turn off echo
    fprintf(out_file,"%c%c",0x0d,0x0a);
    fprintf(out_file,"ATQ1");                                 // Turn off status messages
    fprintf(out_file,"%c%c",0x0d,0x0a);
    fprintf(out_file,"AT#CID=2");                             // Turn on unformatted Caller ID
    fprintf(out_file,"%c%c",0x0d,0x0a);
    fclose(out_file);
    
    // Open modem for input
    FILE *in_file;
    in_file = fopen("/dev/ttyACM0", "r");
    if (in_file == NULL) {
        fprintf(stderr, "Can't open input file\n");
    }
    
    // display splash screen on LCD
    gotoXy(module, 0,0);
    prints(module, "Caller ID       ");
    gotoXy(module, 0,1);
    prints(module, "Version 1.0     ");
    
    while (fgets(str_input, 250, in_file) != NULL ){            // Check to see if there's input available
        if(strstr(str_input,"MESG=80")){                        // Check to see if we received a CID message
            
            // Get Message Length
            str_ptr = 7;                                        // position pointer to point at the message length field
            msg_len = get_fld(str_input, str_ptr);
            msg_end = (msg_len * 2) + 8;                        //  Adjust for two bytes per hexx digit, plus "header" + length
            str_ptr+=2;                                         // position pointer to point to the start of first field
            
            // Parse message while there's still input to be had
            while (str_ptr < msg_end){
                fld_type = get_fld(str_input, str_ptr);         // Get Field Type
                str_ptr+=2;
                
                fld_len = get_fld(str_input, str_ptr);          // Get Field Length
                str_ptr+=2;
                fld_len = fld_len * 2;
                
                int j = 0;                                      // Get Field Value
                int i = 0;
                for (i=str_ptr; i< fld_len + str_ptr - 1; i+=2){
                    fld_val[j++] = hex_to_ascii(str_input[i], str_input[i+1]);
                    fld_val[j] = '\0';
                }
                
                str_ptr += fld_len;                             // decode field values using field type
                switch (fld_type) {
                    case 1: {
                        strcpy(str_date_time, fld_val);
                    } break;
                    case 2: {
                        strcpy(str_nbr, fld_val);
                    } break;
                    case 4: {
                        strcpy(str_nbr, "No Number");
                    } break;
                    case 7: {
                        strcpy(str_nam, fld_val);
                    } break;
                    case 8: {
                        strcpy(str_nam, "No Name");
                    } break;
                }
            }
            // end while parse message
            
            // Echo date, number and name to console
            printf("Date: %s\n",str_date_time);
            printf("Number: %s\n",str_nbr);
            printf("Name: %s\n",str_nam);
            
            // Create the HTML for the web page displaying the Caller ID info
            write_web_page(str_date_time, str_nbr, str_nam);
            
            // Display Caller ID info on LCD
            gotoXy(module, 0,0);
            prints(module,str_nbr);
            prints(module,"                ");
            gotoXy(module, 0,1);
            prints(module,str_nam);
            prints(module,"                ");
        }
        else {                                                  // else extraneous input (i.e. not MESG=...)
            printf("%s\n",str_input);                           // shouldn't be any - we turned off echo and status messages
        }
    }                                                           // end while fgets
    
    
    return 0;
} // main

// Create HTML for web page to display the Caller ID info:
int write_web_page(char *str_date_time, char *str_nbr, char *str_nam){
    FILE *web_file;
    web_file = fopen("/var/www/index.html", "w");  // for Raspberry Pi use /var/www/index.html
    if (web_file == NULL) {
        fprintf(stderr, "Can't open web page output file\n");
    }
    // format the date and time and output a record to the web server.
    fprintf(web_file,"<html><body>\n");
    fprintf(web_file,"<p><b>Caller ID</b></p>\n");
    fprintf(web_file,"<p>Date: %c%c-%c%c</p>\n",str_date_time[0], str_date_time[1], str_date_time[2], str_date_time[3]);
    fprintf(web_file,"<p>Time: %c%c:%c%c</p>\n",str_date_time[4], str_date_time[5], str_date_time[6], str_date_time[7]);
    fprintf(web_file,"<p>Number: %s</p>\n", str_nbr);
    fprintf(web_file,"<p>Name: %s</p>\n", str_nam);
    fprintf(web_file,"</body></html>\n");
    
    fclose(web_file);
    
    return 0;
}

// Convert input hex characters to integer 
int hex_to_int(int c){
    int first = c / 16 - 3;
    int second = c % 16;
    int result = first * 10 + second;
    if(result > 9) result--;
    return result;
}

int hex_to_ascii(int c, int d){
    int high = hex_to_int(c) * 16;
    int low = hex_to_int(d);
    return high+low;
}

// Convert two characters from the incoming string and return as an integer
int get_fld(char *fldBuf, int bufPtr){
    char fldStr[3] = "00";
    char *p;
    fldStr[0] = fldBuf[bufPtr++];
    fldStr[1] = fldBuf[bufPtr++];
    return (int) strtoul(fldStr, &p, 16);
}

Wednesday, December 12, 2012

Caller ID Project - part five

I got the LCD display running with the Raspberry Pi (thanks again to Phil Bambridge's software.)

Next: integrate the LCD software with the Caller ID modules and integration test!


Monday, December 10, 2012

Caller ID Project - part four

So... What have I learned so far in this project?
  • How to use Xcode IDE to code up a C program
  • Installing and using GCC on the Mac
  • Communicating to a USB modem from a C application
  • The format of Caller ID messages
  • How to serve web pages on the Raspberry Pi
I found an LCD app for the Raspberry Pi written in C (by Phil Bambridge, of Bristol) so I've got something to hack for this application.  Thanks Phil!!

The app ran over night and all day today on the RPi without crashing - serving web pages, displaying caller ID's of a dozen callers and generally behaving, so I'd say the unit test is a "pass".

I've been using SSH to communicate with the RPi, editing and compiling in vi in the SSH window.  I only had to make two minor changes to the code when moving to the Raspberry Pi: change the name of the USB Modem and the path to the web page.  I had thought about building a cross compiling environment for this work, but the performance of the RPi just isn't that bad...

Good project so far!




Sunday, December 9, 2012

Caller ID Project - part three


Today I did some cleanup of the code, migrated it to the Raspberry Pi and did some unit testing.

Below is the C program that reads the USB modem output and parses the results.  I used printf's to to output to the console just to verify that it's working.  I also wrote the output to an index.html file that I'm serving with Apache on the RPi.

It took some time to get this working on the Raspberry Pi.  For some reason the /dev/ttyACM0 port (that the USB Modem was hooked up to) had a problem with command echo and the status messages being "on" - so I turned them off.

It appeared that the status messages were causing some kind of "loopback" problem.  Odd, that.

I turned on the web server on the Raspberry Pi.  Now whenever anyone calls, the web page is updated and looks something like this - still have some formatting to do...


Date: 12-09
Time: 20:46
Number: 4195551212
Name: Amos Mark

Next steps:
- Figure out how to display to an LCD display hooked up to the RPi
- Get an LCD display hooked up and tested
- Wire everything together and do integration testing

Here's the code so far:

//
//  main.c
//  USB Reader
//
//  Created by Mark Amos on 12/7/12.
//  Copyright (c) 2012 Mark Amos. All rights reserved.
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int get_fld(char *, int);
int hex_to_ascii(int, int);
int hex_to_int(int);
int write_web_page(char *, char *, char *);

int main(int argc, const char * argv[])
{

    int  msg_len;
    int  msg_end;
    long fld_type;
    long fld_len;
    char fld_val [64] = "";
    int  str_ptr;
    char str_date_time [20] = "  /     :  ";
    char str_nbr [20] = "   -   -    ";
    char str_nam [64] = "";
    char str_input [256] = "\0";
    
    // Send configuration strings to modem
    // On Raspberry Pi, leaving echo on and status messages on causes problems with some kind of "loop back" on the
    // /dev/ttyACM0 port.  So, we have to turn them off.
    FILE *out_file;
    out_file = fopen("/dev/tty.usbmodem0000001", "w");
    if (out_file == NULL) {
        fprintf(stderr, "Can't open output file\n");
    }
    fprintf(out_file,"ATE0\n");                                    // Turn off echo
    fprintf(out_file,"ATQ1\n");                                   // Turn off status messages
    fprintf(out_file,"AT#CID=2\n");                            // Turn on unformatted Caller ID
    fclose(out_file);
    
    // Open modem for input
    FILE *in_file;
    in_file = fopen("/dev/tty.usbmodem0000001", "r");
    if (in_file == NULL) {
        fprintf(stderr, "Can't open input file\n");
    }    
    
    // loop getting input from modem
    while (fgets(str_input, 250, in_file) != NULL ){            // Check to see if there's input available
        if(strstr(str_input,"MESG=80")){                          // Check to see if we received a CID message
            
            // Get Message Length
            str_ptr = 7;                                                // position pointer to point at the message length field
            msg_len = get_fld(str_input, str_ptr);
            msg_end = (msg_len * 2) + 8;                       // Adjust for two bytes per hexx digit, plus "header" + length
            str_ptr+=2;                                                // position pointer to point to the start of first field
            
            while (str_ptr < msg_end){                           // Parse message while there's still input to be had               
                fld_type = get_fld(str_input, str_ptr);          // Get Field Type
                str_ptr+=2;
                
                fld_len = get_fld(str_input, str_ptr);            // Get Field Length
                str_ptr+=2;
                fld_len = fld_len * 2;
                
                int j = 0;                                              // Get Field Value
                for (int i=str_ptr; i< fld_len + str_ptr - 1; i+=2){
                    fld_val[j++] = hex_to_ascii(str_input[i], str_input[i+1]);
                    fld_val[j] = '\0';
                }
                
                str_ptr += fld_len;                                 // decode field values using field type
                switch (fld_type) {
                    case 1: {
                        strcpy(str_date_time, fld_val);
                    } break;
                    case 2: {
                        strcpy(str_nbr, fld_val);
                    } break;
                    case 4: {
                        strcpy(str_nbr, "No Number");
                    } break;
                    case 7: {
                        strcpy(str_nam, fld_val);
                    } break;
                    case 8: {
                        strcpy(str_nam, "No Name");
                    } break;
                }
            }                                                         // end while parse message
            printf("Date: %s\n",str_date_time);
            printf("Number: %s\n",str_nbr);
            printf("Name: %s\n",str_nam);
            write_web_page(str_date_time, str_nbr, str_nam);
        }
        else {                                                      // else extraneous input (i.e. not MESG=...)
            printf("%s\n",str_input);                          // shouldn't be any - we turned off echo and status messages
        }
    }                                                               // end while fgets
}                                                                   // end main()

// Write out the web page that shows date, time, number, name
int write_web_page(char *str_date_time, char *str_nbr, char *str_nam){
    FILE *web_file;
    web_file = fopen("/users/markamos/Sites/index.html", "w");  // for Raspberry Pi use /var/www/index.html
    if (web_file == NULL) {
        fprintf(stderr, "Can't open output file\n");
    }
    // format the date and time and output a record to the web server.
    fprintf(web_file,"<html><body>\n");
    fprintf(web_file,"<p>Date: %c%c-%c%c</p>\n",str_date_time[0], str_date_time[1], str_date_time[2], str_date_time[3]);
    fprintf(web_file,"<p>Time: %c%c:%c%c</p>\n",str_date_time[4], str_date_time[5], str_date_time[6], str_date_time[7]);
    fprintf(web_file,"<p>Number: %s</p>\n", str_nbr);
    fprintf(web_file,"<p>Name: %s</p>\n", str_nam);
    fprintf(web_file,"</body></html>\n");
    fclose(web_file);
    
    return 0;
}

// Convert input hex characters to integer
int hex_to_int(int c){
    int first = c / 16 - 3;
    int second = c % 16;
    int result = first * 10 + second;
    if(result > 9) result--;
    return result;
}

int hex_to_ascii(int c, int d){
    int high = hex_to_int(c) * 16;
    int low = hex_to_int(d);
    return high+low;
}

int get_fld(char *fldBuf, int bufPtr){
    char fldStr[3] = "00";
    char *p;
    fldStr[0] = fldBuf[bufPtr++];
    fldStr[1] = fldBuf[bufPtr++];
    return (int) strtoul(fldStr, &p, 16);
}

Friday, December 7, 2012

Caller ID project - part two

OK - it's been, ahem, "several" years since I've done any serious programming in C.

But, the way I see it, it's just like falling off a bike:  once you forget about it, it hurts more the next time you do it...

In any case, I've got a basic parser coded.  The pseudo code looks something like this right now:
Magically acquire Caller ID string 
Look for "MESG=80" in the input string 
If you find it 
    Retrieve the message length 
    While there's still message left to parse 
        Get the field type 
        Get the field length 
        Get the field 
    end while 
end if
I'm rather embarrassed to say that coding this took a couple of hours using Xcode on the Mac.  It's not pretty - it currently looks like the C equivalent of duct tape.  

Challenges:

Remember how to do string/array manipulation in C 
Convert strings of hex characters to numbers

Next steps
Clean up code (subroutine-ize it, abstract the code for field handling, make it pretty, etc.) 
Implement a read-string-from-USB function
Write date/time and number somewhere that the web server can find and use it
Figure out how to serve this information on a web server
Figure out how to get the information on an LCD display
Wire everything up
Put it in a case