Search code examples
cserial-portgsmat-command

How to wait for byte to be written to serial GSM modem?


Thanks in advance for the help.

Using the following sample Canonical Mode Linux Serial Port, I start writing a little API in Cto send an AT command and receive the response via serial port. I've no problem reading the response (used non blocking read with a poll) and no problem discovering the "at command" enabled device.

The problem I'm facing is with the write function. Most of the commands work (the smallest command like AT, ATI, ATI+CIMI etc). Sometimes a command like send SMS fails. I think the problem is the speed of the write (quicker than serial).

All the problems DO NOT occur if I set a timer between a write and the next write.

The following is the code

int serial_write(int fd, char * command){
size_t len = strlen(command);
int wlen = write(fd, command, len);

if (wlen != len) {
    return -1;
}
usleep(80*1000L);
if ( tcdrain(fd) != 0){
    return -2;
}
return 0;
}

int open_tty(char *portname){
int fd;
/*Aperta NON bloccante, con la poll che aspetta 1 secondo*/
fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC | O_NONBLOCK);
if (fd < 0) {
    printf("Error opening %s: %s\n", portname, strerror(errno));
    return -1;
}
/*baudrate 115200, 8 bits, no parity, 1 stop bit */
if (set_interface_attribs(fd, B115200) < 0 ){
    printf("Error set_interface_attribs: %s\n", strerror(errno));
    return -1;
}
return fd;
}

int set_interface_attribs(int fd, int speed){
struct termios tty;

if (tcgetattr(fd, &tty) < 0) {
    printf("Error from tcgetattr: %s\n", strerror(errno));
    return -1;
}

if ( cfsetospeed(&tty, (speed_t)speed) < 0 ){
    printf("Error from cfsetospeed: %s\n", strerror(errno));
    return -1;
}

if ( cfsetispeed(&tty, (speed_t)speed) < 0 ){
        printf("Error from cfsetispeed: %s\n", strerror(errno));
        return -1;
    }

tty.c_cflag |= CLOCAL | CREAD;
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8;         /* 8-bit characters */
tty.c_cflag &= ~PARENB;     /* no parity bit */
tty.c_cflag &= ~CSTOPB;     /* only need 1 stop bit */
tty.c_cflag &= ~CRTSCTS;    /* no hardware flowcontrol */

tty.c_lflag |= ICANON | ISIG;  /* canonical input */
tty.c_lflag &= ~(ECHO | ECHOE | ECHONL | IEXTEN);

tty.c_iflag &= ~IGNCR;  /* preserve carriage return */
tty.c_iflag &= ~INPCK;
tty.c_iflag &= ~(INLCR | ICRNL | IUCLC | IMAXBEL);
tty.c_iflag &= ~(IXON | IXOFF | IXANY);   /* no SW flowcontrol */

tty.c_oflag &= ~OPOST;

tty.c_cc[VEOL] = 0;
tty.c_cc[VEOL2] = 0;
tty.c_cc[VEOF] = 0x04;
tty.c_cc[VTIME] = 10;
tty.c_cc[VMIN] = 0;

if (tcsetattr(fd, TCSANOW, &tty) != 0) {
    printf("Error from tcsetattr: %s\n", strerror(errno));
    return -1;
}
return 0;
}

int read_response(int fd, char ** res){
int count=1; /* contatore realloc 1 per lo \0*/
tcdrain(fd);    /* waits until all of the data that has been written has been sent */
struct pollfd fds[1];
fds[0].fd = fd;
fds[0].events = POLLIN ;

do {
    unsigned char buf[MAXBUF];
    unsigned char *p;
    int rdlen;

    int n = poll( fds, 1, 1000);
    if (n>0){

        rdlen = read(fd, buf, sizeof(buf) - 1);
        if (rdlen > 0) {
            buf[rdlen] = 0;
            for (p = buf; rdlen-- > 0; p++) {
                if (*p < ' ')
                    *p = '\0';   /* replace any control chars */
            }
            if ( (strcmp((char *)buf, "") != 0) || (buf[0] == '^') ){
                count += (strlen((char *)buf)+1);  /* 2 per ; e ' ' */
                *res = realloc (*res, count);
                strncat(*res, (char *)buf, strlen((char *)buf));
                strcat(*res, ";");
            }

            if (strcmp((char *)buf, ATCMD_OK) == 0){
                return 0;
            }
            if (strcmp((char *)buf, ATCMD_ERROR) == 0){
                return -1;
            }

        } else if (rdlen < 0) {
            return -2;
        } else {  /* rdlen == 0 */
            return -3;
        }
    } else {
        return -4;
    }
    /* repeat read */
} while (1);
}

