SMS remote control

I’ve been expirimenting with cheap GSM cell phones as remote control devices. I wanted to be able to control stuff at my house just by sending a text from my phone. I also wanted to use an AVR since the options are pretty limitless as to what you can control. I started off simple with relay outputs for stuff like the garage door, outside lights, etc. It could also be useful for the AVR to send texts based on events, but I haven’t messed with this yet.

Cell phone and AVR

I planned on just having the AVR recognize a particular text string, like “open garage” if I wanted to let someone in my house when I’m not there without giving them a key for example. It’s not overly secure, but you could add a number sequence as a prefix to the command that would be like a password. Then you could periodically change your password if you wanted.

I’m using cell phones from Siemens. They are older phones with true serial comms. You can score one for $20 or so from ebay. The only thing that sucks about these phones is they use PDU mode for text messages. It’s a really inconvenient way to send text strings. There’s a ton of setup to each packet and each character is encoded in seven bits. I haven’t been able to get my code working with the PDU method yet. It gets stuck trying to decode the received message. I’m going to post it here to see if anyone has any suggestions.  I’m all self-taught in C. I’ve never tried comparing strings before, so that bit is a little cumbersome. I don’t like using the built-in libraries to do that kind of stuff until I figure out how it works. The only thing I think turned out nice in the code is the unpacking function.

Obviously, you’d be ahead of the game if you found a serial-interface phone that used the regular text mode instead of PDU. I couldn’t find any phones that did text mode and didn’t have a USB interface. (at least not for cheap) Programming a USB host interface is beyond my capabilities on the AVR.

References: Developer’s Home SMS Tutorial

#include <avr/io.h>
#include <avr/interrupt.h>

#define unpack_state1 0
#define unpack_state2 1
#define unpack_state3 2
#define unpack_state4 3
#define unpack_state5 4
#define unpack_state6 5
#define unpack_state7 6
#define unpack_state8 7
#define F_CPU       8000000
#define BAUD   9600
#define MYUBRR  (F_CPU/(BAUD*(unsigned long)16))-1

void init_USART(void);
void init_timer0(void);
void turnoff_timer0(void);
void init_timer1(void);
void check_sms(void);
void decode_sms(void);
char unpack(unsigned char octet);
void USART_write(unsigned char *buf);

volatile unsigned char rcv_buf[128];
volatile unsigned char rcv_buf_idx;
volatile unsigned char check_message, check_online;

void main(void)
{
 DDRD |= 0x03;
 PORTD &= ~0x03;
 DDRC |= 0x3F;
 PORTC &= ~0x3F;
 DDRB |= 0x3F;
 PORTB &= ~0x3F;
 init_USART();
 init_timer0();
 sei();

 while(check_online<0xF7)
 {
  rcv_buf_idx= 0x00;
  USART_write("at");
  while((rcv_buf_idx<0x08)&(check_online<0x30)) {}
  if( rcv_buf[0x05]==0x4F &
   rcv_buf[0x06]==0x4B)
  {
   PORTB &= ~(1<<PORTB0);   //diagnostic leds
   PORTB &= ~(1<<PORTB5);   //diagnostic leds
   check_online= 0xFA;
  }
  else
  {
   if(rcv_buf_idx<0x01) PORTB |= (1<<PORTB5);
   PORTB |= (1<<PORTB0);
   while(check_online<0xF4){} //8s
   check_online= 0x00;
  }
 }
 turnoff_timer0();
 init_timer1();

while(1)
 {
  if(check_message) check_sms();
 }
}

ISR(USART_RX_vect)
{
 rcv_buf[rcv_buf_idx]= UDR0;
 rcv_buf_idx++;
}

ISR(TIMER0_OVF_vect)
{
 check_online++;
}

ISR(TIMER1_OVF_vect)
{
 check_message= 0x01;
}

void init_USART(void)
{
 UCSR0B |= (1<<RXCIE0)|(1<<RXEN0)|(1<<TXEN0);
 UCSR0C |= (1<<UCSZ00)|(1<<UCSZ01);

 UBRR0L = MYUBRR;
 UBRR0H = (MYUBRR >> 8);
}

