0

I have a gsm modem that setting is:

  1. BaudRate 9600
  2. Databit 8
  3. No Parity
  4. Stopbit 1
  5. No Flow control

And my OS is Ubuntu. After sending AT command I wrote sleep(2) seconds to receive the answer. But why is the response too late? And how can I solve it?
this is my code to read data:

string PDUSMS::readstring(int fd)
{
    int n = 0,
    spot = 0;
    char buf = '\0';
    /* Whole response*/
    char response[1024];
    memset(response, '\0', sizeof response);
    n=read(fd,&response,1024);
//---------------------------
    if (n < 0) {
        std::cout << "Error reading: " << strerror(errno) << std::endl;
    }
    else if (n == 0) {
        std::cout << "Read nothing!" << std::endl;
    }
    else {
        std::cout << "Response: " << response << std::endl;
    }
    string str(response);
    return str;
//---------------------------------------------------
}

How to make a fast read, in order to read all of the response string?

This is all my code:

int fd; /* File descriptor for the port */
/*
    * 'open_port()' - Open serial port 1.
    *
    * Returns the file descriptor on success or -1 on error.
    */

    int openport(void)
    {       
        fd=open("/dev/ttyS1", O_RDWR|O_NOCTTY|O_NDELAY);
        if (fd==-1)
        {
            perror("open_port: unable to open port\n");
            return -1;
        }
        else
        {
            printf("open_port: succesfully open port /dev/ttyUSB0\n");
            fcntl(fd,F_SETFL,0);
            return 1;
        }
    }
   //========================================================================

   void closeport(void)
   {
       close(fd);
   }

   void configport(void)
   {   
       struct termios tty;
       struct termios tty_old;
       memset (&tty, 0, sizeof tty);

       /* Error Handling */
       if ( tcgetattr ( fd, &tty ) != 0 ) {
          std::cout << "Error " << errno << " from tcgetattr: " << strerror(errno) << std::endl;
       }

       /* Save old tty parameters */
       tty_old = tty;

       /* Set Baud Rate */
       cfsetospeed (&tty, (speed_t)B9600);
       cfsetispeed (&tty, (speed_t)B9600);

       /* Setting other Port Stuff */
       tty.c_cflag     &=  ~PARENB;            // Make 8n1
       tty.c_cflag     &=  ~CSTOPB;
       tty.c_cflag     &=  ~CSIZE;
       tty.c_cflag     |=  CS8;

       tty.c_cflag     &=  ~CRTSCTS;           // no flow control
       tty.c_cc[VMIN]   =0;//  1;                  // read doesn't block
       tty.c_cc[VTIME]  = 2;// 5;                  // 0.5 seconds read timeout
       tty.c_cflag     |=  CREAD | CLOCAL;     // turn on READ & ignore ctrl lines

       /* Make raw */
       cfmakeraw(&tty);

       /* Flush Port, then applies attributes */
       tcflush( fd, TCIFLUSH );
       if ( tcsetattr ( fd, TCSANOW, &tty ) != 0) {
          std::cout << "Error " << errno << " from tcsetattr" << std::endl;
       }
   }
//------------------------------------------------------------  
string PDUSMS::SendandReciveData(string s,int fd)
{    
  int i;
  string o,e,t;

  try
  {
      cout<<" we had sent:"<<s<<"\n";
      SendString(s,fd);    

        sleep(1);
        o=readstring(fd);
//    for(int i=0;i<3;i++)
//     if (o.find(s)!=-1)
//     {
//         sleep(1.5);
//         o=readstring(fd);
//     }
    cout<< " we got :"<<o<<"\n";
    i = StateStr(o, s); //remove source command from the beging of string
    if (i >= 0)   //-becasause the command return back to us
      o = copy(o, s.length(), o.length() - s.length()); //return command to caller

  }
  catch(const std::exception&)
  {
    o = " ";
  }
  return o;

}

void PDUSMS::SendString(string s,int fd)
{
    char buf[255];
    strcpy(buf,s.c_str());
    write(fd, buf, s.length());
//    usleep(500);
}

