#include "SerialReader.h" #include #include #include #include #include #include #include #include #ifdef Q_OS_MACOS #include #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(bytesRead); m_buffer.append(data, static_cast(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(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)))); }