void init_timer0(void)
{
 TCCR0B |= (1<<CS00)|(1<<CS02);  //clk/1024
 TIMSK0 |= (1<<TOIE0);
}

void turnoff_timer0(void)
{
 TCCR0B &= ~((1<<CS00)|(1<<CS02));  //clk/1024
 TIMSK0 &= ~(1<<TOIE0);
}

void init_timer1(void)
{
 TCCR1B |= (1<<CS10)|(1<<CS12);  //clk/1024
 TIMSK1 |= (1<<TOIE1);
}

void check_sms(void)
{
 rcv_buf_idx= 0x00;
 USART_write("at+cmgl");
 while(TCNT1<0x90) {}
 if( rcv_buf[0x0A]==0x4F &
  rcv_buf[0x0B]==0x4B) {PORTB &= ~(1<<PORTB1); PORTB &= ~(1<<PORTB5);}
 else if(rcv_buf[0x0A]=='+' &
   rcv_buf[0x0B]=='C' &
   rcv_buf[0x0C]=='M' &
   rcv_buf[0x0D]=='G' &
   rcv_buf[0x0E]=='L' &
   rcv_buf[0x0F]==':') {PORTB &= ~(1<<PORTB1); PORTB &= ~(1<<PORTB5); decode_sms();}
 else
 {
  PORTB |= (1<<PORTB1);
  if(rcv_buf_idx<0x01) PORTB |= (1<<PORTB5);
 }
 check_message= 0x00;
}

