/******************************************************************************* Simple terminal program based on a terminal and SMS sending program Copyright (C) 2003-2011 Jiri Pittner This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *******************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define BUFL 256 #define MAXLINE 512 //GSM requires #define MAXSMS 161 #define MAXNUMBER 16 #define MAXPDU (MAXSMS*7/8*2+32)+128 #define CELL_ID_LENGTH 4 #define OPER_ID_LENGTH 7 #define MAXLIST (20*MAXPDU) //porting code originally written for atmel avr #define PSTR(X) X #define strcpy_P strcpy #define strcat_P strcat #define itoa10(n,s) sprintf(s,"%d",n) void xerror(error_text) char error_text[]; { fprintf(stderr,"smsm: %s\n",error_text); exit(10); } void usage() { printf("Usage: smsm [-H] [-u] [-D device] [-A] [-h handshake] [-Bbaud] [-S startupdelay] [-d number[:number:...]] [-V] [-I] [-l] [-p] [-m maxpiece] [-r storage_type] [-s storage_type position [number name|file]] [-f name] [name|number 0) { if(FD_ISSET(f,&readfds)) { r=read(f,buf,bufsize); if(r==0) {fprintf(stderr,"Device disconnected!\n"); exit(1);} t=write(fileno(stdout),buf,r); if(t!=r) {perror("write failed 1"); exit(10);} } if(FD_ISSET(fileno(stdin),&readfds)) { char *p; r=read(fileno(stdout),buf,bufsize); if(r==0) {fprintf(stderr,"Unexpected empty read!\n"); exit(1);} p=buf; while(*p) {if(*p == '\n') *p = '\r'; ++p;} t=write(f,buf,r); if(t!=r) {perror("write failed 2"); exit(10);} } } } } struct termios devicetermios0, ttytermios0; int filehandle; void termioscleanup() { tcsetattr(fileno(stdin),TCSANOW,&ttytermios0); tcsetattr(filehandle,TCSANOW,&devicetermios0); } void handler() { termioscleanup(); exit(0); } static int phone_fd; static jmp_buf env,env2; static void myjump() {longjmp(env,1);} static void myjump2() {longjmp(env2,1);} static void myignore(){} static FILE *logfilepointer; static unsigned int phone_not_on=1; int communicate(char *buf,char *bufend, int cs) //return 0 on success 1 on timeout, 2 on error, 3 on RING, 4 on IO error { uint8_t status, x,y,z,w; char *buf0=buf; int r; int t=0; #ifdef debug fprintf(logfilepointer,"DEBUG: communicate(timeout %d): %s\n",cs,buf); fflush(logfilepointer); #endif if(cs<0) cs= -cs; if(cs<20) cs=20; r=write(phone_fd,buf,strlen(buf)); if(r!=strlen(buf)) { fprintf(logfilepointer,"write to phone failed\n"); return 4; } buf[0]=0; status=0; if(cs) { struct itimerval itv; memset((void *)&itv,0,sizeof(itv)); itv.it_value.tv_sec = cs/100; itv.it_value.tv_usec = 10000*(cs%100); if(SIG_ERR == signal(SIGALRM,myjump)) {fprintf(logfilepointer,"signal failed\n"); return 6;}; if(0!= setitimer(ITIMER_REAL, &itv, NULL)) {signal(SIGALRM,SIG_IGN); fprintf(logfilepointer,"setitimer failed\n"); return 5;} } int setjmp_status=setjmp(env); #ifdef debug if(setjmp_status) fprintf(logfilepointer,"DEBUG: setjmp %d\n",setjmp_status); fflush(logfilepointer); #endif if(!setjmp_status) { do { buf+=strlen(buf); { int s; fd_set readfds; fd_set writefds; fd_set exceptfds; FD_ZERO(&writefds); FD_ZERO(&exceptfds); FD_ZERO(&readfds); FD_SET(phone_fd,&readfds); struct timeval timeout; timeout.tv_sec= (cs-10)/100; timeout.tv_usec= 10000*((cs-10) %100); if((s=select(phone_fd+1,&readfds,&writefds,&exceptfds,&timeout)) == -1) { fprintf(logfilepointer,"select(communicate) return code -1: %s \n",strerror(errno)); fflush(logfilepointer); signal(SIGALRM,SIG_IGN); return(1); //caught a signal during select - behave like a timeout } if(FD_ISSET(phone_fd,&readfds)) { r=read(phone_fd,buf,bufend-buf-1); //NOTE: this read can block and somehow the signal was sometimes missed, so we added the select() above #ifdef debug fprintf(logfilepointer,"DEBUG: read returned %d\n",r); #endif if(r<0) { fprintf(logfilepointer,"read from phone failed\n"); signal(SIGALRM,SIG_IGN); return 4; } buf[r]=0; //scan() in atmel did it, but read() does not! } else {signal(SIGALRM,SIG_IGN); longjmp(env,2);}//timeout } #ifdef debug fprintf(logfilepointer,"DEBUG: r=%d %s\n",r,buf); fflush(logfilepointer); #endif w= !strstr(buf,">"); z= !strstr(buf,"OK"); x= !strstr(buf,"ERROR"); y= !strstr(buf,"RING") && !strstr(buf,"BUSY"); delay_cs(100); if(r==0) ++t; //if read returns repeatedly 0 timeout if(t>60) goto do_timeout; } while(z && x && y && w); //break on sms-prompt or termination if(x==0) status=2; //error if(y==0) status=3; //ring if(z==0 || w==0) status=0; //OK #ifdef debug fprintf(logfilepointer,"DEBUG From phone: %s\n",buf0); fflush(logfilepointer); #else fputs(buf0,stdout); #endif } else { do_timeout: status=1; //timeout phone_not_on += 1; #ifdef DEBUG fprintf(logfilepointer,"Phone not answering: timeout\n"); fflush(stderr); #endif } signal(SIGALRM,SIG_IGN); //switch off deadline timer //to be sure that phone is ready to receive new data if(status!=1) delay_cs(20); return status; } void phone_reset(void) { char buf[BUFL]; strcpy_P(buf,PSTR("AT\r")); communicate(buf,buf+BUFL,150); strcpy_P(buf,PSTR("ATZ\r")); communicate(buf,buf+BUFL,150); strcpy_P(buf,PSTR("ATZ\r")); { uint8_t r; r=communicate(buf,buf+BUFL,150); //if timeout mark phone as off if(r==1) {phone_not_on=1; return;} } delay_cs(1000); //some AT commands report error until the phone is connected to the network //we will poll, do not use incoming message notification //strcpy_P(buf,PSTR("AT+CNMI=1,1,0,0,1\r")); communicate(buf,buf+BUFL,200); } void get_location(char *text) { char buf[BUFL],*p, *q; strcpy_P(buf,PSTR("AT+COPS?\r")); communicate(buf,buf+BUFL,100); p=strstr(buf,"COPS:")+5; p=strchr(p,'"')+1; q=strchr(p,'"'); *q=0; strncpy(text,p,OPER_ID_LENGTH); text[OPER_ID_LENGTH]=0; //ericsson_A2628 does not give cell id in +creg and it is thus useless #if !defined (ericsson_a2618s) strcat_P(text,PSTR(":")); text+=strlen(text); #if defined( nokia_6310i) || defined (siemens_c45) strcpy_P(buf,PSTR("AT+CREG=2\r")); communicate(buf,buf+BUFL,100); #endif strcpy_P(buf,PSTR("AT+CREG?\r")); communicate(buf,buf+BUFL,100); p=strstr(buf,"CREG:")+5; p=strrchr(p,',')+2; strncpy(text,p,CELL_ID_LENGTH); text[CELL_ID_LENGTH]=0; #endif } uint8_t get_number(uint8_t loc, char *number, char *label) { char buf[BUFL],*p,*q; strcpy_P(buf,PSTR("AT+CPBR=")); itoa10(loc,buf+strlen(buf)); strcat_P(buf,PSTR("\r")); communicate(buf,buf+BUFL,100); p=strchr(buf,','); if(!p) return 1; p+=2; if(*p == '+') ++p; //skip international + if relevant q=strchr(p,'"'); *q=0; strcpy(number,p); if(label) { p=strchr(q+2,','); if(!p) *label=0; else { p+=2; q=strchr(p,'"'); *q=0; strcpy(label,p); } } return 0; } uint8_t put_number(uint8_t loc, char *number, char *label) { char buf[BUFL],*p,*q; strcpy_P(buf,PSTR("AT+CPBW=")); itoa10(loc,buf+strlen(buf)); strcat_P(buf,PSTR(",\"")); strcat(buf,number);//number contains + strcat_P(buf,PSTR("\",145,\"")); strcat(buf,label); strcat_P(buf,PSTR("\"\r")); return communicate(buf,buf+BUFL,100); } /*PDU-coding and SMS-specific routines*/ uint8_t letter2bin (char c) { return c>'9' ? c+10-(c>='a'?'a':'A') : c-'0'; } uint8_t octet2bin(char* octet) { return (letter2bin(octet[0])<<4) | letter2bin(octet[1]); } void bin2letter(char *c, uint8_t b) { *c = b<10? '0'+b : 'A'+b-10; } void bin2octet(char *octet, uint8_t bin) { bin2letter(octet,bin>>4); bin2letter(octet+1,bin&0x0f); } void pdu2ascii(char* pdu0, char* ascii) /* converts a PDU-String to Ascii */ { /* the first octet is the length of the ascii */ uint8_t nremain,i,*binary; uint16_t len,count; char *pdu; //hexa to binary conversion count=octet2bin(pdu0); pdu=pdu0+2; len=strlen(pdu); if((count<<3)-count> (len<<2)) {strcpy_P(ascii,PSTR("ERROR:unexpected end of PDU")); return;} binary=(uint8_t *) pdu0; while(*pdu) { *binary++ =octet2bin(pdu); pdu+=2; } /* now convert from 8-Bit to 7-Bit */ binary=(uint8_t *)pdu0 -1; nremain=0; for(i=0;i 0) //some bits in current binary { ascii[i] |= (*binary>>(8-nremain)); } if(nremain < 7) //we need a new binary for this char { ascii[i] |= *++binary < len/2) {strcpy(ascii0,"unexpected end of PDU\n"); return;} char *ascii=ascii0; while(*pdu) { uint8_t c=octet2bin(pdu); if(c) *ascii++ =c; //discard zero bytes of wide chars pdu+=2; } *ascii=0; } void swapchars(char* string) /* Swaps every second character, argument must have even length */ { char c; while((c=string[0])) { string[0]=string[1]; string[1]=c; string+=2; } } void decodepdu(char *ascii, char *phone, char *date, char *pdu0) { uint8_t i; char *pdu=pdu0; //skip SMS center info i=octet2bin(pdu); pdu+=(i+2)<<1; //decode sender phone number i=octet2bin(pdu); strncpy(phone,pdu+4,i); phone[i]=0; swapchars(phone); if(i&1) {++i; phone[i-1]=0;} pdu+=i+6; uint8_t dcs=octet2bin(pdu); pdu+=2; strncpy(date,pdu,12); date[12]=0; swapchars(date); pdu+=14; if(dcs & 12 ) pdu2wide(pdu,ascii,dcs); else pdu2ascii(pdu,ascii); } void ascii2pdu(char* ascii,char* pdu) { uint8_t *tmp,*tmp0; uint8_t nfree; char c; //message length bin2octet(pdu,strlen(ascii)); pdu+=2; //bit shifts, rewrite into ascii to save space nfree=0; tmp0=(uint8_t *)ascii; tmp=tmp0-1; while((c= *ascii++)) { //part of ascii which goes to current byte if(nfree>0) { *tmp |= c<<(8-nfree); } //part of ascii which goes to next byte if(nfree<7) { *++tmp = (c&0x7f) >> nfree; } nfree=(1+nfree)&7; } //print in hexa for (; tmp0<=tmp; ++tmp0, pdu+=2) bin2octet(pdu,*tmp0); pdu[0]=0; } /* make the PDU string. The destination variable pdu has to be big enough. */ void makepdu(char* message, char* nummer, char* pdu, uint8_t numtype) { uint8_t l; //header strcpy_P(pdu,PSTR("001100")); pdu+=6; //phone number l=strlen(nummer); bin2octet(pdu,l); pdu+=2; bin2octet(pdu,numtype); pdu+=2; strcpy(pdu,nummer); if (l&1) strcat_P(pdu,PSTR("F")); swapchars(pdu); //some other constant garbage strcat_P(pdu,PSTR("00F1A7")); //message length and text ascii2pdu(message,pdu+strlen(pdu)); } int read_sms(char *number, char *ascii, uint8_t delete, char *pdu_debug) { char pdu[MAXLIST]; char *p; uint8_t loc; strcpy_P(pdu,PSTR("AT+CMGL=4\r")); communicate(pdu,pdu+MAXPDU,7000); //do not wait for OK, it might be after too a long text, switch off watchdog if ((p=strstr(pdu,"+CMGL:"))) { p+=6; loc=atoi(p); p=strchr(p,'\n')+1; //terminate the PDU if several messages are contained char *q=strstr(p,"+CMGL:"); if(q) *q=0; if(pdu_debug) strncpy(pdu_debug,p,MAXPDU); char date[16]; decodepdu(ascii,number,date,p); } else return 1; //delete after it has been read if(delete) { delay_cs(80); strcpy_P(pdu,PSTR("AT\r")); communicate(pdu,pdu+MAXPDU,100); delay_cs(80); strcpy_P(pdu,PSTR("AT\r")); communicate(pdu,pdu+MAXPDU,100); strcpy_P(pdu,PSTR("AT+CMGD=")); itoa10(loc,pdu+strlen(pdu)); strcat_P(pdu,PSTR("\r")); communicate(pdu,pdu+MAXPDU,300); delay_cs(80); } return 0; } void dial(char *number) { char buf[BUFL]; uint8_t i; strcpy_P(buf,PSTR("AT+CBST=7,0,1\r")); //suitable for calling both gsm and fixed network lines communicate(buf,buf+BUFL,100); strcpy_P(buf,PSTR("ATD+")); strcat(buf,number); strcat_P(buf,PSTR("\r")); communicate(buf,buf+BUFL,6000); delay_cs(500); //wait another 5 seconds strcpy_P(buf,PSTR("+++ATH\r")); communicate(buf,buf+BUFL,100); strcpy_P(buf,PSTR("ATZ\r")); communicate(buf,buf+BUFL,100); } int send_sms(char *number, char *ascii0) //its argument ascii will be overwritten { char buf[BUFL], pdu[MAXPDU], ascii[MAXSMS]; uint8_t r; strncpy(ascii,ascii0,MAXSMS-1); ascii[MAXSMS-1]=0; makepdu(ascii,number,pdu,0x91); strcpy_P(buf,PSTR("AT+CMGS=")); itoa10((int)strlen(pdu)/2-1,buf+strlen(buf)); strcat_P(buf,PSTR("\r")); r=communicate(buf,buf+BUFL,350); { char ctrlz[2]={'Z'-'@',0}; strcat(pdu,ctrlz); } return communicate(pdu,pdu+MAXPDU,950); } void reset_termios(int f, speed_t baudrate) { struct termios devicetermios, ttytermios; if(tcgetattr(f,&devicetermios)) perror("cannot tcgetattr 1"); memcpy(&devicetermios0,&devicetermios,sizeof(struct termios)); cfsetispeed(&devicetermios,baudrate); cfsetospeed(&devicetermios,baudrate); devicetermios.c_iflag |= IGNBRK; devicetermios.c_iflag &= ~BRKINT; devicetermios.c_iflag &= ~ICRNL; devicetermios.c_iflag &= ~IMAXBEL; devicetermios.c_iflag &= ~IXON; devicetermios.c_iflag &= ~IXOFF; devicetermios.c_oflag &= ~OPOST; devicetermios.c_oflag &= ~ONLCR; devicetermios.c_cflag |= CLOCAL; devicetermios.c_cflag &= ~CRTSCTS; devicetermios.c_lflag &= ~ISIG; devicetermios.c_lflag &= ~ICANON; devicetermios.c_lflag &= ~IEXTEN; devicetermios.c_lflag &= ~ECHO; devicetermios.c_lflag &= ~ECHOE; devicetermios.c_lflag &= ~ECHOK; devicetermios.c_lflag &= ~ECHOCTL; devicetermios.c_lflag &= ~ECHOKE; if(tcsetattr(f,TCSANOW,&devicetermios)) perror("cannot tcsetattr 1"); } main(int argc, char **argv) { int interactive=0; char device[64]="/dev/rfcomm0"; char baud[16]="115200"; speed_t baudrate=B115200; int aoption=0; logfilepointer=stderr; char number[MAXNUMBER]; number[0]=0; while((--argc>0)&&((*++argv)[0]=='-')){ if (argc==0) usage(); switch ((*argv)[1]){ case 'D': ++argv; --argc; strcpy(device,*argv); break; case 'A': aoption=1; case 'B': strcpy(baud,(*argv)+2); if(!strcmp(baud,"2400")) baudrate=B2400; if(!strcmp(baud,"4800")) baudrate=B4800; if(!strcmp(baud,"9600")) baudrate=B9600; if(!strcmp(baud,"19200")) baudrate=B19200; if(!strcmp(baud,"38400")) baudrate=B38400; if(!strcmp(baud,"57600")) baudrate=B57600; if(!strcmp(baud,"115200")) baudrate=B115200; if(!strcmp(baud,"230400")) baudrate=B230400; if(baudrate==B0) xerror("unsupported baudrate"); break; case 'I': ++interactive; break; case 'H': usage(); break; default : xerror("unknown option,; use option -H for information"); } } if(argc > 1) xerror("wrong command line; use option -H for information"); if(argc==1) { if(argv[0][0]!='+') xerror("only phone numbers in international format with + accepted"); strncpy(number,argv[0]+1,MAXNUMBER); } int f=open(device,O_RDWR+O_SYNC+O_NOCTTY+O_EXCL,0); if(f<0) {perror("cannot open connection to device"); exit(10); } struct termios devicetermios, ttytermios; phone_fd = filehandle=f; if(tcgetattr(f,&devicetermios)) perror("cannot tcgetattr 1"); memcpy(&devicetermios0,&devicetermios,sizeof(struct termios)); cfsetispeed(&devicetermios,baudrate); cfsetospeed(&devicetermios,baudrate); devicetermios.c_iflag |= IGNBRK; devicetermios.c_iflag &= ~BRKINT; devicetermios.c_iflag &= ~ICRNL; devicetermios.c_iflag &= ~IMAXBEL; devicetermios.c_iflag &= ~IXON; devicetermios.c_iflag &= ~IXOFF; devicetermios.c_oflag &= ~OPOST; devicetermios.c_oflag &= ~ONLCR; devicetermios.c_cflag |= CLOCAL; devicetermios.c_cflag |= HUPCL; devicetermios.c_cflag &= ~CRTSCTS; devicetermios.c_lflag &= ~ISIG; devicetermios.c_lflag &= ~ICANON; devicetermios.c_lflag &= ~IEXTEN; devicetermios.c_lflag &= ~ECHO; devicetermios.c_lflag &= ~ECHOE; devicetermios.c_lflag &= ~ECHOK; devicetermios.c_lflag &= ~ECHOCTL; devicetermios.c_lflag &= ~ECHOKE; if(tcsetattr(f,TCSANOW,&devicetermios)) perror("cannot tcsetattr 1"); if(number[0]) { char text[MAXSMS]="",line[MAXSMS]; phone_reset(); while(fgets(line,MAXSMS-1,stdin)) { if(strlen(line)=2) ttytermios.c_lflag &= ~ISIG; else ttytermios.c_lflag |= ISIG; ttytermios.c_lflag &= ~ECHO; ttytermios.c_lflag &= ~ECHOE; ttytermios.c_lflag &= ~ECHOK; ttytermios.c_lflag &= ~ECHOKE; ttytermios.c_lflag &= ~ECHOCTL; if(tcsetattr(fileno(stdin),TCSANOW,&ttytermios)) perror("cannot tcsetattr 2"); atexit(termioscleanup); signal(SIGINT,handler); rwloop(f); } xerror("not implemented"); close(f); }