int send_sms(int fd, char *tel, char *text){
int wlen = 0;
char *res = malloc(sizeof(char*));
char at_send[strlen(ATCMD_CMGS) + strlen(tel) + 3]; //3=2apici+"\0"

strcpy(at_send, ATCMD_CMGS);
strcat(at_send, DL_QUOTE);
strcat(at_send, tel);
strcat(at_send, DL_QUOTE);

printf("Setting to sms text mode... ");

if ( (wlen = serial_write(fd, ATCMD_CMGF)) < 0 ){
        printf("Error from write: %d, %d\n", wlen, errno);
    }

if ( (wlen = serial_write(fd, C_R)) < 0 ){
        printf("Error from write: %d, %d\n", wlen, errno);
    }

if (read_response(fd, &res) < 0 ) {
    printf("FAIL\n");
}
else {
    printf("OK, RES: %s\n",res);
}

free(res);

printf("Sending SMS...");

if ( (wlen = serial_write(fd, at_send)) < 0 ){
        printf("Error from write: %d, %d\n", wlen, errno);
    }
if ( (wlen = serial_write(fd, C_R)) < 0 ){
        printf("Error from write: %d, %d\n", wlen, errno);
    }

if ( (wlen = serial_write(fd, text)) < 0 ){
        printf("Error from write: %d, %d\n", wlen, errno);
    }

if ( (wlen = serial_write(fd, CTRL_Z)) < 0 ){
        printf("Error from write: %d, %d\n", wlen, errno);
    }

if (read_response(fd, &res) < 0 ) {
    printf("FAIL\n");
    free(res);
    return -1;
}
else {
    printf("OK, RES: %s\n",res);
    free(res);
    return 0;
}
}

These are the incriminated functions. You can see, in the serial_write(), I'm using usleep() and all works correctly. Removing the usleep() causes problems (also if there's a tcdrain).

All kinds of help will be appreciated.

Thanks.


Solution

  • Sometimes a command like send SMS fails.

    That is not a helpful or detailed description of the problem. Simply stating that there is a problem and then expecting a solution is unreasonable.

    All the problems DO NOT occur if I set a timer between a write and the next write.

    That is an indication that your program is not properly waiting for a response from the modem before it transmits a new command/message. Receiving the response (rather than waiting for transmission to complete, i.e. calling tcdrain(), and/or delaying for an arbitrary time interval, e.g. calling usleep()) is the proper indication that the modem is now ready to receive.

    The commands that you are not having an issue are characterized as a basic command & response dialog. A one-line message is transmitted, and in short order a one-line message is received as a response.

    But sending a SMS message using the CMGS command does not follow that simple dialog.
    Yet your program tries to force that one-message->one-response construct anyway (resulting is apparently unreliable results).
    According to the GSM Technical Specification, the CMGS command can/should be handled as a write/read exchange of

    [w]command -> [r]prompt_response -> [w]text -> [r]prompt_response ...  
       [w]text -> [r]prompt_response -> [w]text + ^Z -> [r]2-line response 
    

    where prompt_response is specified as

    a four character sequence  <CR><LF><greater_than><space>
    

    Note that this response is ill-suited for a canonical read (i.e. the conventional line termination characters are at the start instead of the end of this sequence).
    Also note that every carriage return character in the transmitted message text will generate the transmission of a prompt_response by the modem.


    Given the complexity of the CMGS command, the transmission of multiple lines by your program, and then expecting to handle just one response is prone to unreliable results.
    I see other issues with your code, but none as serious as this mishandling of the CMGS command.
    This "answer" will only point out this major flaw in your program (i.e. offer "help") rather than provide a solution and/or rewrite.


    In summary:

    How to wait for byte to be written to serial GSM modem?

    Your program should wait until it has read the response to the previous transmission before it sends the (next) message/command. The bytes within that message can be sent without any delays.
    Refer to the GSM specification as what generates responses.