Search code examples
cbashserial-portechocat

Cannot mimic the action of `echo` command in C code


I have written a couple of bash script files that communicate via two serial ports. One script can be thought of as a receiver and the other as a transmitter. The receiving script reads and displays data line by line until it reads the character sequence <break>. It then stops listening, and sends a character sequence of either one or two back to the transmitter. The script is thus:

#!/bin/bash

# Read and reply bash script.

exec 3<> /dev/ttyS4
while true; do
    #cat -v /dev/ttyS4 | while read -r input; do
    while read -r -u 3 input; do
        if [ "$input" = "<break>" ]
        then
            echo "break command received."
            break
        else
            echo -e "${input}"
        fi
    done 

    echo "Sending selection"
    if [ "$selection" = "one" ]
    then
        selection="two"
    else
        selection="one"
    fi
    echo "$selection" >&3

done

The transmitter script transmits some character data and then waits for the reply of one or two from the receiver script above:

exec 4<> /dev/ttyS0
selection="one"
while true; do
    echo "************************************" > /dev/ttyS0
    echo "       Selection: ${selection}" > /dev/ttyS0
    echo "************************************" > /dev/ttyS0
    echo "<break>" > /dev/ttyS0
    read -r -t 3 -u 4 input
    if [ -z "$input" ]
    then
        echo "Response from remote timed out."
    elif [ "$input" = "one" ]
    then
        selection=$input
    elif [ "$input" = "two" ]
    then
        selection=$input
        colour=$RED
    else
        echo "Unknown selection: $input"
    fi

    sleep 1 
done

The above two scripts work fine and the receiver script correctly identifies the <break> character sequence.

I wish to replace the 'transmitter' script with a small C program however I find that when I send the character sequence <break> the receiver script this time does NOT identify is as the 'end-of-transmission', is simple echos <break> to stdout, and NOT break command received. I have tried several things such as adding escape characters but there is obviously something different about the way Bash echo works and how I send the data in my C code:

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>

#define TRUE 1
#define FALSE 0

int main(int argc, char *argv[]) {
    int selected_tool = DEFAULT_TOOL_SELECTION;
    FILE *ser2_fd_write, *ser2_fdrw;
    struct termios tios, tios_w;        // ser 2 termios structure
    int ser2_fd, ser2_fdw;
    char read_buffer[BUFFER_SIZE];
    struct timespec tdelay;

    bzero(&tdelay, sizeof(tdelay));
    tdelay.tv_sec = 1;
    tdelay.tv_nsec = 5000;

    if ((ser2_fd = open("/dev/ttyS0", O_RDWR)) == -1){
        printf("Unable to open ttyS0 as read-write only.\n");
        return EXIT_FAILURE;
    }

    bzero(&tios, sizeof(tios));
    cfsetispeed(&tios, B38400);
    cfsetospeed(&tios, B38400);


    tios.c_cflag = B38400 | CS8 | CLOCAL | CREAD | CRTSCTS;
    tios.c_iflag = IGNPAR;
    tios.c_oflag = 0;
    tios.c_lflag = ICANON; //0
    tios.c_cc[VTIME] = 0;
    tios.c_cc[VMIN] = 10;

    tcflush(ser2_fd, TCIFLUSH);
    if (tcsetattr(ser2_fd, TCSANOW, &tios) == -1){
        printf("Could not set ser 2 attributes.\n");
        return -1;
    }

    if ((ser2_fdrw = fdopen(ser2_fd, "awr")) == NULL){
        printf("Unable to open file descriptor.\n");
        return EXIT_FAILURE;
    }

while(1){

    push_to_ser2(ser2_fdrw, selected_tool);
    /*
     * This is where sending <break> differs from echo
     */

    //fputs("break", ser2_fdrw);
    fprintf(ser2_fdrw, "break\r\n");
    //fprintf(ser2_fdrw, "\r\n");
    //write(ser2_fd,"break\r\n", 9);
    fflush(ser2_fdrw);
    int c = 0;

    nanosleep(&tdelay, NULL);
    tcflush(ser2_fd, TCIOFLUSH);
    tcdrain(ser2_fd);
    fcntl(ser2_fd, F_SETFL, 0);

    if ( (c = read(ser2_fd, read_buffer, BUFFER_SIZE)) > 0){
        read_buffer[c] = 0;
        if (strcmp(read_buffer, "one\r\n") == 0){
            selected_tool = 1;
        } else if (strcmp(read_buffer, "two\r\n") == 0){
            selected_tool = 2;
        }
    }else{

    }
}
    return EXIT_SUCCESS;
}


/*
 * Convenience function to push data to ser 2
 * *c_data      pointer to the card data
 * *t_data      pointer to the tool data
 */
void push_to_ser2(FILE * fd, int tool){

    fprintf(fd, "**********************************************************\n");
    fprintf(fd, "*                                                        *\n");
    fprintf(fd, "*                     Tool %d Data                       *\n", tool);
    fprintf(fd, "*                                                        *\n");
    fprintf(fd, "**********************************************************\n");


    fprintf(fd,"\r\n");
    fflush(fd);
}

I've tried various alterations to the termios struct too but it makes no difference. Any ideas?


Solution

  • There are several issues with your code that should be corrected:

    • Instead of bzero(&tios, sizeof(tios)) the code should be calling tcgetattr() to properly initialize the structure. This can be a serious issue for canonical input, as the existing code will have zeroed out all of the control code specifications instead of having proper definitions.
    • Instead of direct assignments, the code should be performing bit-wise operations (in order to preserve existing settings). See Setting Terminal Modes Properly.
    • You're specifying non-canonical output with canonical input, which is an atypical combination. Seems like you want canonical mode for both input and output.
    • VMIN and VTIME are only meaningful for non-canonical input, and should not be specified for canonical input (as it clobbers the VEOF and VEOL character specifications).
    • The read(ser2_fd,...) is silently ignoring errors.
    • Instead of using fdopen() and fprintf(), just use write(ser2_fd, ...) to simplify the code and overhead. Downsides are that you'll have to specify the byte counts and use sprintf() to perform integer to string conversion.

    See Serial Programming Guide for POSIX Operating Systems.