/
e-face
/
SuperPuperTerminal
Обзор
Документация
Войти
/
e-face
/
SuperPuperTerminal
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
src/serial/serialconnection.cpp
590 строк
15 KB
e-face
First one
14 фев 2026, 19:38
14 фев 2026, 19:38
87f499b
Код
Авторство
О чём код?
/** * @file serialconnection.cpp * @brief Реализация класса SerialConnection */ #include "serialconnection.h" #include "serialconnectionsettingspage.h" #include <QTimer> #include <QDir> #include <QDebug> #ifdef Q_OS_WIN #include <windows.h> #else #include <dirent.h> #include <cstring> #endif //---------------------------------------------- /** * @brief Конструктор * @param settings Настройки подключения * @param parent Родительский виджет */ SerialConnection::SerialConnection(const QString& settings, QWidget* parent) : BaseConnection(parent) #ifdef Q_OS_WIN , _hPort(INVALID_HANDLE_VALUE) #else , _fd(-1) #endif , _readTimer(nullptr) { _settings = unpackSettings(settings); _title = _settings["name"].toString(); BaseConnection::clog(tr("%1 (%2): New connection, type = Serial, Port = %3, Baud = %4") .arg(title()) .arg(sessId()) .arg(_settings["portName"].toString()) .arg(_settings["baudRate"].toInt())); _readTimer = new QTimer(this); _readTimer->setInterval(100); connect(_readTimer, &QTimer::timeout, this, &SerialConnection::sltReadyRead); QTimer::singleShot(0, this, &SerialConnection::openPort); } //---------------------------------------------- /** * @brief Деструктор */ SerialConnection::~SerialConnection() { closePort(); BaseConnection::clog(tr("%1 (%2): Delete connection") .arg(title()) .arg(sessId())); } //---------------------------------------------- /** * @brief Открыть последовательный порт * @return true при успешном открытии */ bool SerialConnection::openPort() { #ifdef Q_OS_WIN QString portName = _settings["portName"].toString(); if (!portName.startsWith("\\\\.\\")) { if (!portName.startsWith("COM")) { portName = "\\\\.\\COM" + portName; } else { portName = "\\\\.\\" + portName; } } _hPort = CreateFileA(portName.toStdString().c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if (_hPort == INVALID_HANDLE_VALUE) { BaseConnection::clog(tr("%1 (%2): Failed to open port") .arg(title()) .arg(sessId())); return false; } COMMTIMEOUTS timeouts = {0}; timeouts.ReadIntervalTimeout = MAXDWORD; timeouts.ReadTotalTimeoutConstant = 50; timeouts.ReadTotalTimeoutMultiplier = 0; timeouts.WriteTotalTimeoutConstant = 50; timeouts.WriteTotalTimeoutMultiplier = 0; if (!SetCommTimeouts(_hPort, &timeouts)) { CloseHandle(_hPort); _hPort = INVALID_HANDLE_VALUE; return false; } if (!configurePort()) { closePort(); return false; } _readTimer->start(); BaseConnection::clog(tr("%1 (%2): Port opened successfully") .arg(title()) .arg(sessId())); return true; #else QString portName = _settings["portName"].toString(); if (!portName.startsWith("/")) { portName = "/dev/" + portName; } _fd = ::open(portName.toStdString().c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK); if (_fd < 0) { BaseConnection::clog(tr("%1 (%2): Failed to open port: %3") .arg(title()) .arg(sessId()) .arg(QString::fromLocal8Bit(strerror(errno)))); return false; } if (!configurePort()) { closePort(); return false; } _readTimer->start(); BaseConnection::clog(tr("%1 (%2): Port opened successfully") .arg(title()) .arg(sessId())); return true; #endif } //---------------------------------------------- /** * @brief Закрыть последовательный порт */ void SerialConnection::closePort() { #ifdef Q_OS_WIN if (_hPort != INVALID_HANDLE_VALUE) { CloseHandle(_hPort); _hPort = INVALID_HANDLE_VALUE; } #else if (_fd >= 0) { ::close(_fd); _fd = -1; } #endif if (_readTimer) { _readTimer->stop(); } } //---------------------------------------------- /** * @brief Настроить параметры порта * @return true при успехе */ bool SerialConnection::configurePort() { #ifdef Q_OS_WIN if (_hPort == INVALID_HANDLE_VALUE) return false; DCB dcbSerialParams = {0}; dcbSerialParams.DCBlength = sizeof(dcbSerialParams); if (!GetCommState(_hPort, &dcbSerialParams)) { return false; } int baudRate = _settings["baudRate"].toInt(); dcbSerialParams.BaudRate = baudRate; dcbSerialParams.ByteSize = (BYTE)_settings["dataBits"].toInt(); int parity = _settings["parity"].toInt(); switch (parity) { case 0: dcbSerialParams.Parity = NOPARITY; break; case 1: dcbSerialParams.Parity = EVENPARITY; break; case 2: dcbSerialParams.Parity = ODDPARITY; break; default: dcbSerialParams.Parity = NOPARITY; } int stopBits = _settings["stopBits"].toInt(); dcbSerialParams.StopBits = (stopBits == 2) ? TWOSTOPBITS : ONESTOPBIT; int flowControl = _settings["flowControl"].toInt(); if (flowControl == 1) { dcbSerialParams.fRtsControl = RTS_CONTROL_HANDSHAKE; dcbSerialParams.fOutxCtsFlow = TRUE; } else { dcbSerialParams.fRtsControl = RTS_CONTROL_ENABLE; dcbSerialParams.fOutxCtsFlow = FALSE; } if (!SetCommState(_hPort, &dcbSerialParams)) { return false; } PurgeComm(_hPort, PURGE_RXCLEAR | PURGE_TXCLEAR); return true; #else if (_fd < 0) return false; struct termios tty; memset(&tty, 0, sizeof(tty)); if (tcgetattr(_fd, &tty) != 0) { BaseConnection::clog(tr("%1 (%2): tcgetattr failed: %3") .arg(title()) .arg(sessId()) .arg(QString::fromLocal8Bit(strerror(errno)))); return false; } speed_t baudRate = B9600; int baud = _settings["baudRate"].toInt(); switch (baud) { case 50: baudRate = B50; break; case 75: baudRate = B75; break; case 110: baudRate = B110; break; case 134: baudRate = B134; break; case 150: baudRate = B150; break; case 200: baudRate = B200; break; case 300: baudRate = B300; break; case 600: baudRate = B600; break; case 1200: baudRate = B1200; break; case 1800: baudRate = B1800; break; case 2400: baudRate = B2400; break; case 4800: baudRate = B4800; break; case 9600: baudRate = B9600; break; case 19200: baudRate = B19200; break; case 38400: baudRate = B38400; break; case 57600: baudRate = B57600; break; case 115200: baudRate = B115200; break; case 230400: baudRate = B230400; break; case 460800: baudRate = B460800; break; case 921600: baudRate = B921600; break; case 2000000: baudRate = B2000000; break; default: baudRate = B9600; } cfsetospeed(&tty, baudRate); cfsetispeed(&tty, baudRate); tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; tty.c_iflag &= ~IGNBRK; tty.c_lflag = 0; tty.c_oflag = 0; tty.c_cc[VMIN] = 0; tty.c_cc[VTIME] = 5; int parity = _settings["parity"].toInt(); if (parity != 0) { tty.c_cflag |= PARENB; if (parity == 2) { tty.c_cflag |= PARODD; } } else { tty.c_cflag &= ~PARENB; } int stopBits = _settings["stopBits"].toInt(); if (stopBits == 2) { tty.c_cflag |= CSTOPB; } else { tty.c_cflag &= ~CSTOPB; } int flowControl = _settings["flowControl"].toInt(); if (flowControl == 1) { tty.c_cflag |= CRTSCTS; } else { tty.c_cflag &= ~CRTSCTS; } tty.c_iflag &= ~(IXON | IXOFF | IXANY); tty.c_cflag |= (CLOCAL | CREAD); tty.c_cflag |= HUPCL; tcflush(_fd, TCIOFLUSH); if (tcsetattr(_fd, TCSANOW, &tty) != 0) { BaseConnection::clog(tr("%1 (%2): tcsetattr failed: %3") .arg(title()) .arg(sessId()) .arg(QString::fromLocal8Bit(strerror(errno)))); return false; } return true; #endif } //---------------------------------------------- /** * @brief Проверить состояние подключения * @return 2 если подключено, 0 если нет */ int SerialConnection::isConnected() const { #ifdef Q_OS_WIN return (_hPort != INVALID_HANDLE_VALUE) ? 2 : 0; #else return (_fd >= 0) ? 2 : 0; #endif } //---------------------------------------------- /** * @brief Проверить и восстановить подключение */ void SerialConnection::shot() { if (isConnected() == 0) { ++_reconnect; if (_reconnect > 4) { if (openPort()) { _reconnect = 0; BaseConnection::clog(tr("%1 (%2): Reconnect") .arg(title()) .arg(sessId())); } } } else { _reconnect = 0; } } //---------------------------------------------- /** * @brief Получить текущие настройки * @return Строка с настройками */ QString SerialConnection::settings() const { return packSettings(_settings); } //---------------------------------------------- /** * @brief Получить тип подключения * @return "Serial" */ QString SerialConnection::connectionType() const { return "Serial"; } //---------------------------------------------- /** * @brief Установить данные для отправки * @param data Данные */ void SerialConnection::setData(const QString& data) { BaseConnection::setBasicData(data); } //---------------------------------------------- /** * @brief Получить данные для отправки * @return Данные */ QString SerialConnection::data() const { return BaseConnection::basicData(); } //---------------------------------------------- /** * @brief Получить настройки по умолчанию * @return Строка с настройками */ QString SerialConnection::defaultSettings() { ConnSettings s; s["type"] = "Serial"; s["name"] = QObject::tr("COM-порт"); #ifdef Q_OS_WIN s["portName"] = "COM1"; #else s["portName"] = "ttyUSB0"; #endif s["baudRate"] = 9600; s["dataBits"] = 8; s["parity"] = 0; s["stopBits"] = 1; s["flowControl"] = 0; return packSettings(s); } //---------------------------------------------- /** * @brief Создать страницу настроек * @return Виджет страницы настроек */ QWidget* SerialConnection::createSettingsPage() { return new SerialConnectionSettingsPage(); } //---------------------------------------------- /** * @brief Получить список доступных портов * @return Список имён портов */ QStringList SerialConnection::availablePorts() { QStringList ports; #ifdef Q_OS_WIN for (int i = 1; i <= 256; ++i) { QString portName = QString("COM%1").arg(i); HANDLE hPort = CreateFileA(QString("\\\\.\\%1").arg(portName).toStdString().c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if (hPort != INVALID_HANDLE_VALUE) { COMMTIMEOUTS timeouts = {0}; timeouts.ReadIntervalTimeout = MAXDWORD; timeouts.ReadTotalTimeoutConstant = 50; timeouts.ReadTotalTimeoutMultiplier = 0; SetCommTimeouts(hPort, &timeouts); CloseHandle(hPort); ports << portName; } } #else QDir dev("/dev"); QStringList entries = dev.entryList(QDir::System); for (const QString& entry : entries) { if (entry.startsWith("tty") || entry.startsWith("cu.")) { ports << entry; } } #endif return ports; } //---------------------------------------------- /** * @brief Обработка принятых данных * @param data Данные */ void SerialConnection::read(QByteArray data) { BaseConnection::read(data); } //---------------------------------------------- /** * @brief Отправить данные * @param data Данные для отправки */ void SerialConnection::write(QByteArray data) { #ifdef Q_OS_WIN if (_hPort == INVALID_HANDLE_VALUE) { BaseConnection::clog(tr("%1 (%2): Port not open") .arg(title()) .arg(sessId())); return; } DWORD written; if (!WriteFile(_hPort, data.constData(), data.size(), &written, NULL)) { BaseConnection::clog(tr("%1 (%2): Write error") .arg(title()) .arg(sessId())); } else if ((DWORD)data.size() != written) { BaseConnection::clog(tr("%1 (%2): Partial write") .arg(title()) .arg(sessId())); } else { BaseConnection::write(data); } #else if (_fd < 0) { BaseConnection::clog(tr("%1 (%2): Port not open") .arg(title()) .arg(sessId())); return; } ssize_t written = ::write(_fd, data.constData(), data.size()); if (written != (ssize_t)data.size()) { BaseConnection::clog(tr("%1 (%2): Write error: %3") .arg(title()) .arg(sessId()) .arg(QString::fromLocal8Bit(strerror(errno)))); } else { BaseConnection::write(data); } #endif } //---------------------------------------------- /** * @brief Обработчик готовности данных для чтения */ void SerialConnection::sltReadyRead() { #ifdef Q_OS_WIN if (_hPort == INVALID_HANDLE_VALUE) return; char buf[4096]; DWORD bytesRead = 0; if (ReadFile(_hPort, buf, sizeof(buf), &bytesRead, NULL)) { if (bytesRead > 0) { read(QByteArray(buf, bytesRead)); } } #else if (_fd < 0) return; char buf[4096]; ssize_t n = ::read(_fd, buf, sizeof(buf)); if (n > 0) { read(QByteArray(buf, n)); } #endif } //---------------------------------------------- /** * @brief Обработчик ошибки */ void SerialConnection::sltError() { #ifdef Q_OS_WIN if (_hPort != INVALID_HANDLE_VALUE) { BaseConnection::clog(tr("%1 (%2): Serial error") .arg(title()) .arg(sessId())); closePort(); } #else if (_fd >= 0) { BaseConnection::clog(tr("%1 (%2): Serial error") .arg(title()) .arg(sessId())); closePort(); } #endif } //----------------------------------------------