/******************************* * 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); }