Sharing serial ports

To share one serial port among a number of programs. Use socat to create some virtual ports to which the software are going to connect and then use the software below to organize the data flow.

Socat command to create interconnected virtual ports:

socat -d -d pty,raw,link=/home/user/serialThreeA,echo=0 pty,raw,link=/home/user/serialThreeB,echo=0

Source code of the data flow software:

// C library headers
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// Linux headers
#include <fcntl.h> // Contains file controls like O_RDWR
#include <errno.h> // Error integer and strerror() function
#include <termios.h> // Contains POSIX terminal control definitions
#include <unistd.h> // write(), read(), close()
#include <stdbool.h>
#include <pthread.h>

bool open_serial( char* serialport, int* serial_port)
{
    if (serialport == NULL)
        return 1;

    (*serial_port) = open( serialport, O_RDWR);

    // Create new termios struct, we call it 'tty' for convention
    struct termios tty;
    // Read in existing settings, and handle any error
    if(tcgetattr((*serial_port), &tty) != 0) {
        printf("Error %i from tcgetattr: %s\n", errno, strerror(errno));
        return 1;
    }
    tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity (most common)
    tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication (most common)
    tty.c_cflag &= ~CSIZE; // Clear all bits that set the data size 
    tty.c_cflag |= CS8; // 8 bits per byte (most common)
    tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control (most common)
    tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1)

    tty.c_lflag &= ~ICANON;
    tty.c_lflag &= ~ECHO; // Disable echo
    tty.c_lflag &= ~ECHOE; // Disable erasure
    tty.c_lflag &= ~ECHONL; // Disable new-line echo
    tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP
    tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl
    tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // Disable any special handling of received bytes

    tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars)
    tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed
    // tty.c_oflag &= ~OXTABS; // Prevent conversion of tabs to spaces (NOT PRESENT ON LINUX)
    // tty.c_oflag &= ~ONOEOT; // Prevent removal of C-d chars (0x004) in output (NOT PRESENT ON LINUX)
    tty.c_cc[VTIME] = 10;    // Wait for up to 1s (10 deciseconds), returning as soon as any data is received.
    tty.c_cc[VMIN] = 0;

    // Set in/out baud rate to be 9600
    cfsetispeed(&tty, B9600);
    cfsetospeed(&tty, B9600);

    // Save tty settings, also checking for error
    if (tcsetattr((*serial_port), TCSANOW, &tty) != 0) {
        printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));
        return 1;
    }
    return 0;
}

int serial_port_one, serial_port_two, serial_port_three;

void *thread_one(void *vargp)
{
    while (true){
        char read_buf [256];
        int num_bytes = read(serial_port_one, &read_buf, sizeof(read_buf));
        if (num_bytes > 0){
            write(serial_port_two, read_buf, num_bytes);
            write(serial_port_three, read_buf, num_bytes);
        }
    }
    return NULL;
}

void *thread_two(void *vargp)
{
    while (true){
        char read_buf [256];
        int num_bytes = read(serial_port_two, &read_buf, sizeof(read_buf));
        if (num_bytes > 0){
            write(serial_port_one, read_buf, num_bytes);
        }
    }
    return NULL;
}

void *thread_three(void *vargp)
{
    while (true){
        char read_buf [256];
        int num_bytes = read(serial_port_three, &read_buf, sizeof(read_buf));
        if (num_bytes > 0){
            write(serial_port_one, read_buf, num_bytes);
        }
    }
    return NULL;
}

int main(int argc, char* argv[]) {
    pthread_t threadone, threadtwo, threadthree;
  // Open the serial port. Change device path as needed (currently set to an standard FTDI USB-UART cable type device)
  //int serial_port_one;
    if (open_serial(argv[1], &serial_port_one) != EXIT_SUCCESS)
        return 1;

    if (open_serial(argv[2], &serial_port_two) != EXIT_SUCCESS)
        return 1;
    
    if (open_serial(argv[3], &serial_port_three) != EXIT_SUCCESS)
        return 1;

    pthread_create(&threadone, NULL, thread_one, NULL);
    pthread_create(&threadtwo, NULL, thread_two, NULL);
    pthread_create(&threadthree, NULL, thread_three, NULL);
    pthread_join(threadone, NULL);
    pthread_join(threadtwo, NULL);
    pthread_join(threadthree, NULL);
    
    
    /*// Write to serial port
    unsigned char msg[] = { 'H', 'e', 'l', 'l', 'o', '\r' };
    write(serial_port_one, msg, sizeof(msg));

    // Allocate memory for read buffer, set size according to your needs
    char read_buf [256];

    // Normally you wouldn't do this memset() call, but since we will just receive
    // ASCII data for this example, we'll set everything to 0 so we can
    // call printf() easily.
    memset(&read_buf, '\0', sizeof(read_buf));

    // Read bytes. The behaviour of read() (e.g. does it block?,
    // how long does it block for?) depends on the configuration
    // settings above, specifically VMIN and VTIME
    int num_bytes = read(serial_port_one, &read_buf, sizeof(read_buf));

    // n is the number of bytes read. n may be 0 if no bytes were received, and can also be -1 to signal an error.
    if (num_bytes < 0) {
        printf("Error reading: %s", strerror(errno));
        return 1;
    }

    // Here we assume we received ASCII data, but you might be sending raw bytes (in that case, don't try and
    // print it to the screen like this!)
    printf("Read %i bytes. Received message: %s", num_bytes, read_buf);

    printf("\r\n");*/
    close(serial_port_one);
    return 0; // success
};