Mathieu Gauquelin
Mathieu Gauquelin

Reputation: 635

C++/Python RS485 communication through USB, can we block already opened port?

Context

I am working on a personal project on which I have to connect a Python script (3.7) to a QML application (Qt 5.2, C++17), all running on a Linux RHEL8 distribution.

Hardware connections

I have two computers with 2 USB ports, and 2 homemade cables with USB/RS485 converters in each sides.

What works

I can communicate from PC1 to PC2 with first cable, and in another direction with second cable.

What is wrong

I want to be able to detect the port is already in use if I run twice my software to avoid the second opening and return an error message. In C++ first then Python.

RS485 configuration

Question

I tried a lot of solutions which didn't work for now (ex: using ioctl with different flag, ...). Is it at least possible to block the opening for such configuration (RS485 communication on USB port, and on RS485 ports at the end)?

Minimalist code

In each side I have a thread for the reception, and another one to send data.

Python:

import threading, struct
from .GenericMessage import crc_16
import serial


class SerialManager:

    # Constructor
    def __init__(self):
        # Update parameter for the RS485 connection
        self.serialPortName_read = "/dev/ttyUSB1"
        self.serialPortName_send = "/dev/ttyUSB0"
        self.serialPort_read = serial.Serial()
        self.serialPort_send = serial.Serial()

        # Create sockets and start listening thread
        self.createRS485connection()
        self.receiverThread = threading.Thread(target=self.listeningThread)
        self.receiverThread.daemon = True
        self.receiverThread.start()


    def createRS485connection(self):
        # Configure sending device
        self.serialPort_send.port       = self.serialPortName_send  # Set device name
        self.serialPort_send.baudrate   = 115200                    # Set baud rate at 115 200 baud
        self.serialPort_send.bytesize   = serial.EIGHTBITS          # Set Number of data bits for a byte = 8
        self.serialPort_send.parity     = serial.PARITY_EVEN        # Set parity bit to even
        self.serialPort_send.stopbits   = serial.STOPBITS_ONE       # Set stop bit to 1

        # Open sending device
        self.serialPort_send.open()

        # Configure receiving device
        self.serialPort_read.port       = self.serialPortName_read  # Set device name
        self.serialPort_read.baudrate   = 115200                    # Set baud rate at 115 200 baud
        self.serialPort_read.bytesize   = serial.EIGHTBITS          # Set Number of data bits for a byte = 8
        self.serialPort_read.parity     = serial.PARITY_EVEN        # Set parity bit to even
        self.serialPort_read.stopbits   = serial.STOPBITS_ONE       # Set stop bit to 1
        self.serialPort_read.timeout    = 0.0001                    # Set timeout to read to 0.0001 second

        # Open reading device
        self.serialPort_read.open()

        # Reset all previous data received
        self.serialPort_read.reset_input_buffer()
        return


    def sendMessage(self, message):
        bytesSent = self.serialPort_send.write(message)
        if bytesSent < 0:
            print("Message not sending")
        elif bytesSent != len(message):
            print("Message not sent completely")
        else:
            return

    def listeningThread(self):
        # Initialize variables
        dataReceived = b''

        while self.isListening:

            # Wait for data to read
            if self.serialPort_read.in_waiting > 0:
                # Read byte one by one
                for i in range(self.serialPort_read.in_waiting):
                    dataReceived += self.serialPort_read.read(1)

                    # Function to manage character one by one and reconstruct the original message to deal with

C++

#include "../inc/SerialManager.h"

SerialManager::SerialManager()
{
    m_RS485_serialPortName_sending = "/dev/ttyUSB0";
    m_RS485_serialPortName_reading = "/dev/ttyUSB1";

    // Configure RS485 devices
    configureRS485sendingSide();
    configureRS485readingSide();

    // Create a new thread for listening and one for sending
    m_threadListeningServer = std::thread(&SerialManager::listenWhileLoop, this);
    m_threadListeningServer.detach();
    m_threadSendingServer = std::thread(&SerialManager::sendWhileLoop, this);
    m_threadSendingServer.detach();
}