string PDUSMS::readstring(int fd)
{
    int n = 0,
        spot = 0;
    char buf = '\0';

    /* Whole response*/
    char response[1024];
    memset(response, '\0', sizeof response);


    n=read(fd,&response,1024);
//---------------------------
    if (n < 0) {
        std::cout << "Error reading: " << strerror(errno) << std::endl;
    }
    else if (n == 0) {
        std::cout << "Read nothing!" << std::endl;
    }
    else {
        std::cout << "Response: " << response << std::endl;
    }
    string str(response);
    return str;
//---------------------------------------------------
}
bool PDUSMS::SendSMS(int fd,string Num,string Text,int MR,int CMR,int SMS_PART,int sms_id,int &sms_index,bool Delivery,bool MagicSMS,bool &Deliverd)
{

  string c, o, id;
  int i, l, Curr_PART, R_MR;
  string SNum, SDate, STime, PDU_Data, SMSC_Num, RTime, RDate, num1;
  ReceievedMessageKind PDU_Data_Type;
  bool sent, deliv;

  string Temp;
    MagicSMS=false;
    string result=" ";
    result=SendandReciveData("AT+CSMP=49,167,0,0\r",fd);
    result=SendandReciveData("AT+CNMI=2,2,0,1,0\r",fd);

    c = "AT+CMGS="; // at commmand for s} SMS
    o = EncodePDU(Num, Text, MR, CMR, SMS_PART, sms_id, Delivery, MagicSMS);

    c = c + IntToStr(o.length()/ 2 - 1); //Adding length of Pdu to at command
    c += "\r"; //adding <CR> to at comm &&
    Temp = SendandReciveData(c,fd); //send at command to phone
    o += (char)26; //add <CTRL-Z> to the PDU Text

    Temp = SendandReciveData(o,fd); //S} Text To The Phone

}

this is my output without sleep :

open_port: succesfully open port /dev/ttyUSB0 we had sent:AT Response: AT we got :AT ATAT we had sent:AT Response:

we got :

we had sent:AT Response: O we got :O OO we had sent:AT Response: K we got :K KK we had sent:AT Response:

we got :

we had sent:AT Response: A we got :A AA we had sent:AT Response: T we got :T TT we had sent:AT Response: we got : we had sent:AT Response: A we got :A AA we had sent:AT Response: T we got :T TT we had sent:AT Aesponse: Awe got : A we had sent:AT Response: T we got :T TT we had sent:AT ATsponse: ATe got : we had sent:AT Response: A we got :A AA we had sent:AT Response: T we got :T we had sent:AT Response: OK

we got : OK

OK

OK we had sent:AT+CSMP=49,167,0,0 Response: we got : we had sent:AT+CNMI=2,2,0,1,0 Response:

we got :

we had sent:AT+CMGS=20 Response: OK

OK we got :OK

OK we had sent:0031010c918939881454270000AA06f3701bce2e03 Response: we got : Response:

O Response: K A ATsponse: T Aesponse: AT Response: T ATsponse: ATsponse: T Response: A ATsponse: T ATsponse: Response: AT+CS Response: MP=49 Response: ,167, Aesponse: 0,0 Response: T+CN Response: MI=2, Response: 2,0,1 ATsponse: ,0 Response: +CMGS Response: =20 Response: 00310 Response: 10c91893 Response: 98 Response: 81454 Response: 2700 Response: 00AA0 Response: 6f370 Response: 1bce2 Response: e03 Response: OK Response:

Response: OK Response:

OK Response:

Response: OK

Response: OK

Response: OK Response:

OK Response:

Response: OK

Response: OK

Response: OK Response:

OK Response:

Response: OK

Response: OK

Response:

Response: Response: +CUSD Response: : 0," Response: Hazin Response: e SM Response: S: 2 Response: 0 Response: 9 Ria Response: l. Et Response: ebar Response: asl Response: i Response: : 13623 Rial. Shegeftzad Response: eh sh Response: avid Response: ! Response: Response: Ba s Response: homar Response: e g Response: i Response: ry c Response: o Response: de*44 Response: 44*1# Response: tarh Response: e v Response: i Response: je kh Response: od r Response: a Response: dar Response: y Response: aft k Response: oni Response: d Response: ",15 Response:

Response: +CM Response: G Response: S: 21 Response: 8

O Response: K Response: Response:

Response:

Response: +CUSD: Response: 2

Response:

Response: +CDS: Response: 25

Response: 0 Response: 006D Response: A Response: 0C9 Response: 1 Response: 8939 Response: 8 Response: 8145 Response: 4 Response: 2751 Response: 1 Response: 16131 Response: 016 Response: 3 Response: 4151 Response: 1 Response: 1613 Response: 1 Response: 0183 Response: 4 Response: 100 Response:

