|
- /*******************************
- * Copyright (C) 2025.
- *
- * File Name: serialcommunicator.cpp
- * Description: 串口通信管理类实现文件
- * Others:
- * Version: 1.0
- * Author: lipengpeng
- * Date: 2025-7-23
- *******************************/
-
- #include "serialcommunicator.h"
-
- /**
- * @brief 构造函数
- * @param parent 父对象指针
- *
- * @details 初始化串口通信组件,包括:
- * 1. 创建串口对象
- * 2. 创建接收和重发定时器
- * 3. 设置默认超时参数
- * 4. 连接信号槽
- */
- SerialCommunicator::SerialCommunicator(QObject *parent) : QObject(parent)
- , serialPort_(new QSerialPort(this))
- , recvTimer_(new QTimer(this))
- , resendTimer_(new QTimer(this))
- , stationCheck_(new QTimer(this))
- , checkTimeOut_(new QTimer(this))
- , comCount_(0)
- , maxRetry_(3)
- , recvTimeout_(50)
- , resendTimeout_(1000)
- , test_(QByteArray::fromHex("010100010001AC0A"))
- {
- // 配置定时器(单次触发)
- recvTimer_->setSingleShot(true);
- resendTimer_->setSingleShot(true);
- stationCheck_->setSingleShot(true);
-
- // 连接信号槽
- connect(serialPort_, &QSerialPort::readyRead, this, &SerialCommunicator::onReadyRead);
- connect(recvTimer_, &QTimer::timeout, this, &SerialCommunicator::onRecvTimeout);
- connect(resendTimer_, &QTimer::timeout, this, &SerialCommunicator::onResendTimeout);
- connect(stationCheck_, QTimer::timeout, this, SerialCommunicator::stationCheck);
- connect(checkTimeOut_, QTimer::timeout, this, SerialCommunicator::checkTimeOut);
- connect(serialPort_, SIGNAL(error(QSerialPort::SerialPortError)),
- this, SLOT(handleSerialError(QSerialPort::SerialPortError)));
- }
-
- /**
- * @brief 析构函数
- * @note 确保串口关闭并释放资源
- */
- SerialCommunicator::~SerialCommunicator()
- {
- close();
- }
-
- /**
- * @brief 设置串口参数
- * @param portName 串口号(如"COM1")
- * @param baudRate 波特率(如9600)
- * @param dataBits 数据位
- * @param parity 校验位
- * @param stopBits 停止位
- *
- * @note 此方法应在打开串口前调用,设置值将在下次open()时生效
- */
- void SerialCommunicator::setPortParams(QString portName, qint32 baudRate,
- QSerialPort::DataBits dataBits,
- QSerialPort::Parity parity,
- QSerialPort::StopBits stopBits)
- {
- this->portName_ = portName;
- this->baudRate_ = baudRate;
- this->dataBits_ = dataBits;
- this->parity_ = parity;
- this->stopBits_ = stopBits;
- }
-
- /**
- * @brief 打开串口
- * @return true-打开成功, false-打开失败
- *
- * @details 执行流程:
- * 1. 关闭已打开的串口(如果存在)
- * 2. 配置串口参数(波特率、数据位等)
- * 3. 尝试打开串口
- * 4. 发送状态变化信号
- */
- bool SerialCommunicator::open()
- {
- // 关闭已打开的串口(如果存在)
- if (serialPort_->isOpen())
- {
- serialPort_->close();
- }
-
- // 配置串口参数
- serialPort_->setPortName(portName_);
- if (!serialPort_->setBaudRate(baudRate_))
- {
- emit statusChanged("波特率设置失败");
- return false;
- }
- if (!serialPort_->setDataBits(dataBits_))
- {
- emit statusChanged("数据位设置失败");
- return false;
- }
- if (!serialPort_->setParity(parity_))
- {
- emit statusChanged("校验位设置失败");
- return false;
- }
- if (!serialPort_->setStopBits(stopBits_))
- {
- emit statusChanged("停止位设置失败");
- return false;
- }
- serialPort_->setFlowControl(QSerialPort::NoFlowControl); // 无流控制
-
- // 打开串口
- if (serialPort_->open(QIODevice::ReadWrite))
- {
- emit statusChanged("串口连接成功");
- stationCheck_->start(1000);
- return true;
- }
- else
- {
- emit statusChanged("串口连接失败,请检查参数");
- return false;
- }
- }
-
- /**
- * @brief 关闭串口
- *
- * @details 执行流程:
- * 1. 关闭所有定时器
- * 2. 清理待处理数据
- * 3. 检查串口是否打开
- * 4. 关闭串口
- * 5. 发送状态变化信号
- */
- void SerialCommunicator::close()
- {
- if (serialPort_->isOpen())
- {
- //关闭所有定时器
- recvTimer_->stop();
- resendTimer_->stop();
- stationCheck_->stop();
- checkTimeOut_->stop();
- emit stationConnect(false);
-
- // 清除可能存在的待处理数据
- serialPort_->clear();
-
- // 关闭串口
- serialPort_->close();
- emit statusChanged("串口断开");
-
- comCount_ = 0; // 重置通信计数器
- }
- }
-
- /**
- * @brief 发送数据并启动超时重发机制
- * @param data 待发送的数据
- *
- * @details 执行流程:
- * 1. 检查串口是否打开
- * 2. 保存待发送数据(用于重发)
- * 3. 重置重发计数器
- * 4. 发送数据
- * 5. 启动重发超时定时器
- */
- void SerialCommunicator::sendData(const QByteArray &data)
- {
- if (!serialPort_->isOpen())
- {
- emit statusChanged("串口未打开,无法发送数据");
- return;
- }
- stationCheck_->stop();
- checkTimeOut_->stop();
- // 保存待重发数据,初始化计数器,启动重发定时器
- currentData_ = data;
- comCount_ = 0;
- serialPort_->write(data); // 发送数据
- emit statusChanged("发送报文:" + data.toHex().toUpper());
- resendTimer_->start(resendTimeout_); // 启动超时计时
- }
-
- /**
- * @brief 设置最大重发次数
- * @param count 最大重发次数
- */
- void SerialCommunicator::setMaxRetry(int count)
- {
- if (count < 0)
- {
- return;
- }
- maxRetry_ = count;
- }
-
- /**
- * @brief 设置接收完成超时时间
- * @param ms 超时时间(毫秒)
- *
- * @note 当串口持续ms毫秒无新数据时,认为接收完成
- */
- void SerialCommunicator::setRecvTimeout(int ms)
- {
- if (ms < 0)
- {
- return;
- }
- recvTimeout_ = ms;
- }
-
- /**
- * @brief 设置重发间隔时间
- * @param ms 重发间隔(毫秒)
- */
- void SerialCommunicator::setResendTimeout(int ms)
- {
- if (ms < 0)
- {
- return;
- }
- resendTimeout_ = ms;
- }
-
- /**
- * @brief 检查串口是否打开
- * @return true-已打开, false-已关闭
- */
- bool SerialCommunicator::isOpen() const
- {
- return serialPort_->isOpen();
- }
-
- void SerialCommunicator::setCheckMsg(QByteArray msg)
- {
- this->test_ = msg;
- }
-
- // ====================== 私有槽函数 ====================== //
-
- /**
- * @brief 串口数据到达处理
- *
- * @details 执行流程:
- * 1. 读取所有可用数据到接收缓冲区
- * 2. 重启接收超时定时器
- * 3. 定时器超时后将触发onRecvTimeout
- */
- void SerialCommunicator::onReadyRead()
- {
- if(checkTimeOut_->isActive() || stationCheck_->isActive())
- {
- checkTimeOut_->stop();
- serialPort_->clear();
- emit stationConnect(true);
- stationCheck_->start(1000);
- }
- else
- {
- // 读取数据到缓冲区
- recvBuffer_.append(serialPort_->readAll());
- //重置接收超时定时器
- recvTimer_->start(recvTimeout_);
- }
- }
-
- /**
- * @brief 数据接收完成处理
- *
- * @details 执行流程:
- * 1. 停止重发定时器(表示已收到响应)
- * 2. 发送dataReceived信号包含完整数据
- * 3. 清空接收缓冲区
- */
- void SerialCommunicator::onRecvTimeout()
- {
- // 接收完成,发送完整数据信号,停止重发定时器
- stationCheck_->start(1000);
- resendTimer_->stop();
- emit dataReceived(recvBuffer_);
- recvBuffer_.clear();
- }
-
- /**
- * @brief 重发超时处理
- *
- * @details 执行流程:
- * 1. 检查是否达到最大重发次数
- * 2. 未达到则重发数据并更新计数器
- * 3. 达到最大次数则触发超时信号
- */
- void SerialCommunicator::onResendTimeout()
- {
- if (comCount_ < maxRetry_)
- {
- // 重发数据,更新计数器
- serialPort_->write(currentData_);
- comCount_++;
- emit statusChanged(QString("通信超时,重新发送中(%1/%2)").arg(comCount_).arg(maxRetry_));
- resendTimer_->start(resendTimeout_); // 继续计时
- }
- else
- {
- // 达到最大重发次数,触发超时信号
- resendTimer_->stop();
- stationCheck_->start(1000);
- emit timeoutOccurred();
- }
- }
-
- /**
- * @brief 处理串口错误
- * @param error 串口错误代码
- *
- * @details 执行流程:
- * 1. 忽略无错误和超时错误(超时由其他机制处理)
- * 2. 根据错误代码生成错误描述
- * 3. 对于物理断开错误(ResourceError):
- * - 自动关闭串口
- * - 停止所有定时器
- * - 清空接收缓冲区
- * - 发送物理断开信号
- * 4. 其他错误只发送状态通知
- */
- void SerialCommunicator::handleSerialError(QSerialPort::SerialPortError error)
- {
- // 忽略无错误和超时错误(超时由其他机制处理)
- if (error == QSerialPort::NoError || error == QSerialPort::TimeoutError)
- {
- return;
- }
-
- QString errorMsg;
- switch (error)
- {
- case QSerialPort::DeviceNotFoundError:
- errorMsg = "设备未找到";
- break;
- case QSerialPort::PermissionError:
- errorMsg = "没有权限访问设备";
- break;
- case QSerialPort::OpenError:
- errorMsg = "设备已打开";
- break;
- case QSerialPort::ParityError:
- errorMsg = "奇偶校验错误";
- break;
- case QSerialPort::FramingError:
- errorMsg = "帧错误";
- break;
- case QSerialPort::BreakConditionError:
- errorMsg = "Break条件错误";
- break;
- case QSerialPort::WriteError:
- errorMsg = "写入错误";
- break;
- case QSerialPort::ReadError:
- errorMsg = "读取错误";
- break;
- case QSerialPort::ResourceError:
- errorMsg = "资源错误(设备已断开)";
- break;
- case QSerialPort::UnsupportedOperationError:
- errorMsg = "不支持的操作";
- break;
- case QSerialPort::UnknownError:
- errorMsg = "未知错误";
- break;
- case QSerialPort::NotOpenError:
- errorMsg = "设备未打开";
- break;
- default:
- errorMsg = "未定义错误";
- }
-
- // 物理断开处理(资源错误)
- if (error == QSerialPort::ResourceError)
- {
- // 自动关闭串口
- close();
-
- // 停止所有定时器
- recvTimer_->stop();
- resendTimer_->stop();
-
- // 清空缓冲区
- recvBuffer_.clear();
-
- // 通知上层
- emit statusChanged("物理连接断开: " + errorMsg);
- emit physicalDisconnected();
- }
- else
- {
- // 其他错误只通知不自动断开
- emit statusChanged("串口错误: " + errorMsg);
- }
-
- }
-
- void SerialCommunicator::stationCheck()
- {
- serialPort_->write(test_);
- checkTimeOut_->setSingleShot(true);
- checkTimeOut_->start(200);
- }
-
- void SerialCommunicator::checkTimeOut()
- {
- emit stationConnect(false);
- stationCheck_->start(1000);
- }
|