SerialManager::~SerialManager()
{
    // Close the RS485 devices when finished
    if (close(m_RS485_serialPort_reading) < 0)
        qDebug() << "Reading device not closed properly: " << m_RS485_serialPortName_reading;
    if (close(m_RS485_serialPort_sending) < 0)
        qDebug() << "Reading device not closed properly: " << m_RS485_serialPortName_sending;
}

bool SerialManager::configureRS485readingSide()
{
    // Open the serial port in read only mode
    m_RS485_serialPort_reading = open(m_RS485_serialPortName_reading.toStdString().c_str(), O_RDONLY);

    // Create new termios struct, we call it 'tty' for convention
    struct termios tty;

    // Read in existing settings, and handle any error
    if(tcgetattr(m_RS485_serialPort_reading, &tty) != 0)
    {
        std::cout << "Error in reading side from tcgetattr: " + std::string(std::strerror(errno));
        throw std::runtime_error("Error in reading side from tcgetattr: " + std::string(std::strerror(errno)));
    }

    // Set control modes
    tty.c_cflag |= PARENB;     // 1. Set parity bit, enabling parity
    tty.c_cflag &= ~CSTOPB;    // 2. Clear stop field, only one stop bit used in communication
    tty.c_cflag &= ~CSIZE;     // 3.a. Clear all bits that set the data size
    tty.c_cflag |= CS8;        // 3.b. 8 bits per byte
    tty.c_cflag &= ~CRTSCTS;   // 4. Disable RTS/CTS hardware flow control (most common)
    tty.c_cflag |= CREAD | CLOCAL;     // 5. Turn on READ & ignore ctrl lines (CLOCAL = 1)

    // Set local modes
    tty.c_lflag &= ~ICANON;    // 1. Disable canonical mode
    tty.c_lflag &= ~ECHO;      // 2. Disable echo
    tty.c_lflag &= ~ECHOE;     // 3. Disable erasure
    tty.c_lflag &= ~ECHONL;    // 4. Disable new-line echo
    tty.c_lflag &= ~ISIG;      // 5. Disable interpretation of INTR, QUIT and SUSP characters

    // Set input modes
    tty.c_iflag &= ~(IXON | IXOFF | IXANY);                          // 1. Turn off software flow control
    tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // 2. Disable any special handling of received bytes

    // Set output modes
    tty.c_oflag &= ~OPOST;     // 1. Prevent special interpretation of output bytes (e.g. newline chars)
    tty.c_oflag &= ~ONLCR;     // 2. Prevent conversion of newline to carriage return/line feed

    // Set read configuration: blocking read of any number of chars with a maximum timeout (given by VTIME)
    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 baud rate to be 115200 baud
    cfsetispeed(&tty, B115200);

    // Save tty settings, also checking for error
    if (tcsetattr(m_RS485_serialPort_reading, TCSANOW, &tty) != 0)
    {
        std::cout << "Error in reading side from tcgetattr: " + std::string(std::strerror(errno));
        throw std::runtime_error("Error in reading side from tcsetattr: " + std::string(std::strerror(errno)));
    }

    qDebug() << "Reading side correctly configured";

    return true;
}

