Reputation: 635
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