Roberto Caboni
  • 7,252
  • 10
  • 25
  • 39
H.Ghassami
  • 1,012
  • 2
  • 21
  • 42
  • *"Too late"* implies a reference point or a deadline. So what is that? Do you simply mean you think the response is *slow*? Why the 2 seconds of sleep? What happens if there is no sleep at all (assuming you use a blocking read)? – sawdust Nov 16 '15 at 07:45
  • Which AT command are you sending? What timeout do you have on your serial port? Where is the sleep? (As it doesn't appear in your posted code) – Mats Petersson Nov 16 '15 at 07:59
  • @sawdust . yes , sorry too slow. when i comment the sleep(2). when i send "AT+CMGS=.." to my method .First write it , then sleep then read the port and come back and i send another AT command. but it responses to first method and so on. and respone has delay.. and what is blocking read? – H.Ghassami Nov 16 '15 at 08:25
  • @Mats Petersson : these AT command to send PDU sms : "AT+CSMP" , "AT+CNMI" , "AT+CMGS" and pdu format.. this is my timeout port : tty.c_cc[VMIN] =0;// 1; // read doesn't block tty.c_cc[VTIME] = 2;//2 second. and the sleep is after write method befor read method. – H.Ghassami Nov 16 '15 at 08:29
  • You need to show more of your code, such as the open and initialization of the serial port, and the full write and read sequence. What is *"read the port and come back"*? **What is the read returning?** Looks like you may be setup for raw reads but expect to read lines; that's simply won't work. *"what is blocking read? "* -- See http://stackoverflow.com/questions/25996171/linux-blocking-vs-non-blocking-serial-read – sawdust Nov 16 '15 at 08:33
  • Have you tried doing the steps in a terminal program to see how long it takes and what happens? Maybe it actually DOES take more than 2 seconds (or 4 seconds)? – Mats Petersson Nov 16 '15 at 08:40
  • @Mats Petersson : no. in terminal it answer low of 0.5 second. but in my program some times the sleep(2) in not enought to get all response from port. – H.Ghassami Nov 16 '15 at 08:50
  • Ah, so the problem is that you are missing PART of the reply? I would say that then selbie's solution is probably a good plan - read until you get no newline for X amount of time. – Mats Petersson Nov 16 '15 at 08:52
  • Try changing `tty.c_cc[VMIN] = ` from 0 to 1. (And do not use the sleep!) You could still get a short read, so canonical mode might be preferable (read would then return a full line). – sawdust Nov 16 '15 at 09:04
  • Since you're willing to sleep for 2 seconds (and to avoid a short read from the modem), also increase `tty.c_cc[VTIME] = ` from 2 to 10. – sawdust Nov 16 '15 at 09:21
  • " I would say that then selbie's solution is probably a good plan" ---> he was deleted the post because of down vote. I miss that :( @sawdust : thanks i test it – H.Ghassami Nov 16 '15 at 09:26
  • @sawdust : I changed the VMIN and VTIME and delete sleep. but it does not changed. see the output in my question :( – H.Ghassami Nov 16 '15 at 09:45
  • Fix the bugs in readstring(): `n=read(fd,&response,1024)` to `n = read(fd, response, sizeof(response) - 1)` Bug #1: you're passing the address of the array address. Bug #2: the count is too long to preserve at least one null terminator. Why does the output show multiple "responses" without a preceding "we had sent"? – sawdust Nov 16 '15 at 19:31
  • @sawdust : thanks. Why does the output show multiple "responses" without a preceding "we had sent"? -> this is my exactly problem.My question is Why my GSM modem response is slow? – H.Ghassami Nov 17 '15 at 05:52
  • 1
    You have the serial port setup for non-canonical (aka raw) mode. A raw read is terminated by byte count and/or timing, which is unreliable for reading a line. But you actually need to read a line (which is canonical input) from the modem. Maybe read 2 lines because of the apparent command echo. Either setup for canonical input instead of raw, OR in readstring() put the read() in a loop that concatenates the input until a line terminator is received. Either turn off echo at the modem, or be prepared to read 2 lines per each command written. – sawdust Nov 18 '15 at 00:33
  • @sawdust : really thanks for your help.I have questions: 1- How to read the port like you said? and i canceled echo by ATE0 – H.Ghassami Nov 21 '15 at 08:08

2 Answers2

1

The output seems to indicate that there is command echo. Either turn off echo at the modem, or be prepared to read 2 lines per each command written.


You have the serial port setup for non-canonical (aka raw) mode. A raw read is terminated by byte count and/or timing, which is unreliable for reading a line. While the modem is in command mode, the modem will send its responses as lines.

So your program needs to read a line (which is canonical input) from the modem. Either (a) put the read() in a loop that concatenates the input until a line terminator is received, OR (b) setup for canonical input instead of raw. #Raw mode

In order to reliably read lines using non-canonical mode, the program should cope with the worst-case scenario of a line-terminator received in the middle of a returned buffer (rather than the trivial case of the last character received). To handle this a static buffer must be maintained between read syscalls to hold the partially received line, and to preserve the input after a line-terminator for the "next" line.

static char response[1024] = {0};
static int offset = 0;

string PDUSMS::readline(int fd)
{
    int n;
    char line[1024];
    char *nlp;

    while ((nlp = strpbrk(&response[offset], "\n\r")) == NULL) {
        n = read(fd, &response[offset], sizeof(response) - offset - 1);
        if (n < 0) {
            std::cout << "Error reading: " << strerror(errno) << std::endl;
            continue;
        }
        offset += n;
        response[offset] = '\0';
        if (offset >= sizeof(response) - 1) {
            nlp = &response[offset - 1];
            break;
        }
    }
    std::cout << "Response: " << response << std::endl;

    /* extract a line from the buffer */
    strncpy(line, response, nlp - response + 1);
    line[nlp - response + 1] = '\0';
    /* move remnant string to beginning */
    strcpy(response, nlp + 1);
    offset = strlen(response);

    string str(line);
    return str;
}

Note: code is untested, and is essentially C. I don't know C++.

#Canonical mode According to the Linux man page for termios(3)

In canonical mode:

  • Input is made available line by line. An input line is available when one of the line delimiters is typed (NL, EOL, EOL2; or EOF at the start of line). Except in the case of EOF, the line delimiter is included in the buffer returned by read(2).
  • Line editing is enabled (ERASE, KILL; and if the IEXTEN flag is set: WERASE, REPRINT, LNEXT). A read(2) returns at most one line of input; if the read(2) requested fewer bytes than are available in the current line of input, then only as many bytes as requested are read, and the remaining characters will be available for a future read(2).

To configure the serial port for canonical mode for a modem in command mode (rather than general-purpose terminal input), in your configport() delete three statements (with their comments):

    tty.c_cc[VMIN]   =0;//  1;                  // read doesn't block
    tty.c_cc[VTIME]  = 2;// 5;                  // 0.5 seconds read timeout

   /* Make raw */
   cfmakeraw(&tty);

(Be sure to keep the CREAD | CLOCAL settings.)
And insert new statements:

    tty.c_iflag |= ICRNL | IGNBRK;
    tty.c_iflag &= ~(IXON | IXOFF | IXANY | INLCR);

    tty.c_lflag |= ICANON | ISIG  | IEXTEN;
    tty.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | ECHOKE);

The read() call in your readstring() will then return a complete line of input (including the '\n' character). If the modem terminates its line with both a '\n' and '\r', then beware that this configuration will introduce a blank line (because each '\r' will be converted to a '\n').

Note that canonical mode may be inappropriate when your program switches the modem out of command mode into transparent mode. If the data is not pure ASCII text but contains binary values, then the program should switch the port to raw mode when the modem switches mode.


For reference guides, see Serial Programming Guide for POSIX Operating Systems
and Setting Terminal Modes Properly.

sawdust
  • 16,103
  • 3
  • 40
  • 50
0

Some advice regarding AT commands in general, useful to know before writing a single line of code:

  1. Modems, by default, have echo enabled. Take it into account when you design your application, especially if you decide to read line by line instead of implementing a normal read which timeout.

    In order to disable echo provide 'ATE0" command to the modem

  2. AT+CMGS has the following behavior:

    • The host sends 'AT+CMGS= '
    • Modem sends prompt char ('>') immediately
    • The host sends data either PDU or text terminating with CTRL+Z char
    • !! Some time depending on the network !!
    • OK

So, don't assume to receive OK 'immediately'. It could even take several tens of seconds!

  1. You spend some effort to send SMS in PDU mode, but also TEXT mode can be used:
    • AT+CMGF=1 (in order to switch to text mode)
    • AT+CMGS=
      '>' text-to-be-sent
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Roberto Caboni
  • 7,252
  • 10
  • 25
  • 39