bool SerialManager::configureRS485sendingSide()
{
    // Open the serial port in write only mode
    m_RS485_serialPort_sending = open(m_RS485_serialPortName_sending.toStdString().c_str(), O_WRONLY);

    // Create new termios struct, we call it 'tty' for convention
    struct termios tty;

    // Read in existing settings, and handle any error
    if(tcgetattr(m_RS485_serialPort_sending, &tty) != 0)
    {
        std::cout << "Error in sending side from tcgetattr: " + std::string(std::strerror(errno));
        throw std::runtime_error("Error in sending side from tcgetattr: " + std::string(std::strerror(errno)));
    }

    // Set control modes
    tty.c_cflag |= PARENB;     // 1. Set parity bit, enabling parity
    tty.c_cflag &= ~CSTOPB;    // 2. Clear stop field, only one stop bit used in communication
    tty.c_cflag &= ~CSIZE;     // 3.a. Clear all bits that set the data size
    tty.c_cflag |= CS8;        // 3.b. 8 bits per byte
//    tty.c_cflag &= ~CRTSCTS;   // 4. Disable RTS/CTS hardware flow control (most common)
    tty.c_cflag |= CLOCAL;     // 5. Do not turn on READ & ignore ctrl lines (CLOCAL = 1)

    // Set local modes
    tty.c_lflag &= ~ICANON;    // 1. Disable canonical mode
    tty.c_lflag &= ~ECHO;      // 2. Disable echo
    tty.c_lflag &= ~ECHOE;     // 3. Disable erasure
    tty.c_lflag &= ~ECHONL;    // 4. Disable new-line echo
    tty.c_lflag &= ~ISIG;      // 5. Disable interpretation of INTR, QUIT and SUSP characters

    // Set input modes
    tty.c_iflag &= ~(IXON | IXOFF | IXANY);                          // 1. Turn off software flow control
    tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // 2. Disable any special handling of received bytes

    // Set output modes
    tty.c_oflag &= ~OPOST;     // 1. Prevent special interpretation of output bytes (e.g. newline chars)
    tty.c_oflag &= ~ONLCR;     // 2. Prevent conversion of newline to carriage return/line feed

    // Set read configuration: blocking read of any number of chars with a maximum timeout (given by VTIME)
    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 out baud rate to be 115200 baud
    cfsetospeed(&tty, B115200);

    // Save tty settings, also checking for error
    if (tcsetattr(m_RS485_serialPort_sending, TCSANOW, &tty) != 0)
    {
        std::cout << "Error in sending side from tcgetattr: " + std::string(std::strerror(errno));
        throw std::runtime_error("Error in sending side from tcsetattr: " + std::string(std::strerror(errno)));
    }

    qDebug() << "Writing side correctly configured";

    return true;
}


bool SerialManager::sendResponse(const char* msg, int msgSize)
{
    int bytesSent = write(m_RS485_serialPort_sending, msg, msgSize);

    if (bytesSent < 0)
    {
        std::cout << "Error sending message: " + std::string(std::strerror(errno));
        throw std::runtime_error("Error sending message: " + std::string(std::strerror(errno)));
    }
    else if (static_cast<size_t>(bytesSent) != msgSize)
    {
        std::cout << "Incomplete message sent: " + std::to_string(bytesSent) + " bytes sent instead of " + std::to_string(msgSize) + " bytes";
        throw std::runtime_error("Incomplete message sent: " + std::to_string(bytesSent) + " bytes sent instead of " + std::to_string(msgSize) + " bytes");
    }

    return true;
}


void SerialManager::listenWhileLoop()
{
    pthread_setname_np(pthread_self(), "listenWhileLoop");

    // First wait the end of the initialisation
    while (!m_swIsInitialized)
    {
        // Wait for 100s not to override processor
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }

    char buf[1024];
    while (m_softwareIsRunning)
    {
        // Wait the software to be initialized
        if (m_swIsInitialized)
        {
            // Wait until receive a message from a socket
            int sizeDataReceived = read(m_RS485_serialPort_reading, &buf, sizeof(buf));

            if (sizeDataReceived > 0)
            {
                switchTypeMessageReceived(buf, sizeDataReceived);
            }
            //clean buffer
            memset(&buf[0], 0, sizeof(buf));
        }
    }
}

void SerialManager::switchTypeMessageReceived(char *buffer, int sizeDataReceived)
{
    // Manage data received
}

Upvotes: 0

Views: 44

Answers (0)

Related Questions