void decode_sms(void)
{
 unsigned char offset;
 while(rcv_buf_idx<0x17){}
 unsigned char lenTPDU= (((rcv_buf[0x16] &= ~0x30)*10) + (rcv_buf[0x17] &= ~0x30));
 lenTPDU*= 2;
 while(rcv_buf_idx<0x1B){}
 unsigned char lenSMSC= (((rcv_buf[0x1A] &= ~0x30)*10) + (rcv_buf[0x1B] &= ~0x30));
 lenSMSC*= 2;
 offset= 0x1B;
 while(rcv_buf_idx<(lenSMSC+offset+lenTPDU)){}
 //ignore SMSC part
 //ignore first byte
 offset= 0x1E;
 unsigned char len_sender_number= ((rcv_buf[offset+lenSMSC] &= ~0x30)*10);
 offset= 0x1F;
 if((rcv_buf[offset+lenSMSC]>0x2F) & (rcv_buf[offset+lenSMSC]<0x3A))
  len_sender_number+= (rcv_buf[offset+lenSMSC] &= ~0x30);
 else if(rcv_buf[offset+lenSMSC]=='A') len_sender_number+= 10;
 else if(rcv_buf[offset+lenSMSC]=='B') len_sender_number+= 11;
 else if(rcv_buf[offset+lenSMSC]=='C') len_sender_number+= 12;
 else if(rcv_buf[offset+lenSMSC]=='D') len_sender_number+= 13;
 else if(rcv_buf[offset+lenSMSC]=='E') len_sender_number+= 14;
 else if(rcv_buf[offset+lenSMSC]=='F') len_sender_number+= 15;
 if(len_sender_number%2) len_sender_number++;
 rcv_buf_idx= (0x1B+lenSMSC);
 unsigned char i= len_sender_number;
 while(i)
 {
  unsigned char *sender_number= rcv_buf[++rcv_buf_idx];
  sender_number++;
  *sender_number= rcv_buf[--rcv_buf_idx];
  sender_number++;
  rcv_buf_idx += 0x02;
  i-= 0x02;
 }
 offset= 0x35;
 unsigned char num_septets= ((rcv_buf[offset+lenSMSC+len_sender_number] &= ~0x30)*10);
 offset= 0x36;
 if((rcv_buf[offset+lenSMSC+len_sender_number]>0x2F) & (rcv_buf[offset+lenSMSC+len_sender_number]<0x3A))
  num_septets+= (rcv_buf[offset+lenSMSC+len_sender_number] &= ~0x30);
 else if(rcv_buf[offset+lenSMSC+len_sender_number]=='A') num_septets+= 10;
 else if(rcv_buf[offset+lenSMSC+len_sender_number]=='B') num_septets+= 11;
 else if(rcv_buf[offset+lenSMSC+len_sender_number]=='C') num_septets+= 12;
 else if(rcv_buf[offset+lenSMSC+len_sender_number]=='D') num_septets+= 13;
 else if(rcv_buf[offset+lenSMSC+len_sender_number]=='E') num_septets+= 14;
 else if(rcv_buf[offset+lenSMSC+len_sender_number]=='F') num_septets+= 15;
 offset= 0x37;
 rcv_buf_idx= (offset+lenSMSC+len_sender_number);
 unsigned char loop= 0;
 unsigned char message[16];
 unsigned char message_idx= 0x00;
 offset= 0x1C;
 while(rcv_buf_idx<=(offset+lenTPDU+lenSMSC))
 {
  unsigned char octet= 0;
  if(rcv_buf[rcv_buf_idx]=='A') octet |= 0xA0;
  else if(rcv_buf[rcv_buf_idx]=='B') octet |= 0xB0;
  else if(rcv_buf[rcv_buf_idx]=='C') octet |= 0xC0;
  else if(rcv_buf[rcv_buf_idx]=='D') octet |= 0xD0;
  else if(rcv_buf[rcv_buf_idx]=='E') octet |= 0xE0;
  else if(rcv_buf[rcv_buf_idx]=='F') octet |= 0xF0;
  else octet= ((rcv_buf[rcv_buf_idx] &= ~0x30)*10);
  rcv_buf_idx++;
  if(rcv_buf[rcv_buf_idx]=='A') octet |= 0x0A;
  else if(rcv_buf[rcv_buf_idx]=='B') octet |= 0x0B;
  else if(rcv_buf[rcv_buf_idx]=='C') octet |= 0x0C;
  else if(rcv_buf[rcv_buf_idx]=='D') octet |= 0x0D;
  else if(rcv_buf[rcv_buf_idx]=='E') octet |= 0x0E;
  else if(rcv_buf[rcv_buf_idx]=='F') octet |= 0x0F;
  else octet+= (rcv_buf[rcv_buf_idx] &= ~0x30);
  rcv_buf_idx++;
  message[message_idx]= unpack(octet);
  message_idx++;
  if(++loop==7)
  {
   message[message_idx]= unpack(0x00);
   message_idx++;
   loop=0;
  }
 }
 if( message[0x00]=='r' &
  message[0x01]=='e' &
  message[0x02]=='l' &
  message[0x03]=='a' &
  message[0x04]=='y' &
  message[0x05]==' ' &
  message[0x06]=='1' &
  message[0x07]==' ' &
  message[0x08]=='o' &
  message[0x09]=='n') {PORTB &= ~(1<<PORTB2); PORTC |= (1<<PORTC0);}
 else if(message[0x00]=='r' &
  message[0x01]=='e' &
  message[0x02]=='l' &
  message[0x03]=='a' &
  message[0x04]=='y' &
  message[0x05]==' ' &
  message[0x06]=='1' &
  message[0x07]==' ' &
  message[0x08]=='o' &
  message[0x09]=='f' &
  message[0x0A]=='f') {PORTB &= ~(1<<PORTB2); PORTC &= ~(1<<PORTC0);}
 else if(message[0x00]=='r' &
  message[0x01]=='e' &
  message[0x02]=='l' &
  message[0x03]=='a' &
  message[0x04]=='y' &
  message[0x05]==' ' &
  message[0x06]=='2' &
  message[0x07]==' ' &
  message[0x08]=='o' &
  message[0x09]=='n') {PORTB &= ~(1<<PORTB2); PORTC |= (1<<PORTC1);}
 else if(message[0x00]=='r' &
  message[0x01]=='e' &
  message[0x02]=='l' &
  message[0x03]=='a' &
  message[0x04]=='y' &
  message[0x05]==' ' &
  message[0x06]=='2' &
  message[0x07]==' ' &
  message[0x08]=='o' &
  message[0x09]=='f' &
  message[0x0A]=='f') {PORTB &= ~(1<<PORTB2); PORTC &= ~(1<<PORTC1);}
 else if(message[0x00]=='r' &
  message[0x01]=='e' &
  message[0x02]=='l' &
  message[0x03]=='a' &
  message[0x04]=='y' &
  message[0x05]==' ' &
  message[0x06]=='3' &
  message[0x07]==' ' &
  message[0x08]=='o' &
  message[0x09]=='n') {PORTB &= ~(1<<PORTB2); PORTC |= (1<<PORTC2);}
 else if(message[0x00]=='r' &
  message[0x01]=='e' &
  message[0x02]=='l' &
  message[0x03]=='a' &
  message[0x04]=='y' &
  message[0x05]==' ' &
  message[0x06]=='3' &
  message[0x07]==' ' &
  message[0x08]=='o' &
  message[0x09]=='f' &
  message[0x0A]=='f') {PORTB &= ~(1<<PORTB2); PORTC &= ~(1<<PORTC2);}
 else if(message[0x00]=='r' &
  message[0x01]=='e' &
  message[0x02]=='l' &
  message[0x03]=='a' &
  message[0x04]=='y' &
  message[0x05]==' ' &
  message[0x06]=='4' &
  message[0x07]==' ' &
  message[0x08]=='o' &
  message[0x09]=='n') {PORTB &= ~(1<<PORTB2); PORTC |= (1<<PORTC3);}
 else if(message[0x00]=='r' &
  message[0x01]=='e' &
  message[0x02]=='l' &
  message[0x03]=='a' &
  message[0x04]=='y' &
  message[0x05]==' ' &
  message[0x06]=='4' &
  message[0x07]==' ' &
  message[0x08]=='o' &
  message[0x09]=='f' &
  message[0x0A]=='f') {PORTB &= ~(1<<PORTB2); PORTC &= ~(1<<PORTC3);}
 else if(message[0x00]=='r' &
  message[0x01]=='e' &
  message[0x02]=='l' &
  message[0x03]=='a' &
  message[0x04]=='y' &
  message[0x05]==' ' &
  message[0x06]=='5' &
  message[0x07]==' ' &
  message[0x08]=='o' &
  message[0x09]=='n') {PORTB &= ~(1<<PORTB2); PORTC |= (1<<PORTC4);}
 else if(message[0x00]=='r' &
  message[0x01]=='e' &
  message[0x02]=='l' &
  message[0x03]=='a' &
  message[0x04]=='y' &
  message[0x05]==' ' &
  message[0x06]=='5' &
  message[0x07]==' ' &
  message[0x08]=='o' &
  message[0x09]=='f' &
  message[0x0A]=='f') {PORTB &= ~(1<<PORTB2); PORTC &= ~(1<<PORTC4);}
 else if(message[0x00]=='r' &
  message[0x01]=='e' &
  message[0x02]=='l' &
  message[0x03]=='a' &
  message[0x04]=='y' &
  message[0x05]==' ' &
  message[0x06]=='6' &
  message[0x07]==' ' &
  message[0x08]=='o' &
  message[0x09]=='n') {PORTB &= ~(1<<PORTB2); PORTC |= (1<<PORTC5);}
 else if(message[0x00]=='r' &
  message[0x01]=='e' &
  message[0x02]=='l' &
  message[0x03]=='a' &
  message[0x04]=='y' &
  message[0x05]==' ' &
  message[0x06]=='6' &
  message[0x07]==' ' &
  message[0x08]=='o' &
  message[0x09]=='f' &
  message[0x0A]=='f') {PORTB &= ~(1<<PORTB2); PORTC &= ~(1<<PORTC5);}
 else PORTB |= (1<<PORTB2);
}

