Files
location_plotter/src/SerialReader.cpp
T
onur 049f6301e8
Ubuntu build / build-and-test (ubuntu-22.04) (push) Has been cancelled
Ubuntu build / build-and-test (ubuntu-24.04) (push) Has been cancelled
Add macOS USB UART support
2026-07-18 12:03:38 +03:00

303 lines
7.0 KiB
C++

#include "SerialReader.h"
#include <QSocketNotifier>
#include <QTimer>
#include <cerrno>
#include <cstring>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#ifdef Q_OS_MACOS
#include <IOKit/serial/ioss.h>
#endif
namespace {
constexpr qsizetype MaxBytesPerReadCycle = 64 * 1024;
bool applyBaudRate(termios &options, int baudRate)
{
speed_t speed = 0;
switch (baudRate) {
case 1200:
speed = B1200;
break;
case 2400:
speed = B2400;
break;
case 4800:
speed = B4800;
break;
case 9600:
speed = B9600;
break;
case 19200:
speed = B19200;
break;
case 38400:
speed = B38400;
break;
case 57600:
speed = B57600;
break;
case 115200:
speed = B115200;
break;
#ifdef B230400
case 230400:
speed = B230400;
break;
#endif
#ifdef B460800
case 460800:
speed = B460800;
break;
#endif
#ifdef B921600
case 921600:
speed = B921600;
break;
#endif
default:
#ifdef Q_OS_MACOS
// IOSSIOSPEED below applies the exact requested rate. A valid termios
// placeholder is still required before that ioctl can be issued.
speed = B9600;
break;
#else
return false;
#endif
}
return ::cfsetispeed(&options, speed) == 0
&& ::cfsetospeed(&options, speed) == 0;
}
} // namespace
SerialReader::SerialReader(QObject *parent)
: QObject(parent)
{
m_pollTimer = new QTimer(this);
m_pollTimer->setInterval(20);
connect(m_pollTimer, &QTimer::timeout, this, &SerialReader::handleReadyRead);
m_partialLineTimer = new QTimer(this);
m_partialLineTimer->setSingleShot(true);
m_partialLineTimer->setInterval(250);
connect(m_partialLineTimer, &QTimer::timeout, this, &SerialReader::flushPartialLine);
}
SerialReader::~SerialReader()
{
closePort();
}
bool SerialReader::isOpen() const
{
return m_fd >= 0;
}
QString SerialReader::portName() const
{
return m_portName;
}
bool SerialReader::openPort(const QString &path, int baudRate)
{
closePort();
const QByteArray localPath = path.toLocal8Bit();
m_fd = ::open(localPath.constData(), O_RDWR | O_NOCTTY | O_NONBLOCK);
if (m_fd < 0) {
emitSystemError(QStringLiteral("Port acilamadi"));
return false;
}
m_portName = path;
if (!configurePort(baudRate)) {
closePort();
return false;
}
setModemControlLines();
m_notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this);
connect(m_notifier, &QSocketNotifier::activated, this, &SerialReader::handleReadyRead);
m_pollTimer->start();
emit statusChanged(QStringLiteral("%1 dinleniyor (%2 baud)").arg(path).arg(baudRate));
return true;
}
void SerialReader::closePort()
{
if (m_notifier != nullptr) {
m_notifier->setEnabled(false);
m_notifier->deleteLater();
m_notifier = nullptr;
}
m_pollTimer->stop();
m_partialLineTimer->stop();
if (m_fd >= 0) {
::close(m_fd);
m_fd = -1;
}
m_buffer.clear();
if (!m_portName.isEmpty()) {
emit statusChanged(QStringLiteral("%1 kapatildi").arg(m_portName));
}
m_portName.clear();
}
void SerialReader::handleReadyRead()
{
if (m_fd < 0) {
return;
}
char data[4096];
qsizetype bytesProcessed = 0;
while (true) {
const ssize_t bytesRead = ::read(m_fd, data, sizeof(data));
if (bytesRead > 0) {
bytesProcessed += static_cast<qsizetype>(bytesRead);
m_buffer.append(data, static_cast<qsizetype>(bytesRead));
processBuffer();
if (m_buffer.size() > 64 * 1024) {
m_buffer.clear();
emit errorOccurred(QStringLiteral("Satir sonu gelmeyen seri veri tamponu temizlendi"));
}
if (bytesProcessed >= MaxBytesPerReadCycle) {
QTimer::singleShot(0, this, &SerialReader::handleReadyRead);
return;
}
continue;
}
if (bytesRead == 0 || errno == EAGAIN || errno == EWOULDBLOCK) {
return;
}
emitSystemError(QStringLiteral("Seri port okuma hatasi"));
closePort();
return;
}
}
void SerialReader::flushPartialLine()
{
if (m_buffer.isEmpty()) {
return;
}
QByteArray line = m_buffer;
m_buffer.clear();
while (line.endsWith('\r') || line.endsWith('\n')) {
line.chop(1);
}
if (!line.isEmpty()) {
emit lineReceived(QString::fromUtf8(line));
}
}
bool SerialReader::configurePort(int baudRate)
{
termios options {};
if (::tcgetattr(m_fd, &options) != 0) {
emitSystemError(QStringLiteral("Port ayarlari okunamadi"));
return false;
}
::cfmakeraw(&options);
if (!applyBaudRate(options, baudRate)) {
emit errorOccurred(QStringLiteral("Bu sistemde desteklenmeyen baud rate: %1").arg(baudRate));
return false;
}
options.c_cflag |= CLOCAL | CREAD;
options.c_cflag &= ~HUPCL;
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
options.c_cc[VMIN] = 0;
options.c_cc[VTIME] = 0;
if (::tcsetattr(m_fd, TCSANOW, &options) != 0) {
emitSystemError(QStringLiteral("Port ayarlari uygulanamadi"));
return false;
}
#ifdef Q_OS_MACOS
speed_t requestedSpeed = static_cast<speed_t>(baudRate);
if (::ioctl(m_fd, IOSSIOSPEED, &requestedSpeed) != 0) {
emitSystemError(QStringLiteral("macOS baud rate uygulanamadi"));
return false;
}
#endif
::tcflush(m_fd, TCIFLUSH);
return true;
}
void SerialReader::processBuffer()
{
bool emittedLine = false;
while (true) {
qsizetype delimiterIndex = -1;
for (qsizetype i = 0; i < m_buffer.size(); ++i) {
if (m_buffer.at(i) == '\n' || m_buffer.at(i) == '\r') {
delimiterIndex = i;
break;
}
}
if (delimiterIndex < 0) {
break;
}
QByteArray line = m_buffer.left(delimiterIndex);
qsizetype removeCount = delimiterIndex + 1;
while (removeCount < m_buffer.size()
&& (m_buffer.at(removeCount) == '\n' || m_buffer.at(removeCount) == '\r')) {
++removeCount;
}
m_buffer.remove(0, removeCount);
if (!line.isEmpty()) {
emittedLine = true;
emit lineReceived(QString::fromUtf8(line));
}
}
if (!m_buffer.isEmpty()) {
m_partialLineTimer->start();
} else if (emittedLine) {
m_partialLineTimer->stop();
}
}
void SerialReader::setModemControlLines()
{
int status = 0;
if (::ioctl(m_fd, TIOCMGET, &status) != 0) {
return;
}
status |= TIOCM_DTR;
status |= TIOCM_RTS;
::ioctl(m_fd, TIOCMSET, &status);
}
void SerialReader::emitSystemError(const QString &prefix)
{
emit errorOccurred(QStringLiteral("%1: %2").arg(prefix, QString::fromLocal8Bit(std::strerror(errno))));
}