Search code examples
c++serial-portgsm

GSM modem response is received too late


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:


Solution

  • 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.