char unpack(unsigned char octet)
{
 static unsigned char unpack_state, remain_val;
 unsigned char septet, masked_val;

 switch(unpack_state)
 {
  case unpack_state1:
   septet= (octet & 0x7F);
   remain_val= (octet & 0x80);
   unpack_state= unpack_state2;
   break;
  case unpack_state2:
   masked_val= (octet & 0x3F);
   septet= (masked_val<<1) | (remain_val>>7);
   remain_val= (octet & 0xC0);
   unpack_state= unpack_state3;
   break;
  case unpack_state3:
   masked_val= (octet & 0x1F);
   septet= (masked_val<<2) | (remain_val>>6);
   remain_val= (octet & 0xE0);
   unpack_state= unpack_state4;
   break;
  case unpack_state4:
   masked_val= (octet & 0x0F);
   septet= (masked_val<<3) | (remain_val>>5);
   remain_val= (octet & 0xF0);
   unpack_state= unpack_state5;
   break;
  case unpack_state5:
   masked_val= (octet & 0x07);
   septet= (masked_val<<4) | (remain_val>>4);
   remain_val= (octet & 0xF8);
   unpack_state= unpack_state6;
   break;
  case unpack_state6:
   masked_val= (octet & 0x03);
   septet= (masked_val<<5) | (remain_val>>3);
   remain_val= (octet & 0xFC);
   unpack_state= unpack_state7;
   break;
  case unpack_state7:
   masked_val= (octet & 0x01);
   septet= (masked_val<<6) | (remain_val>>2);
   remain_val= (octet & 0xFE);
   unpack_state= unpack_state8;
   break;
  case unpack_state8:
   septet= (remain_val>>1);
   unpack_state= unpack_state1;
   break;
 }
 return septet;
}

