303 lines
7.0 KiB
C++
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))));
|
|
}
|