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:
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!
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.
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.)
Software
I thought I'd start by trying the method outlined in "Asterisk The Definitive Guide": compiling 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.
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.
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.
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!
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.
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 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
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);
}
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!
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:
Challenges:
Next steps
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 ifI'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
Subscribe to:
Posts (Atom)