void USART_write(unsigned char *buf)
{
 while(*buf)
    {
       while ( !( UCSR0A & (1<<UDRE0)) ){}
       UDR0= *buf;
       buf++;
    }
 while ( !( UCSR0A & (1<<UDRE0)) ){}
 UDR0= 0x0D;
}

Tags: , , , ,

Friday, July 16th, 2010 Electronics

8 Comments to SMS remote control

  • Antoine says:

    Hey,

    I quickly surveyed your program and found that when you are comparing strings such as in :
    message[0x01]==’e’ &
    message[0x02]==’l’ &
    message[0x03]==’a’ &

    you are using a single ampersand which is the bitwise AND operator. You want the AND logical operator which is &&. I think your comparisons will still work, but they are dependent on a true being represented by the same value while the C standard defines true as not 0. Doing it this way probably leads to a faster comparison, but its risky.

    Otherwise, I cannot see what is wrong with it. It would help a lot if it was commented.

    You probably tried sending commands to the AVR using a computer’s serial port or sniffing the contents or command from the cell-phone. Where does it stops working exactly?

  • Jon S says:

    I would look into one of the GSM modules from Mikroelektronika. They connect thru serial link to your uC. http://www.mikroe.com/eng/categories/view/19/gsm-gprs-tools/

    I’ve been eyeing some of their boards for GSM SMS capabilities. You just need to make sure that the specific module supports the SMS without being in PDU mode. The Telit GM862 supports SMS in Text mode. Although a downside is that it will probably cost more than the cellphone you bought.

  • imsolidstate says:

    Thanks, I’ll fix that. The program never returns from the decode_sms() routine. Unfortunately, there’s a lot that could go wrong there. I suspect one of my offsets to break up the PDU string is incorrect. I need to debug the code (I added a bunch of diagnostic LEDs for that purpose) but haven’t had time due to other projects. Thanks for your help!

  • imsolidstate says:

    I looked into GSM modules, but I couldn’t find any for cheap. The best deals are over at Sparkfun, but you’re spending roughly $90 to get a module. The link you sent from mikroElektronika is just the breakout board for a module, you still have to buy the module. That’s why I went with the cell phone, it’s about twenty bucks.
    The PDU thing is a pain, so spending the money is probably worth it.

  • ..that’s totally sweet. You always blow me away with this stuff. How are we even related??

    You remember when Calvin imagined that he had a huge brain and his head was all crazy and misshaped? That’s still how I think of you. Well, no.. not.. having a deformed head but…

    nevermind.

  • palas says:

    palas_ruet_eee@yahoo.com
    i need a circuit diagram on how this siemens mobile is connected to microcontroller.
    will any-one help me.
    if any one has a link or this connection diagram please mail me.palas_ruet_eee@yahoo.com

  • roboka says:

    i need a circuit diagram on how this siemens mobile is connected to microcontroller.
    will any-one help me.imsolidstate can you share it?????

  • Oleg says:

    Hi, I very interested in this project. Today once I found this topic I have examined the code with AVR Studio during some hours. I managed to duild and compile it.
    But I do not understand an idea to help us both. I would be quite grateful for the scheme.

  • Leave a Reply